From 8191f8fb00c78578755a35952fc62557889e1ecf Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Mon, 23 Jul 2018 09:34:56 +0200 Subject: [PATCH 001/113] Updated README and paper references (#245) --- README.rst | 117 +++++++++++++++++++++++++++++++- docs/images/braket_notation.svg | 85 +++++++++++++++++++++++ docs/index.rst | 4 +- examples/README.rst | 2 +- 4 files changed, 202 insertions(+), 6 deletions(-) create mode 100644 docs/images/braket_notation.svg diff --git a/README.rst b/README.rst index 0a31fb186..614f23ad4 100755 --- a/README.rst +++ b/README.rst @@ -32,6 +32,114 @@ This allows users to - export quantum programs as circuits (using TikZ) - get resource estimates +Examples +-------- + +**First quantum program** + +.. 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 a quantum register with 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))) # converting a qubit to int or bool gives access to the measurement result + + +ProjectQ features a lean syntax which is close to the mathematical notation used in quantum physics. For example, a rotation of a qubit around the x-axis is usually specified as: + +.. image:: docs/images/braket_notation.svg + :alt: Rx(theta)|qubit> + :width: 100px + +The same statement in ProjectQ's syntax is: + +.. code-block:: python + + Rx(theta) | qubit + +The **|**-operator separates the specification of the gate operation (left-hand side) from the quantum bits to which the operation is applied (right-hand side). + +**Changing the compiler and using a resource counter as a back-end** + +Instead of simulating a quantum program, one can use our resource counter (as a back-end) to determine how many operations it would take on a future quantum computer with a given architecture. Suppose the qubits are arranged on a linear chain and the architecture supports any single-qubit gate as well as the two-qubit CNOT and Swap operations: + +.. code-block:: python + + from projectq import MainEngine + from projectq.backends import ResourceCounter + from projectq.ops import QFT + from projectq.setups import linear + + compiler_engines = linear.get_engine_list(num_qubits=16, + one_qubit_gates='any', + two_qubit_gates=(CNOT, Swap)) + resource_counter = ResourceCounter() + eng = MainEngine(backend=resource_counter, engine_list=compiler_engines) + qureg = eng.allocate_qureg(16) + QFT | qureg + eng.flush() + + print(resource_counter) + + # This will output, among other information, + # how many operations are needed to perform + # this quantum fourier transform (QFT), i.e., + # Gate class counts: + # AllocateQubitGate : 16 + # CXGate : 240 + # HGate : 16 + # R : 120 + # Rz : 240 + # SwapGate : 262 + + +**Running a quantum program on IBM's QE chips** + +To run a program on the IBM Quantum Experience chips, all one has to do is choose the `IBMBackend` and the corresponding compiler: + +.. code-block:: python + + compiler_engines = projectq.setups.ibm16.get_engine_list() + eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024, + verbose=False, device='ibmqx5'), + 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 `__. + + +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 + + eng = MainEngine() + wavefunction = eng.allocate_qureg(2) + # Specify a Hamiltonian in terms of Pauli operators: + hamiltonian = QubitOperator("X0 X1") + 0.5 * QubitOperator("Y0 Y1") + # Apply exp(-i * Hamiltonian * time) (without Trotter error) + TimeEvolution(time=1, hamiltonian=hamiltonian) | wavefunction + # Measure the expection value using the simulator shortcut: + eng.flush() + value = eng.backend.get_expectation_value(hamiltonian, wavefunction) + + # Last operation in any program should be measuring all qubits + All(Measure) | qureg + eng.flush() + + + Getting started --------------- @@ -54,10 +162,11 @@ When using ProjectQ for research projects, please cite - Damian S. Steiger, Thomas Häner, and Matthias Troyer "ProjectQ: An Open Source Software Framework for Quantum Computing" - `[arxiv:1612.08091] `__ + `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" - `[arxiv:1604.01401] `__ + "A Software Methodology for Compiling Quantum Programs" `Quantum Sci. Technol. 3 (2018) 020501 `__ + (published on `arXiv `__ on 5 Apr 2016) Authors ------- @@ -70,6 +179,8 @@ in the group of `Prof. Dr. Matthias Troyer `__ at ETH Zurich. +ProjectQ is constantly growing and `many other people `__ have already contributed to it in the meantime. + License ------- diff --git a/docs/images/braket_notation.svg b/docs/images/braket_notation.svg new file mode 100644 index 000000000..f4f711d36 --- /dev/null +++ b/docs/images/braket_notation.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/index.rst b/docs/index.rst index 4b7239693..e75130022 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,8 +14,8 @@ 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" [`arxiv:1612.08091 `_] - * Thomas Häner, Damian S. Steiger, Krysta M. Svore, and Matthias Troyer "A Software Methodology for Compiling Quantum Programs" [`arxiv:1604.01401 `_] + * 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 diff --git a/examples/README.rst b/examples/README.rst index 6ee67c41b..14ff5190e 100644 --- a/examples/README.rst +++ b/examples/README.rst @@ -12,7 +12,7 @@ Getting started / background information It might be a good starting point to have a look at our paper which explains the goals of the ProjectQ framework and also gives a good overview: -* Damian S. Steiger, Thomas Häner, and Matthias Troyer "ProjectQ: An Open Source Software Framework for Quantum Computing" `[arxiv:1612.08091] `__ +* 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) Our second paper looks at a few aspects of ProjectQ in more details: From b18dc35cbf0fed7c405bb2a2ceff25ffbbcfd8cb Mon Sep 17 00:00:00 2001 From: Thomas Haener Date: Mon, 23 Jul 2018 10:12:50 +0200 Subject: [PATCH 002/113] Add new tutorial on how to use the IBM backend. (#246) --- examples/ibmq_tutorial.ipynb | 510 +++++++++++++++++++++++++++++++++++ 1 file changed, 510 insertions(+) create mode 100644 examples/ibmq_tutorial.ipynb diff --git a/examples/ibmq_tutorial.ipynb b/examples/ibmq_tutorial.ipynb new file mode 100644 index 000000000..464612ca5 --- /dev/null +++ b/examples/ibmq_tutorial.ipynb @@ -0,0 +1,510 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Running ProjectQ code on IBM Q devices" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial, we will see how to run code on IBM Q devices directly from within ProjectQ. All that is needed is an IBM Q Experience user account. To sign up, visit https://quantumexperience.ng.bluemix.net/.\n", + "\n", + "ProjectQ supports two IBM Q devices called `ibmqx4` and `ibmqx5` which feature 5 and 16 qubits, respectively. Let us start with entangling the qubits of the 5-qubit device:\n", + "\n", + "## Entangling 5 qubits\n", + "First, we import all necessary operations (`Entangle`, measurement), the back-end (`IBMBackend`), and the main compiler engine (`MainEngine`). The Entangle operation is defined as a Hadamard gate on the first qubit (creates an equal superposition of |0> and |1>), followed by controlled NOT gates acting on all other qubits controlled on the first." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import projectq.setups.ibm\n", + "from projectq.backends import IBMBackend\n", + "from projectq.ops import Measure, Entangle, All\n", + "from projectq import MainEngine" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we instantiate a main compiler engine using the IBM Q back-end and the predefined compiler engines which take care of the qubit placement, translation of operations, etc.:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024,\n", + " verbose=False, device='ibmqx4'),\n", + " engine_list=projectq.setups.ibm.get_engine_list())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If `use_hardware` is set to `False`, it will use the IBM Q simulator instead. `num_runs` specifies the number of samples to collect for statistics, `verbose=True` would output additional information which may be helpful for debugging, and the device parameter lets users choose between the two devices (\"ibmqx4\" and \"ibmqx5\").\n", + "\n", + "With our compiler set up, we can now allocate our qubits, entangle them, measure the outcome, and then flush the entire circuit down the compilation pipeline such that it is executed (and measurements are registered). Note that there are many jobs queued for execution on the IBM Q device and, as a result, our execution times out. We will learn how to retrieve our results despite this time out." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "IBM QE user (e-mail) > haenert@phys.ethz.ch\n", + "IBM QE password > \n", + "Waiting for results. [Job ID: 5b557df2306393003b746da2]\n", + "Currently there are 49 jobs queued for execution on ibmqx4.\n", + "Currently there are 48 jobs queued for execution on ibmqx4.\n" + ] + }, + { + "ename": "Exception", + "evalue": "Timeout. The ID of your submitted job is 5b557df2306393003b746da2.\n raised in:\n' File \"/home/thomas/ProjectQ/projectq/backends/_ibm/_ibm_http_client.py\", line 174, in _get_result'\n' .format(execution_id))'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mException\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mq\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mq\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mqureg\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 21\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 22\u001b[0;31m \u001b[0mrun_entangle\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0meng\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnum_qubits\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# run it\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mrun_entangle\u001b[0;34m(eng, num_qubits)\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;31m# run the circuit\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0meng\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mflush\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 13\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0;31m# access the probabilities via the back-end:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/home/thomas/ProjectQ/projectq/cengines/_main.py\u001b[0m in \u001b[0;36mflush\u001b[0;34m(self, deallocate_qubits)\u001b[0m\n\u001b[1;32m 302\u001b[0m \u001b[0mqb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mactive_qubits\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 303\u001b[0m \u001b[0mqb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__del__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 304\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreceive\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mCommand\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mFlushGate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mWeakQubitRef\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/home/thomas/ProjectQ/projectq/cengines/_main.py\u001b[0m in \u001b[0;36mreceive\u001b[0;34m(self, command_list)\u001b[0m\n\u001b[1;32m 264\u001b[0m then send on)\n\u001b[1;32m 265\u001b[0m \"\"\"\n\u001b[0;32m--> 266\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcommand_list\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 267\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 268\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcommand_list\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/home/thomas/ProjectQ/projectq/cengines/_main.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, command_list)\u001b[0m\n\u001b[1;32m 286\u001b[0m \"\\n\" + repr(last_line[-2]))\n\u001b[1;32m 287\u001b[0m \u001b[0mcompact_exception\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__cause__\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 288\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mcompact_exception\u001b[0m \u001b[0;31m# use verbose=True for more info\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 289\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 290\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mflush\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdeallocate_qubits\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mException\u001b[0m: Timeout. The ID of your submitted job is 5b557df2306393003b746da2.\n raised in:\n' File \"/home/thomas/ProjectQ/projectq/backends/_ibm/_ibm_http_client.py\", line 174, in _get_result'\n' .format(execution_id))'" + ] + } + ], + "source": [ + "def run_entangle(eng, num_qubits):\n", + " # allocate a quantum register of 5 qubits\n", + " qureg = eng.allocate_qureg(num_qubits)\n", + "\n", + " # entangle the qureg\n", + " Entangle | qureg\n", + "\n", + " # measure; should be all-0 or all-1\n", + " All(Measure) | qureg\n", + "\n", + " # run the circuit\n", + " eng.flush()\n", + "\n", + " # access the probabilities via the back-end:\n", + " results = eng.backend.get_probabilities(qureg)\n", + " for state in results:\n", + " print(\"Measured {} with p = {}.\".format(state, results[state]))\n", + "\n", + " # return one (random) measurement outcome.\n", + " return [int(q) for q in qureg]\n", + "\n", + "run_entangle(eng, num_qubits=5) # run it" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieving a timed-out execution\n", + "Sometimes, the queue is very long and the waiting times may exceed the limit of 5 minutes. In this case, ProjectQ will raise an exception which contains the job ID, as could be seen above, where the job ID was `5b557df2306393003b746da2`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to still retrieve all results at a later point in time, one can simply re-run the entire program using a slightly modified back-end:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "IBM QE user (e-mail) > haenert@phys.ethz.ch\n", + "IBM QE password > \n", + "Waiting for results. [Job ID: 5b557df2306393003b746da2]\n", + "Measured 00001 with p = 0.0185546875.\n", + "Measured 01101 with p = 0.00390625.\n", + "Measured 10001 with p = 0.0107421875.\n", + "Measured 11001 with p = 0.0029296875.\n", + "Measured 10101 with p = 0.0107421875.\n", + "Measured 11101 with p = 0.0419921875.\n", + "Measured 00011 with p = 0.005859375.\n", + "Measured 01011 with p = 0.00390625.\n", + "Measured 00111 with p = 0.0029296875.\n", + "Measured 01111 with p = 0.0107421875.\n", + "Measured 10011 with p = 0.0322265625.\n", + "Measured 11011 with p = 0.0419921875.\n", + "Measured 10111 with p = 0.056640625.\n", + "Measured 11111 with p = 0.2744140625.\n", + "Measured 00000 with p = 0.392578125.\n", + "Measured 01000 with p = 0.0029296875.\n", + "Measured 00100 with p = 0.01171875.\n", + "Measured 01100 with p = 0.0126953125.\n", + "Measured 10000 with p = 0.0009765625.\n", + "Measured 00010 with p = 0.009765625.\n", + "Measured 01010 with p = 0.0009765625.\n", + "Measured 00110 with p = 0.0029296875.\n", + "Measured 01110 with p = 0.0087890625.\n", + "Measured 10010 with p = 0.0029296875.\n", + "Measured 11010 with p = 0.0068359375.\n", + "Measured 10110 with p = 0.00390625.\n", + "Measured 11110 with p = 0.025390625.\n" + ] + }, + { + "data": { + "text/plain": [ + "[0, 0, 0, 0, 0]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024,\n", + " verbose=False, device='ibmqx4',\n", + " retrieve_execution=\"5b557df2306393003b746da2\"), # provide job ID\n", + " engine_list=projectq.setups.ibm.get_engine_list())\n", + "\n", + "run_entangle(eng, num_qubits=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Entangling more qubits: Using ibmqx5\n", + "\n", + "If you have access to the 16-qubit device as well, you can also use ProjectQ to run your quantum programs on this device. ProjectQ contains a 2D grid mapper, which takes care of the mapping for you. We only have to change two things in order to use the 16-qubit chip as opposed to the 5-qubit chip:\n", + "\n", + "1) Import the new 16-qubit setup which contains the compiler engines for this device\n", + "\n", + "2) Modify the device parameter in the IBMBackend to \"ibmqx5\"\n", + "\n", + "Therefore, in order to entangle more than 5 qubits, we can simply write" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "import projectq.setups.ibm16 # import setup which contains the grid mapper\n", + "eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024,\n", + " verbose=False, device='ibmqx5'), # use ibmqx5 now\n", + " engine_list=projectq.setups.ibm16.get_engine_list()) # note: ibm16 setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "and then re-run the example from before via `run_entangle(eng, num_qubits)`. If an execution times out, it can also be retrieved at a later point by providing the additional `retrieve_execution=\"execution_id\"` parameter to the IBMBackend (but this time with `device='ibmqx5'`)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "IBM QE user (e-mail) > haenert@phys.ethz.ch\n", + "IBM QE password > \n", + "Waiting for results. [Job ID: 5b5580e0e291fd003ea62acf]\n", + "Currently there are 12 jobs queued for execution on ibmqx5.\n", + "Currently there are 12 jobs queued for execution on ibmqx5.\n", + "Measured 00000000 with p = 0.0234375.\n", + "Measured 00100000 with p = 0.017578125.\n", + "Measured 01000000 with p = 0.0234375.\n", + "Measured 01100000 with p = 0.0087890625.\n", + "Measured 00010000 with p = 0.013671875.\n", + "Measured 00110000 with p = 0.0126953125.\n", + "Measured 01010000 with p = 0.0146484375.\n", + "Measured 01110000 with p = 0.013671875.\n", + "Measured 00000010 with p = 0.013671875.\n", + "Measured 00100010 with p = 0.009765625.\n", + "Measured 01000010 with p = 0.0107421875.\n", + "Measured 01100010 with p = 0.0068359375.\n", + "Measured 00010010 with p = 0.0048828125.\n", + "Measured 00110010 with p = 0.0078125.\n", + "Measured 01010010 with p = 0.0068359375.\n", + "Measured 01110010 with p = 0.0078125.\n", + "Measured 00000100 with p = 0.001953125.\n", + "Measured 00100100 with p = 0.009765625.\n", + "Measured 01000100 with p = 0.0068359375.\n", + "Measured 01100100 with p = 0.0048828125.\n", + "Measured 00010100 with p = 0.005859375.\n", + "Measured 00110100 with p = 0.005859375.\n", + "Measured 01010100 with p = 0.001953125.\n", + "Measured 01110100 with p = 0.005859375.\n", + "Measured 00000110 with p = 0.001953125.\n", + "Measured 00100110 with p = 0.005859375.\n", + "Measured 01000110 with p = 0.00390625.\n", + "Measured 01100110 with p = 0.005859375.\n", + "Measured 00010110 with p = 0.0107421875.\n", + "Measured 00110110 with p = 0.0009765625.\n", + "Measured 01010110 with p = 0.001953125.\n", + "Measured 01110110 with p = 0.0029296875.\n", + "Measured 10000000 with p = 0.0009765625.\n", + "Measured 10100000 with p = 0.0087890625.\n", + "Measured 11000000 with p = 0.001953125.\n", + "Measured 11100000 with p = 0.0029296875.\n", + "Measured 10010000 with p = 0.0029296875.\n", + "Measured 10110000 with p = 0.001953125.\n", + "Measured 11010000 with p = 0.001953125.\n", + "Measured 11110000 with p = 0.0009765625.\n", + "Measured 10000010 with p = 0.0009765625.\n", + "Measured 10100010 with p = 0.0048828125.\n", + "Measured 11000010 with p = 0.0009765625.\n", + "Measured 11100010 with p = 0.0029296875.\n", + "Measured 10010010 with p = 0.001953125.\n", + "Measured 10110010 with p = 0.0087890625.\n", + "Measured 11010010 with p = 0.0009765625.\n", + "Measured 11110010 with p = 0.0009765625.\n", + "Measured 10000100 with p = 0.0009765625.\n", + "Measured 10100100 with p = 0.0009765625.\n", + "Measured 11000100 with p = 0.0048828125.\n", + "Measured 10010100 with p = 0.0048828125.\n", + "Measured 10110100 with p = 0.001953125.\n", + "Measured 11010100 with p = 0.0029296875.\n", + "Measured 11110100 with p = 0.001953125.\n", + "Measured 10000110 with p = 0.001953125.\n", + "Measured 10100110 with p = 0.001953125.\n", + "Measured 11100110 with p = 0.0029296875.\n", + "Measured 10010110 with p = 0.001953125.\n", + "Measured 10110110 with p = 0.001953125.\n", + "Measured 11110110 with p = 0.0048828125.\n", + "Measured 00000001 with p = 0.0029296875.\n", + "Measured 00100001 with p = 0.0029296875.\n", + "Measured 01000001 with p = 0.0029296875.\n", + "Measured 01100001 with p = 0.0009765625.\n", + "Measured 00110001 with p = 0.00390625.\n", + "Measured 01010001 with p = 0.005859375.\n", + "Measured 01110001 with p = 0.0009765625.\n", + "Measured 00000011 with p = 0.0029296875.\n", + "Measured 00100011 with p = 0.0029296875.\n", + "Measured 01000011 with p = 0.0009765625.\n", + "Measured 01100011 with p = 0.0009765625.\n", + "Measured 00010011 with p = 0.0029296875.\n", + "Measured 00000101 with p = 0.0029296875.\n", + "Measured 00100101 with p = 0.0029296875.\n", + "Measured 01000101 with p = 0.001953125.\n", + "Measured 01100101 with p = 0.0029296875.\n", + "Measured 00110101 with p = 0.001953125.\n", + "Measured 01010101 with p = 0.001953125.\n", + "Measured 01110101 with p = 0.0029296875.\n", + "Measured 00000111 with p = 0.0029296875.\n", + "Measured 01100111 with p = 0.0009765625.\n", + "Measured 00010111 with p = 0.001953125.\n", + "Measured 00110111 with p = 0.0009765625.\n", + "Measured 01010111 with p = 0.0009765625.\n", + "Measured 01110111 with p = 0.001953125.\n", + "Measured 10000001 with p = 0.00390625.\n", + "Measured 10100001 with p = 0.001953125.\n", + "Measured 11000001 with p = 0.0029296875.\n", + "Measured 11100001 with p = 0.0048828125.\n", + "Measured 10010001 with p = 0.0048828125.\n", + "Measured 10110001 with p = 0.0029296875.\n", + "Measured 11010001 with p = 0.001953125.\n", + "Measured 11110001 with p = 0.0029296875.\n", + "Measured 10000011 with p = 0.0029296875.\n", + "Measured 10100011 with p = 0.0048828125.\n", + "Measured 11000011 with p = 0.0048828125.\n", + "Measured 11100011 with p = 0.0029296875.\n", + "Measured 10010011 with p = 0.001953125.\n", + "Measured 10110011 with p = 0.001953125.\n", + "Measured 11010011 with p = 0.001953125.\n", + "Measured 11110011 with p = 0.0029296875.\n", + "Measured 10000101 with p = 0.005859375.\n", + "Measured 10100101 with p = 0.0107421875.\n", + "Measured 11000101 with p = 0.009765625.\n", + "Measured 11100101 with p = 0.0029296875.\n", + "Measured 10010101 with p = 0.0078125.\n", + "Measured 10110101 with p = 0.0068359375.\n", + "Measured 11010101 with p = 0.0078125.\n", + "Measured 11110101 with p = 0.00390625.\n", + "Measured 10000111 with p = 0.0078125.\n", + "Measured 10100111 with p = 0.005859375.\n", + "Measured 11000111 with p = 0.001953125.\n", + "Measured 11100111 with p = 0.0048828125.\n", + "Measured 10010111 with p = 0.0048828125.\n", + "Measured 10110111 with p = 0.001953125.\n", + "Measured 11010111 with p = 0.00390625.\n", + "Measured 11110111 with p = 0.0068359375.\n", + "Measured 00001000 with p = 0.0087890625.\n", + "Measured 00101000 with p = 0.017578125.\n", + "Measured 01001000 with p = 0.0107421875.\n", + "Measured 01101000 with p = 0.0146484375.\n", + "Measured 00011000 with p = 0.0048828125.\n", + "Measured 00111000 with p = 0.01171875.\n", + "Measured 01011000 with p = 0.0126953125.\n", + "Measured 01111000 with p = 0.0146484375.\n", + "Measured 00001010 with p = 0.009765625.\n", + "Measured 00101010 with p = 0.005859375.\n", + "Measured 01001010 with p = 0.0029296875.\n", + "Measured 01101010 with p = 0.017578125.\n", + "Measured 00011010 with p = 0.0087890625.\n", + "Measured 00111010 with p = 0.01171875.\n", + "Measured 01011010 with p = 0.0029296875.\n", + "Measured 01111010 with p = 0.00390625.\n", + "Measured 00001100 with p = 0.0068359375.\n", + "Measured 00101100 with p = 0.001953125.\n", + "Measured 01001100 with p = 0.005859375.\n", + "Measured 01101100 with p = 0.0078125.\n", + "Measured 00011100 with p = 0.005859375.\n", + "Measured 00111100 with p = 0.00390625.\n", + "Measured 01011100 with p = 0.00390625.\n", + "Measured 01111100 with p = 0.0068359375.\n", + "Measured 00001110 with p = 0.0029296875.\n", + "Measured 00101110 with p = 0.00390625.\n", + "Measured 01101110 with p = 0.0029296875.\n", + "Measured 00011110 with p = 0.0048828125.\n", + "Measured 00111110 with p = 0.00390625.\n", + "Measured 01011110 with p = 0.001953125.\n", + "Measured 01111110 with p = 0.0009765625.\n", + "Measured 10001000 with p = 0.001953125.\n", + "Measured 10101000 with p = 0.001953125.\n", + "Measured 11101000 with p = 0.00390625.\n", + "Measured 10011000 with p = 0.0048828125.\n", + "Measured 10111000 with p = 0.001953125.\n", + "Measured 11011000 with p = 0.001953125.\n", + "Measured 10001010 with p = 0.0029296875.\n", + "Measured 10101010 with p = 0.001953125.\n", + "Measured 11101010 with p = 0.001953125.\n", + "Measured 10011010 with p = 0.0009765625.\n", + "Measured 10111010 with p = 0.001953125.\n", + "Measured 11011010 with p = 0.001953125.\n", + "Measured 11111010 with p = 0.001953125.\n", + "Measured 10001100 with p = 0.0029296875.\n", + "Measured 10101100 with p = 0.00390625.\n", + "Measured 11001100 with p = 0.0009765625.\n", + "Measured 11101100 with p = 0.0009765625.\n", + "Measured 10011100 with p = 0.0029296875.\n", + "Measured 10111100 with p = 0.001953125.\n", + "Measured 10001110 with p = 0.005859375.\n", + "Measured 10101110 with p = 0.001953125.\n", + "Measured 11001110 with p = 0.0029296875.\n", + "Measured 11101110 with p = 0.001953125.\n", + "Measured 10011110 with p = 0.0029296875.\n", + "Measured 10111110 with p = 0.001953125.\n", + "Measured 11011110 with p = 0.001953125.\n", + "Measured 11111110 with p = 0.0029296875.\n", + "Measured 00001001 with p = 0.001953125.\n", + "Measured 00101001 with p = 0.0029296875.\n", + "Measured 01001001 with p = 0.0029296875.\n", + "Measured 01101001 with p = 0.0048828125.\n", + "Measured 00011001 with p = 0.0048828125.\n", + "Measured 01011001 with p = 0.0009765625.\n", + "Measured 01111001 with p = 0.00390625.\n", + "Measured 00001011 with p = 0.001953125.\n", + "Measured 00101011 with p = 0.0029296875.\n", + "Measured 01001011 with p = 0.001953125.\n", + "Measured 00011011 with p = 0.0009765625.\n", + "Measured 01111011 with p = 0.0009765625.\n", + "Measured 00001101 with p = 0.0009765625.\n", + "Measured 01001101 with p = 0.0009765625.\n", + "Measured 00011101 with p = 0.0009765625.\n", + "Measured 01011101 with p = 0.0029296875.\n", + "Measured 00001111 with p = 0.0009765625.\n", + "Measured 00101111 with p = 0.0029296875.\n", + "Measured 01001111 with p = 0.0009765625.\n", + "Measured 00011111 with p = 0.001953125.\n", + "Measured 00111111 with p = 0.0009765625.\n", + "Measured 01111111 with p = 0.0009765625.\n", + "Measured 10001001 with p = 0.0029296875.\n", + "Measured 10101001 with p = 0.00390625.\n", + "Measured 11001001 with p = 0.001953125.\n", + "Measured 11101001 with p = 0.0068359375.\n", + "Measured 10011001 with p = 0.001953125.\n", + "Measured 10111001 with p = 0.0029296875.\n", + "Measured 11011001 with p = 0.00390625.\n", + "Measured 11111001 with p = 0.0068359375.\n", + "Measured 10001011 with p = 0.0048828125.\n", + "Measured 10101011 with p = 0.00390625.\n", + "Measured 11001011 with p = 0.00390625.\n", + "Measured 11101011 with p = 0.0029296875.\n", + "Measured 10011011 with p = 0.0009765625.\n", + "Measured 10111011 with p = 0.001953125.\n", + "Measured 11011011 with p = 0.001953125.\n", + "Measured 11111011 with p = 0.001953125.\n", + "Measured 10001101 with p = 0.0009765625.\n", + "Measured 10101101 with p = 0.00390625.\n", + "Measured 11101101 with p = 0.0029296875.\n", + "Measured 10011101 with p = 0.00390625.\n", + "Measured 10111101 with p = 0.0078125.\n", + "Measured 11011101 with p = 0.005859375.\n", + "Measured 11111101 with p = 0.0048828125.\n", + "Measured 10001111 with p = 0.0009765625.\n", + "Measured 10101111 with p = 0.001953125.\n", + "Measured 11001111 with p = 0.001953125.\n", + "Measured 11101111 with p = 0.0078125.\n", + "Measured 10011111 with p = 0.0009765625.\n", + "Measured 10111111 with p = 0.005859375.\n", + "Measured 11011111 with p = 0.00390625.\n", + "Measured 11111111 with p = 0.0009765625.\n" + ] + }, + { + "data": { + "text/plain": [ + "[0, 0, 1, 0, 0, 0, 1, 0]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_entangle(eng, num_qubits=8)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "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.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 20f45669389b818093049602c84eb18a172a074b Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Mon, 23 Jul 2018 10:14:59 +0200 Subject: [PATCH 003/113] Bumped version number to 0.4 --- projectq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/_version.py b/projectq/_version.py index 297633d91..e2835d3b6 100755 --- a/projectq/_version.py +++ b/projectq/_version.py @@ -13,4 +13,4 @@ # limitations under the License. """Define version number here and read it from setup.py automatically""" -__version__ = "0.3.6" +__version__ = "0.4" From d6306458c278244a086ceb7a7e078481ca8f5d92 Mon Sep 17 00:00:00 2001 From: Thomas Haener Date: Fri, 3 Aug 2018 10:32:54 +0200 Subject: [PATCH 004/113] Specialize the CnU decomposition rule for Pauli X gates. (#251) --- .../setups/decompositions/cnu2toffoliandcu.py | 16 +++++++++++++--- .../decompositions/cnu2toffoliandcu_test.py | 8 ++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/projectq/setups/decompositions/cnu2toffoliandcu.py b/projectq/setups/decompositions/cnu2toffoliandcu.py index 5f7c72761..9ce2610c3 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu.py @@ -40,15 +40,21 @@ def _recognize_CnU(cmd): def _decompose_CnU(cmd): """ - Decompose a multi-controlled gate U into a single-controlled U. + Decompose a multi-controlled gate U with n control qubits into a single- + controlled U. - It uses (n-1) work qubits and 2 * (n-1) Toffoli gates. + It uses (n-1) work qubits and 2 * (n-1) Toffoli gates for general U + and (n-2) work qubits and 2n - 3 Toffoli gates if U is an X-gate. """ eng = cmd.engine qubits = cmd.qubits ctrl_qureg = cmd.control_qubits gate = cmd.gate n = get_control_count(cmd) + + # specialized for X-gate + if gate == XGate() and n > 2: + n -= 1 ancilla_qureg = eng.allocate_qureg(n-1) with Compute(eng): @@ -56,8 +62,12 @@ def _decompose_CnU(cmd): for ctrl_index in range(2, n): Toffoli | (ctrl_qureg[ctrl_index], ancilla_qureg[ctrl_index-2], ancilla_qureg[ctrl_index-1]) + ctrls = [ancilla_qureg[-1]] - with Control(eng, ancilla_qureg[-1]): + # specialized for X-gate + if gate == XGate() and get_control_count(cmd) > 2: + ctrls += [ctrl_qureg[-1]] + with Control(eng, ctrls): gate | qubits Uncompute(eng) diff --git a/projectq/setups/decompositions/cnu2toffoliandcu_test.py b/projectq/setups/decompositions/cnu2toffoliandcu_test.py index 5c815bfd2..b0e27760f 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu_test.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu_test.py @@ -105,17 +105,21 @@ def test_decomposition(): Rx(0.4) | test_qb with Control(test_eng, test_ctrl_qureg): Ry(0.6) | test_qb + with Control(test_eng, test_ctrl_qureg): + X | test_qb with Control(correct_eng, correct_ctrl_qureg[:2]): Rx(0.4) | correct_qb with Control(correct_eng, correct_ctrl_qureg): Ry(0.6) | correct_qb + with Control(correct_eng, correct_ctrl_qureg): + X | correct_qb test_eng.flush() correct_eng.flush() - assert len(correct_dummy_eng.received_commands) == 8 - assert len(test_dummy_eng.received_commands) == 20 + assert len(correct_dummy_eng.received_commands) == 9 + assert len(test_dummy_eng.received_commands) == 25 for fstate in range(16): binary_state = format(fstate, '04b') From 4674e11baa58e08e1544bd8ac606dde1479802a4 Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Tue, 7 Aug 2018 14:30:13 +0200 Subject: [PATCH 005/113] Change BasicGate __eq__ to test for matrix attribute (#252) --- projectq/backends/_sim/_simulator_test.py | 6 +-- projectq/cengines/_replacer/_replacer_test.py | 2 - projectq/ops/_basics.py | 39 ++++++++++++++++++- projectq/ops/_basics_test.py | 11 +++++- projectq/ops/_qftgate.py | 1 + projectq/ops/_qftgate_test.py | 4 ++ .../carb1qubit2cnotrzandry_test.py | 4 +- 7 files changed, 57 insertions(+), 10 deletions(-) diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 19c46eb15..546ab3584 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -147,15 +147,15 @@ def test_simulator_is_available(sim): new_cmd.gate = Mock1QubitGate() assert sim.is_available(new_cmd) - assert new_cmd.gate.cnt == 1 + assert new_cmd.gate.cnt == 4 new_cmd.gate = Mock6QubitGate() assert not sim.is_available(new_cmd) - assert new_cmd.gate.cnt == 1 + assert new_cmd.gate.cnt == 4 new_cmd.gate = MockNoMatrixGate() assert not sim.is_available(new_cmd) - assert new_cmd.gate.cnt == 1 + assert new_cmd.gate.cnt == 7 def test_simulator_cheat(sim): diff --git a/projectq/cengines/_replacer/_replacer_test.py b/projectq/cengines/_replacer/_replacer_test.py index f532cd998..a27b5557c 100755 --- a/projectq/cengines/_replacer/_replacer_test.py +++ b/projectq/cengines/_replacer/_replacer_test.py @@ -178,8 +178,6 @@ def magic_filter(self, cmd): qb = eng.allocate_qubit() MagicGate() | qb eng.flush() - for cmd in backend.received_commands: - print(cmd) assert len(backend.received_commands) == 4 assert backend.received_commands[1].gate == H assert backend.received_commands[2].gate == Rx(-0.6) diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 052b3abed..7f724c93e 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -34,12 +34,16 @@ import math from copy import deepcopy +import numpy as np + from projectq.types import BasicQubit from ._command import Command, apply_command ANGLE_PRECISION = 12 ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION +RTOL = 1e-10 +ATOL = 1e-12 class NotMergeable(Exception): @@ -200,8 +204,39 @@ def __or__(self, qubits): apply_command(cmd) def __eq__(self, other): - """ Return True if equal (i.e., instance of same class). """ - return isinstance(other, self.__class__) + """ Return True if equal (i.e., instance of same class). + + Unless both have a matrix attribute in which case we also check + that the matrices are identical as people might want to do the + following: + + Example: + .. code-block:: python + + gate = BasicGate() + gate.matrix = numpy.matrix([[1,0],[0, -1]]) + """ + if hasattr(self, 'matrix'): + if not hasattr(other, 'matrix'): + return False + if hasattr(other, 'matrix'): + if not hasattr(self, 'matrix'): + return False + if hasattr(self, 'matrix') and hasattr(other, 'matrix'): + if (not isinstance(self.matrix, np.matrix) or + not isinstance(other.matrix, np.matrix)): + raise TypeError("One of the gates doesn't have the correct " + "type (numpy.matrix) for the matrix " + "attribute.") + if (self.matrix.shape == other.matrix.shape and + np.allclose(self.matrix, other.matrix, + rtol=RTOL, atol=ATOL, + equal_nan=False)): + return True + else: + return False + else: + return isinstance(other, self.__class__) def __ne__(self, other): return not self.__eq__(other) diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index fead34616..61a1de767 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -14,10 +14,12 @@ """Tests for projectq.ops._basics.""" -import pytest from copy import deepcopy import math +import numpy as np +import pytest + from projectq.types import Qubit, Qureg from projectq.ops import Command from projectq import MainEngine @@ -116,6 +118,13 @@ def test_basic_gate_compare(): gate2 = _basics.BasicGate() assert gate1 == gate2 assert not (gate1 != gate2) + gate3 = _basics.BasicGate() + gate3.matrix = np.matrix([[1, 0], [0, -1]]) + assert gate1 != gate3 + gate4 = _basics.BasicGate() + gate4.matrix = [[1, 0], [0, -1]] + with pytest.raises(TypeError): + gate4 == gate3 def test_comparing_different_gates(): diff --git a/projectq/ops/_qftgate.py b/projectq/ops/_qftgate.py index 97b045072..7c1e3984b 100755 --- a/projectq/ops/_qftgate.py +++ b/projectq/ops/_qftgate.py @@ -22,5 +22,6 @@ class QFTGate(BasicGate): def __str__(self): return "QFT" + #: Shortcut (instance of) :class:`projectq.ops.QFTGate` QFT = QFTGate() diff --git a/projectq/ops/_qftgate_test.py b/projectq/ops/_qftgate_test.py index a74683006..4382d632b 100755 --- a/projectq/ops/_qftgate_test.py +++ b/projectq/ops/_qftgate_test.py @@ -20,3 +20,7 @@ def test_qft_gate_str(): gate = _qftgate.QFT assert str(gate) == "QFT" + + +def test_qft_equality(): + assert _qftgate.QFT == _qftgate.QFTGate() diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py index 686c4991e..7d324e81c 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py @@ -58,8 +58,8 @@ def test_recognize_incorrect_gates(): BasicGate() | qubit # Two qubit gate: two_qubit_gate = BasicGate() - two_qubit_gate.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: From d3631e23f9825a35959fa5155ad053fbdb87d435 Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Thu, 9 Aug 2018 13:45:17 +0200 Subject: [PATCH 006/113] Adds Uniformly controlled rotations (Ry and Rz) (#253) --- projectq/ops/__init__.py | 2 + projectq/ops/_qubit_operator.py | 4 +- projectq/ops/_qubit_operator_test.py | 3 + .../ops/_uniformly_controlled_rotation.py | 147 ++++++++++++++++++ .../_uniformly_controlled_rotation_test.py | 73 +++++++++ projectq/setups/decompositions/__init__.py | 6 +- .../uniformlycontrolledr2cnot.py | 139 +++++++++++++++++ .../uniformlycontrolledr2cnot_test.py | 127 +++++++++++++++ 8 files changed, 497 insertions(+), 4 deletions(-) create mode 100644 projectq/ops/_uniformly_controlled_rotation.py create mode 100644 projectq/ops/_uniformly_controlled_rotation_test.py create mode 100644 projectq/setups/decompositions/uniformlycontrolledr2cnot.py create mode 100644 projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 56dbec862..571fdf0c1 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -33,3 +33,5 @@ from ._qubit_operator import QubitOperator from ._shortcuts import * from ._time_evolution import TimeEvolution +from ._uniformly_controlled_rotation import (UniformlyControlledRy, + UniformlyControlledRz) diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index a95e3ea9a..c38bd2f57 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -396,7 +396,7 @@ def __iadd__(self, addend): if abs(addend.terms[term] + self.terms[term]) > 0.: self.terms[term] += addend.terms[term] else: - del self.terms[term] + self.terms.pop(term) else: self.terms[term] = addend.terms[term] else: @@ -425,7 +425,7 @@ def __isub__(self, subtrahend): if abs(self.terms[term] - subtrahend.terms[term]) > 0.: self.terms[term] -= subtrahend.terms[term] else: - del self.terms[term] + self.terms.pop(term) else: self.terms[term] = -subtrahend.terms[term] else: diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index f7f76cd61..12e1d19dd 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -417,6 +417,9 @@ def test_isub_different_term(): assert len(a.terms) == 2 assert a.terms[term_a] == pytest.approx(1.0) assert a.terms[term_b] == pytest.approx(-1.0) + b = qo.QubitOperator(term_a, 1.0) + b -= qo.QubitOperator(term_a, 1.0) + assert b.terms == {} def test_isub_bad_addend(): diff --git a/projectq/ops/_uniformly_controlled_rotation.py b/projectq/ops/_uniformly_controlled_rotation.py new file mode 100644 index 000000000..d3bef04fa --- /dev/null +++ b/projectq/ops/_uniformly_controlled_rotation.py @@ -0,0 +1,147 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +from ._basics import ANGLE_PRECISION, ANGLE_TOLERANCE, BasicGate, NotMergeable + + +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. + + Example: + .. code-block:: python + + controls = eng.allocate_qureg(2) + target = eng.allocate_qubit() + 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. + + Args: + angles(list[float]): Rotation angles. Ry(angles[k]) is applied + 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) + if new_angle > 4 * math.pi - ANGLE_TOLERANCE: + new_angle = 0. + rounded_angles.append(new_angle) + self.angles = rounded_angles + + def get_inverse(self): + return self.__class__([-1 * angle for angle in self.angles]) + + def get_merged(self, other): + if isinstance(other, self.__class__): + new_angles = [angle1 + angle2 for (angle1, angle2) in + zip(self.angles, other.angles)] + return self.__class__(new_angles) + raise NotMergeable() + + def __str__(self): + return "UniformlyControlledRy(" + str(self.angles) + ")" + + 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 + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(str(self)) + + +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. + + Example: + .. code-block:: python + + controls = eng.allocate_qureg(2) + target = eng.allocate_qubit() + 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. + + Args: + angles(list[float]): Rotation angles. Rz(angles[k]) is applied + 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) + if new_angle > 4 * math.pi - ANGLE_TOLERANCE: + new_angle = 0. + rounded_angles.append(new_angle) + self.angles = rounded_angles + + def get_inverse(self): + return self.__class__([-1 * angle for angle in self.angles]) + + def get_merged(self, other): + if isinstance(other, self.__class__): + new_angles = [angle1 + angle2 for (angle1, angle2) in + zip(self.angles, other.angles)] + return self.__class__(new_angles) + raise NotMergeable() + + def __str__(self): + return "UniformlyControlledRz(" + str(self.angles) + ")" + + 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 + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(str(self)) diff --git a/projectq/ops/_uniformly_controlled_rotation_test.py b/projectq/ops/_uniformly_controlled_rotation_test.py new file mode 100644 index 000000000..f58b198f4 --- /dev/null +++ b/projectq/ops/_uniformly_controlled_rotation_test.py @@ -0,0 +1,73 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Tests for projectq.ops._uniformly_controlled_rotation.""" +import math + +import pytest + +from projectq.ops import Rx +from ._basics import NotMergeable + +from projectq.ops import _uniformly_controlled_rotation as ucr + + +@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.] + + +@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]) +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]) + merged_gate = gate1.get_merged(gate2) + assert merged_gate == gate_class([0.2, 0.4, 0.6, 0.8]) + with pytest.raises(NotMergeable): + gate1.get_merged(Rx(0.1)) + + +def test_str_and_hash(): + gate1 = ucr.UniformlyControlledRy([0.1, 0.2, 0.3, 0.4]) + gate2 = ucr.UniformlyControlledRz([0.1, 0.2, 0.3, 0.4]) + assert str(gate1) == "UniformlyControlledRy([0.1, 0.2, 0.3, 0.4])" + assert str(gate2) == "UniformlyControlledRz([0.1, 0.2, 0.3, 0.4])" + assert hash(gate1) == hash("UniformlyControlledRy([0.1, 0.2, 0.3, 0.4])") + assert hash(gate2) == hash("UniformlyControlledRz([0.1, 0.2, 0.3, 0.4])") + + +@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]) + assert gate1 == gate2 + gate3 = gate_class([0.1, 0.2, 0.1, 0.2]) + assert gate2 != gate3 + gate4 = ucr.UniformlyControlledRz([0.1, 0.2]) + gate5 = ucr.UniformlyControlledRy([0.1, 0.2]) + assert gate4 != gate5 + assert not gate5 == gate4 diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index e7dd915cd..26d7f2752 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -27,7 +27,8 @@ ry2rz, swap2cnot, toffoli2cnotandtgate, - time_evolution) + time_evolution, + uniformlycontrolledr2cnot) all_defined_decomposition_rules = [ rule @@ -46,6 +47,7 @@ ry2rz, swap2cnot, toffoli2cnotandtgate, - time_evolution] + time_evolution, + uniformlycontrolledr2cnot] for rule in module.all_defined_decomposition_rules ] diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py new file mode 100644 index 000000000..0f27d2455 --- /dev/null +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py @@ -0,0 +1,139 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +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) + + +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): + gate | target_qubit + else: + if rightmost_cnot[len(ucontrol_qubits)]: + angles1 = [] + angles2 = [] + 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.) + else: + angles1 = [] + angles2 = [] + 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) + # Very custom usage of Compute/CustomUncompute in the following. + if rightmost_cnot[len(ucontrol_qubits)]: + with Compute(eng): + CNOT | (ucontrol_qubits[-1], target_qubit) + 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) + # Next iteration on this level do the other cnot placement + rightmost_cnot[len(ucontrol_qubits)] = ( + not rightmost_cnot[len(ucontrol_qubits)]) + + +def _decompose_ucr(cmd, gate_class): + """ + Decomposition for an uniformly controlled single qubit rotation gate. + + Follows decomposition in arXiv:quant-ph/0407010 section II and + arXiv:quant-ph/0410066v2 Fig. 9a. + + For Ry and Rz it uses 2**len(ucontrol_qubits) CNOT and also + 2**len(ucontrol_qubits) single qubit rotations. + + Args: + cmd: CommandObject to decompose. + gate_class: Ry or Rz + """ + eng = cmd.engine + with Control(eng, cmd.control_qubits): + if not (len(cmd.qubits) == 2 and len(cmd.qubits[1]) == 1): + 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): + 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)): + 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.) + 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) + # 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) + with CustomUncompute(eng): + CNOT | (ucontrol_qubits[-1], target_qubit) + + +def _decompose_ucry(cmd): + return _decompose_ucr(cmd, gate_class=Ry) + + +def _decompose_ucrz(cmd): + return _decompose_ucr(cmd, gate_class=Rz) + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(UniformlyControlledRy, _decompose_ucry), + DecompositionRule(UniformlyControlledRz, _decompose_ucrz) +] diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py new file mode 100644 index 000000000..52c2555a9 --- /dev/null +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py @@ -0,0 +1,127 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for projectq.setups.decompositions.uniformlycontrolledr2cnot.""" + +import pytest + +import projectq +from projectq import MainEngine +from projectq.backends import Simulator +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) + +import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot + + +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)): + with Compute(eng): + for bit_pos in range(len(control_qubits)): + if not (index >> bit_pos) & 1: + X | control_qubits[bit_pos] + with Control(eng, control_qubits): + gate_class(angles[index]) | target_qubit + Uncompute(eng) + + +def _decomp_gates(eng, cmd): + 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)]) + qb = eng.allocate_qubit() + with pytest.raises(TypeError): + UniformlyControlledRy([0.1]) | qb + + +def test_wrong_number_of_angles(): + rule_set = DecompositionRuleSet(modules=[ucr2cnot]) + 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("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. + correct_dummy_eng = DummyEngine(save_commands=True) + 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_sim = test_eng.backend + correct_sim = correct_eng.backend + correct_qb = correct_eng.allocate_qubit() + correct_ctrl_qureg = correct_eng.allocate_qureg(n) + correct_eng.flush() + test_qb = test_eng.allocate_qubit() + test_ctrl_qureg = test_eng.allocate_qureg(n) + test_eng.flush() + + 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]) + 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) + assert correct == pytest.approx(test, rel=1e-10, abs=1e-10) + + All(Measure) | test_qb + test_ctrl_qureg + All(Measure) | correct_qb + correct_ctrl_qureg + test_eng.flush(deallocate_qubits=True) + correct_eng.flush(deallocate_qubits=True) From 0d1a78f3e147e3f69651261498d43730da08308b Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Thu, 9 Aug 2018 17:14:24 +0200 Subject: [PATCH 007/113] Add state preparation (#254) --- projectq/ops/__init__.py | 1 + projectq/ops/_state_prep.py | 57 +++++++++++++ projectq/ops/_state_prep_test.py | 34 ++++++++ projectq/setups/decompositions/__init__.py | 2 + .../setups/decompositions/stateprep2cnot.py | 79 +++++++++++++++++++ .../decompositions/stateprep2cnot_test.py | 64 +++++++++++++++ 6 files changed, 237 insertions(+) create mode 100644 projectq/ops/_state_prep.py create mode 100644 projectq/ops/_state_prep_test.py create mode 100644 projectq/setups/decompositions/stateprep2cnot.py create mode 100644 projectq/setups/decompositions/stateprep2cnot_test.py diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 571fdf0c1..32ff8ab54 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -35,3 +35,4 @@ from ._time_evolution import TimeEvolution from ._uniformly_controlled_rotation import (UniformlyControlledRy, UniformlyControlledRz) +from ._state_prep import StatePreparation diff --git a/projectq/ops/_state_prep.py b/projectq/ops/_state_prep.py new file mode 100644 index 000000000..455a47a52 --- /dev/null +++ b/projectq/ops/_state_prep.py @@ -0,0 +1,57 @@ +# 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. + +from ._basics import BasicGate + + +class StatePreparation(BasicGate): + """ + Gate for transforming qubits in state |0> to any desired quantum state. + """ + def __init__(self, final_state): + """ + Initialize StatePreparation gate. + + Example: + .. code-block:: python + + qureg = eng.allocate_qureg(2) + StatePreparation([0.5, -0.5j, -0.5, 0.5]) | qureg + + Note: + The amplitude of state k is final_state[k]. When the state k is + written in binary notation, then qureg[0] denotes the qubit + whose state corresponds to the least significant bit of k. + + Args: + final_state(list[complex]): wavefunction of the desired + quantum state. len(final_state) must + be 2**len(qureg). Must be normalized! + """ + BasicGate.__init__(self) + self.final_state = list(final_state) + + def __str__(self): + return "StatePreparation" + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.final_state == other.final_state + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash("StatePreparation(" + str(self.final_state) + ")") diff --git a/projectq/ops/_state_prep_test.py b/projectq/ops/_state_prep_test.py new file mode 100644 index 000000000..161bd53a1 --- /dev/null +++ b/projectq/ops/_state_prep_test.py @@ -0,0 +1,34 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for projectq.ops._state_prep.""" + +import projectq + +from projectq.ops import _state_prep, X + + +def test_equality_and_hash(): + gate1 = _state_prep.StatePreparation([0.5, -0.5, 0.5, -0.5]) + gate2 = _state_prep.StatePreparation([0.5, -0.5, 0.5, -0.5]) + gate3 = _state_prep.StatePreparation([0.5, -0.5, 0.5, 0.5]) + assert gate1 == gate2 + assert hash(gate1) == hash(gate2) + assert gate1 != gate3 + assert gate1 != X + + +def test_str(): + gate1 = _state_prep.StatePreparation([0, 1]) + assert str(gate1) == "StatePreparation" diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index 26d7f2752..f4437fa8d 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -25,6 +25,7 @@ r2rzandph, rx2rz, ry2rz, + stateprep2cnot, swap2cnot, toffoli2cnotandtgate, time_evolution, @@ -45,6 +46,7 @@ r2rzandph, rx2rz, ry2rz, + stateprep2cnot, swap2cnot, toffoli2cnotandtgate, time_evolution, diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py new file mode 100644 index 000000000..37b094c4a --- /dev/null +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -0,0 +1,79 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Registers decomposition for StatePreparation. +""" + +import cmath +import math + +from projectq.cengines import DecompositionRule +from projectq.meta import Control, Dagger +from projectq.ops import (StatePreparation, Ry, Rz, UniformlyControlledRy, + UniformlyControlledRz, Ph) + + +def _decompose_state_preparation(cmd): + """ + Implements state preparation based on arXiv:quant-ph/0407010v1. + """ + eng = cmd.engine + assert len(cmd.qubits) == 1 + num_qubits = len(cmd.qubits[0]) + qureg = cmd.qubits[0] + final_state = cmd.gate.final_state + if len(final_state) != 2**num_qubits: + raise ValueError("Length of final_state is invalid.") + with Control(eng, cmd.control_qubits): + # As in the paper reference, we implement the inverse: + with Dagger(eng): + # Cancel all the relative phases + phase_of_blocks = [] + for amplitude in final_state: + phase_of_blocks.append(cmath.phase(amplitude)) + 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] + angles.append(phase0 - phase1) + phase_of_next_blocks.append((phase0 + phase1)/2.) + UniformlyControlledRz(angles) | (qureg[(target_qubit+1):], + qureg[target_qubit]) + phase_of_blocks = phase_of_next_blocks + # Cancel global phase + Ph(-phase_of_blocks[0]) | qureg[-1] + # Remove amplitudes from states which contain a bit value 1: + abs_of_blocks = [] + for amplitude in final_state: + abs_of_blocks.append(abs(amplitude)) + 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] + 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]) + abs_of_blocks = abs_of_next_blocks + + +#: Decomposition rules +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 new file mode 100644 index 000000000..a514b44e4 --- /dev/null +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -0,0 +1,64 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for projectq.setups.decompositions.stateprep2cnot.""" + +import cmath +from copy import deepcopy +import math + +import numpy as np +import pytest + +import projectq +from projectq.ops import All, Command, Measure, Ry, Rz, StatePreparation, Ph +from projectq.setups import restrictedgateset +from projectq.types import WeakQubitRef + +import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot + + +def test_wrong_final_state(): + 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) + + +@pytest.mark.parametrize("n_qubits", [1, 2, 3, 4]) +def test_state_preparation(n_qubits): + 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)] + norm = 0 + for amplitude in f_state: + norm += abs(amplitude)**2 + f_state = [x / math.sqrt(norm) for x in f_state] + + StatePreparation(f_state) | qureg + eng.flush() + + wavefunction = deepcopy(eng.backend.cheat()[1]) + # Test that simulator hasn't reordered wavefunction + mapping = eng.backend.cheat()[0] + for key in mapping: + assert mapping[key] == key + All(Measure) | qureg + eng.flush() + assert np.allclose(wavefunction, f_state, rtol=1e-10, atol=1e-10) From 11f45873e20c2fa78fab2b59b5b460ac5f595fc6 Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Sun, 12 Aug 2018 20:53:46 +0200 Subject: [PATCH 008/113] Fix StatePreparation division by 0 and update docs (#255) --- docs/projectq.ops.rst | 3 +++ projectq/setups/decompositions/stateprep2cnot.py | 6 +++++- projectq/setups/decompositions/stateprep2cnot_test.py | 6 +++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/projectq.ops.rst b/docs/projectq.ops.rst index e04ca2e91..8f8d4cceb 100755 --- a/docs/projectq.ops.rst +++ b/docs/projectq.ops.rst @@ -49,6 +49,9 @@ The operations collection consists of various default gates and is a work-in-pro projectq.ops.CZ projectq.ops.Toffoli projectq.ops.TimeEvolution + projectq.ops.UniformlyControlledRy + projectq.ops.UniformlyControlledRz + projectq.ops.StatePreparation Module contents diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py index 37b094c4a..0df2c372b 100644 --- a/projectq/setups/decompositions/stateprep2cnot.py +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -66,7 +66,11 @@ def _decompose_state_preparation(cmd): for block in range(2**(len(qureg)-target_qubit-1)): a0 = abs_of_blocks[2*block] a1 = abs_of_blocks[2*block+1] - angles.append(-2. * math.acos(a0/math.sqrt(a0**2 + a1**2))) + 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]) diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index a514b44e4..402937b50 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -37,8 +37,9 @@ def test_wrong_final_state(): stateprep2cnot._decompose_state_preparation(cmd) +@pytest.mark.parametrize("zeros", [True, False]) @pytest.mark.parametrize("n_qubits", [1, 2, 3, 4]) -def test_state_preparation(n_qubits): +def test_state_preparation(n_qubits, zeros): engine_list = restrictedgateset.get_engine_list( one_qubit_gates=(Ry, Rz, Ph)) eng = projectq.MainEngine(engine_list=engine_list) @@ -46,6 +47,9 @@ def test_state_preparation(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)] + if zeros: + for i in range(2**(n_qubits-1)): + f_state[i] = 0 norm = 0 for amplitude in f_state: norm += abs(amplitude)**2 From db8bee59a34b69bcfd934079a0a2105e4e813d6e Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Tue, 14 Aug 2018 14:29:23 +0200 Subject: [PATCH 009/113] Check for more user errors (normalized state) and extend collapse_wavefunction input (#256) --- .travis.yml | 2 +- projectq/backends/_sim/_simulator.py | 4 ++-- projectq/setups/decompositions/stateprep2cnot.py | 5 +++++ projectq/setups/decompositions/stateprep2cnot_test.py | 3 +++ pytest.ini | 4 ++++ 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 89700c84f..fbe009436 100755 --- a/.travis.yml +++ b/.travis.yml @@ -43,7 +43,7 @@ install: - pip$PY install -e . # command to run tests -script: export OMP_NUM_THREADS=1 && pytest -W error projectq --cov projectq +script: export OMP_NUM_THREADS=1 && pytest projectq --cov projectq after_success: - coveralls diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index 883cc5cfe..500c0954d 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -303,7 +303,7 @@ def collapse_wavefunction(self, qureg, values): Args: qureg (Qureg|list[Qubit]): Qubits to collapse. - values (list[bool]): Measurement outcome for each of the qubits + values (list[bool|int]|string[0|1]): Measurement outcome for each of the qubits in `qureg`. Raises: @@ -321,7 +321,7 @@ def collapse_wavefunction(self, qureg, values): """ qureg = self._convert_logical_to_mapped_qureg(qureg) return self._simulator.collapse_wavefunction([qb.id for qb in qureg], - [bool(v) for v in + [bool(int(v)) for v in values]) def cheat(self): diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py index 0df2c372b..aa81c2fb7 100644 --- a/projectq/setups/decompositions/stateprep2cnot.py +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -36,6 +36,11 @@ def _decompose_state_preparation(cmd): final_state = cmd.gate.final_state if len(final_state) != 2**num_qubits: raise ValueError("Length of final_state is invalid.") + norm = 0. + for amplitude in final_state: + 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): # As in the paper reference, we implement the inverse: with Dagger(eng): diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index 402937b50..e07cd73cc 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -35,6 +35,9 @@ def test_wrong_final_state(): cmd = Command(None, StatePreparation([0, 1j]), qubits=([qb0, qb1],)) with pytest.raises(ValueError): stateprep2cnot._decompose_state_preparation(cmd) + cmd2 = Command(None, StatePreparation([0, 0.999j]), qubits=([qb0, qb1],)) + with pytest.raises(ValueError): + stateprep2cnot._decompose_state_preparation(cmd2) @pytest.mark.parametrize("zeros", [True, False]) diff --git a/pytest.ini b/pytest.ini index e5ac5982e..fab634b12 100755 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,6 @@ [pytest] testpaths = projectq + +filterwarnings = + error + ignore:the matrix subclass is not the recommended way:PendingDeprecationWarning From e0917d14f818a334cc41371d8f85ac05bbf923e1 Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Mon, 20 Aug 2018 18:36:43 +0200 Subject: [PATCH 010/113] Decomposition for SqrtSwap (#262) --- projectq/setups/decompositions/__init__.py | 2 + .../setups/decompositions/sqrtswap2cnot.py | 44 +++++++++++ .../decompositions/sqrtswap2cnot_test.py | 75 +++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 projectq/setups/decompositions/sqrtswap2cnot.py create mode 100644 projectq/setups/decompositions/sqrtswap2cnot_test.py diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index f4437fa8d..e2f38a7d3 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -25,6 +25,7 @@ r2rzandph, rx2rz, ry2rz, + sqrtswap2cnot, stateprep2cnot, swap2cnot, toffoli2cnotandtgate, @@ -46,6 +47,7 @@ r2rzandph, rx2rz, ry2rz, + sqrtswap2cnot, stateprep2cnot, swap2cnot, toffoli2cnotandtgate, diff --git a/projectq/setups/decompositions/sqrtswap2cnot.py b/projectq/setups/decompositions/sqrtswap2cnot.py new file mode 100644 index 000000000..4eed87ff6 --- /dev/null +++ b/projectq/setups/decompositions/sqrtswap2cnot.py @@ -0,0 +1,44 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Registers a decomposition to achieve a SqrtSwap gate. +""" + +from projectq.cengines import DecompositionRule +from projectq.meta import Compute, Control, Uncompute +from projectq.ops import CNOT, SqrtSwap, SqrtX + + +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) + ctrl = cmd.control_qubits + qubit0 = cmd.qubits[0][0] + qubit1 = cmd.qubits[1][0] + eng = cmd.engine + + with Control(eng, ctrl): + with Compute(eng): + CNOT | (qubit0, qubit1) + with Control(eng, qubit1): + SqrtX | qubit0 + Uncompute(eng) + + +#: Decomposition rules +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 new file mode 100644 index 000000000..b804e7e81 --- /dev/null +++ b/projectq/setups/decompositions/sqrtswap2cnot_test.py @@ -0,0 +1,75 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for projectq.setups.decompositions.sqrtswap2cnot.""" + +import pytest + +import projectq +from projectq import MainEngine +from projectq.backends import Simulator +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 + + +def _decomp_gates(eng, cmd): + if isinstance(cmd.gate, SqrtSwap.__class__): + return False + return True + + +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) + 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_sim = test_eng.backend + correct_sim = correct_eng.backend + correct_qureg = correct_eng.allocate_qureg(2) + correct_eng.flush() + test_qureg = test_eng.allocate_qureg(2) + test_eng.flush() + + correct_sim.set_wavefunction(basis_state, correct_qureg) + test_sim.set_wavefunction(basis_state, test_qureg) + + SqrtSwap | (test_qureg[0], test_qureg[1]) + test_eng.flush() + SqrtSwap | (correct_qureg[0], correct_qureg[1]) + correct_eng.flush() + + 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) + correct = correct_sim.get_amplitude(binary_state, correct_qureg) + assert correct == pytest.approx(test, rel=1e-10, abs=1e-10) + + All(Measure) | test_qureg + All(Measure) | correct_qureg + test_eng.flush(deallocate_qubits=True) + correct_eng.flush(deallocate_qubits=True) From 3eaab56f6b8a6497a6ccc586b9f08d5ba228728e Mon Sep 17 00:00:00 2001 From: Thomas Haener Date: Sun, 26 Aug 2018 09:56:19 +0200 Subject: [PATCH 011/113] Allow running trivial circuits on IBM Q, as long as there is at least one measurement. (#265) --- projectq/backends/_ibm/_ibm.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index 1bae69722..d14dbc27f 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -232,14 +232,16 @@ def _run(self): Send the circuit via the IBM API (JSON QASM) using the provided user data / ask for username & password. """ - if self.qasm == "": - return # 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) + # return if no operations / measurements have been performed. + if self.qasm == "": + return + max_qubit_id = max(self._allocated_qubits) qasm = ("\ninclude \"qelib1.inc\";\nqreg q[{nq}];\ncreg c[{nq}];" + self.qasm).format(nq=max_qubit_id + 1) From 371ead650594b4a81601849521b0165ab3d87d81 Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Sun, 26 Aug 2018 10:40:16 +0200 Subject: [PATCH 012/113] Allow to apply unitary qubit operators to qubits (#263) --- projectq/backends/_sim/_simulator.py | 4 +- .../libs/revkit/_control_function_test.py | 1 + projectq/ops/_qubit_operator.py | 167 +++++++++++++++++- projectq/ops/_qubit_operator_test.py | 104 +++++++++++ projectq/setups/decompositions/__init__.py | 2 + .../setups/decompositions/qubitop2onequbit.py | 48 +++++ .../decompositions/qubitop2onequbit_test.py | 100 +++++++++++ .../decompositions/stateprep2cnot_test.py | 2 +- 8 files changed, 424 insertions(+), 4 deletions(-) create mode 100644 projectq/setups/decompositions/qubitop2onequbit.py create mode 100644 projectq/setups/decompositions/qubitop2onequbit_test.py diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index 500c0954d..2218c3471 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -303,8 +303,8 @@ def collapse_wavefunction(self, qureg, values): Args: qureg (Qureg|list[Qubit]): Qubits to collapse. - values (list[bool|int]|string[0|1]): Measurement outcome for each of the qubits - in `qureg`. + values (list[bool|int]|string[0|1]): Measurement outcome for each + of the qubits in `qureg`. Raises: RuntimeError: If an outcome has probability (approximately) 0 or diff --git a/projectq/libs/revkit/_control_function_test.py b/projectq/libs/revkit/_control_function_test.py index 36164a0b7..84ee3cc41 100644 --- a/projectq/libs/revkit/_control_function_test.py +++ b/projectq/libs/revkit/_control_function_test.py @@ -41,6 +41,7 @@ def test_control_function_majority(): assert len(saving_backend.received_commands) == 7 + def test_control_function_majority_from_python(): dormouse = pytest.importorskip('dormouse') diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index c38bd2f57..aa9a29f9a 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -13,11 +13,16 @@ # 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 @@ -45,7 +50,7 @@ class QubitOperatorError(Exception): pass -class QubitOperator(object): +class QubitOperator(BasicGate): """ A sum of terms acting on qubits, e.g., 0.5 * 'X0 X5' + 0.3 * 'Z1 Z2'. @@ -68,6 +73,25 @@ class QubitOperator(object): hamiltonian = 0.5 * QubitOperator('X0 X5') + 0.3 * QubitOperator('Z0') + Our Simulator takes a hermitian QubitOperator to directly calculate the + expectation value (see Simulator.get_expectation_value) of this observable. + + A hermitian QubitOperator can also be used as input for the + TimeEvolution gate. + + If the QubitOperator is unitary, i.e., it contains only one term with a + coefficient, whose absolute value is 1, then one can apply 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 + + Attributes: terms (dict): **key**: A term represented by a tuple containing all non-trivial local Pauli operators ('X', 'Y', or 'Z'). @@ -128,6 +152,7 @@ def __init__(self, term=None, coefficient=1.): Raises: QubitOperatorError: Invalid operators provided to QubitOperator. """ + BasicGate.__init__(self) if not isinstance(coefficient, (int, float, complex)): raise ValueError('Coefficient must be a numeric type.') self.terms = {} @@ -226,6 +251,143 @@ def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): return False return True + def __or__(self, qubits): + """ + Operator| overload which enables the following syntax: + + .. code-block:: python + + QubitOperator(...) | qureg + QubitOperator(...) | (qureg,) + QubitOperator(...) | qubit + QubitOperator(...) | (qubit,) + + Unlike other gates, this gate is only allowed to be applied to one + quantum register or one qubit and only if the QubitOperator is + unitary, i.e., consists of one term with a coefficient whose absolute + values is 1. + + Example: + + .. code-block:: python + + eng = projectq.MainEngine() + qureg = eng.allocate_qureg(6) + QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5 + # with an additional global + # phase of 1.j + + While in the above example the QubitOperator gate is applied to 6 + qubits, it only acts non-trivially on the two qubits qureg[0] and + qureg[5]. Therefore, the operator| will create a new rescaled + QubitOperator, i.e, it sends the equivalent of the following new gate + to the MainEngine: + + .. code-block:: python + + QubitOperator('X0 X1', 1.j) | [qureg[0], qureg[5]] + + which is only a two qubit gate. + + Args: + qubits: one Qubit object, one list of Qubit objects, one Qureg + object, or a tuple of the former three cases. + + Raises: + TypeError: If QubitOperator is not unitary or applied to more than + one quantum register. + ValueError: If quantum register does not have enough qubits + """ + # Check that input is only one qureg or one qubit + qubits = self.make_tuple_of_qureg(qubits) + if len(qubits) != 1: + raise TypeError("Only one qubit or qureg allowed.") + # Check that operator is unitary + if not len(self.terms) == 1: + raise TypeError("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.") + # Test if we need to apply only Ph + if term == (): + Ph(phase) | qubits[0][0] + return + # Check that Qureg has enough qubits: + num_qubits = len(qubits[0]) + non_trivial_qubits = set() + for index, action in term: + non_trivial_qubits.add(index) + if max(non_trivial_qubits) >= num_qubits: + raise ValueError("QubitOperator acts on more qubits than the gate " + "is applied to.") + # Apply X, Y, Z, if QubitOperator acts only on one qubit + if len(term) == 1: + if term[0][1] == "X": + X | qubits[0][term[0][0]] + elif term[0][1] == "Y": + Y | qubits[0][term[0][0]] + elif term[0][1] == "Z": + Z | qubits[0][term[0][0]] + Ph(phase) | qubits[0][term[0][0]] + return + # Create new QubitOperator gate with rescaled qubit indices in + # 0,..., len(non_trivial_qubits) - 1 + new_index = dict() + non_trivial_qubits = sorted(list(non_trivial_qubits)) + for i in range(len(non_trivial_qubits)): + new_index[non_trivial_qubits[i]] = i + new_qubitoperator = QubitOperator() + assert len(new_qubitoperator.terms) == 0 + new_term = tuple([(new_index[index], action) + for index, action in term]) + new_qubitoperator.terms[new_term] = coefficient + new_qubits = [qubits[0][i] for i in non_trivial_qubits] + # Apply new gate + cmd = new_qubitoperator.generate_command(new_qubits) + apply_command(cmd) + + def get_inverse(self): + """ + Return the inverse gate of a QubitOperator if applied as a gate. + + Raises: + NotInvertible: Not implemented for QubitOperators which have + multiple terms or a coefficient with absolute value + not equal to 1. + """ + + if len(self.terms) == 1: + (term, coefficient), = self.terms.items() + 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): + """ + Return this gate merged with another gate. + + Standard implementation of get_merged: + + Raises: + NotMergeable: merging is not possible + """ + if (isinstance(other, self.__class__) and + len(other.terms) == 1 and + len(self.terms) == 1): + return self * other + else: + raise NotMergeable() + def __imul__(self, multiplier): """ In-place multiply (*=) terms with scalar or QubitOperator. @@ -463,3 +625,6 @@ def __str__(self): def __repr__(self): return str(self) + + def __hash__(self): + return hash(str(self)) diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index 12e1d19dd..76c832bf8 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -13,11 +13,18 @@ # limitations under the License. """Tests for _qubit_operator.py.""" +import cmath import copy +import math import numpy import pytest +from projectq import MainEngine +from projectq.cengines import DummyEngine +from ._basics import NotInvertible, NotMergeable +from ._gates import Ph, T, X, Y, Z + from projectq.ops import _qubit_operator as qo @@ -184,6 +191,98 @@ def test_isclose_different_num_terms(): assert not a.isclose(b, rel_tol=1e-12, abs_tol=0.05) +def test_get_inverse(): + qo0 = qo.QubitOperator("X1 Z2", cmath.exp(0.6j)) + qo1 = qo.QubitOperator("", 1j) + assert qo0.get_inverse().isclose( + qo.QubitOperator("X1 Z2", cmath.exp(-0.6j))) + assert qo1.get_inverse().isclose(qo.QubitOperator("", -1j)) + qo0 += qo1 + with pytest.raises(NotInvertible): + qo0.get_inverse() + + +def test_get_merged(): + qo0 = qo.QubitOperator("X1 Z2", 1j) + qo1 = qo.QubitOperator("Y3", 1j) + merged = qo0.get_merged(qo1) + assert qo0.isclose(qo.QubitOperator("X1 Z2", 1j)) + assert qo1.isclose(qo.QubitOperator("Y3", 1j)) + assert qo0.get_merged(qo1).isclose(qo.QubitOperator("X1 Z2 Y3", -1)) + with pytest.raises(NotMergeable): + qo1.get_merged(T) + qo2 = qo0 + qo1 + with pytest.raises(NotMergeable): + qo2.get_merged(qo0) + with pytest.raises(NotMergeable): + qo0.get_merged(qo2) + + +def test_or_one_qubit(): + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend, engine_list=[]) + qureg = eng.allocate_qureg(3) + eng.flush() + identity = qo.QubitOperator("", 1j) + x = qo.QubitOperator("X1", cmath.exp(0.5j)) + y = qo.QubitOperator("Y2", cmath.exp(0.6j)) + z = qo.QubitOperator("Z0", cmath.exp(4.5j)) + identity | qureg + eng.flush() + x | qureg + eng.flush() + y | qureg + eng.flush() + z | qureg + eng.flush() + assert saving_backend.received_commands[4].gate == Ph(math.pi/2.) + + assert saving_backend.received_commands[6].gate == X + assert saving_backend.received_commands[6].qubits == ([qureg[1]],) + assert saving_backend.received_commands[7].gate == Ph(0.5) + assert saving_backend.received_commands[7].qubits == ([qureg[1]],) + + assert saving_backend.received_commands[9].gate == Y + assert saving_backend.received_commands[9].qubits == ([qureg[2]],) + assert saving_backend.received_commands[10].gate == Ph(0.6) + assert saving_backend.received_commands[10].qubits == ([qureg[2]],) + + assert saving_backend.received_commands[12].gate == Z + assert saving_backend.received_commands[12].qubits == ([qureg[0]],) + assert saving_backend.received_commands[13].gate == Ph(4.5) + assert saving_backend.received_commands[13].qubits == ([qureg[0]],) + + +def test_wrong_input(): + eng = MainEngine() + qureg = eng.allocate_qureg(3) + op0 = qo.QubitOperator("X1", 0.99) + with pytest.raises(TypeError): + op0 | qureg + op1 = qo.QubitOperator("X2", 1) + with pytest.raises(ValueError): + op1 | qureg[1] + with pytest.raises(TypeError): + op0 | (qureg[1], qureg[2]) + op2 = op0 + op1 + with pytest.raises(TypeError): + op2 | qureg + + +def test_rescaling_of_indices(): + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend, engine_list=[]) + qureg = eng.allocate_qureg(4) + eng.flush() + op = qo.QubitOperator("X0 Y1 Z3", 1j) + op | qureg + eng.flush() + assert saving_backend.received_commands[5].gate.isclose( + qo.QubitOperator("X0 Y1 Z2", 1j)) + # test that gate creates a new QubitOperator + assert op.isclose(qo.QubitOperator("X0 Y1 Z3", 1j)) + + def test_imul_inplace(): qubit_op = qo.QubitOperator("X1") prev_id = id(qubit_op) @@ -444,6 +543,11 @@ def test_str(): assert str(op2) == "2 I" +def test_hash(): + op = qo.QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) + assert hash(op) == hash("0.5 X1 Y3 Z8") + + def test_str_empty(): op = qo.QubitOperator() assert str(op) == '0' diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index e2f38a7d3..aab71b28c 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -21,6 +21,7 @@ entangle, globalphase, ph2r, + qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, @@ -43,6 +44,7 @@ entangle, globalphase, ph2r, + qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, diff --git a/projectq/setups/decompositions/qubitop2onequbit.py b/projectq/setups/decompositions/qubitop2onequbit.py new file mode 100644 index 000000000..b66f64b25 --- /dev/null +++ b/projectq/setups/decompositions/qubitop2onequbit.py @@ -0,0 +1,48 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import cmath + +from projectq.cengines import DecompositionRule +from projectq.meta import Control, get_control_count +from projectq.ops import Ph, QubitOperator, X, Y, Z + + +def _recognize_qubitop(cmd): + """ For efficiency only use this if at most 1 control qubit.""" + return get_control_count(cmd) <= 1 + + +def _decompose_qubitop(cmd): + assert len(cmd.qubits) == 1 + qureg = cmd.qubits[0] + eng = cmd.engine + qubit_op = cmd.gate + with Control(eng, cmd.control_qubits): + (term, coefficient), = qubit_op.terms.items() + phase = cmath.phase(coefficient) + Ph(phase) | qureg[0] + for index, action in term: + if action == "X": + X | qureg[index] + elif action == "Y": + Y | qureg[index] + elif action == "Z": + Z | qureg[index] + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(QubitOperator, _decompose_qubitop, _recognize_qubitop) +] diff --git a/projectq/setups/decompositions/qubitop2onequbit_test.py b/projectq/setups/decompositions/qubitop2onequbit_test.py new file mode 100644 index 000000000..3b1741cfb --- /dev/null +++ b/projectq/setups/decompositions/qubitop2onequbit_test.py @@ -0,0 +1,100 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import cmath + +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) +from projectq.meta import Control +from projectq.ops import All, Measure, Ph, QubitOperator, X, Y, Z + + +import projectq.setups.decompositions.qubitop2onequbit as qubitop2onequbit + + +def test_recognize(): + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend, engine_list=[]) + ctrl_qureg = eng.allocate_qureg(2) + qureg = eng.allocate_qureg(2) + with Control(eng, ctrl_qureg): + QubitOperator("X0 Y1") | qureg + with Control(eng, ctrl_qureg[0]): + QubitOperator("X0 Y1") | qureg + eng.flush() + cmd0 = saving_backend.received_commands[4] + cmd1 = saving_backend.received_commands[5] + assert not qubitop2onequbit._recognize_qubitop(cmd0) + assert qubitop2onequbit._recognize_qubitop(cmd1) + + +def _decomp_gates(eng, cmd): + if isinstance(cmd.gate, QubitOperator): + return False + else: + return True + + +def test_qubitop2singlequbit(): + num_qubits = 4 + random_initial_state = [0.2+0.1*x*cmath.exp(0.1j+0.2j*x) + for x in range(2**(num_qubits+1))] + rule_set = DecompositionRuleSet(modules=[qubitop2onequbit]) + test_eng = MainEngine(backend=Simulator(), + engine_list=[AutoReplacer(rule_set), + InstructionFilter(_decomp_gates)]) + test_qureg = test_eng.allocate_qureg(num_qubits) + test_ctrl_qb = test_eng.allocate_qubit() + test_eng.flush() + test_eng.backend.set_wavefunction(random_initial_state, + test_qureg + test_ctrl_qb) + correct_eng = MainEngine() + correct_qureg = correct_eng.allocate_qureg(num_qubits) + correct_ctrl_qb = correct_eng.allocate_qubit() + correct_eng.flush() + correct_eng.backend.set_wavefunction(random_initial_state, + correct_qureg + correct_ctrl_qb) + + qubit_op_0 = QubitOperator("X0 Y1 Z3", -1.j) + qubit_op_1 = QubitOperator("Z0 Y1 X3", cmath.exp(0.6j)) + + qubit_op_0 | test_qureg + with Control(test_eng, test_ctrl_qb): + qubit_op_1 | test_qureg + test_eng.flush() + + correct_eng.backend.apply_qubit_operator(qubit_op_0, correct_qureg) + with Control(correct_eng, correct_ctrl_qb): + Ph(0.6) | correct_qureg[0] + Z | correct_qureg[0] + Y | correct_qureg[1] + X | correct_qureg[3] + correct_eng.flush() + + for fstate in range(2**(num_qubits+1)): + binary_state = format(fstate, '0' + str(num_qubits+1) + 'b') + test = test_eng.backend.get_amplitude(binary_state, + test_qureg + test_ctrl_qb) + correct = correct_eng.backend.get_amplitude( + binary_state, correct_qureg + correct_ctrl_qb) + assert correct == pytest.approx(test, rel=1e-10, abs=1e-10) + + All(Measure) | correct_qureg + correct_ctrl_qb + All(Measure) | test_qureg + test_ctrl_qb + correct_eng.flush() + test_eng.flush() diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index e07cd73cc..81137b169 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -35,7 +35,7 @@ def test_wrong_final_state(): cmd = Command(None, StatePreparation([0, 1j]), qubits=([qb0, qb1],)) with pytest.raises(ValueError): stateprep2cnot._decompose_state_preparation(cmd) - cmd2 = Command(None, StatePreparation([0, 0.999j]), qubits=([qb0, qb1],)) + cmd2 = Command(None, StatePreparation([0, 0.999j]), qubits=([qb0],)) with pytest.raises(ValueError): stateprep2cnot._decompose_state_preparation(cmd2) From 95cd679776460041e070c7db518b03a76819fb1a Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Sun, 26 Aug 2018 11:02:22 +0200 Subject: [PATCH 013/113] Quantum Algorithm for Spectral Measurement with Lower Gate Count (#264) --- examples/spectral_measurement.ipynb | 467 ++++++++++++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 examples/spectral_measurement.ipynb diff --git a/examples/spectral_measurement.ipynb b/examples/spectral_measurement.ipynb new file mode 100644 index 000000000..0c791ecc0 --- /dev/null +++ b/examples/spectral_measurement.ipynb @@ -0,0 +1,467 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quantum Algorithm for Spectral Measurement with Lower Gate Count\n", + "\n", + "This tutorial shows how to implement the algorithm introduced in the following paper:\n", + "\n", + "**Quantum Algorithm for Spectral Measurement with Lower Gate Count**\n", + "by David Poulin, Alexei Kitaev, Damian S. Steiger, Matthew B. Hastings, Matthias Troyer\n", + "[Phys. Rev. Lett. 121, 010501 (2018)](https://doi.org/10.1103/PhysRevLett.121.010501)\n", + "([arXiv:1711.11025](https://arxiv.org/abs/1711.11025))\n", + "\n", + "For details please see the above paper. The implementation in ProjectQ is discussed in the PhD thesis of Damian S. Steiger (soon available online). A more detailed discussion will be uploaded soon.\n", + "Here we only show a small part of the paper, namely the implementation of W and how it can be used with iterative phase estimation to obtain eigenvalues and eigenstates." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from copy import deepcopy\n", + "import math\n", + "\n", + "import scipy.sparse.linalg as spsl\n", + "\n", + "import projectq\n", + "from projectq.backends import Simulator\n", + "from projectq.meta import Compute, Control, Dagger, Uncompute\n", + "from projectq.ops import All, H, Measure, Ph, QubitOperator, R, StatePreparation, X, Z" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. Let's use a simple Hamiltonian acting on 3 qubits for which we want to know the eigenvalues:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "num_qubits = 3\n", + "\n", + "hamiltonian = QubitOperator()\n", + "hamiltonian += QubitOperator(\"X0\", -1/12.)\n", + "hamiltonian += QubitOperator(\"X1\", -1/12.)\n", + "hamiltonian += QubitOperator(\"X2\", -1/12.)\n", + "hamiltonian += QubitOperator(\"Z0 Z1\", -1/12.)\n", + "hamiltonian += QubitOperator(\"Z0 Z2\", -1/12.)\n", + "hamiltonian += QubitOperator(\"\", 7/12.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this quantum algorithm, we need to normalize the hamiltonian:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "hamiltonian_norm = 0.\n", + "for term in hamiltonian.terms:\n", + " hamiltonian_norm += abs(hamiltonian.terms[term])\n", + "normalized_hamiltonian = deepcopy(hamiltonian)\n", + "normalized_hamiltonian /= hamiltonian_norm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**1.** We implement a short helper function which uses the ProjectQ simulator to numerically calculate some eigenvalues and eigenvectors of Hamiltonians stored in ProjectQ's `QubitOperator` in order to check our implemenation of the quantum algorithm. This function is particularly fast because it doesn't need to build the matrix of the hamiltonian but instead uses implicit matrix vector multiplication by using our simulator:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def get_eigenvalue_and_eigenvector(n_sites, hamiltonian, k, which='SA'):\n", + " \"\"\"\n", + " Returns k eigenvalues and eigenvectors of the hamiltonian.\n", + " \n", + " Args:\n", + " n_sites(int): Number of qubits/sites in the hamiltonian\n", + " hamiltonian(QubitOperator): QubitOperator representating the Hamiltonian\n", + " k: num of eigenvalue and eigenvector pairs (see spsl.eigsh k)\n", + " which: see spsl.eigsh which\n", + " \n", + " \"\"\"\n", + " def mv(v):\n", + " eng = projectq.MainEngine(backend=Simulator(), engine_list=[])\n", + " qureg = eng.allocate_qureg(n_sites)\n", + " eng.flush()\n", + " eng.backend.set_wavefunction(v, qureg)\n", + " eng.backend.apply_qubit_operator(hamiltonian, qureg)\n", + " order, output = deepcopy(eng.backend.cheat())\n", + " for i in order:\n", + " assert i == order[i]\n", + " eng.backend.set_wavefunction([1]+[0]*(2**n_sites-1), qureg)\n", + " return output\n", + "\n", + " A = spsl.LinearOperator((2**n_sites,2**n_sites), matvec=mv)\n", + "\n", + " eigenvalues, eigenvectormatrix = spsl.eigsh(A, k=k, which=which)\n", + " eigenvectors = []\n", + " for i in range(k):\n", + " eigenvectors.append(list(eigenvectormatrix[:, i]))\n", + " return eigenvalues, eigenvectors" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use this function to find the 4 lowest eigenstates of the normalized hamiltonian:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0.29217007 0.36634371 0.5 0.57417364]\n" + ] + } + ], + "source": [ + "eigenvalues, eigenvectors = get_eigenvalue_and_eigenvector(\n", + " n_sites=num_qubits,\n", + " hamiltonian=normalized_hamiltonian,\n", + " k=4)\n", + "print(eigenvalues)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that the eigenvalues are all positive as required (otherwise increase identity term in hamiltonian)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**2.** Let's define the W operator:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def W(eng, individual_terms, initial_wavefunction, ancilla_qubits, system_qubits):\n", + " \"\"\"\n", + " Applies the W operator as defined in arXiv:1711.11025.\n", + " \n", + " Args:\n", + " eng(MainEngine): compiler engine\n", + " individual_terms(list): list of individual unitary\n", + " QubitOperators. It applies\n", + " individual_terms[0] if ancilla\n", + " qubits are in state |0> where\n", + " ancilla_qubits[0] is the least\n", + " significant bit.\n", + " initial_wavefunction: Initial wavefunction of the ancilla qubits\n", + " ancilla_qubits(Qureg): ancilla quantum register in state |0>\n", + " system_qubits(Qureg): system quantum register\n", + " \"\"\"\n", + " # Apply V:\n", + " for ancilla_state in range(len(individual_terms)):\n", + " with Compute(eng):\n", + " for bit_pos in range(len(ancilla_qubits)):\n", + " if not (ancilla_state >> bit_pos) & 1:\n", + " X | ancilla_qubits[bit_pos]\n", + " with Control(eng, ancilla_qubits):\n", + " individual_terms[ancilla_state] | system_qubits\n", + " Uncompute(eng)\n", + " # Apply S: 1) Apply B^dagger\n", + " with Compute(eng):\n", + " with Dagger(eng):\n", + " StatePreparation(initial_wavefunction) | ancilla_qubits\n", + " # Apply S: 2) Apply I-2|0><0|\n", + " with Compute(eng):\n", + " All(X) | ancilla_qubits\n", + " with Control(eng, ancilla_qubits[:-1]):\n", + " Z | ancilla_qubits[-1]\n", + " Uncompute(eng)\n", + " # Apply S: 3) Apply B\n", + " Uncompute(eng)\n", + " # Could also be omitted and added when calculating the eigenvalues:\n", + " Ph(math.pi) | system_qubits[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**3.** For testing this algorithm, let's initialize the qubits in a superposition state of the lowest and second lowest eigenstate of the hamiltonian:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "eng = projectq.MainEngine()\n", + "system_qubits = eng.allocate_qureg(num_qubits)\n", + "\n", + "# Create a normalized equal superposition of the two eigenstates for numerical testing:\n", + "initial_state_norm =0.\n", + "initial_state = [i+j for i,j in zip(eigenvectors[0], eigenvectors[1])]\n", + "for amplitude in initial_state:\n", + " initial_state_norm += abs(amplitude)**2\n", + "normalized_initial_state = [amp / math.sqrt(initial_state_norm) for amp in initial_state]\n", + "\n", + "#initialize system qubits in this state:\n", + "StatePreparation(normalized_initial_state) | system_qubits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**4.** Split the normalized_hamiltonian into individual terms and build the wavefunction for the ancilla qubits:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "individual_terms = []\n", + "initial_ancilla_wavefunction = []\n", + "for term in normalized_hamiltonian.terms:\n", + " coefficient = normalized_hamiltonian.terms[term]\n", + " initial_ancilla_wavefunction.append(math.sqrt(abs(coefficient)))\n", + " if coefficient < 0:\n", + " individual_terms.append(QubitOperator(term, -1))\n", + " else:\n", + " individual_terms.append(QubitOperator(term))\n", + "\n", + "# Calculate the number of ancilla qubits required and pad\n", + "# the ancilla wavefunction with zeros:\n", + "num_ancilla_qubits = int(math.ceil(math.log(len(individual_terms), 2)))\n", + "required_padding = 2**num_ancilla_qubits - len(initial_ancilla_wavefunction)\n", + "initial_ancilla_wavefunction.extend([0]*required_padding)\n", + "\n", + "# Initialize ancillas by applying B\n", + "ancillas = eng.allocate_qureg(num_ancilla_qubits)\n", + "StatePreparation(initial_ancilla_wavefunction) | ancillas" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**5.** Perform an iterative phase estimation of the unitary W to collapse to one of the eigenvalues of the `normalized_hamiltonian`:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "# Semiclassical iterative phase estimation\n", + "bits_of_precision = 8\n", + "pe_ancilla = eng.allocate_qubit()\n", + "\n", + "measurements = [0] * bits_of_precision\n", + "\n", + "for k in range(bits_of_precision):\n", + " H | pe_ancilla\n", + " with Control(eng, pe_ancilla):\n", + " for i in range(2**(bits_of_precision-k-1)):\n", + " W(eng=eng,\n", + " individual_terms=individual_terms,\n", + " initial_wavefunction=initial_ancilla_wavefunction,\n", + " ancilla_qubits=ancillas,\n", + " system_qubits=system_qubits)\n", + "\n", + " #inverse QFT using one qubit\n", + " for i in range(k):\n", + " if measurements[i]:\n", + " R(-math.pi/(1 << (k - i))) | pe_ancilla\n", + "\n", + " H | pe_ancilla\n", + " Measure | pe_ancilla\n", + " eng.flush()\n", + " measurements[k] = int(pe_ancilla)\n", + " # put the ancilla in state |0> again\n", + " if measurements[k]:\n", + " X | pe_ancilla\n", + "\n", + "est_phase = sum(\n", + " [(measurements[bits_of_precision - 1 - i]*1. / (1 << (i + 1)))\n", + " for i in range(bits_of_precision)])" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "We measured 0.203125 corresponding to energy 0.290284677254\n" + ] + } + ], + "source": [ + "print(\"We measured {} corresponding to energy {}\".format(est_phase, math.cos(2*math.pi*est_phase)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**6.** We measured the lowest eigenstate. You can verify that this happens with 50% probability as we chose our initial state to have 50% overlap with the ground state. As the paper notes, the `system_qubits` are not in an eigenstate and one can easily test that using our simulator to get the energy of the current state:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.33236578253447085" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng.backend.get_expectation_value(normalized_hamiltonian, system_qubits)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**7.** As explained in the paper, one can change this state into an eigenstate by undoing the `StatePreparation` of the ancillas and then by measuring if the ancilla qubits in are state 0. The paper says that this should be the case with 50% probability. So let's check this (we require an ancilla to measure this):" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.5004522593645913" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with Dagger(eng):\n", + " StatePreparation(initial_ancilla_wavefunction) | ancillas\n", + "measure_qb = eng.allocate_qubit()\n", + "with Compute(eng):\n", + " All(X) | ancillas\n", + "with Control(eng, ancillas):\n", + " X | measure_qb\n", + "Uncompute(eng)\n", + "eng.flush()\n", + "eng.backend.get_probability('1', measure_qb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, we would measure 1 (corresponding to the ancilla qubits in state 0) with probability 50% as explained in the paper. Let's assume we measure 1, then we can easily check that we are in an eigenstate of the `normalized_hamiltonian` by numerically calculating its energy:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.29263140625433537" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng.backend.collapse_wavefunction(measure_qb, [1])\n", + "eng.backend.get_expectation_value(normalized_hamiltonian, system_qubits)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indeed we are in the ground state of the `normalized_hamiltonian`. Have a look at the paper on how to recover, when the ancilla qubits are not in state 0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.15" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 704cb171e06c22cd677dea20c488678b829d9a79 Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Sun, 26 Aug 2018 14:24:26 +0200 Subject: [PATCH 014/113] Bumped version number to 0.4.1 --- projectq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/_version.py b/projectq/_version.py index e2835d3b6..02ac3660d 100755 --- a/projectq/_version.py +++ b/projectq/_version.py @@ -13,4 +13,4 @@ # limitations under the License. """Define version number here and read it from setup.py automatically""" -__version__ = "0.4" +__version__ = "0.4.1" From 86f2e221a1b1a3b3ec619647ca755db4f9336d63 Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Tue, 18 Sep 2018 08:38:58 +0200 Subject: [PATCH 015/113] Update to newer RevKit version. (#271) --- projectq/libs/revkit/_control_function.py | 2 +- projectq/libs/revkit/_permutation.py | 2 +- projectq/libs/revkit/_phase.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projectq/libs/revkit/_control_function.py b/projectq/libs/revkit/_control_function.py index 99f7b880d..87f4e3804 100644 --- a/projectq/libs/revkit/_control_function.py +++ b/projectq/libs/revkit/_control_function.py @@ -109,7 +109,7 @@ def __or__(self, qubits): "provided qubits") # convert reversible circuit to ProjectQ code and execute it - _exec(revkit.write_projectq(log=True)["contents"], qs) + _exec(revkit.to_projectq(mct=True), qs) def _check_function(self): """ diff --git a/projectq/libs/revkit/_permutation.py b/projectq/libs/revkit/_permutation.py index b4967528a..160f027d2 100644 --- a/projectq/libs/revkit/_permutation.py +++ b/projectq/libs/revkit/_permutation.py @@ -81,7 +81,7 @@ def __or__(self, qubits): self.kwargs.get("synth", revkit.tbs)() # convert reversible circuit to ProjectQ code and execute it - _exec(revkit.write_projectq(log=True)["contents"], qs) + _exec(revkit.to_projectq(mct=True), qs) def _check_permutation(self): """ diff --git a/projectq/libs/revkit/_phase.py b/projectq/libs/revkit/_phase.py index 4aadc319e..8a9e2ab24 100644 --- a/projectq/libs/revkit/_phase.py +++ b/projectq/libs/revkit/_phase.py @@ -111,7 +111,7 @@ def __or__(self, qubits): "provided qubits") # convert reversible circuit to ProjectQ code and execute it - _exec(revkit.write_projectq(log=True)["contents"], qs) + _exec(revkit.to_projectq(mct=True), qs) def _check_function(self): """ From 78bfb923ceae39174d5d982360292d0a53ff7efd Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Sun, 23 Sep 2018 09:53:42 +0200 Subject: [PATCH 016/113] Add VQE example (#274) --- .../variational_quantum_eigensolver.ipynb | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 examples/variational_quantum_eigensolver.ipynb diff --git a/examples/variational_quantum_eigensolver.ipynb b/examples/variational_quantum_eigensolver.ipynb new file mode 100644 index 000000000..b893a8698 --- /dev/null +++ b/examples/variational_quantum_eigensolver.ipynb @@ -0,0 +1,197 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Implementation of a Variational Quantum Eigensolver (VQE).\n", + "\n", + "The example shown here is from the paper \"Scalable Quantum Simulation of\n", + "Molecular Energies\" by P.J.J. O'Malley et al. [arXiv:1512.06860v2](https://arxiv.org/abs/1512.06860v2)\n", + "(Note that only the latest arXiv version contains the correct coefficients of\n", + " the Hamiltonian)\n", + "\n", + "Eq. 2 of the paper shows the functional which one needs to minimize and Eq. 3\n", + "shows the coupled cluster ansatz for the trial wavefunction (using the unitary\n", + "coupled cluster approach). The Hamiltonian is given in Eq. 1. The coefficients\n", + "can be found in Table 1. Note that both the ansatz and the Hamiltonian can be\n", + "calculated using FermiLib which is a library for simulating quantum systems\n", + "on top of ProjectQ." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import projectq\n", + "from projectq.ops import All, Measure, QubitOperator, TimeEvolution, X\n", + "\n", + "import matplotlib.pyplot as plt\n", + "from scipy.optimize import minimize_scalar\n", + "\n", + "# Data from paper (arXiv:1512.06860v2) table 1: R, I, Z0, Z1, Z0Z1, X0X1, Y0Y1\n", + "raw_data_table_1 = [\n", + " [0.20, 2.8489, 0.5678, -1.4508, 0.6799, 0.0791, 0.0791],\n", + " [0.25, 2.1868, 0.5449, -1.2870, 0.6719, 0.0798, 0.0798],\n", + " [0.30, 1.7252, 0.5215, -1.1458, 0.6631, 0.0806, 0.0806],\n", + " [0.35, 1.3827, 0.4982, -1.0226, 0.6537, 0.0815, 0.0815],\n", + " [0.40, 1.1182, 0.4754, -0.9145, 0.6438, 0.0825, 0.0825],\n", + " [0.45, 0.9083, 0.4534, -0.8194, 0.6336, 0.0835, 0.0835],\n", + " [0.50, 0.7381, 0.4325, -0.7355, 0.6233, 0.0846, 0.0846],\n", + " [0.55, 0.5979, 0.4125, -0.6612, 0.6129, 0.0858, 0.0858],\n", + " [0.60, 0.4808, 0.3937, -0.5950, 0.6025, 0.0870, 0.0870],\n", + " [0.65, 0.3819, 0.3760, -0.5358, 0.5921, 0.0883, 0.0883],\n", + " [0.70, 0.2976, 0.3593, -0.4826, 0.5818, 0.0896, 0.0896],\n", + " [0.75, 0.2252, 0.3435, -0.4347, 0.5716, 0.0910, 0.0910],\n", + " [0.80, 0.1626, 0.3288, -0.3915, 0.5616, 0.0925, 0.0925],\n", + " [0.85, 0.1083, 0.3149, -0.3523, 0.5518, 0.0939, 0.0939],\n", + " [0.90, 0.0609, 0.3018, -0.3168, 0.5421, 0.0954, 0.0954],\n", + " [0.95, 0.0193, 0.2895, -0.2845, 0.5327, 0.0970, 0.0970],\n", + " [1.00, -0.0172, 0.2779, -0.2550, 0.5235, 0.0986, 0.0986],\n", + " [1.05, -0.0493, 0.2669, -0.2282, 0.5146, 0.1002, 0.1002],\n", + " [1.10, -0.0778, 0.2565, -0.2036, 0.5059, 0.1018, 0.1018],\n", + " [1.15, -0.1029, 0.2467, -0.1810, 0.4974, 0.1034, 0.1034],\n", + " [1.20, -0.1253, 0.2374, -0.1603, 0.4892, 0.1050, 0.1050],\n", + " [1.25, -0.1452, 0.2286, -0.1413, 0.4812, 0.1067, 0.1067],\n", + " [1.30, -0.1629, 0.2203, -0.1238, 0.4735, 0.1083, 0.1083],\n", + " [1.35, -0.1786, 0.2123, -0.1077, 0.4660, 0.1100, 0.1100],\n", + " [1.40, -0.1927, 0.2048, -0.0929, 0.4588, 0.1116, 0.1116],\n", + " [1.45, -0.2053, 0.1976, -0.0792, 0.4518, 0.1133, 0.1133],\n", + " [1.50, -0.2165, 0.1908, -0.0666, 0.4451, 0.1149, 0.1149],\n", + " [1.55, -0.2265, 0.1843, -0.0549, 0.4386, 0.1165, 0.1165],\n", + " [1.60, -0.2355, 0.1782, -0.0442, 0.4323, 0.1181, 0.1181],\n", + " [1.65, -0.2436, 0.1723, -0.0342, 0.4262, 0.1196, 0.1196],\n", + " [1.70, -0.2508, 0.1667, -0.0251, 0.4204, 0.1211, 0.1211],\n", + " [1.75, -0.2573, 0.1615, -0.0166, 0.4148, 0.1226, 0.1226],\n", + " [1.80, -0.2632, 0.1565, -0.0088, 0.4094, 0.1241, 0.1241],\n", + " [1.85, -0.2684, 0.1517, -0.0015, 0.4042, 0.1256, 0.1256],\n", + " [1.90, -0.2731, 0.1472, 0.0052, 0.3992, 0.1270, 0.1270],\n", + " [1.95, -0.2774, 0.1430, 0.0114, 0.3944, 0.1284, 0.1284],\n", + " [2.00, -0.2812, 0.1390, 0.0171, 0.3898, 0.1297, 0.1297],\n", + " [2.05, -0.2847, 0.1352, 0.0223, 0.3853, 0.1310, 0.1310],\n", + " [2.10, -0.2879, 0.1316, 0.0272, 0.3811, 0.1323, 0.1323],\n", + " [2.15, -0.2908, 0.1282, 0.0317, 0.3769, 0.1335, 0.1335],\n", + " [2.20, -0.2934, 0.1251, 0.0359, 0.3730, 0.1347, 0.1347],\n", + " [2.25, -0.2958, 0.1221, 0.0397, 0.3692, 0.1359, 0.1359],\n", + " [2.30, -0.2980, 0.1193, 0.0432, 0.3655, 0.1370, 0.1370],\n", + " [2.35, -0.3000, 0.1167, 0.0465, 0.3620, 0.1381, 0.1381],\n", + " [2.40, -0.3018, 0.1142, 0.0495, 0.3586, 0.1392, 0.1392],\n", + " [2.45, -0.3035, 0.1119, 0.0523, 0.3553, 0.1402, 0.1402],\n", + " [2.50, -0.3051, 0.1098, 0.0549, 0.3521, 0.1412, 0.1412],\n", + " [2.55, -0.3066, 0.1078, 0.0572, 0.3491, 0.1422, 0.1422],\n", + " [2.60, -0.3079, 0.1059, 0.0594, 0.3461, 0.1432, 0.1432],\n", + " [2.65, -0.3092, 0.1042, 0.0614, 0.3433, 0.1441, 0.1441],\n", + " [2.70, -0.3104, 0.1026, 0.0632, 0.3406, 0.1450, 0.1450],\n", + " [2.75, -0.3115, 0.1011, 0.0649, 0.3379, 0.1458, 0.1458],\n", + " [2.80, -0.3125, 0.0997, 0.0665, 0.3354, 0.1467, 0.1467],\n", + " [2.85, -0.3135, 0.0984, 0.0679, 0.3329, 0.1475, 0.1475]]\n", + "\n", + "\n", + "def variational_quantum_eigensolver(theta, hamiltonian):\n", + " \"\"\"\n", + " Args:\n", + " theta (float): variational parameter for ansatz wavefunction\n", + " hamiltonian (QubitOperator): Hamiltonian of the system\n", + " Returns:\n", + " energy of the wavefunction for parameter theta\n", + " \"\"\"\n", + " # Create a ProjectQ compiler with a simulator as a backend\n", + " eng = projectq.MainEngine()\n", + " # Allocate 2 qubits in state |00>\n", + " wavefunction = eng.allocate_qureg(2)\n", + " # Initialize the Hartree Fock state |01>\n", + " X | wavefunction[0]\n", + " # build the operator for ansatz wavefunction\n", + " ansatz_op = QubitOperator('X0 Y1')\n", + " # Apply the unitary e^{-i * ansatz_op * t}\n", + " TimeEvolution(theta, ansatz_op) | wavefunction\n", + " # flush all gates\n", + " eng.flush()\n", + " # Calculate the energy.\n", + " # The simulator can directly return expectation values, while on a\n", + " # real quantum devices one would have to measure each term of the\n", + " # Hamiltonian.\n", + " energy = eng.backend.get_expectation_value(hamiltonian, wavefunction)\n", + " # Measure in order to return to return to a classical state\n", + " # (as otherwise the simulator will give an error)\n", + " All(Measure) | wavefunction\n", + " return energy" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAY4AAAEWCAYAAABxMXBSAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzt3Xl4FFXWwOHfSSDgKItCQJAl4Ma+JUSjIquIA4qooLgMODKoqJ+OMw7MOKO4jBvOqDCO+wKKG7jhLiCISHAABVRUVETZZAdZBLKc749bnXSS7k6FpNOd9Hmfp59UV1VXneru1Ol7b9W9oqoYY4wxfiXFOgBjjDFViyUOY4wxZWKJwxhjTJlY4jDGGFMmljiMMcaUiSUOY4wxZWKJw5SZiLQQkd0iknyQr/+biDxe0XGF2M9cERkV7f1UByLypYj0inUcoYhILxFZG+s4TCFLHNWciLwrIreGmD9YRH4WkRpl3aaq/qSqh6lqno/9l/inV9U7VDXmJ3QRaSciM0Rkp4jsEpEPROTEStr3SBGZXxn7Ctpfnpfwgx9NAVS1varOrax4TNVmiaP6mwxcLCJSbP4lwFRVzS3Lxg4m0cQjETka+Bj4HGgFNAVeA2aKSGYsY4uibC/hBz/WxzqoWKku3+VYsMRR/b0GNAB6BGaIyOHAIGCK93ygiHwmIr+IyBoRGR+0bpqIqIhcJiI/AR8EzavhrXOpiHzl/WpfJSKXe/MPBd4Bmgb/whWR8SLybNA+zvKqSnZ41Uttg5atFpE/i8hyr2TwoojUDhyHiLwpIptFZLs33czn+zIedyK9UVW3qeouVZ0IPAvc7W2/RGnJi6efN50pItle3BtE5D8ikhK0rorIFSLyrbfOg+K0BR4Gsrz3ZIe3fpGqteKlEm97Y7zt7RKR20TkaBFZ4H12LwXvvyyKHdchIjLZe0+/EpG/BL8P3mf4sve+/yAi/xe0bLwXxxQvxi9FJCNo+VgRWect+0ZE+nrza4nI/SKy3nvcLyK1QsQ5VkSmF5v3gIhM9KbricgT3uexTkRuF69K1Xs/PxaR+0RkK+47YA6CJY5qTlV/BV4Cfhc0exjwtaou857v8ZbXBwYCV4rI2cU21RNoC5weYjebcImoLnApcJ+IdFPVPcAZwPpwv3BF5DjgeeA6IBV4G3ij2AlwGDAAVzLoBIz05icBTwEtgRbAr8B/SntPPKcB00LMfwnoEUhOpcgD/gg0BLKAvsCYYusMArp7cQ8DTlfVr4ArKCwB1PcZM7j3Px04EfgL8ChwMdAc6AAML8O2wrkZSANa496niwMLRCQJeANYBhyFO+brRCT4e3EW8ALu+zQD7zMRkeOBq4HuqlrHO5bV3mtu9I6pC9AZyAT+HiK2F4Dfikgdb5vJuPf1OW/500AucAzQFegPBFeLngCsAhoD//T5fphiLHEkhsnAeUEnw9958wBQ1bmq+rmq5qvqctyJvGexbYxX1T1eIipCVd9S1e/V+RB4n6ASTinOB95S1ZmqmgPcCxwCnBS0zkRVXa+q23AnrS7efreq6suquldVd+FOBMXjDqchsCHE/A1AMnBEaRtQ1SWqulBVc1V1NfBIiP3fpao7VPUnYE4g9nK4R1V/UdUvgS+A91V1laruxJXuukZ47YleySfw+D7MesOAO1R1u6quBSYGLesOpKrqrap6QFVXAY8BFwStM19V3/bawJ7BJQJwibYW0E5EaqrqalUNxHARcKuqblLVzcAtuOrUIlT1R+BTYIg3qw+wV1UXikhj4LfAdd53dRNwX7HY1qvqJO8zK/FdNv5YHV8CUNX5IrIFOFtEFuF+zZ0TWC4iJwB34X6xpuD+uYv/Gl8TbvsicgbuV+pxuB8jv8G1HfjRFPgxKNZ8EVmD+zUb8HPQ9F7vNYjIb3AnhgHA4d7yOiKS7KPhfgvQJMT8JoACW0sL3Cst/RvIwB1zDWBJsdWKx35Yadstxcag6V9DPD8ywmsXquopPvbRlKKfd/B0S1zV446gecnAR0HPix9zbRGpoarfich1uCqi9iLyHnC9Vwot8j3wppuGie85XMlqCnAhhaWNlkBNYIMUNuklRTgWc5CsxJE4puBKGhcD76lq8AnnOVyVQnNVrYerfy/emB6yG2WvHvplXEmhsVft8nbQ60vrfnk97h8+sD3BVbus83FMfwKOB05Q1brAqYHN+HjtLGBoiPnDcCfY/bgqvN8ExZaMq04LeAj4GjjW2//ffO4bQr8vRfZH5CQQTRuA4Lai5kHTa4AfVLV+0KOOqv7Wz4ZV9TkvebXEvQd3e4uKfA9wVY/hGu6nAb289qwhFCaONcB+oGFQbHVVtX1wCH7iNJFZ4kgcU4B+wB8Iqqby1AG2qeo+cVcUXViG7QZKKJuBXK/00T9o+UaggYjUC/P6l4CBItJXRGriksF+YIGPfdfB/creISJH4Eo9ft0CnCQi/xSRI0Skjohcg2ujuclbZyXu1/JAL7a/e8cavP9fgN0i0ga4sgz73wg0K9aWsxQ4R0R+IyLHAJeVYXsV6SXgr+IuPjgK1y4R8D9gl9dIfYiIJItIBxHpXtpGReR4Eenj/djYh/vs8r3FzwN/F5FUEWmI+wyeDbUdryprLq596wevzQhV3YCrJv2XiNQVkSTv4gG/1ZfGJ0scCcKrg18AHIorXQQbA9wqIrtw/7AvlWG7u4D/816zHZd0ZgQt/xp3Uljl1as3Lfb6b3CloEm46qMzgTNV9YCP3d+Paw/ZAiwE3i1D3N8Cp+Dq31cDO4DbgCGqOstbZyfuvXkcVwLaAwRfZfVn73h34er5X/S7f+AD4EvgZ68aEVy12wFcUpkMTC3D9vwIXMUV/Ah1wr8Vd5w/4Epm03HJHK8KcBCureYH3Hv/OBDuh0GwWrgq0S246qxGwF+9ZbcDi4HluGrOT7154TyH+yH0XLH5v8P9mFmB+z5OJ3SVpCkHsYGcjAGv2mMhcLOqPhHreOKJiFwJXKCq9svdAFbiMAYA7+qhM4AmIlLeBuwqTUSaiMjJXlXP8bjqw1djHZeJH1biMMYUISItgbdw983swN078Vef1YcmAVjiMMYYUyYxraoSkQFetwPfici4EMuvF5EV4rqbmO39EjLGGBNDMStxeNfEr8R1abAWWAQMV9UVQev0Bj5R1b1eA10vVT2/tG03bNhQ09LSohO4McZUQ0uWLNmiqqmlrxnbO8czge+8LgsQkReAwbjL6ABQ1TlB6y8kqM+cSNLS0li8eHEFhmqMMdWbiPxY+lpOLKuqjqLo7f9rKdrNRHGX4friCUlERovIYhFZvHnz5goK0RhjTHFV4nJcEbkY1x/QhHDrqOqjqpqhqhmpqb5KW8YYYw5CLKuq1lG0D5xmhOifSNwYATcCPb3+g4wxxsRQLBPHIuBYEWmFSxgXUKyPJBHpiuuqeoDXRbKpIDk5Oaxdu5Z9+/bFOhRjTCWqXbs2zZo1o2bNmge9jZglDlXNFZGrgfdw3TI/qapfihsfe7GqzsBVTR0GTPO6Sf5JVc+KVczVydq1a6lTpw5paWlIiVFljTHVkaqydetW1q5dS6tWrQ56OzEdj0NV38Z1wR0876ag6X6VHlSC2LdvnyUNYxKMiNCgQQPKewFRlWgcryzZ2XDnne5vIrCkYUziqYj/exsB0LNgAfTsCfn5UKsWzJ4NWVmxjsoYY+KPlTg8H34IubkucRw4AHPnxjqi6m/jxo1ceOGFtG7dmvT0dLKysnj11crvhDUtLY0tW7aUviKwevVqnnuu+BAQB79eVTdt2jTatm1L7969K3S748eP5957763QbY4cOZLp06cf9OvLG1N59x9PLHF4evUCEfdISXHPTfSoKmeffTannnoqq1atYsmSJbzwwgusXbu2xLq5ubkxiDC0qpg4ovn+PfHEEzz22GPMmTOn9JWrmMr63uXl5VXKfiqSJQ5PVha0bQtHH23VVOFUZBvQBx98QEpKCldccUXBvJYtW3LNNdcA8PTTT3PWWWfRp08f+vbti6pyww030KFDBzp27MiLL7rB9ubOncugQYMKtnH11Vfz9NNPA64kcfPNN9OtWzc6duzI119/DcDWrVvp378/7du3Z9SoUYTrr+3DDz+kS5cudOnSha5du7Jr1y7GjRvHRx99RJcuXbjvvvtYvXo1PXr0oFu3bnTr1o0FC9yIt8XXy8vL44YbbqB79+506tSJRx55JOQ+n332WTIzM+nSpQuXX355wUnlsMMO48Ybb6Rz586ceOKJbNzohozfvHkz5557Lt27d6d79+58/PHHgPt1fMkll3DyySdzySWXsHfvXoYNG0a7du0YMmQIJ5xwAosXL+bJJ5/kuuuuK9j/Y489xh//+McScT3//PN07NiRDh06MHbsWABuvfVW5s+fz2WXXcYNN9xQZP25c+fSs2dPBg8eTOvWrRk3bhxTp04lMzOTjh078v333wMuwfbp04dOnTrRt29ffvrppxL7/v777xkwYADp6en06NGj4HPcuHEjQ4YMoXPnznTu3JkFCxawevVqOnToUPDae++9l/Hjx5fY5q233kr37t3p0KEDo0ePLvgO9OrVi+uuu46MjAweeOCBEq9bsWIFvXr1onXr1kycOBGAm266ifvvv79gnRtvvJEHHngAVeXqq6/m+OOPp1+/fmzaVHhHQVpaGmPHjqVbt25MmzaNpUuXcuKJJ9KpUyeGDBnC9u3bAVi0aBGdOnWiS5cuBd9/IOz3ae7cufTq1YvzzjuPNm3acNFFF4X9fpeLqla7R3p6uh6MgQNVu3Y9qJdWOStWrCiYvvZa1Z49Iz+6dFFNSlIF97dLl8jrX3tt5P0/8MADet1114Vd/tRTT+lRRx2lW7duVVXV6dOna79+/TQ3N1d//vlnbd68ua5fv17nzJmjAwcOLHjdVVddpU899ZSqqrZs2VInTpyoqqoPPvigXnbZZaqqes011+gtt9yiqqpvvvmmArp58+YSMQwaNEjnz5+vqqq7du3SnJycEvvbs2eP/vrrr6qqunLlSg1894qv98gjj+htt92mqqr79u3T9PR0XbVqVZH9rVixQgcNGqQHDhxQVdUrr7xSJ0+erKqqgM6YMUNVVW+44YaCbQ0fPlw/+ugjVVX98ccftU2bNqqqevPNN2u3bt107969qqo6YcIEHT16tKqqfv7555qcnKyLFi3SXbt2aevWrQv2mZWVpcuXLy8S17p167R58+a6adMmzcnJ0d69e+urr76qqqo9e/bURYsWlXjv5syZo/Xq1dP169frvn37tGnTpnrTTTepqur999+v13pfkEGDBunTTz+tqqpPPPGEDh48uCD+CRMmqKpqnz59dOXKlaqqunDhQu3du7eqqg4bNkzvu+8+VVXNzc3VHTt26A8//KDt27cviGPChAl68803q6rqiBEjdNq0aaqqBd8rVdWLL7644L3t2bOnXnnllSWOJxBTVlaW7tu3Tzdv3qxHHHGEHjhwQH/44Qft6p048vLytHXr1rplyxZ9+eWXC76z69at03r16hXsv2XLlnr33XcXbLtjx446d+5cVVX9xz/+UfD+tG/fXhcsWKCqqmPHji04tnDfpzlz5mjdunV1zZo1mpeXpyeeeGLB9yNY8P9/AO42CF/nWGscD5KaCsuXxzqK+LRzp2v/Afd3506o52eUaZ+uuuoq5s+fT0pKCosWLQLgtNNO44gjjgBg/vz5DB8+nOTkZBo3bkzPnj1ZtGgRdevWjbjdc845B4D09HReeeUVAObNm1cwPXDgQA4//PCQrz355JO5/vrrueiiizjnnHNo1qxZiXVycnK4+uqrWbp0KcnJyaxcuTLktt5//32WL19eUMe9c+dOvv322yLX0s+ePZslS5bQvbsbBvzXX3+lUaNGAKSkpBSUrNLT05k5cyYAs2bNYsWKgn5B+eWXX9i9ezcAZ511FoccckjB+3fttdcC0KFDBzp16gS4kkyfPn148803adu2LTk5OXTs2LFI7IsWLaJXr14EuvK56KKLmDdvHmeffXbIYw3o3r07TZq44b6PPvpo+vfvD0DHjh0Lqrays7MLPotLLrmEv/zlL0W2sXv3bhYsWMDQoUML5u3f7zqQ+OCDD5gyZQoAycnJ1KtXr+CXemnmzJnDPffcw969e9m2bRvt27fnzDPPBOD888N3wD1w4EBq1apFrVq1aNSoERs3biQtLY0GDRrw2WefsXHjRrp27UqDBg2YN29ewXe2adOm9OnTp8i2AvvZuXMnO3bsoGdPNzLviBEjGDp0KDt27GDXrl1kedUfF154IW+++SYQ/vuUkpJCZmZmwXe1S5curF69mlNOOcXX++KXJY4gDRvC5s2g6to6EkVQKTus7Gzo29ddOJCSAlOnlq86r3379rz88ssFzx988EG2bNlCRkZGwbxDDz201O3UqFGD/EBGgxJ3wteqVQtwJ5bS6qwffPBBHnvsMQDefvttxo0bx8CBA3n77bc5+eSTee+990q85r777qNx48YsW7aM/Px8ateuHXLbqsqkSZM4/fTTw+5fVRkxYgR33nlniWU1a9YsuIwy+Fjy8/NZuHBhyP36ef8ARo0axR133EGbNm249NJLfb3Gj8B7D5CUlFTwPCkpyXf7QX5+PvXr12fp0qW+1i/t+xCYN2bMGBYvXkzz5s0ZP358kfUivW/BxxT8OYwaNYqnn36an3/+md///ve+YvX7+YQS7vs0d+7csDFWJGvjCJKaCvv2wd69sY4k/mRlubaf226rmDagPn36sG/fPh566KGCeXsjvPE9evTgxRdfJC8vj82bNzNv3jwyMzNp2bIlK1asYP/+/ezYsYPZs2eXuu9TTz21oOH6nXfeKfiVetVVV7F06VKWLl1K06ZN+f777+nYsSNjx46le/fufP3119SpU4ddu3YVbGvnzp00adKEpKQknnnmmYI2ieLrnX766Tz00EPk5OQAsHLlSvbs2VMkrr59+zJ9+vSCuvBt27bx44+Re7ru378/kyZNKnge7gR78skn89JLLwGunv7zzz8vWHbCCSewZs0annvuOYYPH17itZmZmXz44Yds2bKFvLw8nn/++YJfx+V10kkn8cILLwAwdepUevToUWR53bp1adWqFdOmTQPcCXPZsmWAe78C35+8vDx27txJ48aN2bRpE1u3bmX//v0Fv9CDBZJEw4YN2b17d4Vc6TRkyBDeffddFi1aVHAyP/XUUwu+sxs2bAh7AUG9evU4/PDD+eijjwB45pln6NmzJ/Xr16dOnTp88sknAAXvE/j7PkWTlTiCNGzo/m7eDOX4MVBtZWVV3EUDIsJrr73GH//4R+655x5SU1M59NBDufvuu0OuP2TIELKzs+ncuTMiwj333MORRx4JwLBhw+jQoQOtWrWia9eupe775ptvZvjw4bRv356TTjqJFi1ahFzv/vvvZ86cOSQlJdG+fXvOOOMMkpKSSE5OpnPnzowcOZIxY8Zw7rnnMmXKFAYMGFDwK7JTp05F1rv22mtZvXo13bp1Q1VJTU3ltddeK7K/du3acfvtt9O/f3/y8/OpWbMmDz74IC1bhh/4cuLEiVx11VV06tSJ3NxcTj31VB5++OES640ZM4YRI0bQrl072rRpQ/v27akXVNc4bNgwli5dGrLarkmTJtx111307t0bVWXgwIEMHjy41PfZj0mTJnHppZcyYcIEUlNTeeqpp0qsM3XqVK688kpuv/12cnJyuOCCC+jcuTMPPPAAo0eP5oknniA5OZmHHnqIrKwsbrrpJjIzMznqqKNo06ZNie3Vr1+fP/zhD3To0IEjjzyyoGqwPFJSUujduzf169cnOTkZcN/ZDz74gHbt2tGiRYuCKqdQJk+ezBVXXMHevXtp3bp1wfvwxBNP8Ic//IGkpCR69uxZ8JmNGjWq1O9TVPltDKlKj4NtHJ8xwzX+hmjrq3ZCNY6Z6is3N7egEf+7777TtLQ03b9/f8HygQMH6qxZs2IVXpWXl5ennTt3LmjEryi7du0qmL7zzjv1//7v/ypku9Y4XoGCSxzGVCd79+6ld+/e5OTkoKr897//JSUlhR07dpCZmUnnzp3p27dvrMOsklasWMGgQYMYMmQIxx57bIVu+6233uLOO+8kNzeXli1bFlxqHmuWOIIExn/yeROxMVVGnTp1Qg6nXL9+/bBXghl/2rVrx6pVq6Ky7fPPPz/iVV6xYo3jQRKtxOFKp8aYRFIR//eWOILUqwc1aiRGiaN27dps3brVkocxCUTVjccR7rJxv6yqKohI4b0c1V2zZs1Yu3ZtufvlN8ZULYERAMvDEkcxqamJUeKoWbNmuUYAM8YkLquqKiZRShzGGHOwLHEU07BhYpQ4jDHmYFniKCY11UocxhgTiSWOYho2hO3b3WiAxhhjSopp4hCRASLyjYh8JyLjQiyvJSIvess/EZG0aMeUmup6x922Ldp7MsaYqilmiUNEkoEHgTOAdsBwEWlXbLXLgO2qegxwHxC6B7wKFLgJ0No5jDEmtFiWODKB71R1laoeAF4Aine5ORiY7E1PB/qKRHekjEC3I9bOYYwxocUycRwFrAl6vtabF3IdVc0FdgINQm1MREaLyGIRWVyem9qsxGGMMZFVm8ZxVX1UVTNUNSMwxOXBsBKHMcZEFsvEsQ5oHvS8mTcv5DoiUgOoB2yNZlANvPKMlTiMMSa0WCaORcCxItJKRFKAC4AZxdaZAYzwps8DPtAo98pXqxbUrWslDmOMCSdmfVWpaq6IXA28ByQDT6rqlyJyK24kqhnAE8AzIvIdsA2XXKLO7h43xpjwYtrJoaq+DbxdbN5NQdP7gKGVHZfdPW6MMeFVm8bximQlDmOMCc8SRwhW4jDGmPAscYQQKHHY4HjGGFOSJY4QUlNh3z7YsyfWkRhjTPyxxBGC3T1ujDHhWeIIwe4eN8aY8CxxhGAlDmOMCc8SRwhW4jDGmPAscYRgJQ5jjAnPEkcI9epBjRpW4jDGmFAscYQgYnePG2NMOJY4wrC7x40xJjRLHGFYicMYY0KzxBGGlTiMMSY0SxxhWInDGGNCs8QRRmoqbNsGubmxjsQYY+KLJY4wAvdybNsW2ziMMSbehB0BUETO8vH6far6fgXGEzeC7x5v1Ci2sRhjTDyJNHTsU8BbgERY5yTg6AqNKE7Y3ePGGBNapMQxU1V/F+nFIvJCBccTN6y/KmOMCS1sG4eqXlDai/2sU1VZicMYY0IrtXFcRA4Rkb+KyMPe82NE5IzohxZbljiMMSY0P1dVPYlr5zjFe74euKM8OxWRI0Rkpoh86/09PMQ6XUQkW0S+FJHlInJ+efZZVikpULeuVVUZY0xxfhLHsap6B5ADoKp7idxg7sc4YLaqHgvM9p4Xtxf4naq2BwYA94tI/XLut0xSU63EYYwxxflJHAdEpDagACLSCjhQzv0OBiZ705OBs4uvoKorVfVbb3o9sAlILed+y6RhQytxGGNMcX4Sx63Au0AzEZkMzAH+Ws79NlbVDd70z0DjSCuLSCaQAnwfYZ3RIrJYRBZvrqCzvZU4jDGmpEiX4wKgqu+KyBLcPRsC3KCqm0p7nYjMAo4MsejGYttXEdEI22kCPAOMUNX8CHE+CjwKkJGREXZ7ZdGwISxdWhFbMsaY6qPUxOHpCxytqv8UkeYikq6qSyK9QFX7hVsmIhtFpImqbvASQ8hEJCJ1cTch3qiqC33GWmECJQ5VN7iTMcYYf5fj/gfoDVzszdoDPFzO/c4ARnjTI4DXQ+w3BXgVmKKq08u5v4PSsCHs2wd79sRi78YYE5/8tHGcpKqXA/sAVHUbrr2hPO4CThORb4F+3nNEJENEHvfWGQacCowUkaXeo0s591smgbvHrZ3DGGMK+amqyhGRJAqvqmoAhG1r8ENVt+Kqv4rPXwyM8qafBZ4tz37KK3AT4ObNkJYWy0iMMSZ++ClxPAi8DKSKyC3AfODuqEYVJ6zEYYwxJfm5qmqKd1VVP9xVVUNV9YuoRxYHgkscxhhjnIiJQ0SSgeXe3dtfVk5I8cNKHMYYU1LEqipVzQNWichRlRRPXKlbF2rWtBKHMcYE89M4fhjwlYhk4y7FBUBVz4laVHFCxFVXWYnDGGMK+Ukct0c9ijhm/VUZY0xRfhJHX1X9W/AMEbkD16tttWf9VRljTFF+LscdEGLewIoOJF5ZicMYY4oKW+IQkcuBK4DjReTToEV1gIj9VFUnVuIwxpiiIlVVvYSrjrqTogMt7fLTO2510bAhbNsGublQw2+XkMYYU42FPRWq6nYR+QVop6phx8Go7gL3cmzbBo0axTYWY4yJB3YfRykCd4/fcQdkZ8c2FmOMiQd+GscD93G8JyKvBB7RDixebPIq5SZNgr59LXkYY4zdx1GKVavc3/x8OHAA5s6FrKyYhmSMMTHlp5PDhLhfI5zBg+H++91d5Ckp0KtXrCMyxpjY8jMCYHcRWSgiO0Vkn4js9xrNE0KvXtCgAXTrBrNnW2nDGGP8VFX9Fzds7AtAJjASaBnFmOLO8ce70oYlDWOM8dc4nqSq3wA1VDVHVR8jge4cB2jVCn74IdZRGGNMfPCTOPaISAqwTETuEJFrgOQoxxVXWrWCNWsgJyfWkRhjTOz5SRwjvfWuBvKAY4HzohhT3Gnd2l1V9dNPsY7EGGNiz89VVd4FqewD/hHdcOJTq1bu7w8/wNFHxzYWY4yJtUidHH4GaLjlqtqtPDsWkSOAF4E0YDUwTFW3h1m3LrACeE1Vry7Pfg9GcOIwxphEF6nEEaiOEuB14KwK3vc4YLaq3iUi47znY8Osexswr4L371uzZq6DQ0scxhgTuZPDgo4NRWR/FDo6HAz08qYnA3MJkThEJB1oDLwLZFRwDL4kJ0OLFpY4jDEG/DWOR0tjVd3gTf+MSw5FiEgS8C/gz5UZWCitWxd2P2KMMYksUhtHp6Cnh4hIR1y1FQCqury0jYvILODIEItuDH6iqioiodpTxgBvq+paEQmxuMi+RgOjAVq0aFFaaGXWqhW89lqFb9YYY6qcSG0cDwZNb8HdQR6gwKmlbVxV+4VbJiIbRaSJqm4QkSZAqMGhsoAeIjIG10tviojsVtWhMv6gAAAdcklEQVRxxVdU1UeBRwEyMjLCNuofrFat3BCyu3fDYYdV9NaNMabqiNTG0SPK+54BjADu8v6+HiKGiwLTIjISyAiVNCpD4Mqq1auhQ4dYRGCMMfEhbBtHsaqqg14ngruA00TkW6Cf9xwRyRCRx8ux3aiwS3KNMcaJVFX1jIicQlC7RgiTga4Hs2NV3Qr0DTF/MTAqxPyngacPZl8VoXVr99cayI0xiS5S4mgAfEnkxBGqXaJaatgQDj3UShzGGBOpjaNZZQYS70Ssl1xjjIHY3sdR5VjiMMYYSxxlEkgcWuEX+xpjTNVhiaMMWrd293Fs2RLrSIwxJnb8jDn+ooicLqXdup0A7JJcY4zxV+J4Cvg9sFJEbheRY6IcU9yyxGGMMT4Sh6q+q6rnA5m4zgjniMg8EblEREodCKo6scRhjDE+2zhE5HDgQuASYDnwCHASrqvzhHHYYe5+DkscxphEVmqJQUSmAR2BqcC5qrrWWzTVGyUwobRubYnDGJPY/FQ1PQrMUi15EaqqHlR3I1VZq1aweHGsozDGmNjxkzgOAc4sdlHVTuALr7+phNKqFbzyCuTluZEBjTEm0fhJHFfixsX40Ht+KvAp0FJEblLV56IVXDxq1QpycmDdOjecrDHGJBo/jeNJQFtVHayqg4F2wAHgROBv0QwuHtmVVcaYROcncTQPGhscb7qlqm4BcqMWWZwKdK9uicMYk6j8VFXNE5HXgZe85+cBH4nIocAvUYssTrVoAUlJNi6HMSZx+UkcY4ChwCne8xeBl1Q1Hx/jjlc3NWtCs2ZW4jDGJK6IiUNEkoF3VfU0XMIwWPfqxpjEFrGNQ1XzgGQRqVtJ8VQJljiMMYnMT1XVTmCZiLwP7AnMVNXroxZVnGvVCtavh337oHbtWEdjjDGVy0/ieNN7GE/gyqrVq6FNm5iGYowxla7UxKGqT4hICtBCVb+rhJjiXvC9HJY4jDGJxs9ATgOBz4GZ3vMuIvJqeXYqIkeIyEwR+db7e3iY9VqIyPsi8pWIrBCRtPLst6LYTYDGmETm5wbAW4ETgB0AqroUKO9gTuOA2ap6LDDbex7KFGCCqrbFjQeyqZz7rRBHHgm1alniMMYkJj+JI0dVdxSbV6Kn3DIaDEz2picDZxdfQUTaATVUdSaAqu5W1b3l3G+FSEqCtDRLHMaYxOQncXwlIsOAJBFpJSL3AQvLud/GQd2Y/Aw0DrHOccAOEXlFRD4TkQnefSUhichoEVksIos3b95czvBKd/jhsGABZGdHfVfGGBNX/CSOq4F0IB94FdfB4XWlvUhEZonIFyEeg4PX88b5CFWCqQH0AP4MdAdaAyPD7U9VH1XVDFXNSE1N9XFYBy87243JsWED9O1rycMYk1j8XFW1BxjrPXxT1X7hlonIRhFpoqobRKQJodsu1gJLVXWV95rXcD3yPlGWOKJh7lw3HgfAgQPueVZWLCMyxpjK42fo2GOA64G04PVVtX859jsDGAHc5f19PcQ6i4D6IpKqqpuBPkBcjL3XqxekpMD+/W4wp169Yh2RMcZUHj83AE7H/cp/FsiroP3eBbwkIpcBPwLDAEQkA7hCVUepap6I/BmYLW74wSXAYxW0/3LJyoI334TTToMRI6y0YYxJLH4SR76qTqrInXpDzvYNMX8xMCro+UygU0Xuu6L06wfHHQeb4uICYWOMqTx+Gsdf965YShWRuoFH1COrAtLTYcmSWEdhjDGVy0/iGAX8AzfO+Jfe44toBlVVpKfD2rVW6jDGJBY/V1U1r4xAqqL0dPd3yRI444zYxmKMMZUlbIlDRP4UNH1OsWW3RTOoqqJbN/fXqquMMYkkUlXVRUHTfy+2bGAUYqly6tZ1DeSWOIwxiSRS4pAw06GeJyxrIDfGJJpIiUPDTId6nrDS02HNGmsgN8YkjkiJo7OIbBOR7UAnbzrwvGMlxRf3ghvIjTEmEURKHClAKtAQqOVNB57bSNuerl3dX0scxphEEfZyXFWtqO5FqrV69eDYYy1xGGMSh58bAE0pMjIscRhjEocljgoQaCCvhPGjjDEm5ixxVABrIDfGJJJId45vD7qSKvixXUS2VWaQ8S7QQL44LkYLMcaY6IrUV1XDSouiirMGcmNMIvF9VZWIHEHRy3DXRyuoqig9HT7+ONZRGGNM9JXaxiEiA0VkJW4M8E+8vx9EO7CqxhrIjTGJwk/j+D+Bk4FvvC7WTwc+impUVVBGhvtr1VXGmOrOT+LIVdXNQJKIiDeca2aU46py7A5yY0yi8DPm+E4ROQyYD0wRkU3Ar9ENq+qxBnJjTKLwU+I4G5corgPmAuuAQVGMqcpKT7dLco0x1Z+fxPFXVc1T1RxVfUJV/w1cH+3AqiJrIDfGJAI/iWNAiHnlHgFQRI4QkZki8q339/Aw690jIl+KyFciMlFE4nYQKbuD3BiTCCLdOX65iHwGHC8inwY9vgW+qoB9jwNmq+qxwGzvefEYTsJd0dUJ6AB0B3pWwL6jIjAG+b//DdnZsY3FGGOiJVLj+Eu4E/qdFD2p71LVihjvbjDQy5uejGs/GVtsHcXddJiCG662JrCxAvYdFStWgAjMnAnz58Ps2ZCVFeuojDGmYoUtcajqdlX9TlWH4k7ep3mP1Arad2NV3eBN/ww0DhFDNjAH2OA93lPVkKUdERktIotFZPHmGDUyzJ0L6g2qe+CAe26MMdWNnzvHrwKmAS28x0siMsbPxkVkloh8EeIxOHg9VVVCjGMuIscAbYFmwFFAHxHpEWpfqvqoqmaoakZqakXltrLp1QtSUtx0jRruuTHGVDd+7uO4HMhU1d0AInIHsAD4b2kvVNV+4ZaJyEYRaaKqG0SkCRCq+msIsDBo3+8AWcTpnetZWfDWWzBgAJxzjlVTGWOqJz9XVQlwIOh5jjevvGYAI7zpEcDrIdb5CegpIjVEpCauYbwiGuajpl8/6NvXrqwyxlRfka6qCpRGngE+EZG/i8jfcaWNyRWw77uA07yrtPp5zxGRDBF53FtnOvA98DmwDFimqm9UwL6j6swzYeVK9zDGmOpGVEs0LbgFIp+qajdvOhM4xVv0kaouqqT4DkpGRoYujuEt3D/+CGlpcO+98Kc/xSwMY4zxTUSWqGqGn3UjtXEUVEep6v+A/5U3sETRsiV07AhvvmmJwxhT/URKHKkiErZrEa/rERPGmWfC3XfD9u1weMh74o0xpmqK1DieDBwG1AnzMBGceSbk5cG778Y6EmOMqViRShwbVPXWSoukmsnMhEaN4I03YPjwWEdjjDEVJ1KJI247E6wKkpJg4EB45x3IyYl1NMYYU3EiJY6+lRZFNTVoEOzYAR9/HOtIjDGm4kTqq2pbZQZSHfXv77ogeSPu7zwxxhj//Nw5bg7SYYdB796WOIwx1Ysljig780z49lv45ptYR2KMMRXDEkeUDfJGZ7dShzGmurDEEWUtW0KnTu4ucmOMqQ4scVSCM8+Ejz6Cm26yIWWNMVWfJY5KkJYG+fnwz3+6LtcteRhjqjJLHJVgkzdEVX6+DSlrjKn6LHFUgt69oWZNN21DyhpT+bKz4c47i5b2Q82rqPnxtO1o8DN0rCmnrCzXOH7mmS5p2JCyJpFlZ7tSd/H/hbLMD8w79VRIT4d9+9xjwQKYPx+6dYO2bV0J/7PP4PrrXdc/NWq4tsacHHeSDcwbMwaaNYPvv4fHH3cdlCYnw7Bhrs+5NWvgtddcrUFSkhseukED2LgRZs0qXP/kk0HV9RYRWDcjA+rWdT1lf/ZZ4fz27aFOHdi5E776ys0XgdatoXZt2L0bfvrJbU8EmjZ1x75+feG8xo2hVi137Js2ufmHHAKzZ0f3PGOJo5L07w9XXgkPPgjr1sFRR8U6ImMqTriT+wcfuBNnu3awa5ebd9VVhSfsa6+F1FR34nzmGXcCTkqCHj3gN7+BDRtg2bLCk2qjRi4ZbN9+cHEeOAB//3vReTk58MADJdfNzYVp09yJOCfHxQbu7/z5UL8+/PJL0fnffOPizM938/Lz3THUqAFbthSdv2ePO/Fv2lQ4X9X1NnH88W4E0eBx9ho0cNtet65w3pFHuqs2v/iisEo8UB0e1R+oqlrtHunp6RqPVq1STUpSHTs21pEYE9mCBap33OH+5uer/vKL+/4+/rjqyJGqN9+sOnGi+3vuuarJyargvt9paaoNG7rnB/to1Eg1I8NtKzBPRLVbN9XMTDcdmHf66ar33ac6eHDh/KQk1UsuUX33XdVJk1Rr1XIx1qql+swzqi+8oFq7tptXu7Zbb9cu1blzVQ85xM0/5BB3/IH3w+/8sqwb7W2XBbBYfZ5jww4dW5XFeujYSIYOdUXbNWtclyTGVJbipYLdu10V6uzZrpqmbl336/jzz+H99wt/5deoEbmH59q1XVVJQPv27lf6kiWFVSpnn+2qfdaudb/4c3Ndu99LL7k2wGXL4LTT3K/llJTCqpbsbHclYvB8KDkv3LrFq7ZCVXeVp8qsKmzbr7IMHWuJo5ItXOg+1IkT4ZprYh2NqY6ys92J/7jjXPXGTz+5uv8pU1x1iog7se/dW/K1tWq5ZTt2uOcirtpo0CD45BN49VWXUJKTYdw4GD8eFi3yf3IPxBevJ89EZokjjhMHwEknuUa1lSvdP6AxkYQ6Gebnw+uvw1tvuTYCEVi1CpYvd+0FpcnKciWMmTMLE8Hf/ga33OJ+3ETzF72JT2VJHDFpgwCGAl8C+UBGhPUGAN8A3wHj/G4/Xts4AqZPd/WwL78c60hMvAluW1BVnTPH1csnJanWqKHap49q586qKSlF2wSSk1WPOcY9guv5R49WXb1add48//XloeIobb6p+oj3Ng4RaesljUeAP6tqieKBiCQDK4HTgLXAImC4qq4obfvxXuLIy4Njj3WX182fH+toTCwU/zUeuIJn5EjXnpCUBE2auCtogv9FjzgCTjzRXcK5YIFblpzsSgo33milAnPwylLiiMnluKr6FYBIxNFpM4HvVHWVt+4LwGCg1MQR75KT4brr3KWIn3wCJ5wQ64hMZZo1y7UZHDjgEsQxx8CPPxZtYM7Lg3r13GXczz7rnqekuMbsUNVGffq412VluWQRKhlkZZVMDqHmGVOaeL6P4yhgTdDztUC1OcVeeqm7Eelvf4N+/ewXX3U1Zw68+KJrcN6yBRYvhq+/Llyel+dKG2PGwKGHwj33uOcpKfDYY+47MWpUyURQ1gRhTEWKWuIQkVnAkSEW3aiqr0dhf6OB0QAtWrSo6M1XuDp13K/OqVPdP3+tWtG/29NET3a2SxLHHAP797sG5lmz3AUQAQ0auDuLTzml8AqnlBR341vgcz/jjNBJItT3whKEiZWoJQ5V7VfOTawDmgc9b+bNC7e/R4FHwbVxlHPflaK5d3TBnR/aiSD+BdoFTjrJ3eMwdSo88kjh3b/g7tFp1Mhd7RRoh/jTn+Cvf3XLf/97Ky2Yqiueq6oWAceKSCtcwrgAuDC2IVWss86Cf/2rsDHUOj+Mb7t2uX6M/vIXV50USlKSuz/nX/+C//2vaDtE8OdrCcJUZTHpHVdEhojIWiALeEtE3vPmNxWRtwFUNRe4GngP+Ap4SVW/jEW80ZKV5X51HnOM+0V6ZKiKPRMzs2a5tqhLLnFXMh1+uOssL5A0RODii929FIcc4j7DWrXg/PPddKAd4rbbrBrSVC92A2Ac+Okn6NDBdQY3a5b71WoqT6Dq6ZRT3Al/1ix4+WV3M11Ax46uhNiokbtjOtTNcXZZq6nK4v5yXFNUixauamP0aHj0UbjiilhHlDimTYOLLiraF1OgC+vg9onhwwvbJ7p399+AbUx1ZCWOOKHqrtlfuNB1MpeWFuuIqp/sbHjvPXfZ648/uunvvitcLgIXXACTJrmrocLdSGdMdWR9VVXBxAHuZNaxI2Rmuj6EIt8faSIJVB317OnaJh56yI2FErjyqVYtd//Mcce5ZTk5/jvjM6Y6sqqqKqplS7j3Xrj8cjjnHHf1jp2wym7uXDj99MLqp+K/jZKSXPcc//iHez50qF0aa0xZWOKIMx06uBPba6/BO++4m8rs5BVeoFTQrh38/LPrkuPdd4teLjt4MPzud+4KqEDVU7+gu4wsQRhTNpY44syHHxZWUe3fD08+aSe1UPLz4amn3IUEwUmiVSs3aNAbbxR23TF2bOQuOowxZWOJI8706uVOdgcOuJPjlCmuwbZv31hHFlvZ2e4y2d/8Br791iWG9esLl4u4TiP//W83Hap9wkoWxlQMSxxxJviXcadO7hLQQYPcoD39+8c6usq3caMbLfHuu13fTuCGKh040FVPTZhQ2LA9bFhhac2ShDHRY4kjDgWf9E44wdXHn3UW3Hmn63q7Ole1LFgAL7zgjnP5ctdtR3DjdlKS61E40LAdqlNAY0x02eW4VcDWra5DvZUr3YmzuvSkG6hOysyE3btde84bbxQmirZt4cILXWeQV15p91QYE012OW4106CB6//otttcu8evv8KMGVX35KnqShUjRhS9YzslpTBpJCe7PqICd2sfd5yVLIyJF5Y4qogzznD3eOzb506uEyfCUUe5X+LJybGOLrLsbHdpca1asHq1u2N7TdAQXSJw2WUuUQwYYL3JGhPvrKqqCglU7RxzjBsdbuZM12/SmDGwYUPsf40HX8nUvr0bT/255+D55wvv2D70UHdz3jHHuORX/I5tu1vbmNiwLkeqaeIIFqjuueoq2L7dzUtJgfffd91sVLYZM9wd2MFVT6quTSaQNJKS4NZb3V3bYEnCmHhibRwJQMT12PrVV3D77e4kfeCAq+q5+GLo0gV27oTevct3Ug51cn/vPdfteEqKu1v7k09g7dqir+vTxyUIEXfpbKD6qU+fwnWs+smYqslKHFVcdnZhL641arhE8eGHrgEd3K/8Cy5wl/S2betGsVu8uOSv/OAE0bmzu5Jr9mx3Z/aBA64dpWNH10YRKOGA6368Z083TsXDDxferW2dBRpTtVhVVQIlDih5Yr7lFlclFKgiSk4uvHkuWMOG7k7s3FzXRlLaV6F5c5cgPv20cJyK224rvPLJEoQxVZcljgRLHMUFl0IC7R5HHumSybPPupO+CKSnu0bspUth2TL3WhF3h/p558GWLTB+fNFSBNg4FcZUR5Y4EjxxQOhf/8UTSvCVTOGSQbjtWMnCmOrFEocljrDCnfQtGRiT2CxxWOIwxpgyKUviSIp2MMYYY6qXmCQOERkqIl+KSL6IhMxwItJcROaIyApv3WsrO05jjDElxarE8QVwDjAvwjq5wJ9UtR1wInCViLSrjOCMMcaEF5M7x1X1KwAJjLoTep0NwAZvepeIfAUcBayojBiNMcaEViXaOEQkDegKfBJhndEislhEFm/evLmyQjPGmIQTtRKHiMwCjgyx6EZVfb0M2zkMeBm4TlV/Cbeeqj4KPAruqqoyhmuMMcanqCUOVe1X3m2ISE1c0piqqq/4fd2SJUu2iMiP5d1/jDUEtsQ6iEqQCMeZCMcIiXGc1fkYW/pdMW57xxXXAPIE8JWq/rssr1XV1OhEVXlEZLHfa6qrskQ4zkQ4RkiM40yEY/QjVpfjDhGRtUAW8JaIvOfNbyoib3urnQxcAvQRkaXe47exiNcYY0yhWF1V9Srwaoj564HfetPzgfCXXRljjImJKnFVVYJ6NNYBVJJEOM5EOEZIjONMhGMsVbXsq8oYY0z0WInDGGNMmVjiMMYYUyaWOGJMRAaIyDci8p2IjAuxfKSIbA66smxULOIsDxF5UkQ2icgXYZaLiEz03oPlItKtsmMsLx/H2EtEdgZ9jjdVdozl5afj0WryWfo5zir/eZaLqtojRg8gGfgeaA2kAMuAdsXWGQn8J9axlvM4TwW6AV+EWf5b4B3cVXQnAp/EOuYoHGMv4M1Yx1nOY2wCdPOm6wArQ3xfq8Nn6ec4q/znWZ6HlThiKxP4TlVXqeoB4AVgcIxjqnCqOg/YFmGVwcAUdRYC9UWkSeVEVzF8HGOVp6obVPVTb3oXEOh4NFh1+Cz9HGdCs8QRW0cBa4KeryX0F/Rcr9g/XUSaV05olcrv+1DVZYnIMhF5R0TaxzqY8ojQ8Wi1+ixL6WC12nyeZWWJI/69AaSpaidgJjA5xvGYg/Mp0FJVOwOTgNdiHM9B89vxaFVXynFWm8/zYFjiiK11QHAJopk3r4CqblXV/d7Tx4H0SoqtMpX6PlR1qvqLqu72pt8GaopIwxiHVWY+Oh6tFp9lacdZXT7Pg2WJI7YWAceKSCsRSQEuAGYEr1CsfvgsXH1rdTMD+J13Rc6JwE51A3lVGyJypNdxJyKSifvf2xrbqMrGZ8ejVf6z9HOc1eHzLI+47R03EahqrohcDbyHu8LqSVX9UkRuBRar6gzg/0TkLNxQuttwV1lVKSLyPO4qlIZe55Y3AzUBVPVh4G3c1TjfAXuBS2MT6cHzcYznAVeKSC7wK3CBepfnVCGBjkc/F5Gl3ry/AS2g+nyW+DvO6vB5HjTrcsQYY0yZWFWVMcaYMrHEYYwxpkwscRhjjCkTSxzGGGPKxBKHMcaYMrHEYWJORPK8HkaXicinInJSBW23l4i86Xd+BezvbBFpF/R8rohk+Ihxp4i8XWz+dSKyT0TqRSHOLiLy24rebtD2J4jIzyLy52jtw8SWJQ4TD35V1S5e9w1/Be6MdUAH6WygXalrlfSRqhY/kQ/H3SB6TrmjKqkL7l6LEkSk3Pd2qeoNwMPl3Y6JX5Y4TLypC2yHgrEdJojIFyLyuYic783v5f2any4iX4vI1KC7eAd48z7Fx0lXRA4VN5bG/0TkMxEZ7M0fKSKviMi7IvKtiNwT9JrLRGSl95rHROQ/XinpLGCCV3o62lt9qLfeShHp4ecN8F57GPB3XAIJzC9TTN78od77t0xE5nk9FNwKnO/Feb6IjBeRZ0TkY+AZEaktIk957/lnItI7aP+vichMEVktIleLyPXeOgtF5Ag/x2eqPrtz3MSDQ7w7dGvjxkLo480/B/fruDPQEFgkIvO8ZV2B9sB64GPgZBFZDDzmvf474EUf+74R+EBVfy8i9YH/icgsb1kXbz/7gW9EZBKQB/wDN/bGLuADYJmqLhCRGbgxGqYDeLmshqpmelVDNwP9fMR0Aa6L/Y+A40WksapuLGtM3vo3Aaer6joRqa+qB8QNOpShqld7cY7HlZROUdVfReRPgKpqRxFpA7wvIsd52+vg7b827j0eq6pdReQ+4HfA/T6Oz1RxVuIw8SBQVdUGGABM8UoQpwDPq2qed+L8EOjuveZ/qrpWVfOBpUAa0Ab4QVW/9bp/eNbHvvsD47zENRd3QmzhLZutqjtVdR+wAmiJG0PlQ1Xdpqo5wLRSth/oIG+JF6Mfw4EXvGN7GRgatKysMX0MPC0if8B1axPODFX91Zs+Be+9U9WvgR+BQOKYo6q7VHUzsBPXezPA52U4PlPFWYnDxBVVzRbXy2hqKavuD5rO4+C/ywKcq6rfFJkpckIF7SOwDV+vF5GOwLHATK/EkgL8APyn2PZ8bVNVr/COZSCwRETC9a68p7TYQuw/P+h5fmmxmOrDShwmrnhVI8m4nkY/wtXFJ4tIKm541v9FePnXQFpQ+8LwCOsGvAdcE9RG0rWU9RcBPUXkcK8h+dygZbtwQ42Wx3BgvKqmeY+mQFMRaXkwMYnI0ar6iareBGzGdXleWpwfARd5rz8OVwL7JsL6JsFY4jDx4BCvoXYprl1ihKrmAa8Cy3H19R8Af1HVn8NtxKu+GQ285TWOb/Kx79twvdguF5Evvedhqeo64A5cAvsYWI2rsgHXLnGD11h8dOgtlOoC3HEHe9WbfzAxTfAaub8AFuDeyzlAu0DjeIhN/hdIEpHPcZ/HyKAxYYyx3nGNKSsROUxVd3u/7l/FdYdf/GTvd1u9gD+r6qB4iakieA3uu1X13ljFYKLHShzGlN14r3T0Ba79oTzDhh4AOkixGwBjHFO5iMgE4GL8t5uYKsZKHMYYY8rEShzGGGPKxBKHMcaYMrHEYYwxpkwscRhjjCkTSxzGGGPK5P8BDP+TWmm69e4AAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "lowest_energies = []\n", + "bond_distances = []\n", + "for i in range(len(raw_data_table_1)):\n", + " # Use data of paper to construct the Hamiltonian\n", + " bond_distances.append(raw_data_table_1[i][0])\n", + " hamiltonian = raw_data_table_1[i][1] * QubitOperator(()) # == identity\n", + " hamiltonian += raw_data_table_1[i][2] * QubitOperator(\"Z0\")\n", + " hamiltonian += raw_data_table_1[i][3] * QubitOperator(\"Z1\")\n", + " hamiltonian += raw_data_table_1[i][4] * QubitOperator(\"Z0 Z1\")\n", + " hamiltonian += raw_data_table_1[i][5] * QubitOperator(\"X0 X1\")\n", + " hamiltonian += raw_data_table_1[i][6] * QubitOperator(\"Y0 Y1\")\n", + "\n", + " # Use Scipy to perform the classical outerloop of the variational\n", + " # eigensolver, i.e., the minimization of the parameter theta.\n", + " # See documentation of Scipy for different optimizers.\n", + " minimum = minimize_scalar(lambda theta: variational_quantum_eigensolver(theta, hamiltonian))\n", + " lowest_energies.append(minimum.fun)\n", + "\n", + "# print result\n", + "plt.xlabel(\"Bond length [Angstrom]\")\n", + "plt.ylabel(\"Total Energy [Hartree]\")\n", + "plt.title(\"Variational Quantum Eigensolver\")\n", + "plt.plot(bond_distances, lowest_energies, \"b.-\",\n", + " label=\"Ground-state energy of molecular hydrogen\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.15" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From e5cc9885ce0bede6187191ed0cfe5be76bf868af Mon Sep 17 00:00:00 2001 From: Damian Steiger Date: Mon, 29 Oct 2018 19:53:03 +0100 Subject: [PATCH 017/113] Update docs of decompositions. (#281) --- docs/projectq.setups.decompositions.rst | 58 ++++++++++++++++--- .../setups/decompositions/qubitop2onequbit.py | 4 ++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/docs/projectq.setups.decompositions.rst b/docs/projectq.setups.decompositions.rst index a17e3d1bd..883395db6 100755 --- a/docs/projectq.setups.decompositions.rst +++ b/docs/projectq.setups.decompositions.rst @@ -9,18 +9,23 @@ The decomposition package is a collection of gate decomposition / replacement ru projectq.setups.decompositions.arb1qubit2rzandry projectq.setups.decompositions.barrier projectq.setups.decompositions.carb1qubit2cnotrzandry + projectq.setups.decompositions.cnot2cz projectq.setups.decompositions.cnu2toffoliandcu projectq.setups.decompositions.crz2cxandrz projectq.setups.decompositions.entangle projectq.setups.decompositions.globalphase projectq.setups.decompositions.ph2r projectq.setups.decompositions.qft2crandhadamard + projectq.setups.decompositions.qubitop2onequbit projectq.setups.decompositions.r2rzandph projectq.setups.decompositions.rx2rz projectq.setups.decompositions.ry2rz + projectq.setups.decompositions.sqrtswap2cnot + projectq.setups.decompositions.stateprep2cnot projectq.setups.decompositions.swap2cnot projectq.setups.decompositions.time_evolution projectq.setups.decompositions.toffoli2cnotandtgate + projectq.setups.decompositions.uniformlycontrolledr2cnot Submodules @@ -32,7 +37,7 @@ projectq.setups.decompositions.arb1qubit2rzandry module .. automodule:: projectq.setups.decompositions.arb1qubit2rzandry :members: :undoc-members: - + projectq.setups.decompositions.barrier module --------------------------------------------- @@ -48,6 +53,13 @@ projectq.setups.decompositions.carb1qubit2cnotrzandry module :members: :undoc-members: +projectq.setups.decompositions.cnot2cz module +--------------------------------------------- + +.. automodule:: projectq.setups.decompositions.cnot2cz + :members: + :undoc-members: + projectq.setups.decompositions.cnu2toffoliandcu module ------------------------------------------------------ @@ -61,35 +73,42 @@ projectq.setups.decompositions.crz2cxandrz module .. automodule:: projectq.setups.decompositions.crz2cxandrz :members: :undoc-members: - + projectq.setups.decompositions.entangle module ---------------------------------------------- .. automodule:: projectq.setups.decompositions.entangle :members: :undoc-members: - + projectq.setups.decompositions.globalphase module ------------------------------------------------- .. automodule:: projectq.setups.decompositions.globalphase :members: :undoc-members: - + projectq.setups.decompositions.ph2r module ------------------------------------------ .. automodule:: projectq.setups.decompositions.ph2r :members: :undoc-members: - + projectq.setups.decompositions.qft2crandhadamard module ------------------------------------------------------- .. automodule:: projectq.setups.decompositions.qft2crandhadamard :members: :undoc-members: - + +projectq.setups.decompositions.qubitop2onequbit module +------------------------------------------------------- + +.. automodule:: projectq.setups.decompositions.qubitop2onequbit + :members: + :undoc-members: + projectq.setups.decompositions.r2rzandph module ----------------------------------------------- @@ -110,7 +129,21 @@ projectq.setups.decompositions.ry2rz module .. automodule:: projectq.setups.decompositions.ry2rz :members: :undoc-members: - + +projectq.setups.decompositions.sqrtswap2cnot module +--------------------------------------------------- + +.. automodule:: projectq.setups.decompositions.sqrtswap2cnot + :members: + :undoc-members: + +projectq.setups.decompositions.stateprep2cnot module +---------------------------------------------------- + +.. automodule:: projectq.setups.decompositions.stateprep2cnot + :members: + :undoc-members: + projectq.setups.decompositions.swap2cnot module ----------------------------------------------- @@ -124,14 +157,21 @@ projectq.setups.decompositions.time_evolution module .. automodule:: projectq.setups.decompositions.time_evolution :members: :undoc-members: - + projectq.setups.decompositions.toffoli2cnotandtgate module ---------------------------------------------------------- .. automodule:: projectq.setups.decompositions.toffoli2cnotandtgate :members: :undoc-members: - + +projectq.setups.decompositions.uniformlycontrolledr2cnot module +--------------------------------------------------------------- + +.. automodule:: projectq.setups.decompositions.uniformlycontrolledr2cnot + :members: + :undoc-members: + Module contents --------------- diff --git a/projectq/setups/decompositions/qubitop2onequbit.py b/projectq/setups/decompositions/qubitop2onequbit.py index b66f64b25..61f7954e7 100644 --- a/projectq/setups/decompositions/qubitop2onequbit.py +++ b/projectq/setups/decompositions/qubitop2onequbit.py @@ -12,6 +12,10 @@ # 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. +""" + import cmath from projectq.cengines import DecompositionRule From 867e68ec22e54a3e14a050f30abf61930b9da0f0 Mon Sep 17 00:00:00 2001 From: Christian Gogolin Date: Sat, 22 Dec 2018 17:06:20 +0100 Subject: [PATCH 018/113] Add FlipBits gate (#289) --- docs/projectq.ops.rst | 1 + projectq/ops/_gates.py | 54 +++++++++++++++++++++- projectq/ops/_gates_test.py | 82 +++++++++++++++++++++++++++++++-- projectq/ops/_qubit_operator.py | 4 +- projectq/ops/_state_prep.py | 6 +-- 5 files changed, 138 insertions(+), 9 deletions(-) diff --git a/docs/projectq.ops.rst b/docs/projectq.ops.rst index 8f8d4cceb..8934dd68c 100755 --- a/docs/projectq.ops.rst +++ b/docs/projectq.ops.rst @@ -52,6 +52,7 @@ The operations collection consists of various default gates and is a work-in-pro projectq.ops.UniformlyControlledRy projectq.ops.UniformlyControlledRz projectq.ops.StatePreparation + projectq.ops.FlipBits Module contents diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 530fcce76..d160dc9e3 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -44,7 +44,6 @@ FastForwardingGate, BasicMathGate) from ._command import apply_command -from projectq.types import BasicQubit class HGate(SelfInverseGate): @@ -338,3 +337,56 @@ def get_inverse(self): #: Shortcut (instance of) :class:`projectq.ops.BarrierGate` Barrier = BarrierGate() + + +class FlipBits(SelfInverseGate): + """ Gate for flipping qubits by means of XGates """ + def __init__(self, bits_to_flip): + """ + Initialize FlipBits gate. + + Example: + .. code-block:: python + + qureg = eng.allocate_qureg(2) + FlipBits([0, 1]) | qureg + + Args: + bits_to_flip(list[int]|list[bool]|str|int): int or array of 0/1, + True/False, or string of 0/1 identifying the qubits to flip. + In case of int, the bits to flip are determined from the + binary digits, with the least significant bit corresponding + to qureg[0]. If bits_to_flip is negative, exactly all qubits + which would not be flipped for the input -bits_to_flip-1 are + flipped, i.e., bits_to_flip=-1 flips all qubits. + """ + SelfInverseGate.__init__(self) + if isinstance(bits_to_flip, int): + self.bits_to_flip = bits_to_flip + else: + self.bits_to_flip = 0 + for i in reversed(list(bits_to_flip)): + bit = 0b1 if i == '1' or i == 1 or i is True else 0b0 + self.bits_to_flip = (self.bits_to_flip << 1) | bit + + def __str__(self): + 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') + for qureg in quregs_tuple: + for i, qubit in enumerate(qureg): + if (self.bits_to_flip >> i) & 1: + XGate() | qubit + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.bits_to_flip == other.bits_to_flip + return False + + def __hash__(self): + return hash(self.__str__()) diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index 002ab8d20..efcd63b0a 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -19,9 +19,10 @@ import numpy as np import pytest -from projectq.ops import (get_inverse, SelfInverseGate, BasicRotationGate, - ClassicalInstructionGate, FastForwardingGate, - BasicGate) +from projectq import MainEngine +from projectq.ops import (All, FlipBits, get_inverse, SelfInverseGate, + BasicRotationGate, ClassicalInstructionGate, + FastForwardingGate, BasicGate, Measure) from projectq.ops import _gates @@ -217,3 +218,78 @@ def test_barrier_gate(): assert str(gate) == "Barrier" assert gate.get_inverse() == _gates.BarrierGate() assert isinstance(_gates.Barrier, _gates.BarrierGate) + + +def test_flip_bits_equality_and_hash(): + gate1 = _gates.FlipBits([1, 0, 0, 1]) + gate2 = _gates.FlipBits([1, 0, 0, 1]) + gate3 = _gates.FlipBits([0, 1, 0, 1]) + assert gate1 == gate2 + assert hash(gate1) == hash(gate2) + assert gate1 != gate3 + assert gate1 != _gates.X + + +def test_flip_bits_str(): + gate1 = _gates.FlipBits([0, 0, 1]) + assert str(gate1) == "FlipBits(4)" + + +def test_error_on_tuple_input(): + with pytest.raises(ValueError): + _gates.FlipBits(2) | (None, None) + + +flip_bits_testdata = [ + ([0, 1, 0, 1], '0101'), + ([1, 0, 1, 0], '1010'), + ([False, True, False, True], '0101'), + ('0101', '0101'), + ('1111', '1111'), + ('0000', '0000'), + (8, '0001'), + (11, '1101'), + (1, '1000'), + (-1, '1111'), + (-2, '0111'), + (-3, '1011'), +] + + +@pytest.mark.parametrize("bits_to_flip, result", flip_bits_testdata) +def test_simulator_flip_bits(bits_to_flip, result): + eng = MainEngine() + qubits = eng.allocate_qureg(4) + FlipBits(bits_to_flip) | qubits + eng.flush() + assert pytest.approx(eng.backend.get_probability(result, qubits)) == 1. + All(Measure) | qubits + + +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. + FlipBits([0, 1, 1, 0]) | qubits + eng.flush() + assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1. + FlipBits([1]) | qubits[0] + eng.flush() + assert pytest.approx(eng.backend.get_probability('1110', qubits)) == 1. + FlipBits([1]) | (qubits[0], ) + eng.flush() + assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1. + FlipBits([1, 1]) | [qubits[0], qubits[1]] + eng.flush() + assert pytest.approx(eng.backend.get_probability('1010', qubits)) == 1. + FlipBits(-1) | qubits + eng.flush() + assert pytest.approx(eng.backend.get_probability('0101', qubits)) == 1. + FlipBits(-4) | [qubits[0], qubits[1], qubits[2], qubits[3]] + eng.flush() + assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1. + FlipBits(2) | [qubits[0]] + [qubits[1], qubits[2]] + eng.flush() + assert pytest.approx(eng.backend.get_probability('0010', qubits)) == 1. + All(Measure) | qubits diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index aa9a29f9a..7fb65d1b2 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -362,9 +362,9 @@ def get_inverse(self): Raises: NotInvertible: Not implemented for QubitOperators which have multiple terms or a coefficient with absolute value - not equal to 1. + not equal to 1. """ - + if len(self.terms) == 1: (term, coefficient), = self.terms.items() if (not abs(coefficient) < 1 - EQ_TOLERANCE and not diff --git a/projectq/ops/_state_prep.py b/projectq/ops/_state_prep.py index 455a47a52..4ef51879d 100644 --- a/projectq/ops/_state_prep.py +++ b/projectq/ops/_state_prep.py @@ -30,9 +30,9 @@ def __init__(self, final_state): StatePreparation([0.5, -0.5j, -0.5, 0.5]) | qureg Note: - The amplitude of state k is final_state[k]. When the state k is - written in binary notation, then qureg[0] denotes the qubit - whose state corresponds to the least significant bit of k. + final_state[k] is taken to be the amplitude of the computational + basis state whose string is equal to the binary representation + of k. Args: final_state(list[complex]): wavefunction of the desired From abe86ed61697fe656de2b6120f54566d2b51c471 Mon Sep 17 00:00:00 2001 From: Thomas Haener Date: Fri, 11 Jan 2019 17:58:45 +0100 Subject: [PATCH 019/113] Fix strings with invalid escape sequences. (#300) --- projectq/backends/_circuits/_to_latex.py | 6 +++--- projectq/backends/_circuits/_to_latex_test.py | 2 +- projectq/ops/_metagates.py | 4 ++-- projectq/ops/_metagates_test.py | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index 5cbe89cae..1c028acfc 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -209,7 +209,7 @@ def _footer(settings): Returns: tex_footer_str (string): Latex document footer. """ - return "\n\n\end{tikzpicture}\n\end{document}" + return "\n\n\\end{tikzpicture}\n\\end{document}" class _Circ2Tikz(object): @@ -329,7 +329,7 @@ def to_tikz(self, line, circuit, end=None): self.is_quantum[l] = False elif gate == Allocate: # draw 'begin line' - add_str = "\n\\node[none] ({}) at ({},-{}) {{$\Ket{{0}}{}$}};" + add_str = "\n\\node[none] ({}) at ({},-{}) {{$\\Ket{{0}}{}$}};" id_str = "" if self.settings['gates']['AllocateQubitGate']['draw_id']: id_str = "^{{\\textcolor{{red}}{{{}}}}}".format(cmds[i].id) @@ -433,7 +433,7 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): 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( diff --git a/projectq/backends/_circuits/_to_latex_test.py b/projectq/backends/_circuits/_to_latex_test.py index 8d8ff81df..bf26b9923 100755 --- a/projectq/backends/_circuits/_to_latex_test.py +++ b/projectq/backends/_circuits/_to_latex_test.py @@ -174,7 +174,7 @@ def test_body(): # CZ is two phases plus 2 from CNOTs + 2 from cswap + 2 from csqrtswap assert code.count("phase") == 8 assert code.count("{{{}}}".format(str(H))) == 2 # 2 hadamard gates - assert code.count("{$\Ket{0}") == 3 # 3 qubits allocated + assert code.count("{$\\Ket{0}") == 3 # 3 qubits allocated # 1 cnot, 1 not gate, 3 SqrtSwap, 1 inv(SqrtSwap) assert code.count("xstyle") == 7 assert code.count("measure") == 1 # 1 measurement diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index c2c20969b..b2e7959fe 100755 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -80,10 +80,10 @@ def __init__(self, gate): pass def __str__(self): - """ + r""" Return string representation (str(gate) + \"^\dagger\"). """ - return str(self._gate) + "^\dagger" + return str(self._gate) + r"^\dagger" def tex_str(self): """ diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index c5a62d239..c42393b06 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -72,7 +72,7 @@ def test_daggered_gate_init(): def test_daggered_gate_str(): daggered_gate = _metagates.DaggeredGate(Y) - assert str(daggered_gate) == str(Y) + "^\dagger" + assert str(daggered_gate) == str(Y) + r"^\dagger" def test_daggered_gate_hashable(): @@ -87,13 +87,13 @@ def test_daggered_gate_hashable(): def test_daggered_gate_tex_str(): daggered_gate = _metagates.DaggeredGate(Y) str_Y = Y.tex_str() if hasattr(Y, 'tex_str') else str(Y) - assert daggered_gate.tex_str() == str_Y + "${}^\dagger$" + assert daggered_gate.tex_str() == str_Y + r"${}^\dagger$" # test for a gate with tex_str method rx = Rx(0.5) daggered_rx = _metagates.DaggeredGate(rx) str_rx = rx.tex_str() if hasattr(rx, 'tex_str') else str(rx) - assert daggered_rx.tex_str() == str_rx + "${}^\dagger$" + assert daggered_rx.tex_str() == str_rx + r"${}^\dagger$" def test_daggered_gate_get_inverse(): From 04fd0a3eb14f2a23c0ed33fdd6683f77a6f4d410 Mon Sep 17 00:00:00 2001 From: Christian Gogolin Date: Fri, 11 Jan 2019 19:01:48 +0100 Subject: [PATCH 020/113] Avoid 502 error when waiting for IBM Q results, resolves #291 (#294) --- projectq/backends/_ibm/_ibm_http_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 83b054fcd..954a94521 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -157,7 +157,7 @@ def _get_result(device, execution_id, access_token, num_retries=3000, r_json = r.json() if 'qasms' in r_json: qasm = r_json['qasms'][0] - if 'result' in qasm: + if 'result' in qasm and qasm['result'] is not None: return qasm['result'] time.sleep(interval) if device in ['ibmqx4', 'ibmqx5'] and retries % 60 == 0: From a87039e97825da41e9edd73f8cd6c48ad6868e56 Mon Sep 17 00:00:00 2001 From: Christian Gogolin Date: Mon, 14 Jan 2019 13:57:43 +0100 Subject: [PATCH 021/113] Expose num_retries and interval in IBMBackend (#295) --- projectq/backends/_ibm/_ibm.py | 18 +++++- projectq/backends/_ibm/_ibm_http_client.py | 74 ++++++++++++++-------- 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index d14dbc27f..b1899f043 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -46,6 +46,7 @@ class IBMBackend(BasicEngine): """ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, user=None, password=None, device='ibmqx4', + num_retries=3000, interval=1, retrieve_execution=None): """ Initialize the Backend object. @@ -62,6 +63,11 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, password (string): IBM Quantum Experience password device (string): Device to use ('ibmqx4', or 'ibmqx5') if use_hardware is set to True. Default is ibmqx4. + num_retries (int): Number of times to retry to obtain + results from the IBM API. (default is 3000) + interval (float, int): Number of seconds between successive + attempts to obtain results from the IBM API. + (default is 1) retrieve_execution (int): Job ID to retrieve instead of re- running the circuit (e.g., if previous run timed out). """ @@ -75,6 +81,8 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, self._verbose = verbose self._user = user self._password = password + self._num_retries = num_retries + self._interval = interval self._probabilities = dict() self.qasm = "" self._measured_ids = [] @@ -256,11 +264,17 @@ def _run(self): if self._retrieve_execution is None: res = send(info, device=self.device, user=self._user, password=self._password, - shots=self._num_runs, verbose=self._verbose) + shots=self._num_runs, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose) else: res = retrieve(device=self.device, user=self._user, password=self._password, - jobid=self._retrieve_execution) + jobid=self._retrieve_execution, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose) counts = res['data']['counts'] # Determine random outcome diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 954a94521..d713b17fa 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -17,6 +17,7 @@ import requests import getpass import json +import signal import sys import time from requests.compat import urljoin @@ -35,7 +36,8 @@ def is_online(device): return r.json()['state'] -def retrieve(device, user, password, jobid): +def retrieve(device, user, password, jobid, num_retries=3000, + interval=1, verbose=False): """ Retrieves a previously run job by its ID. @@ -46,12 +48,13 @@ def retrieve(device, user, password, jobid): jobid (str): Id of the job to retrieve """ user_id, access_token = _authenticate(user, password) - res = _get_result(device, jobid, access_token) + res = _get_result(device, jobid, access_token, num_retries=num_retries, + interval=interval, verbose=verbose) return res def send(info, device='sim_trivial_2', user=None, password=None, - shots=1, verbose=False): + shots=1, num_retries=3000, interval=1, verbose=False): """ Sends QASM through the IBM API and runs the quantum circuit. @@ -84,7 +87,9 @@ def send(info, device='sim_trivial_2', user=None, password=None, execution_id = _run(info, device, user_id, access_token, shots) if verbose: print("- Waiting for results...") - res = _get_result(device, execution_id, access_token) + res = _get_result(device, execution_id, access_token, + num_retries=num_retries, + interval=interval, verbose=verbose) if verbose: print("- Done.") return res @@ -143,32 +148,47 @@ def _run(qasm, device, user_id, access_token, shots): def _get_result(device, execution_id, access_token, num_retries=3000, - interval=1): + interval=1, verbose=False): suffix = 'Jobs/{execution_id}'.format(execution_id=execution_id) status_url = urljoin(_api_url, 'Backends/{}/queue/status'.format(device)) - print("Waiting for results. [Job ID: {}]".format(execution_id)) - - for retries in range(num_retries): - r = requests.get(urljoin(_api_url, suffix), - params={"access_token": access_token}) - r.raise_for_status() - - r_json = r.json() - if 'qasms' in r_json: - qasm = r_json['qasms'][0] - if 'result' in qasm and qasm['result'] is not None: - return qasm['result'] - time.sleep(interval) - if device in ['ibmqx4', 'ibmqx5'] and retries % 60 == 0: - r = requests.get(status_url) + if verbose: + print("Waiting for results. [Job ID: {}]".format(execution_id)) + + original_sigint_handler = signal.getsignal(signal.SIGINT) + + def _handle_sigint_during_get_result(*_): + raise Exception("Interrupted. The ID of your submitted job is {}." + .format(execution_id)) + + try: + signal.signal(signal.SIGINT, _handle_sigint_during_get_result) + + for retries in range(num_retries): + r = requests.get(urljoin(_api_url, suffix), + params={"access_token": access_token}) + r.raise_for_status() r_json = r.json() - if 'state' in r_json and not r_json['state']: - raise DeviceOfflineError("Device went offline. The ID of your " - "submitted job is {}." - .format(execution_id)) - if 'lengthQueue' in r_json: - print("Currently there are {} jobs queued for execution on {}." - .format(r_json['lengthQueue'], device)) + if 'qasms' in r_json: + qasm = r_json['qasms'][0] + if 'result' in qasm and qasm['result'] is not None: + return qasm['result'] + time.sleep(interval) + if device in ['ibmqx4', 'ibmqx5'] and retries % 60 == 0: + r = requests.get(status_url) + r_json = r.json() + if 'state' in r_json and not r_json['state']: + raise DeviceOfflineError("Device went offline. The ID of " + "your submitted job is {}." + .format(execution_id)) + if verbose and 'lengthQueue' in r_json: + print("Currently there are {} jobs queued for execution " + "on {}." + .format(r_json['lengthQueue'], device)) + + finally: + if original_sigint_handler is not None: + signal.signal(signal.SIGINT, original_sigint_handler) + raise Exception("Timeout. The ID of your submitted job is {}." .format(execution_id)) From ef99f4897cb6f565412ff7eb5d470d2127f3f6c1 Mon Sep 17 00:00:00 2001 From: Thomas Haener Date: Wed, 30 Jan 2019 10:15:15 +0100 Subject: [PATCH 022/113] Don't accept controlled single-qubit gates in restricted gate set. (#301) --- projectq/setups/restrictedgateset.py | 53 +++++++++++++++++------ projectq/setups/restrictedgateset_test.py | 38 +++++++++------- 2 files changed, 63 insertions(+), 28 deletions(-) diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index f02268fd7..7b3540cf0 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -29,8 +29,8 @@ from projectq.cengines import (AutoReplacer, DecompositionRuleSet, InstructionFilter, LocalOptimizer, TagRemover) -from projectq.ops import (BasicMathGate, ClassicalInstructionGate, CNOT, - ControlledGate, get_inverse, QFT, Swap) +from projectq.ops import (BasicGate, BasicMathGate, ClassicalInstructionGate, + CNOT, ControlledGate, get_inverse, QFT, Swap) def high_level_gates(eng, cmd): @@ -102,7 +102,9 @@ def get_engine_list(one_qubit_gates="any", all gates which are equal to it. If the gate is a class, it allows all instances of this class. Raises: - TypeError: If input is for the gates is not "any" or a tuple. + TypeError: If input is for the gates is not "any" or a tuple. Also if + element within tuple is not a class or instance of BasicGate + (e.g. CRz which is a shortcut function) Returns: A list of suitable compiler engines. @@ -119,39 +121,54 @@ def get_engine_list(one_qubit_gates="any", rule_set = DecompositionRuleSet(modules=[projectq.libs.math, projectq.setups.decompositions]) - allowed_gate_classes = [] + allowed_gate_classes = [] # n-qubit gates allowed_gate_instances = [] + allowed_gate_classes1 = [] # 1-qubit gates + allowed_gate_instances1 = [] + allowed_gate_classes2 = [] # 2-qubit gates + allowed_gate_instances2 = [] + if one_qubit_gates != "any": for gate in one_qubit_gates: if inspect.isclass(gate): - allowed_gate_classes.append(gate) + allowed_gate_classes1.append(gate) + elif isinstance(gate, BasicGate): + allowed_gate_instances1.append(gate) else: - allowed_gate_instances.append((gate, 0)) + raise TypeError("unsupported one_qubit_gates argument") 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: + allowed_gate_classes2.append(gate) + elif isinstance(gate, BasicGate): if isinstance(gate, ControlledGate): - allowed_gate_instances.append((gate._gate, gate._n)) + allowed_gate_instances2.append((gate._gate, gate._n)) else: - allowed_gate_instances.append((gate, 0)) + allowed_gate_instances2.append((gate, 0)) + else: + raise TypeError("unsupported two_qubit_gates argument") for gate in other_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: + elif isinstance(gate, BasicGate): if isinstance(gate, ControlledGate): allowed_gate_instances.append((gate._gate, gate._n)) else: allowed_gate_instances.append((gate, 0)) + else: + raise TypeError("unsupported other_gates argument") allowed_gate_classes = tuple(allowed_gate_classes) allowed_gate_instances = tuple(allowed_gate_instances) + allowed_gate_classes1 = tuple(allowed_gate_classes1) + allowed_gate_instances1 = tuple(allowed_gate_instances1) + allowed_gate_classes2 = tuple(allowed_gate_classes2) + allowed_gate_instances2 = tuple(allowed_gate_instances2) def low_level_gates(eng, cmd): all_qubits = [q for qr in cmd.all_qubits for q in qr] @@ -166,8 +183,18 @@ def low_level_gates(eng, cmd): return True elif (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances: return True - else: - return False + 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): + 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): + return True + return False return [AutoReplacer(rule_set), TagRemover(), diff --git a/projectq/setups/restrictedgateset_test.py b/projectq/setups/restrictedgateset_test.py index ebe767c1e..544746f85 100644 --- a/projectq/setups/restrictedgateset_test.py +++ b/projectq/setups/restrictedgateset_test.py @@ -20,8 +20,9 @@ from projectq.cengines import DummyEngine from projectq.libs.math import (AddConstant, AddConstantModN, MultiplyByConstantModN) -from projectq.ops import (BasicGate, CNOT, H, Measure, QFT, QubitOperator, Rx, - Rz, Swap, TimeEvolution, Toffoli, X) +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 @@ -48,17 +49,18 @@ def test_restriction(): two_qubit_gates=(CNOT, AddConstant, Swap), other_gates=(Toffoli, AddConstantModN, MultiplyByConstantModN(2, 8))) backend = DummyEngine(save_commands=True) - eng = projectq.MainEngine(backend, engine_list) + eng = projectq.MainEngine(backend, engine_list, verbose=True) qubit1 = eng.allocate_qubit() qubit2 = eng.allocate_qubit() qubit3 = eng.allocate_qubit() eng.flush() CNOT | (qubit1, qubit2) H | qubit1 - Rz(0.2) | qubit1 + with Control(eng, qubit2): + Rz(0.2) | qubit1 Measure | qubit1 - AddConstant(1) | qubit1 + qubit2 - AddConstantModN(1, 9) | qubit1 + qubit2 + qubit3 + AddConstant(1) | (qubit1 + qubit2) + AddConstantModN(1, 9) | (qubit1 + qubit2 + qubit3) Toffoli | (qubit1 + qubit2, qubit3) Swap | (qubit1, qubit2) MultiplyByConstantModN(2, 8) | qubit1 + qubit2 + qubit3 @@ -70,15 +72,15 @@ def test_restriction(): assert backend.received_commands[4].gate == X assert len(backend.received_commands[4].control_qubits) == 1 assert backend.received_commands[5].gate == H - assert backend.received_commands[6].gate == Rz(0.2) - assert backend.received_commands[7].gate == Measure - assert backend.received_commands[8].gate == AddConstant(1) - assert backend.received_commands[9].gate == AddConstantModN(1, 9) - assert backend.received_commands[10].gate == X - assert len(backend.received_commands[10].control_qubits) == 2 - assert backend.received_commands[11].gate == Swap - assert backend.received_commands[12].gate == MultiplyByConstantModN(2, 8) - for cmd in backend.received_commands[13:]: + assert backend.received_commands[6].gate == Rz(0.1) + assert backend.received_commands[10].gate == Measure + assert backend.received_commands[11].gate == AddConstant(1) + assert backend.received_commands[12].gate == AddConstantModN(1, 9) + assert backend.received_commands[13].gate == X + assert len(backend.received_commands[13].control_qubits) == 2 + assert backend.received_commands[14].gate == Swap + assert backend.received_commands[15].gate == MultiplyByConstantModN(2, 8) + for cmd in backend.received_commands[16:]: assert cmd.gate != QFT assert not isinstance(cmd.gate, Rx) assert not isinstance(cmd.gate, MultiplyByConstantModN) @@ -92,3 +94,9 @@ def test_wrong_init(): engine_list = restrictedgateset.get_engine_list(one_qubit_gates="Any") with pytest.raises(TypeError): engine_list = restrictedgateset.get_engine_list(other_gates="any") + with pytest.raises(TypeError): + engine_list = restrictedgateset.get_engine_list(one_qubit_gates=(CRz,)) + with pytest.raises(TypeError): + engine_list = restrictedgateset.get_engine_list(two_qubit_gates=(CRz,)) + with pytest.raises(TypeError): + engine_list = restrictedgateset.get_engine_list(other_gates=(CRz,)) From eaf1334eda80d9115b99e96aa92e9838e1273bea Mon Sep 17 00:00:00 2001 From: Christian Gogolin Date: Wed, 30 Jan 2019 21:27:30 +0100 Subject: [PATCH 023/113] Implement MatrixGate and simplify __eq__ in BasicGate to improve performance (#288) --- docs/projectq.ops.rst | 1 + projectq/backends/_sim/_simulator_test.py | 53 ++++----- projectq/ops/__init__.py | 1 + projectq/ops/_basics.py | 111 ++++++++++++------ projectq/ops/_basics_test.py | 41 ++++++- projectq/ops/_gates.py | 1 + .../decompositions/arb1qubit2rzandry_test.py | 10 +- .../carb1qubit2cnotrzandry_test.py | 8 +- 8 files changed, 150 insertions(+), 76 deletions(-) diff --git a/docs/projectq.ops.rst b/docs/projectq.ops.rst index 8934dd68c..8fbe2e287 100755 --- a/docs/projectq.ops.rst +++ b/docs/projectq.ops.rst @@ -6,6 +6,7 @@ The operations collection consists of various default gates and is a work-in-pro .. autosummary:: projectq.ops.BasicGate + projectq.ops.MatrixGate projectq.ops.SelfInverseGate projectq.ops.BasicRotationGate projectq.ops.BasicPhaseGate diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 546ab3584..9e301be0d 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -30,8 +30,8 @@ from projectq.cengines import (BasicEngine, BasicMapperEngine, DummyEngine, LocalOptimizer, NotYetMeasuredError) from projectq.ops import (All, Allocate, BasicGate, BasicMathGate, CNOT, - Command, H, Measure, QubitOperator, Rx, Ry, Rz, S, - TimeEvolution, Toffoli, X, Y, Z) + Command, H, MatrixGate, Measure, QubitOperator, + Rx, Ry, Rz, S, TimeEvolution, Toffoli, X, Y, Z) from projectq.meta import Control, Dagger, LogicalQubitIDTag from projectq.types import WeakQubitRef @@ -97,37 +97,32 @@ def receive(self, command_list): return None -class Mock1QubitGate(BasicGate): - def __init__(self): - BasicGate.__init__(self) - self.cnt = 0 +class Mock1QubitGate(MatrixGate): + def __init__(self): + MatrixGate.__init__(self) + self.cnt = 0 - @property - def matrix(self): - self.cnt += 1 - return [[0, 1], [1, 0]] + @property + def matrix(self): + self.cnt += 1 + return [[0, 1], [1, 0]] -class Mock6QubitGate(BasicGate): - def __init__(self): - BasicGate.__init__(self) - self.cnt = 0 +class Mock6QubitGate(MatrixGate): + def __init__(self): + MatrixGate.__init__(self) + self.cnt = 0 - @property - def matrix(self): - self.cnt += 1 - return numpy.eye(2 ** 6) + @property + def matrix(self): + self.cnt += 1 + return numpy.eye(2 ** 6) class MockNoMatrixGate(BasicGate): - def __init__(self): - BasicGate.__init__(self) - self.cnt = 0 - - @property - def matrix(self): - self.cnt += 1 - raise AttributeError + def __init__(self): + BasicGate.__init__(self) + self.cnt = 0 def test_simulator_is_available(sim): @@ -147,15 +142,15 @@ def test_simulator_is_available(sim): new_cmd.gate = Mock1QubitGate() assert sim.is_available(new_cmd) - assert new_cmd.gate.cnt == 4 + assert new_cmd.gate.cnt == 1 new_cmd.gate = Mock6QubitGate() assert not sim.is_available(new_cmd) - assert new_cmd.gate.cnt == 4 + assert new_cmd.gate.cnt == 1 new_cmd.gate = MockNoMatrixGate() assert not sim.is_available(new_cmd) - assert new_cmd.gate.cnt == 7 + assert new_cmd.gate.cnt == 0 def test_simulator_cheat(sim): diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 32ff8ab54..db4a38b79 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -15,6 +15,7 @@ from ._basics import (NotMergeable, NotInvertible, BasicGate, + MatrixGate, SelfInverseGate, BasicRotationGate, ClassicalInstructionGate, diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 7f724c93e..4bca84429 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -64,7 +64,7 @@ class NotInvertible(Exception): class BasicGate(object): """ - Base class of all gates. + Base class of all gates. (Don't use it directly but derive from it) """ def __init__(self): """ @@ -204,39 +204,19 @@ def __or__(self, qubits): apply_command(cmd) def __eq__(self, other): - """ Return True if equal (i.e., instance of same class). - - Unless both have a matrix attribute in which case we also check - that the matrices are identical as people might want to do the - following: - - Example: - .. code-block:: python - - gate = BasicGate() - gate.matrix = numpy.matrix([[1,0],[0, -1]]) - """ - if hasattr(self, 'matrix'): - if not hasattr(other, 'matrix'): - return False - if hasattr(other, 'matrix'): - if not hasattr(self, 'matrix'): - return False - if hasattr(self, 'matrix') and hasattr(other, 'matrix'): - if (not isinstance(self.matrix, np.matrix) or - not isinstance(other.matrix, np.matrix)): - raise TypeError("One of the gates doesn't have the correct " - "type (numpy.matrix) for the matrix " - "attribute.") - if (self.matrix.shape == other.matrix.shape and - np.allclose(self.matrix, other.matrix, - rtol=RTOL, atol=ATOL, - equal_nan=False)): - return True - else: - return False + """ + Equality comparision + + Return True if instance of the same class, unless other is an instance + of :class:MatrixGate, in which case equality is to be checked by testing + for existence and (approximate) equality of matrix representations. + """ + if isinstance(other, self.__class__): + return True + elif isinstance(other, MatrixGate): + return NotImplemented else: - return isinstance(other, self.__class__) + return False def __ne__(self, other): return not self.__eq__(other) @@ -248,6 +228,71 @@ def __hash__(self): return hash(str(self)) +class MatrixGate(BasicGate): + """ + Defines a gate class whose instances are defined by a matrix. + + Note: + Use this gate class only for gates acting on a small numbers of qubits. + In general, consider instead using one of the provided ProjectQ gates + or define a new class as this allows the compiler to work symbolically. + + Example: + + .. code-block:: python + + gate = MatrixGate([[0, 1], [1, 0]]) + gate | qubit + """ + def __init__(self, matrix=None): + """ + Initialize MatrixGate + + Args: + matrix(numpy.matrix): matrix which defines the gate. Default: None + """ + BasicGate.__init__(self) + self._matrix = np.matrix(matrix) if matrix is not None else None + + @property + def matrix(self): + return self._matrix + + @matrix.setter + def matrix(self, matrix): + self._matrix = np.matrix(matrix) + + def __eq__(self, other): + """ + Equality comparision + + Return True only if both gates have a matrix respresentation and the + matrices are (approximately) equal. Otherwise return False. + """ + if not hasattr(other, 'matrix'): + return False + if (not isinstance(self.matrix, np.matrix) or + not isinstance(other.matrix, np.matrix)): + raise TypeError("One of the gates doesn't have the correct " + "type (numpy.matrix) for the matrix " + "attribute.") + if (self.matrix.shape == other.matrix.shape and + np.allclose(self.matrix, other.matrix, + rtol=RTOL, atol=ATOL, + equal_nan=False)): + return True + return False + + def __str__(self): + return("MatrixGate(" + str(self.matrix.tolist()) + ")") + + def __hash__(self): + return hash(str(self)) + + def get_inverse(self): + return MatrixGate(np.linalg.inv(self.matrix)) + + class SelfInverseGate(BasicGate): """ Self-inverse basic gate class. diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 61a1de767..80cc80183 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -21,7 +21,7 @@ import pytest from projectq.types import Qubit, Qureg -from projectq.ops import Command +from projectq.ops import Command, X from projectq import MainEngine from projectq.cengines import DummyEngine @@ -118,13 +118,12 @@ def test_basic_gate_compare(): gate2 = _basics.BasicGate() assert gate1 == gate2 assert not (gate1 != gate2) - gate3 = _basics.BasicGate() + gate3 = _basics.MatrixGate() gate3.matrix = np.matrix([[1, 0], [0, -1]]) assert gate1 != gate3 - gate4 = _basics.BasicGate() + gate4 = _basics.MatrixGate() gate4.matrix = [[1, 0], [0, -1]] - with pytest.raises(TypeError): - gate4 == gate3 + assert gate4 == gate3 def test_comparing_different_gates(): @@ -295,3 +294,35 @@ def __init__(self): # Test a=2, b=3, and c=5 should give a=2, b=3, c=11 math_fun = gate.get_math_function(("qreg1", "qreg2", "qreg3")) assert math_fun([2, 3, 5]) == [2, 3, 11] + + +def test_matrix_gate(): + gate1 = _basics.MatrixGate() + gate2 = _basics.MatrixGate() + with pytest.raises(TypeError): + assert gate1 == gate2 + gate3 = _basics.MatrixGate([[0, 1], [1, 0]]) + gate4 = _basics.MatrixGate([[0, 1], [1, 0]]) + gate5 = _basics.MatrixGate([[1, 0], [0, -1]]) + assert gate3 == gate4 + assert gate4 != gate5 + with pytest.raises(TypeError): + assert gate1 != gate3 + with pytest.raises(TypeError): + assert gate3 != gate1 + gate6 = _basics.BasicGate() + assert gate6 != gate1 + assert gate6 != gate3 + assert gate1 != gate6 + assert gate3 != gate6 + gate7 = gate5.get_inverse() + gate8 = _basics.MatrixGate([[1, 0], [0, (1+1j)/math.sqrt(2)]]) + assert gate7 == gate5 + assert gate7 != gate8 + gate9 = _basics.MatrixGate([[1, 0], [0, (1-1j)/math.sqrt(2)]]) + gate10 = gate9.get_inverse() + assert gate10 == gate8 + assert gate3 == X + assert X == gate3 + assert str(gate3) == "MatrixGate([[0, 1], [1, 0]])" + assert hash(gate3) == hash("MatrixGate([[0, 1], [1, 0]])") diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index d160dc9e3..d524e4b3b 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -37,6 +37,7 @@ from projectq.ops import get_inverse from ._basics import (BasicGate, + MatrixGate, SelfInverseGate, BasicRotationGate, BasicPhaseGate, diff --git a/projectq/setups/decompositions/arb1qubit2rzandry_test.py b/projectq/setups/decompositions/arb1qubit2rzandry_test.py index 90eba5edf..02ec907d6 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry_test.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry_test.py @@ -23,8 +23,8 @@ from projectq.backends import Simulator from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, InstructionFilter, MainEngine) -from projectq.ops import (BasicGate, ClassicalInstructionGate, Measure, Ph, R, - Rx, Ry, Rz, X) +from projectq.ops import (BasicGate, ClassicalInstructionGate, MatrixGate, + Measure, Ph, R, Rx, Ry, Rz, X) from projectq.meta import Control from . import arb1qubit2rzandry as arb1q @@ -51,7 +51,7 @@ def test_recognize_incorrect_gates(): # Does not have matrix attribute: BasicGate() | qubit # Two qubit gate: - two_qubit_gate = BasicGate() + two_qubit_gate = MatrixGate() two_qubit_gate.matrix = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] two_qubit_gate | qubit @@ -121,7 +121,7 @@ def create_test_matrices(): def test_decomposition(gate_matrix): for basis_state in ([1, 0], [0, 1]): # Create single qubit gate with gate_matrix - test_gate = BasicGate() + test_gate = MatrixGate() test_gate.matrix = np.matrix(gate_matrix) correct_dummy_eng = DummyEngine(save_commands=True) @@ -165,7 +165,7 @@ def test_decomposition(gate_matrix): [[0, 2], [4, 0]], [[1, 2], [4, 0]]]) def test_decomposition_errors(gate_matrix): - test_gate = BasicGate() + test_gate = MatrixGate() test_gate.matrix = np.matrix(gate_matrix) rule_set = DecompositionRuleSet(modules=[arb1q]) eng = MainEngine(backend=DummyEngine(), diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py index 7d324e81c..44b29f526 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py @@ -21,8 +21,8 @@ from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, InstructionFilter, MainEngine) from projectq.meta import Control -from projectq.ops import (All, BasicGate, ClassicalInstructionGate, Measure, - Ph, R, Rx, Ry, Rz, X, XGate) +from projectq.ops import (All, BasicGate, ClassicalInstructionGate, + MatrixGate, Measure, Ph, R, Rx, Ry, Rz, X, XGate) from projectq.setups.decompositions import arb1qubit2rzandry_test as arb1q_t from . import carb1qubit2cnotrzandry as carb1q @@ -57,7 +57,7 @@ def test_recognize_incorrect_gates(): # Does not have matrix attribute: BasicGate() | qubit # Two qubit gate: - two_qubit_gate = BasicGate() + two_qubit_gate = MatrixGate() two_qubit_gate.matrix = np.matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) two_qubit_gate | qubit @@ -94,7 +94,7 @@ def test_recognize_v(gate_matrix): @pytest.mark.parametrize("gate_matrix", arb1q_t.create_test_matrices()) def test_decomposition(gate_matrix): # Create single qubit gate with gate_matrix - test_gate = BasicGate() + test_gate = MatrixGate() test_gate.matrix = np.matrix(gate_matrix) for basis_state in ([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], From 22d368db8baf4909a044c847bfb97a0e50db1f3d Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Wed, 30 Jan 2019 15:38:03 -0500 Subject: [PATCH 024/113] Bumped version number to 0.4.2 --- projectq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/_version.py b/projectq/_version.py index 02ac3660d..61a0a8d6a 100755 --- a/projectq/_version.py +++ b/projectq/_version.py @@ -13,4 +13,4 @@ # limitations under the License. """Define version number here and read it from setup.py automatically""" -__version__ = "0.4.1" +__version__ = "0.4.2" From 3ac3c2c73519391c528cb32a41db92dd9eafbace Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 6 May 2019 08:50:03 +0200 Subject: [PATCH 025/113] Phase Estimation as a Gate in ops (#260) * First testing version of Phase Estimation with a lot of hardcoding * Adapt to All(Measure) * Adding operators for more than 1 quibit, first version * Adding operators for more than 1 quibit, first versioni: testing * Work in progress: create a PhaseX gate to tests via class * Work in progress: create a PhaseX gate to tests via class. Clean garbaje files * Work in progress: create a PhaseX gate to tests via class. Some enhanement * Work in progress: create a PhaseX gate to tests via class. PhaseX testing * Work in progress: Debugging algorithm * Work in progress: Debugging algorithm * Adding 2qubit example * adding 2 qubit Gate * Initial version * Create Phase Estimation as a new Gate in operations * Solving travis checks * python 2 compatibility + error in StatePreparation normalization * test coverage includes now the string * Improve the check test for no eigenvector test * Start modifying to decomposition * QPE as decomposition and gate * QPE as decomposition and gate: correct a detail in the test * try to get the travis-ci freeze solved * Solve a name not defined error in the phaseestimation tests * Solve coverage in tests * Address comments in review + change how to assert the tests * Enhance statistis in the tests bi more executions * Correct bad calculation in tests * Refine test * Address Andi comments: add detail in the examples and atributes and removing code in the test that is never executed --- docs/projectq.ops.rst | 1 + docs/projectq.setups.decompositions.rst | 8 + projectq/ops/__init__.py | 1 + projectq/ops/_qpegate.py | 29 ++++ projectq/ops/_qpegate_test.py | 23 +++ projectq/setups/decompositions/__init__.py | 6 +- .../setups/decompositions/phaseestimation.py | 129 ++++++++++++++ .../decompositions/phaseestimation_test.py | 162 ++++++++++++++++++ 8 files changed, 357 insertions(+), 2 deletions(-) create mode 100755 projectq/ops/_qpegate.py create mode 100755 projectq/ops/_qpegate_test.py create mode 100644 projectq/setups/decompositions/phaseestimation.py create mode 100644 projectq/setups/decompositions/phaseestimation_test.py diff --git a/docs/projectq.ops.rst b/docs/projectq.ops.rst index 8fbe2e287..58d66ee16 100755 --- a/docs/projectq.ops.rst +++ b/docs/projectq.ops.rst @@ -53,6 +53,7 @@ The operations collection consists of various default gates and is a work-in-pro projectq.ops.UniformlyControlledRy projectq.ops.UniformlyControlledRz projectq.ops.StatePreparation + projectq.ops.QPE projectq.ops.FlipBits diff --git a/docs/projectq.setups.decompositions.rst b/docs/projectq.setups.decompositions.rst index 883395db6..d900f5bfc 100755 --- a/docs/projectq.setups.decompositions.rst +++ b/docs/projectq.setups.decompositions.rst @@ -26,6 +26,7 @@ The decomposition package is a collection of gate decomposition / replacement ru projectq.setups.decompositions.time_evolution projectq.setups.decompositions.toffoli2cnotandtgate projectq.setups.decompositions.uniformlycontrolledr2cnot + projectq.setups.decompositions.phaseestimation Submodules @@ -172,6 +173,13 @@ projectq.setups.decompositions.uniformlycontrolledr2cnot module :members: :undoc-members: +projectq.setups.decompositions.phaseestimation module +--------------------------------------------------------------- + +.. automodule:: projectq.setups.decompositions.phaseestimation + :members: + :undoc-members: + Module contents --------------- diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index db4a38b79..388350259 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -37,3 +37,4 @@ from ._uniformly_controlled_rotation import (UniformlyControlledRy, UniformlyControlledRz) from ._state_prep import StatePreparation +from ._qpegate import QPE diff --git a/projectq/ops/_qpegate.py b/projectq/ops/_qpegate.py new file mode 100755 index 000000000..08beee743 --- /dev/null +++ b/projectq/ops/_qpegate.py @@ -0,0 +1,29 @@ +# 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. + +from ._basics import BasicGate + + +class QPE(BasicGate): + """ + Quantum Phase Estimation gate. + + See setups.decompositions for the complete implementation + """ + def __init__(self, unitary): + BasicGate.__init__(self) + self.unitary = unitary + + def __str__(self): + return 'QPE({})'.format(str(self.unitary)) diff --git a/projectq/ops/_qpegate_test.py b/projectq/ops/_qpegate_test.py new file mode 100755 index 000000000..5ffcbf185 --- /dev/null +++ b/projectq/ops/_qpegate_test.py @@ -0,0 +1,23 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for projectq.ops._qpegate.""" + +from projectq.ops import _qpegate, X + + +def test_qpe_str(): + unitary = X + gate = _qpegate.QPE(unitary) + assert str(gate) == "QPE(X)" diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index aab71b28c..db29dc7a8 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -31,7 +31,8 @@ swap2cnot, toffoli2cnotandtgate, time_evolution, - uniformlycontrolledr2cnot) + uniformlycontrolledr2cnot, + phaseestimation) all_defined_decomposition_rules = [ rule @@ -54,6 +55,7 @@ swap2cnot, toffoli2cnotandtgate, time_evolution, - uniformlycontrolledr2cnot] + uniformlycontrolledr2cnot, + phaseestimation] for rule in module.all_defined_decomposition_rules ] diff --git a/projectq/setups/decompositions/phaseestimation.py b/projectq/setups/decompositions/phaseestimation.py new file mode 100644 index 000000000..faf7523cf --- /dev/null +++ b/projectq/setups/decompositions/phaseestimation.py @@ -0,0 +1,129 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Registers a decomposition for phase estimation. + +(reference https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) + +The Quantum Phase Estimation (QPE) executes the algorithm up to the inverse +QFT included. The following steps measuring the ancillas and computing the +phase should be executed outside of the QPE. + +The decomposition uses as ancillas (qpe_ancillas) the first qubit/qureg in +the Command and as system qubits teh second qubit/qureg in the Command. + +The unitary operator for which the phase estimation is estimated (unitary) +is the gate in Command + +Example: + .. 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) + angle = cmath.pi*2.*0.125 + U = Ph(angle) # unitary_specfic_to_the_problem() + + # Apply Quantum Phase Estimation + QPE(U) | (qpe_ancillas, system_qubits) + + All(Measure) | qpe_ancillas + # Compute the phase from the ancilla measurement + #(https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) + phasebinlist = [int(q) for q in qpe_ancillas] + phase_in_bin = ''.join(str(j) for j in phasebinlist) + phase_int = int(phase_in_bin,2) + phase = phase_int / (2 ** n_qpe_ancillas) + print (phase) + + # Example using a function (two_qubit_gate). + # Instead of applying QPE on a gate U one could provide a function + + 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] + CNOT | (system_q[0], system_q[1]) + + n_qpe_ancillas = 3 + qpe_ancillas = eng.allocate_qureg(n_qpe_ancillas) + system_qubits = eng.allocate_qureg(2) + X | system_qubits[0] + + # Apply Quantum Phase Estimation + QPE(two_qubit_gate) | (qpe_ancillas, system_qubits) + + All(Measure) | qpe_ancillas + # Compute the phase from the ancilla measurement + #(https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) + phasebinlist = [int(q) for q in qpe_ancillas] + phase_in_bin = ''.join(str(j) for j in phasebinlist) + phase_int = int(phase_in_bin,2) + phase = phase_int / (2 ** n_qpe_ancillas) + print (phase) + +Attributes: + unitary (BasicGate): Unitary Operation either a ProjectQ gate or a function f. + 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.ops import H, Tensor, get_inverse, QFT + +from projectq.ops import QPE + + +def _decompose_QPE(cmd): + """ Decompose the Quantum Phase Estimation gate. """ + eng = cmd.engine + + # Ancillas is the first qubit/qureg. System-qubit is the second qubit/qureg + qpe_ancillas = cmd.qubits[0] + system_qubits = cmd.qubits[1] + + # Hadamard on the ancillas + Tensor(H) | qpe_ancillas + + # The Unitary Operator + U = cmd.gate.unitary + + # Control U on the system_qubits + 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) + else: + for i in range(len(qpe_ancillas)): + ipower = int(2**i) + with Loop(eng, ipower): + with Control(eng, qpe_ancillas[i]): + U | system_qubits + + # Inverse QFT on the ancillas + get_inverse(QFT) | qpe_ancillas + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(QPE, _decompose_QPE) +] diff --git a/projectq/setups/decompositions/phaseestimation_test.py b/projectq/setups/decompositions/phaseestimation_test.py new file mode 100644 index 000000000..828e522a4 --- /dev/null +++ b/projectq/setups/decompositions/phaseestimation_test.py @@ -0,0 +1,162 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Tests for projectq.setups.decompositions.phaseestimation.py." + +import cmath +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.ops import X, H, All, Measure, Tensor, Ph, CNOT, StatePreparation + +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 +import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot + + +def test_simple_test_X_eigenvectors(): + rule_set = DecompositionRuleSet(modules=[pe, dqft]) + eng = MainEngine(backend=Simulator(), + engine_list=[AutoReplacer(rule_set), + ]) + results = np.array([]) + for i in range(100): + autovector = eng.allocate_qureg(1) + X | autovector + H | autovector + unit = X + ancillas = eng.allocate_qureg(1) + QPE(unit) | (ancillas, autovector) + All(Measure) | ancillas + fasebinlist = [int(q) for q in ancillas] + fasebin = ''.join(str(j) for j in fasebinlist) + faseint = int(fasebin, 2) + phase = faseint / (2. ** (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) + + +def test_Ph_eigenvectors(): + rule_set = DecompositionRuleSet(modules=[pe, dqft]) + 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 + unit = Ph(theta) + ancillas = eng.allocate_qureg(3) + QPE(unit) | (ancillas, autovector) + All(Measure) | ancillas + fasebinlist = [int(q) for q in ancillas] + fasebin = ''.join(str(j) for j in fasebinlist) + faseint = int(fasebin, 2) + phase = faseint / (2. ** (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) + + +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] + 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), + ]) + results = np.array([]) + for i in range(100): + autovector = eng.allocate_qureg(2) + X | autovector[0] + ancillas = eng.allocate_qureg(3) + QPE(two_qubit_gate) | (ancillas, autovector) + All(Measure) | ancillas + fasebinlist = [int(q) for q in ancillas] + fasebin = ''.join(str(j) for j in fasebinlist) + faseint = int(fasebin, 2) + phase = faseint / (2. ** (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) + + +def test_X_no_eigenvectors(): + rule_set = DecompositionRuleSet(modules=[pe, dqft, stateprep2cnot, ucr2cnot]) + 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. + StatePreparation([amplitude0, amplitude1]) | autovector + unit = X + ancillas = eng.allocate_qureg(1) + QPE(unit) | (ancillas, autovector) + All(Measure) | ancillas + fasebinlist = [int(q) for q in ancillas] + fasebin = ''.join(str(j) for j in fasebinlist) + faseint = int(fasebin, 2) + phase = faseint / (2. ** (len(ancillas))) + results = np.append(results, phase) + Tensor(H) | autovector + if np.allclose(phase, .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): + results_minus = np.append(results_minus, phase) + All(Measure) | autovector + autovector_result = int(autovector) + assert autovector_result == 1 + eng.flush() + + total = len(results_plus) + len(results_minus) + plus_probability = len(results_plus)/100. + 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.) + + +def test_string(): + unit = X + gate = QPE(unit) + assert (str(gate) == "QPE(X)") From 6a46f45bec528ccb342661a07db09cdd2d8ad8a2 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 18 Jul 2019 10:44:46 +0200 Subject: [PATCH 026/113] Correct statistics in qpe test (#328) * Amplitude Amplification algorithm as a Gate in ops * Amplitude Amplification algorithm as a Gate in ops, correct test_string_functions * Amplitude Amplification algorithm as a Gate in ops, correct coverage for _qaagate_test * Amplitude Amplification algorithm as a Gate in ops, correct test estimation statistics in phaseestimation_test * Try to triger Travis test because an apt get failure in the travis test * resend docs/projectq.ops.rst file * resend file versions previous to AA * Try to triger Travis test because it never ran * Try to triger Travis test again to try to get the tests ran --- projectq/setups/decompositions/phaseestimation_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/setups/decompositions/phaseestimation_test.py b/projectq/setups/decompositions/phaseestimation_test.py index 828e522a4..1b8f8e63c 100644 --- a/projectq/setups/decompositions/phaseestimation_test.py +++ b/projectq/setups/decompositions/phaseestimation_test.py @@ -112,7 +112,7 @@ def test_2qubitsPh_andfunction_eigenvectors(): 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.34, "Statistics phase calculation are not correct (%f vs. %f)" % (num_phase/100., 0.34) def test_X_no_eigenvectors(): From f3582b7456d2ef860d810bd2ac7a272146467b18 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 18 Jul 2019 11:51:49 +0200 Subject: [PATCH 027/113] Amplitude Amplification algorithm as a Gate in ops (#327) * Amplitude Amplification algorithm as a Gate in ops * Amplitude Amplification algorithm as a Gate in ops, correct test_string_functions * Amplitude Amplification algorithm as a Gate in ops, correct coverage for _qaagate_test * Amplitude Amplification algorithm as a Gate in ops, correct test estimation statistics in phaseestimation_test * Try to triger Travis test because an apt get failure in the travis test * Address changes proposed by Damien * Address changes proposed by Damien, missed one * Address comments by Damien including eliminate the usage of algorith_inverse and eliminate QPE files form the PR * Address comments by Damien including eliminate the usage of algorith_inverse and eliminate QPE files form the PR, second try * Address comments by Damien forgot _qaagate_test * Update amplitudeamplification_test.py Wrap lines to 80 characters * Update amplitudeamplification.py Wrap lines to 80 characters * Update _qaagate.py Minor style correction * Update amplitudeamplification_test.py Cleanup imports --- docs/projectq.ops.rst | 1 + docs/projectq.setups.decompositions.rst | 8 + projectq/ops/__init__.py | 1 + projectq/ops/_qaagate.py | 81 ++++++++ projectq/ops/_qaagate_test.py | 27 +++ projectq/setups/decompositions/__init__.py | 6 +- .../decompositions/amplitudeamplification.py | 111 +++++++++++ .../amplitudeamplification_test.py | 182 ++++++++++++++++++ 8 files changed, 415 insertions(+), 2 deletions(-) create mode 100755 projectq/ops/_qaagate.py create mode 100755 projectq/ops/_qaagate_test.py create mode 100644 projectq/setups/decompositions/amplitudeamplification.py create mode 100644 projectq/setups/decompositions/amplitudeamplification_test.py diff --git a/docs/projectq.ops.rst b/docs/projectq.ops.rst index 58d66ee16..6b9ec5936 100755 --- a/docs/projectq.ops.rst +++ b/docs/projectq.ops.rst @@ -55,6 +55,7 @@ The operations collection consists of various default gates and is a work-in-pro projectq.ops.StatePreparation projectq.ops.QPE projectq.ops.FlipBits + projectq.ops.QAA Module contents diff --git a/docs/projectq.setups.decompositions.rst b/docs/projectq.setups.decompositions.rst index d900f5bfc..26cd392fc 100755 --- a/docs/projectq.setups.decompositions.rst +++ b/docs/projectq.setups.decompositions.rst @@ -27,6 +27,7 @@ The decomposition package is a collection of gate decomposition / replacement ru projectq.setups.decompositions.toffoli2cnotandtgate projectq.setups.decompositions.uniformlycontrolledr2cnot projectq.setups.decompositions.phaseestimation + projectq.setups.decompositions.amplitudeamplification Submodules @@ -180,6 +181,13 @@ projectq.setups.decompositions.phaseestimation module :members: :undoc-members: +projectq.setups.decompositions.amplitudeamplification module +--------------------------------------------------------------- + +.. automodule:: projectq.setups.decompositions.amplitudeamplification + :members: + :undoc-members: + Module contents --------------- diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 388350259..cac384d9e 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -38,3 +38,4 @@ UniformlyControlledRz) from ._state_prep import StatePreparation from ._qpegate import QPE +from ._qaagate import QAA diff --git a/projectq/ops/_qaagate.py b/projectq/ops/_qaagate.py new file mode 100755 index 000000000..751b9dc60 --- /dev/null +++ b/projectq/ops/_qaagate.py @@ -0,0 +1,81 @@ +# Copyright 2019 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 ._basics import BasicGate + + +class QAA(BasicGate): + """ + Quantum Aplitude Aplification 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) + + 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 + + def func_algorithm(eng,system_qubits): + All(H) | system_qubits + + def func_oracle(eng,system_qubits,qaa_ancilla): + # This oracle selects the state |010> as the one marked + with Compute(eng): + All(X) | system_qubits[0::2] + with Control(eng, system_qubits): + X | qaa_ancilla + Uncompute(eng) + + system_qubits = eng.allocate_qureg(3) + # Prepare the qaa_ancilla qubit in the |-> state + qaa_ancilla = eng.allocate_qubit() + X | qaa_ancilla + H | qaa_ancilla + + # Creates the initial state form the Algorithm + func_algorithm(eng, system_qubits) + # Apply Quantum Amplitude Amplification the correct number of times + num_it = int(math.pi/4.*math.sqrt(1 << 3)) + with Loop(eng, num_it): + QAA(func_algorithm, func_oracle) | (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` + + Attributes: + 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 + + """ + 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__)) diff --git a/projectq/ops/_qaagate_test.py b/projectq/ops/_qaagate_test.py new file mode 100755 index 000000000..3e15e6801 --- /dev/null +++ b/projectq/ops/_qaagate_test.py @@ -0,0 +1,27 @@ +# Copyright 2019 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.ops._qaagate.""" + +from projectq.ops import _qaagate, All, H, X + + +def test_qaa_str(): + + def func_algorithm(): All(H) + + 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/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index db29dc7a8..883f04581 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -32,7 +32,8 @@ toffoli2cnotandtgate, time_evolution, uniformlycontrolledr2cnot, - phaseestimation) + phaseestimation, + amplitudeamplification) all_defined_decomposition_rules = [ rule @@ -56,6 +57,7 @@ toffoli2cnotandtgate, time_evolution, uniformlycontrolledr2cnot, - phaseestimation] + phaseestimation, + amplitudeamplification] for rule in module.all_defined_decomposition_rules ] diff --git a/projectq/setups/decompositions/amplitudeamplification.py b/projectq/setups/decompositions/amplitudeamplification.py new file mode 100644 index 000000000..517aadeed --- /dev/null +++ b/projectq/setups/decompositions/amplitudeamplification.py @@ -0,0 +1,111 @@ +# Copyright 2019 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Registers a decomposition for quantum amplitude amplification. + +(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. + +Example: + .. code-block:: python + + def func_algorithm(eng,system_qubits): + All(H) | system_qubits + + def func_oracle(eng,system_qubits,qaa_ancilla): + # This oracle selects the state |010> as the one marked + with Compute(eng): + All(X) | system_qubits[0::2] + with Control(eng, system_qubits): + X | qaa_ancilla + Uncompute(eng) + + system_qubits = eng.allocate_qureg(3) + # Prepare the qaa_ancilla qubit in the |-> state + qaa_ancilla = eng.allocate_qubit() + X | qaa_ancilla + H | qaa_ancilla + + # Creates the initial state form the Algorithm + func_algorithm(eng, system_qubits) + # Apply Quantum Amplitude Amplification the correct number of times + num_it = int(math.pi/4.*math.sqrt(1 << 3)) + with Loop(eng, num_it): + QAA(func_algorithm, func_oracle) | (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` + +Attributes: + 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 + +""" + +import math +import numpy as np + +from projectq.cengines import DecompositionRule +from projectq.meta import Control, Compute, Uncompute, 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. """ + eng = cmd.engine + + # System-qubit is the first qubit/qureg. Ancilla qubit is the second qubit + system_qubits = cmd.qubits[0] + qaa_ancilla = cmd.qubits[1] + + # The Oracle and the Algorithm + Oracle = cmd.gate.oracle + A = cmd.gate.algorithm + + # Apply the oracle to invert the amplitude of the good states, S_Chi + 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) + 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) + Ph(math.pi) | system_qubits[0] + + +#: Decomposition rules +all_defined_decomposition_rules = [DecompositionRule(QAA, _decompose_QAA)] diff --git a/projectq/setups/decompositions/amplitudeamplification_test.py b/projectq/setups/decompositions/amplitudeamplification_test.py new file mode 100644 index 000000000..f99681713 --- /dev/null +++ b/projectq/setups/decompositions/amplitudeamplification_test.py @@ -0,0 +1,182 @@ +# Copyright 2019 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Tests for projectq.setups.decompositions.amplitudeamplification.py." + +import math +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, MainEngine) + +from projectq.ops import (X, H, Ry, All, Measure) +from projectq.meta import Loop, Control, Compute, Uncompute + +from projectq.ops import QAA +from projectq.setups.decompositions import amplitudeamplification as aa + + +def hache_algorithm(eng, qreg): + All(H) | qreg + + +def simple_oracle(eng, system_q, control): + # This oracle selects the state |1010101> as the one marked + with Compute(eng): + All(X) | system_q[1::2] + with Control(eng, system_q): + X | control + Uncompute(eng) + + +def test_simple_grover(): + rule_set = DecompositionRuleSet(modules=[aa]) + + eng = MainEngine(backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + ]) + + system_qubits = eng.allocate_qureg(7) + + # Prepare the control qubit in the |-> state + control = eng.allocate_qubit() + X | control + H | control + + # Creates the initial state form the Algorithm + hache_algorithm(eng, system_qubits) + + # Get the amplitude of the marked state before the AA + # to calculate the number of iterations + eng.flush() + prob1010101 = eng.backend.get_probability('1010101', system_qubits) + + total_amp_before = math.sqrt(prob1010101) + theta_before = math.asin(total_amp_before) + + # Apply Quantum Amplitude Amplification the correct number of times + # 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 + with Loop(eng, num_it): + QAA(hache_algorithm, simple_oracle) | (system_qubits, control) + + # Get the probabilty of getting the marked state after the AA + # to compare with the theoretical probability after teh AA + eng.flush() + prob1010101 = eng.backend.get_probability('1010101', system_qubits) + total_prob_after = prob1010101 + + 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)) + + +def complex_algorithm(eng, qreg): + All(H) | qreg + with Control(eng, qreg[0]): + All(X) | qreg[1:] + All(Ry(math.pi / 4)) | qreg[1:] + with Control(eng, qreg[-1]): + All(X) | qreg[1:-1] + + +def complex_oracle(eng, system_q, control): + # This oracle selects the subspace |000000>+|111111> as the good one + with Compute(eng): + with Control(eng, system_q[0]): + All(X) | system_q[1:] + H | system_q[0] + All(X) | system_q + + with Control(eng, system_q): + X | control + + Uncompute(eng) + + +def test_complex_aa(): + rule_set = DecompositionRuleSet(modules=[aa]) + + eng = MainEngine(backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + ]) + + system_qubits = eng.allocate_qureg(6) + + # Prepare the control qubit in the |-> state + control = eng.allocate_qubit() + X | control + H | control + + # Creates the initial state form the Algorithm + complex_algorithm(eng, system_qubits) + + # Get the probabilty of getting the marked state before the AA + # to calculate the number of iterations + eng.flush() + prob000000 = eng.backend.get_probability('000000', system_qubits) + prob111111 = eng.backend.get_probability('111111', system_qubits) + + total_amp_before = math.sqrt(prob000000 + prob111111) + theta_before = math.asin(total_amp_before) + + # Apply Quantum Amplitude Amplification the correct number of times + # 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 + with Loop(eng, num_it): + QAA(complex_algorithm, complex_oracle) | (system_qubits, control) + + # Get the probabilty of getting the marked state after the AA + # to compare with the theoretical probability after the AA + eng.flush() + prob000000 = eng.backend.get_probability('000000', system_qubits) + prob111111 = eng.backend.get_probability('111111', system_qubits) + total_prob_after = prob000000 + prob111111 + + 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)) + + +def test_string_functions(): + algorithm = hache_algorithm + oracle = simple_oracle + gate = QAA(algorithm, oracle) + assert (str(gate) == + "QAA(Algorithm = hache_algorithm, Oracle = simple_oracle)") From 9db33835239eeaf1e1eb1a318ab3a471646d726d Mon Sep 17 00:00:00 2001 From: David Wierichs Date: Wed, 14 Aug 2019 10:11:13 +0200 Subject: [PATCH 028/113] 3 additional 2-qubit gates (#330) * Modified _gates.py: Documentation, 2-qubit rotations, 1qubit-rotation string attributes. * Strings of rotation gates fixed. * Added two-qubit rotation gate tests. * Resource Counter import Rzz added. * Added Rzz test and fixed expected outcome. * removed wrongfully pushed dev gates. * Update _gates.py Remove unneeded import * Removed hardcoded "Phase" name for Ph(angle) gate --- projectq/backends/_resource_test.py | 5 +-- projectq/ops/_gates.py | 48 +++++++++++++++++++++++++++-- projectq/ops/_gates_test.py | 36 ++++++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/projectq/backends/_resource_test.py b/projectq/backends/_resource_test.py index 664b687ad..cf7122c01 100755 --- a/projectq/backends/_resource_test.py +++ b/projectq/backends/_resource_test.py @@ -20,7 +20,7 @@ from projectq.cengines import DummyEngine, MainEngine, NotYetMeasuredError from projectq.meta import LogicalQubitIDTag -from projectq.ops import All, Allocate, CNOT, Command, H, Measure, QFT, Rz, X +from projectq.ops import All, Allocate, CNOT, Command, H, Measure, QFT, Rz, Rzz, X from projectq.types import WeakQubitRef from projectq.backends import ResourceCounter @@ -74,6 +74,7 @@ def test_resource_counter(): CNOT | (qubit1, qubit3) Rz(0.1) | qubit1 Rz(0.3) | qubit1 + Rzz(0.5) | qubit1 All(Measure) | qubit1 + qubit3 @@ -81,7 +82,7 @@ def test_resource_counter(): int(qubit1) assert resource_counter.max_width == 2 - assert resource_counter.depth_of_dag == 5 + assert resource_counter.depth_of_dag == 6 str_repr = str(resource_counter) assert str_repr.count(" HGate : 1") == 1 diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index d524e4b3b..8967b1da5 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -16,17 +16,29 @@ Contains definitions of standard gates such as * Hadamard (H) * Pauli-X (X / NOT) +* Pauli-Y (Y) * Pauli-Z (Z) +* S and its inverse (S / Sdagger) * T and its inverse (T / Tdagger) +* SqrtX gate (SqrtX) * Swap gate (Swap) +* SqrtSwap gate (SqrtSwap) +* Entangle (Entangle) * Phase gate (Ph) +* Rotation-X (Rx) +* Rotation-Y (Ry) * Rotation-Z (Rz) +* Rotation-XX on two qubits (Rxx) +* Rotation-YY on two qubits (Ryy) +* Rotation-ZZ on two qubits (Rzz) * Phase-shift (R) * Measurement (Measure) and meta gates, i.e., * Allocate / Deallocate qubits * Flush gate (end of circuit) +* Barrier +* FlipBits """ import math @@ -110,7 +122,7 @@ def __str__(self): #: Shortcut (instance of) :class:`projectq.ops.SGate` S = SGate() -#: Shortcut (instance of) :class:`projectq.ops.SGate` +#: Inverse (and shortcut) of :class:`projectq.ops.SGate` Sdag = Sdagger = get_inverse(S) @@ -125,7 +137,7 @@ def __str__(self): #: Shortcut (instance of) :class:`projectq.ops.TGate` T = TGate() -#: Shortcut (instance of) :class:`projectq.ops.TGate` +#: Inverse (and shortcut) of :class:`projectq.ops.TGate` Tdag = Tdagger = get_inverse(T) @@ -217,7 +229,7 @@ def matrix(self): class Ry(BasicRotationGate): - """ RotationX gate class """ + """ RotationY gate class """ @property def matrix(self): return np.matrix([[math.cos(0.5 * self.angle), @@ -234,6 +246,36 @@ def matrix(self): [0, cmath.exp(.5 * 1j * self.angle)]]) +class Rxx(BasicRotationGate): + """ 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)]]) + + +class Ryy(BasicRotationGate): + """ 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)]]) + + +class Rzz(BasicRotationGate): + """ 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)]]) + + class R(BasicPhaseGate): """ Phase-shift gate (equivalent to Rz up to a global phase) """ @property diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index efcd63b0a..88efa3a19 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -156,6 +156,42 @@ def test_rz(angle): assert np.allclose(gate.matrix, expected_matrix) +@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)]]) + 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]) +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)]]) + 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]) +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)]]) + 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]) def test_ph(angle): gate = _gates.Ph(angle) From f27286f520ff8b1ca2dec9142b01b59a2490fcd5 Mon Sep 17 00:00:00 2001 From: Melven Roehrig-Zoellner Date: Tue, 20 Aug 2019 10:12:09 +0200 Subject: [PATCH 029/113] C++ simulator performance improvements (#329) * C++ simulator performance: make the swap-gate run in native C++ It was defined as a BasicMathGate before which made it run as python code through the emulate_math_wrapper. The new variant just uses its matrix representation to run it in native code. * C++ simulator performance: add dedicated C++ code for common math gates The BasicMathGate uses a C++ python wrapper (emulate_math_wrapper) to allow generic calculations which makes it very slow. This detects some math gates and provides a native C++ implementation for it. * C++ simulator performance: use larger memory alignment * C++ simulator performance: recycle large StateVector memory buffers This avoids costly std::vector copying/reallocations by using some static std::vector to reuse the allocated buffer (just by std::swap'ing a vector into a buffer for later use when it would be deallocated otherwise). * C++ simulator performance: improve compiler flags * Add test coverage for constant math emulation * Revert "Add test coverage for constant math emulation" This reverts commit 3bb8a2cc7fd595db48b0f4d260124ccfe60d7fcf. * Add test coverage for constant math emulation --- .../backends/_sim/_cppkernels/simulator.hpp | 154 +++++++++++++----- projectq/backends/_sim/_cppsim.cpp | 3 + projectq/backends/_sim/_simulator.py | 28 +++- projectq/backends/_sim/_simulator_test.py | 52 ++++++ projectq/ops/_gates.py | 3 +- setup.py | 1 + 6 files changed, 196 insertions(+), 45 deletions(-) diff --git a/projectq/backends/_sim/_cppkernels/simulator.hpp b/projectq/backends/_sim/_cppkernels/simulator.hpp index c4e611eba..d248ed038 100755 --- a/projectq/backends/_sim/_cppkernels/simulator.hpp +++ b/projectq/backends/_sim/_cppkernels/simulator.hpp @@ -38,7 +38,7 @@ class Simulator{ public: using calc_type = double; using complex_type = std::complex; - using StateVector = std::vector>; + using StateVector = std::vector>; using Map = std::map; using RndEngine = std::mt19937; using Term = std::vector>; @@ -55,11 +55,18 @@ class Simulator{ void allocate_qubit(unsigned id){ if (map_.count(id) == 0){ map_[id] = N_++; - auto newvec = StateVector(1UL << N_); - #pragma omp parallel for schedule(static) + StateVector newvec; // avoid large memory allocations + if( tmpBuff1_.capacity() >= (1UL << N_) ) + std::swap(newvec, tmpBuff1_); + newvec.resize(1UL << N_); +#pragma omp parallel for schedule(static) for (std::size_t i = 0; i < newvec.size(); ++i) newvec[i] = (i < vec_.size())?vec_[i]:0.; - vec_ = std::move(newvec); + std::swap(vec_, newvec); + // recycle large memory + std::swap(tmpBuff1_, newvec); + if( tmpBuff1_.capacity() < tmpBuff2_.capacity() ) + std::swap(tmpBuff1_, tmpBuff2_); } else throw(std::runtime_error( @@ -113,12 +120,18 @@ class Simulator{ } } else{ - StateVector newvec((1UL << (N_-1))); - #pragma omp parallel for schedule(static) + StateVector newvec; // avoid costly memory reallocations + if( tmpBuff1_.capacity() >= (1UL << (N_-1)) ) + std::swap(tmpBuff1_, newvec); + newvec.resize((1UL << (N_-1))); + #pragma omp parallel for schedule(static) if(0) for (std::size_t i = 0; i < vec_.size(); i += 2*delta) std::copy_n(&vec_[i + static_cast(value)*delta], delta, &newvec[i/2]); - vec_ = std::move(newvec); + std::swap(vec_, newvec); + std::swap(tmpBuff1_, newvec); + if( tmpBuff1_.capacity() < tmpBuff2_.capacity() ) + std::swap(tmpBuff1_, tmpBuff2_); for (auto& p : map_){ if (p.second > pos) @@ -189,8 +202,8 @@ class Simulator{ } template - void apply_controlled_gate(M const& m, std::vector ids, - std::vector ctrl){ + void apply_controlled_gate(M const& m, const std::vector& ids, + const std::vector& ctrl){ auto fused_gates = fused_gates_; fused_gates.insert(m, ids, ctrl); @@ -209,8 +222,8 @@ class Simulator{ } template - void emulate_math(F const& f, QuReg quregs, std::vector ctrl, - unsigned num_threads=1){ + void emulate_math(F const& f, QuReg quregs, const std::vector& ctrl, + bool parallelize = false){ run(); auto ctrlmask = get_control_mask(ctrl); @@ -218,37 +231,76 @@ class Simulator{ for (unsigned j = 0; j < quregs[i].size(); ++j) quregs[i][j] = map_[quregs[i][j]]; - StateVector newvec(vec_.size(), 0.); - std::vector res(quregs.size()); - - #pragma omp parallel for schedule(static) firstprivate(res) num_threads(num_threads) - for (std::size_t i = 0; i < vec_.size(); ++i){ - if ((ctrlmask&i) == ctrlmask){ - for (unsigned qr_i = 0; qr_i < quregs.size(); ++qr_i){ - res[qr_i] = 0; - for (unsigned qb_i = 0; qb_i < quregs[qr_i].size(); ++qb_i) - res[qr_i] |= ((i >> quregs[qr_i][qb_i])&1) << qb_i; - } - f(res); - auto new_i = i; - for (unsigned qr_i = 0; qr_i < quregs.size(); ++qr_i){ - for (unsigned qb_i = 0; qb_i < quregs[qr_i].size(); ++qb_i){ - if (!(((new_i >> quregs[qr_i][qb_i])&1) == ((res[qr_i] >> qb_i)&1))) - new_i ^= (1UL << quregs[qr_i][qb_i]); - } - } - newvec[new_i] += vec_[i]; - } - else - newvec[i] += vec_[i]; + StateVector newvec; // avoid costly memory reallocations + if( tmpBuff1_.capacity() >= vec_.size() ) + std::swap(newvec, tmpBuff1_); + newvec.resize(vec_.size()); +#pragma omp parallel for schedule(static) + for (std::size_t i = 0; i < vec_.size(); i++) + newvec[i] = 0; + +//#pragma omp parallel reduction(+:newvec[:newvec.size()]) if(parallelize) // requires OpenMP 4.5 + { + std::vector res(quregs.size()); + //#pragma omp for schedule(static) + for (std::size_t i = 0; i < vec_.size(); ++i){ + if ((ctrlmask&i) == ctrlmask){ + for (unsigned qr_i = 0; qr_i < quregs.size(); ++qr_i){ + res[qr_i] = 0; + for (unsigned qb_i = 0; qb_i < quregs[qr_i].size(); ++qb_i) + res[qr_i] |= ((i >> quregs[qr_i][qb_i])&1) << qb_i; + } + f(res); + auto new_i = i; + for (unsigned qr_i = 0; qr_i < quregs.size(); ++qr_i){ + for (unsigned qb_i = 0; qb_i < quregs[qr_i].size(); ++qb_i){ + if (!(((new_i >> quregs[qr_i][qb_i])&1) == ((res[qr_i] >> qb_i)&1))) + new_i ^= (1UL << quregs[qr_i][qb_i]); + } + } + newvec[new_i] += vec_[i]; + } + else + newvec[i] += vec_[i]; + } } - vec_ = std::move(newvec); + std::swap(vec_, newvec); + std::swap(tmpBuff1_, newvec); + } + + // 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 + 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 + template + inline void emulate_math_multiplyByConstantModN(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); } calc_type get_expectation_value(TermsDict const& td, std::vector const& ids){ run(); calc_type expectation = 0.; - auto current_state = vec_; + + StateVector current_state; // avoid costly memory reallocations + if( tmpBuff1_.capacity() >= vec_.size() ) + std::swap(tmpBuff1_, current_state); + current_state.resize(vec_.size()); +#pragma omp parallel for schedule(static) + for (std::size_t i = 0; i < vec_.size(); ++i) + current_state[i] = vec_[i]; + for (auto const& term : td){ auto const& coefficient = term.second; apply_term(term.first, ids, {}); @@ -260,17 +312,29 @@ class Simulator{ auto const a2 = std::real(vec_[i]); auto const b2 = std::imag(vec_[i]); delta += a1 * a2 - b1 * b2; + // reset vec_ + vec_[i] = current_state[i]; } expectation += coefficient * delta; - vec_ = current_state; } + std::swap(current_state, tmpBuff1_); return expectation; } void apply_qubit_operator(ComplexTermsDict const& td, std::vector const& ids){ run(); - auto new_state = StateVector(vec_.size(), 0.); - auto current_state = vec_; + StateVector new_state, current_state; // avoid costly memory reallocations + if( tmpBuff1_.capacity() >= vec_.size() ) + std::swap(tmpBuff1_, new_state); + if( tmpBuff2_.capacity() >= vec_.size() ) + std::swap(tmpBuff2_, current_state); + new_state.resize(vec_.size()); + current_state.resize(vec_.size()); +#pragma omp parallel for schedule(static) + for (std::size_t i = 0; i < vec_.size(); ++i){ + new_state[i] = 0; + current_state[i] = vec_[i]; + } for (auto const& term : td){ auto const& coefficient = term.second; apply_term(term.first, ids, {}); @@ -280,7 +344,9 @@ class Simulator{ vec_[i] = current_state[i]; } } - vec_ = std::move(new_state); + std::swap(vec_, new_state); + std::swap(tmpBuff1_, new_state); + std::swap(tmpBuff2_, current_state); } calc_type get_probability(std::vector const& bit_string, @@ -452,6 +518,8 @@ class Simulator{ #pragma omp parallel kernel(vec_, ids[4], ids[3], ids[2], ids[1], ids[0], m, ctrlmask); break; + default: + throw std::invalid_argument("Gates with more than 5 qubits are not supported!"); } fused_gates_ = Fusion(); @@ -500,6 +568,12 @@ class Simulator{ unsigned fusion_qubits_min_, fusion_qubits_max_; RndEngine rnd_eng_; std::function rng_; + + // large array buffers to avoid costly reallocations + static StateVector tmpBuff1_, tmpBuff2_; }; +Simulator::StateVector Simulator::tmpBuff1_; +Simulator::StateVector Simulator::tmpBuff2_; + #endif diff --git a/projectq/backends/_sim/_cppsim.cpp b/projectq/backends/_sim/_cppsim.cpp index 74498d4e2..cab68d0ee 100755 --- a/projectq/backends/_sim/_cppsim.cpp +++ b/projectq/backends/_sim/_cppsim.cpp @@ -50,6 +50,9 @@ PYBIND11_PLUGIN(_cppsim) { .def("measure_qubits", &Simulator::measure_qubits_return) .def("apply_controlled_gate", &Simulator::apply_controlled_gate) .def("emulate_math", &emulate_math_wrapper) + .def("emulate_math_addConstant", &Simulator::emulate_math_addConstant) + .def("emulate_math_addConstantModN", &Simulator::emulate_math_addConstantModN) + .def("emulate_math_multiplyByConstantModN", &Simulator::emulate_math_multiplyByConstantModN) .def("get_expectation_value", &Simulator::get_expectation_value) .def("apply_qubit_operator", &Simulator::apply_qubit_operator) .def("emulate_time_evolution", &Simulator::emulate_time_evolution) diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index 2218c3471..19e884d6d 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -33,10 +33,12 @@ TimeEvolution) from projectq.types import WeakQubitRef +FALLBACK_TO_PYSIM = False try: from ._cppsim import Simulator as SimulatorBackend except ImportError: from ._pysim import Simulator as SimulatorBackend + FALLBACK_TO_PYSIM = True class Simulator(BasicEngine): @@ -384,14 +386,34 @@ def _handle(self, cmd): ID = cmd.qubits[0][0].id 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) qubitids = [] for qr in cmd.qubits: qubitids.append([]) for qb in qr: qubitids[-1].append(qb.id) - math_fun = cmd.gate.get_math_function(cmd.qubits) - self._simulator.emulate_math(math_fun, qubitids, - [qb.id for qb in cmd.control_qubits]) + 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]) + 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]) + 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]) + 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]) + 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]) elif isinstance(cmd.gate, TimeEvolution): op = [(list(term), coeff) for (term, coeff) in cmd.gate.hamiltonian.terms.items()] diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 9e301be0d..9f7d298cb 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -683,3 +683,55 @@ def receive(command_list): qubit1[0].id: qubit0[0].id} assert (sim._convert_logical_to_mapped_qureg(qubit0 + qubit1) == qubit1 + qubit0) + + +def test_simulator_constant_math_emulation(): + if "cpp_simulator" not in get_available_simulators(): + pytest.skip("No C++ simulator") + return + + results = [[[1, 1, 0, 0, 0]], [[0, 1, 0, 0, 0]], [[0, 1, 1, 1, 0]]] + + 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) + + def gate_filter(eng, cmd): + g = cmd.gate + if isinstance(g, BasicMathGate): + return False + return eng.next_engine.is_available(cmd) + + def run_simulation(sim): + eng = MainEngine(sim, []) + quint = eng.allocate_qureg(5) + AddConstant(3) | quint + All(Measure) | quint + eng.flush() + results[0].append([int(qb) for qb in quint]) + + AddConstantModN(4, 5) | quint + All(Measure) | quint + eng.flush() + results[1].append([int(qb) for qb in quint]) + + MultiplyByConstantModN(15, 16) | quint + All(Measure) | quint + eng.flush() + results[2].append([int(qb) for qb in quint]) + + cppsim = Simulator(gate_fusion=False) + cppsim._simulator = CppSim(1) + run_simulation(cppsim) + + _sim.FALLBACK_TO_PYSIM = True + pysim = Simulator() + pysim._simulator = PySim(1) + # run_simulation(pysim) + + for result in results: + ref = result[0] + for res in result[1:]: + assert ref == res diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 8967b1da5..be2240d00 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -157,10 +157,9 @@ def __str__(self): SqrtX = SqrtXGate() -class SwapGate(SelfInverseGate, BasicMathGate): +class SwapGate(SelfInverseGate): """ Swap gate class (swaps 2 qubits) """ def __init__(self): - BasicMathGate.__init__(self, lambda x, y: (y, x)) SelfInverseGate.__init__(self) self.interchangeable_qubit_indices = [[0, 1]] diff --git a/setup.py b/setup.py index 604f006af..a0254b0e1 100755 --- a/setup.py +++ b/setup.py @@ -124,6 +124,7 @@ def build_extensions(self): opts.append('/arch:AVX') else: opts.append('-march=native') + opts.append('-ffast-math') opts.append(openmp) if ct == 'unix': From 44ba35baf84b08741c51c5d96460df4d0a802d31 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Fri, 23 Aug 2019 21:12:36 +0200 Subject: [PATCH 030/113] Update constant math documentation to include list of pre-conditions (#331) --- projectq/libs/math/_gates.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/projectq/libs/math/_gates.py b/projectq/libs/math/_gates.py index fe1df6784..ef5cade99 100755 --- a/projectq/libs/math/_gates.py +++ b/projectq/libs/math/_gates.py @@ -90,6 +90,14 @@ class AddConstantModN(BasicMathGate): qunum = eng.allocate_qureg(5) # 5-qubit number X | qunum[1] # qunum is now equal to 2 AddConstantModN(3, 4) | qunum # qunum is now equal to 1 + + .. note:: + + Pre-conditions: + + * c < N + * c >= 0 + * The value stored in the quantum register must be lower than N """ def __init__(self, a, N): """ @@ -145,6 +153,14 @@ def SubConstantModN(a, N): qunum = eng.allocate_qureg(3) # 3-qubit number X | qunum[1] # qunum is now equal to 2 SubConstantModN(4,5) | qunum # qunum is now -2 = 6 = 1 (mod 5) + + .. note:: + + Pre-conditions: + + * c < N + * c >= 0 + * The value stored in the quantum register must be lower than N """ return AddConstantModN(N - a, N) @@ -162,6 +178,15 @@ class MultiplyByConstantModN(BasicMathGate): qunum = eng.allocate_qureg(5) # 5-qubit number X | qunum[2] # qunum is now equal to 4 MultiplyByConstantModN(3,5) | qunum # qunum is now 2. + + .. note:: + + Pre-conditions: + + * c < N + * c >= 0 + * gcd(c, N) == 1 + * The value stored in the quantum register must be lower than N """ def __init__(self, a, N): """ From 4bd6e6fc055777e308feefcc6d6a73cf4f31e6df Mon Sep 17 00:00:00 2001 From: alexandrupaler Date: Fri, 25 Oct 2019 09:54:23 +0200 Subject: [PATCH 031/113] Allow selection of drawing order in CircuitDrawer (solves #333) (#334) * included the possibility to draw the gates in the order they were added to the circuit * edited param names tests should pass now * solved comments * solved comments * passes tests * Test for unordered and ordered circuit drawing * Reindent files in _circuits * Minor adjustments to _drawer.py * added test_body_with_drawing_order * fixed tests and how draw_gates_in_parallel is interpreted * Fix failing tests with Python 2.7 One test of _to_latex_test.py was failing due to precision issues. --- projectq/backends/_circuits/_drawer.py | 35 ++- projectq/backends/_circuits/_drawer_test.py | 15 +- projectq/backends/_circuits/_to_latex.py | 295 ++++++++++++------ projectq/backends/_circuits/_to_latex_test.py | 184 +++++++++-- 4 files changed, 391 insertions(+), 138 deletions(-) diff --git a/projectq/backends/_circuits/_drawer.py b/projectq/backends/_circuits/_drawer.py index 269f592a2..85aee3dac 100755 --- a/projectq/backends/_circuits/_drawer.py +++ b/projectq/backends/_circuits/_drawer.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Contains a compiler engine which generates TikZ Latex code describing the circuit. @@ -42,9 +41,9 @@ 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) @@ -153,6 +152,9 @@ def __init__(self, accept_input=False, default_measure=0): self._free_lines = [] self._map = dict() + # Order in which qubit lines are drawn + self._drawing_order = [] + def is_available(self, cmd): """ Specialized implementation of is_available: Returns True if the @@ -190,7 +192,7 @@ def set_qubit_locations(self, id_to_loc): 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): + 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" @@ -221,7 +223,7 @@ 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) + assert (get_control_count(cmd) == 0) for qureg in cmd.qubits: for qubit in qureg: if self._accept_input: @@ -244,7 +246,9 @@ def _print_cmd(self, cmd): for l in all_lines: self._qubit_lines[l].append(item) - def get_latex(self): + self._drawing_order.append(all_lines[0]) + + def get_latex(self, ordered=False, draw_gates_in_parallel=True): """ Return the latex document string representing the circuit. @@ -256,6 +260,12 @@ def get_latex(self): python3 my_circuit.py | pdflatex where my_circuit.py calls this function and prints it to the terminal. + + Args: + ordered(bool): flag if the gates should be drawn in the order they + were added to the circuit + draw_gates_in_parallel(bool): flag if parallel gates should be drawn + parallel (True), or not (False) """ qubit_lines = dict() @@ -271,10 +281,13 @@ def get_latex(self): new_cmd.id = cmd.lines[0] qubit_lines[new_line].append(new_cmd) - circuit = [] - for lines in qubit_lines: - circuit.append(qubit_lines[lines]) - return to_latex(qubit_lines) + drawing_order = None + if ordered: + drawing_order = self._drawing_order + + 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_test.py b/projectq/backends/_circuits/_drawer_test.py index 7df4bd0ee..b73513b64 100755 --- a/projectq/backends/_circuits/_drawer_test.py +++ b/projectq/backends/_circuits/_drawer_test.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Tests for projectq.backends.circuits._drawer.py. """ @@ -20,19 +19,17 @@ from projectq import MainEngine from projectq.cengines import LastEngineException -from projectq.ops import (H, - X, - CNOT, - Measure) +from projectq.ops import (H, X, CNOT, Measure) from projectq.meta import Control import projectq.backends._circuits._drawer as _drawer from projectq.backends._circuits._drawer import CircuitItem, CircuitDrawer -def test_drawer_getlatex(): +@pytest.mark.parametrize("ordered", [False, True]) +def test_drawer_getlatex(ordered): old_latex = _drawer.to_latex - _drawer.to_latex = lambda x: x + _drawer.to_latex = lambda x, drawing_order, draw_gates_in_parallel: x drawer = CircuitDrawer() drawer.set_qubit_locations({0: 1, 1: 0}) @@ -46,13 +43,13 @@ def test_drawer_getlatex(): X | qureg[0] CNOT | (qureg[0], qureg[1]) - lines = drawer2.get_latex() + lines = drawer2.get_latex(ordered=ordered) assert len(lines) == 2 assert len(lines[0]) == 4 assert len(lines[1]) == 3 # check if it was sent on correctly: - lines = drawer.get_latex() + lines = drawer.get_latex(ordered=ordered) assert len(lines) == 2 assert len(lines[0]) == 3 assert len(lines[1]) == 4 diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index 1c028acfc..385f3d3f3 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -17,7 +17,7 @@ Measure, SqrtSwap, Swap, X, Z) -def to_latex(circuit): +def to_latex(circuit, drawing_order=None, draw_gates_in_parallel=True): """ Translates a given circuit to a TikZ picture in a Latex document. @@ -38,8 +38,12 @@ class name string as a key. Every gate can have its own width, height, pre function, and written using write_settings(). Args: - circuit (list>): Each qubit line is a list of + circuit (list): Each qubit line is a list of CircuitItem objects, i.e., in circuit[line]. + drawing_order (list): A list of qubit lines from which + the gates to be read from + draw_gates_in_parallel (bool): If gates should (False) + or not (True) be parallel in the circuit Returns: tex_doc_str (string): Latex document string which can be compiled @@ -57,7 +61,10 @@ 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) + text += _body(circuit, + settings, + drawing_order, + draw_gates_in_parallel=draw_gates_in_parallel) text += _footer(settings) return text @@ -83,39 +90,88 @@ def get_default_settings(): """ settings = dict() settings['gate_shadow'] = True - 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}, - 'DeallocateQubitGate': {'height': .15, 'offset': .2, - 'width': .2, - 'pre_offset': .1}, - 'AllocateQubitGate': {'height': .15, 'width': .2, - 'offset': .1, - 'pre_offset': .1, - 'draw_id': False, - 'allocate_at_zero': False}, - 'MeasureGate': {'width': 0.75, 'offset': .2, - 'height': .5, 'pre_offset': .2} - }) + 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 + }, + 'DeallocateQubitGate': { + 'height': .15, + 'offset': .2, + 'width': .2, + 'pre_offset': .1 + }, + 'AllocateQubitGate': { + 'height': .15, + 'width': .2, + 'offset': .1, + 'pre_offset': .1, + 'draw_id': False, + 'allocate_at_zero': False + }, + 'MeasureGate': { + 'width': 0.75, + 'offset': .2, + 'height': .5, + 'pre_offset': .2 + } + }) settings['control'] = {'size': .1, 'shadow': False} return settings @@ -153,8 +209,7 @@ def _header(settings): 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" - ) + "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," @@ -167,9 +222,9 @@ def _header(settings): 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']) + "{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=" @@ -182,13 +237,19 @@ def _header(settings): return packages + init + gate_style + edge_style -def _body(circuit, 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. 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. Returns: tex_str (string): Latex string to draw the entire circuit. @@ -196,8 +257,19 @@ def _body(circuit, settings): code = [] conv = _Circ2Tikz(settings, len(circuit)) - for line in range(len(circuit)): - code.append(conv.to_tikz(line, circuit)) + + to_where = None + if drawing_order is None: + drawing_order = list(range(len(circuit))) + else: + to_where = 1 + + for line in drawing_order: + code.append( + conv.to_tikz(line, + circuit, + end=to_where, + draw_gates_in_parallel=draw_gates_in_parallel)) return "".join(code) @@ -219,7 +291,6 @@ 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. @@ -234,7 +305,7 @@ 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): + def to_tikz(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. @@ -247,6 +318,7 @@ def to_tikz(self, line, circuit, end=None): line (int): Line to generate the TikZ code for. circuit (list>): The circuit to draw. end (int): Gate index to stop at (for recursion). + draw_gates_in_parallel (bool): True or False for how to place gates Returns: tikz_code (string): TikZ code representing the current qubit line @@ -272,19 +344,23 @@ def to_tikz(self, line, circuit, end=None): gate_idx += 1 tikz_code.append(self.to_tikz(l, circuit, gate_idx)) + # we are taking care of gate 0 (the current one) circuit[l] = circuit[l][1:] all_lines = lines + ctrl_lines - pos = max([self.pos[l] for l in range(min(all_lines), - max(all_lines) + 1)]) + 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) connections = "" for l in all_lines: connections += self._line(self.op_count[l] - 1, - self.op_count[l], line=l) + self.op_count[l], + line=l) add_str = "" if gate == X: # draw NOT-gate with controls @@ -298,7 +374,8 @@ def to_tikz(self, line, circuit, end=None): elif gate == Swap: add_str = self._swap_gate(lines, ctrl_lines) elif gate == SqrtSwap: - add_str = self._sqrtswap_gate(lines, ctrl_lines, + 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) @@ -319,10 +396,13 @@ def to_tikz(self, line, circuit, end=None): "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) + "{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)) @@ -336,15 +416,15 @@ def to_tikz(self, line, circuit, end=None): xpos = self.pos[line] try: if (self.settings['gates']['AllocateQubitGate'] - ['allocate_at_zero']): + ['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]) - add_str = add_str.format(self._op(line), xpos, line, - id_str) + self.pos[line] = max( + xpos + self._gate_offset(gate) + self._gate_width(gate), + 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'] elif gate == Deallocate: @@ -353,9 +433,10 @@ def to_tikz(self, line, circuit, end=None): add_str = "\n\\node[none] ({}) at ({},-{}) {{}};" 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) + add_str += ( + "\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)) @@ -370,6 +451,11 @@ def to_tikz(self, line, circuit, end=None): 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] + circuit[line] = circuit[line][end:] return "".join(tikz_code) @@ -402,7 +488,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() @@ -420,20 +506,27 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): 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) + "\\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. pos = self.pos[lines[0]] - op_mid = "line{}_gate{}".format( - '{}-{}'.format(*lines), self.op_count[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=op_mid, line=midpoint, pos=pos, - dagger='^{{\\dagger}}' if daggered else '') + {{\\scriptsize $\\frac{{1}}{{2}}{dagger}$}};").format( + op=op_mid, + line=midpoint, + pos=pos, + dagger='^{{\\dagger}}' if daggered else '') # add two vertical lines to connect circled 1/2 gate_str += "\n\\draw ({}) edge[edgestyle] ({});".format( @@ -468,7 +561,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() @@ -486,10 +579,15 @@ def _swap_gate(self, lines, ctrl_lines): 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) + "\\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: @@ -519,15 +617,15 @@ 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]) + "[edgestyle] ({op}.west)--({op}.east);").format( + op=op, line=line, pos=self.pos[line]) if len(ctrl_lines) > 0: for ctrl in ctrl_lines: @@ -705,10 +803,16 @@ def _line(self, p1, p2, double=False, line=None): 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) + 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): @@ -744,23 +848,24 @@ def _regular_gate(self, gate, lines, ctrl_lines): 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) + " ({},-{}) {{}};").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) tex_str += node1 + node2 + node3 if l not in gate_lines: - tex_str += self._line(self.op_count[l] - 1, self.op_count[l], + 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) + "{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. diff --git a/projectq/backends/_circuits/_to_latex_test.py b/projectq/backends/_circuits/_to_latex_test.py index bf26b9923..c993bcf2e 100755 --- a/projectq/backends/_circuits/_to_latex_test.py +++ b/projectq/backends/_circuits/_to_latex_test.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Tests for projectq.backends._circuits._to_latex.py. """ @@ -22,18 +21,19 @@ from projectq import MainEngine from projectq.cengines import LastEngineException -from projectq.ops import (BasicGate, - H, - X, - CNOT, - Measure, - Z, - Swap, - SqrtX, - SqrtSwap, - C, - get_inverse, - ) +from projectq.ops import ( + BasicGate, + H, + X, + CNOT, + Measure, + Z, + Swap, + SqrtX, + SqrtSwap, + C, + get_inverse, +) from projectq.meta import Control from projectq.backends import CircuitDrawer @@ -47,7 +47,7 @@ def test_tolatex(): old_footer = _to_latex._footer _to_latex._header = lambda x: "H" - _to_latex._body = lambda x, y: x + _to_latex._body = lambda x, settings, drawing_order, draw_gates_in_parallel: x _to_latex._footer = lambda x: "F" latex = _to_latex.to_latex("B") @@ -68,11 +68,26 @@ def test_default_settings(): def test_header(): - settings = {'gate_shadow': False, 'control': {'shadow': False, 'size': 0}, - 'gates': {'MeasureGate': {'height': 0, 'width': 0}, - 'XGate': {'height': 1, 'width': .5} - }, - 'lines': {'style': 'my_style'}} + settings = { + 'gate_shadow': False, + 'control': { + 'shadow': False, + 'size': 0 + }, + 'gates': { + 'MeasureGate': { + 'height': 0, + 'width': 0 + }, + 'XGate': { + 'height': 1, + 'width': .5 + } + }, + 'lines': { + 'style': 'my_style' + } + } header = _to_latex._header(settings) assert 'minimum' in header @@ -104,7 +119,7 @@ def test_large_gates(): drawer = _drawer.CircuitDrawer() eng = MainEngine(drawer, []) old_tolatex = _drawer.to_latex - _drawer.to_latex = lambda x: x + _drawer.to_latex = lambda x, drawing_order, draw_gates_in_parallel: x qubit1 = eng.allocate_qubit() qubit2 = eng.allocate_qubit() @@ -136,7 +151,7 @@ def test_body(): drawer = _drawer.CircuitDrawer() eng = MainEngine(drawer, []) old_tolatex = _drawer.to_latex - _drawer.to_latex = lambda x: x + _drawer.to_latex = lambda x, drawing_order, draw_gates_in_parallel: x qubit1 = eng.allocate_qubit() qubit2 = eng.allocate_qubit() @@ -181,12 +196,135 @@ 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, []) + old_tolatex = _drawer.to_latex + _drawer.to_latex = lambda x, drawing_order, draw_gates_in_parallel: x + + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + qubit3 = eng.allocate_qubit() + + H | qubit1 + H | qubit2 + 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() + + circuit_lines = drawer.get_latex() + _drawer.to_latex = old_tolatex + + 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) + + # there are three Hadamards in parallel + assert code.count("node[pos=.5] {H}") == 3 + + # line1_gate0 is initialisation + # line1_gate1 is empty + # line1_gate2 is for Hadamard on line1 + # line1_gate3 is empty + # XOR of CNOT is node[xstyle] (line1_gate4) + assert code.count("node[xstyle] (line2_gate4)") == 1 + + # and the CNOT is at position 1.4, because of the offsets + assert code.count("node[phase] (line0_gate4) at (1.4") == 1 + assert code.count("node[xstyle] (line2_gate4) at (1.4") == 1 + + +def test_body_with_drawing_order_and_gates_not_parallel(): + drawer = _drawer.CircuitDrawer() + eng = MainEngine(drawer, []) + old_tolatex = _drawer.to_latex + _drawer.to_latex = lambda x, drawing_order, draw_gates_in_parallel: x + + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + qubit3 = eng.allocate_qubit() + + H | qubit1 + H | qubit2 + 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() + + circuit_lines = drawer.get_latex() + _drawer.to_latex = old_tolatex + + 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) + + # 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, []) + old_tolatex = _drawer.to_latex + _drawer.to_latex = lambda x, drawing_order, draw_gates_in_parallel: x + + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + qubit3 = eng.allocate_qubit() + + H | qubit1 + H | qubit2 + 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() + + circuit_lines = drawer.get_latex() + _drawer.to_latex = old_tolatex + + settings = _to_latex.get_default_settings() + settings['gates']['AllocateQubitGate']['draw_id'] = True + 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)") + idx2 = code.find("node[none] (line1_gate1)") + assert idx1 < idx2 + def test_qubit_allocations_at_zero(): drawer = _drawer.CircuitDrawer() eng = MainEngine(drawer, []) old_tolatex = _drawer.to_latex - _drawer.to_latex = lambda x: x + _drawer.to_latex = lambda x, drawing_order, draw_gates_in_parallel: x a = eng.allocate_qureg(4) @@ -219,7 +357,7 @@ def test_qubit_lines_classicalvsquantum1(): drawer = _drawer.CircuitDrawer() eng = MainEngine(drawer, []) old_tolatex = _drawer.to_latex - _drawer.to_latex = lambda x: x + _drawer.to_latex = lambda x, drawing_order, draw_gates_in_parallel: x qubit1 = eng.allocate_qubit() From 93f2d7938b423c51258ac584c13dcb0f734e9330 Mon Sep 17 00:00:00 2001 From: David Bretaud <40793394+dbretaud@users.noreply.github.com> Date: Tue, 14 Jan 2020 12:54:44 +0000 Subject: [PATCH 032/113] Trapped ion decomposer setup and rotation gates improvement (#346) * reduced Ry decomposition : modification of restrictedgateset setup to include compiler chooser, extension of available decompositions, new setup based on restrictedgateset but adapted for trapped ion configurations * Addition of test file for the rz2rx decomposition Addition of test file for the rz2rx decomposition and edit to comments in the rz2rx file * Revert "Addition of test file for the rz2rx decomposition" This reverts commit 5aab56b7384d60d5ac09e5b08bebf14135b5a006. * Create rz2rx_test file Addition of test file for the rz2rx decomposition * Update rz2rx.py Update to comments * Update rz2rx_test.py Update to comments * Minor update: remove accidental file * Minor update rz2rx.py Corrected an angle. * Minor update rz2rx_test.py Edited comments. * Create h2rx_test.py Updated method for test_decomposition which tests that decomposition produces identical wave-state up to a global phase. * Improvement of the decomposition chooser; Note that basic restricted gate set will fail decomposing into Rxx because of the default chooser * Updates to h2rx and cnot2rxx * Create cnot2rxx_test.py Testing file for cnot2rxx decomposition. Includes updated method for test_decomposition which tests that decomposition produces identical wave-state up to a global phase. * basic rotation gates at an angle of 0 or 2pi are removed by the optimizer. basic rotation gates ignore now the global phase and are defined over 0:2pi * Update and create trapped_ion_decomposer_test.py * Minor update Documentation and comments. * Update on comments regarding Identity gates * Changes 1/2 : command can be printed with unicode symbols only via new to_String method; syntax correction; * Work in progress, is commutable * Update to decomposition test files rz2rx_test now tests each decomposition defined in rz2rx.all_defined_decomposition_rules Similar for cnot2rxx_test and h2rx_test * Revert "Work in progress, is commutable" This reverts commit 27f820cac337c81dbc037b5e3e0381b7e31db5b3. * minor fixes; revert rotation gates to [0;4pi) * fix comments * Fix a few issues with the trapped-ion setup - Store the sign of the last Ry gate on an engine-by-engine basis - Cleanup of some remaining print statements - Some stylistic issues fixed * Mostly fixing stylistic issues and some code cleanup * h2rx decomposition with correct global phase * cnot2rxx decomposition with correct global phase * Fix non-ASCII character in cnot2rxx.py * Fix some more files for non-ASCII characters * Specify encoding for files with non-ASCII characters * Fix test errors * Fix tests for Python 2.7 * Complete code coverage for trapped-ion setup Co-authored-by: Nguyen Damien --- docs/projectq.setups.decompositions.rst | 20 ++- docs/projectq.setups.rst | 7 + projectq/cengines/_optimize.py | 16 +- projectq/cengines/_optimize_test.py | 20 +++ projectq/cengines/_replacer/_replacer.py | 1 - projectq/ops/__init__.py | 1 + projectq/ops/_basics.py | 64 ++++++-- projectq/ops/_basics_test.py | 70 ++++---- projectq/ops/_command.py | 68 ++++---- projectq/ops/_command_test.py | 50 +++++- projectq/ops/_metagates.py | 16 ++ projectq/ops/_metagates_test.py | 10 ++ projectq/setups/decompositions/__init__.py | 6 + projectq/setups/decompositions/cnot2rxx.py | 61 +++++++ .../setups/decompositions/cnot2rxx_test.py | 124 +++++++++++++++ projectq/setups/decompositions/h2rx.py | 57 +++++++ projectq/setups/decompositions/h2rx_test.py | 117 ++++++++++++++ projectq/setups/decompositions/rz2rx.py | 67 ++++++++ projectq/setups/decompositions/rz2rx_test.py | 125 +++++++++++++++ projectq/setups/restrictedgateset.py | 71 +++++---- projectq/setups/restrictedgateset_test.py | 13 +- projectq/setups/trapped_ion_decomposer.py | 148 +++++++++++++++++ .../setups/trapped_ion_decomposer_test.py | 150 ++++++++++++++++++ 23 files changed, 1157 insertions(+), 125 deletions(-) mode change 100755 => 100644 projectq/ops/_metagates.py create mode 100644 projectq/setups/decompositions/cnot2rxx.py create mode 100644 projectq/setups/decompositions/cnot2rxx_test.py create mode 100644 projectq/setups/decompositions/h2rx.py create mode 100644 projectq/setups/decompositions/h2rx_test.py create mode 100644 projectq/setups/decompositions/rz2rx.py create mode 100644 projectq/setups/decompositions/rz2rx_test.py create mode 100644 projectq/setups/trapped_ion_decomposer.py create mode 100644 projectq/setups/trapped_ion_decomposer_test.py diff --git a/docs/projectq.setups.decompositions.rst b/docs/projectq.setups.decompositions.rst index 26cd392fc..6206c4a95 100755 --- a/docs/projectq.setups.decompositions.rst +++ b/docs/projectq.setups.decompositions.rst @@ -10,16 +10,19 @@ The decomposition package is a collection of gate decomposition / replacement ru projectq.setups.decompositions.barrier projectq.setups.decompositions.carb1qubit2cnotrzandry projectq.setups.decompositions.cnot2cz + projectq.setups.decompositions.cnot2rxx projectq.setups.decompositions.cnu2toffoliandcu projectq.setups.decompositions.crz2cxandrz projectq.setups.decompositions.entangle projectq.setups.decompositions.globalphase + projectq.setups.decompositions.h2rx projectq.setups.decompositions.ph2r projectq.setups.decompositions.qft2crandhadamard projectq.setups.decompositions.qubitop2onequbit projectq.setups.decompositions.r2rzandph projectq.setups.decompositions.rx2rz projectq.setups.decompositions.ry2rz + projectq.setups.decompositions.rz2rx projectq.setups.decompositions.sqrtswap2cnot projectq.setups.decompositions.stateprep2cnot projectq.setups.decompositions.swap2cnot @@ -62,6 +65,13 @@ projectq.setups.decompositions.cnot2cz module :members: :undoc-members: +projectq.setups.decompositions.cnot2rxx module +--------------------------------------------- + +.. automodule:: projectq.setups.decompositions.cnot2rxx + :members: + :undoc-members: + projectq.setups.decompositions.cnu2toffoliandcu module ------------------------------------------------------ @@ -90,7 +100,8 @@ projectq.setups.decompositions.globalphase module :members: :undoc-members: -projectq.setups.decompositions.ph2r module + +projectq.setups.decompositions.h2rx module ------------------------------------------ .. automodule:: projectq.setups.decompositions.ph2r @@ -132,6 +143,13 @@ projectq.setups.decompositions.ry2rz module :members: :undoc-members: +projectq.setups.decompositions.rz2rx module +------------------------------------------- + +.. automodule:: projectq.setups.decompositions.rz2rx + :members: + :undoc-members: + projectq.setups.decompositions.sqrtswap2cnot module --------------------------------------------------- diff --git a/docs/projectq.setups.rst b/docs/projectq.setups.rst index 058469f07..98e7e611a 100755 --- a/docs/projectq.setups.rst +++ b/docs/projectq.setups.rst @@ -86,6 +86,13 @@ restrictedgateset :special-members: __init__ :undoc-members: +trapped_ion_decomposer +---------------------- +.. automodule:: projectq.setups.trapped_ion_decomposer + :members: + :special-members: __init__ + :undoc-members: + Module contents --------------- diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 2e72540b9..0c9765288 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -116,7 +116,7 @@ def _get_gate_indices(self, idx, i, IDs): def _optimize(self, idx, lim=None): """ - Try to merge or even cancel successive gates using the get_merged and + Try to remove identity gates using the is_identity function, then merge or even cancel successive gates using the get_merged and get_inverse functions of the gate (see, e.g., BasicRotationGate). It does so for all qubit command lists. @@ -130,6 +130,20 @@ def _optimize(self, idx, lim=None): new_gateloc = limit while i < limit - 1: + # can be dropped if the gate is equivalent to an identity gate + if self._l[idx][i].is_identity(): + # determine index of this gate on all qubits + qubitids = [qb.id for sublist in self._l[idx][i].all_qubits + for qb in sublist] + gid = self._get_gate_indices(idx, i, qubitids) + for j in range(len(qubitids)): + new_list = (self._l[qubitids[j]][0:gid[j]] + + self._l[qubitids[j]][gid[j] +1:]) + self._l[qubitids[j]] = new_list + i = 0 + limit -= 1 + continue + # can be dropped if two in a row are self-inverses inv = self._l[idx][i].get_inverse() diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index e0196f83b..121dbb471 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -16,6 +16,7 @@ import pytest +import math from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.ops import (CNOT, H, Rx, Ry, AllocateQubitGate, X, @@ -127,3 +128,22 @@ def test_local_optimizer_mergeable_gates(): # Expect allocate, one Rx gate, and flush gate assert len(backend.received_commands) == 3 assert backend.received_commands[1].gate == Rx(10 * 0.5) + + +def test_local_optimizer_identity_gates(): + local_optimizer = _optimize.LocalOptimizer(m=4) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + # Test that it merges mergeable gates such as Rx + qb0 = eng.allocate_qubit() + for _ in range(10): + Rx(0.0) | qb0 + Ry(0.0) | qb0 + Rx(4*math.pi) | qb0 + Ry(4*math.pi) | qb0 + Rx(0.5) | qb0 + assert len(backend.received_commands) == 0 + eng.flush() + # Expect allocate, one Rx gate, and flush gate + assert len(backend.received_commands) == 3 + assert backend.received_commands[1].gate == Rx(0.5) diff --git a/projectq/cengines/_replacer/_replacer.py b/projectq/cengines/_replacer/_replacer.py index 29c883123..85fa303dc 100755 --- a/projectq/cengines/_replacer/_replacer.py +++ b/projectq/cengines/_replacer/_replacer.py @@ -175,7 +175,6 @@ def _process_command(self, cmd): # use decomposition chooser to determine the best decomposition chosen_decomp = self._decomp_chooser(cmd, decomp_list) - # the decomposed command must have the same tags # (plus the ones it gets from meta-statements inside the # decomposition rule). diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index cac384d9e..dd73cc2d5 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -25,6 +25,7 @@ from ._command import apply_command, Command from ._metagates import (DaggeredGate, get_inverse, + is_identity, ControlledGate, C, Tensor, diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 4bca84429..c7bdd31bc 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Defines the BasicGate class, the base class of all gates, the BasicRotationGate class, the SelfInverseGate, the FastForwardingGate, the @@ -39,9 +38,10 @@ from projectq.types import BasicQubit from ._command import Command, apply_command +import unicodedata ANGLE_PRECISION = 12 -ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION +ANGLE_TOLERANCE = 10**-ANGLE_PRECISION RTOL = 1e-10 ATOL = 1e-12 @@ -157,7 +157,7 @@ def make_tuple_of_qureg(qubits): (or list of Qubits) objects. """ if not isinstance(qubits, tuple): - qubits = (qubits,) + qubits = (qubits, ) qubits = list(qubits) @@ -208,8 +208,9 @@ def __eq__(self, other): Equality comparision Return True if instance of the same class, unless other is an instance - of :class:MatrixGate, in which case equality is to be checked by testing - for existence and (approximate) equality of matrix representations. + of :class:MatrixGate, in which case equality is to be checked by + testing for existence and (approximate) equality of matrix + representations. """ if isinstance(other, self.__class__): return True @@ -224,9 +225,21 @@ def __ne__(self, other): def __str__(self): raise NotImplementedError('This gate does not implement __str__.') + def to_string(self, symbols): + """ + String representation + + Achieve same function as str() but can be extended for configurable + representation + """ + return str(self) + def __hash__(self): return hash(str(self)) + def is_identity(self): + return False + class MatrixGate(BasicGate): """ @@ -271,20 +284,19 @@ def __eq__(self, other): """ if not hasattr(other, 'matrix'): return False - if (not isinstance(self.matrix, np.matrix) or - not isinstance(other.matrix, np.matrix)): + if (not isinstance(self.matrix, np.matrix) + or not isinstance(other.matrix, np.matrix)): raise TypeError("One of the gates doesn't have the correct " "type (numpy.matrix) for the matrix " "attribute.") - if (self.matrix.shape == other.matrix.shape and - np.allclose(self.matrix, other.matrix, - rtol=RTOL, atol=ATOL, - equal_nan=False)): + if (self.matrix.shape == other.matrix.shape and np.allclose( + self.matrix, other.matrix, rtol=RTOL, atol=ATOL, + equal_nan=False)): return True return False def __str__(self): - return("MatrixGate(" + str(self.matrix.tolist()) + ")") + return ("MatrixGate(" + str(self.matrix.tolist()) + ")") def __hash__(self): return hash(str(self)) @@ -343,7 +355,23 @@ def __str__(self): [CLASSNAME]([ANGLE]) """ - return str(self.__class__.__name__) + "(" + str(self.angle) + ")" + return self.to_string() + + def to_string(self, symbols=False): + """ + Return the string representation of a BasicRotationGate. + + Args: + symbols (bool): uses the pi character and round the angle for a + more user friendly display if True, full angle + written in radian otherwise. + """ + if symbols: + angle = ("(" + str(round(self.angle / math.pi, 3)) + + unicodedata.lookup("GREEK SMALL LETTER PI") + ")") + else: + angle = "(" + str(self.angle) + ")" + return str(self.__class__.__name__) + angle def tex_str(self): """ @@ -355,7 +383,8 @@ def tex_str(self): [CLASSNAME]$_[ANGLE]$ """ - return str(self.__class__.__name__) + "$_{" + str(self.angle) + "}$" + return (str(self.__class__.__name__) + "$_{" + + str(round(self.angle / math.pi, 3)) + "\\pi}$") def get_inverse(self): """ @@ -401,6 +430,12 @@ def __ne__(self, other): def __hash__(self): return hash(str(self)) + def is_identity(self): + """ + Return True if the gate is equivalent to an Identity gate + """ + return self.angle == 0. or self.angle == 4 * math.pi + class BasicPhaseGate(BasicGate): """ @@ -597,6 +632,7 @@ def math_fun(a): def math_function(x): return list(math_fun(*x)) + self._math_function = math_function def __str__(self): diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 80cc80183..a58a24e4c 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,10 +12,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.ops._basics.""" -from copy import deepcopy import math import numpy as np @@ -49,13 +48,13 @@ def test_basic_gate_make_tuple_of_qureg(main_engine): qubit3 = Qubit(main_engine, 3) qureg = Qureg([qubit2, qubit3]) case1 = _basics.BasicGate.make_tuple_of_qureg(qubit0) - assert case1 == ([qubit0],) + assert case1 == ([qubit0], ) case2 = _basics.BasicGate.make_tuple_of_qureg([qubit0, qubit1]) - assert case2 == ([qubit0, qubit1],) + assert case2 == ([qubit0, qubit1], ) case3 = _basics.BasicGate.make_tuple_of_qureg(qureg) - assert case3 == (qureg,) - case4 = _basics.BasicGate.make_tuple_of_qureg((qubit0,)) - assert case4 == ([qubit0],) + assert case3 == (qureg, ) + case4 = _basics.BasicGate.make_tuple_of_qureg((qubit0, )) + assert case4 == ([qubit0], ) case5 = _basics.BasicGate.make_tuple_of_qureg((qureg, qubit0)) assert case5 == (qureg, [qubit0]) @@ -68,20 +67,15 @@ def test_basic_gate_generate_command(main_engine): qureg = Qureg([qubit2, qubit3]) basic_gate = _basics.BasicGate() command1 = basic_gate.generate_command(qubit0) - assert command1 == Command(main_engine, basic_gate, - ([qubit0],)) + assert command1 == Command(main_engine, basic_gate, ([qubit0], )) command2 = basic_gate.generate_command([qubit0, qubit1]) - assert command2 == Command(main_engine, basic_gate, - ([qubit0, qubit1],)) + assert command2 == Command(main_engine, basic_gate, ([qubit0, qubit1], )) command3 = basic_gate.generate_command(qureg) - assert command3 == Command(main_engine, basic_gate, - (qureg,)) - command4 = basic_gate.generate_command((qubit0,)) - assert command4 == Command(main_engine, basic_gate, - ([qubit0],)) + assert command3 == Command(main_engine, basic_gate, (qureg, )) + command4 = basic_gate.generate_command((qubit0, )) + assert command4 == Command(main_engine, basic_gate, ([qubit0], )) command5 = basic_gate.generate_command((qureg, qubit0)) - assert command5 == Command(main_engine, basic_gate, - (qureg, [qubit0])) + assert command5 == Command(main_engine, basic_gate, (qureg, [qubit0])) def test_basic_gate_or(): @@ -100,8 +94,8 @@ def test_basic_gate_or(): basic_gate | [qubit0, qubit1] command3 = basic_gate.generate_command(qureg) basic_gate | qureg - command4 = basic_gate.generate_command((qubit0,)) - basic_gate | (qubit0,) + command4 = basic_gate.generate_command((qubit0, )) + basic_gate | (qubit0, ) command5 = basic_gate.generate_command((qureg, qubit0)) basic_gate | (qureg, qubit0) received_commands = [] @@ -109,8 +103,9 @@ def test_basic_gate_or(): for cmd in saving_backend.received_commands: if not isinstance(cmd.gate, _basics.FastForwardingGate): received_commands.append(cmd) - assert received_commands == ([command1, command2, command3, command4, - command5]) + assert received_commands == ([ + command1, command2, command3, command4, command5 + ]) def test_basic_gate_compare(): @@ -163,15 +158,17 @@ def test_basic_rotation_gate_init(input_angle, modulo_angle): def test_basic_rotation_gate_str(): - basic_rotation_gate = _basics.BasicRotationGate(0.5) - assert str(basic_rotation_gate) == "BasicRotationGate(0.5)" + gate = _basics.BasicRotationGate(math.pi) + assert str(gate) == "BasicRotationGate(3.14159265359)" + assert gate.to_string(symbols=True) == u"BasicRotationGate(1.0π)" + assert gate.to_string(symbols=False) == "BasicRotationGate(3.14159265359)" def test_basic_rotation_tex_str(): - basic_rotation_gate = _basics.BasicRotationGate(0.5) - assert basic_rotation_gate.tex_str() == "BasicRotationGate$_{0.5}$" - basic_rotation_gate = _basics.BasicRotationGate(4 * math.pi - 1e-13) - assert basic_rotation_gate.tex_str() == "BasicRotationGate$_{0.0}$" + gate = _basics.BasicRotationGate(0.5 * math.pi) + assert gate.tex_str() == "BasicRotationGate$_{0.5\\pi}$" + gate = _basics.BasicRotationGate(4 * math.pi - 1e-13) + assert gate.tex_str() == "BasicRotationGate$_{0.0\\pi}$" @pytest.mark.parametrize("input_angle, inverse_angle", @@ -194,6 +191,19 @@ def test_basic_rotation_gate_get_merged(): assert merged_gate == basic_rotation_gate3 +def test_basic_rotation_gate_is_identity(): + basic_rotation_gate1 = _basics.BasicRotationGate(0.) + basic_rotation_gate2 = _basics.BasicRotationGate(1. * math.pi) + basic_rotation_gate3 = _basics.BasicRotationGate(2. * math.pi) + basic_rotation_gate4 = _basics.BasicRotationGate(3. * math.pi) + basic_rotation_gate5 = _basics.BasicRotationGate(4. * math.pi) + assert basic_rotation_gate1.is_identity() + assert not basic_rotation_gate2.is_identity() + assert not basic_rotation_gate3.is_identity() + assert not basic_rotation_gate4.is_identity() + assert basic_rotation_gate5.is_identity() + + def test_basic_rotation_gate_comparison_and_hash(): basic_rotation_gate1 = _basics.BasicRotationGate(0.5) basic_rotation_gate2 = _basics.BasicRotationGate(0.5) @@ -316,10 +326,10 @@ def test_matrix_gate(): assert gate1 != gate6 assert gate3 != gate6 gate7 = gate5.get_inverse() - gate8 = _basics.MatrixGate([[1, 0], [0, (1+1j)/math.sqrt(2)]]) + gate8 = _basics.MatrixGate([[1, 0], [0, (1 + 1j) / math.sqrt(2)]]) assert gate7 == gate5 assert gate7 != gate8 - gate9 = _basics.MatrixGate([[1, 0], [0, (1-1j)/math.sqrt(2)]]) + gate9 = _basics.MatrixGate([[1, 0], [0, (1 - 1j) / math.sqrt(2)]]) gate10 = gate9.get_inverse() assert gate10 == gate8 assert gate3 == X diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 5186502fa..6c320f375 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ This file defines the apply_command function and the Command class. @@ -82,7 +81,6 @@ class Command(object): and hence adds its LoopTag to the end. all_qubits: A tuple of control_qubits + qubits """ - def __init__(self, engine, gate, qubits, controls=(), tags=()): """ Initialize a Command object. @@ -106,9 +104,9 @@ def __init__(self, engine, gate, qubits, controls=(), tags=()): tags (list[object]): Tags associated with the command. """ - qubits = tuple([WeakQubitRef(qubit.engine, qubit.id) - for qubit in qreg] - for qreg in qubits) + qubits = tuple( + [WeakQubitRef(qubit.engine, qubit.id) for qubit in qreg] + for qreg in qubits) self.gate = gate self.tags = list(tags) @@ -126,11 +124,8 @@ def qubits(self, qubits): def __deepcopy__(self, memo): """ Deepcopy implementation. Engine should stay a reference.""" - return Command(self.engine, - deepcopy(self.gate), - self.qubits, - list(self.control_qubits), - deepcopy(self.tags)) + return Command(self.engine, deepcopy(self.gate), self.qubits, + list(self.control_qubits), deepcopy(self.tags)) def get_inverse(self): """ @@ -143,12 +138,19 @@ def get_inverse(self): NotInvertible: If the gate does not provide an inverse (see BasicGate.get_inverse) """ - return Command(self._engine, - projectq.ops.get_inverse(self.gate), - self.qubits, - list(self.control_qubits), + return Command(self._engine, projectq.ops.get_inverse(self.gate), + self.qubits, list(self.control_qubits), deepcopy(self.tags)) + def is_identity(self): + """ + Evaluate if the gate called in the command object is an identity gate. + + Returns: + True if the gate is equivalent to an Identity gate, False otherwise + """ + return projectq.ops.is_identity(self.gate) + def get_merged(self, other): """ Merge this command with another one and return the merged command @@ -161,12 +163,10 @@ def get_merged(self, other): NotMergeable: if the gates don't supply a get_merged()-function or can't be merged for other reasons. """ - if (self.tags == other.tags and self.all_qubits == other.all_qubits and - self.engine == other.engine): - return Command(self.engine, - self.gate.get_merged(other.gate), - self.qubits, - self.control_qubits, + if (self.tags == other.tags and self.all_qubits == other.all_qubits + and self.engine == other.engine): + return Command(self.engine, self.gate.get_merged(other.gate), + self.qubits, self.control_qubits, deepcopy(self.tags)) raise projectq.ops.NotMergeable("Commands not mergeable.") @@ -219,8 +219,9 @@ def control_qubits(self, qubits): Args: control_qubits (Qureg): quantum register """ - self._control_qubits = ([WeakQubitRef(qubit.engine, qubit.id) - for qubit in qubits]) + self._control_qubits = ([ + WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits + ]) self._control_qubits = sorted(self._control_qubits, key=lambda x: x.id) def add_control_qubits(self, qubits): @@ -236,9 +237,9 @@ def add_control_qubits(self, qubits): gate, i.e., the gate is only executed if all qubits are in state 1. """ - assert(isinstance(qubits, list)) - self._control_qubits.extend([WeakQubitRef(qubit.engine, qubit.id) - for qubit in qubits]) + assert (isinstance(qubits, list)) + self._control_qubits.extend( + [WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits]) self._control_qubits = sorted(self._control_qubits, key=lambda x: x.id) @property @@ -250,7 +251,7 @@ def all_qubits(self): WeakQubitRef objects) containing the control qubits and T[1:] contains the quantum registers to which the gate is applied. """ - return (self.control_qubits,) + self.qubits + return (self.control_qubits, ) + self.qubits @property def engine(self): @@ -285,11 +286,9 @@ def __eq__(self, other): Returns: True if Command objects are equal (same gate, applied to same qubits; ordered modulo interchangeability; and same tags) """ - if (isinstance(other, self.__class__) and - self.gate == other.gate and - self.tags == other.tags and - self.engine == other.engine and - self.all_qubits == other.all_qubits): + if (isinstance(other, self.__class__) and self.gate == other.gate + and self.tags == other.tags and self.engine == other.engine + and self.all_qubits == other.all_qubits): return True return False @@ -297,13 +296,16 @@ def __ne__(self, other): return not self.__eq__(other) def __str__(self): + return self.to_string() + + def to_string(self, symbols=False): """ Get string representation of this Command object. """ qubits = self.qubits ctrlqubits = self.control_qubits if len(ctrlqubits) > 0: - qubits = (self.control_qubits,) + qubits + qubits = (self.control_qubits, ) + qubits qstring = "" if len(qubits) == 1: qstring = str(Qureg(qubits[0])) @@ -314,4 +316,4 @@ def __str__(self): qstring += ", " qstring = qstring[:-2] + " )" cstring = "C" * len(ctrlqubits) - return cstring + str(self.gate) + " | " + qstring + return cstring + self.gate.to_string(symbols) + " | " + qstring diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index ae1407836..b0b4d54c8 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +16,7 @@ """Tests for projectq.ops._command.""" from copy import deepcopy +import sys import math import pytest @@ -36,8 +38,8 @@ def test_command_init(main_engine): qureg0 = Qureg([Qubit(main_engine, 0)]) qureg1 = Qureg([Qubit(main_engine, 1)]) qureg2 = Qureg([Qubit(main_engine, 2)]) - qureg3 = Qureg([Qubit(main_engine, 3)]) - qureg4 = Qureg([Qubit(main_engine, 4)]) + # qureg3 = Qureg([Qubit(main_engine, 3)]) + # qureg4 = Qureg([Qubit(main_engine, 4)]) gate = BasicGate() cmd = _command.Command(main_engine, gate, (qureg0, qureg1, qureg2)) assert cmd.gate == gate @@ -133,6 +135,19 @@ def test_command_get_merged(main_engine): cmd.get_merged(cmd4) +def test_command_is_identity(main_engine): + qubit = main_engine.allocate_qubit() + qubit2 = main_engine.allocate_qubit() + cmd = _command.Command(main_engine, Rx(0.), (qubit,)) + cmd2 = _command.Command(main_engine, Rx(0.5), (qubit2,)) + inverse_cmd = cmd.get_inverse() + inverse_cmd2 = cmd2.get_inverse() + assert inverse_cmd.gate.is_identity() + assert cmd.gate.is_identity() + assert not inverse_cmd2.gate.is_identity() + assert not cmd2.gate.is_identity() + + def test_command_order_qubits(main_engine): qubit0 = Qureg([Qubit(main_engine, 0)]) qubit1 = Qureg([Qubit(main_engine, 1)]) @@ -232,9 +247,32 @@ def test_command_comparison(main_engine): def test_command_str(): qubit = Qureg([Qubit(main_engine, 0)]) ctrl_qubit = Qureg([Qubit(main_engine, 1)]) - cmd = _command.Command(main_engine, Rx(0.5), (qubit,)) + cmd = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) cmd.tags = ["TestTag"] cmd.add_control_qubits(ctrl_qubit) - assert str(cmd) == "CRx(0.5) | ( Qureg[1], Qureg[0] )" - cmd2 = _command.Command(main_engine, Rx(0.5), (qubit,)) - assert str(cmd2) == "Rx(0.5) | Qureg[0]" + cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) + if sys.version_info.major == 3: + assert cmd.to_string(symbols=False) == "CRx(1.570796326795) | ( Qureg[1], Qureg[0] )" + assert str(cmd2) == "Rx(1.570796326795) | Qureg[0]" + else: + assert cmd.to_string(symbols=False) == "CRx(1.5707963268) | ( Qureg[1], Qureg[0] )" + assert str(cmd2) == "Rx(1.5707963268) | Qureg[0]" + + +def test_command_to_string(): + qubit = Qureg([Qubit(main_engine, 0)]) + ctrl_qubit = Qureg([Qubit(main_engine, 1)]) + cmd = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) + cmd.tags = ["TestTag"] + cmd.add_control_qubits(ctrl_qubit) + cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) + + assert cmd.to_string(symbols=True) == u"CRx(0.5π) | ( Qureg[1], Qureg[0] )" + assert cmd2.to_string(symbols=True) == u"Rx(0.5π) | Qureg[0]" + if sys.version_info.major == 3: + assert cmd.to_string(symbols=False) == "CRx(1.570796326795) | ( Qureg[1], Qureg[0] )" + assert cmd2.to_string(symbols=False) == "Rx(1.570796326795) | Qureg[0]" + else: + assert cmd.to_string(symbols=False) == "CRx(1.5707963268) | ( Qureg[1], Qureg[0] )" + assert cmd2.to_string(symbols=False) == "Rx(1.5707963268) | Qureg[0]" + diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py old mode 100755 new mode 100644 index b2e7959fe..cca5e7412 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -132,6 +132,22 @@ def get_inverse(gate): except NotInvertible: return DaggeredGate(gate) +def is_identity(gate): + """ + Return True if the gate is an identity gate. + + Tries to call gate.is_identity and, upon failure, returns False + + Args: + gate: Gate of which to get the inverse + + Example: + .. code-block:: python + + get_inverse(Rx(2*math.pi)) # returns True + get_inverse(Rx(math.pi)) # returns False + """ + return gate.is_identity() class ControlledGate(BasicGate): """ diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index c42393b06..8632a99e5 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -122,6 +122,16 @@ def test_get_inverse(): inv2 = _metagates.get_inverse(invertible_gate) assert inv2 == Y +def test_is_identity(): + # Choose gate which is not an identity gate: + non_identity_gate=Rx(0.5) + assert not non_identity_gate.is_identity() + assert not _metagates.is_identity(non_identity_gate) + # Choose gate which is an identity gate: + identity_gate=Rx(0.) + assert identity_gate.is_identity() + assert _metagates.is_identity(identity_gate) + def test_controlled_gate_init(): one_control = _metagates.ControlledGate(Y, 1) diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index 883f04581..de557a065 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -16,16 +16,19 @@ barrier, carb1qubit2cnotrzandry, crz2cxandrz, + cnot2rxx, cnot2cz, cnu2toffoliandcu, entangle, globalphase, + h2rx, ph2r, qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, ry2rz, + rz2rx, sqrtswap2cnot, stateprep2cnot, swap2cnot, @@ -41,16 +44,19 @@ barrier, carb1qubit2cnotrzandry, crz2cxandrz, + cnot2rxx, cnot2cz, cnu2toffoliandcu, entangle, globalphase, + h2rx, ph2r, qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, ry2rz, + rz2rx, sqrtswap2cnot, stateprep2cnot, swap2cnot, diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py new file mode 100644 index 000000000..a1fa2e6ac --- /dev/null +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -0,0 +1,61 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Module uses ideas from "Basic circuit compilation techniques +# for an ion-trap quantum machine" by Dmitri Maslov (2017) at +# https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 +""" +Registers a decomposition to for a CNOT gate in terms of Rxx, Rx and Ry gates. +""" + +from projectq.cengines import DecompositionRule +from projectq.meta import get_control_count +from projectq.ops import Ph, Rxx, Ry, Rx, X +import math + + +def _decompose_cnot2rxx_M(cmd): + """ Decompose CNOT gate into Rxx gate. """ + # Labelled 'M' for 'minus' because decomposition ends with a Ry(-pi/2) + ctrl = cmd.control_qubits + Ry(math.pi / 2) | ctrl[0] + Ph(7 * math.pi / 4) | ctrl[0] + Rx(-math.pi / 2) | ctrl[0] + Rx(-math.pi / 2) | cmd.qubits[0][0] + Rxx(math.pi / 2) | (ctrl[0], cmd.qubits[0][0]) + Ry(-1 * math.pi / 2) | ctrl[0] + + +def _decompose_cnot2rxx_P(cmd): + """ Decompose CNOT gate into Rxx gate. """ + # Labelled 'P' for 'plus' because decomposition ends with a Ry(+pi/2) + ctrl = cmd.control_qubits + Ry(-math.pi / 2) | ctrl[0] + Ph(math.pi / 4) | ctrl[0] + Rx(-math.pi / 2) | ctrl[0] + Rx(math.pi / 2) | cmd.qubits[0][0] + Rxx(math.pi / 2) | (ctrl[0], cmd.qubits[0][0]) + Ry(math.pi / 2) | ctrl[0] + + +def _recognize_cnot2(cmd): + """ Identify that the command is a CNOT gate (control - X gate)""" + return get_control_count(cmd) == 1 + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(X.__class__, _decompose_cnot2rxx_M, _recognize_cnot2), + DecompositionRule(X.__class__, _decompose_cnot2rxx_P, _recognize_cnot2) +] diff --git a/projectq/setups/decompositions/cnot2rxx_test.py b/projectq/setups/decompositions/cnot2rxx_test.py new file mode 100644 index 000000000..bc0d0c077 --- /dev/null +++ b/projectq/setups/decompositions/cnot2rxx_test.py @@ -0,0 +1,124 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Tests for projectq.setups.decompositions.cnot2rxx.py." + +import pytest +import numpy as np + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) +from projectq.meta import Control +from projectq.ops import All, CNOT, CZ, Measure, X, Z + +from . import cnot2rxx + + +def test_recognize_correct_gates(): + """Test that recognize_cnot recognizes cnot gates. """ + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + qubit3 = eng.allocate_qubit() + eng.flush() + # Create a control function in 3 different ways + CZ | (qubit1, qubit2) + with Control(eng, qubit2): + Z | qubit1 + X | qubit1 + with Control(eng, qubit2 + qubit3): + Z | qubit1 + eng.flush() + eng.flush(deallocate_qubits=True) + for cmd in saving_backend.received_commands[4:7]: + assert cnot2rxx._recognize_cnot2(cmd) + for cmd in saving_backend.received_commands[7:9]: + assert not cnot2rxx._recognize_cnot2(cmd) + + +def _decomp_gates(eng, cmd): + """ Test that the cmd.gate is a gate of class X """ + if len(cmd.control_qubits) == 1 and isinstance(cmd.gate, X.__class__): + return False + return True + + +# ------------test_decomposition function-------------# +# Creates two engines, correct_eng and test_eng. +# correct_eng implements CNOT gate. +# test_eng implements the decomposition of the CNOT gate. +# correct_qb and test_qb represent results of these two engines, respectively. +# +# The decomposition in this case only produces the same state as CNOT up to a +# global phase. +# test_vector and correct_vector represent the final wave states of correct_qb +# and test_qb. +# +# The dot product of correct_vector and test_vector should have absolute value +# 1, if the two vectors are the same up to a global phase. + + +def test_decomposition(): + """ Test that this decomposition of CNOT produces correct amplitudes + + Function tests each DecompositionRule in + cnot2rxx.all_defined_decomposition_rules + """ + decomposition_rule_list = cnot2rxx.all_defined_decomposition_rules + for rule in decomposition_rule_list: + for basis_state_index in range(0, 4): + basis_state = [0] * 4 + basis_state[basis_state_index] = 1. + correct_dummy_eng = DummyEngine(save_commands=True) + correct_eng = MainEngine(backend=Simulator(), + engine_list=[correct_dummy_eng]) + rule_set = DecompositionRuleSet(rules=[rule]) + test_dummy_eng = DummyEngine(save_commands=True) + test_eng = MainEngine(backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(_decomp_gates), + test_dummy_eng + ]) + test_sim = test_eng.backend + correct_sim = correct_eng.backend + correct_qb = correct_eng.allocate_qubit() + correct_ctrl_qb = correct_eng.allocate_qubit() + correct_eng.flush() + test_qb = test_eng.allocate_qubit() + test_ctrl_qb = test_eng.allocate_qubit() + test_eng.flush() + + correct_sim.set_wavefunction(basis_state, + correct_qb + correct_ctrl_qb) + test_sim.set_wavefunction(basis_state, test_qb + test_ctrl_qb) + CNOT | (test_ctrl_qb, test_qb) + CNOT | (correct_ctrl_qb, correct_qb) + + test_eng.flush() + correct_eng.flush() + + assert len(correct_dummy_eng.received_commands) == 5 + assert len(test_dummy_eng.received_commands) == 10 + + assert correct_eng.backend.cheat()[1] == pytest.approx( + test_eng.backend.cheat()[1], rel=1e-12, abs=1e-12) + + All(Measure) | test_qb + test_ctrl_qb + All(Measure) | correct_qb + correct_ctrl_qb + test_eng.flush(deallocate_qubits=True) + correct_eng.flush(deallocate_qubits=True) diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py new file mode 100644 index 000000000..b54533bad --- /dev/null +++ b/projectq/setups/decompositions/h2rx.py @@ -0,0 +1,57 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Module uses ideas from "Basic circuit compilation techniques for an +# ion-trap quantum machine" by Dmitri Maslov (2017) at +# https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 +""" +Registers a decomposition for the H gate into an Ry and Rx gate. +""" + +import math + +from projectq.cengines import DecompositionRule +from projectq.meta import get_control_count +from projectq.ops import Ph, Rx, Ry, H + + +def _decompose_h2rx_M(cmd): + """ Decompose the Ry gate.""" + # Labelled 'M' for 'minus' because decomposition ends with a Ry(-pi/2) + qubit = cmd.qubits[0] + Rx(math.pi) | qubit + Ph(math.pi/2) | qubit + Ry(-1 * math.pi / 2) | qubit + + +def _decompose_h2rx_N(cmd): + """ Decompose the Ry gate.""" + # Labelled 'N' for 'neutral' because decomposition doesn't end with + # Ry(pi/2) or Ry(-pi/2) + qubit = cmd.qubits[0] + Ry(math.pi / 2) | qubit + Ph(3*math.pi/2) | qubit + Rx(-1 * math.pi) | qubit + + +def _recognize_HNoCtrl(cmd): + """ For efficiency reasons only if no control qubits.""" + return get_control_count(cmd) == 0 + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(H.__class__, _decompose_h2rx_N, _recognize_HNoCtrl), + DecompositionRule(H.__class__, _decompose_h2rx_M, _recognize_HNoCtrl) +] diff --git a/projectq/setups/decompositions/h2rx_test.py b/projectq/setups/decompositions/h2rx_test.py new file mode 100644 index 000000000..2df048801 --- /dev/null +++ b/projectq/setups/decompositions/h2rx_test.py @@ -0,0 +1,117 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Tests for projectq.setups.decompositions.h2rx.py" + +import numpy as np + +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) +from projectq.meta import Control +from projectq.ops import Measure, X, H, HGate + +from . import h2rx + + +def test_recognize_correct_gates(): + """ Test that recognize_HNoCtrl recognizes ctrl qubits """ + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend) + qubit = eng.allocate_qubit() + ctrl_qubit = eng.allocate_qubit() + eng.flush() + H | qubit + with Control(eng, ctrl_qubit): + H | qubit + eng.flush(deallocate_qubits=True) + assert h2rx._recognize_HNoCtrl(saving_backend.received_commands[3]) + assert not h2rx._recognize_HNoCtrl(saving_backend.received_commands[4]) + + +def h_decomp_gates(eng, cmd): + """ Test that cmd.gate is a gate of class HGate """ + g = cmd.gate + if isinstance(g, HGate): # H is just a shortcut to HGate + return False + else: + return True + + +# ------------test_decomposition function-------------# +# Creates two engines, correct_eng and test_eng. +# correct_eng implements H gate. +# test_eng implements the decomposition of the H gate. +# correct_qb and test_qb represent results of these two engines, respectively. +# +# The decomposition in this case only produces the same state as H up to a +# global phase. +# test_vector and correct_vector represent the final wave states of correct_qb +# and test_qb. +# The dot product of correct_vector and test_vector should have absolute value +# 1, if the two vectors are the same up to a global phase. + + +def test_decomposition(): + """ Test that this decomposition of H produces correct amplitudes + + Function tests each DecompositionRule in + h2rx.all_defined_decomposition_rules + """ + decomposition_rule_list = h2rx.all_defined_decomposition_rules + for rule in decomposition_rule_list: + for basis_state_index in range(2): + basis_state = [0] * 2 + basis_state[basis_state_index] = 1. + + correct_dummy_eng = DummyEngine(save_commands=True) + correct_eng = MainEngine(backend=Simulator(), + engine_list=[correct_dummy_eng]) + + rule_set = DecompositionRuleSet(rules=[rule]) + test_dummy_eng = DummyEngine(save_commands=True) + test_eng = MainEngine(backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(h_decomp_gates), + test_dummy_eng + ]) + + correct_qb = correct_eng.allocate_qubit() + correct_eng.flush() + test_qb = test_eng.allocate_qubit() + test_eng.flush() + + correct_eng.backend.set_wavefunction(basis_state, correct_qb) + test_eng.backend.set_wavefunction(basis_state, test_qb) + + H | correct_qb + H | test_qb + + correct_eng.flush() + test_eng.flush() + + assert H in (cmd.gate + for cmd in correct_dummy_eng.received_commands) + assert H not in (cmd.gate + for cmd in test_dummy_eng.received_commands) + + assert correct_eng.backend.cheat()[1] == pytest.approx( + test_eng.backend.cheat()[1], rel=1e-12, abs=1e-12) + + Measure | test_qb + Measure | correct_qb diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py new file mode 100644 index 000000000..f49ba72e1 --- /dev/null +++ b/projectq/setups/decompositions/rz2rx.py @@ -0,0 +1,67 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Module uses ideas from "Basic circuit compilation techniques for an +# ion-trap quantum machine" by Dmitri Maslov (2017) at +# https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 +""" +Registers a decomposition for the Rz gate into an Rx and Ry(pi/2) or Ry(-pi/2) +gate +""" + +import math + +from projectq.cengines import DecompositionRule +from projectq.meta import Compute, Control, get_control_count, Uncompute +from projectq.ops import Rx, Ry, Rz + + +def _decompose_rz2rx_P(cmd): + """ Decompose the Rz using negative angle. """ + # Labelled 'P' for 'plus' because decomposition ends with a Ry(+pi/2) + qubit = cmd.qubits[0] + eng = cmd.engine + angle = cmd.gate.angle + + with Control(eng, cmd.control_qubits): + with Compute(eng): + Ry(-math.pi / 2.) | qubit + Rx(-angle) | qubit + Uncompute(eng) + + +def _decompose_rz2rx_M(cmd): + """ Decompose the Rz using positive angle. """ + # Labelled 'M' for 'minus' because decomposition ends with a Ry(-pi/2) + qubit = cmd.qubits[0] + eng = cmd.engine + angle = cmd.gate.angle + + with Control(eng, cmd.control_qubits): + with Compute(eng): + Ry(math.pi / 2.) | qubit + Rx(angle) | qubit + Uncompute(eng) + + +def _recognize_RzNoCtrl(cmd): + """ Decompose the gate only if the command represents a single qubit gate (if it is not part of a control gate).""" + return get_control_count(cmd) == 0 + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(Rz, _decompose_rz2rx_P, _recognize_RzNoCtrl), + DecompositionRule(Rz, _decompose_rz2rx_M, _recognize_RzNoCtrl) +] diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py new file mode 100644 index 000000000..7c6c9962f --- /dev/null +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -0,0 +1,125 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Tests for projectq.setups.decompositions.rz2rx.py" + +import math +import numpy as np +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) +from projectq.meta import Control +from projectq.ops import Measure, Rz + +from . import rz2rx + + +def test_recognize_correct_gates(): + """ Test that recognize_RzNoCtrl recognizes ctrl qubits """ + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend) + qubit = eng.allocate_qubit() + ctrl_qubit = eng.allocate_qubit() + eng.flush() + Rz(0.3) | qubit + with Control(eng, ctrl_qubit): + Rz(0.4) | qubit + eng.flush(deallocate_qubits=True) + assert rz2rx._recognize_RzNoCtrl(saving_backend.received_commands[3]) + assert not rz2rx._recognize_RzNoCtrl(saving_backend.received_commands[4]) + + +def rz_decomp_gates(eng, cmd): + """ Test that cmd.gate is the gate Rz """ + g = cmd.gate + if isinstance(g, Rz): + return False + else: + return True + + +# ------------test_decomposition function-------------# +# Creates two engines, correct_eng and test_eng. +# correct_eng implements Rz(angle) gate. +# test_eng implements the decomposition of the Rz(angle) gate. +# correct_qb and test_qb represent results of these two engines, respectively. +# +# The decomposition only needs to produce the same state in a qubit up to a +# global phase. +# test_vector and correct_vector represent the final wave states of correct_qb +# and test_qb. +# +# The dot product of correct_vector and test_vector should have absolute value +# 1, if the two vectors are the same up to a global phase. + + +@pytest.mark.parametrize("angle", [0, math.pi, 2 * math.pi, 4 * math.pi, 0.5]) +def test_decomposition(angle): + """ + Test that this decomposition of Rz produces correct amplitudes + + Note that this function tests each DecompositionRule in + rz2rx.all_defined_decomposition_rules + """ + decomposition_rule_list = rz2rx.all_defined_decomposition_rules + for rule in decomposition_rule_list: + for basis_state in ([1, 0], [0, 1]): + correct_dummy_eng = DummyEngine(save_commands=True) + correct_eng = MainEngine(backend=Simulator(), + engine_list=[correct_dummy_eng]) + + rule_set = DecompositionRuleSet(rules=[rule]) + test_dummy_eng = DummyEngine(save_commands=True) + test_eng = MainEngine(backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(rz_decomp_gates), + test_dummy_eng + ]) + + correct_qb = correct_eng.allocate_qubit() + Rz(angle) | correct_qb + correct_eng.flush() + + test_qb = test_eng.allocate_qubit() + Rz(angle) | test_qb + test_eng.flush() + + # Create empty vectors for the wave vectors for the correct and + # test qubits + correct_vector = np.zeros((2, 1), dtype=np.complex_) + test_vector = np.zeros((2, 1), dtype=np.complex_) + + i = 0 + for fstate in ['0', '1']: + test = test_eng.backend.get_amplitude(fstate, test_qb) + correct = correct_eng.backend.get_amplitude(fstate, correct_qb) + correct_vector[i] = correct + test_vector[i] = test + i += 1 + + # Necessary to transpose vector to use matrix dot product + test_vector = test_vector.transpose() + # Remember that transposed vector should come first in product + vector_dot_product = np.dot(test_vector, correct_vector) + + assert np.absolute(vector_dot_product) == pytest.approx(1, + rel=1e-12, + abs=1e-12) + + Measure | test_qb + Measure | correct_qb diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index 7b3540cf0..fe0c00ba2 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Defines a setup to compile to a restricted gate set. @@ -27,8 +26,7 @@ import projectq.libs.math import projectq.setups.decompositions from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - InstructionFilter, LocalOptimizer, - TagRemover) + InstructionFilter, LocalOptimizer, TagRemover) from projectq.ops import (BasicGate, BasicMathGate, ClassicalInstructionGate, CNOT, ControlledGate, get_inverse, QFT, Swap) @@ -60,9 +58,14 @@ def one_and_two_qubit_gates(eng, cmd): return False +def default_chooser(cmd, decomposition_list): + return decomposition_list[0] + + def get_engine_list(one_qubit_gates="any", - two_qubit_gates=(CNOT,), - other_gates=()): + two_qubit_gates=(CNOT, ), + other_gates=(), + compiler_chooser=default_chooser): """ Returns an engine list to compile to a restricted gate set. @@ -73,8 +76,8 @@ def get_engine_list(one_qubit_gates="any", even the gate sets which work might not yet be optimized. So make sure to double check and potentially extend the decomposition rules. This implemention currently requires that the one qubit gates must - contain Rz and at least one of {Ry(best), Rx, H} and the two qubit gate - must contain CNOT (recommended) or CZ. + contain Rz and at least one of {Ry(best), Rx, H} and the two qubit + gate must contain CNOT (recommended) or CZ. Note: Classical instructions gates such as e.g. Flush and Measure are @@ -86,21 +89,24 @@ def get_engine_list(one_qubit_gates="any", other_gates=(TimeEvolution,)) Args: - one_qubit_gates: "any" allows any one qubit gate, otherwise provide - a tuple of the allowed gates. If the gates are + one_qubit_gates: "any" allows any one qubit gate, otherwise provide a + tuple of the allowed gates. If the gates are instances of a class (e.g. X), it allows all gates - which are equal to it. If the gate is a class (Rz), it - allows all instances of this class. Default is "any" - two_qubit_gates: "any" allows any two qubit gate, otherwise provide - a tuple of the allowed gates. If the gates are + which are equal to it. If the gate is a class (Rz), + it allows all instances of this class. Default is + "any" + two_qubit_gates: "any" allows any two qubit gate, otherwise provide a + tuple of the allowed gates. If the gates are instances of a class (e.g. CNOT), it allows all gates which are equal to it. If the gate is a class, it allows all instances of this class. Default is (CNOT,). other_gates: A tuple of the allowed gates. If the gates are - instances of a class (e.g. QFT), it allows - all gates which are equal to it. If the gate is a - class, it allows all instances of this class. + instances of a class (e.g. QFT), it allows all gates + which are equal to it. If the gate is a class, it + allows all instances of this class. + compiler_chooser:function selecting the decomposition to use in the + Autoreplacer engine Raises: TypeError: If input is for the gates is not "any" or a tuple. Also if element within tuple is not a class or instance of BasicGate @@ -119,8 +125,8 @@ def get_engine_list(one_qubit_gates="any", if not isinstance(other_gates, tuple): raise TypeError("other_gates parameter must be a tuple.") - rule_set = DecompositionRuleSet(modules=[projectq.libs.math, - projectq.setups.decompositions]) + rule_set = DecompositionRuleSet( + modules=[projectq.libs.math, projectq.setups.decompositions]) allowed_gate_classes = [] # n-qubit gates allowed_gate_instances = [] allowed_gate_classes1 = [] # 1-qubit gates @@ -192,20 +198,21 @@ def low_level_gates(eng, cmd): elif cmd.gate in allowed_gate_instances1 and len(all_qubits) == 1: return True elif ((cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances2 - and len(all_qubits) == 2): + and len(all_qubits) == 2): return True return False - return [AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(high_level_gates), - LocalOptimizer(5), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(one_and_two_qubit_gates), - LocalOptimizer(5), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(low_level_gates), - LocalOptimizer(5), - ] + return [ + AutoReplacer(rule_set, compiler_chooser), + TagRemover(), + InstructionFilter(high_level_gates), + LocalOptimizer(5), + AutoReplacer(rule_set, compiler_chooser), + TagRemover(), + InstructionFilter(one_and_two_qubit_gates), + LocalOptimizer(5), + AutoReplacer(rule_set, compiler_chooser), + TagRemover(), + InstructionFilter(low_level_gates), + LocalOptimizer(5), + ] diff --git a/projectq/setups/restrictedgateset_test.py b/projectq/setups/restrictedgateset_test.py index 544746f85..fe9754aa7 100644 --- a/projectq/setups/restrictedgateset_test.py +++ b/projectq/setups/restrictedgateset_test.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.setups.restrictedgateset.""" import pytest @@ -89,14 +88,14 @@ def test_restriction(): def test_wrong_init(): with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(two_qubit_gates=(CNOT)) + restrictedgateset.get_engine_list(two_qubit_gates=(CNOT)) with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(one_qubit_gates="Any") + restrictedgateset.get_engine_list(one_qubit_gates="Any") with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(other_gates="any") + restrictedgateset.get_engine_list(other_gates="any") with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(one_qubit_gates=(CRz,)) + restrictedgateset.get_engine_list(one_qubit_gates=(CRz, )) with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(two_qubit_gates=(CRz,)) + restrictedgateset.get_engine_list(two_qubit_gates=(CRz, )) with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(other_gates=(CRz,)) + restrictedgateset.get_engine_list(other_gates=(CRz, )) diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py new file mode 100644 index 000000000..f5d19f1c8 --- /dev/null +++ b/projectq/setups/trapped_ion_decomposer.py @@ -0,0 +1,148 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Module uses ideas from "Basic circuit compilation techniques +# for an ion-trap quantum machine" by Dmitri Maslov (2017) at +# https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 +""" +Apply the restricted gate set setup for trapped ion based quantum computers. + +It provides the `engine_list` for the `MainEngine`, restricting the gate set to +Rx and Ry single qubit gates and the Rxx two qubit gates. + +A decomposition chooser is implemented following the ideas in QUOTE for +reducing the number of Ry gates in the new circuit. + +NOTE: + +Because the decomposition chooser is only called when a gate has to be +decomposed, this reduction will work better when the entire circuit has to be +decomposed. Otherwise, If the circuit has both superconding gates and native +ion trapped gates the decomposed circuit will not be optimal. +""" + +from projectq.setups import restrictedgateset +from projectq.ops import (Rxx, Rx, Ry) +from projectq.meta import get_control_count + +# ------------------chooser_Ry_reducer-------------------# +# If the qubit is not in the prev_Ry_sign dictionary, then no decomposition +# occured +# If the value is: +# -1 then the last gate applied (during a decomposition!) was Ry(-math.pi/2) +# 1 then the last gate applied (during a decomposition!) was Ry(+math.pi/2) +# 0 then the last gate applied (during a decomposition!) was a Rx + +prev_Ry_sign = dict() # Keeps track of most recent Ry sign, i.e. +# whether we had Ry(-pi/2) or Ry(pi/2) +# prev_Ry_sign[qubit_index] should hold -1 or +# +1 + + +def chooser_Ry_reducer(cmd, decomposition_list): + """ + Choose the decomposition so as to maximise Ry cancellations, based on the + previous decomposition used for the given qubit. + + Note: + Classical instructions gates e.g. Flush and Measure are automatically + allowed. + + Returns: + A decomposition object from the decomposition_list. + """ + decomp_rule = dict() + name = 'default' + + for decomp in decomposition_list: + try: + # NB: need to (possibly) raise an exception before setting the + # name variable below + decomposition = decomp.decompose.__name__.split('_') + decomp_rule[decomposition[3]] = decomp + name = decomposition[2] + # 'M' stands for minus, 'P' stands for plus 'N' stands for neutral + # e.g. decomp_rule['M'] will give you the decomposition_rule that + # ends with a Ry(-pi/2) + except IndexError: + pass + + local_prev_Ry_sign = prev_Ry_sign.setdefault(cmd.engine, dict()) + + if name == 'cnot2rxx': + assert get_control_count(cmd) == 1 + ctrl_id = cmd.control_qubits[0].id + + if local_prev_Ry_sign.get(ctrl_id, -1) <= 0: + # If the previous qubit had Ry(-pi/2) choose the decomposition + # that starts with Ry(pi/2) + local_prev_Ry_sign[ctrl_id] = -1 + # Now the prev_Ry_sign is set to -1 since at the end of the + # decomposition we will have a Ry(-pi/2) + return decomp_rule['M'] + + # Previous qubit had Ry(pi/2) choose decomposition that starts + # with Ry(-pi/2) and ends with R(pi/2) + local_prev_Ry_sign[ctrl_id] = 1 + return decomp_rule['P'] + + if name == 'h2rx': + qubit_id = [qb.id for qureg in cmd.qubits for qb in qureg] + assert len(qubit_id) == 1 # this should be a single qubit gate + qubit_id = qubit_id[0] + + if local_prev_Ry_sign.get(qubit_id, 0) == 0: + local_prev_Ry_sign[qubit_id] = 1 + return decomp_rule['M'] + + local_prev_Ry_sign[qubit_id] = 0 + return decomp_rule['N'] + + if name == 'rz2rx': + qubit_id = [qb.id for qureg in cmd.qubits for qb in qureg] + assert len(qubit_id) == 1 # this should be a single qubit gate + qubit_id = qubit_id[0] + + if local_prev_Ry_sign.get(qubit_id, -1) <= 0: + local_prev_Ry_sign[qubit_id] = -1 + return decomp_rule['M'] + + local_prev_Ry_sign[qubit_id] = 1 + return decomp_rule['P'] + + # No decomposition chosen, so use the first decompostion in the list + # like the default function + return decomposition_list[0] + + +def get_engine_list(): + """ + Returns an engine list compiling code into a trapped ion based compiled + circuit code. + + Note: + + - Classical instructions gates such as e.g. Flush and Measure are + automatically allowed. + - The restricted gate set engine does not work with Rxx gates, as + ProjectQ will by default bounce back and forth between Cz gates and Cx + gates. An appropriate decomposition chooser needs to be used! + + Returns: + A list of suitable compiler engines. + """ + return restrictedgateset.get_engine_list( + one_qubit_gates=(Rx, Ry), + two_qubit_gates=(Rxx, ), + compiler_chooser=chooser_Ry_reducer) diff --git a/projectq/setups/trapped_ion_decomposer_test.py b/projectq/setups/trapped_ion_decomposer_test.py new file mode 100644 index 000000000..23b6485c6 --- /dev/null +++ b/projectq/setups/trapped_ion_decomposer_test.py @@ -0,0 +1,150 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Tests for projectq.setups.trapped_ion_decomposer.py." + +import projectq +from projectq.ops import (Rx, Ry, Rz, H, X, CNOT, Measure, Rxx, + ClassicalInstructionGate) +from projectq.cengines import (MainEngine, DummyEngine, AutoReplacer, + TagRemover, InstructionFilter, + DecompositionRuleSet, DecompositionRule) +from projectq.meta import get_control_count + +from . import restrictedgateset +from .trapped_ion_decomposer import chooser_Ry_reducer, get_engine_list + + +def filter_gates(eng, cmd): + if isinstance(cmd.gate, ClassicalInstructionGate): + return True + if ((cmd.gate == X and get_control_count(cmd) == 1) or cmd.gate == H + or isinstance(cmd.gate, Rz)): + return False + return True + + +def test_chooser_Ry_reducer_synthetic(): + backend = DummyEngine(save_commands=True) + rule_set = DecompositionRuleSet( + modules=[projectq.libs.math, projectq.setups.decompositions]) + + engine_list = [ + AutoReplacer(rule_set, chooser_Ry_reducer), + TagRemover(), + InstructionFilter(filter_gates), + ] + + eng = MainEngine(backend=backend, engine_list=engine_list) + control = eng.allocate_qubit() + target = eng.allocate_qubit() + CNOT | (control, target) + CNOT | (control, target) + eng.flush() + idx0 = len(backend.received_commands) - 2 + idx1 = len(backend.received_commands) + CNOT | (control, target) + eng.flush() + + assert isinstance(backend.received_commands[idx0].gate, Ry) + assert isinstance(backend.received_commands[idx1].gate, Ry) + assert (backend.received_commands[idx0].gate.get_inverse() == + backend.received_commands[idx1].gate) + + eng = MainEngine(backend=backend, engine_list=engine_list) + control = eng.allocate_qubit() + target = eng.allocate_qubit() + H | target + eng.flush() + idx0 = len(backend.received_commands) - 2 + idx1 = len(backend.received_commands) + H | target + eng.flush() + + assert isinstance(backend.received_commands[idx0].gate, Ry) + assert isinstance(backend.received_commands[idx1].gate, Ry) + assert (backend.received_commands[idx0].gate.get_inverse() == + backend.received_commands[idx1].gate) + + eng = MainEngine(backend=backend, engine_list=engine_list) + control = eng.allocate_qubit() + target = eng.allocate_qubit() + Rz(1.23456) | target + eng.flush() + idx0 = len(backend.received_commands) - 2 + idx1 = len(backend.received_commands) + Rz(1.23456) | target + eng.flush() + + assert isinstance(backend.received_commands[idx0].gate, Ry) + assert isinstance(backend.received_commands[idx1].gate, Ry) + assert (backend.received_commands[idx0].gate.get_inverse() == + backend.received_commands[idx1].gate) + + +def _dummy_h2nothing_A(cmd): + qubit = cmd.qubits[0] + Ry(1.23456) | qubit + + +def test_chooser_Ry_reducer_unsupported_gate(): + backend = DummyEngine(save_commands=True) + rule_set = DecompositionRuleSet( + rules=[DecompositionRule(H.__class__, _dummy_h2nothing_A)]) + + engine_list = [ + AutoReplacer(rule_set, chooser_Ry_reducer), + TagRemover(), + InstructionFilter(filter_gates), + ] + + eng = MainEngine(backend=backend, engine_list=engine_list) + qubit = eng.allocate_qubit() + H | qubit + eng.flush() + + for cmd in backend.received_commands: + print(cmd) + + assert isinstance(backend.received_commands[1].gate, Ry) + + +def test_chooser_Ry_reducer(): + # Without the chooser_Ry_reducer function, i.e. if the restricted gate set + # just picked the first option in each decomposition list, the circuit + # below would be decomposed into 8 single qubit gates and 1 two qubit + # gate. + # + # Including the Allocate, Measure and Flush commands, this would result in + # 13 commands. + # + # Using the chooser_Rx_reducer you get 10 commands, since you now have 4 + # single qubit gates and 1 two qubit gate. + + for engine_list, count in [(restrictedgateset.get_engine_list( + one_qubit_gates=(Rx, Ry), + two_qubit_gates=(Rxx, )), 13), + (get_engine_list(), 11)]: + + backend = DummyEngine(save_commands=True) + eng = projectq.MainEngine(backend, engine_list, verbose=True) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + H | qubit1 + CNOT | (qubit1, qubit2) + Rz(0.2) | qubit1 + Measure | qubit1 + eng.flush() + + assert len(backend.received_commands) == count From a80fa84765904650df38970440262c232d0230e7 Mon Sep 17 00:00:00 2001 From: Cheng Li Date: Tue, 4 Feb 2020 13:23:43 +0100 Subject: [PATCH 033/113] Matplotlib drawer backend (#352) * Adding tutorials directory * test * BV Algorithm * add Matplotlib circuit drawer backend, this version works for H, CNOT, and Multi-CNOT. * Delete the added unnecessary attributes in Command object * Create the CircuitDrawerMatplotlib Class to handles drawing with matplotlib * Deleted tutorials/.gitkeep * update * fix measurement gate * Delete unrelated files. * fix Toffoli gate position issue and change the qubit position from 'str' to 'int' * Pytest for drawer_mpl * Tests for _plot function * Fix the R(angle) gate drawing * added test for is_available and QFT gate * fix drawing distance between gates when gate_length >2 * new test png for pytest mpl * added Swap gates and CSwap gate with multi-control and multi-target. * update test and comments * Address comments in _drawer.py * Reindent and reformat parts of _drawer.py * Address comments in _plot.py - Minor tweaks, code cleanup, rewrites, etc. * Reindent and reformat _plot.py * update tests * Move matplotlib drawer into its own file + add test coverage * Use regular expressions to rewrite and shorten gate names * Change internal storage format for CircuitDrawerMatplotlib * Better graphics and adapt plot functions to new internal format - Support for new internal format - Resulting quantum circuit figure whould work better with scaling - Large quantum circuits will now result in wider figure instead of squeezing everything into the default matplotlib size - Some support for multi-target qubit gates - General code cleanup - Dropped support for double lines when qubit is in classical state * Complete test coverage + add some checks for to_draw() inputs * Compatibility with matplotlib 2.2.3 * Remove compatibility code for MacOSX. Use local matplotlibrc if necessary instead. * Add matplotlib dependency to requirements.txt * Fix non-UTF8 character in file * Fix .travis.yml * Remove unnecessary PNG files * Add CircuitDrawerMatplotlib to documentation and minor code fix * Fix docstring for CircuitDrawerMatplotlib Co-authored-by: Nguyen Damien --- .travis.yml | 3 + docs/projectq.backends.rst | 1 + projectq/backends/__init__.py | 2 +- projectq/backends/_circuits/__init__.py | 4 + projectq/backends/_circuits/_drawer.py | 7 +- .../backends/_circuits/_drawer_matplotlib.py | 208 ++++++ .../_circuits/_drawer_matplotlib_test.py | 148 +++++ projectq/backends/_circuits/_plot.py | 607 ++++++++++++++++++ projectq/backends/_circuits/_plot_test.py | 289 +++++++++ projectq/ops/_command.py | 1 + projectq/tests/_drawmpl_test.py | 53 ++ requirements.txt | 1 + 12 files changed, 1319 insertions(+), 5 deletions(-) create mode 100644 projectq/backends/_circuits/_drawer_matplotlib.py create mode 100644 projectq/backends/_circuits/_drawer_matplotlib_test.py create mode 100644 projectq/backends/_circuits/_plot.py create mode 100644 projectq/backends/_circuits/_plot_test.py create mode 100644 projectq/tests/_drawmpl_test.py diff --git a/.travis.yml b/.travis.yml index fbe009436..47f727e2a 100755 --- a/.travis.yml +++ b/.travis.yml @@ -42,6 +42,9 @@ install: - if [ "${PYTHON:0:1}" = "3" ]; then pip$PY install dormouse; fi - pip$PY install -e . +before_script: + - "echo 'backend: Agg' > matplotlibrc" + # command to run tests script: export OMP_NUM_THREADS=1 && pytest projectq --cov projectq diff --git a/docs/projectq.backends.rst b/docs/projectq.backends.rst index 621f7ce86..cc6531df4 100755 --- a/docs/projectq.backends.rst +++ b/docs/projectq.backends.rst @@ -5,6 +5,7 @@ backends projectq.backends.CommandPrinter projectq.backends.CircuitDrawer + projectq.backends.CircuitDrawerMatplotlib projectq.backends.Simulator projectq.backends.ClassicalSimulator projectq.backends.ResourceCounter diff --git a/projectq/backends/__init__.py b/projectq/backends/__init__.py index 6a3319779..4813a52b4 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -26,7 +26,7 @@ * an interface to the IBM Quantum Experience chip (and simulator). """ from ._printer import CommandPrinter -from ._circuits import CircuitDrawer +from ._circuits import CircuitDrawer, CircuitDrawerMatplotlib from ._sim import Simulator, ClassicalSimulator from ._resource import ResourceCounter from ._ibm import IBMBackend diff --git a/projectq/backends/_circuits/__init__.py b/projectq/backends/_circuits/__init__.py index 1f22faec4..be22d24d2 100755 --- a/projectq/backends/_circuits/__init__.py +++ b/projectq/backends/_circuits/__init__.py @@ -13,4 +13,8 @@ # limitations under the License. from ._to_latex import to_latex +from ._plot import to_draw + from ._drawer import CircuitDrawer +from ._drawer_matplotlib import CircuitDrawerMatplotlib + diff --git a/projectq/backends/_circuits/_drawer.py b/projectq/backends/_circuits/_drawer.py index 85aee3dac..2562a07dd 100755 --- a/projectq/backends/_circuits/_drawer.py +++ b/projectq/backends/_circuits/_drawer.py @@ -15,8 +15,6 @@ Contains a compiler engine which generates TikZ Latex code describing the circuit. """ -import sys - from builtins import input from projectq.cengines import LastEngineException, BasicEngine @@ -223,12 +221,13 @@ 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) + assert get_control_count(cmd) == 0 + 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: + while m not in ('0', '1', 1, 0): prompt = ("Input measurement result (0 or 1) for " "qubit " + str(qubit) + ": ") m = input(prompt) diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py new file mode 100644 index 000000000..23a07c767 --- /dev/null +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -0,0 +1,208 @@ +# 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. +""" +Contains a compiler engine which generates matplotlib figures describing the +circuit. +""" + +from builtins import input +import re +import itertools + +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 + +# ============================================================================== + + +def _format_gate_str(cmd): + param_str = '' + gate_name = str(cmd.gate) + if '(' in gate_name: + (gate_name, param_str) = re.search(r'(.+)\((.*)\)', gate_name).groups() + params = re.findall(r'([^,]+)', param_str) + params_str_list = [] + for param in params: + try: + params_str_list.append('{0:.2f}'.format(float(param))) + except ValueError: + if len(param) < 8: + params_str_list.append(param) + else: + params_str_list.append(param[:5] + '...') + + gate_name += '(' + ','.join(params_str_list) + ')' + return gate_name + + +# ============================================================================== + + +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 + is the last engine. Otherwise, all measurements yield the + result default_measure (0 or 1). + default_measure (bool): Default value to use as measurement + results if accept_input is False and there is no underlying + backend to register real measurement results. + """ + BasicEngine.__init__(self) + self._accept_input = accept_input + self._default_measure = default_measure + self._map = dict() + self._qubit_lines = {} + + def is_available(self, cmd): + """ + Specialized implementation of is_available: Returns True if the + CircuitDrawerMatplotlib is the last engine + (since it can print any command). + + Args: + cmd (Command): Command for 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). + """ + try: + # Multi-qubit gates may fail at drawing time if the target qubits + # are not right next to each other on the output graphic. + return BasicEngine.is_available(self, cmd) + except LastEngineException: + return True + + def _process(self, cmd): + """ + Process the command cmd and stores it in the internal storage + + Queries the user for measurement input if a measurement command + arrives if accept_input was set to True. Otherwise, it uses the + default_measure parameter to register the measurement outcome. + + Args: + cmd (Command): Command to add to the circuit diagram. + """ + 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] = [] + return + + if cmd.gate == Deallocate: + return + + if self.is_last_engine and cmd.gate == Measure: + assert get_control_count(cmd) == 0 + for qureg in cmd.qubits: + for qubit in qureg: + 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) + ": ") + measurement = input(prompt) + else: + measurement = self._default_measure + 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] + + ref_qubit_id = targets[0] + gate_str = _format_gate_str(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)) + + # 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 + # over all the known qubit axes. + # This is to avoid the possibility of a multi-qubit gate overlapping + # with some other gates. This could potentially be improved by only + # 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) + + 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)) + else: + self._qubit_lines[qubit_id].append(None) + + def receive(self, command_list): + """ + Receive a list of commands from the previous engine, print the + commands, and then send them on to the next engine. + + Args: + command_list (list): List of Commands to print (and + potentially send on to the next engine). + """ + for cmd in command_list: + if not isinstance(cmd.gate, FlushGate): + self._process(cmd) + + if not self.is_last_engine: + self.send([cmd]) + + def draw(self, qubit_labels=None, drawing_order=None): + """ + Generates and returns the plot of the quantum circuit stored so far + + Args: + qubit_labels (dict): label for each wire in the output figure. + Keys: qubit IDs, Values: string to print out as label for + that particular qubit wire. + drawing_order (dict): position of each qubit in the output + graphic. Keys: qubit IDs, Values: position of qubit on the + qubit line in the graphic. + + Returns: + A tuple containing the matplotlib figure and axes objects + """ + 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) diff --git a/projectq/backends/_circuits/_drawer_matplotlib_test.py b/projectq/backends/_circuits/_drawer_matplotlib_test.py new file mode 100644 index 000000000..a76fbc99b --- /dev/null +++ b/projectq/backends/_circuits/_drawer_matplotlib_test.py @@ -0,0 +1,148 @@ +# 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.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.types import WeakQubitRef + +from . import _drawer_matplotlib as _drawer +from ._drawer_matplotlib import CircuitDrawerMatplotlib + + +def test_drawer_measurement(): + drawer = CircuitDrawerMatplotlib(default_measure=0) + eng = MainEngine(drawer, []) + qubit = eng.allocate_qubit() + Measure | qubit + assert int(qubit) == 0 + + drawer = CircuitDrawerMatplotlib(default_measure=1) + eng = MainEngine(drawer, []) + qubit = eng.allocate_qubit() + Measure | qubit + assert int(qubit) == 1 + + drawer = CircuitDrawerMatplotlib(accept_input=True) + eng = MainEngine(drawer, []) + qubit = eng.allocate_qubit() + + old_input = _drawer.input + + _drawer.input = lambda x: '1' + Measure | qubit + assert int(qubit) == 1 + _drawer.input = old_input + + +class MockEngine(object): + def is_available(self, cmd): + self.cmd = cmd + self.called = True + return False + + +def test_drawer_isavailable(): + drawer = CircuitDrawerMatplotlib() + drawer.is_last_engine = True + + qb0 = WeakQubitRef(None, 0) + qb1 = WeakQubitRef(None, 1) + qb2 = WeakQubitRef(None, 2) + qb3 = WeakQubitRef(None, 3) + + for gate in (X, Rx(1.0)): + 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], )) + cmd1 = Command(None, Swap, ([qb0], [qb1])) + cmd2 = Command(None, Swap, ([qb0], [qb1]), [qb2]) + cmd3 = Command(None, Swap, ([qb0], [qb1]), [qb2, qb3]) + + assert drawer.is_available(cmd1) + assert drawer.is_available(cmd2) + assert drawer.is_available(cmd3) + + mock_engine = MockEngine() + mock_engine.called = False + drawer.is_last_engine = False + drawer.next_engine = mock_engine + + assert not drawer.is_available(cmd0) + assert mock_engine.called + assert mock_engine.cmd is cmd0 + + assert not drawer.is_available(cmd1) + assert mock_engine.called + assert mock_engine.cmd is cmd1 + + +def _draw_subst(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): + return qubit_lines + + +class MyGate(BasicGate): + def __init__(self, *args): + BasicGate.__init__(self) + self.params = args + + def __str__(self): + param_str = '{}'.format(self.params[0]) + for param in self.params[1:]: + param_str += ',{}'.format(param) + return str(self.__class__.__name__) + "(" + param_str + ")" + + +def test_drawer_draw(): + old_draw = _drawer.to_draw + _drawer.to_draw = _draw_subst + + backend = DummyEngine() + + drawer = CircuitDrawerMatplotlib() + + eng = MainEngine(backend, [drawer]) + qureg = eng.allocate_qureg(3) + H | qureg[1] + H | qureg[0] + X | qureg[0] + Rx(1) | qureg[1] + CNOT | (qureg[0], qureg[1]) + Swap | (qureg[0], qureg[1]) + MyGate(1.2) | qureg[2] + MyGate(1.23456789) | qureg[2] + MyGate(1.23456789, 2.3456789) | qureg[2] + MyGate(1.23456789, 'aaaaaaaa', 'bbb', 2.34) | qureg[2] + X | qureg[0] + + 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], []), + ('MyGate(1.23,2.35)', [2], []), + ('MyGate(1.23,aaaaa...,bbb,2.34)', [2], []), None] + } + + _drawer.to_draw = old_draw diff --git a/projectq/backends/_circuits/_plot.py b/projectq/backends/_circuits/_plot.py new file mode 100644 index 000000000..009b00ab7 --- /dev/null +++ b/projectq/backends/_circuits/_plot.py @@ -0,0 +1,607 @@ +# 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. +""" +This module provides the basic functionality required to plot a quantum +circuit in a matplotlib figure. +It is mainly used by the CircuitDrawerMatplotlib compiler engine. + +Currently, it supports all single-qubit gates, including their controlled +versions to an arbitrary number of control qubits. It also supports +multi-target qubit gates under some restrictions. Namely that the target +qubits must be neighbours in the output figure (which cannot be determined +durinng compilation at this time). +""" + +from copy import deepcopy +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.collections import PatchCollection, LineCollection +from matplotlib.lines import Line2D +from matplotlib.patches import Circle, Arc, Rectangle + +# Important note on units for the plot parameters. +# The following entries are in inches: +# - column_spacing +# - labels_margin +# - wire_height +# +# The following entries are in data units (matplotlib) +# - control_radius +# - gate_offset +# - mgate_width +# - not_radius +# - swap_delta +# - 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) + +# ============================================================================== + + +def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): + """ + Translates a given circuit to a matplotlib figure. + + Args: + qubit_lines (dict): list of gates for each qubit axis + qubit_labels (dict): label to print in front of the qubit wire for + each qubit ID + drawing_order (dict): index of the wire for each qubit ID to be drawn. + **kwargs (dict): additional parameters are used to update the default + plot parameters + + Returns: + A tuple with (figure, axes) + + Note: + Numbering of qubit wires starts at 0 at the bottom and increases + vertically. + """ + if qubit_labels is None: + 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!') + + 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) + } + 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( + '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) + + n_labels = len(list(qubit_lines)) + + wire_height = plot_params['wire_height'] + # Grid in inches + wire_grid = np.arange(wire_height, (n_labels + 1) * wire_height, + wire_height, + dtype=float) + + fig, axes = create_figure(plot_params) + + # Grid in inches + gate_grid = calculate_gate_grid(axes, qubit_lines, plot_params) + + width = gate_grid[-1] + plot_params['column_spacing'] + height = wire_grid[-1] + wire_height + + resize_figure(fig, axes, width, height, plot_params) + + # Convert grids into data coordinates + units_per_inch = plot_params['units_per_inch'] + + gate_grid *= units_per_inch + gate_grid = gate_grid + plot_params['x_offset'] + wire_grid *= units_per_inch + plot_params['column_spacing'] *= units_per_inch + + draw_wires(axes, n_labels, gate_grid, wire_grid, plot_params) + + draw_labels(axes, qubit_labels, drawing_order, wire_grid, plot_params) + + draw_gates(axes, qubit_lines, drawing_order, gate_grid, wire_grid, + plot_params) + return fig, axes + + +# ============================================================================== +# Functions used to calculate the layout + + +def gate_width(axes, gate_str, plot_params): + """ + Calculate the width of a gate based on its string representation. + + Args: + axes (matplotlib.axes.Axes): axes object + gate_str (str): string representation of a gate + plot_params (dict): plot parameters + + Returns: + The width of a gate on the figure (in inches) + """ + if gate_str == 'X': + return 2 * plot_params['not_radius'] / plot_params['units_per_inch'] + if gate_str == 'Swap': + return 2 * plot_params['swap_delta'] / plot_params['units_per_inch'] + + 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.figure.canvas.draw() + width = (obj.get_window_extent(obj.figure.canvas.get_renderer()).width + / axes.figure.dpi) + obj.remove() + return width + 2 * plot_params['gate_offset'] + + +def calculate_gate_grid(axes, qubit_lines, plot_params): + """ + Calculate an optimal grid spacing for a list of quantum gates. + + Args: + axes (matplotlib.axes.Axes): axes object + qubit_lines (dict): list of gates for each qubit axis + plot_params (dict): plot parameters + + Returns: + An array (np.ndarray) with the gate x positions. + """ + # NB: column_spacing is still in inch when this function is called + column_spacing = plot_params['column_spacing'] + data = list(qubit_lines.values()) + 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) + ] + + gate_grid = np.array([0] * (depth + 1), dtype=float) + + gate_grid[0] = plot_params['labels_margin'] + (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[-1] = gate_grid[-2] + column_spacing + width_list[-1] * 0.5 + return gate_grid + + +# ============================================================================== +# Basic helper functions + + +def text(axes, gate_pos, wire_pos, textstr, plot_params): + """ + Draws a text box on the figure. + + Args: + axes (matplotlib.axes.Axes): axes object + gate_pos (float): x coordinate of the gate [data units] + wire_pos (float): y coordinate of the qubit wire + textstr (str): text of the gate and box + 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']) + + +# ============================================================================== + + +def create_figure(plot_params): + """ + Create a new figure as well as a new axes instance + + Args: + plot_params (dict): plot parameters + + Returns: + A tuple with (figure, axes) + """ + fig = plt.figure(facecolor='w', edgecolor='w') + axes = plt.axes() + axes.set_axis_off() + axes.set_aspect('equal') + plot_params['units_per_inch'] = fig.dpi / axes.get_window_extent().width + return fig, axes + + +def resize_figure(fig, axes, width, height, plot_params): + """ + Resizes a figure and adjust the limits of the axes instance to make sure + that the distances in data coordinates on the screen stay constant. + + Args: + fig (matplotlib.figure.Figure): figure object + axes (matplotlib.axes.Axes): axes object + width (float): new figure width + height (float): new figure height + plot_params (dict): plot parameters + + Returns: + A tuple with (figure, axes) + """ + fig.set_size_inches(width, height) + + new_limits = plot_params['units_per_inch'] * np.array([width, height]) + axes.set_xlim(0, new_limits[0]) + axes.set_ylim(0, new_limits[1]) + + +def draw_gates(axes, qubit_lines, drawing_order, gate_grid, wire_grid, + plot_params): + """ + Draws the gates. + + Args: + qubit_lines (dict): list of gates for each qubit axis + drawing_order (dict): index of the wire for each qubit ID to be drawn + gate_grid (np.ndarray): x positions of the gates + wire_grid (np.ndarray): y positions of the qubit wires + plot_params (dict): plot parameters + + Returns: + A tuple with (figure, axes) + """ + for qubit_line in qubit_lines.values(): + for idx, data in enumerate(qubit_line): + if data is not None: + (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) + + +def draw_gate(axes, gate_str, gate_pos, target_wires, targets_order, + control_wires, plot_params): + """ + Draws a single gate at a given location. + + Args: + axes (AxesSubplot): axes object + gate_str (str): string representation of a gate + gate_pos (float): x coordinate of the gate [data units] + target_wires (list): y coordinates of the target qubits + targets_order (list): index of the wires corresponding to the target + qubit IDs + control_wires (list): y coordinates of the control qubits + plot_params (dict): plot parameters + + Returns: + A tuple with (figure, axes) + """ + # 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) + 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) + 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) + else: + 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) + + 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'])) + + 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'])) + + +def draw_generic_gate(axes, gate_pos, wire_pos, gate_str, plot_params): + """ + Draws a measurement gate. + + Args: + axes (AxesSubplot): axes object + gate_pos (float): x coordinate of the gate [data units] + wire_pos (float): y coordinate of the qubit wire + gate_str (str) : string representation of a gate + plot_params (dict): plot parameters + """ + obj = text(axes, gate_pos, wire_pos, gate_str, plot_params) + obj.set_zorder(7) + + factor = plot_params['units_per_inch'] / obj.figure.dpi + gate_offset = plot_params['gate_offset'] + + renderer = obj.figure.canvas.get_renderer() + width = obj.get_window_extent(renderer).width * factor + 2 * gate_offset + 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)) + + +def draw_measure_gate(axes, gate_pos, wire_pos, plot_params): + """ + Draws a measurement gate. + + Args: + axes (AxesSubplot): axes object + gate_pos (float): x coordinate of the gate [data units] + wire_pos (float): y coordinate of the qubit wire + plot_params (dict): plot parameters + """ + # pylint: disable=invalid-name + + width = plot_params['mgate_width'] + height = 0.9 * width + 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) + 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) + ] + + 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): + """ + Draws a multi-target qubit gate. + + Args: + axes (matplotlib.axes.Axes): axes object + gate_str (str): string representation of a gate + gate_pos (float): x coordinate of the gate [data units] + wire_pos_min (float): y coordinate of the lowest qubit wire + wire_pos_max (float): y coordinate of the highest qubit wire + plot_params (dict): plot parameters + """ + 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) + 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 + 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)) + + +def draw_x_gate(axes, gate_pos, wire_pos, plot_params): + """ + Draws the symbol for a X/NOT gate. + + Args: + axes (matplotlib.axes.Axes): axes object + gate_pos (float): x coordinate of the gate [data units] + wire_pos (float): y coordinate of the qubit wire [data units] + plot_params (dict): plot parameters + """ + 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.set_label('NOT') + axes.add_collection(gate) + + +def draw_control_z_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): + """ + Draws the symbol for a controlled-Z gate. + + Args: + axes (matplotlib.axes.Axes): axes object + wire_pos (float): x coordinate of the gate [data units] + y1 (float): y coordinate of the 1st qubit wire + 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.set_label('CZ') + axes.add_collection(gate) + + +def draw_swap_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): + """ + Draws the symbol for a SWAP gate. + + Args: + axes (matplotlib.axes.Axes): axes object + x (float): x coordinate [data units] + y1 (float): y coordinate of the 1st qubit wire + y2 (float): y coordinate of the 2nd qubit wire + plot_params (dict): plot parameters + """ + delta = plot_params['swap_delta'] + + 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, wire_pos1), (gate_pos, wire_pos2)]) + + gate = LineCollection(lines, + colors='k', + linewidths=plot_params['linewidth']) + gate.set_label('SWAP') + axes.add_collection(gate) + + +def draw_wires(axes, n_labels, gate_grid, wire_grid, plot_params): + """ + Draws all the circuit qubit wires. + + Args: + axes (matplotlib.axes.Axes): axes object + n_labels (int): number of qubit + gate_grid (ndarray): array with the ref. x positions of the gates + wire_grid (ndarray): array with the ref. y positions of the qubit + wires + plot_params (dict): plot parameters + """ + # pylint: disable=invalid-name + + 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') + all_lines.set_label('qubit_wires') + axes.add_collection(all_lines) + + +def draw_labels(axes, qubit_labels, drawing_order, wire_grid, plot_params): + """ + Draws the labels at the start of each qubit wire + + Args: + axes (matplotlib.axes.Axes): axes object + qubit_labels (list): labels of the qubit to be drawn + drawing_order (dict): Mapping between wire indices and qubit IDs + gate_grid (ndarray): array with the ref. x positions of the gates + wire_grid (ndarray): array with the ref. y positions of the qubit + wires + plot_params (dict): plot parameters + """ + 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) diff --git a/projectq/backends/_circuits/_plot_test.py b/projectq/backends/_circuits/_plot_test.py new file mode 100644 index 000000000..cd5d3ab0f --- /dev/null +++ b/projectq/backends/_circuits/_plot_test.py @@ -0,0 +1,289 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" + Tests for projectq.backends._circuits._plot.py. + + To generate the baseline images, + run the tests with '--mpl-generate-path=baseline' + + Then run the tests simply with '--mpl' +""" +import pytest +from copy import deepcopy +import projectq.backends._circuits._plot as _plot + +# ============================================================================== + + +class PseudoCanvas(object): + def __init__(self): + pass + + def draw(self): + pass + + def get_renderer(self): + return + + +class PseudoFigure(object): + def __init__(self): + self.canvas = PseudoCanvas() + self.dpi = 1 + + +class PseudoBBox(object): + def __init__(self, width, height): + self.width = width + self.height = height + + +class PseudoText(object): + def __init__(self, text): + self.text = text + self.figure = PseudoFigure() + + def get_window_extent(self, *args): + return PseudoBBox(len(self.text), 1) + + def remove(self): + pass + + +class PseudoTransform(object): + def __init__(self): + pass + + def inverted(self): + return self + + def transform_bbox(self, bbox): + return bbox + + +class PseudoAxes(object): + def __init__(self): + self.figure = PseudoFigure() + self.transData = PseudoTransform() + + def add_patch(self, x): + return x + + def text(self, x, y, text, *args, **kwargse): + return PseudoText(text) + + +# ============================================================================== + + +@pytest.fixture(scope="module") +def plot_params(): + params = deepcopy(_plot._DEFAULT_PLOT_PARAMS) + params.update([('units_per_inch', 1)]) + return params + + +@pytest.fixture +def axes(): + return PseudoAxes() + + +# ============================================================================== + + +@pytest.mark.parametrize('gate_str', ['X', 'Swap', 'Measure', 'Y', 'Rz(1.00)']) +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'] + elif gate_str == 'Swap': + assert width == 2 * plot_params['swap_delta'] / plot_params[ + 'units_per_inch'] + elif gate_str == 'Measure': + assert width == plot_params['mgate_width'] + else: + assert width == len(gate_str) + 2 * plot_params['gate_offset'] + + +def test_calculate_gate_grid(axes, plot_params): + 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 + assert gate_grid[0] > plot_params['labels_margin'] + width = [gate_grid[i + 1] - gate_grid[i] for i in range(4)] + + # Column grid is given by: + # |---*---|---*---|---*---|---*---| + # |-- w --|-- w --|-- w --|.5w| + + column_spacing = plot_params['column_spacing'] + ref_width = _plot.gate_width(axes, 'X', plot_params) + + for w in width[:-1]: + assert ref_width + column_spacing == pytest.approx(w) + assert 0.5 * ref_width + column_spacing == pytest.approx(width[-1]) + + +def test_create_figure(plot_params): + fig, axes = _plot.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, 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] + } + fig, axes = _plot.to_draw(qubit_lines) + + units_per_inch = plot_params['units_per_inch'] + not_radius = plot_params['not_radius'] + control_radius = plot_params['control_radius'] + swap_delta = plot_params['swap_delta'] + wire_height = plot_params['wire_height'] * units_per_inch + mgate_width = plot_params['mgate_width'] + + labels = [] + text_gates = [] + measure_gates = [] + for text in axes.texts: + if text.get_text() == '$|0\\rangle$': + labels.append(text) + elif text.get_text() == ' ': + measure_gates.append(text) + 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)) + + # 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)) + # 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'])) + + # Z gate + assert len(text_gates) == 1 + assert text_gates[0].get_text() == 'Z' + assert text_gates[0].get_position()[1] == pytest.approx(2 * wire_height) + + # CZ gate + cz_gate = [obj for obj in axes.collections if obj.get_label() == 'CZ'][0] + # 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) + # 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)) + + # Swap gate + 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) + assert qubit.get_extents().height == pytest.approx(2 * swap_delta) + # 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)) + + # Measure gate + 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)) + + +def test_draw_advanced(plot_params): + qubit_lines = {0: [('X', [0], []), ('Measure', [0], [])], 1: [None, None]} + + with pytest.raises(RuntimeError): + _plot.to_draw(qubit_lines, qubit_labels={1: 'qb1', 2: 'qb2'}) + + with pytest.raises(RuntimeError): + _plot.to_draw(qubit_lines, drawing_order={0: 0, 1: 2}) + + with pytest.raises(RuntimeError): + _plot.to_draw(qubit_lines, drawing_order={1: 1, 2: 0}) + + # -------------------------------------------------------------------------- + + _, axes = _plot.to_draw(qubit_lines) + for text in axes.texts: + 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']) + + 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']) + + positions = [axes.texts[qubit_id].get_position() for qubit_id in range(2)] + assert positions[1][1] < positions[0][1] diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 6c320f375..f9268c420 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -104,6 +104,7 @@ def __init__(self, engine, gate, qubits, controls=(), tags=()): tags (list[object]): Tags associated with the command. """ + qubits = tuple( [WeakQubitRef(qubit.engine, qubit.id) for qubit in qreg] for qreg in qubits) diff --git a/projectq/tests/_drawmpl_test.py b/projectq/tests/_drawmpl_test.py new file mode 100644 index 000000000..3d78befa6 --- /dev/null +++ b/projectq/tests/_drawmpl_test.py @@ -0,0 +1,53 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' + Tests for projectq.backends._circuits._drawer.py. + + To generate the baseline images + run the tests with '--mpl-generate-path=baseline' + + Then run the tests simply with '--mpl' +''' + +import pytest +from projectq import MainEngine +from projectq.ops import * +from projectq.backends import Simulator +from projectq.backends import CircuitDrawerMatplotlib +from projectq.cengines import DecompositionRuleSet, AutoReplacer +import projectq.setups.decompositions + +@pytest.mark.mpl_image_compare +def test_drawer_mpl(): + drawer = CircuitDrawerMatplotlib() + rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) + eng = MainEngine(backend=Simulator(), engine_list=[AutoReplacer(rule_set), + drawer]) + ctrl = eng.allocate_qureg(2) + qureg = eng.allocate_qureg(3) + + Swap | (qureg[0], qureg[2]) + C(Swap) | (qureg[0], qureg[1], qureg[2]) + + CNOT | (qureg[0], qureg[2]) + Rx(1.0) | qureg[0] + CNOT | (qureg[1], qureg[2]) + C(X, 2) | (ctrl[0], ctrl[1], qureg[2]) + QFT | qureg + All(Measure) | qureg + + eng.flush() + fig, ax = drawer.draw() + return fig \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 903d45bdc..60d6b013c 100755 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ pybind11>=2.2.3 requests scipy networkx +matplotlib>=2.2.3 From 7da50cf7a126a29c21d7c935c71684e86132b522 Mon Sep 17 00:00:00 2001 From: David Bretaud <40793394+dbretaud@users.noreply.github.com> Date: Tue, 4 Feb 2020 13:16:02 +0000 Subject: [PATCH 034/113] Ibm backend v2 (solves #318 and #347) (#349) * reduced Ry decomposition : modification of restrictedgateset setup to include compiler chooser, extension of available decompositions, new setup based on restrictedgateset but adapted for trapped ion configurations * Addition of test file for the rz2rx decomposition Addition of test file for the rz2rx decomposition and edit to comments in the rz2rx file * Revert "Addition of test file for the rz2rx decomposition" This reverts commit 5aab56b7384d60d5ac09e5b08bebf14135b5a006. * Create rz2rx_test file Addition of test file for the rz2rx decomposition * Update rz2rx.py Update to comments * Update rz2rx_test.py Update to comments * Minor update: remove accidental file * Minor update rz2rx.py Corrected an angle. * Minor update rz2rx_test.py Edited comments. * Create h2rx_test.py Updated method for test_decomposition which tests that decomposition produces identical wave-state up to a global phase. * Improvement of the decomposition chooser; Note that basic restricted gate set will fail decomposing into Rxx because of the default chooser * Updates to h2rx and cnot2rxx * Create cnot2rxx_test.py Testing file for cnot2rxx decomposition. Includes updated method for test_decomposition which tests that decomposition produces identical wave-state up to a global phase. * basic rotation gates at an angle of 0 or 2pi are removed by the optimizer. basic rotation gates ignore now the global phase and are defined over 0:2pi * Update and create trapped_ion_decomposer_test.py * Minor update Documentation and comments. * Update on comments regarding Identity gates * Changes 1/2 : command can be printed with unicode symbols only via new to_String method; syntax correction; * Work in progress, is commutable * Update to decomposition test files rz2rx_test now tests each decomposition defined in rz2rx.all_defined_decomposition_rules Similar for cnot2rxx_test and h2rx_test * Revert "Work in progress, is commutable" This reverts commit 27f820cac337c81dbc037b5e3e0381b7e31db5b3. * ibmq fix: projectq uses new ibmq API (no doc available, just look at qiskit). Connection fixed for 5qb devices, the 15qb melbourne device and the online simulator coupling map obtained from ibm server instead of being manually written one setup can be used for the 3 different backend * minor fixes * update on the ibm example * minor fixes; revert rotation gates to [0;4pi) * fix comments * fix mapper choice for simulator. added comments * minor fixes with results, mapper and testing files. Improvement of get_probabilities * Revert "Merge branch 'develop' into ibm_V2" This reverts commit cd0452a5f56d8d7fc95dc17f6dc5d4970f3ad130, reversing changes made to 03daf7915ce663f8dc79975ba87dabb4534272e6. * minor fixes * bug fix in client test file * fixing bug and python2.7 compatibility for testing files * fix errors * major fix on testing files * minor fix on comments and python 2.7 compatibility * fix 'super()' call for python 2.7 * additional tests * python2.7 fix * increase coverage, fix a print statement * Some minor stylistic adjustments * Reindent files and fix some linting warnings/errors * Improve test coverage * Improve code readability Co-authored-by: Nguyen Damien --- examples/ibm.py | 19 +- projectq/backends/_ibm/_ibm.py | 112 ++-- projectq/backends/_ibm/_ibm_http_client.py | 435 +++++++++----- .../backends/_ibm/_ibm_http_client_test.py | 533 ++++++++++++------ projectq/backends/_ibm/_ibm_test.py | 306 +++++++--- projectq/cengines/_basicmapper.py | 4 + projectq/cengines/_ibm5qubitmapper.py | 59 +- projectq/cengines/_ibm5qubitmapper_test.py | 51 +- projectq/setups/ibm.py | 142 +++-- projectq/setups/ibm_test.py | 65 ++- 10 files changed, 1210 insertions(+), 516 deletions(-) diff --git a/examples/ibm.py b/examples/ibm.py index 05e042230..37ba4e0d7 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -2,9 +2,10 @@ from projectq.backends import IBMBackend from projectq.ops import Measure, Entangle, All from projectq import MainEngine +import getpass -def run_entangle(eng, num_qubits=5): +def run_entangle(eng, num_qubits=3): """ Runs an entangling operation on the provided compiler engine. @@ -37,9 +38,19 @@ def run_entangle(eng, num_qubits=5): if __name__ == "__main__": + #devices commonly available : + #ibmq_16_melbourne (15 qubit) + #ibmq_essex (5 qubit) + #ibmq_qasm_simulator (32 qubits) + device = None #replace by the IBM device name you want to use + token = None #replace by the token given by IBMQ + if token is None: + token = getpass.getpass(prompt='IBM Q token > ') + if device is None: + token = getpass.getpass(prompt='IBM device > ') # create main compiler engine for the IBM back-end - eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024, - verbose=False, device='ibmqx4'), - engine_list=projectq.setups.ibm.get_engine_list()) + eng = MainEngine(IBMBackend(use_hardware=True, token=token num_runs=1024, + verbose=False, device=device), + engine_list=projectq.setups.ibm.get_engine_list(token=token, device=device)) # run the circuit and print the result print(run_entangle(eng)) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index b1899f043..6486ab4d0 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -13,7 +13,7 @@ # limitations under the License. """ Back-end to run quantum program on IBM's Quantum Experience.""" - +import math import random import json @@ -41,11 +41,11 @@ class IBMBackend(BasicEngine): """ - The IBM Backend class, which stores the circuit, transforms it to JSON - QASM, and sends the circuit through the IBM API. + The IBM Backend class, which stores the circuit, transforms it to JSON, + and sends the circuit through the IBM API. """ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, - user=None, password=None, device='ibmqx4', + token='', device='ibmq_essex', num_retries=3000, interval=1, retrieve_execution=None): """ @@ -59,10 +59,8 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, verbose (bool): If True, statistics are printed, in addition to the measurement result being registered (at the end of the circuit). - user (string): IBM Quantum Experience user name - password (string): IBM Quantum Experience password - device (string): Device to use ('ibmqx4', or 'ibmqx5') - if use_hardware is set to True. Default is ibmqx4. + token (str): IBM quantum experience user password. + device (str): name of the IBM device to use. ibmq_essex By default num_retries (int): Number of times to retry to obtain results from the IBM API. (default is 3000) interval (float, int): Number of seconds between successive @@ -76,15 +74,15 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, if use_hardware: self.device = device else: - self.device = 'simulator' + self.device = 'ibmq_qasm_simulator' self._num_runs = num_runs self._verbose = verbose - self._user = user - self._password = password + self._token=token self._num_retries = num_retries self._interval = interval self._probabilities = dict() self.qasm = "" + self._json=[] self._measured_ids = [] self._allocated_qubits = set() self._retrieve_execution = retrieve_execution @@ -93,17 +91,17 @@ def is_available(self, cmd): """ Return true if the command can be executed. - The IBM quantum chip can do X, Y, Z, T, Tdag, S, Sdag, - rotation gates, barriers, and CX / CNOT. + The IBM quantum chip can only do U1,U2,U3,barriers, and CX / CNOT. + Conversion implemented for Rotation gates and H gates. Args: cmd (Command): Command for which to check availability """ g = cmd.gate - if g == NOT and get_control_count(cmd) <= 1: + if g == NOT and get_control_count(cmd) == 1: return True if get_control_count(cmd) == 0: - if g in (T, Tdag, S, Sdag, H, Y, Z): + if g == H: return True if isinstance(g, (Rx, Ry, Rz)): return True @@ -111,6 +109,11 @@ def is_available(self, cmd): return True return False + def get_qasm(self): + """ Return the QASM representation of the circuit sent to the backend. + Should be called AFTER calling the ibm device """ + return self.qasm + def _reset(self): """ Reset all temporary variables (after flush gate). """ self._clear = True @@ -129,10 +132,10 @@ def _store(self, cmd): self._probabilities = dict() self._clear = False self.qasm = "" + self._json=[] self._allocated_qubits = set() gate = cmd.gate - if gate == Allocate: self._allocated_qubits.add(cmd.qubits[0][0].id) return @@ -154,6 +157,7 @@ def _store(self, cmd): ctrl_pos = cmd.control_qubits[0].id qb_pos = cmd.qubits[0][0].id self.qasm += "\ncx q[{}], q[{}];".format(ctrl_pos, qb_pos) + self._json.append({'qubits': [ctrl_pos, qb_pos], 'name': 'cx'}) elif gate == Barrier: qb_pos = [qb.id for qr in cmd.qubits for qb in qr] self.qasm += "\nbarrier " @@ -161,22 +165,28 @@ def _store(self, cmd): for pos in qb_pos: qb_str += "q[{}], ".format(pos) self.qasm += qb_str[:-2] + ";" + self._json.append({'qubits': qb_pos, 'name': 'barrier'}) elif isinstance(gate, (Rx, Ry, Rz)): assert get_control_count(cmd) == 0 qb_pos = cmd.qubits[0][0].id u_strs = {'Rx': 'u3({}, -pi/2, pi/2)', 'Ry': 'u3({}, 0, 0)', 'Rz': 'u1({})'} - gate = u_strs[str(gate)[0:2]].format(gate.angle) - self.qasm += "\n{} q[{}];".format(gate, qb_pos) - else: + u_name = {'Rx': 'u3', 'Ry': 'u3', + 'Rz': 'u1'} + u_angle = {'Rx': [gate.angle, -math.pi/2, math.pi/2], 'Ry': [gate.angle, 0, 0], + 'Rz': [gate.angle]} + gate_qasm = u_strs[str(gate)[0:2]].format(gate.angle) + gate_name=u_name[str(gate)[0:2]] + params= u_angle[str(gate)[0:2]] + self.qasm += "\n{} q[{}];".format(gate_qasm, qb_pos) + self._json.append({'qubits': [qb_pos], 'name': gate_name,'params': params}) + elif gate == H: assert get_control_count(cmd) == 0 - if str(gate) in self._gate_names: - gate_str = self._gate_names[str(gate)] - else: - gate_str = str(gate).lower() - qb_pos = cmd.qubits[0][0].id - self.qasm += "\n{} q[{}];".format(gate_str, qb_pos) + self.qasm += "\nu2(0,pi/2) q[{}];".format(qb_pos) + self._json.append({'qubits': [qb_pos], 'name': 'u2','params': [0, 3.141592653589793]}) + else: + raise Exception('Command not authorized. You should run the circuit with the appropriate ibm setup.') def _logical_to_physical(self, qb_id): """ @@ -198,6 +208,8 @@ def _logical_to_physical(self, qb_id): def get_probabilities(self, qureg): """ Return the list of basis states with corresponding probabilities. + If input qureg is a subset of the register used for the experiment, + then returns the projected probabilities over the other states. The measured bits are ordered according to the supplied quantum register, i.e., the left-most bit in the state-string corresponds to @@ -212,7 +224,7 @@ def get_probabilities(self, qureg): Returns: probability_dict (dict): Dictionary mapping n-bit strings to - probabilities. + probabilities. Raises: RuntimeError: If no data is available (i.e., if the circuit has @@ -223,68 +235,70 @@ def get_probabilities(self, qureg): raise RuntimeError("Please, run the circuit first!") probability_dict = dict() - for state in self._probabilities: mapped_state = ['0'] * len(qureg) for i in range(len(qureg)): mapped_state[i] = state[self._logical_to_physical(qureg[i].id)] probability = self._probabilities[state] - probability_dict["".join(mapped_state)] = probability - + mapped_state = "".join(mapped_state) + if mapped_state not in probability_dict: + probability_dict[mapped_state] = probability + else: + probability_dict[mapped_state] += probability return probability_dict def _run(self): """ Run the circuit. - Send the circuit via the IBM API (JSON QASM) using the provided user - data / ask for username & password. + Send the circuit via a non documented IBM API (using JSON written + circuits) using the provided user data / ask for the user token. """ # finally: add measurements (no intermediate measurements are allowed) for measured_id in self._measured_ids: qb_loc = self.main_engine.mapper.current_mapping[measured_id] self.qasm += "\nmeasure q[{}] -> c[{}];".format(qb_loc, qb_loc) - + self._json.append({'qubits': [qb_loc], 'name': 'measure','memory':[qb_loc]}) # return if no operations / measurements have been performed. if self.qasm == "": return - - max_qubit_id = max(self._allocated_qubits) + max_qubit_id = max(self._allocated_qubits) + 1 qasm = ("\ninclude \"qelib1.inc\";\nqreg q[{nq}];\ncreg c[{nq}];" + - self.qasm).format(nq=max_qubit_id + 1) + self.qasm).format(nq=max_qubit_id) info = {} - info['qasms'] = [{'qasm': qasm}] + info['json']=self._json + info['nq']=max_qubit_id + info['shots'] = self._num_runs - info['maxCredits'] = 5 + info['maxCredits'] = 10 info['backend'] = {'name': self.device} - info = json.dumps(info) - try: if self._retrieve_execution is None: res = send(info, device=self.device, - user=self._user, password=self._password, - shots=self._num_runs, + token=self._token, num_retries=self._num_retries, interval=self._interval, verbose=self._verbose) else: - res = retrieve(device=self.device, user=self._user, - password=self._password, + res = retrieve(device=self.device, + token=self._token, jobid=self._retrieve_execution, num_retries=self._num_retries, interval=self._interval, verbose=self._verbose) - counts = res['data']['counts'] # Determine random outcome P = random.random() p_sum = 0. measured = "" + length=len(self._measured_ids) for state in counts: probability = counts[state] * 1. / self._num_runs - state = list(reversed(state)) - state = "".join(state) + state="{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 == "": @@ -322,9 +336,3 @@ def receive(self, command_list): else: self._run() self._reset() - - """ - Mapping of gate names from our gate objects to the IBM QASM representation. - """ - _gate_names = {str(Tdag): "tdg", - str(Sdag): "sdg"} diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index d713b17fa..98751bf90 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -13,83 +13,350 @@ # limitations under the License. # helpers to run the jsonified gate sequence on ibm quantum experience server -# api documentation is at https://qcwi-staging.mybluemix.net/explorer/ -import requests +# api documentation does not exist and has to be deduced from the qiskit code source +# at: https://github.com/Qiskit/qiskit-ibmq-provider + import getpass -import json -import signal -import sys import time +import signal +import requests from requests.compat import urljoin +from requests import Session + +_AUTH_API_URL = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' +_API_URL = 'https://api.quantum-computing.ibm.com/api/' + +# TODO: call to get the API version automatically +CLIENT_APPLICATION = 'ibmqprovider/0.4.4' + + +class IBMQ(Session): + """ + Manage a session between ProjectQ and the IBMQ web API. + """ + + def __init__(self, **kwargs): + super(IBMQ, self).__init__(**kwargs) # Python 2 compatibility + self.backends = dict() + self.timeout = 5.0 + + def get_list_devices(self, verbose=False): + """ + Get the list of available IBM backends with their properties + + Args: + verbose (bool): print the returned dictionnary if True + + Returns: + (dict) backends dictionary by name device, containing the qubit + size 'nq', the coupling map 'coupling_map' as well as the + device version 'version' + """ + list_device_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' + argument = {'allow_redirects': True, 'timeout': (self.timeout, None)} + request = super(IBMQ, self).get(urljoin(_API_URL, list_device_url), + **argument) + request.raise_for_status() + r_json = request.json() + self.backends = dict() + for el in r_json: + self.backends[el['backend_name']] = { + 'nq': el['n_qubits'], + 'coupling_map': el['coupling_map'], + 'version': el['backend_version'] + } + + if verbose: + print('- List of IBMQ devices available:') + print(self.backends) + return self.backends + + def is_online(self, device): + """ + Check if the device is in the list of available IBM backends. + + Args: + device (str): name of the device to check + + Returns: + (bool) True if device is available, False otherwise + """ + return device in self.backends + + def can_run_experiment(self, info, device): + """ + Check if the device is big enough to run the code. + + Args: + info (dict): dictionary sent by the backend containing the code to + run + device (str): name of the ibm device to use + + Returns: + (tuple): (bool) True if device is big enough, False otherwise + (int) maximum number of qubit available on the device + (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 _authenticate(self, token=None): + """ + Args: + token (str): IBM quantum experience user API token. + """ + if token is None: + token = getpass.getpass(prompt="IBM QE token > ") + if len(token) == 0: + raise Exception('Error with the IBM QE token') + self.headers.update({'X-Qx-Client-Application': CLIENT_APPLICATION}) + args = { + 'data': None, + 'json': { + 'apiToken': token + }, + 'timeout': (self.timeout, None) + } + request = super(IBMQ, self).post(_AUTH_API_URL, **args) + request.raise_for_status() + r_json = request.json() + self.params.update({'access_token': r_json['id']}) + + def _run(self, info, device): + post_job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs' + shots = info['shots'] + n_classical_reg = info['nq'] + n_qubits = self.backends[device]['nq'] + version = self.backends[device]['version'] + instructions = info['json'] + maxcredit = info['maxCredits'] + c_label = [] + q_label = [] + for i in range(n_classical_reg): + c_label.append(['c', i]) + for i in range(n_qubits): + q_label.append(['q', i]) + experiment = [{ + 'header': { + 'qreg_sizes': [['q', n_qubits]], + 'n_qubits': n_qubits, + 'memory_slots': n_classical_reg, + 'creg_sizes': [['c', n_classical_reg]], + 'clbit_labels': c_label, + 'qubit_labels': q_label, + 'name': 'circuit0' + }, + 'config': { + 'n_qubits': n_qubits, + 'memory_slots': n_classical_reg + }, + 'instructions': instructions + }] + # Note: qobj_id is not necessary in projectQ, so fixed string for now + argument = { + 'data': None, + 'json': { + 'qObject': { + 'type': 'QASM', + 'schema_version': '1.1.0', + 'config': { + 'shots': shots, + 'max_credits': maxcredit, + 'n_qubits': n_qubits, + 'memory_slots': n_classical_reg, + 'memory': False, + 'parameter_binds': [] + }, + 'experiments': experiment, + 'header': { + 'backend_version': version, + 'backend_name': device + }, + 'qobj_id': 'e72443f5-7752-4e32-9ac8-156f1f3fee18' + }, + 'backend': { + 'name': device + }, + 'shots': shots + }, + 'timeout': (self.timeout, None) + } + request = super(IBMQ, self).post(urljoin(_API_URL, post_job_url), + **argument) + request.raise_for_status() + r_json = request.json() + execution_id = r_json["id"] + return execution_id + + def _get_result(self, + device, + execution_id, + num_retries=3000, + interval=1, + verbose=False): + + job_status_url = ('Network/ibm-q/Groups/open/Projects/main/Jobs/' + + execution_id) + if verbose: + print("Waiting for results. [Job ID: {}]".format(execution_id)) -_api_url = 'https://quantumexperience.ng.bluemix.net/api/' + original_sigint_handler = signal.getsignal(signal.SIGINT) + + def _handle_sigint_during_get_result(*_): + raise Exception( + "Interrupted. The ID of your submitted job is {}.".format( + execution_id)) + + try: + signal.signal(signal.SIGINT, _handle_sigint_during_get_result) + for retries in range(num_retries): + + argument = { + 'allow_redirects': True, + 'timeout': (self.timeout, None) + } + request = super(IBMQ, + self).get(urljoin(_API_URL, job_status_url), + **argument) + request.raise_for_status() + r_json = request.json() + if r_json['status'] == 'COMPLETED': + return r_json['qObjectResult']['results'][0] + if r_json['status'] != 'RUNNING': + raise Exception("Error while running the code: {}.".format( + r_json['status'])) + time.sleep(interval) + if self.is_online(device) and retries % 60 == 0: + self.get_list_devices() + if not self.is_online(device): + raise DeviceOfflineError( + "Device went offline. The ID of " + "your submitted job is {}.".format(execution_id)) + + finally: + if original_sigint_handler is not None: + signal.signal(signal.SIGINT, original_sigint_handler) + + raise Exception("Timeout. The ID of your submitted job is {}.".format( + execution_id)) + + +class DeviceTooSmall(Exception): + pass class DeviceOfflineError(Exception): pass -def is_online(device): - url = 'Backends/{}/queue/status'.format(device) - r = requests.get(urljoin(_api_url, url)) - return r.json()['state'] +def show_devices(token=None, verbose=False): + """ + Access the list of available devices and their properties (ex: for setup + configuration) + Args: + token (str): IBM quantum experience user API token. + verbose (bool): If True, additional information is printed + + Returns: + (list) list of available devices and their properties + """ + ibmq_session = IBMQ() + ibmq_session._authenticate(token=token) + return ibmq_session.get_list_devices(verbose=verbose) -def retrieve(device, user, password, jobid, num_retries=3000, - interval=1, verbose=False): + +def retrieve(device, + token, + jobid, + num_retries=3000, + interval=1, + verbose=False): """ Retrieves a previously run job by its ID. Args: device (str): Device on which the code was run / is running. - user (str): IBM quantum experience user (e-mail) - password (str): IBM quantum experience password + token (str): IBM quantum experience user API token. jobid (str): Id of the job to retrieve + + Returns: + (dict) result form the IBMQ server """ - user_id, access_token = _authenticate(user, password) - res = _get_result(device, jobid, access_token, num_retries=num_retries, - interval=interval, verbose=verbose) + 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) return res -def send(info, device='sim_trivial_2', user=None, password=None, - shots=1, 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. Args: - info: Contains QASM representation of the circuit to run. - device (str): Either 'simulator', 'ibmqx4', or 'ibmqx5'. - user (str): IBM quantum experience user. - password (str): IBM quantum experience user password. + info(dict): Contains representation of the circuit to run. + device (str): name of the ibm device. Simulator chosen by default + token (str): IBM quantum experience user API token. shots (int): Number of runs of the same circuit to collect statistics. verbose (bool): If True, additional information is printed, such as measurement statistics. Otherwise, the backend simply registers one measurement result (same behavior as the projectq Simulator). - """ - try: - # check if the device is online - if device in ['ibmqx4', 'ibmqx5']: - online = is_online(device) - if not online: - print("The device is offline (for maintenance?). Use the " - "simulator instead or try again later.") - raise DeviceOfflineError("Device is offline.") + Returns: + (dict) result form the IBMQ server + """ + try: + ibmq_session = IBMQ() + # Shots argument deprecated, as already + if shots is not None: + info['shots'] = shots if verbose: print("- Authenticating...") - user_id, access_token = _authenticate(user, password) + if token is not None: + print('user API token: ' + token) + ibmq_session._authenticate(token) + + # check if the device is online + ibmq_session.get_list_devices(verbose) + online = ibmq_session.is_online(device) + if not online: + print("The device is offline (for maintenance?). Use the " + "simulator instead or try again later.") + raise DeviceOfflineError("Device is offline.") + + # check if the device has enough qubit to run the code + runnable, qmax, qneeded = ibmq_session.can_run_experiment(info, device) + if not runnable: + print( + ("The device is too small ({} qubits available) for the code " + + "requested({} qubits needed) Try to look for another " + + "device with more qubits").format(qmax, qneeded)) + raise DeviceTooSmall("Device is too small.") if verbose: - print("- Running code: {}".format( - json.loads(info)['qasms'][0]['qasm'])) - execution_id = _run(info, device, user_id, access_token, shots) + print("- Running code: {}".format(info)) + execution_id = ibmq_session._run(info, device) if verbose: print("- Waiting for results...") - res = _get_result(device, execution_id, access_token, - num_retries=num_retries, - interval=interval, verbose=verbose) + res = ibmq_session._get_result(device, + execution_id, + num_retries=num_retries, + interval=interval, + verbose=verbose) if verbose: print("- Done.") return res @@ -102,93 +369,3 @@ def send(info, device='sim_trivial_2', user=None, password=None, except KeyError as err: print("- Failed to parse response:") print(err) - - -def _authenticate(email=None, password=None): - """ - :param email: - :param password: - :return: - """ - if email is None: - try: - input_fun = raw_input - except NameError: - input_fun = input - email = input_fun('IBM QE user (e-mail) > ') - if password is None: - password = getpass.getpass(prompt='IBM QE password > ') - - r = requests.post(urljoin(_api_url, 'users/login'), - data={"email": email, "password": password}) - r.raise_for_status() - - json_data = r.json() - user_id = json_data['userId'] - access_token = json_data['id'] - - return user_id, access_token - - -def _run(qasm, device, user_id, access_token, shots): - suffix = 'Jobs' - - r = requests.post(urljoin(_api_url, suffix), - data=qasm, - params={"access_token": access_token, - "deviceRunType": device, - "fromCache": "false", - "shots": shots}, - headers={"Content-Type": "application/json"}) - r.raise_for_status() - - r_json = r.json() - execution_id = r_json["id"] - return execution_id - - -def _get_result(device, execution_id, access_token, num_retries=3000, - interval=1, verbose=False): - suffix = 'Jobs/{execution_id}'.format(execution_id=execution_id) - status_url = urljoin(_api_url, 'Backends/{}/queue/status'.format(device)) - - if verbose: - print("Waiting for results. [Job ID: {}]".format(execution_id)) - - original_sigint_handler = signal.getsignal(signal.SIGINT) - - def _handle_sigint_during_get_result(*_): - raise Exception("Interrupted. The ID of your submitted job is {}." - .format(execution_id)) - - try: - signal.signal(signal.SIGINT, _handle_sigint_during_get_result) - - for retries in range(num_retries): - r = requests.get(urljoin(_api_url, suffix), - params={"access_token": access_token}) - r.raise_for_status() - r_json = r.json() - if 'qasms' in r_json: - qasm = r_json['qasms'][0] - if 'result' in qasm and qasm['result'] is not None: - return qasm['result'] - time.sleep(interval) - if device in ['ibmqx4', 'ibmqx5'] and retries % 60 == 0: - r = requests.get(status_url) - r_json = r.json() - if 'state' in r_json and not r_json['state']: - raise DeviceOfflineError("Device went offline. The ID of " - "your submitted job is {}." - .format(execution_id)) - if verbose and 'lengthQueue' in r_json: - print("Currently there are {} jobs queued for execution " - "on {}." - .format(r_json['lengthQueue'], device)) - - finally: - if original_sigint_handler is not None: - signal.signal(signal.SIGINT, original_sigint_handler) - - raise Exception("Timeout. The ID of your submitted job is {}." - .format(execution_id)) diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 6162fa618..eb56b1ee4 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.backends._ibm_http_client._ibm.py.""" import json @@ -28,24 +27,31 @@ def no_requests(monkeypatch): monkeypatch.delattr("requests.sessions.Session.request") -_api_url = 'https://quantumexperience.ng.bluemix.net/api/' -_api_url_status = 'https://quantumexperience.ng.bluemix.net/api/' +_API_URL = 'https://api.quantum-computing.ibm.com/api/' +_AUTH_API_URL = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' def test_send_real_device_online_verbose(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}]} - json_qasm = json.dumps(qasms) + json_qasm = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + '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_qasm]) + json_body = ''.join([name_item, json.dumps(json_qasm)]) json_data = ''.join(['{', json_body, '}']) shots = 1 device = "ibmqx4" - json_data_run = ''.join(['{"qasm":', json_qasm, '}']) - execution_id = 3 + execution_id = '3' result_ready = [False] result = "my_result" request_num = [0] # To assert correct order of calls @@ -70,24 +76,39 @@ def raise_for_status(self): pass # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' - if (args[0] == urljoin(_api_url_status, status_url) and - (request_num[0] == 0 or request_num[0] == 3)): + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' + if (args[1] == urljoin(_API_URL, status_url) + and (request_num[0] == 1 or request_num[0] == 4)): request_num[0] += 1 - return MockResponse({"state": True}, 200) + connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{ + 'backend_name': 'ibmqx4', + 'coupling_map': connections, + 'backend_version': '0.1.547', + 'n_qubits': 32 + }], 200) # Getting result - elif (args[0] == urljoin(_api_url, - "Jobs/{execution_id}".format(execution_id=execution_id)) and - kwargs["params"]["access_token"] == access_token and not - result_ready[0] and request_num[0] == 3): + elif (args[1] == urljoin( + _API_URL, + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}". + format(execution_id=execution_id)) and not result_ready[0] + and request_num[0] == 3): result_ready[0] = True - return MockResponse({"status": {"id": "NotDone"}}, 200) - elif (args[0] == urljoin(_api_url, - "Jobs/{execution_id}".format(execution_id=execution_id)) and - kwargs["params"]["access_token"] == access_token and - result_ready[0] and request_num[0] == 4): - print("state ok") - return MockResponse({"qasms": [{"result": result}]}, 200) + request_num[0] += 1 + return MockResponse({"status": "RUNNING"}, 200) + elif (args[1] == urljoin( + _API_URL, + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}". + format(execution_id=execution_id)) and result_ready[0] + and request_num[0] == 5): + return MockResponse( + { + 'qObjectResult': { + "results": [result] + }, + "status": "COMPLETED" + }, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -107,49 +128,69 @@ def json(self): def raise_for_status(self): pass + jobs_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs' # Authentication - if (args[0] == urljoin(_api_url, "users/login") and - kwargs["data"]["email"] == email and - kwargs["data"]["password"] == password and - request_num[0] == 1): + if (args[1] == _AUTH_API_URL and kwargs["json"]["apiToken"] == token + and request_num[0] == 0): request_num[0] += 1 return MockPostResponse({"userId": user_id, "id": access_token}) # Run code - elif (args[0] == urljoin(_api_url, "Jobs") and - kwargs["data"] == json_qasm and - kwargs["params"]["access_token"] == access_token and - kwargs["params"]["deviceRunType"] == device and - kwargs["params"]["fromCache"] == "false" and - kwargs["params"]["shots"] == shots and - kwargs["headers"]["Content-Type"] == "application/json" and - request_num[0] == 2): + elif (args[1] == urljoin(_API_URL, jobs_url) and kwargs["data"] is None + and kwargs["json"]["backend"]["name"] == device + and kwargs["json"]["qObject"]['config']['shots'] == shots + and request_num[0] == 2): request_num[0] += 1 return MockPostResponse({"id": execution_id}) - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) - # Patch login data - password = 12345 - email = "test@projectq.ch" - monkeypatch.setitem(__builtins__, "input", lambda x: email) - monkeypatch.setitem(__builtins__, "raw_input", lambda x: email) + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) def user_password_input(prompt): - if prompt == "IBM QE password > ": - return password + if prompt == "IBM QE token > ": + return token monkeypatch.setattr("getpass.getpass", user_password_input) # Code to test: res = _ibm_http_client.send(json_qasm, device="ibmqx4", - user=None, password=None, - shots=shots, verbose=True) - print(res) + token=None, + shots=shots, + verbose=True) assert res == result + json_qasm['nq'] = 40 + request_num[0] = 0 + with pytest.raises(_ibm_http_client.DeviceTooSmall): + res = _ibm_http_client.send(json_qasm, + device="ibmqx4", + token=None, + shots=shots, + verbose=True) + + +def test_no_password_given(monkeypatch): + token = '' + json_qasm = '' + + def user_password_input(prompt): + if prompt == "IBM QE token > ": + return token + + monkeypatch.setattr("getpass.getpass", user_password_input) + + with pytest.raises(Exception): + res = _ibm_http_client.send(json_qasm, + device="ibmqx4", + token=None, + shots=1, + verbose=True) def test_send_real_device_offline(monkeypatch): + token = '12345' + access_token = "access" + user_id = 2016 + def mocked_requests_get(*args, **kwargs): class MockResponse: def __init__(self, json_data, status_code): @@ -159,22 +200,63 @@ def __init__(self, json_data, status_code): def json(self): return self.json_data - # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' - if args[0] == urljoin(_api_url_status, status_url): - return MockResponse({"state": False}, 200) - monkeypatch.setattr("requests.get", mocked_requests_get) + def raise_for_status(self): + pass + + # Accessing status of device. Return offline. + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' + if args[1] == urljoin(_API_URL, status_url): + return MockResponse({}, 200) + + def mocked_requests_post(*args, **kwargs): + class MockRequest: + def __init__(self, body="", url=""): + self.body = body + self.url = url + + class MockPostResponse: + def __init__(self, json_data, text=" "): + self.json_data = json_data + self.text = text + self.request = MockRequest() + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + # Authentication + if (args[1] == _AUTH_API_URL and kwargs["json"]["apiToken"] == token): + return MockPostResponse({"userId": user_id, "id": access_token}) + + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) + shots = 1 - json_qasm = "my_json_qasm" + token = '12345' + json_qasm = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } name = 'projectq_test' with pytest.raises(_ibm_http_client.DeviceOfflineError): _ibm_http_client.send(json_qasm, device="ibmqx4", - user=None, password=None, - shots=shots, verbose=True) + token=token, + shots=shots, + verbose=True) -def test_send_that_errors_are_caught(monkeypatch): +def test_show_device(monkeypatch): + access_token = "access" + user_id = 2016 + class MockResponse: def __init__(self, json_data, status_code): self.json_data = json_data @@ -183,123 +265,191 @@ def __init__(self, json_data, status_code): def json(self): return self.json_data + def raise_for_status(self): + pass + def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' - if args[0] == urljoin(_api_url_status, status_url): - return MockResponse({"state": True}, 200) + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' + if args[1] == urljoin(_API_URL, status_url): + connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{ + 'backend_name': 'ibmqx4', + 'coupling_map': connections, + 'backend_version': '0.1.547', + 'n_qubits': 32 + }], 200) + + def mocked_requests_post(*args, **kwargs): + class MockRequest: + def __init__(self, body="", url=""): + self.body = body + self.url = url + + class MockPostResponse: + def __init__(self, json_data, text=" "): + self.json_data = json_data + self.text = text + self.request = MockRequest() + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + # Authentication + if (args[1] == _AUTH_API_URL and kwargs["json"]["apiToken"] == token): + return MockPostResponse({"userId": user_id, "id": access_token}) + + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) + # Patch login data + token = '12345' + + def user_password_input(prompt): + if prompt == "IBM QE token > ": + return token + + monkeypatch.setattr("getpass.getpass", user_password_input) + assert _ibm_http_client.show_devices() == { + 'ibmqx4': { + 'coupling_map': {(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)}, + 'version': '0.1.547', + 'nq': 32 + } + } + + +def test_send_that_errors_are_caught(monkeypatch): + class MockResponse: + def __init__(self, json_data, status_code): + pass def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise requests.exceptions.HTTPError - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) # Patch login data - password = 12345 - email = "test@projectq.ch" - monkeypatch.setitem(__builtins__, "input", lambda x: email) - monkeypatch.setitem(__builtins__, "raw_input", lambda x: email) + token = '12345' def user_password_input(prompt): - if prompt == "IBM QE password > ": - return password + if prompt == "IBM QE token > ": + return token monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 - json_qasm = "my_json_qasm" + json_qasm = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", - user=None, password=None, - shots=shots, verbose=True) - + token=None, + shots=shots, + verbose=True) -def test_send_that_errors_are_caught2(monkeypatch): - def mocked_requests_get(*args, **kwargs): - class MockResponse: - def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code + token = '' + with pytest.raises(Exception): + _ibm_http_client.send(json_qasm, + device="ibmqx4", + token=None, + shots=shots, + verbose=True) - def json(self): - return self.json_data - # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' - if args[0] == urljoin(_api_url_status, status_url): - return MockResponse({"state": True}, 200) +def test_send_that_errors_are_caught2(monkeypatch): + class MockResponse: + def __init__(self, json_data, status_code): + pass def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise requests.exceptions.RequestException - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) # Patch login data - password = 12345 - email = "test@projectq.ch" - monkeypatch.setitem(__builtins__, "input", lambda x: email) - monkeypatch.setitem(__builtins__, "raw_input", lambda x: email) + token = '12345' def user_password_input(prompt): - if prompt == "IBM QE password > ": - return password + if prompt == "IBM QE token > ": + return token monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 - json_qasm = "my_json_qasm" + json_qasm = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", - user=None, password=None, - shots=shots, verbose=True) + token=None, + shots=shots, + verbose=True) def test_send_that_errors_are_caught3(monkeypatch): - def mocked_requests_get(*args, **kwargs): - class MockResponse: - def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code - - def json(self): - return self.json_data - - # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' - if args[0] == urljoin(_api_url_status, status_url): - return MockResponse({"state": True}, 200) + class MockResponse: + def __init__(self, json_data, status_code): + pass def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise KeyError - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) # Patch login data - password = 12345 - email = "test@projectq.ch" - monkeypatch.setitem(__builtins__, "input", lambda x: email) - monkeypatch.setitem(__builtins__, "raw_input", lambda x: email) + token = '12345' def user_password_input(prompt): - if prompt == "IBM QE password > ": - return password + if prompt == "IBM QE token > ": + return token monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 - json_qasm = "my_json_qasm" + json_qasm = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", - user=None, password=None, - shots=shots, verbose=True) + token=None, + shots=shots, + verbose=True) def test_timeout_exception(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}]} - json_qasm = json.dumps(qasms) + qasms = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } + json_qasm = qasms tries = [0] def mocked_requests_get(*args, **kwargs): @@ -314,14 +464,22 @@ def json(self): def raise_for_status(self): pass - # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' - if args[0] == urljoin(_api_url, status_url): - return MockResponse({"state": True}, 200) - job_url = 'Jobs/{}'.format("123e") - if args[0] == urljoin(_api_url, job_url): + # Accessing status of device. Return device info. + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' + if args[1] == urljoin(_API_URL, status_url): + connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{ + 'backend_name': 'ibmqx4', + 'coupling_map': connections, + 'backend_version': '0.1.547', + 'n_qubits': 32 + }], 200) + job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format( + "123e") + if args[1] == urljoin(_API_URL, job_url): tries[0] += 1 - return MockResponse({"noqasms": "not done"}, 200) + return MockResponse({"status": "RUNNING"}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -340,27 +498,28 @@ def json(self): def raise_for_status(self): pass - login_url = 'users/login' - if args[0] == urljoin(_api_url, login_url): + jobs_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs' + if args[1] == _AUTH_API_URL: return MockPostResponse({"userId": "1", "id": "12"}) - if args[0] == urljoin(_api_url, 'Jobs'): + if args[1] == urljoin(_API_URL, jobs_url): return MockPostResponse({"id": "123e"}) - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) + _ibm_http_client.time.sleep = lambda x: x with pytest.raises(Exception) as excinfo: _ibm_http_client.send(json_qasm, device="ibmqx4", - user="test", password="test", - shots=1, verbose=False) + token="test", + shots=1, + num_retries=10, + verbose=False) assert "123e" in str(excinfo.value) # check that job id is in exception assert tries[0] > 0 def test_retrieve_and_device_offline_exception(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}]} - json_qasm = json.dumps(qasms) request_num = [0] def mocked_requests_get(*args, **kwargs): @@ -376,15 +535,41 @@ def raise_for_status(self): pass # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' - if args[0] == urljoin(_api_url, status_url) and request_num[0] < 2: - return MockResponse({"state": True, "lengthQueue": 10}, 200) - elif args[0] == urljoin(_api_url, status_url): - return MockResponse({"state": False}, 200) - job_url = 'Jobs/{}'.format("123e") - if args[0] == urljoin(_api_url, job_url): + 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") + if args[1] == urljoin(_API_URL, job_url): + request_num[0] += 1 + return MockResponse( + { + "status": "RUNNING", + 'iteration': request_num[0] + }, 200) + if args[1] == urljoin(_API_URL, err_url): request_num[0] += 1 - return MockResponse({"noqasms": "not done"}, 200) + return MockResponse( + { + "status": "TERMINATED", + 'iteration': request_num[0] + }, 400) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -403,22 +588,26 @@ def json(self): def raise_for_status(self): pass - login_url = 'users/login' - if args[0] == urljoin(_api_url, login_url): + if args[1] == _AUTH_API_URL: return MockPostResponse({"userId": "1", "id": "12"}) - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) + _ibm_http_client.time.sleep = lambda x: x with pytest.raises(_ibm_http_client.DeviceOfflineError): _ibm_http_client.retrieve(device="ibmqx4", - user="test", password="test", - jobid="123e") + token="test", + jobid="123e", + num_retries=200) + with pytest.raises(Exception): + _ibm_http_client.retrieve(device="ibmqx4", + token="test", + jobid="123ee", + num_retries=200) def test_retrieve(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}]} - json_qasm = json.dumps(qasms) request_num = [0] def mocked_requests_get(*args, **kwargs): @@ -434,16 +623,28 @@ def raise_for_status(self): pass # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' - if args[0] == urljoin(_api_url, status_url): - return MockResponse({"state": True}, 200) - job_url = 'Jobs/{}'.format("123e") - if args[0] == urljoin(_api_url, job_url) and request_num[0] < 1: + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' + if args[1] == urljoin(_API_URL, status_url): + return MockResponse([{ + 'backend_name': 'ibmqx4', + 'coupling_map': None, + 'backend_version': '0.1.547', + 'n_qubits': 32 + }], 200) + job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/{}'.format( + "123e") + if args[1] == urljoin(_API_URL, job_url) and request_num[0] < 1: request_num[0] += 1 - return MockResponse({"noqasms": "not done"}, 200) - elif args[0] == urljoin(_api_url, job_url): - return MockResponse({"qasms": [{'qasm': 'qasm', - 'result': 'correct'}]}, 200) + return MockResponse({"status": "RUNNING"}, 200) + elif args[1] == urljoin(_API_URL, job_url): + return MockResponse( + { + "qObjectResult": { + 'qasm': 'qasm', + 'results': ['correct'] + }, + "status": "COMPLETED" + }, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -462,14 +663,14 @@ def json(self): def raise_for_status(self): pass - login_url = 'users/login' - if args[0] == urljoin(_api_url, login_url): + if args[1] == _AUTH_API_URL: return MockPostResponse({"userId": "1", "id": "12"}) - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) + _ibm_http_client.time.sleep = lambda x: x res = _ibm_http_client.retrieve(device="ibmqx4", - user="test", password="test", + token="test", jobid="123e") assert res == 'correct' diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index df1652b7a..f6890d34c 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -11,27 +11,18 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.backends._ibm._ibm.py.""" import pytest -import json - -import projectq.setups.decompositions +import math +from projectq.setups import restrictedgateset from projectq import MainEngine from projectq.backends._ibm import _ibm -from projectq.cengines import (TagRemover, - LocalOptimizer, - AutoReplacer, - IBM5QubitMapper, - SwapAndCNOTFlipper, - DummyEngine, - DecompositionRuleSet) +from projectq.cengines import (BasicMapperEngine, DummyEngine) + from projectq.ops import (All, Allocate, Barrier, Command, Deallocate, Entangle, Measure, NOT, Rx, Ry, Rz, S, Sdag, T, Tdag, - X, Y, Z) - -from projectq.setups.ibm import ibmqx4_connections + X, Y, Z, H, CNOT) # Insure that no HTTP request can be made in all tests in this module @@ -40,31 +31,29 @@ def no_requests(monkeypatch): monkeypatch.delattr("requests.sessions.Session.request") -_api_url = 'https://quantumexperience.ng.bluemix.net/api/' -_api_url_status = 'https://quantumexperience.ng.bluemix.net/api/' - - -@pytest.mark.parametrize("single_qubit_gate, is_available", [ - (X, True), (Y, True), (Z, True), (T, True), (Tdag, True), (S, True), - (Sdag, True), (Allocate, True), (Deallocate, True), (Measure, True), - (NOT, True), (Rx(0.5), True), (Ry(0.5), True), (Rz(0.5), True), - (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, True), (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 @@ -83,14 +72,17 @@ def test_ibm_sent_error(monkeypatch): # patch send def mock_send(*args, **kwargs): raise TypeError - monkeypatch.setattr(_ibm, "send", mock_send) + monkeypatch.setattr(_ibm, "send", mock_send) backend = _ibm.IBMBackend(verbose=True) - eng = MainEngine(backend=backend, - engine_list=[IBM5QubitMapper(), - SwapAndCNOTFlipper(set())]) + mapper = BasicMapperEngine() + res = dict() + for i in range(4): + res[i] = i + mapper.current_mapping = res + eng = MainEngine(backend=backend, engine_list=[mapper]) qubit = eng.allocate_qubit() - X | qubit + Rx(math.pi) | qubit with pytest.raises(Exception): qubit[0].__del__() eng.flush() @@ -100,26 +92,80 @@ def mock_send(*args, **kwargs): eng.next_engine = dummy +def test_ibm_sent_error_2(monkeypatch): + backend = _ibm.IBMBackend(verbose=True) + mapper = BasicMapperEngine() + res = dict() + for i in range(4): + res[i] = i + mapper.current_mapping = res + eng = MainEngine(backend=backend, engine_list=[mapper]) + qubit = eng.allocate_qubit() + Rx(math.pi) | qubit + + with pytest.raises(Exception): + S | qubit # no setup to decompose S gate, so not accepted by the backend + dummy = DummyEngine() + dummy.is_last_engine = True + eng.next_engine = dummy + + def test_ibm_retrieve(monkeypatch): # patch send def mock_retrieve(*args, **kwargs): - return {'date': '2017-01-19T14:28:47.622Z', - 'data': {'time': 14.429004907608032, 'counts': {'00111': 396, - '00101': 27, - '00000': 601}, - 'qasm': ('...')}} + return { + 'data': { + 'counts': { + '0x0': 504, + '0x2': 8, + '0xc': 6, + '0xe': 482 + } + }, + 'header': { + 'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], + 'creg_sizes': [['c', 4]], + 'memory_slots': + 4, + 'n_qubits': + 32, + 'name': + 'circuit0', + 'qreg_sizes': [['q', 32]], + 'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3], + ['q', 4], ['q', 5], ['q', 6], ['q', 7], + ['q', 8], ['q', 9], ['q', 10], ['q', 11], + ['q', 12], ['q', 13], ['q', 14], ['q', 15], + ['q', 16], ['q', 17], ['q', 18], ['q', 19], + ['q', 20], ['q', 21], ['q', 22], ['q', 23], + ['q', 24], ['q', 25], ['q', 26], ['q', 27], + ['q', 28], ['q', 29], ['q', 30], ['q', 31]] + }, + 'metadata': { + 'measure_sampling': True, + 'method': 'statevector', + 'parallel_shots': 1, + 'parallel_state_update': 16 + }, + 'seed_simulator': 465435780, + 'shots': 1000, + 'status': 'DONE', + 'success': True, + 'time_taken': 0.0045786460000000005 + } + monkeypatch.setattr(_ibm, "retrieve", mock_retrieve) - backend = _ibm.IBMBackend(retrieve_execution="ab1s2") - rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - connectivity = set([(1, 2), (2, 4), (0, 2), (3, 2), (4, 3), (0, 1)]) - engine_list = [TagRemover(), - LocalOptimizer(10), - AutoReplacer(rule_set), - TagRemover(), - IBM5QubitMapper(), - SwapAndCNOTFlipper(connectivity), - LocalOptimizer(10)] - eng = MainEngine(backend=backend, engine_list=engine_list) + backend = _ibm.IBMBackend(retrieve_execution="ab1s2", num_runs=1000) + mapper = BasicMapperEngine() + res = dict() + for i in range(4): + res[i] = i + mapper.current_mapping = res + ibm_setup = [mapper] + setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry, Rz, H), + two_qubit_gates=(CNOT, )) + setup.extend(ibm_setup) + eng = MainEngine(backend=backend, engine_list=setup) unused_qubit = eng.allocate_qubit() qureg = eng.allocate_qureg(3) # entangle the qureg @@ -134,43 +180,135 @@ def mock_retrieve(*args, **kwargs): # run the circuit eng.flush() prob_dict = eng.backend.get_probabilities([qureg[0], qureg[2], qureg[1]]) - assert prob_dict['111'] == pytest.approx(0.38671875) - assert prob_dict['101'] == pytest.approx(0.0263671875) + assert prob_dict['000'] == pytest.approx(0.504) + assert prob_dict['111'] == pytest.approx(0.482) + assert prob_dict['011'] == pytest.approx(0.006) def test_ibm_backend_functional_test(monkeypatch): - correct_info = ('{"qasms": [{"qasm": "\\ninclude \\"qelib1.inc\\";' - '\\nqreg q[3];\\ncreg c[3];\\nh q[2];\\ncx q[2], q[0];' - '\\ncx q[2], q[1];\\ntdg q[2];\\nsdg q[2];' - '\\nbarrier q[2], q[0], q[1];' - '\\nu3(0.2, -pi/2, pi/2) q[2];\\nmeasure q[2] -> ' - 'c[2];\\nmeasure q[0] -> c[0];\\nmeasure q[1] -> c[1];"}]' - ', "shots": 1024, "maxCredits": 5, "backend": {"name": ' - '"simulator"}}') + correct_info = { + '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 json.loads(args[0]) == json.loads(correct_info) - return {'date': '2017-01-19T14:28:47.622Z', - 'data': {'time': 14.429004907608032, 'counts': {'00111': 396, - '00101': 27, - '00000': 601}, - 'qasm': ('...')}} + assert args[0] == correct_info + return { + 'data': { + 'counts': { + '0x0': 504, + '0x2': 8, + '0xc': 6, + '0xe': 482 + } + }, + 'header': { + 'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], + 'creg_sizes': [['c', 4]], + 'memory_slots': + 4, + 'n_qubits': + 32, + 'name': + 'circuit0', + 'qreg_sizes': [['q', 32]], + 'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3], + ['q', 4], ['q', 5], ['q', 6], ['q', 7], + ['q', 8], ['q', 9], ['q', 10], ['q', 11], + ['q', 12], ['q', 13], ['q', 14], ['q', 15], + ['q', 16], ['q', 17], ['q', 18], ['q', 19], + ['q', 20], ['q', 21], ['q', 22], ['q', 23], + ['q', 24], ['q', 25], ['q', 26], ['q', 27], + ['q', 28], ['q', 29], ['q', 30], ['q', 31]] + }, + 'metadata': { + 'measure_sampling': True, + 'method': 'statevector', + 'parallel_shots': 1, + 'parallel_state_update': 16 + }, + 'seed_simulator': 465435780, + 'shots': 1000, + 'status': 'DONE', + 'success': True, + 'time_taken': 0.0045786460000000005 + } + monkeypatch.setattr(_ibm, "send", mock_send) - backend = _ibm.IBMBackend(verbose=True) + backend = _ibm.IBMBackend(verbose=True, num_runs=1000) + import sys # no circuit has been executed -> raises exception with pytest.raises(RuntimeError): backend.get_probabilities([]) - rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - - engine_list = [TagRemover(), - LocalOptimizer(10), - AutoReplacer(rule_set), - TagRemover(), - IBM5QubitMapper(), - SwapAndCNOTFlipper(ibmqx4_connections), - LocalOptimizer(10)] - eng = MainEngine(backend=backend, engine_list=engine_list) + mapper = BasicMapperEngine() + res = dict() + for i in range(4): + res[i] = i + mapper.current_mapping = res + ibm_setup = [mapper] + setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry, Rz, H), + two_qubit_gates=(CNOT, ), + other_gates=(Barrier, )) + setup.extend(ibm_setup) + eng = MainEngine(backend=backend, engine_list=setup) + # 4 qubits circuit is run, but first is unused to test ability for + # get_probability to return the correct values for a subset of the total + # register unused_qubit = eng.allocate_qubit() qureg = eng.allocate_qureg(3) # entangle the qureg @@ -184,9 +322,21 @@ def mock_send(*args, **kwargs): All(Measure) | qureg # run the circuit eng.flush() - prob_dict = eng.backend.get_probabilities([qureg[0], qureg[2], qureg[1]]) - assert prob_dict['111'] == pytest.approx(0.38671875) - assert prob_dict['101'] == pytest.approx(0.0263671875) + prob_dict = eng.backend.get_probabilities([qureg[2], qureg[1]]) + assert prob_dict['00'] == pytest.approx(0.512) + assert prob_dict['11'] == pytest.approx(0.488) + result = "\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];" + if sys.version_info.major == 3: + result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];" + result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];" + else: + result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972451) q[1];" + result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(10.9955742876) q[1];" + result += "\nbarrier q[1], q[2], q[3];" + result += "\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];" + result += "\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];" + + assert eng.backend.get_qasm() == result with pytest.raises(RuntimeError): eng.backend.get_probabilities(eng.allocate_qubit()) diff --git a/projectq/cengines/_basicmapper.py b/projectq/cengines/_basicmapper.py index 4d4cef177..5fc0f9a81 100644 --- a/projectq/cengines/_basicmapper.py +++ b/projectq/cengines/_basicmapper.py @@ -81,3 +81,7 @@ def add_logical_id(command, old_tags=deepcopy(cmd.tags)): drop_engine_after(self) else: self.send([new_cmd]) + + def receive(self, command_list): + for cmd in command_list: + self._send_cmd_with_mapped_ids(cmd) diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index 7a2659a30..2c85749d2 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -11,12 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Contains a compiler engine to map to the 5-qubit IBM chip """ -from copy import deepcopy - import itertools from projectq.cengines import BasicMapperEngine @@ -39,8 +36,7 @@ class IBM5QubitMapper(BasicMapperEngine): without performing Swaps, the mapping procedure **raises an Exception**. """ - - def __init__(self): + def __init__(self, connections=None): """ Initialize an IBM 5-qubit mapper compiler engine. @@ -49,6 +45,16 @@ def __init__(self): BasicMapperEngine.__init__(self) self.current_mapping = dict() self._reset() + self._cmds = [] + self._interactions = dict() + + if connections is None: + #general connectivity easier for testing functions + self.connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), + (4, 3)]) + else: + self.connections = connections def is_available(self, cmd): """ @@ -67,17 +73,6 @@ def _reset(self): self._cmds = [] self._interactions = dict() - def _is_cnot(self, cmd): - """ - Check if the command corresponds to a CNOT (controlled NOT gate). - - Args: - cmd (Command): Command to check whether it is a controlled NOT - gate. - """ - return (isinstance(cmd.gate, NOT.__class__) and - get_control_count(cmd) == 1) - def _determine_cost(self, mapping): """ Determines the cost of the circuit with the given mapping. @@ -90,15 +85,15 @@ def _determine_cost(self, mapping): Cost measure taking into account CNOT directionality or None if the circuit cannot be executed given the mapping. """ - from projectq.setups.ibm import ibmqx4_connections as connections + cost = 0 for tpl in self._interactions: ctrl_id = tpl[0] target_id = tpl[1] ctrl_pos = mapping[ctrl_id] target_pos = mapping[target_id] - if not (ctrl_pos, target_pos) in connections: - if (target_pos, ctrl_pos) in connections: + if not (ctrl_pos, target_pos) in self.connections: + if (target_pos, ctrl_pos) in self.connections: cost += self._interactions[tpl] else: return None @@ -114,20 +109,22 @@ def _run(self): the mapping was already determined but more CNOTs get sent down the pipeline. """ - if (len(self.current_mapping) > 0 and - max(self.current_mapping.values()) > 4): + if (len(self.current_mapping) > 0 + and max(self.current_mapping.values()) > 4): raise RuntimeError("Too many qubits allocated. The IBM Q " "device supports at most 5 qubits and no " "intermediate measurements / " "reallocations.") if len(self._interactions) > 0: - logical_ids = [qbid for qbid in self.current_mapping] + logical_ids = list(self.current_mapping) best_mapping = self.current_mapping best_cost = None for physical_ids in itertools.permutations(list(range(5)), len(logical_ids)): - mapping = {logical_ids[i]: physical_ids[i] - for i in range(len(logical_ids))} + mapping = { + logical_ids[i]: physical_ids[i] + for i in range(len(logical_ids)) + } new_cost = self._determine_cost(mapping) if new_cost is not None: if best_cost is None or new_cost < best_cost: @@ -153,7 +150,7 @@ def _store(self, cmd): """ if not cmd.gate == FlushGate(): target = cmd.qubits[0][0].id - if self._is_cnot(cmd): + if _is_cnot(cmd): # CNOT encountered ctrl = cmd.control_qubits[0].id if not (ctrl, target) in self._interactions: @@ -187,3 +184,15 @@ def receive(self, command_list): if isinstance(cmd.gate, FlushGate): self._run() self._reset() + + +def _is_cnot(cmd): + """ + Check if the command corresponds to a CNOT (controlled NOT gate). + + Args: + cmd (Command): Command to check whether it is a controlled NOT + gate. + """ + return (isinstance(cmd.gate, NOT.__class__) + and get_control_count(cmd) == 1) diff --git a/projectq/cengines/_ibm5qubitmapper_test.py b/projectq/cengines/_ibm5qubitmapper_test.py index 5c4c4c4da..ea6d383b6 100755 --- a/projectq/cengines/_ibm5qubitmapper_test.py +++ b/projectq/cengines/_ibm5qubitmapper_test.py @@ -11,14 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.cengines._ibm5qubitmapper.py.""" import pytest from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import H, CNOT, X, Measure, All +from projectq.ops import H, CNOT, All from projectq.cengines import _ibm5qubitmapper, SwapAndCNOTFlipper from projectq.backends import IBMBackend @@ -28,15 +27,20 @@ def test_ibm5qubitmapper_is_available(monkeypatch): # Test that IBM5QubitMapper calls IBMBackend if gate is available. def mock_send(*args, **kwargs): return "Yes" + monkeypatch.setattr(_ibm5qubitmapper.IBMBackend, "is_available", mock_send) mapper = _ibm5qubitmapper.IBM5QubitMapper() assert mapper.is_available("TestCommand") == "Yes" def test_ibm5qubitmapper_invalid_circuit(): + connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper()]) + eng = MainEngine( + backend=backend, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) + ]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -51,9 +55,13 @@ def test_ibm5qubitmapper_invalid_circuit(): def test_ibm5qubitmapper_valid_circuit1(): + connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper()]) + eng = MainEngine( + backend=backend, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) + ]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -70,9 +78,13 @@ def test_ibm5qubitmapper_valid_circuit1(): def test_ibm5qubitmapper_valid_circuit2(): + connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper()]) + eng = MainEngine( + backend=backend, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) + ]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -89,6 +101,7 @@ def test_ibm5qubitmapper_valid_circuit2(): def test_ibm5qubitmapper_valid_circuit2_ibmqx4(): + connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) class FakeIBMBackend(IBMBackend): @@ -99,8 +112,11 @@ class FakeIBMBackend(IBMBackend): fake.is_available = backend.is_available backend.is_last_engine = True - eng = MainEngine(backend=fake, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper()]) + eng = MainEngine( + backend=fake, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) + ]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -119,9 +135,12 @@ class FakeIBMBackend(IBMBackend): def test_ibm5qubitmapper_optimizeifpossible(): backend = DummyEngine(save_commands=True) connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) - eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper(), - SwapAndCNOTFlipper(connectivity)]) + eng = MainEngine( + backend=backend, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity), + SwapAndCNOTFlipper(connectivity) + ]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -158,8 +177,10 @@ def test_ibm5qubitmapper_toomanyqubits(): backend = DummyEngine(save_commands=True) connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper(), - SwapAndCNOTFlipper(connectivity)]) + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(), + SwapAndCNOTFlipper(connectivity) + ]) qubits = eng.allocate_qureg(6) All(H) | qubits CNOT | (qubits[0], qubits[1]) diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index a5fb2c802..acedeed00 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -11,46 +11,116 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +""" +Defines a setup allowing to compile code for the IBM quantum chips: +->Any 5 qubit devices +->the ibmq online simulator +->the melbourne 15 qubit device +It provides the `engine_list` for the `MainEngine' based on the requested +device. Decompose the circuit into a Rx/Ry/Rz/H/CNOT gate set that will be +translated in the backend in the U1/U2/U3/CX gate set. """ -Defines a setup useful for the IBM QE chip with 5 qubits. -It provides the `engine_list` for the `MainEngine`, and contains an -AutoReplacer with most of the gate decompositions of ProjectQ, among others -it includes: +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.backends._ibm._ibm_http_client import show_devices - * Controlled z-rotations --> Controlled NOTs and single-qubit rotations - * Toffoli gate --> CNOT and single-qubit gates - * m-Controlled global phases --> (m-1)-controlled phase-shifts - * Global phases --> ignore - * (controlled) Swap gates --> CNOTs and Toffolis - * Arbitrary single qubit gates --> Rz and Ry - * Controlled arbitrary single qubit gates --> Rz, Ry, and CNOT gates -Moreover, it contains `LocalOptimizers` and a custom mapper for the CNOT -gates. +def get_engine_list(token=None, device=None): + # Access to the hardware properties via show_devices + # Can also be extended to take into account gate fidelities, new available + # gate, etc.. + devices = show_devices(token) + ibm_setup = [] + if device not in devices: + 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) + ] + elif device == 'ibmq_qasm_simulator': + # The 32 qubit online simulator doesn't need a specific mapping for + # gates. Can also run wider gateset but this setup keep the + # restrictedgateset setup for coherence + mapper = BasicMapperEngine() + # Note: Manual Mapper doesn't work, because its map is updated only if + # gates are applied if gates in the register are not used, then it + # will lead to state errors + res = dict() + for i in range(devices[device]['nq']): + res[i] = i + mapper.current_mapping = res + ibm_setup = [mapper] + elif device == 'ibmq_16_melbourne': + # Only 15 qubits available on this ibmqx2 unit(in particular qubit 7 + # on the grid), therefore need custom grid mapping + grid_to_physical = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 15, + 8: 14, + 9: 13, + 10: 12, + 11: 11, + 12: 10, + 13: 9, + 14: 8, + 15: 7 + } + coupling_map = devices[device]['coupling_map'] + coupling_map = list2set(coupling_map) + ibm_setup = [ + GridMapper(2, 8, grid_to_physical), + LocalOptimizer(5), + SwapAndCNOTFlipper(coupling_map), + LocalOptimizer(5) + ] + else: + # If there is an online device not handled into ProjectQ it's not too + # bad, the engine_list can be constructed manually with the + # appropriate mapper and the 'coupling_map' parameter + raise DeviceNotHandledError('Device not yet fully handled by ProjectQ') -""" + # Most IBM devices accept U1,U2,U3,CX gates. + # Most gates need to be decomposed into a subset that is manually converted + # in the backend (until the implementation of the U1,U2,U3) + # available gates decomposable now for U1,U2,U3: Rx,Ry,Rz and H + setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry, Rz, H), + two_qubit_gates=(CNOT, ), + other_gates=(Barrier, )) + setup.extend(ibm_setup) + return setup -import projectq -import projectq.setups.decompositions -from projectq.cengines import (TagRemover, - LocalOptimizer, - AutoReplacer, - IBM5QubitMapper, - SwapAndCNOTFlipper, - DecompositionRuleSet) - - -ibmqx4_connections = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) - - -def get_engine_list(): - rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - return [TagRemover(), - LocalOptimizer(10), - AutoReplacer(rule_set), - TagRemover(), - IBM5QubitMapper(), - SwapAndCNOTFlipper(ibmqx4_connections), - LocalOptimizer(10)] + +class DeviceOfflineError(Exception): + pass + + +class DeviceNotHandledError(Exception): + pass + + +def list2set(coupling_list): + result = [] + for el in coupling_list: + result.append(tuple(el)) + return set(result) diff --git a/projectq/setups/ibm_test.py b/projectq/setups/ibm_test.py index 598b949cb..26b41b24a 100644 --- a/projectq/setups/ibm_test.py +++ b/projectq/setups/ibm_test.py @@ -13,17 +13,60 @@ # limitations under the License. """Tests for projectq.setup.ibm.""" -import projectq -from projectq import MainEngine -from projectq.cengines import IBM5QubitMapper, SwapAndCNOTFlipper +import pytest -def test_ibm_cnot_mapper_in_cengines(): +def test_ibm_cnot_mapper_in_cengines(monkeypatch): import projectq.setups.ibm - found = 0 - for engine in projectq.setups.ibm.get_engine_list(): - if isinstance(engine, IBM5QubitMapper): - found |= 1 - if isinstance(engine, SwapAndCNOTFlipper): - found |= 2 - assert found == 3 + + def mock_show_devices(*args, **kwargs): + connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return { + 'ibmq_burlington': { + 'coupling_map': connections, + 'version': '0.0.0', + 'nq': 5 + }, + 'ibmq_16_melbourne': { + 'coupling_map': connections, + 'version': '0.0.0', + 'nq': 15 + }, + 'ibmq_qasm_simulator': { + 'coupling_map': connections, + 'version': '0.0.0', + 'nq': 32 + } + } + + monkeypatch.setattr(projectq.setups.ibm, "show_devices", mock_show_devices) + engines_5qb = projectq.setups.ibm.get_engine_list(device='ibmq_burlington') + engines_15qb = projectq.setups.ibm.get_engine_list( + device='ibmq_16_melbourne') + engines_simulator = projectq.setups.ibm.get_engine_list( + device='ibmq_qasm_simulator') + assert len(engines_5qb) == 15 + assert len(engines_15qb) == 16 + assert len(engines_simulator) == 13 + + +def test_ibm_errors(monkeypatch): + import projectq.setups.ibm + + def mock_show_devices(*args, **kwargs): + connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return { + 'ibmq_imaginary': { + 'coupling_map': connections, + 'version': '0.0.0', + 'nq': 6 + } + } + + monkeypatch.setattr(projectq.setups.ibm, "show_devices", mock_show_devices) + with pytest.raises(projectq.setups.ibm.DeviceOfflineError): + projectq.setups.ibm.get_engine_list(device='ibmq_burlington') + with pytest.raises(projectq.setups.ibm.DeviceNotHandledError): + projectq.setups.ibm.get_engine_list(device='ibmq_imaginary') From d5bf14e378e1b1d63d217cfbbaa382dcca83dfd7 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Wed, 5 Feb 2020 11:24:33 +0100 Subject: [PATCH 035/113] Automatically generate documentation ReST files (#339) * Automate generation of documentation ReST files * Fix error in conf.py * Adjust .gitignore --- .gitignore | 4 + docs/conf.py | 168 +++++++++++++++--- docs/package_description.py | 167 ++++++++++++++++++ docs/projectq.backends.rst | 21 --- docs/projectq.cengines.rst | 33 ---- docs/projectq.libs.math.rst | 21 --- docs/projectq.libs.revkit.rst | 34 ---- docs/projectq.libs.rst | 20 --- docs/projectq.meta.rst | 32 ---- docs/projectq.ops.rst | 67 -------- docs/projectq.setups.decompositions.rst | 216 ------------------------ docs/projectq.setups.rst | 101 ----------- docs/projectq.types.rst | 18 -- 13 files changed, 319 insertions(+), 583 deletions(-) create mode 100644 docs/package_description.py delete mode 100755 docs/projectq.backends.rst delete mode 100755 docs/projectq.cengines.rst delete mode 100755 docs/projectq.libs.math.rst delete mode 100644 docs/projectq.libs.revkit.rst delete mode 100755 docs/projectq.libs.rst delete mode 100755 docs/projectq.meta.rst delete mode 100755 docs/projectq.ops.rst delete mode 100755 docs/projectq.setups.decompositions.rst delete mode 100755 docs/projectq.setups.rst delete mode 100755 docs/projectq.types.rst diff --git a/.gitignore b/.gitignore index c3957d2e0..c80e00ac7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ # python artifacts *.pyc +docs/projectq.*.rst +*.so +build/ +var/ diff --git a/docs/conf.py b/docs/conf.py index 971cefd8d..ef791d9ae 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,6 +21,18 @@ import sys sys.path.insert(0, os.path.abspath('..')) +import projectq +# Also import all the modules that are not automatically imported +import projectq.libs.math +import projectq.libs.revkit +import projectq.setups.default +import projectq.setups.grid +import projectq.setups.ibm +import projectq.setups.ibm16 +import projectq.setups.linear +import projectq.setups.restrictedgateset +import projectq.setups.decompositions + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -33,8 +45,11 @@ import sphinx_rtd_theme extensions = [ - 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.mathjax', - 'sphinx.ext.autosummary', 'sphinx.ext.linkcode', + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.mathjax', + 'sphinx.ext.autosummary', + 'sphinx.ext.linkcode', ] autosummary_generate = True @@ -125,7 +140,6 @@ # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False - # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -271,8 +285,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'projectq.tex', 'projectq Documentation', - 'a', 'manual'), + (master_doc, 'projectq.tex', 'projectq Documentation', 'a', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -307,30 +320,24 @@ # # latex_domain_indices = True - # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'projectq', 'projectq Documentation', - [author], 1) -] +man_pages = [(master_doc, 'projectq', 'projectq Documentation', [author], 1)] # If true, show URL addresses after external links. # # man_show_urls = False - # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (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. @@ -351,7 +358,6 @@ # -- Options for sphinx.ext.linkcode -------------------------------------- import inspect -import projectq def linkcode_resolve(domain, info): @@ -381,7 +387,11 @@ def linkcode_resolve(domain, info): return None else: try: - obj = eval(info['module'] + '.' + 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 except AttributeError: # Object might be a non-static attribute of a class, e.g., # self.num_qubits, which would only exist after init was called. @@ -400,8 +410,8 @@ 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: @@ -409,6 +419,124 @@ 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') + +PackageDescription = desc.PackageDescription + +# ------------------------------------------------------------------------------ +# Define the description of ProjectQ packages and their submodules below. +# +# In order for the automatic package recognition to work properly, it is +# important that PackageDescription of sub-packages appear earlier in the list +# than their parent package (see for example libs.math and libs.revkit +# compared to libs). +# +# It is also possible to customize the presentation of submodules (see for +# example the setups and setups.decompositions) or even to have private +# sub-modules listed in the documentation page of a parent packages (see for +# example the cengines package) + +descriptions = [ + PackageDescription('backends'), + PackageDescription('cengines', + desc=''' +The ProjectQ compiler engines package. +'''), + 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=''' +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 +circuits: + +- :class:`~projectq.libs.revkit.ControlFunctionOracle`: Synthesizes a reversible circuit from Boolean control function +- :class:`~projectq.libs.revkit.PermutationOracle`: Synthesizes a reversible circuit for a permutation +- :class:`~projectq.libs.revkit.PhaseOracle`: Synthesizes phase circuit from an arbitrary Boolean function + +RevKit can be installed from PyPi with `pip install revkit`. + +.. note:: + + The RevKit Python module must be installed in order to use this ProjectQ library. + + There exist precompiled binaries in PyPi, as well as a source distribution. + Note that a C++ compiler with C++17 support is required to build the RevKit + python module from source. Examples for compatible compilers are Clang + 6.0, GCC 7.3, and GCC 8.1. + +The integration of RevKit into ProjectQ and other quantum programming languages is described in the paper + + * 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=''' +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=''' +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=''' +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=''' +The decomposition package is a collection of gate decomposition / replacement rules which can be used by, e.g., the AutoReplacer engine. +'''), + 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: + .. code-block:: python + + import projectq.setups.ibm as ibm_setup + from projectq import MainEngine + eng = MainEngine(engine_list=ibm_setup.get_engine_list()) + # eng uses the default Simulator backend + +The subpackage decompositions contains all the individual decomposition rules +which can be given to, e.g., an `AutoReplacer`. +''', + 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__'), + PackageDescription( + '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 + +for desc in descriptions: + fname = os.path.join(os.path.dirname(os.path.abspath('__file__')), + 'projectq.{}.rst'.format(desc.name)) + lines = None + if os.path.exists(fname): + with open(fname, 'r') as fd: + lines = [line[:-1] for line in fd.readlines()] + + new_lines = desc.get_ReST() + + if new_lines != lines: + with open(fname, 'w') as fd: + fd.write('\n'.join(desc.get_ReST())) diff --git a/docs/package_description.py b/docs/package_description.py new file mode 100644 index 000000000..afb18ba18 --- /dev/null +++ b/docs/package_description.py @@ -0,0 +1,167 @@ +import inspect +import sys +import os + + +class PackageDescription(object): + package_list = [] + + def __init__(self, name, desc='', module_special_members='__init__', + submodule_special_members='', submodules_desc='', + helper_submodules=None): + """ + Args: + name (str): Name of ProjectQ module + desc (str): (optional) Description of module + module_special_members (str): (optional) Special members to include + in the documentation of the module + submodule_special_members (str): (optional) Special members to + include in the documentation of submodules + submodules_desc (str): (optional) Description to print out before + the list of submodules + helper_submodules (list): (optional) List of tuples for helper + sub-modules to include in the documentation. + Tuples are (section_title, submodukle_name, + automodule_properties) + """ + + self.name = name + self.desc = desc + if name not in PackageDescription.package_list: + PackageDescription.package_list.append(name) + + self.module = sys.modules['projectq.{}'.format(self.name)] + self.module_special_members = module_special_members + + self.submodule_special_members = module_special_members + self.submodules_desc = submodules_desc + + 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 module_root in obj.__file__) + if name[0] != '_'] + + self.subpackages = [] + self.submodules = [] + for name, obj in sub: + if '{}.{}'.format(self.name, + name) in PackageDescription.package_list: + self.subpackages.append((name, obj)) + else: + self.submodules.append((name, obj)) + + 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.sort(key=lambda x: x[0].lower()) + + def get_ReST(self): + new_lines = [] + new_lines.append(self.name) + new_lines.append('=' * len(self.name)) + new_lines.append('') + + if self.desc: + new_lines.append(self.desc.strip()) + new_lines.append('') + + submodule_has_index = False + + if self.subpackages: + new_lines.append('Subpackages') + new_lines.append('-' * len(new_lines[-1])) + new_lines.append('') + new_lines.append('.. toctree::') + new_lines.append(' :maxdepth: 1') + new_lines.append('') + for name, _ in self.subpackages: + new_lines.append(' projectq.{}.{}'.format(self.name, name)) + new_lines.append('') + else: + submodule_has_index = True + new_lines.append('.. autosummary::') + new_lines.append('') + if self.submodules: + for name, _ in self.submodules: + 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('') + + if self.submodules: + new_lines.append('Submodules') + new_lines.append('-' * len(new_lines[-1])) + new_lines.append('') + if self.submodules_desc: + new_lines.append(self.submodules_desc.strip()) + new_lines.append('') + + if not submodule_has_index: + new_lines.append('.. autosummary::') + new_lines.append('') + for name, _ in self.submodules: + 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(' :members:') + if self.submodule_special_members: + new_lines.append(' :special-members: {}'.format( + self.submodule_special_members)) + new_lines.append(' :undoc-members:') + new_lines.append('') + + new_lines.append('Module contents') + new_lines.append('-' * len(new_lines[-1])) + new_lines.append('') + 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(' :imported-members:') + new_lines.append('') + + if self.helper_submodules: + new_lines.append('Helper sub-modules') + new_lines.append('-' * len(new_lines[-1])) + new_lines.append('') + for title, name, params in self.helper_submodules: + new_lines.append(title) + new_lines.append('^' * len(title)) + new_lines.append('') + new_lines.append('.. automodule:: projectq.{}.{}'.format( + self.name, name)) + for param in params: + new_lines.append(' {}'.format(param)) + new_lines.append('') + + assert not new_lines[-1] + return new_lines[:-1] diff --git a/docs/projectq.backends.rst b/docs/projectq.backends.rst deleted file mode 100755 index cc6531df4..000000000 --- a/docs/projectq.backends.rst +++ /dev/null @@ -1,21 +0,0 @@ -backends -======== - -.. autosummary:: - - projectq.backends.CommandPrinter - projectq.backends.CircuitDrawer - projectq.backends.CircuitDrawerMatplotlib - projectq.backends.Simulator - projectq.backends.ClassicalSimulator - projectq.backends.ResourceCounter - projectq.backends.IBMBackend - - -Module contents ---------------- - -.. automodule:: projectq.backends - :members: - :special-members: __init__ - :imported-members: diff --git a/docs/projectq.cengines.rst b/docs/projectq.cengines.rst deleted file mode 100755 index 5a3c963a6..000000000 --- a/docs/projectq.cengines.rst +++ /dev/null @@ -1,33 +0,0 @@ -cengines -======== - -The ProjectQ compiler engines package. - -.. autosummary:: - projectq.cengines.AutoReplacer - projectq.cengines.BasicEngine - projectq.cengines.BasicMapper - projectq.cengines.CommandModifier - projectq.cengines.CompareEngine - projectq.cengines.DecompositionRule - projectq.cengines.DecompositionRuleSet - projectq.cengines.DummyEngine - projectq.cengines.ForwarderEngine - projectq.cengines.GridMapper - projectq.cengines.InstructionFilter - projectq.cengines.IBM5QubitMapper - projectq.cengines.LinearMapper - projectq.cengines.LocalOptimizer - projectq.cengines.ManualMapper - projectq.cengines.MainEngine - projectq.cengines.SwapAndCNOTFlipper - projectq.cengines.TagRemover - - -Module contents ---------------- - -.. automodule:: projectq.cengines - :members: - :special-members: __init__ - :imported-members: diff --git a/docs/projectq.libs.math.rst b/docs/projectq.libs.math.rst deleted file mode 100755 index 1567978b5..000000000 --- a/docs/projectq.libs.math.rst +++ /dev/null @@ -1,21 +0,0 @@ -math -==== - -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. - -.. autosummary:: - - projectq.libs.math.all_defined_decomposition_rules - projectq.libs.math.AddConstant - projectq.libs.math.SubConstant - projectq.libs.math.AddConstantModN - projectq.libs.math.SubConstantModN - projectq.libs.math.MultiplyByConstantModN - -Module contents ---------------- - -.. automodule:: projectq.libs.math - :members: - :special-members: __init__ - :imported-members: diff --git a/docs/projectq.libs.revkit.rst b/docs/projectq.libs.revkit.rst deleted file mode 100644 index 90a2dbb18..000000000 --- a/docs/projectq.libs.revkit.rst +++ /dev/null @@ -1,34 +0,0 @@ -revkit -====== - -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 -circuits: - -- :class:`~projectq.libs.revkit.ControlFunctionOracle`: Synthesizes a reversible circuit from Boolean control function -- :class:`~projectq.libs.revkit.PermutationOracle`: Synthesizes a reversible circuit for a permutation -- :class:`~projectq.libs.revkit.PhaseOracle`: Synthesizes phase circuit from an arbitrary Boolean function - -RevKit can be installed from PyPi with `pip install revkit`. - -.. note:: - - The RevKit Python module must be installed in order to use this ProjectQ library. - - There exist precompiled binaries in PyPi, as well as a source distribution. - Note that a C++ compiler with C++17 support is required to build the RevKit - python module from source. Examples for compatible compilers are Clang - 6.0, GCC 7.3, and GCC 8.1. - -The integration of RevKit into ProjectQ and other quantum programming languages is described in the paper - - * 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 contents ---------------- - -.. automodule:: projectq.libs.revkit - :members: - :special-members: __init__,__or__ - :imported-members: diff --git a/docs/projectq.libs.rst b/docs/projectq.libs.rst deleted file mode 100755 index 9f2c8cd4b..000000000 --- a/docs/projectq.libs.rst +++ /dev/null @@ -1,20 +0,0 @@ -libs -==== - -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. - -Subpackages ------------ - -.. toctree:: - - projectq.libs.math - projectq.libs.revkit - -Module contents ---------------- - -.. automodule:: projectq.libs - :members: - :special-members: __init__ - :imported-members: diff --git a/docs/projectq.meta.rst b/docs/projectq.meta.rst deleted file mode 100755 index 14c3d9eea..000000000 --- a/docs/projectq.meta.rst +++ /dev/null @@ -1,32 +0,0 @@ -meta -==== - -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. - - -.. autosummary:: - - projectq.meta.DirtyQubitTag - projectq.meta.LogicalQubitIDTag - projectq.meta.LoopTag - projectq.meta.Loop - projectq.meta.Compute - projectq.meta.Uncompute - projectq.meta.CustomUncompute - projectq.meta.ComputeTag - projectq.meta.UncomputeTag - projectq.meta.Control - projectq.meta.get_control_count - projectq.meta.Dagger - projectq.meta.insert_engine - projectq.meta.drop_engine_after - -Module contents ---------------- - -.. automodule:: projectq.meta - :members: - :undoc-members: - :special-members: __init__ - :imported-members: diff --git a/docs/projectq.ops.rst b/docs/projectq.ops.rst deleted file mode 100755 index 6b9ec5936..000000000 --- a/docs/projectq.ops.rst +++ /dev/null @@ -1,67 +0,0 @@ -ops -=== - -The operations collection consists of various default gates and is a work-in-progress, as users start to work with ProjectQ. - -.. autosummary:: - - projectq.ops.BasicGate - projectq.ops.MatrixGate - projectq.ops.SelfInverseGate - projectq.ops.BasicRotationGate - projectq.ops.BasicPhaseGate - projectq.ops.ClassicalInstructionGate - projectq.ops.FastForwardingGate - projectq.ops.BasicMathGate - projectq.ops.apply_command - projectq.ops.Command - projectq.ops.H - projectq.ops.X - projectq.ops.Y - projectq.ops.Z - projectq.ops.S - projectq.ops.Sdag - projectq.ops.T - projectq.ops.Tdag - projectq.ops.SqrtX - projectq.ops.Swap - projectq.ops.SqrtSwap - projectq.ops.Entangle - projectq.ops.Ph - projectq.ops.Rx - projectq.ops.Ry - projectq.ops.Rz - projectq.ops.R - projectq.ops.FlushGate - projectq.ops.MeasureGate - projectq.ops.Allocate - projectq.ops.Deallocate - projectq.ops.AllocateDirty - projectq.ops.Barrier - projectq.ops.DaggeredGate - projectq.ops.ControlledGate - projectq.ops.C - projectq.ops.All - projectq.ops.Tensor - projectq.ops.QFT - projectq.ops.QubitOperator - projectq.ops.CRz - projectq.ops.CNOT - projectq.ops.CZ - projectq.ops.Toffoli - projectq.ops.TimeEvolution - projectq.ops.UniformlyControlledRy - projectq.ops.UniformlyControlledRz - projectq.ops.StatePreparation - projectq.ops.QPE - projectq.ops.FlipBits - projectq.ops.QAA - - -Module contents ---------------- - -.. automodule:: projectq.ops - :members: - :special-members: __init__,__or__ - :imported-members: diff --git a/docs/projectq.setups.decompositions.rst b/docs/projectq.setups.decompositions.rst deleted file mode 100755 index 6206c4a95..000000000 --- a/docs/projectq.setups.decompositions.rst +++ /dev/null @@ -1,216 +0,0 @@ -decompositions -============== - -The decomposition package is a collection of gate decomposition / replacement rules which can be used by, e.g., the AutoReplacer engine. - - -.. autosummary:: - - projectq.setups.decompositions.arb1qubit2rzandry - projectq.setups.decompositions.barrier - projectq.setups.decompositions.carb1qubit2cnotrzandry - projectq.setups.decompositions.cnot2cz - projectq.setups.decompositions.cnot2rxx - projectq.setups.decompositions.cnu2toffoliandcu - projectq.setups.decompositions.crz2cxandrz - projectq.setups.decompositions.entangle - projectq.setups.decompositions.globalphase - projectq.setups.decompositions.h2rx - projectq.setups.decompositions.ph2r - projectq.setups.decompositions.qft2crandhadamard - projectq.setups.decompositions.qubitop2onequbit - projectq.setups.decompositions.r2rzandph - projectq.setups.decompositions.rx2rz - projectq.setups.decompositions.ry2rz - projectq.setups.decompositions.rz2rx - projectq.setups.decompositions.sqrtswap2cnot - projectq.setups.decompositions.stateprep2cnot - projectq.setups.decompositions.swap2cnot - projectq.setups.decompositions.time_evolution - projectq.setups.decompositions.toffoli2cnotandtgate - projectq.setups.decompositions.uniformlycontrolledr2cnot - projectq.setups.decompositions.phaseestimation - projectq.setups.decompositions.amplitudeamplification - - -Submodules ----------- - -projectq.setups.decompositions.arb1qubit2rzandry module -------------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.arb1qubit2rzandry - :members: - :undoc-members: - - -projectq.setups.decompositions.barrier module ---------------------------------------------- - -.. automodule:: projectq.setups.decompositions.barrier - :members: - :undoc-members: - -projectq.setups.decompositions.carb1qubit2cnotrzandry module ------------------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.carb1qubit2cnotrzandry - :members: - :undoc-members: - -projectq.setups.decompositions.cnot2cz module ---------------------------------------------- - -.. automodule:: projectq.setups.decompositions.cnot2cz - :members: - :undoc-members: - -projectq.setups.decompositions.cnot2rxx module ---------------------------------------------- - -.. automodule:: projectq.setups.decompositions.cnot2rxx - :members: - :undoc-members: - -projectq.setups.decompositions.cnu2toffoliandcu module ------------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.cnu2toffoliandcu - :members: - :undoc-members: - -projectq.setups.decompositions.crz2cxandrz module -------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.crz2cxandrz - :members: - :undoc-members: - -projectq.setups.decompositions.entangle module ----------------------------------------------- - -.. automodule:: projectq.setups.decompositions.entangle - :members: - :undoc-members: - -projectq.setups.decompositions.globalphase module -------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.globalphase - :members: - :undoc-members: - - -projectq.setups.decompositions.h2rx module ------------------------------------------- - -.. automodule:: projectq.setups.decompositions.ph2r - :members: - :undoc-members: - -projectq.setups.decompositions.qft2crandhadamard module -------------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.qft2crandhadamard - :members: - :undoc-members: - -projectq.setups.decompositions.qubitop2onequbit module -------------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.qubitop2onequbit - :members: - :undoc-members: - -projectq.setups.decompositions.r2rzandph module ------------------------------------------------ - -.. automodule:: projectq.setups.decompositions.r2rzandph - :members: - :undoc-members: - -projectq.setups.decompositions.rx2rz module -------------------------------------------- - -.. automodule:: projectq.setups.decompositions.rx2rz - :members: - :undoc-members: - -projectq.setups.decompositions.ry2rz module -------------------------------------------- - -.. automodule:: projectq.setups.decompositions.ry2rz - :members: - :undoc-members: - -projectq.setups.decompositions.rz2rx module -------------------------------------------- - -.. automodule:: projectq.setups.decompositions.rz2rx - :members: - :undoc-members: - -projectq.setups.decompositions.sqrtswap2cnot module ---------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.sqrtswap2cnot - :members: - :undoc-members: - -projectq.setups.decompositions.stateprep2cnot module ----------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.stateprep2cnot - :members: - :undoc-members: - -projectq.setups.decompositions.swap2cnot module ------------------------------------------------ - -.. automodule:: projectq.setups.decompositions.swap2cnot - :members: - :undoc-members: - -projectq.setups.decompositions.time_evolution module ----------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.time_evolution - :members: - :undoc-members: - -projectq.setups.decompositions.toffoli2cnotandtgate module ----------------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.toffoli2cnotandtgate - :members: - :undoc-members: - -projectq.setups.decompositions.uniformlycontrolledr2cnot module ---------------------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.uniformlycontrolledr2cnot - :members: - :undoc-members: - -projectq.setups.decompositions.phaseestimation module ---------------------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.phaseestimation - :members: - :undoc-members: - -projectq.setups.decompositions.amplitudeamplification module ---------------------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.amplitudeamplification - :members: - :undoc-members: - - -Module contents ---------------- - -.. automodule:: projectq.setups.decompositions - :members: - :undoc-members: - :imported-members: diff --git a/docs/projectq.setups.rst b/docs/projectq.setups.rst deleted file mode 100755 index 98e7e611a..000000000 --- a/docs/projectq.setups.rst +++ /dev/null @@ -1,101 +0,0 @@ -setups -====== - -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: - .. code-block:: python - - import projectq.setups.ibm as ibm_setup - from projectq import MainEngine - eng = MainEngine(engine_list=ibm_setup.get_engine_list()) - # eng uses the default Simulator backend - -The subpackage decompositions contains all the individual decomposition rules -which can be given to, e.g., an `AutoReplacer`. - - -Subpackages ------------ - -.. toctree:: - :maxdepth: 1 - - projectq.setups.decompositions - -Submodules ----------- - -Each of the submodules contains a setup which can be used to specify the -`engine_list` used by the `MainEngine` : - -.. autosummary:: - - projectq.setups.default - projectq.setups.grid - projectq.setups.ibm - projectq.setups.ibm16 - projectq.setups.linear - projectq.setups.restrictedgateset - -default -------- - -.. automodule:: projectq.setups.default - :members: - :special-members: __init__ - :undoc-members: - -grid ----- - -.. automodule:: projectq.setups.grid - :members: - :special-members: __init__ - :undoc-members: - -ibm ---- - -.. automodule:: projectq.setups.ibm - :members: - :special-members: __init__ - :undoc-members: - -ibm16 ------ - -.. automodule:: projectq.setups.ibm16 - :members: - :special-members: __init__ - :undoc-members: - -linear ------- - -.. automodule:: projectq.setups.linear - :members: - :special-members: __init__ - :undoc-members: - -restrictedgateset ------------------ - -.. automodule:: projectq.setups.restrictedgateset - :members: - :special-members: __init__ - :undoc-members: - -trapped_ion_decomposer ----------------------- -.. automodule:: projectq.setups.trapped_ion_decomposer - :members: - :special-members: __init__ - :undoc-members: - -Module contents ---------------- - -.. automodule:: projectq.setups - :members: - :special-members: __init__ diff --git a/docs/projectq.types.rst b/docs/projectq.types.rst deleted file mode 100755 index 4f26edc9d..000000000 --- a/docs/projectq.types.rst +++ /dev/null @@ -1,18 +0,0 @@ -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. - -.. autosummary:: - projectq.types.BasicQubit - projectq.types.Qubit - projectq.types.Qureg - projectq.types.WeakQubitRef - -Module contents ---------------- - -.. automodule:: projectq.types - :members: - :special-members: - :imported-members: From 63af3bfe043690fd7ea65778a3b15a83bde06c18 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Wed, 19 Feb 2020 10:19:57 +0100 Subject: [PATCH 036/113] Update setup.py (#337) * Rewrite setup.py to fix some issues on Mac OSX and some code cleanup - On Mac OSX with Homebrew, properly find the path to the OpenMP library if compiling with clang from Homebrew - Code cleanup and some reformating * Remove use of deprecated setuptools.Feature Now try to compile the C++ simulator and if it fails, simply install a pure Python package with some warning messages for the user. * Update documentation to reflect the latest change to setup.py * Fix error with deleted method of BuildExt * Remove global COPYING file and move text to setup.py file itself * Fix compilation issues on MacOS X * Update .gitignore * Fix setup.py for Python2 and MacPorts (Mac OS) * Fix setup.py on Windows * Some more fixes * Attempt to fix failing installation on Travis CI * Fix installation under Linux * Undo changes in .travis.yml * Update setup related tutorials * Fix a few remaining typos. --- .gitignore | 179 ++++++++++++++- docs/tutorials.rst | 119 ++++++---- setup.py | 548 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 679 insertions(+), 167 deletions(-) diff --git a/.gitignore b/.gitignore index c80e00ac7..ca0430898 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,179 @@ -# python artifacts -*.pyc -docs/projectq.*.rst +# Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions *.so + +# Distribution / packaging +.Python build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +docs/doxygen + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# ============================================================================== +# Prerequisites +*.d + +# C++ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# ============================================================================== + +# Windows artifacts +thumbs.db + +# Mac OSX artifacts +*.DS_Store \ No newline at end of file diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 289af1aef..cec2e75e7 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -25,13 +25,9 @@ or, alternatively, `clone/download `_ thi 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 it fails, you may use the `--without-cppsimulator` parameter, i.e., - - .. code-block:: bash - - python -m pip install --user --global-option=--without-cppsimulator . - - and the framework will use the **slow Python simulator instead**. Note that this only works if the installation has been tried once without the `--without-cppsimulator` parameter and hence all requirements are now installed. See the instructions below if you want to run larger simulations. The Python simulator works perfectly fine for the 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. .. note:: If building the C++-Simulator does not work out of the box, consider specifying a different compiler. For example: @@ -40,13 +36,13 @@ ProjectQ comes with a high-performance quantum simulator written in C++. Please env CC=g++-5 python -m pip install --user projectq - Please note that the compiler you specify must support **C++11**! + 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 `_. .. 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 can cause 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. Detailed instructions and OS-specific hints @@ -70,38 +66,75 @@ Detailed instructions and OS-specific hints .. code-block:: bash - sudo pip3 install --user projectq + 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. **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 Python3.5 installers from `python.org `_ or `ANACONDA `_. Installing ProjectQ right away will succeed for the (slow) Python simulator (i.e., with the `--without-cppsimulator` flag). 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 msvc. + 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 + + 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. **macOS**: - These are the steps to install ProjectQ on a new Mac: + Similarly to the other platforms, installing ProjectQ without the C++ simulator is really easy: - In order to install the fast C++ simulator, we require that your system has a C++ compiler (see option 3 below on how to only install the slower Python simulator via the `--without-cppsimulator` parameter) + .. code-block:: bash - Below you will find two options to install the fast C++ simulator. The first one is the easiest and requires only the standard compiler which Apple distributes with XCode. The second option uses macports to install the simulator with additional support for multi-threading by using OpenMP, which makes it slightly faster. We show how to install the required C++ compiler (clang) which supports OpenMP and additionally, we show how to install a newer python version. + python3 -m pip install --user projectq -.. note:: - Depending on your system you might need to use `sudo` for the installation. + + 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 + + For both options 2 and 3, you will be required to first install the XCode command line tools -1. Installation using XCode and the default python: - Install XCode by opening a terminal and running the following command: + **Apple XCode command line tool** + + Install the XCode command line tools by opening a terminal window and running the following command: .. code-block:: bash xcode-select --install - - Next, you will need to install Python and pip. See option 2 for information on how to install a newer python version with macports. Here, we are using the standard python which is preinstalled with macOS. Pip can be installed by: + + 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 @@ -111,56 +144,64 @@ Detailed instructions and OS-specific hints .. code-block:: bash - python -m pip install --user projectq + python3 -m pip install --user projectq + Note that the compiler provided by Apple is currently not able to compile ProjectQ's multi-threaded code. -2. Installation using macports: + **Homebrew** - Either use the standard python and install pip as shown in option 1 or better use macports to install a newer python version, e.g., Python 3.5 and the corresponding pip. Visit `macports.org `_ and install the latest version (afterwards open a new terminal). Then, use macports to install Python 3.5 by + First install the XCode command line tools. Then install Homebrew with the following command: .. code-block:: bash - sudo port install python35 + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" - It might show a warning that if you intend to use python from the terminal, you should also install + 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 - sudo port install py35-readline - - Install pip by + brew install python llvm + + You should now be able to install ProjectQ with the C++ simulator using the following command: .. code-block:: bash - sudo port install py35-pip + env P=/usr/local/opt/llvm/bin CC=$P/clang CXX=$P/clang++ python3 -m pip install --user projectq + + + **MacPorts** + + Visit `macports.org `_ and install the latest version that corresponds to your operating system's version. Afterwards, open a new terminal window. - 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 3.9 also using macports (note: gcc installed via macports does not work) + Then, use macports to install Python 3.7 by entering the following command .. code-block:: bash - sudo port install clang-3.9 + sudo port install python37 - ProjectQ is now installed by: + 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 - env CC=clang-mp-3.9 env CXX=clang++-mp-3.9 python3.5 -m pip install --user projectq + sudo port install py37-gnureadline + + Install pip by -3. Installation with only the slow Python simulator: + .. code-block:: bash - While this simulator works fine for small examples, it is suggested to install the high performance simulator written in C++. + sudo port install py37-pip - If you just want to install ProjectQ with the (slow) Python simulator and no compiler, then first try to install ProjectQ with the default compiler + 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 - python -m pip install --user projectq + sudo port install clang-9.0 - which most likely will fail. Then, try again with the flag ``--without-cppsimulator``: + ProjectQ is now installed by: .. code-block:: bash - python -m pip install --user --global-option=--without-cppsimulator 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 diff --git a/setup.py b/setup.py index a0254b0e1..b1cb36321 100755 --- a/setup.py +++ b/setup.py @@ -1,29 +1,60 @@ -from setuptools import setup, Extension, find_packages, Feature -from setuptools.command.build_ext import build_ext -import sys -import os -import setuptools +# Some of the setup.py code is inspired or copied from SQLAlchemy +# SQLAlchemy was created by Michael Bayer. -# This reads the __version__ variable from projectq/_version.py -exec(open('projectq/_version.py').read()) +# Major contributing authors include: -# Readme file as long_description: -long_description = open('README.rst').read() +# - Michael Bayer +# - Jason Kirtland +# - Gaetan de Menten +# - Diana Clarke +# - Michael Trier +# - Philip Jenvey +# - Ants Aasma +# - Paul Johnston +# - Jonathan Ellis -# Read in requirements.txt -with open('requirements.txt', 'r') as f_requirements: - requirements = f_requirements.readlines() -requirements = [r.strip() for r in requirements] +# Copyright 2005-2019 SQLAlchemy authors and contributors (see above) + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# 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, find_packages +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 +import os +import subprocess +import platform + +# ============================================================================== +# Helper functions and classes 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 @@ -32,142 +63,409 @@ def __str__(self): return pybind11.get_include(self.user) -cppsim = Feature( - 'C++ Simulator', - standard=True, - ext_modules=[ - Extension( - 'projectq.backends._sim._cppsim', - ['projectq/backends/_sim/_cppsim.cpp'], - include_dirs=[ - # Path to pybind11 headers - get_pybind_include(), - get_pybind_include(user=True) - ], - language='c++' - ), - ], -) - - -def has_flag(compiler, flagname=None): - """ +def important_msgs(*msgs): + print('*' * 75) + for msg in msgs: + print(msg) + print('*' * 75) + + +def status_msgs(*msgs): + print('-' * 75) + for msg in msgs: + print('# INFO: ', msg) + print('-' * 75) + + +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('int main (int argc, char **argv) { return 0; }') + f.write('{}\nint main (int argc, char **argv) {{ {} return 0; }}'.format( + include, body)) f.close() ret = True - try: - if flagname is None: - compiler.compile([f.name]) - else: - compiler.compile([f.name], extra_postargs=[flagname]) - except: - ret = False - os.unlink(f.name) - return ret + if postargs is None: + postargs = [flagname] if flagname is not None else None + elif flagname is not None: + postargs.append(flagname) -def knows_intrinsics(compiler): - """ - Return a boolean indicating whether the compiler can handle intrinsics. - """ - import tempfile - f = tempfile.NamedTemporaryFile('w', suffix='.cpp', delete=False) - f.write('#include \nint main (int argc, char **argv) ' - '{ __m256d neg = _mm256_set1_pd(1.0); }') - f.close() - ret = True try: - compiler.compile([f.name], extra_postargs=['-march=native']) - except setuptools.distutils.errors.CompileError: + exec_name = os.path.join(tempfile.mkdtemp(), 'test') + + if compiler.compiler_type == 'msvc': + olderr = os.dup(sys.stderr.fileno()) + err = open('err.txt', 'w') + os.dup2(err.fileno(), sys.stderr.fileno()) + + obj_file = compiler.compile([f.name], extra_postargs=postargs) + if not os.path.exists(obj_file[0]): + raise RuntimeError('') + if link: + compiler.link_executable(obj_file, + exec_name, + extra_postargs=postargs) + + if compiler.compiler_type == 'msvc': + err.close() + os.dup2(olderr, sys.stderr.fileno()) + with open('err.txt', 'r') as err_file: + if err_file.readlines(): + raise RuntimeError('') + except (CompileError, LinkError, RuntimeError): ret = False os.unlink(f.name) 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/' + _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) + + +# ------------------------------------------------------------------------------ + + +class BuildFailed(Exception): + def __init__(self): + self.cause = sys.exc_info()[1] # work around py 2/3 different syntax + + +# ------------------------------------------------------------------------------ +# Python build related variable + +cpython = platform.python_implementation() == 'CPython' +ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError) +if sys.platform == 'win32': + # 2.6's distutils.msvc9compiler can raise an IOError when failing to + # find the compiler + 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 = [ + Extension( + 'projectq.backends._sim._cppsim', + ['projectq/backends/_sim/_cppsim.cpp'], + include_dirs=[ + # Path to pybind11 headers + get_pybind_include(), + get_pybind_include(user=True) + ], + language='c++'), +] + +# ============================================================================== + + class BuildExt(build_ext): - """A custom build extension for adding compiler-specific options.""" + '''A custom build extension for adding compiler-specific options.''' c_opts = { 'msvc': ['/EHsc'], 'unix': [], } + def run(self): + try: + build_ext.run(self) + except DistutilsPlatformError: + raise BuildFailed() + 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 + try: + build_ext.build_extensions(self) + except ext_errors: + raise BuildFailed() + except ValueError: + # 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 + + def _configure_compiler(self): if sys.platform == 'darwin': - self.c_opts['unix'] += ['-mmacosx-version-min=10.7'] - if has_flag(self.compiler, '-stdlib=libc++'): + _fix_macosx_header_paths(self.compiler.compiler, + self.compiler.compiler_so) + + if compiler_test(self.compiler, '-stdlib=libc++'): self.c_opts['unix'] += ['-stdlib=libc++'] ct = self.compiler.compiler_type - opts = self.c_opts.get(ct, []) + self.opts = self.c_opts.get(ct, []) + self.link_opts = [] - if not has_flag(self.compiler): - self.warning("Something is wrong with your C++ compiler.\n" - "Failed to compile a simple test program!\n") - return + if not compiler_test(self.compiler): + important_msgs( + 'ERROR: something is wrong with your C++ compiler.\n' + 'Failed to compile a simple test program!') + raise BuildFailed() + + # ------------------------------ + + status_msgs('Configuring OpenMP') + self._configure_openmp() + status_msgs('Configuring compiler intrinsics') + self._configure_intrinsics() + status_msgs('Configuring C++ standard') + self._configure_cxx_standard() - openmp = '' - if has_flag(self.compiler, '-fopenmp'): - openmp = '-fopenmp' - elif has_flag(self.compiler, '-qopenmp'): - openmp = '-qopenmp' - if ct == 'msvc': - openmp = '' # supports only OpenMP 2.0 - - if knows_intrinsics(self.compiler): - opts.append('-DINTRIN') - if ct == 'msvc': - opts.append('/arch:AVX') - else: - opts.append('-march=native') - opts.append('-ffast-math') - - opts.append(openmp) + # ------------------------------ + # Other compiler tests + + status_msgs('Other compiler tests') if ct == 'unix': - if not has_flag(self.compiler, '-std=c++11'): - self.warning("Compiler needs to have C++11 support!") + 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())) + + status_msgs('Finished configuring compiler!') + + def _configure_openmp(self): + if self.compiler.compiler_type == 'msvc': + return + + kwargs = { + 'link': True, + 'include': '#include ', + 'body': 'int a = omp_get_num_threads(); ++a;' + } + + for flag in ['-openmp', '-fopenmp', '-qopenmp', '/Qopenmp']: + if compiler_test(self.compiler, flag, **kwargs): + self.opts.append(flag) + self.link_opts.append(flag) return - opts.append('-DVERSION_INFO="%s"' - % self.distribution.get_version()) - opts.append('-std=c++11') - if has_flag(self.compiler, '-fvisibility=hidden'): - opts.append('-fvisibility=hidden') - elif ct == 'msvc': - opts.append('/DVERSION_INFO=\\"%s\\"' - % self.distribution.get_version()) - for ext in self.extensions: - ext.extra_compile_args = opts - ext.extra_link_args = [openmp] - try: - build_ext.build_extensions(self) - except setuptools.distutils.errors.CompileError: - self.warning("") - - def warning(self, warning_text): - raise Exception(warning_text + "\nCould not install the C++-Simulator." - "\nProjectQ will default to the (slow) Python " - "simulator.\nUse --without-cppsimulator to skip " - "building the (faster) C++ version of the simulator.") - - -setup( - name='projectq', - version=__version__, - author='ProjectQ', - author_email='info@projectq.ch', - url='http://www.projectq.ch', - description=('ProjectQ - ' - 'An open source software framework for quantum computing'), - long_description=long_description, - features={'cppsimulator': cppsim}, - install_requires=requirements, - cmdclass={'build_ext': BuildExt}, - zip_safe=False, - license='Apache 2', - packages=find_packages() -) + flag = '-fopenmp' + 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] + + # 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): + self.opts.append(flag) + self.link_opts.extend((l_arg, flag)) + return + except subprocess.CalledProcessError: + pass + + try: + # Only relevant for MacPorts users with clang-3.7 + 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] + + # 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)) + return + except subprocess.CalledProcessError: + pass + + important_msgs('WARNING: compiler does not support OpenMP!') + + def _configure_intrinsics(self): + for flag in [ + '-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)) + break + + for flag in ['-ffast-math', '-fast', '/fast', '/fp:precise']: + if compiler_test(self.compiler, flagname=flag): + self.opts.append(flag) + break + + def _configure_cxx_standard(self): + if self.compiler.compiler_type == 'msvc': + return + + cxx_standards = [17, 14, 11] + if sys.version_info[0] < 3: + 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: + cxx_standards = [year for year in cxx_standards if year < 17] + + for year in cxx_standards: + flag = '-std=c++{}'.format(year) + if compiler_test(self.compiler, flag): + self.opts.append(flag) + return + flag = '/Qstd=c++{}'.format(year) + if compiler_test(self.compiler, flag): + self.opts.append(flag) + return + + important_msgs('ERROR: compiler needs to have at least C++11 support!') + raise BuildFailed() + + +class Distribution(_Distribution): + def has_ext_modules(self): + # 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 + # Wheel package when building a wheel without C support. This will + # ensure that Wheel knows to treat us as if the build output is + # platform specific. + return True + + +# ============================================================================== + + +def run_setup(with_cext): + kwargs = {} + if with_cext: + kwargs['ext_modules'] = ext_modules + 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, + **kwargs) + + +# ============================================================================== + +if not cpython: + run_setup(False) + important_msgs( + '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.', + 'Plain-Python build succeeded.', + ) + +else: + try: + run_setup(True) + except BuildFailed as exc: + important_msgs( + exc.cause, + 'WARNING: Some C/C++ extensions could not be compiled, ' + + 'some features are disabled (e.g. C++ simulator).', + 'Failure information, if any, is above.', + 'Retrying the build without the C/C++ extensions now.', + ) + + run_setup(False) + + important_msgs( + 'WARNING: Some C/C++ extensions could not be compiled, ' + + 'some features are disabled (e.g. C++ simulator).', + 'Plain-Python build succeeded.', + ) From fce5921a3c03f6dd6d84d9eb26677c74e30d8651 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 19 Feb 2020 11:25:48 +0100 Subject: [PATCH 037/113] Bumped version number to 0.5.0 --- projectq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/_version.py b/projectq/_version.py index 61a0a8d6a..6900d1135 100755 --- a/projectq/_version.py +++ b/projectq/_version.py @@ -13,4 +13,4 @@ # limitations under the License. """Define version number here and read it from setup.py automatically""" -__version__ = "0.4.2" +__version__ = "0.5.0" From cec7452e67db119f3918ffe2ab6d9ab99214f164 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Fri, 28 Feb 2020 17:43:11 +0100 Subject: [PATCH 038/113] Remove unneeded test --- projectq/tests/_drawmpl_test.py | 53 --------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 projectq/tests/_drawmpl_test.py diff --git a/projectq/tests/_drawmpl_test.py b/projectq/tests/_drawmpl_test.py deleted file mode 100644 index 3d78befa6..000000000 --- a/projectq/tests/_drawmpl_test.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -''' - Tests for projectq.backends._circuits._drawer.py. - - To generate the baseline images - run the tests with '--mpl-generate-path=baseline' - - Then run the tests simply with '--mpl' -''' - -import pytest -from projectq import MainEngine -from projectq.ops import * -from projectq.backends import Simulator -from projectq.backends import CircuitDrawerMatplotlib -from projectq.cengines import DecompositionRuleSet, AutoReplacer -import projectq.setups.decompositions - -@pytest.mark.mpl_image_compare -def test_drawer_mpl(): - drawer = CircuitDrawerMatplotlib() - rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - eng = MainEngine(backend=Simulator(), engine_list=[AutoReplacer(rule_set), - drawer]) - ctrl = eng.allocate_qureg(2) - qureg = eng.allocate_qureg(3) - - Swap | (qureg[0], qureg[2]) - C(Swap) | (qureg[0], qureg[1], qureg[2]) - - CNOT | (qureg[0], qureg[2]) - Rx(1.0) | qureg[0] - CNOT | (qureg[1], qureg[2]) - C(X, 2) | (ctrl[0], ctrl[1], qureg[2]) - QFT | qureg - All(Measure) | qureg - - eng.flush() - fig, ax = drawer.draw() - return fig \ No newline at end of file From f7f57a1553d00758e0f4425f35461210943f0c6a Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Fri, 28 Feb 2020 14:21:36 +0100 Subject: [PATCH 039/113] Fix error in examples/ibm.py --- examples/ibm.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/ibm.py b/examples/ibm.py index 37ba4e0d7..11a81a832 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -49,8 +49,9 @@ def run_entangle(eng, num_qubits=3): if device is None: token = getpass.getpass(prompt='IBM device > ') # create main compiler engine for the IBM back-end - eng = MainEngine(IBMBackend(use_hardware=True, token=token num_runs=1024, + 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)) + engine_list=projectq.setups.ibm.get_engine_list( + token=token, device=device)) # run the circuit and print the result print(run_entangle(eng)) From f5d43e9844f41293b0269b35563dfc77046c14b5 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Thu, 20 Feb 2020 09:33:05 +0100 Subject: [PATCH 040/113] Add documentation for **kwargs for CircuitDrawerMatplotlib --- .../backends/_circuits/_drawer_matplotlib.py | 23 +++++++++++++++++++ projectq/backends/_circuits/_plot.py | 21 +++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py index 23a07c767..3b16d914e 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib.py +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -192,9 +192,32 @@ def draw(self, qubit_labels=None, drawing_order=None): drawing_order (dict): position of each qubit in the output graphic. Keys: qubit IDs, Values: position of qubit on the qubit line in the graphic. + **kwargs (dict): additional parameters are used to update + the default plot parameters Returns: A tuple containing the matplotlib figure and axes objects + + Note: + Additional keyword arguments can be passed to this + function in order to further customize the figure output + by matplotlib (default value in parentheses): + + - fontsize (14): Font size in pt + - column_spacing (.5): Vertical spacing between two + neighbouring gates (roughly in inches) + - control_radius (.015): Radius of the circle for controls + - labels_margin (1): Margin between labels and begin of + wire (roughly in inches) + - linewidth (1): Width of line + - not_radius (.03): Radius of the circle for X/NOT gates + - gate_offset (.05): Inner margins for gates with a text + representation + - mgate_width (.1): Width of the measurement gate + - swap_delta (.02): Half-size of the SWAP gate + - x_offset (.05): Absolute X-offset for drawing within the axes + - 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) diff --git a/projectq/backends/_circuits/_plot.py b/projectq/backends/_circuits/_plot.py index 009b00ab7..edc0a1f72 100644 --- a/projectq/backends/_circuits/_plot.py +++ b/projectq/backends/_circuits/_plot.py @@ -78,6 +78,27 @@ def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): Note: Numbering of qubit wires starts at 0 at the bottom and increases vertically. + + Note: + Additional keyword arguments can be passed to this + function in order to further customize the figure output + by matplotlib (default value in parentheses): + + - fontsize (14): Font size in pt + - column_spacing (.5): Vertical spacing between two + neighbouring gates (roughly in inches) + - control_radius (.015): Radius of the circle for controls + - labels_margin (1): Margin between labels and begin of + wire (roughly in inches) + - linewidth (1): Width of line + - not_radius (.03): Radius of the circle for X/NOT gates + - gate_offset (.05): Inner margins for gates with a text + representation + - mgate_width (.1): Width of the measurement gate + - swap_delta (.02): Half-size of the SWAP gate + - x_offset (.05): Absolute X-offset for drawing within the axes + - wire_height (1): Vertical spacing between two qubit + wires (roughly in inches) """ if qubit_labels is None: qubit_labels = {qubit_id: r'$|0\rangle$' for qubit_id in qubit_lines} From 32513f1095907a064a1d39095c1fa17637766da2 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Thu, 19 Mar 2020 09:12:35 +0100 Subject: [PATCH 041/113] Update setup.py license header --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b1cb36321..5049a3a06 100755 --- a/setup.py +++ b/setup.py @@ -13,8 +13,9 @@ # - Ants Aasma # - Paul Johnston # - Jonathan Ellis +# - Damien Nguyen (ProjectQ) -# Copyright 2005-2019 SQLAlchemy authors and contributors (see above) +# Copyright 2005-2020 SQLAlchemy and ProjectQ authors and contributors (see above) # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to From 89cb7fdf800e36cb66a8f7ab868ff4495960518b Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Thu, 19 Mar 2020 11:35:41 +0100 Subject: [PATCH 042/113] ProjectQ v0.5.0 (#356) * Bumped version number to 0.5.0 * Remove unneeded test * Fix error in examples/ibm.py * Add documentation for **kwargs for CircuitDrawerMatplotlib * Update setup.py license header Co-authored-by: Damian Steiger --- examples/ibm.py | 5 +- projectq/_version.py | 2 +- .../backends/_circuits/_drawer_matplotlib.py | 23 ++++++++ projectq/backends/_circuits/_plot.py | 21 ++++++++ projectq/tests/_drawmpl_test.py | 53 ------------------- setup.py | 3 +- 6 files changed, 50 insertions(+), 57 deletions(-) delete mode 100644 projectq/tests/_drawmpl_test.py diff --git a/examples/ibm.py b/examples/ibm.py index 37ba4e0d7..11a81a832 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -49,8 +49,9 @@ def run_entangle(eng, num_qubits=3): if device is None: token = getpass.getpass(prompt='IBM device > ') # create main compiler engine for the IBM back-end - eng = MainEngine(IBMBackend(use_hardware=True, token=token num_runs=1024, + 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)) + 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/projectq/_version.py b/projectq/_version.py index 61a0a8d6a..6900d1135 100755 --- a/projectq/_version.py +++ b/projectq/_version.py @@ -13,4 +13,4 @@ # limitations under the License. """Define version number here and read it from setup.py automatically""" -__version__ = "0.4.2" +__version__ = "0.5.0" diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py index 23a07c767..3b16d914e 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib.py +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -192,9 +192,32 @@ def draw(self, qubit_labels=None, drawing_order=None): drawing_order (dict): position of each qubit in the output graphic. Keys: qubit IDs, Values: position of qubit on the qubit line in the graphic. + **kwargs (dict): additional parameters are used to update + the default plot parameters Returns: A tuple containing the matplotlib figure and axes objects + + Note: + Additional keyword arguments can be passed to this + function in order to further customize the figure output + by matplotlib (default value in parentheses): + + - fontsize (14): Font size in pt + - column_spacing (.5): Vertical spacing between two + neighbouring gates (roughly in inches) + - control_radius (.015): Radius of the circle for controls + - labels_margin (1): Margin between labels and begin of + wire (roughly in inches) + - linewidth (1): Width of line + - not_radius (.03): Radius of the circle for X/NOT gates + - gate_offset (.05): Inner margins for gates with a text + representation + - mgate_width (.1): Width of the measurement gate + - swap_delta (.02): Half-size of the SWAP gate + - x_offset (.05): Absolute X-offset for drawing within the axes + - 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) diff --git a/projectq/backends/_circuits/_plot.py b/projectq/backends/_circuits/_plot.py index 009b00ab7..edc0a1f72 100644 --- a/projectq/backends/_circuits/_plot.py +++ b/projectq/backends/_circuits/_plot.py @@ -78,6 +78,27 @@ def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): Note: Numbering of qubit wires starts at 0 at the bottom and increases vertically. + + Note: + Additional keyword arguments can be passed to this + function in order to further customize the figure output + by matplotlib (default value in parentheses): + + - fontsize (14): Font size in pt + - column_spacing (.5): Vertical spacing between two + neighbouring gates (roughly in inches) + - control_radius (.015): Radius of the circle for controls + - labels_margin (1): Margin between labels and begin of + wire (roughly in inches) + - linewidth (1): Width of line + - not_radius (.03): Radius of the circle for X/NOT gates + - gate_offset (.05): Inner margins for gates with a text + representation + - mgate_width (.1): Width of the measurement gate + - swap_delta (.02): Half-size of the SWAP gate + - x_offset (.05): Absolute X-offset for drawing within the axes + - wire_height (1): Vertical spacing between two qubit + wires (roughly in inches) """ if qubit_labels is None: qubit_labels = {qubit_id: r'$|0\rangle$' for qubit_id in qubit_lines} diff --git a/projectq/tests/_drawmpl_test.py b/projectq/tests/_drawmpl_test.py deleted file mode 100644 index 3d78befa6..000000000 --- a/projectq/tests/_drawmpl_test.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -''' - Tests for projectq.backends._circuits._drawer.py. - - To generate the baseline images - run the tests with '--mpl-generate-path=baseline' - - Then run the tests simply with '--mpl' -''' - -import pytest -from projectq import MainEngine -from projectq.ops import * -from projectq.backends import Simulator -from projectq.backends import CircuitDrawerMatplotlib -from projectq.cengines import DecompositionRuleSet, AutoReplacer -import projectq.setups.decompositions - -@pytest.mark.mpl_image_compare -def test_drawer_mpl(): - drawer = CircuitDrawerMatplotlib() - rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - eng = MainEngine(backend=Simulator(), engine_list=[AutoReplacer(rule_set), - drawer]) - ctrl = eng.allocate_qureg(2) - qureg = eng.allocate_qureg(3) - - Swap | (qureg[0], qureg[2]) - C(Swap) | (qureg[0], qureg[1], qureg[2]) - - CNOT | (qureg[0], qureg[2]) - Rx(1.0) | qureg[0] - CNOT | (qureg[1], qureg[2]) - C(X, 2) | (ctrl[0], ctrl[1], qureg[2]) - QFT | qureg - All(Measure) | qureg - - eng.flush() - fig, ax = drawer.draw() - return fig \ No newline at end of file diff --git a/setup.py b/setup.py index b1cb36321..5049a3a06 100755 --- a/setup.py +++ b/setup.py @@ -13,8 +13,9 @@ # - Ants Aasma # - Paul Johnston # - Jonathan Ellis +# - Damien Nguyen (ProjectQ) -# Copyright 2005-2019 SQLAlchemy authors and contributors (see above) +# Copyright 2005-2020 SQLAlchemy and ProjectQ authors and contributors (see above) # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to From dfff0f35294c9a9081359e484fe20e972547f157 Mon Sep 17 00:00:00 2001 From: Ari Jordan <56979766+AriJordan@users.noreply.github.com> Date: Wed, 25 Mar 2020 16:19:49 +0100 Subject: [PATCH 043/113] Fix generated documentation (#360) * Fix some issues with builtin modules and fix some warnings * Move generated documentation files into dedicated folder Co-authored-by: Damien Nguyen --- .gitignore | 1 + docs/conf.py | 6 +++-- docs/package_description.py | 51 +++++++++++++++++-------------------- docs/projectq.rst | 14 +++++----- projectq/meta/_loop.py | 1 + projectq/ops/_command.py | 1 + 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index ca0430898..24d243a5b 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,7 @@ instance/ # Sphinx documentation docs/_build/ +docs/_doc_gen/ docs/doxygen # PyBuilder diff --git a/docs/conf.py b/docs/conf.py index ef791d9ae..169414e6c 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -527,9 +527,11 @@ def linkcode_resolve(domain, info): # ------------------------------------------------------------------------------ # 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) for desc in descriptions: - fname = os.path.join(os.path.dirname(os.path.abspath('__file__')), - 'projectq.{}.rst'.format(desc.name)) + fname = os.path.join(docgen_path, 'projectq.{}.rst'.format(desc.name)) lines = None if os.path.exists(fname): with open(fname, 'r') as fd: diff --git a/docs/package_description.py b/docs/package_description.py index afb18ba18..9980e4235 100644 --- a/docs/package_description.py +++ b/docs/package_description.py @@ -6,8 +6,12 @@ class PackageDescription(object): package_list = [] - def __init__(self, name, desc='', module_special_members='__init__', - submodule_special_members='', submodules_desc='', + def __init__(self, + pkg_name, + desc='', + module_special_members='__init__', + submodule_special_members='', + submodules_desc='', helper_submodules=None): """ Args: @@ -25,24 +29,24 @@ def __init__(self, name, desc='', module_special_members='__init__', automodule_properties) """ - self.name = name + self.name = pkg_name self.desc = desc - if name not in PackageDescription.package_list: - PackageDescription.package_list.append(name) + if pkg_name not in PackageDescription.package_list: + PackageDescription.package_list.append(pkg_name) self.module = sys.modules['projectq.{}'.format(self.name)] self.module_special_members = module_special_members - self.submodule_special_members = module_special_members + self.submodule_special_members = submodule_special_members self.submodules_desc = submodules_desc 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 module_root in obj.__file__) - if name[0] != '_'] + 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 = [] @@ -56,19 +60,10 @@ def __init__(self, name, desc='', module_special_members='__init__', 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)))) + 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()) @@ -100,13 +95,13 @@ 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: @@ -121,8 +116,8 @@ 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: diff --git a/docs/projectq.rst b/docs/projectq.rst index cf69c7ab8..16a948655 100755 --- a/docs/projectq.rst +++ b/docs/projectq.rst @@ -11,12 +11,12 @@ For a detailed documentation of a subpackage or module, click on its name below: :maxdepth: 1 :titlesonly: - projectq.backends - projectq.cengines - projectq.libs - projectq.meta - projectq.ops - projectq.setups - projectq.types + _doc_gen/projectq.backends + _doc_gen/projectq.cengines + _doc_gen/projectq.libs + _doc_gen/projectq.meta + _doc_gen/projectq.ops + _doc_gen/projectq.setups + _doc_gen/projectq.types diff --git a/projectq/meta/_loop.py b/projectq/meta/_loop.py index e28d64ea8..1b7408232 100755 --- a/projectq/meta/_loop.py +++ b/projectq/meta/_loop.py @@ -87,6 +87,7 @@ def run(self): engines, i.e., if .. code-block:: python + is_meta_tag_supported(next_engine, LoopTag) == False """ error_message = ("\n Error. Qubits have been allocated in with " diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index f9268c420..317356c19 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -30,6 +30,7 @@ optimizer to cancel the following two gates .. code-block:: python + Swap | (qubit1, qubit2) Swap | (qubit2, qubit1) From 67ce24bd5d4aa06eb43c8e094c551c89567ef57d Mon Sep 17 00:00:00 2001 From: Ari Jordan <56979766+AriJordan@users.noreply.github.com> Date: Wed, 25 Mar 2020 16:21:31 +0100 Subject: [PATCH 044/113] Fix bugs with matplotlib drawer (#361) * Accept keywords arguments for Matplotlib drawing * fix circ drawer when depth == 1 Co-authored-by: Damien Nguyen --- projectq/backends/_circuits/_drawer_matplotlib.py | 5 +++-- projectq/backends/_circuits/_plot.py | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py index 3b16d914e..1fd61df1f 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib.py +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -181,7 +181,7 @@ def receive(self, command_list): if not self.is_last_engine: self.send([cmd]) - def draw(self, qubit_labels=None, drawing_order=None): + def draw(self, qubit_labels=None, drawing_order=None, **kwargs): """ Generates and returns the plot of the quantum circuit stored so far @@ -228,4 +228,5 @@ def draw(self, qubit_labels=None, drawing_order=None): return to_draw(self._qubit_lines, qubit_labels=qubit_labels, - drawing_order=drawing_order) + drawing_order=drawing_order, + **kwargs) diff --git a/projectq/backends/_circuits/_plot.py b/projectq/backends/_circuits/_plot.py index edc0a1f72..f972f605b 100644 --- a/projectq/backends/_circuits/_plot.py +++ b/projectq/backends/_circuits/_plot.py @@ -222,12 +222,14 @@ def calculate_gate_grid(axes, qubit_lines, plot_params): ] gate_grid = np.array([0] * (depth + 1), dtype=float) - - gate_grid[0] = plot_params['labels_margin'] + (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[-1] = gate_grid[-2] + column_spacing + width_list[-1] * 0.5 + + 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[-1] = gate_grid[-2] + column_spacing + width_list[-1] * 0.5 return gate_grid From 86cd1d298b57576be004f68bdbdcdc7a084b94a8 Mon Sep 17 00:00:00 2001 From: David Bretaud <40793394+dbretaud@users.noreply.github.com> Date: Mon, 20 Apr 2020 08:39:42 +0100 Subject: [PATCH 045/113] Aqt Backend (#353) * Backend implementation for aqt trapped ion quantum computer. * testing files for aqt backend * major fixes on testing files * minor fixes * remove requierement for external mapper; bug fixes * syntax cleaning * name fix * Added setup for AQT backend * Reindent files * Fix typo * Add setup for AQT backend * Increase test coverage for _aqt.py * Increase test coverage * Code cleanup and increase test coverage * More code cleanup and adjust license year * Final test coverage increase * fix comments * fixes on aqt setup test * revert bug Co-authored-by: dbretaud Co-authored-by: Damien Nguyen --- projectq/backends/__init__.py | 2 + projectq/backends/_aqt/__init__.py | 15 + projectq/backends/_aqt/_aqt.py | 319 +++++++++++ projectq/backends/_aqt/_aqt_http_client.py | 293 ++++++++++ .../backends/_aqt/_aqt_http_client_test.py | 525 ++++++++++++++++++ projectq/backends/_aqt/_aqt_test.py | 211 +++++++ projectq/setups/aqt.py | 79 +++ projectq/setups/aqt_test.py | 57 ++ 8 files changed, 1501 insertions(+) create mode 100644 projectq/backends/_aqt/__init__.py create mode 100644 projectq/backends/_aqt/_aqt.py create mode 100644 projectq/backends/_aqt/_aqt_http_client.py create mode 100644 projectq/backends/_aqt/_aqt_http_client_test.py create mode 100644 projectq/backends/_aqt/_aqt_test.py create mode 100644 projectq/setups/aqt.py create mode 100644 projectq/setups/aqt_test.py diff --git a/projectq/backends/__init__.py b/projectq/backends/__init__.py index 4813a52b4..f35a3acec 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -24,9 +24,11 @@ * a resource counter (counts gates and keeps track of the maximal width of the circuit) * an interface to the IBM Quantum Experience chip (and simulator). +* an interface to the AQT trapped ion system (and simulator). """ from ._printer import CommandPrinter from ._circuits import CircuitDrawer, CircuitDrawerMatplotlib from ._sim import Simulator, ClassicalSimulator from ._resource import ResourceCounter from ._ibm import IBMBackend +from ._aqt import AQTBackend diff --git a/projectq/backends/_aqt/__init__.py b/projectq/backends/_aqt/__init__.py new file mode 100644 index 000000000..391c1ff4a --- /dev/null +++ b/projectq/backends/_aqt/__init__.py @@ -0,0 +1,15 @@ +# 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 ._aqt import AQTBackend diff --git a/projectq/backends/_aqt/_aqt.py b/projectq/backends/_aqt/_aqt.py new file mode 100644 index 000000000..23ae5fbd7 --- /dev/null +++ b/projectq/backends/_aqt/_aqt.py @@ -0,0 +1,319 @@ +# 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. +""" Back-end to run quantum program on AQT's API.""" + +import math +import random + +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 ._aqt_http_client import send, retrieve + + +# _rearrange_result & _format_counts imported and modified from qiskit +def _rearrange_result(input_result, length): + bin_input = list(bin(input_result)[2:].rjust(length, '0')) + return ''.join(bin_input)[::-1] + + +def _format_counts(samples, length): + counts = {} + for result in samples: + h_result = _rearrange_result(result, length) + if h_result not in counts: + 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 + + +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): + """ + Initialize the Backend object. + + Args: + use_hardware (bool): If True, the code is run on the AQT quantum + chip (instead of using the AQT simulator) + num_runs (int): Number of runs to collect statistics. + (default is 100, max is usually around 200) + verbose (bool): If True, statistics are printed, in addition to + the measurement result being registered (at the end of the + circuit). + token (str): AQT user API token. + device (str): name of the AQT device to use. simulator By default + num_retries (int): Number of times to retry to obtain + results from the AQT API. (default is 3000) + interval (float, int): Number of seconds between successive + attempts to obtain results from the AQT API. + (default is 1) + retrieve_execution (int): Job ID to retrieve instead of re- + running the circuit (e.g., if previous run timed out). + """ + BasicEngine.__init__(self) + self._reset() + if use_hardware: + self.device = device + else: + self.device = 'simulator' + self._clear = True + self._num_runs = num_runs + self._verbose = verbose + self._token = token + self._num_retries = num_retries + self._interval = interval + self._probabilities = dict() + self._circuit = [] + self._mapper = [] + self._measured_ids = [] + self._allocated_qubits = set() + self._retrieve_execution = retrieve_execution + + def is_available(self, cmd): + """ + Return true if the command can be executed. + + The AQT ion trap can only do Rx,Ry and Rxx. + + Args: + cmd (Command): Command for which to check availability + """ + if get_control_count(cmd) == 0: + if isinstance(cmd.gate, (Rx, Ry, Rxx)): + return True + if cmd.gate in (Measure, Allocate, Deallocate, Barrier): + return True + 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._cmds). + + Args: + cmd: Command to store + """ + if self._clear: + self._probabilities = dict() + self._clear = False + self._circuit = [] + self._allocated_qubits = set() + + gate = cmd.gate + if gate == Allocate: + self._allocated_qubits.add(cmd.qubits[0][0].id) + return + 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) + self._measured_ids += [logical_id] + return + if isinstance(gate, (Rx, Ry, Rxx)): + qubits = [] + qubits.append(cmd.qubits[0][0].id) + if len(cmd.qubits) == 2: + qubits.append(cmd.qubits[1][0].id) + 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(round(angle, 2)) + instruction.append(qubits) + self._circuit.append(instruction) + return + if gate == Barrier: + return + raise Exception('Invalid command: ' + str(cmd)) + + def _logical_to_physical(self, qb_id): + """ + Return the physical location of the qubit with the given logical id. + If no mapper is present then simply returns the qubit ID. + + Args: + qb_id (int): ID of the logical qubit whose position should be + returned. + """ + try: + 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)) + 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)) + 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. + Warning: + Only call this function after the circuit has been executed! + 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). + """ + 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, qubit in enumerate(qureg): + mapped_state[i] = state[self._logical_to_physical(qubit.id)] + 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. + + Send the circuit via the AQT API using the provided user + token / ask for the user token. + """ + # finally: measurements + # NOTE AQT DOESN'T SEEM TO HAVE MEASUREMENT INSTRUCTIONS (no + # intermediate measurements are allowed, so implicit at the end) + # return if no operations. + if self._circuit == []: + return + + n_qubit = max(self._allocated_qubits) + 1 + info = {} + # Hack: AQT instructions specifically need "GATE" string representation + # instead of 'GATE' + info['circuit'] = str(self._circuit).replace("'", '"') + info['nq'] = n_qubit + info['shots'] = self._num_runs + info['backend'] = {'name': self.device} + if self._num_runs > 200: + 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) + else: + 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. + measured = "" + for state in counts: + probability = counts[state] * 1. / self._num_runs + p_sum += probability + star = "" + if p_sum >= P 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 + 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._reset() + except TypeError: + raise Exception("Failed to run the circuit. Aborting.") + + 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() diff --git a/projectq/backends/_aqt/_aqt_http_client.py b/projectq/backends/_aqt/_aqt_http_client.py new file mode 100644 index 000000000..b25dc1137 --- /dev/null +++ b/projectq/backends/_aqt/_aqt_http_client.py @@ -0,0 +1,293 @@ +# 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. + +""" Back-end to run quantum program on AQT cloud platform""" + +import getpass +import signal +import time + +import requests +from requests.compat import urljoin +from requests import Session + +# An AQT token can be requested at: +# https://gateway-portal.aqt.eu/ + +_API_URL = 'https://gateway.aqt.eu/marmot/' + + +class AQT(Session): + def __init__(self): + super(AQT, self).__init__() + self.backends = dict() + self.timeout = 5.0 + self.token = None + + def update_devices_list(self, verbose=False): + """ + Returns: + (list): list of available devices + + Up to my knowledge there is no proper API call for online devices, + so we just assume that the list from AQT portal always up to date + """ + # 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_noise'] = { + 'nq': 11, + 'version': '0.0.1', + '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) + + def is_online(self, device): + # useless at the moment, may change if API evolves + 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 aqt device to use + Returns: + (bool): True if device is big enough, False otherwise + """ + nb_qubit_max = self.backends[device]['nq'] + nb_qubit_needed = info['nq'] + return nb_qubit_needed <= nb_qubit_max, nb_qubit_max, nb_qubit_needed + + def _authenticate(self, token=None): + """ + Args: + token (str): AQT user API token. + """ + if token is None: + token = getpass.getpass(prompt='AQT token > ') + self.headers.update({ + 'Ocp-Apim-Subscription-Key': token, + 'SDK': 'ProjectQ' + }) + self.token = token + + def _run(self, info, device): + 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.raise_for_status() + r_json = req.json() + if r_json['status'] != 'queued': + raise Exception('Error in sending the code online') + execution_id = r_json["id"] + return execution_id + + 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)) + + 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)) + + try: + signal.signal(signal.SIGINT, _handle_sigint_during_get_result) + + 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.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'])) + 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)) + + finally: + if original_sigint_handler is not None: + signal.signal(signal.SIGINT, original_sigint_handler) + + raise Exception("Timeout. The ID of your submitted job is {}.".format( + execution_id)) + + +class DeviceTooSmall(Exception): + pass + + +class DeviceOfflineError(Exception): + pass + + +def show_devices(verbose=False): + """ + Access the list of available devices and their properties (ex: for setup + configuration) + + Args: + verbose (bool): If True, additional information is printed + + Returns: + (list) list of available devices and their properties + """ + aqt_session = AQT() + aqt_session.update_devices_list(verbose=verbose) + return aqt_session.backends + + +def retrieve(device, + token, + jobid, + num_retries=3000, + interval=1, + verbose=False): + """ + Retrieves a previously run job by its ID. + + Args: + device (str): Device on which the code was run / is running. + token (str): AQT user API token. + jobid (str): Id of the job to retrieve + + Returns: + (list) samples form the AQT server + """ + 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) + return res + + +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. + + Args: + 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). + + Returns: + (list) samples form the AQT server + + """ + try: + aqt_session = AQT() + + if verbose: + print("- Authenticating...") + if token is not None: + print('user API token: ' + token) + aqt_session._authenticate(token) + + # check if the device is online + aqt_session.update_devices_list(verbose) + 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.") + raise DeviceOfflineError("Device is offline.") + + # check if the device has enough qubit to run the code + runnable, qmax, qneeded = aqt_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)) + 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) + if verbose: + print("- Done.") + return res + except requests.exceptions.HTTPError as err: + 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) + except KeyError as err: + print("- Failed to parse response:") + print(err) diff --git a/projectq/backends/_aqt/_aqt_http_client_test.py b/projectq/backends/_aqt/_aqt_http_client_test.py new file mode 100644 index 000000000..97bf9fef1 --- /dev/null +++ b/projectq/backends/_aqt/_aqt_http_client_test.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. +"""Tests for projectq.backends._aqt._aqt_http_client.py.""" + +import pytest +import requests +from requests.compat import urljoin + +from projectq.backends._aqt import _aqt_http_client + + +# 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://gateway.aqt.eu/marmot/' + + +def test_is_online(): + token = 'access' + + aqt_session = _aqt_http_client.AQT() + aqt_session._authenticate(token) + aqt_session.update_devices_list() + assert aqt_session.is_online('aqt_simulator') + assert aqt_session.is_online('aqt_simulator_noise') + assert aqt_session.is_online('aqt_device') + assert not aqt_session.is_online('aqt_unknown') + + +def test_show_devices(): + device_list = _aqt_http_client.show_devices(verbose=True) + # TODO: update once the API for getting online devices is available + assert len(device_list) == 3 + + +def test_send_too_many_qubits(monkeypatch): + 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]], ' + '["Y", 3.5, [1]], ["X", 3.5, [2]]]', + '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) + + +def test_send_real_device_online_verbose(monkeypatch): + json_aqt = { + '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 + } + 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]], ' + '["Y", 3.5, [1]], ["X", 3.5, [2]]]', + 'nq': + 3, + 'shots': + 1, + 'backend': { + 'name': 'aqt_simulator' + } + } + token = "access" + shots = 1 + device = "aqt_simulator" + execution_id = '3' + result_ready = [False] + result = "my_result" + request_num = [0] # To assert correct order of calls + + def mocked_requests_put(*args, **kwargs): + class MockRequest: + def __init__(self, body="", url=""): + self.body = body + self.url = url + + class MockPutResponse: + def __init__(self, json_data, text=" "): + self.json_data = json_data + self.text = text + self.request = MockRequest() + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + # Run code + 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): + 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) + + monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) + + def user_password_input(prompt): + if prompt == "AQT token > ": + return token + + 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) + assert res == result + + +def test_send_that_errors_are_caught(monkeypatch): + def mocked_requests_put(*args, **kwargs): + # Test that this error gets caught + raise requests.exceptions.HTTPError + + monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) + # Patch login data + token = 12345 + + def user_password_input(prompt): + if prompt == "AQT token > ": + 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]], ' + '["Y", 3.5, [1]], ["X", 3.5, [2]]]', + 'nq': + 3, + 'shots': + 1, + 'backend': { + 'name': 'aqt_simulator' + } + } + _aqt_http_client.send(info, + device="aqt_simulator", + token=None, + shots=shots, + verbose=True) + + +def test_send_that_errors_are_caught2(monkeypatch): + def mocked_requests_put(*args, **kwargs): + # Test that this error gets caught + raise requests.exceptions.RequestException + + monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) + # Patch login data + token = 12345 + + def user_password_input(prompt): + if prompt == "AQT token > ": + 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]], ' + '["Y", 3.5, [1]], ["X", 3.5, [2]]]', + 'nq': + 3, + 'shots': + 1, + 'backend': { + 'name': 'aqt_simulator' + } + } + _aqt_http_client.send(info, + device="aqt_simulator", + token=None, + shots=shots, + verbose=True) + + +def test_send_that_errors_are_caught3(monkeypatch): + def mocked_requests_put(*args, **kwargs): + # Test that this error gets caught + raise KeyError + + monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) + # Patch login data + token = 12345 + + def user_password_input(prompt): + if prompt == "AQT token > ": + 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]], ' + '["Y", 3.5, [1]], ["X", 3.5, [2]]]', + 'nq': + 3, + 'shots': + 1, + 'backend': { + 'name': 'aqt_simulator' + } + } + _aqt_http_client.send(info, + device="aqt_simulator", + token=None, + shots=shots, + verbose=True) + + +def test_send_that_errors_are_caught4(monkeypatch): + json_aqt = { + 'data': '[]', + 'access_token': 'access', + 'repetitions': 1, + '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): + class MockRequest: + def __init__(self, body="", url=""): + self.body = body + self.url = url + + class MockPutResponse: + def __init__(self, json_data, text=" "): + self.json_data = json_data + self.text = text + self.request = MockRequest() + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + # Run code + 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) + + +def test_timeout_exception(monkeypatch): + json_aqt = { + '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 + } + 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]], ' + '["Y", 3.5, [1]], ["X", 3.5, [2]]]', + 'nq': + 3, + 'shots': + 1, + 'backend': { + 'name': 'aqt_simulator' + } + } + token = "access" + shots = 1 + device = "aqt_simulator" + execution_id = '123e' + tries = [0] + + def mocked_requests_put(*args, **kwargs): + class MockRequest: + def __init__(self, body="", url=""): + self.body = body + self.url = url + + class MockPutResponse: + def __init__(self, json_data, text=" "): + self.json_data = json_data + self.text = text + self.request = MockRequest() + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + # 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): + tries[0] += 1 + return MockPutResponse({"status": 'running'}, 200) + + monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) + + def user_password_input(prompt): + if prompt == "AQT token > ": + return token + + monkeypatch.setattr("getpass.getpass", user_password_input) + + # Code to test: + _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) + 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" + request_num = [0] # To assert correct order of calls + + def mocked_requests_put(*args, **kwargs): + class MockRequest: + def __init__(self, body="", url=""): + self.body = body + self.url = url + + class MockPutResponse: + def __init__(self, json_data, text=" "): + self.json_data = json_data + self.text = text + self.request = MockRequest() + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + # 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): + 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) + + monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) + + def user_password_input(prompt): + if prompt == "AQT token > ": + return token + + monkeypatch.setattr("getpass.getpass", user_password_input) + + # 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") + 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 + + def mocked_requests_put(*args, **kwargs): + class MockRequest: + def __init__(self, body="", url=""): + self.body = body + self.url = url + + class MockPutResponse: + def __init__(self, json_data, text=" "): + self.json_data = json_data + self.text = text + self.request = MockRequest() + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + # 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): + 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({"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.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 new file mode 100644 index 000000000..7006f5c6a --- /dev/null +++ b/projectq/backends/_aqt/_aqt_test.py @@ -0,0 +1,211 @@ +# 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.backends._aqt._aqt.py.""" + +import pytest +import math + +from projectq import MainEngine +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) + + +# 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") + + +@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, )) + assert aqt_backend.is_available(cmd) == is_available + + +@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) + assert aqt_backend.is_available(cmd) == is_available + cmd = Command(eng, Rxx(0.5), (qubit1, ), controls=qureg) + assert aqt_backend.is_available(cmd) == is_available + + +def test_aqt_backend_init(): + backend = _aqt.AQTBackend(verbose=True, use_hardware=True) + assert len(backend._circuit) == 0 + + +def test_aqt_empty_circuit(): + backend = _aqt.AQTBackend(verbose=True) + eng = MainEngine(backend=backend) + eng.flush() + + +def test_aqt_invalid_command(): + backend = _aqt.AQTBackend(verbose=True) + + qb = WeakQubitRef(None, 1) + cmd = Command(None, gate=S, qubits=[(qb, )]) + with pytest.raises(Exception): + backend.receive([cmd]) + + +def test_aqt_sent_error(monkeypatch): + # patch send + def mock_send(*args, **kwargs): + raise TypeError + + monkeypatch.setattr(_aqt, "send", mock_send) + + backend = _aqt.AQTBackend(verbose=True) + eng = MainEngine(backend=backend) + qubit = eng.allocate_qubit() + Rx(0.5) | qubit + with pytest.raises(Exception): + qubit[0].__del__() + eng.flush() + # atexit sends another FlushGate, therefore we remove the backend: + dummy = DummyEngine() + dummy.is_last_engine = True + eng.next_engine = dummy + + +def test_aqt_too_many_runs(): + backend = _aqt.AQTBackend(num_runs=300, verbose=True) + eng = MainEngine(backend=backend, engine_list=[]) + with pytest.raises(Exception): + qubit = eng.allocate_qubit() + Rx(math.pi / 2) | qubit + eng.flush() + + +def test_aqt_retrieve(monkeypatch): + # patch send + 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) + + eng = MainEngine(backend=backend, engine_list=[]) + 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 and no mapper + invalid_qubit = [Qubit(eng, 10)] + with pytest.raises(RuntimeError): + eng.backend.get_probabilities(invalid_qubit) + + +def test_aqt_backend_functional_test(monkeypatch): + correct_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]], ' + '["Y", 3.5, [1]], ["X", 3.5, [2]]]', + 'nq': + 3, + 'shots': + 10, + 'backend': { + 'name': 'simulator' + } + } + + def mock_send(*args, **kwargs): + assert args[0] == correct_info + return [0, 6, 0, 6, 0, 0, 0, 6, 0, 6] + + monkeypatch.setattr(_aqt, "send", mock_send) + + backend = _aqt.AQTBackend(verbose=True, num_runs=10) + # no circuit has been executed -> raises exception + with pytest.raises(RuntimeError): + backend.get_probabilities([]) + + mapper = BasicMapperEngine() + res = dict() + for i in range(4): + res[i] = i + mapper.current_mapping = res + + eng = MainEngine(backend=backend, engine_list=[mapper]) + + 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] + 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['11'] == pytest.approx(0.4) + assert prob_dict['00'] == pytest.approx(0.6) + + # Unknown qubit and no mapper + invalid_qubit = [Qubit(eng, 10)] + with pytest.raises(RuntimeError): + eng.backend.get_probabilities(invalid_qubit) + + with pytest.raises(RuntimeError): + eng.backend.get_probabilities(eng.allocate_qubit()) diff --git a/projectq/setups/aqt.py b/projectq/setups/aqt.py new file mode 100644 index 000000000..38f0b4591 --- /dev/null +++ b/projectq/setups/aqt.py @@ -0,0 +1,79 @@ +# 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. +""" +Defines a setup allowing to compile code for the AQT trapped ion devices: +->The 4 qubits device +->The 11 qubits simulator +->The 11 qubits noisy simulator + +It provides the `engine_list` for the `MainEngine' based on the requested +device. Decompose the circuit into a Rx/Ry/Rxx gate set that will be +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.backends._aqt._aqt_http_client import show_devices + + +def get_engine_list(token=None, device=None): + # Access to the hardware properties via show_devices + # Can also be extended to take into account gate fidelities, new available + # gate, etc.. + devices = show_devices(token) + aqt_setup = [] + if device not in devices: + 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 + # restrictedgateset setup for coherence + mapper = BasicMapperEngine() + # Note: Manual Mapper doesn't work, because its map is updated only if + # gates are applied if gates in the register are not used, then it + # will lead to state errors + res = dict() + for i in range(devices[device]['nq']): + res[i] = i + mapper.current_mapping = res + aqt_setup = [mapper] + else: + # If there is an online device not handled into ProjectQ it's not too + # bad, the engine_list can be constructed manually with the + # appropriate mapper and the 'coupling_map' parameter + raise DeviceNotHandledError('Device not yet fully handled by ProjectQ') + + # 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.extend(aqt_setup) + return setup + + +class DeviceOfflineError(Exception): + pass + + +class DeviceNotHandledError(Exception): + pass + + diff --git a/projectq/setups/aqt_test.py b/projectq/setups/aqt_test.py new file mode 100644 index 000000000..341a0cc66 --- /dev/null +++ b/projectq/setups/aqt_test.py @@ -0,0 +1,57 @@ +# 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.setup.aqt.""" + +import pytest + + +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 + } + } + + monkeypatch.setattr(projectq.setups.aqt, "show_devices", mock_show_devices) + engines_simulator = projectq.setups.aqt.get_engine_list( + device='aqt_simulator') + assert len(engines_simulator) == 13 + + +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 + } + } + + monkeypatch.setattr(projectq.setups.aqt, "show_devices", mock_show_devices) + with pytest.raises(projectq.setups.aqt.DeviceOfflineError): + projectq.setups.aqt.get_engine_list(device='simulator') + with pytest.raises(projectq.setups.aqt.DeviceNotHandledError): + projectq.setups.aqt.get_engine_list(device='aqt_imaginary') From dfaf7256feed6f08a3119a1115c9855a37a8345f Mon Sep 17 00:00:00 2001 From: David Bretaud <40793394+dbretaud@users.noreply.github.com> Date: Mon, 20 Apr 2020 08:39:53 +0100 Subject: [PATCH 046/113] IBM backend update (#366) * update ibm backend protocol and cleanup ibm files * update README regarding ibmq * Reindent _ibm_http_client.py * Some code cleanup * Ignore signal handler for test coverage Co-authored-by: dbretaud Co-authored-by: Damien Nguyen --- README.rst | 8 +- examples/ibm16.py | 43 ---- projectq/backends/_ibm/_ibm_http_client.py | 223 ++++++++++++------ .../backends/_ibm/_ibm_http_client_test.py | 190 ++++++++++++--- projectq/setups/ibm16.py | 70 ------ projectq/setups/ibm16_test.py | 47 ---- 6 files changed, 318 insertions(+), 263 deletions(-) delete mode 100755 examples/ibm16.py delete mode 100755 projectq/setups/ibm16.py delete mode 100644 projectq/setups/ibm16_test.py diff --git a/README.rst b/README.rst index 614f23ad4..5b5dc5f66 100755 --- a/README.rst +++ b/README.rst @@ -106,9 +106,13 @@ To run a program on the IBM Quantum Experience chips, all one has to do is choos .. code-block:: python - compiler_engines = projectq.setups.ibm16.get_engine_list() + import projectq.setups.ibm + from projectq.backends import IBMBackend + + device='ibmq_16_melbourne' + compiler_engines = projectq.setups.ibm.get_engine_list(device=device) eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024, - verbose=False, device='ibmqx5'), + verbose=False, device=device), engine_list=compiler_engines) diff --git a/examples/ibm16.py b/examples/ibm16.py deleted file mode 100755 index 894c644ce..000000000 --- a/examples/ibm16.py +++ /dev/null @@ -1,43 +0,0 @@ -import projectq.setups.ibm16 -from projectq.backends import IBMBackend -from projectq.ops import All, Entangle, Measure -from projectq import MainEngine - - -def run_test(eng): - """ - Runs a test circuit on the provided compiler engine. - - Args: - eng (MainEngine): Main compiler engine to use. - - Returns: - measurement (list): List of measurement outcomes. - """ - # allocate the quantum register to entangle - qureg = eng.allocate_qureg(8) - - 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, probability in sorted(list(results.items())): - print("Measured {} with p = {}.".format(state, probability)) - - # return one (random) measurement outcome. - return [int(q) for q in qureg] - - -if __name__ == "__main__": - # create main compiler engine for the 16-qubit IBM back-end - eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024, - verbose=False, device='ibmqx5'), - engine_list=projectq.setups.ibm16.get_engine_list()) - # run the circuit and print the result - print(run_test(eng)) diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 98751bf90..a6d2ab54a 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -13,17 +13,20 @@ # limitations under the License. # 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 +# api documentation does not exist and has to be deduced from the qiskit code +# source at: https://github.com/Qiskit/qiskit-ibmq-provider import getpass import time import signal +import uuid + import requests 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 @@ -34,7 +37,6 @@ 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() @@ -59,11 +61,11 @@ def get_list_devices(self, verbose=False): request.raise_for_status() r_json = request.json() self.backends = dict() - for el in r_json: - self.backends[el['backend_name']] = { - 'nq': el['n_qubits'], - 'coupling_map': el['coupling_map'], - 'version': el['backend_version'] + for obj in r_json: + self.backends[obj['backend_name']] = { + 'nq': obj['n_qubits'], + 'coupling_map': obj['coupling_map'], + 'version': obj['backend_version'] } if verbose: @@ -125,69 +127,110 @@ def _authenticate(self, token=None): self.params.update({'access_token': r_json['id']}) def _run(self, info, device): - post_job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs' - shots = info['shots'] - n_classical_reg = info['nq'] - n_qubits = self.backends[device]['nq'] - version = self.backends[device]['version'] - instructions = info['json'] - maxcredit = info['maxCredits'] - c_label = [] - q_label = [] - for i in range(n_classical_reg): - c_label.append(['c', i]) - for i in range(n_qubits): - q_label.append(['q', i]) - experiment = [{ - 'header': { - 'qreg_sizes': [['q', n_qubits]], - 'n_qubits': n_qubits, - 'memory_slots': n_classical_reg, - 'creg_sizes': [['c', n_classical_reg]], - 'clbit_labels': c_label, - 'qubit_labels': q_label, - 'name': 'circuit0' - }, - 'config': { - 'n_qubits': n_qubits, - 'memory_slots': n_classical_reg - }, - 'instructions': instructions - }] - # Note: qobj_id is not necessary in projectQ, so fixed string for now - argument = { + """ + Run the quantum code to the IBMQ machine. + Update since March2020: 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. + + Args: + info (dict): dictionary sent by the backend containing the code to + run + device (str): name of the ibm device to use + + Returns: + (tuple): (str) Execution Id + + """ + + # STEP1: Obtain most of the URLs for handling communication with + # quantum device + json_step1 = { 'data': None, 'json': { - 'qObject': { - 'type': 'QASM', - 'schema_version': '1.1.0', - 'config': { - 'shots': shots, - 'max_credits': maxcredit, - 'n_qubits': n_qubits, - 'memory_slots': n_classical_reg, - 'memory': False, - 'parameter_binds': [] - }, - 'experiments': experiment, - 'header': { - 'backend_version': version, - 'backend_name': device - }, - 'qobj_id': 'e72443f5-7752-4e32-9ac8-156f1f3fee18' - }, 'backend': { 'name': device }, - 'shots': shots + 'allowObjectStorage': True, + 'shareLevel': 'none' }, 'timeout': (self.timeout, None) } - request = super(IBMQ, self).post(urljoin(_API_URL, post_job_url), - **argument) + request = super(IBMQ, self).post( + urljoin(_API_URL, 'Network/ibm-q/Groups/open/Projects/main/Jobs'), + **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'] + + # 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() - execution_id = r_json["id"] + + # STEP3: WE USE THE ENDPOINT TO GET THE UPLOT LINK + n_classical_reg = info['nq'] + # hack: easier to restrict labels to measured qubits + n_qubits = n_classical_reg # self.backends[device]['nq'] + instructions = info['json'] + maxcredit = info['maxCredits'] + c_label = [["c", i] for i in range(n_classical_reg)] + q_label = [["q", i] for i in range(n_qubits)] + + # hack: the data value in the json quantum code is a string + instruction_str = str(instructions).replace('\'', '\"') + data = '{"qobj_id": "' + str(uuid.uuid4()) + '", ' + data += '"header": {"backend_name": "' + device + '", ' + 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.1.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 += '"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"}, "instructions": ' + instruction_str + + '}]}') + + json_step3 = { + 'data': data, + 'params': { + 'access_token': None + }, + 'timeout': (5.0, None) + } + request = super(IBMQ, self).put(r_json['url'], **json_step3) + request.raise_for_status() + + # STEP4: CONFIRM UPLOAD + json_step4 = { + '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) + request.raise_for_status() + r_json = request.json() + execution_id = upload_endpoint_url.split('/')[-2] + return execution_id def _get_result(self, @@ -205,7 +248,7 @@ def _get_result(self, original_sigint_handler = signal.getsignal(signal.SIGINT) - def _handle_sigint_during_get_result(*_): + def _handle_sigint_during_get_result(*_): # pragma: no cover raise Exception( "Interrupted. The ID of your submitted job is {}.".format( execution_id)) @@ -214,20 +257,62 @@ def _handle_sigint_during_get_result(*_): signal.signal(signal.SIGINT, _handle_sigint_during_get_result) for retries in range(num_retries): - argument = { + # 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), - **argument) + **json_step5) request.raise_for_status() r_json = request.json() + acceptable_status = ['VALIDATING', 'VALIDATED', 'RUNNING'] if r_json['status'] == 'COMPLETED': - return r_json['qObjectResult']['results'][0] - if r_json['status'] != 'RUNNING': - raise Exception("Error while running the code: {}.".format( - r_json['status'])) + # STEP6: Get the endpoint to get the result + json_step6 = { + 'allow_redirects': True, + 'timeout': (self.timeout, None) + } + request = super(IBMQ, self).get( + 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) + } + 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) + } + request = super(IBMQ, self).post( + 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'])) time.sleep(interval) if self.is_online(device) and retries % 60 == 0: self.get_list_devices() diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index eb56b1ee4..5f43b5d28 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -74,11 +74,10 @@ 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] == 4)): + and (request_num[0] == 1 or request_num[0] == 7)): 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)]) @@ -88,12 +87,17 @@ def raise_for_status(self): 'backend_version': '0.1.547', 'n_qubits': 32 }], 200) - # Getting result + #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( _API_URL, "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}". format(execution_id=execution_id)) and not result_ready[0] - and request_num[0] == 3): + and request_num[0] == 6): result_ready[0] = True request_num[0] += 1 return MockResponse({"status": "RUNNING"}, 200) @@ -101,14 +105,25 @@ def raise_for_status(self): _API_URL, "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}". format(execution_id=execution_id)) and result_ready[0] - and request_num[0] == 5): + and request_num[0] == 8): + request_num[0] += 1 return MockResponse( - { - 'qObjectResult': { - "results": [result] - }, - "status": "COMPLETED" - }, 200) + {"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] == 9): + request_num[0] += 1 + return MockResponse( + {"url": "result_download_url"}, 200) + #STEP7 + elif (args[1] == "result_download_url" + and request_num[0] == 10): + request_num[0] += 1 + return MockResponse( + {"results": [result]}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -134,16 +149,61 @@ def raise_for_status(self): and request_num[0] == 0): request_num[0] += 1 return MockPostResponse({"userId": user_id, "id": access_token}) - # Run code - elif (args[1] == urljoin(_API_URL, jobs_url) and kwargs["data"] is None - and kwargs["json"]["backend"]["name"] == device - and kwargs["json"]["qObject"]['config']['shots'] == shots + # STEP1 + elif (args[1] == urljoin(_API_URL, jobs_url) and request_num[0] == 2): request_num[0] += 1 - return MockPostResponse({"id": execution_id}) + answer1={'objectStorageInfo':{ + 'downloadQObjectUrlEndpoint':'url_dld_endpoint', + 'uploadQobjectUrlEndpoint':'/'+execution_id+'/jobUploadUrl', + 'uploadUrl':'url_upld'} + } + return MockPostResponse(answer1,200) + + # STEP4 + elif (args[1] == "/"+execution_id+"/jobDataUploaded" + and request_num[0] == 5): + request_num[0] += 1 + 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)) + and request_num[0] == 11): + request_num[0] += 1 + return MockPostResponse( + {}, 200) + + def mocked_requests_put(*args, **kwargs): + class MockRequest: + def __init__(self, url=""): + self.url = url + + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + self.request = MockRequest() + self.text = "" + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + # STEP3 + if (args[1] == "s3_url" + and request_num[0] == 4): + 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) def user_password_input(prompt): if prompt == "IBM QE token > ": @@ -157,6 +217,7 @@ def user_password_input(prompt): token=None, shots=shots, verbose=True) + assert res == result json_qasm['nq'] = 40 request_num[0] = 0 @@ -451,6 +512,7 @@ def test_timeout_exception(monkeypatch): } json_qasm = qasms tries = [0] + execution_id = '3' def mocked_requests_get(*args, **kwargs): class MockResponse: @@ -476,11 +538,21 @@ def raise_for_status(self): 'n_qubits': 32 }], 200) job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format( - "123e") + 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"): + 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))): + return MockResponse({"status": "RUNNING"}, 200) + def mocked_requests_post(*args, **kwargs): class MockRequest: def __init__(self, url=""): @@ -501,11 +573,45 @@ def raise_for_status(self): jobs_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs' if args[1] == _AUTH_API_URL: return MockPostResponse({"userId": "1", "id": "12"}) - if args[1] == urljoin(_API_URL, jobs_url): - return MockPostResponse({"id": "123e"}) + + # STEP1 + elif (args[1] == urljoin(_API_URL, jobs_url)): + answer1={'objectStorageInfo':{ + 'downloadQObjectUrlEndpoint':'url_dld_endpoint', + 'uploadQobjectUrlEndpoint':'/'+execution_id+'/jobUploadUrl', + 'uploadUrl':'url_upld'} + } + return MockPostResponse(answer1,200) + + # STEP4 + elif (args[1] == "/"+execution_id+"/jobDataUploaded"): + return MockPostResponse({}, 200) + + def mocked_requests_put(*args, **kwargs): + class MockRequest: + def __init__(self, url=""): + self.url = url + + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + self.request = MockRequest() + self.text = "" + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + # STEP3 + if (args[1] == "s3_url"): + 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) _ibm_http_client.time.sleep = lambda x: x with pytest.raises(Exception) as excinfo: @@ -515,7 +621,7 @@ def raise_for_status(self): shots=1, num_retries=10, verbose=False) - assert "123e" in str(excinfo.value) # check that job id is in exception + assert execution_id in str(excinfo.value) # check that job id is in exception assert tries[0] > 0 @@ -609,6 +715,7 @@ def raise_for_status(self): def test_retrieve(monkeypatch): request_num = [0] + execution_id='3' def mocked_requests_get(*args, **kwargs): class MockResponse: @@ -631,20 +738,31 @@ def raise_for_status(self): 'backend_version': '0.1.547', 'n_qubits': 32 }], 200) - job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/{}'.format( - "123e") - if args[1] == urljoin(_API_URL, job_url) and request_num[0] < 1: + + #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): request_num[0] += 1 return MockResponse({"status": "RUNNING"}, 200) - elif args[1] == urljoin(_API_URL, job_url): + elif (args[1] == urljoin( + _API_URL, + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}". + format(execution_id=execution_id))): return MockResponse( - { - "qObjectResult": { - 'qasm': 'qasm', - 'results': ['correct'] - }, - "status": "COMPLETED" - }, 200) + {"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: @@ -666,11 +784,19 @@ 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) + 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="123e") + jobid=execution_id) assert res == 'correct' diff --git a/projectq/setups/ibm16.py b/projectq/setups/ibm16.py deleted file mode 100755 index a00551a21..000000000 --- a/projectq/setups/ibm16.py +++ /dev/null @@ -1,70 +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. - -""" -Defines a setup useful for the IBM QE chip with 16 qubits. - -It provides the `engine_list` for the `MainEngine`, and contains an -AutoReplacer with most of the gate decompositions of ProjectQ, among others -it includes: - - * Controlled z-rotations --> Controlled NOTs and single-qubit rotations - * Toffoli gate --> CNOT and single-qubit gates - * m-Controlled global phases --> (m-1)-controlled phase-shifts - * Global phases --> ignore - * (controlled) Swap gates --> CNOTs and Toffolis - * Arbitrary single qubit gates --> Rz and Ry - * Controlled arbitrary single qubit gates --> Rz, Ry, and CNOT gates - -Moreover, it contains `LocalOptimizers`. -""" - -import projectq -import projectq.libs.math -import projectq.setups.decompositions -from projectq.cengines import (AutoReplacer, - DecompositionRuleSet, - GridMapper, - InstructionFilter, - LocalOptimizer, - SwapAndCNOTFlipper, - TagRemover) -from projectq.setups.grid import high_level_gates - - -ibmqx5_connections = set([(1, 0), (1, 2), (2, 3), (3, 4), (3, 14), (5, 4), - (6, 5), (6, 7), (6, 11), (7, 10), (8, 7), (9, 8), - (9, 10), (11, 10), (12, 5), (12, 11), (12, 13), - (13, 4), (13, 14), (15, 0), (15, 2), (15, 14)]) - - -grid_to_physical = {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 0, - 9: 15, 10: 14, 11: 13, 12: 12, 13: 11, 14: 10, 15: 9} - - -def get_engine_list(): - rule_set = DecompositionRuleSet(modules=[projectq.libs.math, - projectq.setups.decompositions]) - return [TagRemover(), - LocalOptimizer(5), - AutoReplacer(rule_set), - InstructionFilter(high_level_gates), - TagRemover(), - LocalOptimizer(5), - AutoReplacer(rule_set), - TagRemover(), - GridMapper(2, 8, grid_to_physical), - LocalOptimizer(5), - SwapAndCNOTFlipper(ibmqx5_connections), - LocalOptimizer(5)] diff --git a/projectq/setups/ibm16_test.py b/projectq/setups/ibm16_test.py deleted file mode 100644 index cae50b168..000000000 --- a/projectq/setups/ibm16_test.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Tests for projectq.setup.ibm16.""" - -import projectq -import projectq.setups.ibm16 -from projectq import MainEngine -from projectq.cengines import GridMapper, SwapAndCNOTFlipper, DummyEngine -from projectq.libs.math import AddConstant -from projectq.ops import QFT, get_inverse - - -def test_mappers_in_cengines(): - found = 0 - for engine in projectq.setups.ibm16.get_engine_list(): - if isinstance(engine, GridMapper): - found |= 1 - if isinstance(engine, SwapAndCNOTFlipper): - found |= 2 - assert found == 3 - - -def test_high_level_gate_set(): - mod_list = projectq.setups.ibm16.get_engine_list() - saving_engine = DummyEngine(save_commands=True) - mod_list = mod_list[:6] + [saving_engine] + mod_list[6:] - eng = MainEngine(DummyEngine(), - engine_list=mod_list) - qureg = eng.allocate_qureg(3) - AddConstant(3) | qureg - QFT | qureg - eng.flush() - received_gates = [cmd.gate for cmd in saving_engine.received_commands] - assert sum([1 for g in received_gates if g == QFT]) == 1 - assert get_inverse(QFT) not in received_gates - assert AddConstant(3) not in received_gates From f88a0f620f71f724a9acfab684f9cffc84e72f61 Mon Sep 17 00:00:00 2001 From: Ari Jordan <56979766+AriJordan@users.noreply.github.com> Date: Mon, 20 Apr 2020 14:06:22 +0200 Subject: [PATCH 047/113] Add histogram plotting feature for simulator (#362) * Add histogram plotting function for Simulator * Apply suggestion to projectq/libs/__init__.py * Apply suggestion to projectq/libs/hist/__init__.py * Update _histogram.py * Apply suggestion to projectq/libs/hist/_histogram.py * Apply suggestion to projectq/libs/hist/_histogram.py * Add docstrings * All suggestions applied. * Minor fixes for _histogram.py * Minor modifications to _histogram_test.py * Check that some message was printed in case of too many qubits * Update __init__.py Trigger Travis-CI * Add print_function for Python 2.7 * Remove unsupported 'flush' keyword for Python 2.7 * Update bellpair example with histogram plot of probabilities * Add support for other backends * Update IBM examples with histogram plots * Fix error in conf.py * Increase test coverage Co-authored-by: Ari Jordan 80055203 Co-authored-by: Damien Nguyen --- docs/conf.py | 2 +- examples/bellpair_circuit.py | 11 ++- examples/ibm.py | 28 +++--- examples/ibmq_tutorial.ipynb | 90 +++++++++++++---- projectq/libs/hist/__init__.py | 20 ++++ projectq/libs/hist/_histogram.py | 78 +++++++++++++++ projectq/libs/hist/_histogram_test.py | 135 ++++++++++++++++++++++++++ pytest.ini | 1 + 8 files changed, 333 insertions(+), 32 deletions(-) create mode 100644 projectq/libs/hist/__init__.py create mode 100644 projectq/libs/hist/_histogram.py create mode 100644 projectq/libs/hist/_histogram_test.py diff --git a/docs/conf.py b/docs/conf.py index 169414e6c..4083653fa 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,3 +1,4 @@ + #!/usr/bin/env python3 # -*- coding: utf-8 -*- # @@ -28,7 +29,6 @@ import projectq.setups.default import projectq.setups.grid import projectq.setups.ibm -import projectq.setups.ibm16 import projectq.setups.linear import projectq.setups.restrictedgateset import projectq.setups.decompositions diff --git a/examples/bellpair_circuit.py b/examples/bellpair_circuit.py index 108e224fa..96c001ffe 100755 --- a/examples/bellpair_circuit.py +++ b/examples/bellpair_circuit.py @@ -1,13 +1,20 @@ +import matplotlib.pyplot as plt + from projectq import MainEngine from projectq.backends import CircuitDrawer +from projectq.setups.default import get_engine_list +from projectq.libs.hist import histogram from teleport import create_bell_pair # create a main compiler engine drawing_engine = CircuitDrawer() -eng = MainEngine(drawing_engine) +eng = MainEngine(engine_list = get_engine_list() + [drawing_engine]) -create_bell_pair(eng) +qb0, qb1 = create_bell_pair(eng) eng.flush() print(drawing_engine.get_latex()) + +histogram(eng.backend, [qb0, qb1]) +plt.show() diff --git a/examples/ibm.py b/examples/ibm.py index 11a81a832..eafd50b80 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -1,8 +1,11 @@ -import projectq.setups.ibm +import matplotlib.pyplot as plt +import getpass + +from projectq import MainEngine from projectq.backends import IBMBackend +from projectq.libs.hist import histogram from projectq.ops import Measure, Entangle, All -from projectq import MainEngine -import getpass +import projectq.setups.ibm def run_entangle(eng, num_qubits=3): @@ -29,9 +32,12 @@ def run_entangle(eng, num_qubits=3): 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])) + # 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] @@ -39,11 +45,11 @@ def run_entangle(eng, num_qubits=3): if __name__ == "__main__": #devices commonly available : - #ibmq_16_melbourne (15 qubit) - #ibmq_essex (5 qubit) - #ibmq_qasm_simulator (32 qubits) - device = None #replace by the IBM device name you want to use - token = None #replace by the token given by IBMQ + # ibmq_16_melbourne (15 qubit) + # ibmq_essex (5 qubit) + # ibmq_qasm_simulator (32 qubits) + device = None # replace by the IBM device name you want to use + token = None # replace by the token given by IBMQ if token is None: token = getpass.getpass(prompt='IBM Q token > ') if device is None: diff --git a/examples/ibmq_tutorial.ipynb b/examples/ibmq_tutorial.ipynb index 464612ca5..358bdcf9e 100644 --- a/examples/ibmq_tutorial.ipynb +++ b/examples/ibmq_tutorial.ipynb @@ -2,14 +2,18 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": false + }, "source": [ "# Running ProjectQ code on IBM Q devices" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": false + }, "source": [ "In this tutorial, we will see how to run code on IBM Q devices directly from within ProjectQ. All that is needed is an IBM Q Experience user account. To sign up, visit https://quantumexperience.ng.bluemix.net/.\n", "\n", @@ -19,10 +23,24 @@ "First, we import all necessary operations (`Entangle`, measurement), the back-end (`IBMBackend`), and the main compiler engine (`MainEngine`). The Entangle operation is defined as a Hadamard gate on the first qubit (creates an equal superposition of |0> and |1>), followed by controlled NOT gates acting on all other qubits controlled on the first." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt" + ] + }, { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "import projectq.setups.ibm\n", @@ -33,7 +51,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": false + }, "source": [ "Next, we instantiate a main compiler engine using the IBM Q back-end and the predefined compiler engines which take care of the qubit placement, translation of operations, etc.:" ] @@ -41,7 +61,9 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024,\n", @@ -51,7 +73,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": false + }, "source": [ "If `use_hardware` is set to `False`, it will use the IBM Q simulator instead. `num_runs` specifies the number of samples to collect for statistics, `verbose=True` would output additional information which may be helpful for debugging, and the device parameter lets users choose between the two devices (\"ibmqx4\" and \"ibmqx5\").\n", "\n", @@ -61,7 +85,9 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", @@ -105,9 +131,12 @@ " eng.flush()\n", "\n", " # access the probabilities via the back-end:\n", - " results = eng.backend.get_probabilities(qureg)\n", - " for state in results:\n", - " print(\"Measured {} with p = {}.\".format(state, results[state]))\n", + " # results = eng.backend.get_probabilities(qureg)\n", + " # for state in results:\n", + " # print(\"Measured {} with p = {}.\".format(state, results[state]))\n", + " # or plot them directly:\n", + " histogram(eng.backend, qureg)\n", + " plt.show()\n", "\n", " # return one (random) measurement outcome.\n", " return [int(q) for q in qureg]\n", @@ -117,7 +146,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": false + }, "source": [ "## Retrieving a timed-out execution\n", "Sometimes, the queue is very long and the waiting times may exceed the limit of 5 minutes. In this case, ProjectQ will raise an exception which contains the job ID, as could be seen above, where the job ID was `5b557df2306393003b746da2`." @@ -125,7 +156,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": false + }, "source": [ "In order to still retrieve all results at a later point in time, one can simply re-run the entire program using a slightly modified back-end:" ] @@ -133,7 +166,9 @@ { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", @@ -193,7 +228,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": false + }, "source": [ "## Entangling more qubits: Using ibmqx5\n", "\n", @@ -209,7 +246,9 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [], "source": [ "import projectq.setups.ibm16 # import setup which contains the grid mapper\n", @@ -220,7 +259,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": false + }, "source": [ "and then re-run the example from before via `run_entangle(eng, num_qubits)`. If an execution times out, it can also be retrieved at a later point by providing the additional `retrieve_execution=\"execution_id\"` parameter to the IBMBackend (but this time with `device='ibmqx5'`)." ] @@ -228,7 +269,9 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", @@ -488,8 +531,18 @@ ], "metadata": { "kernelspec": { + "argv": [ + "python", + "-m", + "ipykernel_launcher", + "-f", + "{connection_file}" + ], "display_name": "Python 3", + "env": null, + "interrupt_mode": "signal", "language": "python", + "metadata": null, "name": "python3" }, "language_info": { @@ -503,7 +556,8 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" - } + }, + "name": "ibmq_tutorial.ipynb" }, "nbformat": 4, "nbformat_minor": 2 diff --git a/projectq/libs/hist/__init__.py b/projectq/libs/hist/__init__.py new file mode 100644 index 000000000..088766263 --- /dev/null +++ b/projectq/libs/hist/__init__.py @@ -0,0 +1,20 @@ +# 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. + +""" +contains a function to plot measurement outcome probabilities +as a histogram for the simulator +""" + +from ._histogram import histogram diff --git a/projectq/libs/hist/_histogram.py b/projectq/libs/hist/_histogram.py new file mode 100644 index 000000000..77b0d2c20 --- /dev/null +++ b/projectq/libs/hist/_histogram.py @@ -0,0 +1,78 @@ +# 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 __future__ import print_function +import matplotlib.pyplot as plt + +from projectq.backends import Simulator + + +def histogram(backend, qureg): + """ + Make a measurement outcome probability histogram for the given qubits. + + Args: + backend (BasicEngine): A ProjectQ backend + qureg (list of qubits and/or quregs): The qubits, + for which to make the histogram + + Returns: + A tuple (fig, axes, probabilities), where: + fig: The histogram as figure + axes: The axes of the histogram + probabilities (dict): A dictionary mapping outcomes as string + to their probabilities + + Note: + 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) + else: + 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("The resulting histogram may look bad and/or take too long.") + print("Consider calling histogram() with a sublist of the qubits.") + + if hasattr(backend, 'get_probabilities'): + probabilities = backend.get_probabilities(qureg) + elif isinstance(backend, Simulator): + outcome = [0] * len(qubit_list) + n_outcomes = (1 << len(qubit_list)) + probabilities = {} + for i in range(n_outcomes): + for pos in range(len(qubit_list)): + if (1 << pos) & i: + outcome[pos] = 1 + else: + outcome[pos] = 0 + 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)) + names = list(probabilities.keys()) + values = list(probabilities.values()) + axes.bar(names, values) + fig.suptitle('Measurement Probabilities') + return (fig, axes, probabilities) diff --git a/projectq/libs/hist/_histogram_test.py b/projectq/libs/hist/_histogram_test.py new file mode 100644 index 000000000..1f38573b0 --- /dev/null +++ b/projectq/libs/hist/_histogram_test.py @@ -0,0 +1,135 @@ +# 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 +import matplotlib +import matplotlib.pyplot as plt + +from projectq import MainEngine +from projectq.ops import H, C, X, Measure, All, AllocateQubitGate, FlushGate +from projectq.cengines import DummyEngine, BasicEngine +from projectq.backends import Simulator +from projectq.libs.hist import histogram + + +@pytest.fixture(scope="module") +def matplotlib_setup(): + old_backend = matplotlib.get_backend() + matplotlib.use('agg') # avoid showing the histogram plots + yield + matplotlib.use(old_backend) + + +def test_invalid_backend(matplotlib_setup): + eng = MainEngine(backend=DummyEngine()) + qubit = eng.allocate_qubit() + eng.flush() + + with pytest.raises(RuntimeError): + histogram(eng.backend, qubit) + + +def test_backend_get_probabilities_method(matplotlib_setup): + class MyBackend(BasicEngine): + def get_probabilities(self, qureg): + return {'000': 0.5, '111': 0.5} + + def is_available(self, cmd): + return True + + def receive(self, command_list): + for cmd in command_list: + if not isinstance(cmd.gate, FlushGate): + assert isinstance(cmd.gate, AllocateQubitGate) + + eng = MainEngine(backend=MyBackend(), verbose=True) + qureg = eng.allocate_qureg(3) + eng.flush() + _, _, prob = histogram(eng.backend, qureg) + assert prob['000'] == 0.5 + assert prob['111'] == 0.5 + + +def test_qubit(matplotlib_setup): + sim = Simulator() + eng = MainEngine(sim) + qubit = eng.allocate_qubit() + eng.flush() + _, _, prob = histogram(sim, qubit) + assert prob["0"] == pytest.approx(1) + assert prob["1"] == pytest.approx(0) + H | qubit + eng.flush() + _, _, prob = histogram(sim, qubit) + assert prob["0"] == pytest.approx(0.5) + Measure | qubit + eng.flush() + _, _, prob = histogram(sim, qubit) + assert prob["0"] == pytest.approx(1) or prob["1"] == pytest.approx(1) + + +def test_qureg(matplotlib_setup): + sim = Simulator() + eng = MainEngine(sim) + qureg = eng.allocate_qureg(3) + eng.flush() + _, _, prob = histogram(sim, qureg) + assert prob["000"] == pytest.approx(1) + assert prob["110"] == pytest.approx(0) + H | qureg[0] + C(X, 1) | (qureg[0], qureg[1]) + H | qureg[2] + eng.flush() + _, _, prob = histogram(sim, qureg) + assert prob["110"] == pytest.approx(0.25) + assert prob["100"] == pytest.approx(0) + 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) + + +def test_combination(matplotlib_setup): + sim = Simulator() + eng = MainEngine(sim) + qureg = eng.allocate_qureg(2) + qubit = eng.allocate_qubit() + eng.flush() + _, _, prob = histogram(sim, [qureg, qubit]) + assert prob["000"] == pytest.approx(1) + H | qureg[0] + C(X, 1) | (qureg[0], qureg[1]) + H | qubit + 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["100"] == pytest.approx(0) + Measure | qubit + + +def test_too_many_qubits(matplotlib_setup, capsys): + sim = Simulator() + eng = MainEngine(sim) + qureg = eng.allocate_qureg(6) + eng.flush() + l_ref = len(capsys.readouterr().out) + _, _, prob = histogram(sim, qureg) + assert len(capsys.readouterr().out) > l_ref + assert prob["000000"] == pytest.approx(1) + All(Measure) diff --git a/pytest.ini b/pytest.ini index fab634b12..d17d4ce2e 100755 --- a/pytest.ini +++ b/pytest.ini @@ -4,3 +4,4 @@ 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. From 6ef8a0eeb06267b27bbbe9207b71013e9cca65d4 Mon Sep 17 00:00:00 2001 From: David Bretaud <40793394+dbretaud@users.noreply.github.com> Date: Thu, 30 Apr 2020 15:06:32 +0100 Subject: [PATCH 048/113] bug fix on ibm example; new aqt example; updated the main README file (#368) Co-authored-by: dbretaud --- README.rst | 24 +++++++++++++++--- examples/aqt.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ examples/ibm.py | 7 +++++- 3 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 examples/aqt.py diff --git a/README.rst b/README.rst index 5b5dc5f66..7b6e93beb 100755 --- a/README.rst +++ b/README.rst @@ -102,16 +102,34 @@ Instead of simulating a quantum program, one can use our resource counter (as a **Running a quantum program on IBM's QE chips** -To run a program on the IBM Quantum Experience chips, all one has to do is choose the `IBMBackend` and the corresponding compiler: +To run a program on the IBM Quantum Experience chips, all one has to do is choose the `IBMBackend` and the corresponding setup: .. code-block:: python import projectq.setups.ibm from projectq.backends import IBMBackend + token='MY_TOKEN' device='ibmq_16_melbourne' - compiler_engines = projectq.setups.ibm.get_engine_list(device=device) - eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024, + compiler_engines = projectq.setups.ibm.get_engine_list(token=token,device=device) + eng = MainEngine(IBMBackend(token=token, use_hardware=True, num_runs=1024, + verbose=False, device=device), + engine_list=compiler_engines) + + +**Running a quantum program on AQT devices** + +To run a program on the AQT trapped ion quantum computer, choose the `AQTBackend` and the corresponding setup: + +.. code-block:: python + + 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) + eng = MainEngine(AQTBackend(token=token,use_hardware=True, num_runs=1024, verbose=False, device=device), engine_list=compiler_engines) diff --git a/examples/aqt.py b/examples/aqt.py new file mode 100644 index 000000000..c4cadf17f --- /dev/null +++ b/examples/aqt.py @@ -0,0 +1,67 @@ +import matplotlib.pyplot as plt +import getpass + +from projectq import MainEngine +from projectq.backends import AQTBackend +from projectq.libs.hist import histogram +from projectq.ops import Measure, Entangle, All +import projectq.setups.aqt + + +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__": + #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 + 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)) + # run the circuit and print the result + print(run_entangle(eng)) diff --git a/examples/ibm.py b/examples/ibm.py index eafd50b80..33427adc9 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -48,12 +48,17 @@ def run_entangle(eng, num_qubits=3): # ibmq_16_melbourne (15 qubit) # ibmq_essex (5 qubit) # ibmq_qasm_simulator (32 qubits) + # and plenty of other 5 qubits devices! + # + # 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 token = None # replace by the token given by IBMQ if token is None: token = getpass.getpass(prompt='IBM Q token > ') if device is None: - token = getpass.getpass(prompt='IBM device > ') + 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), From dd8199fa03be41d6132117d69b2ba0ff3e82ee8e Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Thu, 7 May 2020 11:12:19 +0200 Subject: [PATCH 049/113] Bumped version to v0.5.1 --- projectq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/_version.py b/projectq/_version.py index 6900d1135..14eec918c 100755 --- a/projectq/_version.py +++ b/projectq/_version.py @@ -13,4 +13,4 @@ # limitations under the License. """Define version number here and read it from setup.py automatically""" -__version__ = "0.5.0" +__version__ = "0.5.1" 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 050/113] 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 051/113] 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 2354150f8e165875bce5743d28b672d5941fc325 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Fri, 12 Feb 2021 11:42:38 +0100 Subject: [PATCH 052/113] ProjectQ v0.5.2 (#385) * Update on the ibmq client (#379) * Fix install on macOS Big Sur (#383) * Bump version number * Fix tests * Patch travs.yml Co-authored-by: David Bretaud <40793394+dbretaud@users.noreply.github.com> Co-authored-by: Bartley Gillan --- .travis.yml | 2 +- projectq/_version.py | 2 +- projectq/backends/_ibm/_ibm_http_client.py | 37 +++++++------------ .../backends/_ibm/_ibm_http_client_test.py | 30 ++++++++------- setup.py | 7 ++-- 5 files changed, 35 insertions(+), 43 deletions(-) diff --git a/.travis.yml b/.travis.yml index 47f727e2a..ea518e9ef 100755 --- a/.travis.yml +++ b/.travis.yml @@ -46,7 +46,7 @@ before_script: - "echo 'backend: Agg' > matplotlibrc" # command to run tests -script: export OMP_NUM_THREADS=1 && pytest projectq --cov projectq +script: export OMP_NUM_THREADS=1 && pytest projectq --cov projectq -p no:warnings after_success: - coveralls diff --git a/projectq/_version.py b/projectq/_version.py index 14eec918c..f66d9b474 100755 --- a/projectq/_version.py +++ b/projectq/_version.py @@ -13,4 +13,4 @@ # limitations under the License. """Define version number here and read it from setup.py automatically""" -__version__ = "0.5.1" +__version__ = "0.5.2" 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 diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 5f43b5d28..460ccdf8f 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -77,7 +77,7 @@ def raise_for_status(self): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if (args[1] == urljoin(_API_URL, status_url) - and (request_num[0] == 1 or request_num[0] == 7)): + 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)]) @@ -97,7 +97,7 @@ def raise_for_status(self): _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] == 6): + and request_num[0] == 5): result_ready[0] = True request_num[0] += 1 return MockResponse({"status": "RUNNING"}, 200) @@ -105,7 +105,7 @@ def raise_for_status(self): _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] == 8): + and request_num[0] == 7): request_num[0] += 1 return MockResponse( {"status": "COMPLETED"}, 200) @@ -114,13 +114,13 @@ def raise_for_status(self): _API_URL, "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl". format(execution_id=execution_id)) - and request_num[0] == 9): + 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] == 10): + and request_num[0] == 9): request_num[0] += 1 return MockResponse( {"results": [result]}, 200) @@ -156,13 +156,14 @@ def raise_for_status(self): answer1={'objectStorageInfo':{ 'downloadQObjectUrlEndpoint':'url_dld_endpoint', 'uploadQobjectUrlEndpoint':'/'+execution_id+'/jobUploadUrl', - 'uploadUrl':'url_upld'} + 'uploadUrl':'url_upld'}, + 'id': execution_id } return MockPostResponse(answer1,200) # STEP4 - elif (args[1] == "/"+execution_id+"/jobDataUploaded" - and request_num[0] == 5): + elif (args[1] == urljoin(_API_URL, jobs_url + "/"+execution_id+"/jobDataUploaded") + and request_num[0] == 4): request_num[0] += 1 return MockPostResponse({}, 200) @@ -171,7 +172,7 @@ def raise_for_status(self): _API_URL, "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded". format(execution_id=execution_id)) - and request_num[0] == 11): + and request_num[0] == 10): request_num[0] += 1 return MockPostResponse( {}, 200) @@ -195,8 +196,8 @@ def raise_for_status(self): pass # STEP3 - if (args[1] == "s3_url" - and request_num[0] == 4): + if (args[1] == "url_upld" + and request_num[0] == 3): request_num[0] += 1 return MockResponse({}, 200) @@ -579,12 +580,13 @@ def raise_for_status(self): answer1={'objectStorageInfo':{ 'downloadQObjectUrlEndpoint':'url_dld_endpoint', 'uploadQobjectUrlEndpoint':'/'+execution_id+'/jobUploadUrl', - 'uploadUrl':'url_upld'} + 'uploadUrl':'url_upld'}, + 'id': execution_id, } return MockPostResponse(answer1,200) # STEP4 - elif (args[1] == "/"+execution_id+"/jobDataUploaded"): + elif (args[1] == urljoin(_API_URL, jobs_url + "/"+execution_id+"/jobDataUploaded")): return MockPostResponse({}, 200) def mocked_requests_put(*args, **kwargs): @@ -606,7 +608,7 @@ def raise_for_status(self): pass # STEP3 - if (args[1] == "s3_url"): + if (args[1] == "url_upld"): return MockResponse({}, 200) monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) 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 053/113] 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 054/113] 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 055/113] 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 056/113] 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 057/113] 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 058/113] 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 059/113] 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 060/113] 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 061/113] 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 From 62fd63275f4253594cbe40cf745bc09c6f7c226c Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Wed, 23 Jun 2021 17:08:50 +0200 Subject: [PATCH 062/113] Fix publishing GitHub workflow --- .github/workflows/publish_release.yml | 42 ++++++++++++++++++++++----- CHANGELOG.md | 6 ++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 8717a479a..0539ec6c8 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -27,32 +27,58 @@ 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/') + - name: Extract version from tag name (Unix) + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && runner.os != 'Windows' 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/') + - name: Extract version from branch name (for release branches) (Unix) + if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') && runner.os != 'Windows' run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#release/} echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + git tag v${RELEASE_VERSION} master - - name: Extract version from branch name (for hotfix branches) - if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') + - name: Extract version from branch name (for hotfix branches) (Unix) + if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os != 'Windows' run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#hotfix/} echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + git tag v${RELEASE_VERSION} master + + + - name: Extract version from tag name (Windows) + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && runner.os == 'Windows' + run: | + $TAG_NAME = $GITHUB_REF -replace "refs/tags/","" + $VERSION = $TAG_NAME -replace "v","" + + Write-Output "RELEASE_VERSION=$VERSION"" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Extract version from branch name (for release branches) (Windows) + if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') && runner.os == 'Windows' + run: | + $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + $VERSION = $BRANCH_NAME -replace "release/","" + + Write-Output "RELEASE_VERSION=$VERSION"" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + git tag v${RELEASE_VERSION} master + + - name: Extract version from branch name (for hotfix branches) (Unix) + if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os == 'Windows' + run: | + $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + $VERSION = $BRANCH_NAME -replace hotfix/","" - - name: Set tag for setuptools-scm - run: git tag v${RELEASE_VERSION} master + Write-Output "RELEASE_VERSION=$VERSION"" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + git tag v${RELEASE_VERSION} master - name: Build wheels uses: joerick/cibuildwheel@v1.11.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 46af704bf..c692911e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### Repository +## [0.6.1] - 2021-06-23 + +### Repository + +- Fix GitHub workflow for publishing a new release + ## [0.6.0] - 2021-06-23 ### Added From f0a0c1e9687c6f0a2e4bdc894fac78b65daabe61 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Fri, 25 Jun 2021 10:33:09 +0200 Subject: [PATCH 063/113] Add checks to avoid too many compiler engines in the engine list (#405) * Add checks to avoid too many compiler engines in the engine list * Update CHANGELOG * Apply suggestions from Andreas --- CHANGELOG.md | 3 +++ projectq/cengines/_main.py | 15 +++++++++++++-- projectq/cengines/_main_test.py | 12 ++++++++++++ projectq/meta/_util.py | 8 ++++++++ projectq/meta/_util_test.py | 26 +++++++++++++++++++++++--- 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c692911e1..e6c1aca4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Deprecated ### Fixed + +- Prevent infinite recursion errors when too many compiler engines are added to the MainEngine + ### Removed ### Repository diff --git a/projectq/cengines/_main.py b/projectq/cengines/_main.py index 2c961b9a6..ec6145ef5 100755 --- a/projectq/cengines/_main.py +++ b/projectq/cengines/_main.py @@ -47,6 +47,9 @@ def receive(self, command_list): # pylint: disable=unused-argument """No-op""" +_N_ENGINES_THRESHOLD = 100 + + class MainEngine(BasicEngine): # pylint: disable=too-many-instance-attributes """ The MainEngine class provides all functionality of the main compiler engine. @@ -61,10 +64,13 @@ class MainEngine(BasicEngine): # pylint: disable=too-many-instance-attributes dirty_qubits (Set): Containing all dirty qubit ids backend (BasicEngine): Access the back-end. mapper (BasicMapperEngine): Access to the mapper if there is one. - + n_engines (int): Current number of compiler engines in the engine list + n_engines_max (int): Maximum number of compiler engines allowed in the engine list. Defaults to 100. """ - def __init__(self, backend=None, engine_list=None, verbose=False): + def __init__( # pylint: disable=too-many-statements,too-many-branches + self, backend=None, engine_list=None, verbose=False + ): """ Initialize the main compiler engine and all compiler engines. @@ -118,6 +124,7 @@ def __init__(self, backend=None, engine_list=None, verbose=False): self.dirty_qubits = set() self.verbose = verbose self.main_engine = self + self.n_engines_max = _N_ENGINES_THRESHOLD if backend is None: backend = Simulator() @@ -174,6 +181,10 @@ def __init__(self, backend=None, engine_list=None, verbose=False): " twice.\n" ) + self.n_engines = len(engine_list) + if self.n_engines > self.n_engines_max: + raise ValueError('Too many compiler engines added to the MainEngine!') + self._qubit_idx = int(0) for i in range(len(engine_list) - 1): engine_list[i].next_engine = engine_list[i + 1] diff --git a/projectq/cengines/_main_test.py b/projectq/cengines/_main_test.py index 3178cd5c7..b8ab365c4 100755 --- a/projectq/cengines/_main_test.py +++ b/projectq/cengines/_main_test.py @@ -73,6 +73,18 @@ def test_main_engine_init_defaults(): assert type(engine) == type(expected) +def test_main_engine_too_many_compiler_engines(): + old = _main._N_ENGINES_THRESHOLD + _main._N_ENGINES_THRESHOLD = 3 + + _main.MainEngine(backend=DummyEngine(), engine_list=[DummyEngine(), DummyEngine()]) + + with pytest.raises(ValueError): + _main.MainEngine(backend=DummyEngine(), engine_list=[DummyEngine(), DummyEngine(), DummyEngine()]) + + _main._N_ENGINES_THRESHOLD = old + + def test_main_engine_init_mapper(): class LinearMapper(BasicMapperEngine): pass diff --git a/projectq/meta/_util.py b/projectq/meta/_util.py index 4dab11ede..4a5300eb2 100755 --- a/projectq/meta/_util.py +++ b/projectq/meta/_util.py @@ -30,6 +30,12 @@ def insert_engine(prev_engine, engine_to_insert): engine_to_insert (projectq.cengines.BasicEngine): The engine to insert at the insertion point. """ + if prev_engine.main_engine is not None: + prev_engine.main_engine.n_engines += 1 + + if prev_engine.main_engine.n_engines > prev_engine.main_engine.n_engines_max: + raise RuntimeError('Too many compiler engines added to the MainEngine!') + engine_to_insert.main_engine = prev_engine.main_engine engine_to_insert.next_engine = prev_engine.next_engine prev_engine.next_engine = engine_to_insert @@ -47,6 +53,8 @@ def drop_engine_after(prev_engine): """ dropped_engine = prev_engine.next_engine prev_engine.next_engine = dropped_engine.next_engine + if prev_engine.main_engine is not None: + prev_engine.main_engine.n_engines -= 1 dropped_engine.next_engine = None dropped_engine.main_engine = None return dropped_engine diff --git a/projectq/meta/_util_test.py b/projectq/meta/_util_test.py index 496d2f9b4..a010d1b81 100755 --- a/projectq/meta/_util_test.py +++ b/projectq/meta/_util_test.py @@ -13,9 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.meta import insert_engine, drop_engine_after + + +from . import _util def test_insert_then_drop(): @@ -30,19 +34,35 @@ def test_insert_then_drop(): assert d1.main_engine is eng assert d2.main_engine is None assert d3.main_engine is eng + assert eng.n_engines == 2 - insert_engine(d1, d2) + _util.insert_engine(d1, d2) assert d1.next_engine is d2 assert d2.next_engine is d3 assert d3.next_engine is None assert d1.main_engine is eng assert d2.main_engine is eng assert d3.main_engine is eng + assert eng.n_engines == 3 - drop_engine_after(d1) + _util.drop_engine_after(d1) assert d1.next_engine is d3 assert d2.next_engine is None assert d3.next_engine is None assert d1.main_engine is eng assert d2.main_engine is None assert d3.main_engine is eng + assert eng.n_engines == 2 + + +def test_too_many_engines(): + N = 10 + + eng = MainEngine(backend=DummyEngine(), engine_list=[]) + eng.n_engines_max = N + + for _ in range(N - 1): + _util.insert_engine(eng, DummyEngine()) + + with pytest.raises(RuntimeError): + _util.insert_engine(eng, DummyEngine()) From 10b4077acb81e8abe7a1e4220ed8eb20011e7f2e Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Fri, 25 Jun 2021 11:07:33 +0200 Subject: [PATCH 064/113] Fix some issues with automatic release publication (#407) * Only release source package... ... and leave commented out some code that would implement better testing before uploading to Pypi. * Remove unneeded comments * Update publish_release.yml * Update publish_release.yml * Update publish_release.yml --- .github/workflows/publish_release.yml | 82 ++++++++++++++------------- CHANGELOG.md | 1 + 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 96ae496d5..d0f54099a 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -1,6 +1,7 @@ name: "Publish new release" on: + workflow_dispatch: push: tags: - v[0-9]+.* @@ -14,78 +15,59 @@ 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) + if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || github.event_name == 'workflow_dispatch' strategy: matrix: - os: [ubuntu-20.04, windows-2019, macos-10.15] + os: + - ubuntu-latest steps: - uses: actions/checkout@v2 + if: github.event_name != 'workflow_dispatch' + + - uses: actions/checkout@v2 + if: github.event_name == 'workflow_dispatch' + with: + ref: 'master' - 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: Extract version from tag name (Unix) - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && runner.os != 'Windows' - 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) (Unix) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') && runner.os != 'Windows' run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#release/} - - echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV - git tag v${RELEASE_VERSION} master + git tag v${VERSION} master - name: Extract version from branch name (for hotfix branches) (Unix) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os != 'Windows' run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#hotfix/} + git tag v${VERSION} master - echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV - git tag v${RELEASE_VERSION} master - - - - name: Extract version from tag name (Windows) - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && runner.os == 'Windows' - run: | - $TAG_NAME = $GITHUB_REF -replace "refs/tags/","" - $VERSION = $TAG_NAME -replace "v","" - - Write-Output "RELEASE_VERSION=$VERSION" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + # ------------------------------------------------------------------------ - name: Extract version from branch name (for release branches) (Windows) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') && runner.os == 'Windows' run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "release/","" + git tag v${VERSION} master - Write-Output "RELEASE_VERSION=$VERSION" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - git tag v${RELEASE_VERSION} master - - - name: Extract version from branch name (for hotfix branches) (Unix) + - name: Extract version from branch name (for hotfix branches) (Windows) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os == 'Windows' run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "hotfix/","" + git tag v${VERSION} master - Write-Output "RELEASE_VERSION=$VERSION" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - git tag v${RELEASE_VERSION} master - - - name: Build wheels - uses: joerick/cibuildwheel@v1.11.1 - 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' @@ -101,11 +83,21 @@ jobs: name: packages path: ./wheelhouse/* + release: name: Publish new release runs-on: ubuntu-latest - needs: packaging + needs: + - packaging steps: + - name: Extract version from tag name (workflow_dispatch) + if: github.event_name == 'workflow_dispatch' + run: | + TAG_NAME=$(git describe --tags `git rev-list --tags --max-count=1`) + VERSION=${TAG_NAME#v} + + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + - name: Extract version from tag name if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') run: | @@ -130,8 +122,18 @@ jobs: echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + # ------------------------------------------------------------------------ + # Checkout repository to get CHANGELOG + - uses: actions/checkout@v2 + if: github.event_name != 'workflow_dispatch' + + - uses: actions/checkout@v2 + if: github.event_name == 'workflow_dispatch' + with: + ref: 'master' + # ------------------------------------------------------------------------ # Downloads all to directories matching the artifact names - uses: actions/download-artifact@v2 @@ -164,7 +166,7 @@ jobs: upload_to_pypi: name: Upload to PyPI runs-on: ubuntu-latest - needs: release # Only upload to PyPi if everything was successful + needs: release steps: - uses: actions/setup-python@v2 @@ -181,7 +183,9 @@ jobs: master_to_develop_pr: name: Merge master back into develop runs-on: ubuntu-latest - needs: release # Only create PR if everything was successful + needs: + - release + - upload_to_pypi steps: - name: Merge master into develop branch uses: thomaseizinger/create-pull-request@1.1.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index e6c1aca4b..76ae99d46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix GitHub workflow for publishing a new release + ## [0.6.0] - 2021-06-23 ### Added From 0c44fba051cff5ab68793cd1e5aaa69670a132ef Mon Sep 17 00:00:00 2001 From: XYShe <30593841+XYShe@users.noreply.github.com> Date: Thu, 1 Jul 2021 16:09:14 +0200 Subject: [PATCH 065/113] Implement a UnitarySimulator to save the unitary of a quantum circuit (#409) * Add matout() to check for matrices applied. Add check for self-control. * Move unitary logic to its own compiler engine class * Improve unitary simulator example * Added support for measurement. Added history for unitaries. Added tests * Clean up directory * Fix simulator error, add changelog * Undo unneeded space changes * Refactor example a little * Reformat some of the docstrings * Update CHANGELOG * Improve code to enable all pylint checks for the UnitarySimulator * Improve test coverage and simply parts of the code * Tweak UnitarySimulator some more - Make it so that multiple calls to flush() do not unnecessarily increase the history list of unitary matrices Co-authored-by: Damien Nguyen --- CHANGELOG.md | 3 + examples/unitary_simulator.py | 82 ++++++++ projectq/backends/__init__.py | 1 + projectq/backends/_unitary.py | 290 ++++++++++++++++++++++++++++ projectq/backends/_unitary_test.py | 292 +++++++++++++++++++++++++++++ 5 files changed, 668 insertions(+) create mode 100644 examples/unitary_simulator.py create mode 100644 projectq/backends/_unitary.py create mode 100644 projectq/backends/_unitary_test.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 76ae99d46..8b8a64b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added + +- UnitarySimulator backend for computing the unitary transformation corresponding to a quantum circuit. + ### Changed ### Deprecated ### Fixed diff --git a/examples/unitary_simulator.py b/examples/unitary_simulator.py new file mode 100644 index 000000000..420034784 --- /dev/null +++ b/examples/unitary_simulator.py @@ -0,0 +1,82 @@ +# -*- 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. +# pylint: skip-file + +"""Example of using the UnitarySimulator.""" + + +import numpy as np + +from projectq.backends import UnitarySimulator +from projectq.cengines import MainEngine +from projectq.meta import Control +from projectq.ops import All, X, QFT, Measure, CtrlAll + + +def run_circuit(eng, n_qubits, circuit_num, gate_after_measure=False): + """Run a quantum circuit demonstrating the capabilities of the UnitarySimulator.""" + qureg = eng.allocate_qureg(n_qubits) + + if circuit_num == 1: + All(X) | qureg + elif circuit_num == 2: + X | qureg[0] + with Control(eng, qureg[:2]): + All(X) | qureg[2:] + elif circuit_num == 3: + with Control(eng, qureg[:2], ctrl_state=CtrlAll.Zero): + All(X) | qureg[2:] + elif circuit_num == 4: + QFT | qureg + + eng.flush() + All(Measure) | qureg + + if gate_after_measure: + QFT | qureg + eng.flush() + All(Measure) | qureg + + +def main(): + """Definition of the main function of this example.""" + # Create a MainEngine with a unitary simulator backend + eng = MainEngine(backend=UnitarySimulator()) + + n_qubits = 3 + + # Run out quantum circuit + # 1 - circuit applying X on all qubits + # 2 - circuit applying an X gate followed by a controlled-X gate + # 3 - circuit applying a off-controlled-X gate + # 4 - circuit applying a QFT on all qubits (QFT will get decomposed) + run_circuit(eng, n_qubits, 3, gate_after_measure=True) + + # Output the unitary transformation of the circuit + print('The unitary of the circuit is:') + print(eng.backend.unitary) + + # Output the final state of the qubits (assuming they all start in state |0>) + print('The final state of the qubits is:') + print(eng.backend.unitary @ np.array([1] + ([0] * (2 ** n_qubits - 1)))) + print('\n') + + # Show the unitaries separated by measurement: + for history in eng.backend.history: + print('Previous unitary is: \n', history, '\n') + + +if __name__ == '__main__': + main() diff --git a/projectq/backends/__init__.py b/projectq/backends/__init__.py index 2b6ce5520..044b78b84 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -36,3 +36,4 @@ from ._aqt import AQTBackend from ._awsbraket import AWSBraketBackend from ._ionq import IonQBackend +from ._unitary import UnitarySimulator diff --git a/projectq/backends/_unitary.py b/projectq/backends/_unitary.py new file mode 100644 index 000000000..1ebdbc2cb --- /dev/null +++ b/projectq/backends/_unitary.py @@ -0,0 +1,290 @@ +# -*- 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. + +"""Contain a backend that saves the unitary of a quantum circuit.""" + +from copy import deepcopy +import itertools +import math +import warnings +import random +import numpy as np + +from projectq.cengines import BasicEngine +from projectq.types import WeakQubitRef +from projectq.meta import has_negative_control, get_control_count, LogicalQubitIDTag +from projectq.ops import ( + AllocateQubitGate, + DeallocateQubitGate, + MeasureGate, + FlushGate, +) + + +def _qidmask(target_ids, control_ids, n_qubits): + """ + Calculate index masks. + + Args: + target_ids (list): list of target qubit indices + control_ids (list): list of control qubit indices + control_state (list): list of states for the control qubits (0 or 1) + n_qubits (int): number of qubits + """ + mask_list = [] + perms = np.array([x[::-1] for x in itertools.product("01", repeat=n_qubits)]).astype(int) + all_ids = np.array(range(n_qubits)) + irel_ids = np.delete(all_ids, control_ids + target_ids) + + if len(control_ids) > 0: + cmask = np.where(np.all(perms[:, control_ids] == [1] * len(control_ids), axis=1)) + else: + cmask = np.array(range(perms.shape[0])) + + if len(irel_ids) > 0: + irel_perms = np.array([x[::-1] for x in itertools.product("01", repeat=len(irel_ids))]).astype(int) + for i in range(2 ** len(irel_ids)): + irel_mask = np.where(np.all(perms[:, irel_ids] == irel_perms[i], axis=1)) + common = np.intersect1d(irel_mask, cmask) + if len(common) > 0: + mask_list.append(common) + else: + irel_mask = np.array(range(perms.shape[0])) + mask_list.append(np.intersect1d(irel_mask, cmask)) + return mask_list + + +class UnitarySimulator(BasicEngine): + """ + Simulator engine aimed at calculating the unitary transformation that represents the current quantum circuit. + + Attributes: + unitary (np.ndarray): Current unitary representing the quantum circuit being processed so far. + history (list): List of previous quantum circuit unitaries. + + Note: + The current implementation of this backend resets the unitary after the first gate that is neither a qubit + deallocation nor a measurement occurs after one of those two aforementioned gates. + + The old unitary call be accessed at anytime after such a situation occurs via the `history` property. + + .. code-block:: python + + eng = MainEngine(backend=UnitarySimulator(), engine_list=[]) + qureg = eng.allocate_qureg(3) + All(X) | qureg + + eng.flush() + All(Measure) | qureg + eng.deallocate_qubit(qureg[1]) + + X | qureg[0] # WARNING: appending gate after measurements or deallocations resets the unitary + """ + + def __init__(self): + """Initialize a UnitarySimulator object.""" + super().__init__() + self._qubit_map = dict() + self._unitary = [1] + self._num_qubits = 0 + self._is_valid = True + self._is_flushed = False + self._state = [1] + self._history = [] + + @property + def unitary(self): + """ + Access the last unitary matrix directly. + + Returns: + A numpy array which is the unitary matrix of the circuit. + """ + return deepcopy(self._unitary) + + @property + def history(self): + """ + Access all previous unitary matrices. + + The current unitary matrix is appended to this list once a gate is received after either a measurement or a + qubit deallocation has occurred. + + Returns: + A list where the elements are all previous unitary matrices representing the circuit, separated by + measurement/deallocate gates. + """ + return deepcopy(self._history) + + def is_available(self, cmd): + """ + Test whether a Command is supported by a compiler engine. + + Specialized implementation of is_available: The unitary simulator can deal with all arbitrarily-controlled gates + which provide a gate-matrix (via gate.matrix). + + Args: + cmd (Command): Command for which to check availability (single- qubit gate, arbitrary controls) + + Returns: + True if it can be simulated and False otherwise. + """ + if has_negative_control(cmd): + return False + + if isinstance(cmd.gate, (AllocateQubitGate, DeallocateQubitGate, MeasureGate)): + return True + + try: + gate_mat = cmd.gate.matrix + if len(gate_mat) > 2 ** 6: + warnings.warn("Potentially large matrix gate encountered! ({} qubits)".format(math.log2(len(gate_mat)))) + return True + except AttributeError: + return False + + def receive(self, command_list): + """ + Receive a list of commands. + + Receive a list of commands from the previous engine and handle them: + * update the unitary of the quantum circuit + * update the internal quantum state if a measurement or a qubit deallocation occurs + + prior to sending them on to the next engine. + + Args: + command_list (list): List of commands to execute on the simulator. + """ + for cmd in command_list: + self._handle(cmd) + + if not self.is_last_engine: + self.send(command_list) + + def _flush(self): + """Flush the simulator state.""" + if not self._is_flushed: + self._is_flushed = True + self._state = self._unitary @ self._state + + def _handle(self, cmd): + """ + Handle all commands. + + Args: + cmd (Command): Command to handle. + + Raises: + RuntimeError: If a measurement is performed before flush gate. + """ + if isinstance(cmd.gate, AllocateQubitGate): + self._qubit_map[cmd.qubits[0][0].id] = self._num_qubits + self._num_qubits += 1 + self._unitary = np.kron(np.identity(2), self._unitary) + self._state.extend([0] * len(self._state)) + + elif isinstance(cmd.gate, DeallocateQubitGate): + pos = self._qubit_map[cmd.qubits[0][0].id] + self._qubit_map = {key: value - 1 if value > pos else value for key, value in self._qubit_map.items()} + self._num_qubits -= 1 + self._is_valid = False + + elif isinstance(cmd.gate, MeasureGate): + self._is_valid = False + + if not self._is_flushed: + raise RuntimeError( + 'Please make sure all previous gates are flushed before measurement so the state gets updated' + ) + + if get_control_count(cmd) != 0: + raise ValueError('Cannot have control qubits with a measurement gate!') + + all_qubits = [qb for qr in cmd.qubits for qb in qr] + measurements = self.measure_qubits([qb.id for qb in all_qubits]) + + for qb, res in zip(all_qubits, measurements): + # Check if a mapper assigned a different logical id + for tag in cmd.tags: + if isinstance(tag, LogicalQubitIDTag): + qb = WeakQubitRef(qb.engine, tag.logical_qubit_id) + break + self.main_engine.set_measurement_result(qb, res) + + elif isinstance(cmd.gate, FlushGate): + self._flush() + else: + if not self._is_valid: + self._flush() + + warnings.warn( + "Processing of other gates after a qubit deallocation or measurement will reset the unitary," + "previous unitary can be accessed in history" + ) + self._history.append(self._unitary) + self._unitary = np.identity(2 ** self._num_qubits, dtype=complex) + self._state = np.array([1] + ([0] * (2 ** self._num_qubits - 1)), dtype=complex) + self._is_valid = True + + self._is_flushed = False + mask_list = _qidmask( + [self._qubit_map[qb.id] for qr in cmd.qubits for qb in qr], + [self._qubit_map[qb.id] for qb in cmd.control_qubits], + self._num_qubits, + ) + for mask in mask_list: + cache = np.identity(2 ** self._num_qubits, dtype=complex) + cache[np.ix_(mask, mask)] = cmd.gate.matrix + self._unitary = cache @ self._unitary + + def measure_qubits(self, ids): + """ + Measure the qubits with IDs ids and return a list of measurement outcomes (True/False). + + Args: + ids (list): List of qubit IDs to measure. + + Returns: + List of measurement results (containing either True or False). + """ + random_outcome = random.random() + val = 0.0 + i_picked = 0 + while val < random_outcome and i_picked < len(self._state): + val += np.abs(self._state[i_picked]) ** 2 + i_picked += 1 + + i_picked -= 1 + + pos = [self._qubit_map[ID] for ID in ids] + res = [False] * len(pos) + + mask = 0 + val = 0 + 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, _state in enumerate(self._state): + if (mask & i) != val: + self._state[i] = 0.0 + else: + nrm += np.abs(_state) ** 2 + + self._state *= 1.0 / np.sqrt(nrm) + return res diff --git a/projectq/backends/_unitary_test.py b/projectq/backends/_unitary_test.py new file mode 100644 index 000000000..e082305e8 --- /dev/null +++ b/projectq/backends/_unitary_test.py @@ -0,0 +1,292 @@ +# -*- 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. + +""" +Contains the tests for the UnitarySimulator +""" + +import itertools +import numpy as np +import pytest +from scipy.stats import unitary_group + +from projectq.cengines import MainEngine, DummyEngine, NotYetMeasuredError +from projectq.ops import ( + BasicGate, + MatrixGate, + All, + Measure, + Allocate, + Deallocate, + Command, + X, + Y, + Rx, + Rxx, + H, + CNOT, +) +from projectq.meta import Control, LogicalQubitIDTag +from projectq.types import WeakQubitRef + +from ._unitary import UnitarySimulator + + +def test_unitary_is_available(): + sim = UnitarySimulator() + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + qb2 = WeakQubitRef(engine=None, idx=2) + qb3 = WeakQubitRef(engine=None, idx=2) + qb4 = WeakQubitRef(engine=None, idx=2) + qb5 = WeakQubitRef(engine=None, idx=2) + qb6 = WeakQubitRef(engine=None, idx=2) + + assert sim.is_available(Command(None, Allocate, qubits=([qb0],))) + assert sim.is_available(Command(None, Deallocate, qubits=([qb0],))) + assert sim.is_available(Command(None, Measure, qubits=([qb0],))) + assert sim.is_available(Command(None, X, qubits=([qb0],))) + assert sim.is_available(Command(None, Rx(1.2), qubits=([qb0],))) + assert sim.is_available(Command(None, Rxx(1.2), qubits=([qb0, qb1],))) + 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, BasicGate(), qubits=([qb0],))) + assert not sim.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='0')) + + with pytest.warns(UserWarning): + assert sim.is_available( + Command( + None, + MatrixGate(np.identity(2 ** 7)), + qubits=([qb0, qb1, qb2, qb3, qb4, qb5, qb6],), + ) + ) + + +def test_unitary_warnings(): + eng = MainEngine(backend=DummyEngine(save_commands=True), engine_list=[UnitarySimulator()]) + qubit = eng.allocate_qubit() + X | qubit + + with pytest.raises(RuntimeError): + Measure | qubit + + +def test_unitary_not_last_engine(): + eng = MainEngine(backend=DummyEngine(save_commands=True), engine_list=[UnitarySimulator()]) + qubit = eng.allocate_qubit() + X | qubit + eng.flush() + Measure | qubit + assert len(eng.backend.received_commands) == 4 + + +def test_unitary_flush_does_not_invalidate(): + eng = MainEngine(backend=UnitarySimulator(), engine_list=[]) + qureg = eng.allocate_qureg(2) + + X | qureg[0] + eng.flush() + + Y | qureg[1] + eng.flush() + + # Make sure that calling flush() multiple time is ok (before measurements) + eng.flush() + eng.flush() + + # Nothing should be added to the history here since no measurements or qubit deallocation happened + assert not eng.backend.history + assert np.allclose(eng.backend.unitary, np.kron(Y.matrix, X.matrix)) + + All(Measure) | qureg + + # Make sure that calling flush() multiple time is ok (after measurement) + eng.flush() + eng.flush() + + # Nothing should be added to the history here since no gate since measurements or qubit deallocation happened + assert not eng.backend.history + assert np.allclose(eng.backend.unitary, np.kron(Y.matrix, X.matrix)) + + +def test_unitary_after_deallocation_or_measurement(): + eng = MainEngine(backend=UnitarySimulator(), engine_list=[]) + qubit = eng.allocate_qubit() + X | qubit + + assert not eng.backend.history + + eng.flush() + Measure | qubit + + # FlushGate and MeasureGate do not append to the history + assert not eng.backend.history + assert np.allclose(eng.backend.unitary, X.matrix) + + with pytest.warns(UserWarning): + Y | qubit + + # YGate after FlushGate and MeasureGate does not append current unitary (identity) to the history + assert len(eng.backend.history) == 1 + assert np.allclose(eng.backend.unitary, Y.matrix) # Reset of unitary when applying Y above + assert np.allclose(eng.backend.history[0], X.matrix) + + # Still ok + eng.flush() + Measure | qubit + + # FlushGate and MeasureGate do not append to the history + assert len(eng.backend.history) == 1 + assert np.allclose(eng.backend.unitary, Y.matrix) + assert np.allclose(eng.backend.history[0], X.matrix) + + # Make sure that the new gate will trigger appending to the history and modify the current unitary + with pytest.warns(UserWarning): + Rx(1) | qubit + assert len(eng.backend.history) == 2 + assert np.allclose(eng.backend.unitary, Rx(1).matrix) + assert np.allclose(eng.backend.history[0], X.matrix) + assert np.allclose(eng.backend.history[1], Y.matrix) + + # -------------------------------------------------------------------------- + + eng = MainEngine(backend=UnitarySimulator(), engine_list=[]) + qureg = eng.allocate_qureg(2) + All(X) | qureg + + XX_matrix = np.kron(X.matrix, X.matrix) + assert not eng.backend.history + assert np.allclose(eng.backend.unitary, XX_matrix) + + eng.deallocate_qubit(qureg[0]) + + assert not eng.backend.history + + with pytest.warns(UserWarning): + Y | qureg[1] + + # An internal call to flush() happens automatically since the X + # gate occurs as the simulator is in an invalid state (after qubit + # deallocation) + assert len(eng.backend.history) == 1 + assert np.allclose(eng.backend.history[0], XX_matrix) + assert np.allclose(eng.backend.unitary, Y.matrix) + + # Still ok + eng.flush() + Measure | qureg[1] + + # Nothing should have changed + assert len(eng.backend.history) == 1 + assert np.allclose(eng.backend.history[0], XX_matrix) + assert np.allclose(eng.backend.unitary, Y.matrix) + + +def test_unitary_simulator(): + def create_random_unitary(n): + return unitary_group.rvs(2 ** n) + + mat1 = create_random_unitary(1) + mat2 = create_random_unitary(2) + mat3 = create_random_unitary(3) + mat4 = create_random_unitary(1) + + n_qubits = 3 + + def apply_gates(eng, qureg): + MatrixGate(mat1) | qureg[0] + MatrixGate(mat2) | qureg[1:] + MatrixGate(mat3) | qureg + + with Control(eng, qureg[1]): + MatrixGate(mat2) | (qureg[0], qureg[2]) + MatrixGate(mat4) | qureg[0] + + with Control(eng, qureg[1], ctrl_state='0'): + MatrixGate(mat1) | qureg[0] + with Control(eng, qureg[2], ctrl_state='0'): + MatrixGate(mat1) | qureg[0] + + for basis_state in [list(x[::-1]) for x in itertools.product([0, 1], repeat=2 ** n_qubits)][1:]: + ref_eng = MainEngine(engine_list=[], verbose=True) + ref_qureg = ref_eng.allocate_qureg(n_qubits) + ref_eng.backend.set_wavefunction(basis_state, ref_qureg) + apply_gates(ref_eng, ref_qureg) + + test_eng = MainEngine(backend=UnitarySimulator(), engine_list=[], verbose=True) + test_qureg = test_eng.allocate_qureg(n_qubits) + + assert np.allclose(test_eng.backend.unitary, np.identity(2 ** n_qubits)) + + apply_gates(test_eng, test_qureg) + + qubit_map, ref_state = ref_eng.backend.cheat() + assert qubit_map == {i: i for i in range(n_qubits)} + + test_state = test_eng.backend.unitary @ np.array(basis_state) + + assert np.allclose(ref_eng.backend.cheat()[1], test_state) + + ref_eng.flush() + test_eng.flush() + All(Measure) | ref_qureg + All(Measure) | test_qureg + + +def test_unitary_functional_measurement(): + eng = MainEngine(UnitarySimulator()) + qubits = eng.allocate_qureg(5) + # entangle all qubits: + H | qubits[0] + for qb in qubits[1:]: + CNOT | (qubits[0], qb) + eng.flush() + All(Measure) | qubits + + 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_unitary_measure_mapped_qubit(): + eng = MainEngine(UnitarySimulator()) + 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=X, qubits=([qb1],)) + cmd2 = Command( + engine=eng, + gate=Measure, + qubits=([qb1],), + controls=[], + tags=[LogicalQubitIDTag(2)], + ) + with pytest.raises(NotYetMeasuredError): + int(qb1) + with pytest.raises(NotYetMeasuredError): + int(qb2) + + eng.send([cmd0, cmd1]) + eng.flush() + eng.send([cmd2]) + with pytest.raises(NotYetMeasuredError): + int(qb1) + assert int(qb2) == 1 From 1c07475ad9dbc30baedddf78fddafa96112ffcdc Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Wed, 14 Jul 2021 19:12:09 +0200 Subject: [PATCH 066/113] Modernize ProjectQ (isort, PEP 257 docstrings, drop Python 2 code, more flake8 plugins) and fix phase estimation unit tests (#408) * Fix tests for the phase estimation decomposition * Fix docstrings according to PEP257 * Modernize code by removing some Python 2 compatibility code * Update CHANGELOG * Add isort to pre-commit-config.yaml and run it on the project * Move common exception classes to their own files * Fix changes from the latest on develop * Update CHANGELOG * Update Python code in documentation folder * Add `meta` repository to pre-commit configuration * Re-indent and cleanup .pre-commit-config.yaml * Add some more flake8 plugins to pre-commit config file - flake8-breakpoint - flake8-comprehensions - flake8-eradicate - flake8-mutable * Fix small bug with drawing using matplotlib * Address review comments * Better comment in docs/conf.py --- .pre-commit-config.yaml | 111 ++++++----- CHANGELOG.md | 8 + docs/conf.py | 146 +++++++------- docs/package_description.py | 50 +++-- examples/aqt.py | 11 +- examples/bellpair_circuit.py | 7 +- examples/control_tester.py | 4 +- examples/gate_zoo.py | 46 ++--- examples/grover.py | 14 +- examples/hws4.py | 7 +- examples/hws6.py | 11 +- examples/ibm.py | 11 +- examples/ionq.py | 2 +- examples/ionq_bv.py | 2 +- examples/ionq_half_adder.py | 3 +- examples/quantum_random_numbers.py | 4 +- examples/quantum_random_numbers_ibm.py | 4 +- examples/shor.py | 9 +- examples/teleport.py | 28 +-- examples/teleport_circuit.py | 8 +- examples/unitary_simulator.py | 2 +- projectq/backends/__init__.py | 11 +- projectq/backends/_aqt/_aqt.py | 85 ++++---- projectq/backends/_aqt/_aqt_http_client.py | 56 +++--- projectq/backends/_aqt/_aqt_test.py | 13 +- projectq/backends/_awsbraket/_awsbraket.py | 67 ++++--- .../_awsbraket/_awsbraket_boto3_client.py | 95 ++++----- .../_awsbraket_boto3_client_test.py | 1 + .../_awsbraket_boto3_client_test_fixtures.py | 28 ++- .../backends/_awsbraket/_awsbraket_test.py | 47 +++-- .../_awsbraket/_awsbraket_test_fixtures.py | 21 +- projectq/backends/_circuits/__init__.py | 5 +- projectq/backends/_circuits/_drawer.py | 119 +++++------ .../backends/_circuits/_drawer_matplotlib.py | 85 ++++---- .../_circuits/_drawer_matplotlib_test.py | 4 +- projectq/backends/_circuits/_drawer_test.py | 7 +- projectq/backends/_circuits/_plot.py | 105 +++++----- projectq/backends/_circuits/_plot_test.py | 4 +- projectq/backends/_circuits/_to_latex.py | 28 +-- projectq/backends/_circuits/_to_latex_test.py | 19 +- .../{_ionq/_ionq_exc.py => _exceptions.py} | 22 +-- projectq/backends/_ibm/_ibm.py | 68 ++++--- projectq/backends/_ibm/_ibm_http_client.py | 67 +++---- .../backends/_ibm/_ibm_http_client_test.py | 84 ++++---- projectq/backends/_ibm/_ibm_test.py | 20 +- projectq/backends/_ionq/_ionq.py | 62 +++--- projectq/backends/_ionq/_ionq_http_client.py | 17 +- .../backends/_ionq/_ionq_http_client_test.py | 2 +- projectq/backends/_ionq/_ionq_mapper.py | 15 +- projectq/backends/_ionq/_ionq_mapper_test.py | 2 +- projectq/backends/_ionq/_ionq_test.py | 7 +- projectq/backends/_printer.py | 20 +- projectq/backends/_printer_test.py | 5 +- projectq/backends/_resource.py | 28 +-- projectq/backends/_resource_test.py | 5 +- projectq/backends/_sim/__init__.py | 2 +- .../backends/_sim/_classical_simulator.py | 51 +++-- .../_sim/_classical_simulator_test.py | 16 +- projectq/backends/_sim/_pysim.py | 42 ++-- projectq/backends/_sim/_simulator.py | 159 +++++++-------- projectq/backends/_sim/_simulator_test.py | 26 ++- .../backends/_sim/_simulator_test_fixtures.py | 11 +- projectq/backends/_unitary.py | 16 +- projectq/backends/_unitary_test.py | 21 +- projectq/cengines/__init__.py | 17 +- projectq/cengines/_basicmapper.py | 20 +- projectq/cengines/_basicmapper_test.py | 4 +- projectq/cengines/_basics.py | 66 +++---- projectq/cengines/_basics_test.py | 35 ++-- projectq/cengines/_cmdmodifier.py | 8 +- projectq/cengines/_cmdmodifier_test.py | 6 +- projectq/cengines/_ibm5qubitmapper.py | 90 ++++----- projectq/cengines/_ibm5qubitmapper_test.py | 18 +- projectq/cengines/_linearmapper.py | 40 ++-- projectq/cengines/_linearmapper_test.py | 125 ++++++------ projectq/cengines/_main.py | 28 +-- projectq/cengines/_main_test.py | 4 +- projectq/cengines/_manualmapper.py | 13 +- projectq/cengines/_manualmapper_test.py | 6 +- projectq/cengines/_optimize.py | 44 ++--- projectq/cengines/_optimize_test.py | 10 +- .../cengines/_replacer/_decomposition_rule.py | 38 ++-- .../_replacer/_decomposition_rule_set.py | 76 +++----- .../_replacer/_decomposition_rule_test.py | 1 + projectq/cengines/_replacer/_replacer.py | 40 ++-- projectq/cengines/_replacer/_replacer_test.py | 6 +- projectq/cengines/_swapandcnotflipper.py | 33 ++-- projectq/cengines/_swapandcnotflipper_test.py | 14 +- projectq/cengines/_tagremover.py | 12 +- projectq/cengines/_tagremover_test.py | 4 +- projectq/cengines/_testengine.py | 27 ++- projectq/cengines/_testengine_test.py | 6 +- projectq/cengines/_twodmapper.py | 50 +++-- projectq/cengines/_twodmapper_test.py | 31 ++- projectq/libs/hist/_histogram.py | 3 +- projectq/libs/hist/_histogram_test.py | 6 +- projectq/libs/math/__init__.py | 11 +- projectq/libs/math/_constantmath.py | 31 ++- projectq/libs/math/_constantmath_test.py | 7 +- projectq/libs/math/_default_rules.py | 33 ++-- projectq/libs/math/_gates.py | 184 +++++++++--------- projectq/libs/math/_gates_math_test.py | 19 +- projectq/libs/math/_gates_test.py | 14 +- projectq/libs/math/_quantummath.py | 43 ++-- projectq/libs/math/_quantummath_test.py | 15 +- projectq/libs/revkit/__init__.py | 2 +- projectq/libs/revkit/_control_function.py | 13 +- .../libs/revkit/_control_function_test.py | 1 - projectq/libs/revkit/_permutation.py | 24 +-- projectq/libs/revkit/_permutation_test.py | 1 - projectq/libs/revkit/_phase.py | 42 ++-- projectq/libs/revkit/_phase_test.py | 6 +- projectq/libs/revkit/_utils.py | 2 +- projectq/meta/__init__.py | 15 +- projectq/meta/_compute.py | 65 +++---- projectq/meta/_compute_test.py | 11 +- projectq/meta/_control.py | 22 +-- projectq/meta/_control_test.py | 13 +- projectq/meta/_dagger.py | 20 +- projectq/meta/_dagger_test.py | 9 +- projectq/meta/_dirtyqubit.py | 15 +- projectq/meta/_dirtyqubit_test.py | 4 +- projectq/meta/_exceptions.py | 24 +++ projectq/meta/_logicalqubit.py | 12 +- projectq/meta/_loop.py | 30 ++- projectq/meta/_loop_test.py | 11 +- projectq/meta/_util.py | 18 +- projectq/meta/_util_test.py | 1 - projectq/ops/__init__.py | 30 +-- projectq/ops/_basics.py | 107 +++++----- projectq/ops/_basics_test.py | 9 +- projectq/ops/_command.py | 85 ++++---- projectq/ops/_command_test.py | 9 +- projectq/ops/_gates.py | 151 ++++++++------ projectq/ops/_gates_test.py | 12 +- projectq/ops/_metagates.py | 57 ++---- projectq/ops/_metagates_test.py | 23 ++- projectq/ops/_qaagate.py | 6 +- projectq/ops/_qaagate_test.py | 2 +- projectq/ops/_qftgate.py | 7 +- projectq/ops/_qpegate.py | 6 +- projectq/ops/_qpegate_test.py | 2 +- projectq/ops/_qubit_operator.py | 35 ++-- projectq/ops/_qubit_operator_test.py | 6 +- projectq/ops/_shortcuts.py | 10 +- projectq/ops/_shortcuts_test.py | 4 +- projectq/ops/_state_prep.py | 26 ++- projectq/ops/_state_prep_test.py | 2 +- projectq/ops/_time_evolution.py | 25 +-- projectq/ops/_time_evolution_test.py | 5 +- .../ops/_uniformly_controlled_rotation.py | 25 +-- .../_uniformly_controlled_rotation_test.py | 4 +- projectq/setups/_utils.py | 30 +-- projectq/setups/aqt.py | 25 +-- projectq/setups/aqt_test.py | 56 +++--- projectq/setups/awsbraket.py | 26 ++- projectq/setups/awsbraket_test.py | 5 +- projectq/setups/decompositions/__init__.py | 12 +- projectq/setups/decompositions/_gates_test.py | 10 +- .../decompositions/amplitudeamplification.py | 6 +- .../amplitudeamplification_test.py | 8 +- .../decompositions/arb1qubit2rzandry.py | 13 +- .../decompositions/arb1qubit2rzandry_test.py | 4 +- .../decompositions/carb1qubit2cnotrzandry.py | 11 +- projectq/setups/decompositions/cnot2cz.py | 7 +- .../setups/decompositions/cnot2cz_test.py | 3 +- projectq/setups/decompositions/cnot2rxx.py | 9 +- .../setups/decompositions/cnot2rxx_test.py | 2 +- .../setups/decompositions/cnu2toffoliandcu.py | 17 +- .../decompositions/cnu2toffoliandcu_test.py | 2 +- .../setups/decompositions/controlstate.py | 11 +- .../decompositions/controlstate_test.py | 9 +- projectq/setups/decompositions/crz2cxandrz.py | 2 +- projectq/setups/decompositions/entangle.py | 2 +- projectq/setups/decompositions/h2rx.py | 7 +- projectq/setups/decompositions/h2rx_test.py | 2 +- .../setups/decompositions/phaseestimation.py | 4 +- .../decompositions/phaseestimation_test.py | 45 +++-- .../decompositions/qft2crandhadamard.py | 2 +- .../setups/decompositions/qubitop2onequbit.py | 5 +- .../decompositions/qubitop2onequbit_test.py | 5 +- projectq/setups/decompositions/r2rzandph.py | 2 +- projectq/setups/decompositions/rx2rz.py | 9 +- projectq/setups/decompositions/ry2rz.py | 7 +- projectq/setups/decompositions/rz2rx.py | 8 +- projectq/setups/decompositions/rz2rx_test.py | 1 + .../setups/decompositions/sqrtswap2cnot.py | 6 +- .../decompositions/sqrtswap2cnot_test.py | 5 +- .../setups/decompositions/stateprep2cnot.py | 11 +- .../decompositions/stateprep2cnot_test.py | 7 +- projectq/setups/decompositions/swap2cnot.py | 4 +- .../setups/decompositions/time_evolution.py | 12 +- .../decompositions/time_evolution_test.py | 20 +- .../decompositions/toffoli2cnotandtgate.py | 2 +- .../uniformlycontrolledr2cnot.py | 5 +- .../uniformlycontrolledr2cnot_test.py | 4 +- projectq/setups/default.py | 17 +- projectq/setups/grid.py | 4 +- projectq/setups/grid_test.py | 5 +- projectq/setups/ibm.py | 46 ++--- projectq/setups/ibm_test.py | 56 +++--- projectq/setups/ionq.py | 8 +- projectq/setups/ionq_test.py | 2 +- projectq/setups/linear.py | 4 +- projectq/setups/linear_test.py | 5 +- projectq/setups/restrictedgateset.py | 71 +++---- projectq/setups/restrictedgateset_test.py | 9 +- projectq/setups/trapped_ion_decomposer.py | 35 ++-- .../setups/trapped_ion_decomposer_test.py | 12 +- projectq/tests/_factoring_test.py | 4 +- projectq/types/_qubit.py | 33 +--- projectq/types/_qubit_test.py | 2 +- pyproject.toml | 6 +- setup.cfg | 2 +- setup.py | 68 ++++--- 215 files changed, 2535 insertions(+), 2600 deletions(-) rename projectq/backends/{_ionq/_ionq_exc.py => _exceptions.py} (70%) create mode 100644 projectq/meta/_exceptions.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 03afd8fe9..1d179c97e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,56 +12,77 @@ # # See https://github.com/pre-commit/pre-commit +--- + ci: - skip: [check-manifest] + skip: [check-manifest] 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: trailing-whitespace - - id: fix-encoding-pragma + - repo: meta + hooks: + - id: check-useless-excludes + + - 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: 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 -# Changes tabs to spaces -- repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.10 - hooks: - - id: remove-tabs + - repo: https://github.com/PyCQA/isort + rev: 5.9.1 + hooks: + - id: isort + name: isort (python) -- 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/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://gitlab.com/pycqa/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - exclude: ^(docs/.*|tools/.*)$ + - repo: https://gitlab.com/PyCQA/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + name: flake8-strict + exclude: ^(.*_test\.py)$ + additional_dependencies: [flake8-comprehensions, flake8-breakpoint, + flake8-eradicate, flake8-mutable, + flake8-docstrings] + - id: flake8 + name: flake8-test-files + additional_dependencies: [flake8-comprehensions, flake8-breakpoint, + flake8-eradicate, flake8-mutable] + files: ^(.*_test\.py)$ -- repo: https://github.com/pre-commit/mirrors-pylint - rev: 'v3.0.0a3' - hooks: - - id: pylint - 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/pre-commit/mirrors-pylint + rev: 'v3.0.0a3' + hooks: + - id: pylint + 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, sympy] -- repo: https://github.com/mgedmin/check-manifest - rev: "0.46" - hooks: - - id: check-manifest - additional_dependencies: ['setuptools-scm', 'pybind11>=2.6'] + - 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 index 8b8a64b87..5adc9dbbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Prevent infinite recursion errors when too many compiler engines are added to the MainEngine +- Error in testing the decomposition for the phase estimation gate +- Make all docstrings PEP257 compliant ### Removed + +- Some compatibility code for Python 2.x + ### Repository +- Added `isort` to the list of pre-commit hooks +- Added `flake8-docstrings` to the flake8 checks to ensure PEP257 compliance for docstrings + ## [0.6.1] - 2021-06-23 ### Repository diff --git a/docs/conf.py b/docs/conf.py index 46527b4fa..4b2b99fa2 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,26 +16,20 @@ # 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 +"""Configuration file for generating the documentation for ProjectQ.""" -sys.path.insert(0, os.path.abspath('..')) +# pylint: disable=invalid-name +import functools +import importlib +import inspect +import os +import sys 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 -import projectq.setups.default -import projectq.setups.grid -import projectq.setups.ibm -import projectq.setups.linear -import projectq.setups.restrictedgateset -import projectq.setups.decompositions +sys.path.insert(0, os.path.abspath('..')) # for projectq +sys.path.append(os.path.abspath('.')) # for package_description # -- General configuration ------------------------------------------------ @@ -43,10 +37,6 @@ # # needs_sphinx = '1.0' -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -import sphinx_rtd_theme extensions = [ 'sphinx.ext.autodoc', @@ -76,7 +66,7 @@ # General information about the project. project = 'ProjectQ' -copyright = '2017, ProjectQ' +copyright = '2017-2021, ProjectQ' # pylint: disable=redefined-builtin author = 'ProjectQ' # The version info for the project you're documenting, acts as replacement for @@ -85,8 +75,8 @@ # # The short X.Y version. -release = version('projectq') -version = '.'.join(release.split('.')[:2]) +release = version('projectq') # Full version string +version = '.'.join(release.split('.')[:3]) # X.Y.Z # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -361,13 +351,23 @@ # texinfo_no_detailmenu = False # -- Options for sphinx.ext.linkcode -------------------------------------- -import inspect + + +def recursive_getattr(obj, attr, *args): + """Recursively get the attributes of a Python object.""" + + def _getattr(obj, attr): + return getattr(obj, attr, *args) + + return functools.reduce(_getattr, [obj] + attr.split('.')) def linkcode_resolve(domain, info): + """Change URLs in documentation on the fly.""" # Copyright 2018 ProjectQ (www.projectq.ch), all rights reserved. on_rtd = os.environ.get('READTHEDOCS') == 'True' github_url = "https://github.com/ProjectQ-Framework/ProjectQ/tree/" + github_tag = 'v' + version if on_rtd: rtd_tag = os.environ.get('READTHEDOCS_VERSION') if rtd_tag == 'latest': @@ -385,53 +385,53 @@ def linkcode_resolve(domain, info): github_tag = ''.join(github_tag) else: github_tag = rtd_tag - else: - github_tag = 'v' + version + if domain != 'py': return None - else: + try: + module = importlib.import_module(info['module']) + obj = recursive_getattr(module, info['fullname']) + except (AttributeError, ValueError): + # AttributeError: + # Object might be a non-static attribute of a class, e.g., self.num_qubits, which would only exist after init + # was called. + # For the moment we don't need a link for that as there is a link for the class already + # + # ValueError: + # info['module'] is empty + return None + try: + filepath = inspect.getsourcefile(obj) + line_number = inspect.getsourcelines(obj)[1] + except TypeError: + # obj might be a property or a static class variable, e.g., + # loop_tag_id in which case obj is an int and inspect will fail try: - if 'module' in info and 'fullname' in info and info['module'] and info['fullname']: - obj = eval(info['module'] + '.' + info['fullname']) + # load obj one hierarchy higher (either class or module) + new_higher_name = info['fullname'].split('.') + module = importlib.import_module(info['module']) + if len(new_higher_name) > 1: + obj = module else: - return None - except AttributeError: - # Object might be a non-static attribute of a class, e.g., - # self.num_qubits, which would only exist after init was called. - # For the moment we don't need a link for that as there is a link - # for the class already - return None - try: + obj = recursive_getattr(module, '.' + '.'.join(new_higher_name[:-1])) + filepath = inspect.getsourcefile(obj) line_number = inspect.getsourcelines(obj)[1] - except: - # obj might be a property or a static class variable, e.g., - # loop_tag_id in which case obj is an int and inspect will fail - try: - # load obj one hierarchy higher (either class or module) - new_higher_name = info['fullname'].split('.') - if len(new_higher_name) <= 1: - obj = eval(info['module']) - else: - obj = eval(info['module'] + '.' + '.'.join(new_higher_name[:-1])) - filepath = inspect.getsourcefile(obj) - line_number = inspect.getsourcelines(obj)[1] - except: - return None - # 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) - return url + except AttributeError: + return None + # Calculate the relative path of the object with respect to the root directory (ie. projectq/some/path/to/a/file.py) + projectq_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) + os.path.sep + idx = len(projectq_path) + relative_path = filepath[idx:] -# ------------------------------------------------------------------------------ + url = github_url + github_tag + "/" + relative_path + "#L" + str(line_number) + return url -import importlib -sys.path.append(os.path.abspath('.')) -desc = importlib.import_module('package_description') +# ------------------------------------------------------------------------------ +desc = importlib.import_module('package_description') PackageDescription = desc.PackageDescription # ------------------------------------------------------------------------------ @@ -458,7 +458,8 @@ def linkcode_resolve(domain, info): 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. +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( @@ -486,40 +487,46 @@ def linkcode_resolve(domain, info): The integration of RevKit into ProjectQ and other quantum programming languages is described in the paper - * Mathias Soeken, Thomas Haener, and Martin Roetteler "Programming Quantum Computers Using Design Automation," in: Design Automation and Test in Europe (2018) [`arXiv:1803.01022 `_] + * 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=''' -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. +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=''' 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. +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=''' -The operations collection consists of various default gates and is a work-in-progress, as users start to work with ProjectQ. +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=''' -The decomposition package is a collection of gate decomposition / replacement rules which can be used by, e.g., the AutoReplacer engine. +The decomposition package is a collection of gate decomposition / replacement rules which can be used by, +e.g., the AutoReplacer engine. ''', ), 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: +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: .. code-block:: python @@ -539,9 +546,10 @@ def linkcode_resolve(domain, info): ), PackageDescription( '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. -''', + ( + '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.' + ), ), ] # ------------------------------------------------------------------------------ diff --git a/docs/package_description.py b/docs/package_description.py index 6beeff848..fcfbb5b21 100644 --- a/docs/package_description.py +++ b/docs/package_description.py @@ -13,15 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing some helper classes for generating the documentation""" +"""Module containing some helper classes for generating the documentation.""" +import importlib import inspect -import sys -import os +import pkgutil class PackageDescription: # pylint: disable=too-many-instance-attributes,too-few-public-methods - """Class representing a package description""" + """A package description class.""" package_list = [] @@ -35,27 +35,24 @@ def __init__( # pylint: disable=too-many-arguments helper_submodules=None, ): """ + Initialize a PackageDescription object. + Args: name (str): Name of ProjectQ module desc (str): (optional) Description of module - module_special_members (str): (optional) Special members to include - in the documentation of the module - submodule_special_members (str): (optional) Special members to - include in the documentation of submodules - submodules_desc (str): (optional) Description to print out before - the list of submodules - helper_submodules (list): (optional) List of tuples for helper - sub-modules to include in the documentation. - Tuples are (section_title, submodukle_name, - automodule_properties) + module_special_members (str): (optional) Special members to include in the documentation of the module + submodule_special_members (str): (optional) Special members to include in the documentation of submodules + submodules_desc (str): (optional) Description to print out before the list of submodules + helper_submodules (list): (optional) List of tuples for helper sub-modules to include in the + documentation. + Tuples are (section_title, submodukle_name, automodule_properties) """ - self.name = pkg_name self.desc = desc if pkg_name not in PackageDescription.package_list: PackageDescription.package_list.append(pkg_name) - self.module = sys.modules['projectq.{}'.format(self.name)] + self.module = importlib.import_module('projectq.{}'.format(self.name)) self.module_special_members = module_special_members self.submodule_special_members = submodule_special_members @@ -63,15 +60,14 @@ def __init__( # pylint: disable=too-many-arguments 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 = [] + for _, module_name, _ in pkgutil.iter_modules(self.module.__path__, self.module.__name__ + '.'): + if not module_name.endswith('_test'): + try: + idx = len(self.module.__name__) + 1 + sub.append((module_name[idx:], importlib.import_module(module_name))) + except ImportError: + pass self.subpackages = [] self.submodules = [] @@ -99,9 +95,7 @@ def __init__( # pylint: disable=too-many-arguments self.members.sort(key=lambda x: x[0].lower()) def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-statements - """ - Conversion to ReST formatted string. - """ + """Conversion to ReST formatted string.""" new_lines = [] new_lines.append(self.name) new_lines.append('=' * len(self.name)) diff --git a/examples/aqt.py b/examples/aqt.py index e5fb6f658..68da13606 100644 --- a/examples/aqt.py +++ b/examples/aqt.py @@ -1,19 +1,22 @@ # -*- coding: utf-8 -*- # pylint: skip-file -import matplotlib.pyplot as plt +"""Example of running a quantum circuit using the AQT APIs.""" + import getpass +import matplotlib.pyplot as plt + +import projectq.setups.aqt from projectq import MainEngine from projectq.backends import AQTBackend from projectq.libs.hist import histogram -from projectq.ops import Measure, Entangle, All -import projectq.setups.aqt +from projectq.ops import All, Entangle, Measure def run_entangle(eng, num_qubits=3): """ - Runs an entangling operation on the provided compiler engine. + Run an entangling operation on the provided compiler engine. Args: eng (MainEngine): Main compiler engine to use. diff --git a/examples/bellpair_circuit.py b/examples/bellpair_circuit.py index d652ebc33..c4343f801 100755 --- a/examples/bellpair_circuit.py +++ b/examples/bellpair_circuit.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- # pylint: skip-file +"""Example implementation of a quantum circuit generating a Bell pair state.""" + import matplotlib.pyplot as plt +from teleport import create_bell_pair from projectq import MainEngine from projectq.backends import CircuitDrawer -from projectq.setups.default import get_engine_list from projectq.libs.hist import histogram - -from teleport import create_bell_pair +from projectq.setups.default import get_engine_list # create a main compiler engine drawing_engine = CircuitDrawer() diff --git a/examples/control_tester.py b/examples/control_tester.py index 5e671fafe..8f9463735 100755 --- a/examples/control_tester.py +++ b/examples/control_tester.py @@ -14,13 +14,15 @@ # limitations under the License. # pylint: skip-file +"""Example of using the control state for control qubits.""" from projectq.cengines import MainEngine from projectq.meta import Control -from projectq.ops import All, X, Measure, CtrlAll +from projectq.ops import All, CtrlAll, Measure, X def run_circuit(eng, circuit_num): + """Run the quantum circuit.""" qubit = eng.allocate_qureg(2) ctrl_fail = eng.allocate_qureg(3) ctrl_success = eng.allocate_qureg(3) diff --git a/examples/gate_zoo.py b/examples/gate_zoo.py index 404d9822b..c8e97ed97 100644 --- a/examples/gate_zoo.py +++ b/examples/gate_zoo.py @@ -1,45 +1,45 @@ # -*- coding: utf-8 -*- # pylint: skip-file +"""Showcase most of the quantum gates available in ProjectQ.""" + import os import sys from projectq import MainEngine from projectq.backends import CircuitDrawer from projectq.ops import ( - X, - Y, - Z, + CNOT, + QFT, + All, + Barrier, + BasicMathGate, + C, + Entangle, + H, + Measure, + Ph, + QubitOperator, Rx, Ry, Rz, - Ph, S, - T, - H, - Toffoli, - Barrier, - Swap, SqrtSwap, SqrtX, - C, - CNOT, - Entangle, - QFT, - TimeEvolution, - QubitOperator, - BasicMathGate, - Measure, - All, + Swap, + T, Tensor, + TimeEvolution, + Toffoli, + X, + Y, + Z, get_inverse, ) def zoo_profile(): - ''' - Generate and display the zoo of quantum gates. - ''' + """Generate and display the zoo of quantum gates.""" # create a main compiler engine with a drawing backend drawing_engine = CircuitDrawer() locations = {0: 1, 1: 2, 2: 0, 3: 3} @@ -104,7 +104,7 @@ def add(x, y): def openfile(filename): - ''' + """ Open a file. Args: @@ -112,7 +112,7 @@ def openfile(filename): Return: bool: succeed if True. - ''' + """ platform = sys.platform if platform == "linux" or platform == "linux2": os.system('xdg-open %s' % filename) diff --git a/examples/grover.py b/examples/grover.py index feff9ef58..539fc525a 100755 --- a/examples/grover.py +++ b/examples/grover.py @@ -1,16 +1,18 @@ # -*- coding: utf-8 -*- # pylint: skip-file +"""Example implementation of Grover's algorithm.""" + import math from projectq import MainEngine -from projectq.ops import H, Z, X, Measure, All -from projectq.meta import Loop, Compute, Uncompute, Control +from projectq.meta import Compute, Control, Loop, Uncompute +from projectq.ops import All, H, Measure, X, Z def run_grover(eng, n, oracle): """ - Runs Grover's algorithm on n qubit using the provided quantum oracle. + Run Grover's algorithm on n qubit using the provided quantum oracle. Args: eng (MainEngine): Main compiler engine to run Grover on. @@ -62,8 +64,10 @@ def run_grover(eng, n, oracle): def alternating_bits_oracle(eng, qubits, output): """ - Marks the solution string 1,0,1,0,...,0,1 by flipping the output qubit, - conditioned on qubits being equal to the alternating bit-string. + Alternating bit oracle. + + Mark the solution string 1,0,1,0,...,0,1 by flipping the output qubit, conditioned on qubits being equal to the + alternating bit-string. Args: eng (MainEngine): Main compiler engine the algorithm is being run on. diff --git a/examples/hws4.py b/examples/hws4.py index 14a4fe551..8ba5b6174 100644 --- a/examples/hws4.py +++ b/examples/hws4.py @@ -1,14 +1,17 @@ # -*- coding: utf-8 -*- # pylint: skip-file +"""Example of a 4-qubit phase function.""" + 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 +from projectq.meta import Compute, Uncompute +from projectq.ops import All, H, Measure, X # phase function def f(a, b, c, d): + """Phase function.""" return (a and b) ^ (c and d) diff --git a/examples/hws6.py b/examples/hws6.py index 38892f371..bf5540d07 100644 --- a/examples/hws6.py +++ b/examples/hws6.py @@ -1,16 +1,19 @@ # -*- 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 -from projectq.libs.revkit import PhaseOracle, PermutationOracle +"""Example of a 6-qubit phase function.""" import revkit +from projectq.cengines import MainEngine +from projectq.libs.revkit import PermutationOracle, PhaseOracle +from projectq.meta import Compute, Dagger, Uncompute +from projectq.ops import All, H, Measure, X + # phase function def f(a, b, c, d, e, f): + """Phase function.""" return (a and b) ^ (c and d) ^ (e and f) diff --git a/examples/ibm.py b/examples/ibm.py index 6914d051f..24bd0c097 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -1,19 +1,22 @@ # -*- coding: utf-8 -*- # pylint: skip-file -import matplotlib.pyplot as plt +"""Example of running a quantum circuit using the IBM QE APIs.""" + import getpass +import matplotlib.pyplot as plt + +import projectq.setups.ibm from projectq import MainEngine from projectq.backends import IBMBackend from projectq.libs.hist import histogram -from projectq.ops import Measure, Entangle, All -import projectq.setups.ibm +from projectq.ops import All, Entangle, Measure def run_entangle(eng, num_qubits=3): """ - Runs an entangling operation on the provided compiler engine. + Run an entangling operation on the provided compiler engine. Args: eng (MainEngine): Main compiler engine to use. diff --git a/examples/ionq.py b/examples/ionq.py index 8ca8cc66b..71f0c6831 100644 --- a/examples/ionq.py +++ b/examples/ionq.py @@ -29,7 +29,7 @@ def run_entangle(eng, num_qubits=3): """ - Runs an entangling operation on the provided compiler engine. + Run an entangling operation on the provided compiler engine. Args: eng (MainEngine): Main compiler engine to use. diff --git a/examples/ionq_bv.py b/examples/ionq_bv.py index 61aa3c16e..afaf48c78 100644 --- a/examples/ionq_bv.py +++ b/examples/ionq_bv.py @@ -30,13 +30,13 @@ 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): + """Run the quantum circuit.""" s = ('{0:0' + str(input_size) + 'b}').format(s_int) print("Secret string: ", s) print("Number of qubits: ", str(input_size + 1)) diff --git a/examples/ionq_half_adder.py b/examples/ionq_half_adder.py index 798ed4ac4..905fa38e9 100644 --- a/examples/ionq_half_adder.py +++ b/examples/ionq_half_adder.py @@ -14,7 +14,7 @@ # limitations under the License. # pylint: skip-file -"""Example of a basic 'half-adder' circuit using an IonQBackend""" +"""Example of a basic 'half-adder' circuit using an IonQBackend.""" import getpass import random @@ -30,6 +30,7 @@ def run_half_adder(eng): + """Run the half-adder circuit.""" # allocate the quantum register to entangle circuit = eng.allocate_qureg(4) qubit1, qubit2, qubit3, qubit4 = circuit diff --git a/examples/quantum_random_numbers.py b/examples/quantum_random_numbers.py index 796603d37..afa09dc4f 100755 --- a/examples/quantum_random_numbers.py +++ b/examples/quantum_random_numbers.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- # pylint: skip-file -from projectq.ops import H, Measure +"""Example of a simple quantum random number generator.""" + from projectq import MainEngine +from projectq.ops import H, Measure # create a main compiler engine eng = MainEngine() diff --git a/examples/quantum_random_numbers_ibm.py b/examples/quantum_random_numbers_ibm.py index 77e427434..adfcb7101 100755 --- a/examples/quantum_random_numbers_ibm.py +++ b/examples/quantum_random_numbers_ibm.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- # pylint: skip-file +"""Example of a simple quantum random number generator using IBM's API.""" + import projectq.setups.ibm -from projectq.ops import H, Measure from projectq import MainEngine from projectq.backends import IBMBackend +from projectq.ops import H, Measure # create a main compiler engine eng = MainEngine(IBMBackend(), engine_list=projectq.setups.ibm.get_engine_list()) diff --git a/examples/shor.py b/examples/shor.py index f604abb25..39dce16dc 100755 --- a/examples/shor.py +++ b/examples/shor.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # pylint: skip-file -from __future__ import print_function +"""Example implementation of Shor's algorithm.""" import math import random @@ -17,7 +17,7 @@ import projectq.libs.math import projectq.setups.decompositions -from projectq.backends import Simulator, ResourceCounter +from projectq.backends import ResourceCounter, Simulator from projectq.cengines import ( AutoReplacer, DecompositionRuleSet, @@ -28,12 +28,12 @@ ) 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 QFT, All, BasicMathGate, H, Measure, R, Swap, X, get_inverse def run_shor(eng, N, a, verbose=False): """ - Runs the quantum subroutine of Shor's algorithm for factoring. + Run the quantum subroutine of Shor's algorithm for factoring. Args: eng (MainEngine): Main compiler engine to use. @@ -92,6 +92,7 @@ def run_shor(eng, N, a, verbose=False): # Filter function, which defines the gate set for the first optimization # (don't decompose QFTs and iQFTs to make cancellation easier) def high_level_gates(eng, cmd): + """Filter high-level gates.""" g = cmd.gate if g == QFT or get_inverse(g) == QFT or g == Swap: return True diff --git a/examples/teleport.py b/examples/teleport.py index 2a6b964da..4d2e684aa 100755 --- a/examples/teleport.py +++ b/examples/teleport.py @@ -1,16 +1,19 @@ # -*- coding: utf-8 -*- # pylint: skip-file -from projectq.ops import CNOT, H, Measure, Rz, X, Z +"""Example of a quantum teleportation circuit.""" + from projectq import MainEngine -from projectq.meta import Dagger, Control +from projectq.meta import Control, Dagger +from projectq.ops import CNOT, H, Measure, Rz, X, Z 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)`). + Create a Bell pair state with two qubits. + + 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)`). Args: eng (MainEngine): MainEngine from which to allocate the qubits. @@ -29,18 +32,16 @@ def create_bell_pair(eng): def run_teleport(eng, state_creation_function, verbose=False): """ - Runs quantum teleportation on the provided main compiler engine. + Run quantum teleportation on the provided main compiler engine. - Creates a state from |0> using the state_creation_function, teleports this - state to Bob who then tries to uncompute his qubit using the inverse of - the state_creation_function. If successful, deleting the qubit won't raise - an error in the underlying Simulator back-end (else it will). + Creates a state from |0> using the state_creation_function, teleports this state to Bob who then tries to + uncompute his qubit using the inverse of the state_creation_function. If successful, deleting the qubit won't + raise an error in the underlying Simulator back-end (else it will). Args: eng (MainEngine): Main compiler engine to run the circuit on. - state_creation_function (function): Function which accepts the main - engine and a qubit in state |0>, which it then transforms to the - state that Alice would like to send to Bob. + state_creation_function (function): Function which accepts the main engine and a qubit in state |0>, which it + then transforms to the state that Alice would like to send to Bob. verbose (bool): If True, info messages will be printed. """ @@ -96,6 +97,7 @@ def run_teleport(eng, state_creation_function, verbose=False): # we would like to send. Bob can then try to uncompute it and, if he # arrives back at |0>, we know that the teleportation worked. def create_state(eng, qb): + """Create a quantum state.""" H | qb Rz(1.21) | qb diff --git a/examples/teleport_circuit.py b/examples/teleport_circuit.py index 1f002b915..1dfd3a485 100755 --- a/examples/teleport_circuit.py +++ b/examples/teleport_circuit.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- # pylint: skip-file -from projectq import MainEngine -from projectq.backends import CircuitDrawer +"""Example if drawing of a quantum teleportation circuit.""" import teleport +from projectq import MainEngine +from projectq.backends import CircuitDrawer + if __name__ == "__main__": # create a main compiler engine with a simulator backend: drawing_engine = CircuitDrawer() @@ -15,7 +17,7 @@ # we just want to draw the teleportation circuit def create_state(eng, qb): - pass + """Create a quantum state.""" # run the teleport and then, let Bob try to uncompute his qubit: teleport.run_teleport(eng, create_state, verbose=False) diff --git a/examples/unitary_simulator.py b/examples/unitary_simulator.py index 420034784..4a840dc70 100644 --- a/examples/unitary_simulator.py +++ b/examples/unitary_simulator.py @@ -22,7 +22,7 @@ from projectq.backends import UnitarySimulator from projectq.cengines import MainEngine from projectq.meta import Control -from projectq.ops import All, X, QFT, Measure, CtrlAll +from projectq.ops import QFT, All, CtrlAll, Measure, X def run_circuit(eng, n_qubits, circuit_num, gate_after_measure=False): diff --git a/projectq/backends/__init__.py b/projectq/backends/__init__.py index 044b78b84..8a8a50646 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -28,12 +28,13 @@ * 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 -from ._sim import Simulator, ClassicalSimulator -from ._resource import ResourceCounter -from ._ibm import IBMBackend from ._aqt import AQTBackend from ._awsbraket import AWSBraketBackend +from ._circuits import CircuitDrawer, CircuitDrawerMatplotlib +from ._exceptions import DeviceNotHandledError, DeviceOfflineError, DeviceTooSmall +from ._ibm import IBMBackend from ._ionq import IonQBackend +from ._printer import CommandPrinter +from ._resource import ResourceCounter +from ._sim import ClassicalSimulator, Simulator from ._unitary import UnitarySimulator diff --git a/projectq/backends/_aqt/_aqt.py b/projectq/backends/_aqt/_aqt.py index e250e239c..245b074f6 100644 --- a/projectq/backends/_aqt/_aqt.py +++ b/projectq/backends/_aqt/_aqt.py @@ -12,17 +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. -""" Back-end to run quantum program on AQT's API.""" + +"""Back-end to run quantum program on AQT's API.""" import math import random 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.meta import LogicalQubitIDTag, get_control_count +from projectq.ops import Allocate, Barrier, Deallocate, FlushGate, Measure, Rx, Rxx, Ry from projectq.types import WeakQubitRef -from ._aqt_http_client import send, retrieve +from .._exceptions import InvalidCommandError +from ._aqt_http_client import retrieve, send # _rearrange_result & _format_counts imported and modified from qiskit @@ -44,8 +46,10 @@ def _format_counts(samples, length): 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. + Backend for building circuits and submitting them to the AQT API. + + 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__( @@ -63,24 +67,19 @@ def __init__( Initialize the Backend object. Args: - use_hardware (bool): If True, the code is run on the AQT quantum - chip (instead of using the AQT simulator) - num_runs (int): Number of runs to collect statistics. - (default is 100, max is usually around 200) - verbose (bool): If True, statistics are printed, in addition to - the measurement result being registered (at the end of the - circuit). + use_hardware (bool): If True, the code is run on the AQT quantum chip (instead of using the AQT simulator) + num_runs (int): Number of runs to collect statistics. (default is 100, max is usually around 200) + verbose (bool): If True, statistics are printed, in addition to the measurement result being registered + (at the end of the circuit). token (str): AQT user API token. device (str): name of the AQT device to use. simulator By default - num_retries (int): Number of times to retry to obtain - results from the AQT API. (default is 3000) - interval (float, int): Number of seconds between successive - attempts to obtain results from the AQT API. + num_retries (int): Number of times to retry to obtain results from the AQT API. (default is 3000) + interval (float, int): Number of seconds between successive attempts to obtain results from the AQT API. (default is 1) - retrieve_execution (int): Job ID to retrieve instead of re- - running the circuit (e.g., if previous run timed out). + 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._reset() if use_hardware: self.device = device @@ -92,7 +91,7 @@ def __init__( self._token = token self._num_retries = num_retries self._interval = interval - self._probabilities = dict() + self._probabilities = {} self._circuit = [] self._mapper = [] self._measured_ids = [] @@ -130,7 +129,7 @@ def _store(self, cmd): cmd: Command to store """ if self._clear: - self._probabilities = dict() + self._probabilities = {} self._clear = False self._circuit = [] self._allocated_qubits = set() @@ -168,16 +167,16 @@ def _store(self, cmd): return if gate == Barrier: return - raise Exception('Invalid command: ' + str(cmd)) + raise InvalidCommandError('Invalid command: ' + str(cmd)) def _logical_to_physical(self, qb_id): """ Return the physical location of the qubit with the given logical id. + If no mapper is present then simply returns the qubit ID. Args: - qb_id (int): ID of the logical qubit whose position should be - returned. + qb_id (int): ID of the logical qubit whose position should be returned. """ try: mapping = self.main_engine.mapper.current_mapping @@ -199,29 +198,30 @@ def _logical_to_physical(self, qb_id): def get_probabilities(self, qureg): """ - Return the list of basis states with corresponding probabilities. - If input qureg is a subset of the register used for the experiment, - then returns the projected probabilities over the other states. - The measured bits are ordered according to the supplied quantum - register, i.e., the left-most bit in the state-string corresponds to - the first qubit in the supplied quantum register. + Return the probability of the outcome `bit_string` when measuring the quantum register `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. + Warning: Only call this function after the circuit has been executed! + Args: - qureg (list): Quantum register determining the order of the - qubits. + qureg (list): Quantum register determining the order of the qubits. + Returns: - probability_dict (dict): Dictionary mapping n-bit strings to - probabilities. + 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). + 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). """ if len(self._probabilities) == 0: raise RuntimeError("Please, run the circuit first!") - probability_dict = dict() + probability_dict = {} for state in self._probabilities: mapped_state = ['0'] * len(qureg) for i, qubit in enumerate(qureg): @@ -236,8 +236,7 @@ def _run(self): """ Run the circuit. - Send the circuit via the AQT API using the provided user - token / ask for the user token. + Send the circuit via the AQT API using the provided user token / ask for the user token. """ # finally: measurements # NOTE AQT DOESN'T SEEM TO HAVE MEASUREMENT INSTRUCTIONS (no @@ -303,7 +302,9 @@ def _run(self): def receive(self, command_list): """ - Receives a command list and, for each command, stores it until completion. Upon flush, send the data to the + Receive a list of commands. + + Receive a command list and, for each command, stores it until completion. Upon flush, send the data to the AQT API. Args: diff --git a/projectq/backends/_aqt/_aqt_http_client.py b/projectq/backends/_aqt/_aqt_http_client.py index a38b69329..7f869d254 100644 --- a/projectq/backends/_aqt/_aqt_http_client.py +++ b/projectq/backends/_aqt/_aqt_http_client.py @@ -12,41 +12,49 @@ # 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""" + +"""Back-end to run quantum program on AQT cloud platform.""" import getpass import signal import time import requests -from requests.compat import urljoin from requests import Session +from requests.compat import urljoin + +from .._exceptions import DeviceOfflineError, DeviceTooSmall, RequestTimeoutError # An AQT token can be requested at: # https://gateway-portal.aqt.eu/ + _API_URL = 'https://gateway.aqt.eu/marmot/' class AQT(Session): - """Class managing the session to AQT's APIs""" + """Class managing the session to AQT's APIs.""" def __init__(self): + """Initialize an AQT session with AQT's APIs.""" super().__init__() - self.backends = dict() + self.backends = {} self.timeout = 5.0 self.token = None def update_devices_list(self, verbose=False): """ + Update the internal device list. + Returns: (list): list of available devices - Up to my knowledge there is no proper API call for online devices, - so we just assume that the list from AQT portal always up to date + Note: + Up to my knowledge there is no proper API call for online devices, so we just assume that the list from + AQT portal always up to date """ # TODO: update once the API for getting online devices is available - self.backends = dict() + self.backends = {} self.backends['aqt_simulator'] = {'nq': 11, 'version': '0.0.1', 'url': 'sim/'} self.backends['aqt_simulator_noise'] = { 'nq': 11, @@ -60,7 +68,7 @@ def update_devices_list(self, verbose=False): def is_online(self, device): """ - Check whether a device is currently online + Check whether a device is currently online. Args: device (str): name of the aqt device to use @@ -72,7 +80,7 @@ def is_online(self, device): def can_run_experiment(self, info, device): """ - check if the device is big enough to run the code + Check if the device is big enough to run the code. Args: info (dict): dictionary sent by the backend containing the code to @@ -87,6 +95,8 @@ def can_run_experiment(self, info, device): def authenticate(self, token=None): """ + Authenticate with the AQT Web API. + Args: token (str): AQT user API token. """ @@ -96,7 +106,7 @@ def authenticate(self, token=None): self.token = token def run(self, info, device): - """Run a quantum circuit""" + """Run a quantum circuit.""" argument = { 'data': info['circuit'], 'access_token': self.token, @@ -114,9 +124,7 @@ def run(self, info, device): 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 - """ + """Get the result of an execution.""" if verbose: print("Waiting for results. [Job ID: {}]".format(execution_id)) @@ -153,21 +161,12 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover if original_sigint_handler is not None: signal.signal(signal.SIGINT, original_sigint_handler) - raise Exception("Timeout. The ID of your submitted job is {}.".format(execution_id)) - - -class DeviceTooSmall(Exception): - """Exception raised if the device is too small to run the circuit""" - - -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" + raise RequestTimeoutError("Timeout. The ID of your submitted job is {}.".format(execution_id)) def show_devices(verbose=False): """ - Access the list of available devices and their properties (ex: for setup - configuration) + Access the list of available devices and their properties (ex: for setup configuration). Args: verbose (bool): If True, additional information is printed @@ -182,7 +181,7 @@ def show_devices(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. + Retrieve a previously run job by its ID. Args: device (str): Device on which the code was run / is running. @@ -208,15 +207,14 @@ def send( verbose=False, ): # pylint: disable=too-many-arguments """ - Sends cicruit through the AQT API and runs the quantum circuit. + Send cicruit through the AQT API and runs the quantum circuit. Args: 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. - 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). + 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 form the AQT server diff --git a/projectq/backends/_aqt/_aqt_test.py b/projectq/backends/_aqt/_aqt_test.py index a11293853..397f6ffd9 100644 --- a/projectq/backends/_aqt/_aqt_test.py +++ b/projectq/backends/_aqt/_aqt_test.py @@ -14,14 +14,15 @@ # limitations under the License. """Tests for projectq.backends._aqt._aqt.py.""" -import pytest import math +import pytest + from projectq import MainEngine from projectq.backends._aqt import _aqt -from projectq.types import WeakQubitRef -from projectq.cengines import DummyEngine, BasicMapperEngine +from projectq.cengines import BasicMapperEngine, DummyEngine from projectq.ops import ( + NOT, All, Allocate, Barrier, @@ -29,11 +30,10 @@ Deallocate, Entangle, Measure, - NOT, Rx, + Rxx, Ry, Rz, - Rxx, S, Sdag, T, @@ -42,6 +42,7 @@ Y, Z, ) +from projectq.types import WeakQubitRef # Insure that no HTTP request can be made in all tests in this module @@ -202,7 +203,7 @@ def mock_send(*args, **kwargs): backend.get_probabilities([]) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res diff --git a/projectq/backends/_awsbraket/_awsbraket.py b/projectq/backends/_awsbraket/_awsbraket.py index 9580c36f0..ba27e64f7 100755 --- a/projectq/backends/_awsbraket/_awsbraket.py +++ b/projectq/backends/_awsbraket/_awsbraket.py @@ -12,44 +12,47 @@ # 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 +"""Back-end to run quantum program on AWS Braket provided devices.""" + import json +import random from projectq.cengines import BasicEngine -from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control -from projectq.types import WeakQubitRef +from projectq.meta import LogicalQubitIDTag, get_control_count, has_negative_control from projectq.ops import ( - R, - SwapGate, + Allocate, + Barrier, + DaggeredGate, + Deallocate, + FlushGate, HGate, + Measure, + R, Rx, Ry, Rz, - SGate, Sdag, - TGate, + SGate, + SqrtXGate, + SwapGate, Tdag, + TGate, XGate, YGate, ZGate, - SqrtXGate, - Measure, - Allocate, - Deallocate, - Barrier, - FlushGate, - DaggeredGate, ) +from projectq.types import WeakQubitRef -# TODO: Add MatrixGate to cover the unitary operation in the SV1 simulator +from ._awsbraket_boto3_client import retrieve, send -from ._awsbraket_boto3_client import send, retrieve +# TODO: Add MatrixGate to cover the unitary operation in the SV1 simulator class AWSBraketBackend(BasicEngine): # pylint: disable=too-many-instance-attributes """ + Compiler engine class implementing support for the AWS Braket framework. + 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. """ @@ -85,7 +88,7 @@ def __init__( timed out). The TaskArns have the form: "arn:aws:braket:us-east-1:123456789012:quantum-task/5766032b-2b47-4bf9-cg00-f11851g4015b" """ - BasicEngine.__init__(self) + super().__init__() self._reset() if use_hardware: self.device = device @@ -98,7 +101,7 @@ def __init__( self._s3_folder = s3_folder self._num_retries = num_retries self._interval = interval - self._probabilities = dict() + self._probabilities = {} self._circuit = "" self._measured_ids = [] self._allocated_qubits = set() @@ -156,7 +159,6 @@ def is_available(self, cmd): # pylint: disable=too-many-return-statements,too-m Args: cmd (Command): Command for which to check availability """ - gate = cmd.gate if gate in (Measure, Allocate, Deallocate, Barrier): return True @@ -270,7 +272,7 @@ def _store(self, cmd): # pylint: disable=too-many-branches ) if self._clear: - self._probabilities = dict() + self._probabilities = {} self._clear = False self._circuit = "" self._allocated_qubits = set() @@ -321,8 +323,7 @@ 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. + 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 @@ -338,24 +339,23 @@ def _logical_to_physical(self, qb_id): def get_probabilities(self, qureg): """ - Return the list of basis states with corresponding probabilities. If input qureg is a subset of the register - used for the experiment, then returns the projected probabilities over the other states. + 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. + qureg (list): Quantum register determining the order of the qubits. Returns: - probability_dict (dict): Dictionary mapping n-bit strings to - probabilities. + 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). + 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! @@ -367,12 +367,11 @@ def get_probabilities(self, qureg): 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() + probability_dict = {} for state in self._probabilities: mapped_state = ['0'] * len(qureg) for i, qubit in enumerate(qureg): diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py index 392d1af0e..39ad3b265 100755 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py @@ -29,23 +29,25 @@ import boto3 import botocore +from .._exceptions import DeviceOfflineError, DeviceTooSmall, RequestTimeoutError + class AWSBraket: - """ - Manage a session between ProjectQ and AWS Braket service. - """ + """Manage a session between ProjectQ and AWS Braket service.""" def __init__(self): - self.backends = dict() + """Initialize a session with the AWS Braket Web APIs.""" + self.backends = {} self.timeout = 5.0 - self._credentials = dict() + self._credentials = {} self._s3_folder = [] def authenticate(self, credentials=None): """ + Authenticate with AWSBraket Web APIs. + Args: - credentials (dict): mapping the AWS key credentials as the - AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + 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: ") @@ -55,9 +57,10 @@ def authenticate(self, credentials=None): def get_s3_folder(self, s3_folder=None): """ + Get the S3 bucket that contains the results. + Args: - s3_folder (list): contains the S3 bucket and directory to store the - results. + s3_folder (list): contains the S3 bucket and directory to store the results. """ if s3_folder is None: # pragma: no cover s3_bucket = input("Enter the S3 Bucket configured in Braket: ") @@ -68,20 +71,18 @@ def get_s3_folder(self, s3_folder=None): def get_list_devices(self, verbose=False): """ - Get the list of available devices with their basic properties + 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 + (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() + self.backends = {} region_names = ['us-west-1', 'us-east-1'] for region in region_names: client = boto3.client( @@ -156,15 +157,13 @@ def can_run_experiment(self, info, device): Check if the device is big enough to run the code. Args: - info (dict): dictionary sent by the backend containing the code to - run + info (dict): dictionary sent by the backend containing the code to run device (str): name of the 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'] @@ -175,14 +174,11 @@ 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 + info (dict): dictionary sent by the backend containing the code to run device (str): name of the device to use Returns: task_arn (str): The Arn of the task - - """ argument = { 'circ': info['circuit'], @@ -220,9 +216,7 @@ def run(self, info, device): return response['quantumTaskArn'] def get_result(self, execution_id, num_retries=30, interval=1, verbose=False): # pylint: disable=too-many-locals - """ - Get the result of an execution - """ + """Get the result of an execution.""" if verbose: print("Waiting for results. [Job Arn: {}]".format(execution_id)) @@ -233,18 +227,19 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover def _calculate_measurement_probs(measurements): """ - Calculate the measurement probabilities based on the - list of measurements for a job sent to a SV1 Braket simulator + Calculate the measurement probabilities . + + 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 + 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)] + unique_mes = [list(x) for x in {tuple(x) for x in measurements}] total_unique_mes = len(unique_mes) len_qubits = len(unique_mes[0]) measurements_probabilities = {} @@ -315,29 +310,19 @@ def _calculate_measurement_probs(measurements): if original_sigint_handler is not None: signal.signal(signal.SIGINT, original_sigint_handler) - raise Exception( + raise RequestTimeoutError( "Timeout. " "The Arn of your submitted job is {} and the status " "of the job is {}.".format(execution_id, status) ) -class DeviceTooSmall(Exception): - """Exception raised if the device is too small to run the circuit""" - - -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" - - def show_devices(credentials=None, verbose=False): """ - Access the list of available devices and their properties (ex: for setup - configuration) + Access the list of available devices and their properties (ex: for setup configuration). Args: - credentials (dict): Dictionary storing the AWS credentials with - keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + 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: @@ -353,16 +338,14 @@ def show_devices(credentials=None, verbose=False): def retrieve(credentials, task_arn, num_retries=30, interval=1, verbose=False): """ - Retrieves a job/task by its Arn. + Retrieve a job/task by its Arn. Args: - credentials (dict): Dictionary storing the AWS credentials with - keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + credentials (dict): Dictionary storing the AWS credentials with keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. task_arn (str): The Arn of the task to retreive Returns: - (dict) measurement probabilities from the result - stored in the S3 folder + (dict) measurement probabilities from the result stored in the S3 folder """ try: awsbraket_session = AWSBraket() @@ -385,22 +368,18 @@ def send( # pylint: disable=too-many-branches,too-many-arguments,too-many-local info, device, credentials, s3_folder, num_retries=30, interval=1, verbose=False ): """ - Sends cicruit through the Boto3 SDK and runs the quantum circuit. + Send 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). + 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() diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py index 267e982ca..795d9e308 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py @@ -23,6 +23,7 @@ _has_boto3 = True try: import botocore + from projectq.backends._awsbraket import _awsbraket_boto3_client except ImportError: _has_boto3 = False diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py index 8092a4a45..0fa5cb2b9 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py @@ -35,8 +35,11 @@ # * real_device_online_setup # ============================================================================== -from io import StringIO +"""Define test fixtures for the AWSBraket HTTP client.""" + import json +from io import StringIO + import pytest try: @@ -44,8 +47,10 @@ except ImportError: class StreamingBody: + """Dummy implementation of a StreamingBody.""" + def __init__(self, raw_stream, content_length): - pass + """Initialize a dummy StreamingBody.""" # ============================================================================== @@ -53,11 +58,13 @@ def __init__(self, raw_stream, content_length): @pytest.fixture def arntask(): + """Define an ARNTask test setup.""" return 'arn:aws:braket:us-east-1:id:taskuuid' @pytest.fixture def creds(): + """Credentials test setup.""" return { 'AWS_ACCESS_KEY_ID': 'aws_access_key_id', 'AWS_SECRET_KEY': 'aws_secret_key', @@ -66,11 +73,13 @@ def creds(): @pytest.fixture def s3_folder(): - return ['S3Bucket', "S3Directory"] + """S3 folder value test setup.""" + return ['S3Bucket', 'S3Directory'] @pytest.fixture def info(): + """Info value test setup.""" return { 'circuit': '{"braketSchemaHeader":' '{"name": "braket.ir.jaqcd.program", "version": "1"}, ' @@ -86,6 +95,7 @@ def info(): @pytest.fixture def results_json(): + """Results test setup.""" return json.dumps( { "braketSchemaHeader": { @@ -106,6 +116,7 @@ def results_json(): @pytest.fixture def results_dict(results_json): + """Results dict test setup.""" body = StreamingBody(StringIO(results_json), len(results_json)) return { 'ResponseMetadata': { @@ -118,11 +129,13 @@ def results_dict(results_json): @pytest.fixture def res_completed(): + """Completed results test setup.""" return {"000": 0.1, "010": 0.4, "110": 0.1, "001": 0.1, "111": 0.3} @pytest.fixture def search_value(): + """Search value test setup.""" return { "devices": [ { @@ -159,6 +172,7 @@ def search_value(): @pytest.fixture def device_value_devicecapabilities(): + """Device capabilities value test setup.""" return json.dumps( { "braketSchemaHeader": { @@ -219,6 +233,7 @@ def device_value_devicecapabilities(): @pytest.fixture def device_value(device_value_devicecapabilities): + """Device value test setup.""" return { "deviceName": "Aspen-8", "deviceType": "QPU", @@ -230,6 +245,7 @@ def device_value(device_value_devicecapabilities): @pytest.fixture def devicelist_result(): + """Device list value test setup.""" return { 'name1': { 'coupling_map': {}, @@ -284,16 +300,19 @@ def devicelist_result(): @pytest.fixture def show_devices_setup(creds, search_value, device_value, devicelist_result): + """Show devices value test setup.""" return creds, search_value, device_value, devicelist_result @pytest.fixture def retrieve_setup(arntask, creds, device_value, res_completed, results_dict): + """Retrieve value test setup.""" 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): + """Retrieve device types value test setup.""" if request.param == "qpu": body_qpu = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { @@ -358,6 +377,7 @@ def retrieve_devicetypes_setup(request, arntask, creds, results_json, device_val @pytest.fixture def send_too_many_setup(creds, s3_folder, search_value, device_value): + """Send too many value test setup.""" info_too_much = { 'circuit': '{"braketSchemaHeader":' '{"name": "braket.ir.jaqcd.program", "version": "1"}, ' @@ -383,6 +403,7 @@ def real_device_online_setup( res_completed, results_json, ): + """Real device online value test setup.""" qtarntask = {'quantumTaskArn': arntask} body = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { @@ -407,4 +428,5 @@ def real_device_online_setup( @pytest.fixture def send_that_error_setup(creds, s3_folder, info, search_value, device_value): + """Send error value test setup.""" 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 index dc51283ab..22205a2f1 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_test.py @@ -14,50 +14,48 @@ # limitations under the License. """ Test for projectq.backends._awsbraket._awsbraket.py""" -import pytest - import copy import math -from projectq import MainEngine +import pytest -from projectq.types import WeakQubitRef +from projectq import MainEngine from projectq.cengines import ( - BasicMapperEngine, - DummyEngine, AutoReplacer, + BasicMapperEngine, DecompositionRuleSet, + DummyEngine, ) from projectq.cengines._replacer import NoGateDecompositionError - from projectq.ops import ( - R, - Swap, + CNOT, + NOT, + All, + Allocate, + Barrier, + C, + Command, + Deallocate, + Entangle, H, + MatrixGate, + Measure, + Ph, + R, Rx, Ry, Rz, S, Sdag, + SqrtX, + Swap, T, Tdag, X, Y, Z, - CNOT, - SqrtX, - MatrixGate, - Entangle, - Ph, - NOT, - C, - Measure, - Allocate, - Deallocate, - Barrier, - All, - Command, ) +from projectq.types import WeakQubitRef from ._awsbraket_test_fixtures import * # noqa: F401,F403 @@ -66,6 +64,7 @@ _has_boto3 = True try: import botocore + from projectq.backends._awsbraket import _awsbraket except ImportError: _has_boto3 = False @@ -85,7 +84,7 @@ def mapper(request): class TrivialMapper(BasicMapperEngine): def __init__(self): super().__init__() - self.current_mapping = dict() + self.current_mapping = {} def receive(self, command_list): for cmd in command_list: @@ -489,7 +488,7 @@ def test_awsbraket_retrieve(mocker, retrieve_setup): backend = _awsbraket.AWSBraketBackend(retrieve_execution=arntask, credentials=creds, num_retries=2, verbose=True) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res diff --git a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py index 894e4dea6..71968697d 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py +++ b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py @@ -29,8 +29,11 @@ # * functional_setup # ============================================================================== -from io import StringIO +"""Define test fixtures for the AWSBraket backend.""" + import json +from io import StringIO + import pytest try: @@ -38,8 +41,10 @@ except ImportError: class StreamingBody: + """Dummy implementation of a StreamingBody.""" + def __init__(self, raw_stream, content_length): - pass + """Initialize a dummy StreamingBody.""" # ============================================================================== @@ -47,11 +52,13 @@ def __init__(self, raw_stream, content_length): @pytest.fixture def arntask(): + """Define an ARNTask test setup.""" return 'arn:aws:braket:us-east-1:id:retrieve_execution' @pytest.fixture def creds(): + """Credentials test setup.""" return { 'AWS_ACCESS_KEY_ID': 'aws_access_key_id', 'AWS_SECRET_KEY': 'aws_secret_key', @@ -60,11 +67,13 @@ def creds(): @pytest.fixture def s3_folder(): + """S3 folder value test setup.""" return ['S3Bucket', 'S3Directory'] @pytest.fixture def device_value(): + """Device value test setup.""" device_value_devicecapabilities = json.dumps( { "braketSchemaHeader": { @@ -133,6 +142,7 @@ def device_value(): @pytest.fixture def search_value(): + """Search value test setup.""" return { "devices": [ { @@ -162,6 +172,7 @@ def search_value(): @pytest.fixture def completed_value(): + """Completed value test setup.""" return { 'deviceArn': 'arndevice', 'deviceParameters': 'parameters', @@ -179,12 +190,13 @@ def completed_value(): @pytest.fixture def sent_error_setup(creds, s3_folder, device_value, search_value): - + """Send error test setup.""" return creds, s3_folder, search_value, device_value @pytest.fixture def results_json(): + """Results test setup.""" return json.dumps( { "braketSchemaHeader": { @@ -205,7 +217,7 @@ def results_json(): @pytest.fixture def retrieve_setup(arntask, creds, device_value, completed_value, results_json): - + """Retrieve test setup.""" body = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { @@ -221,6 +233,7 @@ def retrieve_setup(arntask, creds, device_value, completed_value, results_json): @pytest.fixture def functional_setup(arntask, creds, s3_folder, search_value, device_value, completed_value, results_json): + """Functional test setup.""" qtarntask = {'quantumTaskArn': arntask} body2 = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { diff --git a/projectq/backends/_circuits/__init__.py b/projectq/backends/_circuits/__init__.py index 8985f0fe0..638ddfbbc 100755 --- a/projectq/backends/_circuits/__init__.py +++ b/projectq/backends/_circuits/__init__.py @@ -15,8 +15,7 @@ """ProjectQ module for exporting/printing quantum circuits""" -from ._to_latex import to_latex -from ._plot import to_draw - from ._drawer import CircuitDrawer from ._drawer_matplotlib import CircuitDrawerMatplotlib +from ._plot import to_draw +from ._to_latex import to_latex diff --git a/projectq/backends/_circuits/_drawer.py b/projectq/backends/_circuits/_drawer.py index bea56f40b..bb19408fa 100755 --- a/projectq/backends/_circuits/_drawer.py +++ b/projectq/backends/_circuits/_drawer.py @@ -12,20 +12,20 @@ # 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 generates TikZ Latex code describing the -circuit. -""" + +"""Contain a compiler engine which generates TikZ Latex code describing the circuit.""" + from builtins import input -from projectq.cengines import LastEngineException, BasicEngine -from projectq.ops import FlushGate, Measure, Allocate, Deallocate +from projectq.cengines import BasicEngine, LastEngineException from projectq.meta import get_control_count +from projectq.ops import Allocate, Deallocate, FlushGate, Measure + from ._to_latex import to_latex -class CircuitItem: - """Item of a quantum circuit to draw""" +class CircuitItem: # pylint: disable=too-few-public-methods + """Item of a quantum circuit to draw.""" def __init__(self, gate, lines, ctrl_lines): """ @@ -42,6 +42,7 @@ def __init__(self, gate, lines, ctrl_lines): self.id = -1 def __eq__(self, other): + """Equal operator.""" return ( self.gate == other.gate and self.lines == other.lines @@ -49,22 +50,16 @@ def __eq__(self, other): and self.id == other.id ) - def __ne__(self, other): - return not self.__eq__(other) - class CircuitDrawer(BasicEngine): """ - CircuitDrawer is a compiler engine which generates TikZ code for drawing - quantum circuits. + CircuitDrawer is a compiler engine which generates TikZ code for drawing quantum circuits. - The circuit can be modified by editing the settings.json file which is - generated upon first execution. This includes adjusting the gate width, - height, shadowing, line thickness, and many more options. + The circuit can be modified by editing the settings.json file which is generated upon first execution. This + includes adjusting the gate width, height, shadowing, line thickness, and many more options. - After initializing the CircuitDrawer, it can also be given the mapping - from qubit IDs to wire location (via the :meth:`set_qubit_locations` - function): + After initializing the CircuitDrawer, it can also be given the mapping from qubit IDs to wire location (via the + :meth:`set_qubit_locations` function): .. code-block:: python @@ -76,9 +71,8 @@ class CircuitDrawer(BasicEngine): print(circuit_backend.get_latex()) # prints LaTeX code - To see the qubit IDs in the generated circuit, simply set the `draw_id` - option in the settings.json file under "gates":"AllocateQubitGate" to - True: + To see the qubit IDs in the generated circuit, simply set the `draw_id` option in the settings.json file under + "gates":"AllocateQubitGate" to True: .. code-block:: python @@ -119,8 +113,7 @@ class CircuitDrawer(BasicEngine): } } - All gates (except for the ones requiring special treatment) support the - following properties: + All gates (except for the ones requiring special treatment) support the following properties: .. code-block:: python @@ -137,40 +130,38 @@ def __init__(self, accept_input=False, default_measure=0): """ Initialize a circuit drawing engine. - The TikZ code generator uses a settings file (settings.json), which - can be altered by the user. It contains gate widths, heights, offsets, - etc. + The TikZ code generator uses a settings file (settings.json), which can be altered by the user. It contains + gate widths, heights, offsets, etc. Args: - accept_input (bool): If accept_input is true, the printer queries - the user to input measurement results if the CircuitDrawer is - the last engine. Otherwise, all measurements yield the result - default_measure (0 or 1). - default_measure (bool): Default value to use as measurement - results if accept_input is False and there is no underlying - backend to register real measurement results. + accept_input (bool): If accept_input is true, the printer queries the user to input measurement results if + the CircuitDrawer is the last engine. Otherwise, all measurements yield the result default_measure (0 + or 1). + default_measure (bool): Default value to use as measurement results if accept_input is False and there is + no underlying backend to register real measurement results. """ - BasicEngine.__init__(self) + super().__init__() self._accept_input = accept_input self._default_measure = default_measure - self._qubit_lines = dict() + self._qubit_lines = {} self._free_lines = [] - self._map = dict() + self._map = {} # Order in which qubit lines are drawn self._drawing_order = [] def is_available(self, cmd): """ - Specialized implementation of is_available: Returns True if the - CircuitDrawer is the last engine (since it can print any command). + Test whether a Command is supported by a compiler engine. + + Specialized implementation of is_available: Returns True if the CircuitDrawer is the last engine (since it can + print any command). Args: - cmd (Command): Command for which to check availability (all - Commands can be printed). + cmd (Command): Command for 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) @@ -179,19 +170,17 @@ def is_available(self, cmd): def set_qubit_locations(self, id_to_loc): """ - Sets the qubit lines to use for the qubits explicitly. + Set the qubit lines to use for the qubits explicitly. - To figure out the qubit IDs, simply use the setting `draw_id` in the - settings file. It is located in "gates":"AllocateQubitGate". - If draw_id is True, the qubit IDs are drawn in red. + To figure out the qubit IDs, simply use the setting `draw_id` in the settings file. It is located in + "gates":"AllocateQubitGate". If draw_id is True, the qubit IDs are drawn in red. Args: - id_to_loc (dict): Dictionary mapping qubit ids to qubit line - numbers. + id_to_loc (dict): Dictionary mapping qubit ids to qubit line numbers. Raises: - RuntimeError: If the mapping has already begun (this function - needs be called before any gates have been received). + RuntimeError: If the mapping has already begun (this function 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!") @@ -208,12 +197,13 @@ def set_qubit_locations(self, id_to_loc): def _print_cmd(self, cmd): """ - Add the command cmd to the circuit diagram, taking care of potential - measurements as specified in the __init__ function. + Add a command to the list of commands to be printed. - Queries the user for measurement input if a measurement command - arrives if accept_input was set to True. Otherwise, it uses the - default_measure parameter to register the measurement outcome. + Add the command cmd to the circuit diagram, taking care of potential measurements as specified in the __init__ + function. + + Queries the user for measurement input if a measurement command arrives if accept_input was set to + True. Otherwise, it uses the default_measure parameter to register the measurement outcome. Args: cmd (Command): Command to add to the circuit diagram. @@ -270,12 +260,10 @@ def get_latex(self, ordered=False, draw_gates_in_parallel=True): where my_circuit.py calls this function and prints it to the terminal. Args: - ordered(bool): flag if the gates should be drawn in the order they - were added to the circuit - draw_gates_in_parallel(bool): flag if parallel gates should be drawn - parallel (True), or not (False) + ordered(bool): flag if the gates should be drawn in the order they were added to the circuit + draw_gates_in_parallel(bool): flag if parallel gates should be drawn parallel (True), or not (False) """ - qubit_lines = dict() + qubit_lines = {} for line in range(len(self._qubit_lines)): new_line = self._map[line] @@ -301,12 +289,13 @@ def get_latex(self, ordered=False, draw_gates_in_parallel=True): def receive(self, command_list): """ - Receive a list of commands from the previous engine, print the - commands, and then send them on to the next engine. + Receive a list of commands. + + Receive a list of commands from the previous engine, print the commands, and then send them on to the next + engine. Args: - command_list (list): List of Commands to print (and - potentially send on to the next engine). + command_list (list): List of Commands to print (and potentially send on to the next engine). """ for cmd in command_list: if not cmd.gate == FlushGate(): diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py index ee83c6023..37d42f43a 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib.py +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -12,18 +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. -""" -Contains a compiler engine which generates matplotlib figures describing the -circuit. -""" -from builtins import input -import re +"""Contain a compiler engine which generates matplotlib figures describing the circuit.""" + import itertools +import re +from builtins import input -from projectq.cengines import LastEngineException, BasicEngine -from projectq.ops import FlushGate, Measure, Allocate, Deallocate +from projectq.cengines import BasicEngine, LastEngineException from projectq.meta import get_control_count +from projectq.ops import Allocate, Deallocate, FlushGate, Measure + from ._plot import to_draw # ============================================================================== @@ -53,43 +52,37 @@ def _format_gate_str(cmd): class CircuitDrawerMatplotlib(BasicEngine): - """ - CircuitDrawerMatplotlib is a compiler engine which using Matplotlib library - for drawing quantum circuits - """ + """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) + 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 - is the last engine. Otherwise, all measurements yield the - result default_measure (0 or 1). - default_measure (bool): Default value to use as measurement - results if accept_input is False and there is no underlying - backend to register real measurement results. + accept_input (bool): If accept_input is true, the printer queries the user to input measurement results if + the CircuitDrawerMPL is the last engine. Otherwise, all measurements yield the result default_measure + (0 or 1). + default_measure (bool): Default value to use as measurement results if accept_input is False and there is + no underlying backend to register real measurement results. """ - BasicEngine.__init__(self) + super().__init__() self._accept_input = accept_input self._default_measure = default_measure - self._map = dict() + self._map = {} self._qubit_lines = {} def is_available(self, cmd): """ - Specialized implementation of is_available: Returns True if the - CircuitDrawerMatplotlib is the last engine + Test whether a Command is supported by a compiler engine. + + Specialized implementation of is_available: Returns True if the CircuitDrawerMatplotlib is the last engine (since it can print any command). Args: - cmd (Command): Command for which to check availability (all - Commands can be printed). + cmd (Command): Command for 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: # Multi-qubit gates may fail at drawing time if the target qubits @@ -100,11 +93,10 @@ def is_available(self, cmd): def _process(self, cmd): # pylint: disable=too-many-branches """ - Process the command cmd and stores it in the internal storage + Process the command cmd and stores it in the internal storage. - Queries the user for measurement input if a measurement command - arrives if accept_input was set to True. Otherwise, it uses the - default_measure parameter to register the measurement outcome. + Queries the user for measurement input if a measurement command arrives if accept_input was set to + True. Otherwise, it uses the default_measure parameter to register the measurement outcome. Args: cmd (Command): Command to add to the circuit diagram. @@ -165,12 +157,13 @@ def _process(self, cmd): # pylint: disable=too-many-branches def receive(self, command_list): """ - Receive a list of commands from the previous engine, print the - commands, and then send them on to the next engine. + Receive a list of commands. + + Receive a list of commands from the previous engine, print the commands, and then send them on to the next + engine. Args: - command_list (list): List of Commands to print (and - potentially send on to the next engine). + command_list (list): List of Commands to print (and potentially send on to the next engine). """ for cmd in command_list: if not isinstance(cmd.gate, FlushGate): @@ -181,25 +174,21 @@ def receive(self, command_list): def draw(self, qubit_labels=None, drawing_order=None, **kwargs): """ - Generates and returns the plot of the quantum circuit stored so far + Generate and returns the plot of the quantum circuit stored so far. Args: - qubit_labels (dict): label for each wire in the output figure. - Keys: qubit IDs, Values: string to print out as label for - that particular qubit wire. - drawing_order (dict): position of each qubit in the output - graphic. Keys: qubit IDs, Values: position of qubit on the - qubit line in the graphic. - **kwargs (dict): additional parameters are used to update - the default plot parameters + qubit_labels (dict): label for each wire in the output figure. Keys: qubit IDs, Values: string to print + out as label for that particular qubit wire. + drawing_order (dict): position of each qubit in the output graphic. Keys: qubit IDs, Values: position of + qubit on the qubit line in the graphic. + **kwargs (dict): additional parameters are used to update the default plot parameters Returns: A tuple containing the matplotlib figure and axes objects Note: - Additional keyword arguments can be passed to this - function in order to further customize the figure output - by matplotlib (default value in parentheses): + Additional keyword arguments can be passed to this function in order to further customize the figure + output by matplotlib (default value in parentheses): - fontsize (14): Font size in pt - column_spacing (.5): Vertical spacing between two diff --git a/projectq/backends/_circuits/_drawer_matplotlib_test.py b/projectq/backends/_circuits/_drawer_matplotlib_test.py index 9c52ad34f..885d835fa 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib_test.py +++ b/projectq/backends/_circuits/_drawer_matplotlib_test.py @@ -20,7 +20,7 @@ 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 CNOT, BasicGate, Command, H, Measure, Rx, Swap, X from projectq.types import WeakQubitRef from . import _drawer_matplotlib as _drawer @@ -108,7 +108,7 @@ def _draw_subst(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): class MyGate(BasicGate): def __init__(self, *args): - BasicGate.__init__(self) + super().__init__() self.params = args def __str__(self): diff --git a/projectq/backends/_circuits/_drawer_test.py b/projectq/backends/_circuits/_drawer_test.py index 26ef4974b..d9b94b42c 100755 --- a/projectq/backends/_circuits/_drawer_test.py +++ b/projectq/backends/_circuits/_drawer_test.py @@ -18,13 +18,12 @@ import pytest +import projectq.backends._circuits._drawer as _drawer from projectq import MainEngine -from projectq.ops import H, X, CNOT, Measure, Command +from projectq.backends._circuits._drawer import CircuitDrawer, CircuitItem +from projectq.ops import CNOT, Command, H, Measure, X from projectq.types import WeakQubitRef -import projectq.backends._circuits._drawer as _drawer -from projectq.backends._circuits._drawer import CircuitItem, CircuitDrawer - @pytest.mark.parametrize("ordered", [False, True]) def test_drawer_getlatex(ordered): diff --git a/projectq/backends/_circuits/_plot.py b/projectq/backends/_circuits/_plot.py index 0d3673fc6..7be85711d 100644 --- a/projectq/backends/_circuits/_plot.py +++ b/projectq/backends/_circuits/_plot.py @@ -13,23 +13,22 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -This module provides the basic functionality required to plot a quantum -circuit in a matplotlib figure. +This module provides the basic functionality required to plot a quantum circuit in a matplotlib figure. + It is mainly used by the CircuitDrawerMatplotlib compiler engine. -Currently, it supports all single-qubit gates, including their controlled -versions to an arbitrary number of control qubits. It also supports -multi-target qubit gates under some restrictions. Namely that the target -qubits must be neighbours in the output figure (which cannot be determined -durinng compilation at this time). +Currently, it supports all single-qubit gates, including their controlled versions to an arbitrary number of control +qubits. It also supports multi-target qubit gates under some restrictions. Namely that the target qubits must be +neighbours in the output figure (which cannot be determined durinng compilation at this time). """ from copy import deepcopy -import numpy as np + import matplotlib.pyplot as plt -from matplotlib.collections import PatchCollection, LineCollection +import numpy as np +from matplotlib.collections import LineCollection, PatchCollection from matplotlib.lines import Line2D -from matplotlib.patches import Circle, Arc, Rectangle +from matplotlib.patches import Arc, Circle, Rectangle # Important note on units for the plot parameters. # The following entries are in inches: @@ -46,46 +45,42 @@ # - x_offset # # The rest have misc. units (as defined by matplotlib) -_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, -) +_DEFAULT_PLOT_PARAMS = { + '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, +} # ============================================================================== def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): """ - Translates a given circuit to a matplotlib figure. + Translate a given circuit to a matplotlib figure. Args: qubit_lines (dict): list of gates for each qubit axis - qubit_labels (dict): label to print in front of the qubit wire for - each qubit ID + qubit_labels (dict): label to print in front of the qubit wire for each qubit ID drawing_order (dict): index of the wire for each qubit ID to be drawn. - **kwargs (dict): additional parameters are used to update the default - plot parameters + **kwargs (dict): additional parameters are used to update the default plot parameters Returns: A tuple with (figure, axes) Note: - Numbering of qubit wires starts at 0 at the bottom and increases - vertically. + Numbering of qubit wires starts at 0 at the bottom and increases vertically. Note: - Additional keyword arguments can be passed to this - function in order to further customize the figure output - by matplotlib (default value in parentheses): + Additional keyword arguments can be passed to this function in order to further customize the figure output by + matplotlib (default value in parentheses): - fontsize (14): Font size in pt - column_spacing (.5): Vertical spacing between two @@ -113,9 +108,9 @@ def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): n_qubits = len(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): + if set(drawing_order) != set(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))): + if set(drawing_order.values()) != set(range(len(drawing_order))): raise RuntimeError( 'Indices of qubit wires in drawing_order must be between 0 and {}!'.format(len(drawing_order)) ) @@ -184,7 +179,7 @@ def gate_width(axes, gate_str, plot_params): 0, gate_str, visible=True, - bbox=dict(edgecolor='k', facecolor='w', fill=True, lw=1.0), + bbox={'edgecolor': 'k', 'facecolor': 'w', 'fill': True, 'lw': 1.0}, fontsize=14, ) obj.figure.canvas.draw() @@ -231,7 +226,7 @@ def calculate_gate_grid(axes, qubit_lines, plot_params): def text(axes, gate_pos, wire_pos, textstr, plot_params): """ - Draws a text box on the figure. + Draw a text box on the figure. Args: axes (matplotlib.axes.Axes): axes object @@ -258,7 +253,7 @@ def text(axes, gate_pos, wire_pos, textstr, plot_params): def create_figure(plot_params): """ - Create a new figure as well as a new axes instance + Create a new figure as well as a new axes instance. Args: plot_params (dict): plot parameters @@ -276,8 +271,9 @@ def create_figure(plot_params): def resize_figure(fig, axes, width, height, plot_params): """ - Resizes a figure and adjust the limits of the axes instance to make sure - that the distances in data coordinates on the screen stay constant. + Resize a figure and adjust the limits of the axes instance. + + This functions makes sure that the distances in data coordinates on the screen stay constant. Args: fig (matplotlib.figure.Figure): figure object @@ -300,7 +296,7 @@ def draw_gates( # pylint: disable=too-many-arguments axes, qubit_lines, drawing_order, gate_grid, wire_grid, plot_params ): """ - Draws the gates. + Draw the gates. Args: qubit_lines (dict): list of gates for each qubit axis @@ -332,15 +328,14 @@ 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. + Draw a single gate at a given location. Args: axes (AxesSubplot): axes object gate_str (str): string representation of a gate gate_pos (float): x coordinate of the gate [data units] target_wires (list): y coordinates of the target qubits - targets_order (list): index of the wires corresponding to the target - qubit IDs + targets_order (list): index of the wires corresponding to the target qubit IDs control_wires (list): y coordinates of the control qubits plot_params (dict): plot parameters @@ -403,7 +398,7 @@ def draw_gate( def draw_generic_gate(axes, gate_pos, wire_pos, gate_str, plot_params): """ - Draws a measurement gate. + Draw a measurement gate. Args: axes (AxesSubplot): axes object @@ -438,7 +433,7 @@ def draw_generic_gate(axes, gate_pos, wire_pos, gate_str, plot_params): def draw_measure_gate(axes, gate_pos, wire_pos, plot_params): """ - Draws a measurement gate. + Draw a measurement gate. Args: axes (AxesSubplot): axes object @@ -490,7 +485,7 @@ 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. + Draw a multi-target qubit gate. Args: axes (matplotlib.axes.Axes): axes object @@ -531,7 +526,7 @@ def multi_qubit_gate( # pylint: disable=too-many-arguments def draw_x_gate(axes, gate_pos, wire_pos, plot_params): """ - Draws the symbol for a X/NOT gate. + Draw the symbol for a X/NOT gate. Args: axes (matplotlib.axes.Axes): axes object @@ -556,7 +551,7 @@ def draw_x_gate(axes, gate_pos, wire_pos, plot_params): def draw_control_z_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): """ - Draws the symbol for a controlled-Z gate. + Draw the symbol for a controlled-Z gate. Args: axes (matplotlib.axes.Axes): axes object @@ -581,7 +576,7 @@ def draw_control_z_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): def draw_swap_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): """ - Draws the symbol for a SWAP gate. + Draw the symbol for a SWAP gate. Args: axes (matplotlib.axes.Axes): axes object @@ -605,14 +600,13 @@ def draw_swap_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): def draw_wires(axes, n_labels, gate_grid, wire_grid, plot_params): """ - Draws all the circuit qubit wires. + Draw all the circuit qubit wires. Args: axes (matplotlib.axes.Axes): axes object n_labels (int): number of qubit gate_grid (ndarray): array with the ref. x positions of the gates - wire_grid (ndarray): array with the ref. y positions of the qubit - wires + wire_grid (ndarray): array with the ref. y positions of the qubit wires plot_params (dict): plot parameters """ # pylint: disable=invalid-name @@ -632,15 +626,14 @@ def draw_wires(axes, n_labels, gate_grid, wire_grid, plot_params): def draw_labels(axes, qubit_labels, drawing_order, wire_grid, plot_params): """ - Draws the labels at the start of each qubit wire + Draw the labels at the start of each qubit wire. Args: axes (matplotlib.axes.Axes): axes object qubit_labels (list): labels of the qubit to be drawn drawing_order (dict): Mapping between wire indices and qubit IDs gate_grid (ndarray): array with the ref. x positions of the gates - wire_grid (ndarray): array with the ref. y positions of the qubit - wires + wire_grid (ndarray): array with the ref. y positions of the qubit wires plot_params (dict): plot parameters """ for qubit_id in qubit_labels: diff --git a/projectq/backends/_circuits/_plot_test.py b/projectq/backends/_circuits/_plot_test.py index 85a2f8d4c..d5f3f4f64 100644 --- a/projectq/backends/_circuits/_plot_test.py +++ b/projectq/backends/_circuits/_plot_test.py @@ -20,8 +20,10 @@ Then run the tests simply with '--mpl' """ -import pytest from copy import deepcopy + +import pytest + import projectq.backends._circuits._plot as _plot # ============================================================================== diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index 4b1a568fa..ca64cf407 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -13,19 +13,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module for exporting quantum circuits to LaTeX code""" +"""ProjectQ module for exporting quantum circuits to LaTeX code.""" import json + from projectq.ops import ( Allocate, - Deallocate, DaggeredGate, - get_inverse, + Deallocate, Measure, SqrtSwap, Swap, X, Z, + get_inverse, ) @@ -50,7 +51,7 @@ def _gate_name(gate): def to_latex(circuit, drawing_order=None, draw_gates_in_parallel=True): """ - Translates a given circuit to a TikZ picture in a Latex document. + Translate 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. @@ -108,7 +109,7 @@ def get_default_settings(): Returns: settings (dict): Default circuit settings """ - settings = dict() + settings = {} settings['gate_shadow'] = True settings['lines'] = { 'style': 'very thin', @@ -149,7 +150,7 @@ def get_default_settings(): def _header(settings): """ - Writes the Latex header using the settings file. + Write the Latex header using the settings file. The header includes all packages and defines all tikz styles. @@ -660,12 +661,12 @@ def _gate_pre_offset(self, gate): def _gate_offset(self, gate): """ - Return the offset to use after placing this gate and, if no pre_offset - is defined, the same offset is used in front of the gate. + Return the offset to use after placing this gate. + + If no pre_offset is defined, the same offset is used in front of the gate. Returns: - gate_offset (float): Offset. - (settings['gates'][gate_class_name]['offset']) + gate_offset (float): Offset. (settings['gates'][gate_class_name]['offset']) """ if isinstance(gate, DaggeredGate): gate = gate._gate # pylint: disable=protected-access @@ -709,7 +710,7 @@ def _phase(self, line, pos): def _op(self, line, op=None, offset=0): """ - Returns the gate name for placing a gate on a line. + Return the gate name for placing a gate on a line. Args: line (int): Line number. @@ -725,6 +726,8 @@ def _op(self, line, op=None, offset=0): def _line(self, point1, point2, double=False, line=None): # pylint: disable=too-many-locals,unused-argument """ + Create a line that connects two points. + 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. @@ -733,8 +736,7 @@ def _line(self, point1, point2, double=False, line=None): # pylint: disable=too p1 (int): Index of the first object to connect. p2 (int): Index of the second object to connect. double (bool): Draws double lines if True. - line (int or None): Line index - if provided, p1 and p2 are gate - indices. + line (int or None): Line index - if provided, p1 and p2 are gate indices. Returns: tex_str (string): Latex code to draw this / these line(s). diff --git a/projectq/backends/_circuits/_to_latex_test.py b/projectq/backends/_circuits/_to_latex_test.py index 2d2246114..0ebdc1054 100755 --- a/projectq/backends/_circuits/_to_latex_test.py +++ b/projectq/backends/_circuits/_to_latex_test.py @@ -20,24 +20,23 @@ import pytest +import projectq.backends._circuits._drawer as _drawer +import projectq.backends._circuits._to_latex as _to_latex from projectq import MainEngine +from projectq.meta import Control from projectq.ops import ( + CNOT, BasicGate, + C, H, - X, - CNOT, Measure, - Z, - Swap, - SqrtX, SqrtSwap, - C, + SqrtX, + Swap, + X, + Z, get_inverse, ) -from projectq.meta import Control - -import projectq.backends._circuits._to_latex as _to_latex -import projectq.backends._circuits._drawer as _drawer def test_tolatex(): diff --git a/projectq/backends/_ionq/_ionq_exc.py b/projectq/backends/_exceptions.py similarity index 70% rename from projectq/backends/_ionq/_ionq_exc.py rename to projectq/backends/_exceptions.py index ad7b52e9e..8626df65c 100644 --- a/projectq/backends/_ionq/_ionq_exc.py +++ b/projectq/backends/_exceptions.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Error classes used by the IonQBackend and IonQ http client.""" +"""Exception classes for projectq.backends.""" class DeviceTooSmall(Exception): @@ -24,27 +24,21 @@ class DeviceOfflineError(Exception): """Raised when a device is required but is currently offline.""" +class DeviceNotHandledError(Exception): + """Exception raised if a selected device cannot handle the circuit or is not supported by ProjectQ.""" + + class RequestTimeoutError(Exception): - """Raised if a request to IonQ's Job creation API times out.""" + """Raised if a request to the job creation API times out.""" class JobSubmissionError(Exception): - """Raised when the IonQ Job creation API contains an error of some kind.""" + """Raised when the job creation API contains an error of some kind.""" class InvalidCommandError(Exception): - """Raised if the IonQBackend engine encounters an invalid command.""" + """Raised if the backend 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/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index a020d2a80..ad070b875 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -12,22 +12,37 @@ # 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.""" + +"""Back-end to run quantum program on IBM's Quantum Experience.""" + import math import random 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.meta import LogicalQubitIDTag, get_control_count, has_negative_control +from projectq.ops import ( + NOT, + Allocate, + Barrier, + Deallocate, + FlushGate, + H, + Measure, + Rx, + Ry, + Rz, +) from projectq.types import WeakQubitRef -from ._ibm_http_client import send, retrieve +from .._exceptions import InvalidCommandError +from ._ibm_http_client import retrieve, send 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. + Define the compiler engine class that handles interactions with the IBM API. + + The IBM Backend class, which stores the circuit, transforms it to JSON, and sends the circuit through the IBM API. """ def __init__( @@ -74,7 +89,7 @@ def __init__( self._token = token self._num_retries = num_retries self._interval = interval - self._probabilities = dict() + self._probabilities = {} self.qasm = "" self._json = [] self._measured_ids = [] @@ -103,8 +118,11 @@ 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): @@ -125,7 +143,7 @@ def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements raise RuntimeError('No mapper is present in the compiler engine list!') if self._clear: - self._probabilities = dict() + self._probabilities = {} self._clear = False self.qasm = "" self._json = [] @@ -180,7 +198,9 @@ def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements self.qasm += "\nu2(0,pi/2) q[{}];".format(qb_pos) self._json.append({'qubits': [qb_pos], 'name': 'u2', 'params': [0, 3.141592653589793]}) else: - raise Exception('Command not authorized. You should run the circuit with the appropriate ibm setup.') + raise InvalidCommandError( + 'Command not authorized. You should run the circuit with the appropriate ibm setup.' + ) def _logical_to_physical(self, qb_id): """ @@ -201,13 +221,13 @@ def _logical_to_physical(self, qb_id): def get_probabilities(self, qureg): """ - Return the list of basis states with corresponding probabilities. - If input qureg is a subset of the register used for the experiment, - then returns the projected probabilities over the other states. + Return the probability of the outcome `bit_string` when measuring the quantum register `qureg`. - 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. + 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. Warning: Only call this function after the circuit has been executed! @@ -217,18 +237,16 @@ def get_probabilities(self, qureg): qubits. Returns: - probability_dict (dict): Dictionary mapping n-bit strings to - probabilities. + 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). + 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). """ if len(self._probabilities) == 0: raise RuntimeError("Please, run the circuit first!") - probability_dict = dict() + probability_dict = {} for state in self._probabilities: mapped_state = ['0'] * len(qureg) for i, val in enumerate(qureg): @@ -314,7 +332,9 @@ def _run(self): # pylint: disable=too-many-locals def receive(self, command_list): """ - Receives a command list and, for each command, stores it until completion. Upon flush, send the data to the + Receive a list of commands. + + Receive a command list and, for each command, stores it until completion. Upon flush, send the data to the IBM QE API. Args: diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 323256de2..a30233b44 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -13,7 +13,7 @@ # 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""" +"""Back-end to run quantum program on IBM QE cloud platform.""" # helpers to run the jsonified gate sequence on ibm quantum experience server @@ -21,13 +21,15 @@ # source at: https://github.com/Qiskit/qiskit-ibmq-provider import getpass -import time import signal +import time import uuid import requests -from requests.compat import urljoin from requests import Session +from requests.compat import urljoin + +from .._exceptions import DeviceOfflineError, DeviceTooSmall _AUTH_API_URL = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' _API_URL = 'https://api.quantum-computing.ibm.com/api/' @@ -37,33 +39,31 @@ class IBMQ(Session): - """ - Manage a session between ProjectQ and the IBMQ web API. - """ + """Manage a session between ProjectQ and the IBMQ web API.""" def __init__(self, **kwargs): + """Initialize a session with the IBM QE's APIs.""" super().__init__(**kwargs) - self.backends = dict() + self.backends = {} self.timeout = 5.0 def get_list_devices(self, verbose=False): """ - Get the list of available IBM backends with their properties + Get the list of available IBM backends with their properties. Args: verbose (bool): print the returned dictionnary if True Returns: - (dict) backends dictionary by name device, containing the qubit - size 'nq', the coupling map 'coupling_map' as well as the - device version 'version' + (dict) backends dictionary by name device, containing the qubit size 'nq', the coupling map 'coupling_map' + as well as the device version 'version' """ list_device_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' argument = {'allow_redirects': True, 'timeout': (self.timeout, None)} request = super().get(urljoin(_API_URL, list_device_url), **argument) request.raise_for_status() r_json = request.json() - self.backends = dict() + self.backends = {} for obj in r_json: self.backends[obj['backend_name']] = { 'nq': obj['n_qubits'], @@ -93,8 +93,7 @@ def can_run_experiment(self, info, device): Check if the device is big enough to run the code. Args: - info (dict): dictionary sent by the backend containing the code to - run + info (dict): dictionary sent by the backend containing the code to run device (str): name of the ibm device to use Returns: @@ -109,6 +108,8 @@ def can_run_experiment(self, info, device): def authenticate(self, token=None): """ + Authenticate with IBM's Web API. + Args: token (str): IBM quantum experience user API token. """ @@ -130,21 +131,18 @@ def authenticate(self, token=None): 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 - '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. + + 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. Args: - info (dict): dictionary sent by the backend containing the code to - run + info (dict): dictionary sent by the backend containing the code to run device (str): name of the ibm device to use Returns: (tuple): (str) Execution Id - """ - # STEP1: Obtain most of the URLs for handling communication with # quantum device json_step1 = { @@ -217,10 +215,7 @@ def run(self, info, device): # pylint: disable=too-many-locals 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 - """ - + """Get the result of an execution.""" job_status_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/' + execution_id if verbose: @@ -292,18 +287,9 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover raise Exception("Timeout. The ID of your submitted job is {}.".format(execution_id)) -class DeviceTooSmall(Exception): - """Exception raised if the device is too small to run the circuit""" - - -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" - - def show_devices(token=None, verbose=False): """ - Access the list of available devices and their properties (ex: for setup - configuration) + Access the list of available devices and their properties (ex: for setup configuration). Args: token (str): IBM quantum experience user API token. @@ -319,7 +305,7 @@ def show_devices(token=None, 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. + Retrieve a previously run job by its ID. Args: device (str): Device on which the code was run / is running. @@ -346,16 +332,15 @@ def send( verbose=False, ): # pylint: disable=too-many-arguments """ - Sends QASM through the IBM API and runs the quantum circuit. + Send QASM through the IBM API and runs the quantum circuit. Args: info(dict): Contains representation of the circuit to run. device (str): name of the ibm device. Simulator chosen by default token (str): IBM quantum experience user API token. shots (int): Number of runs of the same circuit to collect statistics. - verbose (bool): If True, additional information is printed, such as - measurement statistics. Otherwise, the backend simply registers - one measurement result (same behavior as the projectq Simulator). + 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: (dict) result form the IBMQ server diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 278017c4e..69b067723 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -71,21 +71,19 @@ def raise_for_status(self): 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): 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), - ] - ) + connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } return MockResponse( [ { @@ -334,21 +332,19 @@ 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), - ] - ) + connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } return MockResponse( [ { @@ -532,21 +528,19 @@ 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), - ] - ) + connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } return MockResponse( [ { diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index c16dbe461..8d744222f 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -14,20 +14,24 @@ # limitations under the License. """Tests for projectq.backends._ibm._ibm.py.""" -import pytest import math + +import pytest + from projectq.backends._ibm import _ibm -from projectq.cengines import MainEngine, BasicMapperEngine, DummyEngine +from projectq.cengines import BasicMapperEngine, DummyEngine, MainEngine from projectq.meta import LogicalQubitIDTag from projectq.ops import ( + CNOT, + NOT, All, Allocate, Barrier, Command, Deallocate, Entangle, + H, Measure, - NOT, Rx, Ry, Rz, @@ -38,8 +42,6 @@ X, Y, Z, - H, - CNOT, ) from projectq.setups import restrictedgateset from projectq.types import WeakQubitRef @@ -121,7 +123,7 @@ def mock_send(*args, **kwargs): monkeypatch.setattr(_ibm, "send", mock_send) backend = _ibm.IBMBackend(verbose=True) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res @@ -140,7 +142,7 @@ def mock_send(*args, **kwargs): def test_ibm_sent_error_2(monkeypatch): backend = _ibm.IBMBackend(verbose=True) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res @@ -218,7 +220,7 @@ def mock_retrieve(*args, **kwargs): monkeypatch.setattr(_ibm, "retrieve", mock_retrieve) backend = _ibm.IBMBackend(retrieve_execution="ab1s2", num_runs=1000) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res @@ -339,7 +341,7 @@ def mock_send(*args, **kwargs): with pytest.raises(RuntimeError): backend.get_probabilities([]) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py index 191d3bd9b..e6480ba74 100644 --- a/projectq/backends/_ionq/_ionq.py +++ b/projectq/backends/_ionq/_ionq.py @@ -13,7 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" Back-end to run quantum programs using IonQ hardware.""" +"""Back-end to run quantum programs using IonQ hardware.""" + import random from projectq.cengines import BasicEngine @@ -45,8 +46,8 @@ ) from projectq.types import WeakQubitRef +from .._exceptions import InvalidCommandError, MidCircuitMeasurementError from . import _ionq_http_client as http_client -from ._ionq_exc import InvalidCommandError, MidCircuitMeasurementError GATE_MAP = { XGate: 'x', @@ -95,27 +96,25 @@ def __init__( interval=1, retrieve_execution=None, ): # pylint: disable=too-many-arguments - """Constructor for the IonQBackend. + """ + Initialize an IonQBackend object. 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. + 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. + 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. + 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) + super().__init__() self.device = device if use_hardware else 'ionq_simulator' self._num_runs = num_runs self._verbose = verbose @@ -124,12 +123,13 @@ def __init__( self._interval = interval self._circuit = [] self._measured_ids = [] - self._probabilities = dict() + self._probabilities = {} self._retrieve_execution = retrieve_execution self._clear = True def is_available(self, cmd): - """Test if this backend is available to process the provided command. + """ + Test if this backend is available to process the provided command. Args: cmd (Command): A command to process. @@ -160,14 +160,12 @@ def is_available(self, cmd): return False def _reset(self): - """Reset this backend. - - .. NOTE:: - - This sets ``_clear = True``, which will trigger state cleanup - on the next call to ``_store``. """ + 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 @@ -185,7 +183,7 @@ def _store(self, cmd): """ if self._clear: self._measured_ids = [] - self._probabilities = dict() + self._probabilities = {} self._clear = False # No-op/Meta gates. @@ -269,13 +267,11 @@ def get_probability(self, state, qureg): return probs[state] def get_probabilities(self, qureg): - """Given the provided qubit register, determine the probability of - each possible outcome. - - .. NOTE:: + """ + Given the provided qubit register, determine the probability of each possible outcome. - This method should only be called *after* a circuit has been - run and its results are available. + 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. diff --git a/projectq/backends/_ionq/_ionq_http_client.py b/projectq/backends/_ionq/_ionq_http_client.py index 8f45389b9..2cb181158 100644 --- a/projectq/backends/_ionq/_ionq_http_client.py +++ b/projectq/backends/_ionq/_ionq_http_client.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" HTTP Client for the IonQ API. """ +"""HTTP Client for the IonQ API.""" import getpass import json @@ -24,7 +24,7 @@ from requests import Session from requests.compat import urljoin -from ._ionq_exc import ( +from .._exceptions import ( DeviceOfflineError, DeviceTooSmall, JobSubmissionError, @@ -38,8 +38,9 @@ class IonQ(Session): """A requests.Session based HTTP client for the IonQ API.""" def __init__(self, verbose=False): + """Initialize an session with IonQ's APIs.""" super().__init__() - self.backends = dict() + self.backends = {} self.timeout = 5.0 self.token = None self._verbose = verbose @@ -61,7 +62,8 @@ def update_devices_list(self): print(self.backends) def is_online(self, device): - """Check if a given device is online. + """ + Check if a given device is online. Args: device (str): An IonQ device name. @@ -73,8 +75,7 @@ def is_online(self, device): def can_run_experiment(self, info, device): """ - Determine whether or not the desired device has enough allocatable - qubits to run something. + 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 @@ -165,7 +166,8 @@ def run(self, info, device): ) 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. + """ + Given a backend and ID, fetch the results for this job's execution. The return dictionary should have at least: @@ -190,7 +192,6 @@ def get_result(self, device, execution_id, num_retries=3000, interval=1): 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)) diff --git a/projectq/backends/_ionq/_ionq_http_client_test.py b/projectq/backends/_ionq/_ionq_http_client_test.py index c1586569d..777c5d7f0 100644 --- a/projectq/backends/_ionq/_ionq_http_client_test.py +++ b/projectq/backends/_ionq/_ionq_http_client_test.py @@ -20,8 +20,8 @@ import requests from requests.compat import urljoin +from projectq.backends._exceptions import JobSubmissionError, RequestTimeoutError 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 diff --git a/projectq/backends/_ionq/_ionq_mapper.py b/projectq/backends/_ionq/_ionq_mapper.py index 24c330f0f..3a5eb5a57 100644 --- a/projectq/backends/_ionq/_ionq_mapper.py +++ b/projectq/backends/_ionq/_ionq_mapper.py @@ -12,7 +12,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Mapper that has a max number of allocatable qubits.""" + +"""Mapper that has a maximum number of allocatable qubits.""" + from projectq.cengines import BasicMapperEngine from projectq.meta import LogicalQubitIDTag from projectq.ops import AllocateQubitGate, Command, DeallocateQubitGate, FlushGate @@ -20,9 +22,10 @@ class BoundedQubitMapper(BasicMapperEngine): - """Maps logical qubits to a fixed number of hardware qubits""" + """Map logical qubits to a fixed number of hardware qubits.""" def __init__(self, max_qubits): + """Initialize a BoundedQubitMapper object.""" super().__init__() self._qubit_idx = 0 self.max_qubits = max_qubits @@ -34,7 +37,7 @@ def _reset(self): def _process_cmd(self, cmd): current_mapping = self.current_mapping if current_mapping is None: - current_mapping = dict() + current_mapping = {} if isinstance(cmd.gate, AllocateQubitGate): qubit_id = cmd.qubits[0][0].id @@ -74,6 +77,12 @@ def _process_cmd(self, cmd): self._send_cmd_with_mapped_ids(cmd) def receive(self, command_list): + """ + Receive a list of commands. + + Args: + command_list (list): List of commands to receive. + """ for cmd in command_list: if isinstance(cmd.gate, FlushGate): self._reset() diff --git a/projectq/backends/_ionq/_ionq_mapper_test.py b/projectq/backends/_ionq/_ionq_mapper_test.py index 4f7e70605..f86fb10d9 100644 --- a/projectq/backends/_ionq/_ionq_mapper_test.py +++ b/projectq/backends/_ionq/_ionq_mapper_test.py @@ -16,7 +16,7 @@ from projectq.backends import Simulator from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper -from projectq.cengines import MainEngine, DummyEngine +from projectq.cengines import DummyEngine, MainEngine from projectq.meta import LogicalQubitIDTag from projectq.ops import AllocateQubitGate, Command, DeallocateQubitGate from projectq.types import WeakQubitRef diff --git a/projectq/backends/_ionq/_ionq_test.py b/projectq/backends/_ionq/_ionq_test.py index 4156edb63..b23450353 100644 --- a/projectq/backends/_ionq/_ionq_test.py +++ b/projectq/backends/_ionq/_ionq_test.py @@ -21,12 +21,11 @@ import pytest from projectq import MainEngine -from projectq.backends._ionq import _ionq, _ionq_http_client -from projectq.backends._ionq._ionq_exc import ( +from projectq.backends._exceptions import ( InvalidCommandError, MidCircuitMeasurementError, ) -from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper +from projectq.backends._ionq import _ionq, _ionq_http_client from projectq.cengines import DummyEngine from projectq.ops import ( CNOT, @@ -56,6 +55,8 @@ ) from projectq.types import WeakQubitRef +from ._ionq_mapper import BoundedQubitMapper + @pytest.fixture(scope='function') def mapper_factory(): diff --git a/projectq/backends/_printer.py b/projectq/backends/_printer.py index fc91ae839..9016b4753 100755 --- a/projectq/backends/_printer.py +++ b/projectq/backends/_printer.py @@ -12,22 +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. -""" -Contains a compiler engine which prints commands to stdout prior to sending them on to the next engines (see -CommandPrinter). -""" -import sys +"""Contains a compiler engine which prints commands to stdout prior to sending them on to the next engines.""" + +import sys from builtins import input from projectq.cengines import BasicEngine, LastEngineException +from projectq.meta import LogicalQubitIDTag, get_control_count from projectq.ops import FlushGate, Measure -from projectq.meta import get_control_count, LogicalQubitIDTag from projectq.types import WeakQubitRef class CommandPrinter(BasicEngine): """ + Compiler engine that prints command to the standard output. + CommandPrinter is a compiler engine which prints commands to stdout prior to sending them on to the next compiler engine. """ @@ -42,13 +42,15 @@ def __init__(self, accept_input=True, default_measure=False, in_place=False): 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) + super().__init__() self._accept_input = accept_input self._default_measure = default_measure self._in_place = in_place def is_available(self, cmd): """ + Test whether a Command is supported by a compiler engine. + Specialized implementation of is_available: Returns True if the CommandPrinter is the last engine (since it can print any command). @@ -64,6 +66,8 @@ def is_available(self, cmd): def _print_cmd(self, cmd): """ + Print a command. + 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). @@ -102,6 +106,8 @@ def _print_cmd(self, cmd): def receive(self, command_list): """ + Receive a list of commands. + Receive a list of commands from the previous engine, print the commands, and then send them on to the next engine. diff --git a/projectq/backends/_printer_test.py b/projectq/backends/_printer_test.py index 3c06b3ed1..8d81ffc1b 100755 --- a/projectq/backends/_printer_test.py +++ b/projectq/backends/_printer_test.py @@ -19,13 +19,12 @@ import pytest from projectq import MainEngine +from projectq.backends import _printer from projectq.cengines import DummyEngine, InstructionFilter, NotYetMeasuredError from projectq.meta import LogicalQubitIDTag -from projectq.ops import Allocate, Command, H, Measure, NOT, T +from projectq.ops import NOT, Allocate, Command, H, Measure, T from projectq.types import WeakQubitRef -from projectq.backends import _printer - def test_command_printer_is_available(): inline_cmd_printer = _printer.CommandPrinter() diff --git a/projectq/backends/_resource.py b/projectq/backends/_resource.py index 558b298bf..e279d579f 100755 --- a/projectq/backends/_resource.py +++ b/projectq/backends/_resource.py @@ -13,13 +13,15 @@ # 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 +Contain a compiler engine to calculate resource count used by a quantum circuit. + +A resrouce counter compiler engine 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 -from projectq.meta import get_control_count, LogicalQubitIDTag -from projectq.ops import FlushGate, Deallocate, Allocate, Measure +from projectq.meta import LogicalQubitIDTag, get_control_count +from projectq.ops import Allocate, Deallocate, FlushGate, Measure from projectq.types import WeakQubitRef @@ -43,17 +45,19 @@ def __init__(self): Sets all statistics to zero. """ - BasicEngine.__init__(self) + super().__init__() self.gate_counts = {} self.gate_class_counts = {} self._active_qubits = 0 self.max_width = 0 # key: qubit id, depth of this qubit - self._depth_of_qubit = dict() + self._depth_of_qubit = {} self._previous_max_depth = 0 def is_available(self, cmd): """ + Test whether a Command is supported by a compiler engine. + Specialized implementation of is_available: Returns True if the ResourceCounter is the last engine (since it can count any command). @@ -70,18 +74,14 @@ def is_available(self, cmd): @property def depth_of_dag(self): - """ - Return the depth of the DAG. - """ + """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) return self._previous_max_depth def _add_cmd(self, cmd): # pylint: disable=too-many-branches - """ - Add a gate to the count. - """ + """Add a gate to the count.""" if cmd.gate == Allocate: self._active_qubits += 1 self._depth_of_qubit[cmd.qubits[0][0].id] = 0 @@ -155,9 +155,9 @@ def __str__(self): return ( "Gate class counts:\n " - + "\n ".join(list(sorted(gate_class_list))) + + "\n ".join(sorted(gate_class_list)) + "\n\nGate counts:\n " - + "\n ".join(list(sorted(gate_list))) + + "\n ".join(sorted(gate_list)) + "\n\nMax. width (number of qubits) : " + str(self.max_width) + "." @@ -166,6 +166,8 @@ def __str__(self): def receive(self, command_list): """ + Receive a list of commands. + 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. diff --git a/projectq/backends/_resource_test.py b/projectq/backends/_resource_test.py index 9031d9cc9..89ebeef0f 100755 --- a/projectq/backends/_resource_test.py +++ b/projectq/backends/_resource_test.py @@ -18,13 +18,12 @@ import pytest +from projectq.backends import ResourceCounter from projectq.cengines import DummyEngine, MainEngine, NotYetMeasuredError from projectq.meta import LogicalQubitIDTag -from projectq.ops import All, Allocate, CNOT, Command, H, Measure, QFT, Rz, Rzz, X +from projectq.ops import CNOT, QFT, All, Allocate, Command, H, Measure, Rz, Rzz, X from projectq.types import WeakQubitRef -from projectq.backends import ResourceCounter - class MockEngine(object): def is_available(self, cmd): diff --git a/projectq/backends/_sim/__init__.py b/projectq/backends/_sim/__init__.py index c0d0d5d3f..1557d03a1 100755 --- a/projectq/backends/_sim/__init__.py +++ b/projectq/backends/_sim/__init__.py @@ -15,5 +15,5 @@ """ProjectQ module dedicated to simulation""" -from ._simulator import Simulator from ._classical_simulator import ClassicalSimulator +from ._simulator import Simulator diff --git a/projectq/backends/_sim/_classical_simulator.py b/projectq/backends/_sim/_classical_simulator.py index 308987ca8..eb270e219 100755 --- a/projectq/backends/_sim/_classical_simulator.py +++ b/projectq/backends/_sim/_classical_simulator.py @@ -12,13 +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. -""" -A simulator that only permits classical operations, for faster/easier testing. -""" + +"""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 Allocate, BasicMathGate, Deallocate, FlushGate, Measure, XGate from projectq.types import WeakQubitRef @@ -31,13 +30,14 @@ class ClassicalSimulator(BasicEngine): """ def __init__(self): - BasicEngine.__init__(self) + """Initialize a ClassicalSimulator object.""" + super().__init__() self._state = 0 self._bit_positions = {} def _convert_logical_to_mapped_qubit(self, qubit): """ - Converts a qubit from a logical to a mapped qubit if there is a mapper. + Convert a qubit from a logical to a mapped qubit if there is a mapper. Args: qubit (projectq.types.Qubit): Logical quantum bit @@ -51,7 +51,7 @@ def _convert_logical_to_mapped_qubit(self, qubit): def read_bit(self, qubit): """ - Reads a bit. + Read a bit. Note: If there is a mapper present in the compiler, this function automatically converts from logical qubits to @@ -67,7 +67,11 @@ 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.""" + """ + Read a mapped bit value. + + For internal use only. Does not change logical to mapped qubits. + """ return (self._state >> self._bit_positions[mapped_qubit.id]) & 1 def write_bit(self, qubit, value): @@ -86,7 +90,11 @@ 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.""" + """ + Write a mapped bit value. + + For internal use only. Does not change logical to mapped qubits. + """ pos = self._bit_positions[mapped_qubit.id] if value: self._state |= 1 << pos @@ -95,7 +103,7 @@ def _write_mapped_bit(self, mapped_qubit, value): 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. + Return 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. @@ -110,7 +118,7 @@ def _mask(self, qureg): def read_register(self, qureg): """ - Reads a group of bits as a little-endian integer. + Read 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 @@ -129,7 +137,11 @@ 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.""" + """ + Read a value to some mapped quantum register. + + For internal use only. Does not change logical to mapped qubits. + """ mask = 0 for i, qubit in enumerate(mapped_qureg): mask |= self._read_mapped_bit(qubit) << i @@ -137,7 +149,7 @@ def _read_mapped_register(self, mapped_qureg): def write_register(self, qureg, value): """ - Sets a group of bits to store a little-endian integer value. + Set 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 @@ -153,13 +165,18 @@ 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.""" + """ + Write a value to some mapped quantum register. + + For 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, mapped_qubit in enumerate(mapped_qureg): self._write_mapped_bit(mapped_qubit, (value >> i) & 1) def is_available(self, cmd): + """Test whether a Command is supported by a compiler engine.""" return ( cmd.gate == Measure or cmd.gate == Allocate @@ -168,7 +185,11 @@ def is_available(self, cmd): ) def receive(self, command_list): - """Forward all commands to the next engine.""" + """ + Receive a list of commands. + + This implementation simply forwards all commands to the next engine. + """ for cmd in command_list: self._handle(cmd) if not self.is_last_engine: diff --git a/projectq/backends/_sim/_classical_simulator_test.py b/projectq/backends/_sim/_classical_simulator_test.py index 8a35d2159..f8e9121bb 100755 --- a/projectq/backends/_sim/_classical_simulator_test.py +++ b/projectq/backends/_sim/_classical_simulator_test.py @@ -16,25 +16,17 @@ import pytest from projectq import MainEngine -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.ops import NOT, All, BasicMathGate, C, Measure, X, Y from projectq.types import WeakQubitRef from ._classical_simulator import ClassicalSimulator +from ._simulator_test import mapper # noqa: F401 def test_simulator_read_write(mapper): # noqa: F811 @@ -103,11 +95,11 @@ def test_simulator_bit_repositioning(mapper): # noqa: F811 def test_simulator_arithmetic(mapper): # noqa: F811 class Offset(BasicMathGate): def __init__(self, amount): - BasicMathGate.__init__(self, lambda x: (x + amount,)) + super().__init__(lambda x: (x + amount,)) class Sub(BasicMathGate): def __init__(self): - BasicMathGate.__init__(self, lambda x, y: (x, y - x)) + super().__init__(lambda x, y: (x, y - x)) engine_list = [] if mapper is not None: diff --git a/projectq/backends/_sim/_pysim.py b/projectq/backends/_sim/_pysim.py index 54860cafd..dc4687dbc 100755 --- a/projectq/backends/_sim/_pysim.py +++ b/projectq/backends/_sim/_pysim.py @@ -12,14 +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. + """ -Contains a (slow) Python simulator. +A (slow) Python simulator. Please compile the c++ simulator for large-scale simulations. """ -import random import os +import random + import numpy as _np _USE_REFCHECK = True @@ -46,7 +48,7 @@ def __init__(self, rnd_seed, *args, **kwargs): # pylint: disable=unused-argumen """ random.seed(rnd_seed) self._state = _np.ones(1, dtype=_np.complex128) - self._map = dict() + self._map = {} self._num_qubits = 0 print("(Note: This is the (slow) Python simulator.)") @@ -64,8 +66,7 @@ def cheat(self): def measure_qubits(self, ids): """ - Measure the qubits with IDs ids and return a list of measurement - outcomes (True/False). + Measure the qubits with IDs ids and return a list of measurement outcomes (True/False). Args: ids (list): List of qubit IDs to measure. @@ -162,7 +163,7 @@ def deallocate_qubit(self, qubit_id): newstate[k : k + (1 << pos)] = self._state[i : i + (1 << pos)] # noqa: E203 k += 1 << pos - newmap = dict() + newmap = {} for key, value in self._map.items(): if value > pos: newmap[key] = value - 1 @@ -290,7 +291,9 @@ def get_probability(self, bit_string, ids): 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 @@ -314,8 +317,9 @@ def get_amplitude(self, bit_string, ids): 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. + Apply 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. TODO: Implement better estimates for s. @@ -361,7 +365,7 @@ def emulate_time_evolution(self, terms_dict, time, ids, ctrlids): # pylint: dis 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. + Apply the k-qubit gate matrix m to the qubits with indices ids, using ctrlids as control qubits. Args: matrix (list[list]): 2^k x 2^k complex matrix describing the k-qubit gate. @@ -378,7 +382,7 @@ def apply_controlled_gate(self, matrix, ids, ctrlids): 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. + Apply the single qubit gate matrix m to the qubit at position `pos` using `mask` to identify control qubits. Args: matrix (list[list]): 2x2 complex matrix describing the single-qubit gate. @@ -398,7 +402,7 @@ def kernel(u, d, m): # pylint: disable=invalid-name 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. + Apply the k-qubit gate matrix m to the qubits at `pos` using `mask` to identify control qubits. Args: matrix (list[list]): 2^k x 2^k complex matrix describing the k-qubit gate. @@ -458,11 +462,10 @@ def collapse_wavefunction(self, ids, values): Args: ids (list[int]): Qubit IDs to collapse. - values (list[bool]): Measurement outcome for each of the qubit IDs - in `ids`. + values (list[bool]): Measurement outcome for each of the qubit IDs in `ids`. + Raises: - RuntimeError: If probability of outcome is ~0 or unknown qubits - are provided. + RuntimeError: If probability of outcome is ~0 or unknown qubits are provided. """ if len(ids) != len(values): raise ValueError('The number of ids and values do not match!') @@ -493,12 +496,15 @@ def collapse_wavefunction(self, ids, values): def run(self): """ - Dummy function to implement the same interface as the c++ simulator. + Provide a dummy implementation for running a quantum circuit. + + Only defined to provide the same interface as the c++ simulator. """ def _apply_term(self, term, ids, ctrlids=None): """ - Applies a QubitOperator term to the state vector. + Apply a QubitOperator term to the state vector. + (Helper function for time evolution & expectation) Args: diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index cfc2b56ec..f7b6f8144 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -12,17 +12,27 @@ # 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 +The ProjectQ interface to a C++-based simulator. + +The C++ simulator has to be built first. If the C++ simulator is not exported to python, a (slow) python implementation is used as an alternative. """ import math import random + from projectq.cengines import BasicEngine -from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control -from projectq.ops import Measure, FlushGate, Allocate, Deallocate, BasicMathGate, TimeEvolution +from projectq.meta import LogicalQubitIDTag, get_control_count, has_negative_control +from projectq.ops import ( + Allocate, + BasicMathGate, + Deallocate, + FlushGate, + Measure, + TimeEvolution, +) from projectq.types import WeakQubitRef FALLBACK_TO_PYSIM = False @@ -36,11 +46,9 @@ class Simulator(BasicEngine): """ - Simulator is a compiler engine which simulates a quantum computer using - C++-based kernels. + Simulator is a compiler engine which simulates a quantum computer using C++-based kernels. - OpenMP is enabled and the number of threads can be controlled using the - OMP_NUM_THREADS environment variable, i.e. + OpenMP is enabled and the number of threads can be controlled using the OMP_NUM_THREADS environment variable, i.e. .. code-block:: bash @@ -50,49 +58,41 @@ class Simulator(BasicEngine): def __init__(self, gate_fusion=False, rnd_seed=None): """ - Construct the C++/Python-simulator object and initialize it with a - random seed. + Construct the C++/Python-simulator object and initialize it with a random seed. Args: - gate_fusion (bool): If True, gates are cached and only executed - once a certain gate-size has been reached (only has an effect - for the c++ simulator). - rnd_seed (int): Random seed (uses random.randint(0, 4294967295) by - default). - - Example of gate_fusion: Instead of applying a Hadamard gate to 5 - qubits, the simulator calculates the kronecker product of the 1-qubit - gate matrices and then applies one 5-qubit gate. This increases - operational intensity and keeps the simulator from having to iterate - through the state vector multiple times. Depending on the system (and, - especially, number of threads), this may or may not be beneficial. + gate_fusion (bool): If True, gates are cached and only executed once a certain gate-size has been reached + (only has an effect for the c++ simulator). + rnd_seed (int): Random seed (uses random.randint(0, 4294967295) by default). + + Example of gate_fusion: Instead of applying a Hadamard gate to 5 qubits, the simulator calculates the + kronecker product of the 1-qubit gate matrices and then applies one 5-qubit gate. This increases operational + intensity and keeps the simulator from having to iterate through the state vector multiple times. Depending on + the system (and, especially, number of threads), this may or may not be beneficial. Note: - If the C++ Simulator extension was not built or cannot be found, - the Simulator defaults to a Python implementation of the kernels. - While this is much slower, it is still good enough to run basic - quantum algorithms. - - If you need to run large simulations, check out the tutorial in - the docs which gives futher hints on how to build the C++ - extension. + If the C++ Simulator extension was not built or cannot be found, the Simulator defaults to a Python + implementation of the kernels. While this is much slower, it is still good enough to run basic quantum + algorithms. + + If you need to run large simulations, check out the tutorial in the docs which gives futher hints on how + to build the C++ extension. """ if rnd_seed is None: rnd_seed = random.randint(0, 4294967295) - BasicEngine.__init__(self) + super().__init__() self._simulator = SimulatorBackend(rnd_seed) self._gate_fusion = gate_fusion def is_available(self, cmd): """ - Specialized implementation of is_available: The simulator can deal - with all arbitrarily-controlled gates which provide a - gate-matrix (via gate.matrix) and acts on 5 or less qubits (not - counting the control qubits). + Test whether a Command is supported by a compiler engine. + + Specialized implementation of is_available: The simulator can deal with all arbitrarily-controlled gates which + provide a gate-matrix (via gate.matrix) and acts on 5 or less qubits (not counting the control qubits). Args: - cmd (Command): Command for which to check availability (single- - qubit gate, arbitrary controls) + cmd (Command): Command for which to check availability (single- qubit gate, arbitrary controls) Returns: True if it can be simulated and False otherwise. @@ -118,7 +118,7 @@ def is_available(self, cmd): def _convert_logical_to_mapped_qureg(self, qureg): """ - Converts a qureg from logical to mapped qubits if there is a mapper. + Convert a qureg from logical to mapped qubits if there is a mapper. Args: qureg (list[Qubit],Qureg): Logical quantum bits @@ -136,6 +136,8 @@ def _convert_logical_to_mapped_qureg(self, qureg): def get_expectation_value(self, qubit_operator, qureg): """ + Return the expectation value of a qubit operator. + Get the expectation value of qubit_operator w.r.t. the current wave function represented by the supplied quantum register. @@ -147,18 +149,15 @@ def get_expectation_value(self, qubit_operator, qureg): Expectation value Note: - Make sure all previous commands (especially allocations) have - passed through the compilation chain (call main_engine.flush() to - make sure). + Make sure all previous commands (especially allocations) have passed through the compilation chain (call + main_engine.flush() to make sure). 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. Raises: - Exception: If `qubit_operator` acts on more qubits than present in - the `qureg` argument. + Exception: If `qubit_operator` acts on more qubits than present in the `qureg` argument. """ qureg = self._convert_logical_to_mapped_qureg(qureg) num_qubits = len(qureg) @@ -170,32 +169,27 @@ def get_expectation_value(self, qubit_operator, qureg): def apply_qubit_operator(self, qubit_operator, qureg): """ - Apply a (possibly non-unitary) qubit_operator to the current wave - function represented by the supplied quantum register. + Apply a (possibly non-unitary) qubit_operator to the current wave function represented by a quantum register. Args: qubit_operator (projectq.ops.QubitOperator): Operator to apply. - qureg (list[Qubit],Qureg): Quantum bits to which to apply the - operator. + qureg (list[Qubit],Qureg): Quantum bits to which to apply the operator. Raises: Exception: If `qubit_operator` acts on more qubits than present in the `qureg` argument. Warning: - This function allows applying non-unitary gates and it will not - re-normalize the wave function! It is for numerical experiments - only and should not be used for other purposes. + This function allows applying non-unitary gates and it will not re-normalize the wave function! It is for + numerical experiments only and should not be used for other purposes. Note: - Make sure all previous commands (especially allocations) have - passed through the compilation chain (call main_engine.flush() to - make sure). + Make sure all previous commands (especially allocations) have passed through the compilation chain (call + main_engine.flush() to make sure). 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. """ qureg = self._convert_logical_to_mapped_qureg(qureg) num_qubits = len(qureg) @@ -207,8 +201,7 @@ def apply_qubit_operator(self, qubit_operator, qureg): def get_probability(self, bit_string, qureg): """ - Return the probability of the outcome `bit_string` when measuring - the quantum register `qureg`. + Return the probability of the outcome `bit_string` when measuring the quantum register `qureg`. Args: bit_string (list[bool|int]|string[0|1]): Measurement outcome. @@ -218,14 +211,12 @@ def get_probability(self, bit_string, qureg): Probability of measuring the provided bit string. Note: - Make sure all previous commands (especially allocations) have - passed through the compilation chain (call main_engine.flush() to - make sure). + Make sure all previous commands (especially allocations) have passed through the compilation chain (call + main_engine.flush() to make sure). 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. """ qureg = self._convert_logical_to_mapped_qureg(qureg) bit_string = [bool(int(b)) for b in bit_string] @@ -234,26 +225,24 @@ def get_probability(self, bit_string, qureg): def get_amplitude(self, bit_string, qureg): """ Return the probability amplitude of the supplied `bit_string`. + The ordering is given by the quantum register `qureg`, which must contain all allocated qubits. Args: bit_string (list[bool|int]|string[0|1]): Computational basis state - qureg (Qureg|list[Qubit]): Quantum register determining the - ordering. Must contain all allocated qubits. + qureg (Qureg|list[Qubit]): Quantum register determining the ordering. Must contain all allocated qubits. Returns: Probability amplitude of the provided bit string. Note: - Make sure all previous commands (especially allocations) have - passed through the compilation chain (call main_engine.flush() to - make sure). + Make sure all previous commands (especially allocations) have passed through the compilation chain (call + main_engine.flush() to make sure). 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. """ qureg = self._convert_logical_to_mapped_qureg(qureg) bit_string = [bool(int(b)) for b in bit_string] @@ -335,18 +324,18 @@ def cheat(self): 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/ + Handle all commands. + + i.e., call the member functions of the C++- simulator object corresponding to measurement, allocation/ deallocation, and (controlled) single-qubit gate. Args: cmd (Command): Command to handle. Raises: - Exception: If a non-single-qubit gate needs to be processed - (which should never happen due to is_available). + Exception: If a non-single-qubit gate needs to be processed (which should never happen due to + is_available). """ - if cmd.gate == Measure: if get_control_count(cmd) != 0: raise ValueError('Cannot have control qubits with a measurement gate!') @@ -436,13 +425,13 @@ def _handle(self, cmd): # pylint: disable=too-many-branches,too-many-locals,too def receive(self, command_list): """ - Receive a list of commands from the previous engine and handle them - (simulate them classically) prior to sending them on to the next - engine. + Receive a list of commands. + + Receive a list of commands from the previous engine and handle them (simulate them classically) prior to + sending them on to the next engine. Args: - command_list (list): List of commands to execute on the - simulator. + command_list (list): List of commands to execute on the simulator. """ for cmd in command_list: if not cmd.gate == FlushGate(): diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 4e6001e35..b386ec48d 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -19,27 +19,29 @@ import copy import math +import random + import numpy import pytest -import random import scipy import scipy.sparse import scipy.sparse.linalg from projectq import MainEngine +from projectq.backends import Simulator from projectq.cengines import ( - BasicEngine, BasicMapperEngine, DummyEngine, LocalOptimizer, NotYetMeasuredError, ) +from projectq.meta import Control, Dagger, LogicalQubitIDTag from projectq.ops import ( + CNOT, All, Allocate, BasicGate, BasicMathGate, - CNOT, Command, H, MatrixGate, @@ -55,11 +57,8 @@ Y, Z, ) -from projectq.meta import Control, Dagger, LogicalQubitIDTag from projectq.types import WeakQubitRef -from projectq.backends import Simulator - def test_is_cpp_simulator_present(): import projectq.backends._sim._cppsim @@ -104,8 +103,8 @@ def mapper(request): class TrivialMapper(BasicMapperEngine): def __init__(self): - BasicEngine.__init__(self) - self.current_mapping = dict() + super().__init__() + self.current_mapping = {} def receive(self, command_list): for cmd in command_list: @@ -126,7 +125,7 @@ def receive(self, command_list): class Mock1QubitGate(MatrixGate): def __init__(self): - MatrixGate.__init__(self) + super().__init__() self.cnt = 0 @property @@ -137,7 +136,7 @@ def matrix(self): class Mock6QubitGate(MatrixGate): def __init__(self): - MatrixGate.__init__(self) + super().__init__() self.cnt = 0 @property @@ -148,7 +147,7 @@ def matrix(self): class MockNoMatrixGate(BasicGate): def __init__(self): - BasicGate.__init__(self) + super().__init__() self.cnt = 0 @@ -264,7 +263,7 @@ def test_simulator_measure_mapped_qubit(sim): class Plus2Gate(BasicMathGate): def __init__(self): - BasicMathGate.__init__(self, lambda x: (x + 2,)) + super().__init__(lambda x: (x + 2,)) def test_simulator_emulation(sim): @@ -746,8 +745,8 @@ def test_simulator_constant_math_emulation(): results = [[[1, 1, 0, 0, 0]], [[0, 1, 0, 0, 0]], [[0, 1, 1, 1, 0]]] 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.backends._sim._pysim import Simulator as PySim from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN def gate_filter(eng, cmd): @@ -781,7 +780,6 @@ def run_simulation(sim): _sim.FALLBACK_TO_PYSIM = True pysim = Simulator() pysim._simulator = PySim(1) - # run_simulation(pysim) for result in results: ref = result[0] diff --git a/projectq/backends/_sim/_simulator_test_fixtures.py b/projectq/backends/_sim/_simulator_test_fixtures.py index 28cb00f5c..8d256fbc2 100644 --- a/projectq/backends/_sim/_simulator_test_fixtures.py +++ b/projectq/backends/_sim/_simulator_test_fixtures.py @@ -14,20 +14,19 @@ # limitations under the License. import pytest -from projectq.cengines import BasicEngine, BasicMapperEngine + +from projectq.cengines import BasicMapperEngine @pytest.fixture(params=["mapper", "no_mapper"]) def mapper(request): - """ - Adds a mapper which changes qubit ids by adding 1 - """ + """Add 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() + super().__init__() + self.current_mapping = {} def receive(self, command_list): for cmd in command_list: diff --git a/projectq/backends/_unitary.py b/projectq/backends/_unitary.py index 1ebdbc2cb..625fc50c2 100644 --- a/projectq/backends/_unitary.py +++ b/projectq/backends/_unitary.py @@ -15,22 +15,18 @@ """Contain a backend that saves the unitary of a quantum circuit.""" -from copy import deepcopy import itertools import math -import warnings import random +import warnings +from copy import deepcopy + import numpy as np from projectq.cengines import BasicEngine +from projectq.meta import LogicalQubitIDTag, get_control_count, has_negative_control +from projectq.ops import AllocateQubitGate, DeallocateQubitGate, FlushGate, MeasureGate from projectq.types import WeakQubitRef -from projectq.meta import has_negative_control, get_control_count, LogicalQubitIDTag -from projectq.ops import ( - AllocateQubitGate, - DeallocateQubitGate, - MeasureGate, - FlushGate, -) def _qidmask(target_ids, control_ids, n_qubits): @@ -96,7 +92,7 @@ class UnitarySimulator(BasicEngine): def __init__(self): """Initialize a UnitarySimulator object.""" super().__init__() - self._qubit_map = dict() + self._qubit_map = {} self._unitary = [1] self._num_qubits = 0 self._is_valid = True diff --git a/projectq/backends/_unitary_test.py b/projectq/backends/_unitary_test.py index e082305e8..27c3fc850 100644 --- a/projectq/backends/_unitary_test.py +++ b/projectq/backends/_unitary_test.py @@ -18,27 +18,28 @@ """ import itertools + import numpy as np import pytest from scipy.stats import unitary_group -from projectq.cengines import MainEngine, DummyEngine, NotYetMeasuredError +from projectq.cengines import DummyEngine, MainEngine, NotYetMeasuredError +from projectq.meta import Control, LogicalQubitIDTag from projectq.ops import ( - BasicGate, - MatrixGate, + CNOT, All, - Measure, Allocate, - Deallocate, + BasicGate, Command, - X, - Y, + Deallocate, + H, + MatrixGate, + Measure, Rx, Rxx, - H, - CNOT, + X, + Y, ) -from projectq.meta import Control, LogicalQubitIDTag from projectq.types import WeakQubitRef from ._unitary import UnitarySimulator diff --git a/projectq/cengines/__init__.py b/projectq/cengines/__init__.py index 9b25fde78..e9ca6e132 100755 --- a/projectq/cengines/__init__.py +++ b/projectq/cengines/__init__.py @@ -13,23 +13,24 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module containing all compiler engines""" +"""ProjectQ module containing all compiler engines.""" + +from ._basics import BasicEngine, ForwarderEngine, LastEngineException # isort:skip +from ._cmdmodifier import CommandModifier # isort:skip +from ._basicmapper import BasicMapperEngine # isort:skip -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 ._manualmapper import ManualMapper from ._optimize import LocalOptimizer from ._replacer import ( AutoReplacer, - InstructionFilter, - DecompositionRuleSet, DecompositionRule, + DecompositionRuleSet, + InstructionFilter, ) +from ._swapandcnotflipper import SwapAndCNOTFlipper 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 876717138..e81a96c19 100644 --- a/projectq/cengines/_basicmapper.py +++ b/projectq/cengines/_basicmapper.py @@ -13,14 +13,14 @@ # 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. +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. """ from copy import deepcopy -from projectq.meta import drop_engine_after, insert_engine, LogicalQubitIDTag +from projectq.meta import LogicalQubitIDTag, drop_engine_after, insert_engine from projectq.ops import MeasureGate from ._basics import BasicEngine @@ -37,21 +37,18 @@ class BasicMapperEngine(BasicEngine): """ def __init__(self): + """Initialize a BasicMapperEngine object.""" super().__init__() self._current_mapping = None @property def current_mapping(self): - """ - Access the current mapping - """ + """Access the current mapping.""" return deepcopy(self._current_mapping) @current_mapping.setter def current_mapping(self, current_mapping): - """ - Set the current mapping - """ + """Set the current mapping.""" self._current_mapping = current_mapping def _send_cmd_with_mapped_ids(self, cmd): @@ -86,6 +83,11 @@ 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.""" + """ + Receive a list of commands. + + This implementation simply forwards all commands to the next compiler engine while adjusting the qubit IDs of + measurement gates. + """ 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 9a7089e60..42c6e3cbf 100644 --- a/projectq/cengines/_basicmapper_test.py +++ b/projectq/cengines/_basicmapper_test.py @@ -14,13 +14,11 @@ # limitations under the License. """Tests for projectq.cengines._basicmapper.py.""" -from projectq.cengines import DummyEngine +from projectq.cengines import DummyEngine, _basicmapper from projectq.meta import LogicalQubitIDTag from projectq.ops import Allocate, BasicGate, Command, Deallocate, FlushGate, Measure from projectq.types import WeakQubitRef -from projectq.cengines import _basicmapper - def test_basic_mapper_engine_send_cmd_with_mapped_ids(): mapper = _basicmapper.BasicMapperEngine() diff --git a/projectq/cengines/_basics.py b/projectq/cengines/_basics.py index 72ddcfaab..a8f393a81 100755 --- a/projectq/cengines/_basics.py +++ b/projectq/cengines/_basics.py @@ -13,22 +13,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing the basic definition of a compiler engine""" +"""Module containing the basic definition of a compiler engine.""" -from projectq.ops import Allocate, Deallocate +from projectq.ops import Allocate, Command, Deallocate from projectq.types import Qubit, Qureg, WeakQubitRef -from projectq.ops import Command 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). """ def __init__(self, engine): + """Initialize the exception.""" super().__init__( ( "\nERROR: Sending to next engine failed. {} as last engine?\nIf this is legal, please override" @@ -39,9 +39,10 @@ def __init__(self, engine): 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 - engine). + 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. @@ -63,8 +64,10 @@ 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). + Test whether a Command is supported by a compiler engine. + + 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. @@ -81,21 +84,16 @@ def is_available(self, cmd): def allocate_qubit(self, dirty=False): """ - Return a new qubit as a list containing 1 qubit object (quantum - register of size 1). - - Allocates a new qubit by getting a (new) qubit id from the MainEngine, - creating the qubit object, and then sending an AllocateQubit command - down the pipeline. If dirty=True, the fresh qubit can be replaced by - a pre-allocated one (in an unknown, dirty, initial state). Dirty qubits - must be returned to their initial states before they are deallocated / - freed. - - All allocated qubits are added to the MainEngine's set of active - qubits as weak references. This allows proper clean-up at the end of - the Python program (using atexit), deallocating all qubits which are - still alive. Qubit ids of dirty qubits are registered in MainEngine's - dirty_qubits set. + Return a new qubit as a list containing 1 qubit object (quantum register of size 1). + + Allocates a new qubit by getting a (new) qubit id from the MainEngine, creating the qubit object, and then + sending an AllocateQubit command down the pipeline. If dirty=True, the fresh qubit can be replaced by a + pre-allocated one (in an unknown, dirty, initial state). Dirty qubits must be returned to their initial states + before they are deallocated / freed. + + All allocated qubits are added to the MainEngine's set of active qubits as weak references. This allows proper + clean-up at the end of the Python program (using atexit), deallocating all qubits which are still alive. Qubit + ids of dirty qubits are registered in MainEngine's dirty_qubits set. Args: dirty (bool): If True, indicates that the allocated qubit may be @@ -108,7 +106,9 @@ def allocate_qubit(self, dirty=False): qb = Qureg([Qubit(self, new_id)]) cmd = Command(self, Allocate, (qb,)) if dirty: - from projectq.meta import DirtyQubitTag # pylint: disable=import-outside-toplevel + from projectq.meta import ( # pylint: disable=import-outside-toplevel + DirtyQubitTag, + ) if self.is_meta_tag_supported(DirtyQubitTag): cmd.tags += [DirtyQubitTag()] @@ -130,8 +130,9 @@ def allocate_qureg(self, 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. @@ -141,7 +142,9 @@ def deallocate_qubit(self, qubit): if qubit.id == -1: raise ValueError("Already deallocated.") - from projectq.meta import DirtyQubitTag # pylint: disable=import-outside-toplevel + from projectq.meta import ( # pylint: disable=import-outside-toplevel + DirtyQubitTag, + ) is_dirty = qubit.id in self.main_engine.dirty_qubits self.send( @@ -159,7 +162,7 @@ def deallocate_qubit(self, qubit): def is_meta_tag_supported(self, meta_tag): """ - Check if there is a compiler engine handling the meta tag + Check if there is a compiler engine handling the meta tag. Args: engine: First engine to check (then iteratively calls getNextEngine) @@ -180,10 +183,7 @@ def is_meta_tag_supported(self, meta_tag): return False def send(self, command_list): - """ - Forward the list of commands to the next engine in the pipeline. - """ - + """Forward the list of commands to the next engine in the pipeline.""" self.next_engine.receive(command_list) diff --git a/projectq/cengines/_basics_test.py b/projectq/cengines/_basics_test.py index e76b94b7f..cb4b2e0b2 100755 --- a/projectq/cengines/_basics_test.py +++ b/projectq/cengines/_basics_test.py @@ -15,26 +15,25 @@ """Tests for projectq.cengines._basics.py.""" import types -import pytest -# try: -# import mock -# except ImportError: -# from unittest import mock +import pytest from projectq import MainEngine -from projectq.types import Qubit -from projectq.cengines import DummyEngine, InstructionFilter +from projectq.cengines import DummyEngine, InstructionFilter, _basics from projectq.meta import DirtyQubitTag from projectq.ops import ( AllocateQubitGate, + ClassicalInstructionGate, DeallocateQubitGate, - H, FastForwardingGate, - ClassicalInstructionGate, + H, ) +from projectq.types import Qubit -from projectq.cengines import _basics +# try: +# import mock +# except ImportError: +# from unittest import mock def test_basic_engine_init(): @@ -112,15 +111,13 @@ def allow_dirty_qubits(self, meta_tag): # 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, - ] - ) + { + qubit[0].id, + not_dirty_qubit[0].id, + dirty_qubit[0].id, + qureg[0].id, + qureg[1].id, + } ) == 5 ) diff --git a/projectq/cengines/_cmdmodifier.py b/projectq/cengines/_cmdmodifier.py index c988cccd6..f5deae94a 100755 --- a/projectq/cengines/_cmdmodifier.py +++ b/projectq/cengines/_cmdmodifier.py @@ -13,7 +13,9 @@ # 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 +A CommandModifier engine that can be used to apply a user-defined transformation to all incoming commands. + +A CommandModifier engine can be used to, e.g., modify the tags of all commands which pass by (see the AutoReplacer for an example). """ @@ -22,6 +24,8 @@ class CommandModifier(BasicEngine): """ + Compiler engine applying a user-defined transformation to all incoming commands. + CommandModifier is a compiler engine which applies a function to all incoming commands, sending on the resulting command instead of the original one. """ @@ -46,6 +50,8 @@ def cmd_mod_fun(cmd): def receive(self, command_list): """ + Receive a list of commands. + Receive a list of commands from the previous engine, modify all commands, and send them on to the next engine. Args: diff --git a/projectq/cengines/_cmdmodifier_test.py b/projectq/cengines/_cmdmodifier_test.py index 79f8d858e..5b7ed9fb6 100755 --- a/projectq/cengines/_cmdmodifier_test.py +++ b/projectq/cengines/_cmdmodifier_test.py @@ -15,10 +15,8 @@ """Tests for projectq.cengines._cmdmodifier.py.""" from projectq import MainEngine -from projectq.cengines import DummyEngine -from projectq.ops import H, FastForwardingGate, ClassicalInstructionGate - -from projectq.cengines import _cmdmodifier +from projectq.cengines import DummyEngine, _cmdmodifier +from projectq.ops import ClassicalInstructionGate, FastForwardingGate, H def test_command_modifier(): diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index 4f9d093e5..6e44f5117 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -12,15 +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. -""" -Contains a compiler engine to map to the 5-qubit IBM chip -""" + +"""Contains a compiler engine to map to the 5-qubit IBM chip.""" + import itertools -from projectq.ops import FlushGate, NOT, Allocate -from projectq.meta import get_control_count from projectq.backends import IBMBackend - +from projectq.meta import get_control_count +from projectq.ops import NOT, Allocate, FlushGate from ._basicmapper import BasicMapperEngine @@ -35,9 +34,8 @@ class IBM5QubitMapper(BasicMapperEngine): The mapper has to be run once on the entire circuit. Warning: - If the provided circuit cannot be mapped to the hardware layout - without performing Swaps, the mapping procedure - **raises an Exception**. + If the provided circuit cannot be mapped to the hardware layout without performing Swaps, the mapping + procedure **raises an Exception**. """ def __init__(self, connections=None): @@ -47,35 +45,32 @@ def __init__(self, connections=None): Resets the mapping. """ super().__init__() - self.current_mapping = dict() + self.current_mapping = {} self._reset() self._cmds = [] - self._interactions = dict() + self._interactions = {} if connections is None: # general connectivity easier for testing functions - self.connections = set( - [ - (0, 1), - (1, 0), - (1, 2), - (1, 3), - (1, 4), - (2, 1), - (2, 3), - (2, 4), - (3, 1), - (3, 4), - (4, 3), - ] - ) + self.connections = { + (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 def is_available(self, cmd): """ - Check if the IBM backend can perform the Command cmd and return True - if so. + Check if the IBM backend can perform the Command cmd and return True if so. Args: cmd (Command): The command to check @@ -83,25 +78,21 @@ def is_available(self, cmd): return IBMBackend().is_available(cmd) def _reset(self): - """ - Reset the mapping parameters so the next circuit can be mapped. - """ + """Reset the mapping parameters so the next circuit can be mapped.""" self._cmds = [] - self._interactions = dict() + self._interactions = {} def _determine_cost(self, mapping): """ - Determines the cost of the circuit with the given mapping. + Determine the cost of the circuit with the given mapping. Args: - mapping (dict): Dictionary with key, value pairs where keys are - logical qubit ids and the corresponding value is the physical - location on the IBM Q chip. + mapping (dict): Dictionary with key, value pairs where keys are logical qubit ids and the corresponding + value is the physical location on the IBM Q chip. Returns: - Cost measure taking into account CNOT directionality or None - if the circuit cannot be executed given the mapping. + Cost measure taking into account CNOT directionality or None if the circuit cannot be executed given the + mapping. """ - cost = 0 for tpl in self._interactions: ctrl_id = tpl[0] @@ -117,13 +108,12 @@ def _determine_cost(self, mapping): def _run(self): """ - Runs all stored gates. + Run all stored gates. Raises: Exception: - If the mapping to the IBM backend cannot be performed or if - the mapping was already determined but more CNOTs get sent - down the pipeline. + If the mapping to the IBM backend cannot be performed or if 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( @@ -145,7 +135,7 @@ def _run(self): best_mapping = mapping if best_cost is None: raise RuntimeError("Circuit cannot be mapped without using Swaps. Mapping failed.") - self._interactions = dict() + self._interactions = {} self.current_mapping = best_mapping for cmd in self._cmds: @@ -178,18 +168,18 @@ def _store(self, cmd): def receive(self, command_list): """ - Receives a command list and, for each command, stores it until - completion. + Receive a list of commands. + + Receive a command list and, for each command, stores it until completion. Args: command_list (list of Command objects): list of commands to receive. Raises: - Exception: If mapping the CNOT gates to 1 qubit would require - Swaps. The current version only supports remapping of CNOT - gates without performing any Swaps due to the large costs - associated with Swapping given the CNOT constraints. + Exception: If mapping the CNOT gates to 1 qubit would require Swaps. The current version only supports + remapping of CNOT gates without performing any Swaps due to the large costs associated with Swapping + given the CNOT constraints. """ for cmd in command_list: self._store(cmd) diff --git a/projectq/cengines/_ibm5qubitmapper_test.py b/projectq/cengines/_ibm5qubitmapper_test.py index 29ed59092..d56e9e7ed 100755 --- a/projectq/cengines/_ibm5qubitmapper_test.py +++ b/projectq/cengines/_ibm5qubitmapper_test.py @@ -17,11 +17,9 @@ import pytest from projectq import MainEngine -from projectq.cengines import DummyEngine -from projectq.ops import H, CNOT, All - -from projectq.cengines import _ibm5qubitmapper, SwapAndCNOTFlipper from projectq.backends import IBMBackend +from projectq.cengines import DummyEngine, SwapAndCNOTFlipper, _ibm5qubitmapper +from projectq.ops import CNOT, All, H def test_ibm5qubitmapper_is_available(monkeypatch): @@ -35,7 +33,7 @@ def mock_send(*args, **kwargs): def test_ibm5qubitmapper_invalid_circuit(): - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} backend = DummyEngine(save_commands=True) eng = MainEngine( backend=backend, @@ -55,7 +53,7 @@ def test_ibm5qubitmapper_invalid_circuit(): def test_ibm5qubitmapper_valid_circuit1(): - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} backend = DummyEngine(save_commands=True) eng = MainEngine( backend=backend, @@ -77,7 +75,7 @@ def test_ibm5qubitmapper_valid_circuit1(): def test_ibm5qubitmapper_valid_circuit2(): - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} backend = DummyEngine(save_commands=True) eng = MainEngine( backend=backend, @@ -99,7 +97,7 @@ def test_ibm5qubitmapper_valid_circuit2(): def test_ibm5qubitmapper_valid_circuit2_ibmqx4(): - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} backend = DummyEngine(save_commands=True) class FakeIBMBackend(IBMBackend): @@ -131,7 +129,7 @@ class FakeIBMBackend(IBMBackend): def test_ibm5qubitmapper_optimizeifpossible(): backend = DummyEngine(save_commands=True) - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} eng = MainEngine( backend=backend, engine_list=[ @@ -173,7 +171,7 @@ 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)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} eng = MainEngine( backend=backend, engine_list=[ diff --git a/projectq/cengines/_linearmapper.py b/projectq/cengines/_linearmapper.py index 911b5e975..8546e3774 100644 --- a/projectq/cengines/_linearmapper.py +++ b/projectq/cengines/_linearmapper.py @@ -27,9 +27,9 @@ from projectq.ops import ( Allocate, AllocateQubitGate, + Command, Deallocate, DeallocateQubitGate, - Command, FlushGate, Swap, ) @@ -40,7 +40,7 @@ def return_swap_depth(swaps): """ - Returns the circuit depth to execute these swaps. + Return 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 @@ -48,7 +48,7 @@ def return_swap_depth(swaps): Returns: Circuit depth to execute these swaps. """ - depth_of_qubits = dict() + depth_of_qubits = {} for qb0_id, qb1_id in swaps: if qb0_id not in depth_of_qubits: depth_of_qubits[qb0_id] = 0 @@ -62,7 +62,7 @@ def return_swap_depth(swaps): class LinearMapper(BasicMapperEngine): # pylint: disable=too-many-instance-attributes """ - Maps a quantum circuit to a linear chain of nearest neighbour interactions. + Map 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. @@ -99,20 +99,18 @@ def __init__(self, num_qubits, cyclic=False, storage=1000): self.cyclic = cyclic self.storage = storage # Storing commands - self._stored_commands = list() + self._stored_commands = [] # Logical qubit ids for which the Allocate gate has already been # processed and sent to the next engine but which are not yet # deallocated: self._currently_allocated_ids = set() # Statistics: self.num_mappings = 0 - self.depth_of_swaps = dict() - self.num_of_swaps_per_mapping = dict() + self.depth_of_swaps = {} + self.num_of_swaps_per_mapping = {} def is_available(self, cmd): - """ - Only allows 1 or two qubit gates. - """ + """Only allows 1 or two qubit gates.""" num_qubits = 0 for qureg in cmd.all_qubits: num_qubits += len(qureg) @@ -121,7 +119,7 @@ def is_available(self, cmd): @staticmethod def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_commands, current_mapping): """ - Builds a mapping of qubits to a linear chain. + Build 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. @@ -150,7 +148,7 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_comma segments = [] # neighbour_ids only used to speedup the lookup process if qubits # are already connected. key: qubit_id, value: set of neighbour ids - neighbour_ids = dict() + neighbour_ids = {} for qubit_id in active_qubits: neighbour_ids[qubit_id] = set() @@ -208,7 +206,7 @@ def _process_two_qubit_gate( # pylint: disable=too-many-arguments,too-many-bran num_qubits, cyclic, qubit0, qubit1, active_qubits, segments, neighbour_ids ): """ - Processes a two qubit gate. + Process 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. @@ -316,7 +314,7 @@ def _return_new_mapping_from_segments( # pylint: disable=too-many-locals,too-ma num_qubits, segments, allocated_qubits, current_mapping ): """ - Combines the individual segments into a new mapping. + Combine 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 @@ -393,7 +391,7 @@ def _return_new_mapping_from_segments( # pylint: disable=too-many-locals,too-ma current_position_to_fill += best_padding + len(best_segment) num_unused_qubits -= best_padding # Create mapping - new_mapping = dict() + new_mapping = {} for pos, logical_id in enumerate(new_chain): if logical_id is not None: new_mapping[logical_id] = pos @@ -401,7 +399,7 @@ def _return_new_mapping_from_segments( # pylint: disable=too-many-locals,too-ma def _odd_even_transposition_sort_swaps(self, old_mapping, new_mapping): """ - Returns the swap operation for an odd-even transposition sort. + Return the swap operation for an odd-even transposition sort. See https://en.wikipedia.org/wiki/Odd-even_sort for more info. @@ -451,7 +449,7 @@ def _odd_even_transposition_sort_swaps(self, old_mapping, new_mapping): def _send_possible_commands(self): # pylint: disable=too-many-branches """ - Sends the stored commands possible without changing the mapping. + Send the stored commands possible without changing the mapping. Note: self.current_mapping must exist already """ @@ -523,7 +521,7 @@ def _send_possible_commands(self): # pylint: disable=too-many-branches def _run(self): # pylint: disable=too-many-locals,too-many-branches """ - Creates a new mapping and executes possible gates. + Create 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 @@ -531,7 +529,7 @@ def _run(self): # pylint: disable=too-many-locals,too-many-branches """ num_of_stored_commands_before = len(self._stored_commands) if not self.current_mapping: - self.current_mapping = dict() + self.current_mapping = {} else: self._send_possible_commands() if len(self._stored_commands) == 0: @@ -595,7 +593,9 @@ def _run(self): # pylint: disable=too-many-locals,too-many-branches 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 + Receive a list of commands. + + Receive a command list and, for each command, stores it until we do a mapping (FlushGate or Cache of stored commands is full). Args: diff --git a/projectq/cengines/_linearmapper_test.py b/projectq/cengines/_linearmapper_test.py index efe6b68d2..0b414fd12 100644 --- a/projectq/cengines/_linearmapper_test.py +++ b/projectq/cengines/_linearmapper_test.py @@ -18,21 +18,20 @@ import pytest from projectq.cengines import DummyEngine +from projectq.cengines import _linearmapper as lm from projectq.meta import LogicalQubitIDTag from projectq.ops import ( + CNOT, + QFT, Allocate, BasicGate, - CNOT, Command, Deallocate, FlushGate, - QFT, X, ) from projectq.types import WeakQubitRef -from projectq.cengines import _linearmapper as lm - def test_return_swap_depth(): swaps = [] @@ -89,7 +88,7 @@ def test_return_new_mapping_allocate_qubits(): mapper = lm.LinearMapper(num_qubits=2, cyclic=False) qb0 = WeakQubitRef(engine=None, idx=0) qb1 = WeakQubitRef(engine=None, idx=1) - mapper._currently_allocated_ids = set([4]) + mapper._currently_allocated_ids = {4} cmd0 = Command(None, Allocate, ([qb0],)) cmd1 = Command(None, Allocate, ([qb1],)) mapper._stored_commands = [cmd0, cmd1] @@ -100,7 +99,7 @@ def test_return_new_mapping_allocate_qubits(): stored_commands=mapper._stored_commands, current_mapping=mapper.current_mapping, ) - assert mapper._currently_allocated_ids == set([4]) + assert mapper._currently_allocated_ids == {4} assert mapper._stored_commands == [cmd0, cmd1] assert len(new_mapping) == 2 assert 4 in new_mapping and 0 in new_mapping @@ -172,8 +171,8 @@ def test_return_new_mapping_previous_error(): def test_process_two_qubit_gate_not_in_segments_test0(): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) segments = [[0, 1]] - active_qubits = set([0, 1, 4, 6]) - neighbour_ids = {0: set([1]), 1: set([0]), 4: set(), 6: set()} + active_qubits = {0, 1, 4, 6} + neighbour_ids = {0: {1}, 1: {0}, 4: set(), 6: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -186,15 +185,15 @@ def test_process_two_qubit_gate_not_in_segments_test0(): assert len(segments) == 2 assert segments[0] == [0, 1] assert segments[1] == [4, 6] - assert neighbour_ids[4] == set([6]) - assert neighbour_ids[6] == set([4]) - assert active_qubits == set([0, 1, 4, 6]) + assert neighbour_ids[4] == {6} + assert neighbour_ids[6] == {4} + assert active_qubits == {0, 1, 4, 6} def test_process_two_qubit_gate_not_in_segments_test1(): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) segments = [] - active_qubits = set([4, 6]) + active_qubits = {4, 6} neighbour_ids = {4: set(), 6: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, @@ -206,7 +205,7 @@ def test_process_two_qubit_gate_not_in_segments_test1(): neighbour_ids=neighbour_ids, ) assert len(segments) == 0 - assert active_qubits == set([4]) + assert active_qubits == {4} @pytest.mark.parametrize("qb0, qb1", [(1, 2), (2, 1)]) @@ -214,8 +213,8 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment(qb0, qb1): # add on the right to segment mapper = lm.LinearMapper(num_qubits=3, cyclic=False) segments = [[0, 1]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([1]), 1: set([0]), 2: set()} + active_qubits = {0, 1, 2} + neighbour_ids = {0: {1}, 1: {0}, 2: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -226,9 +225,9 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment(qb0, qb1): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([0, 1, 2]) - assert neighbour_ids[1] == set([0, 2]) - assert neighbour_ids[2] == set([1]) + assert active_qubits == {0, 1, 2} + assert neighbour_ids[1] == {0, 2} + assert neighbour_ids[2] == {1} @pytest.mark.parametrize("qb0, qb1", [(0, 1), (1, 0)]) @@ -236,8 +235,8 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment2(qb0, qb1): # add on the left to segment mapper = lm.LinearMapper(num_qubits=3, cyclic=False) segments = [[1, 2]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([]), 1: set([2]), 2: set([1])} + active_qubits = {0, 1, 2} + neighbour_ids = {0: set(), 1: {2}, 2: {1}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -248,17 +247,17 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment2(qb0, qb1): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([0, 1, 2]) - assert neighbour_ids[1] == set([0, 2]) - assert neighbour_ids[0] == set([1]) + assert active_qubits == {0, 1, 2} + assert neighbour_ids[1] == {0, 2} + assert neighbour_ids[0] == {1} @pytest.mark.parametrize("qb0, qb1", [(1, 2), (2, 1)]) def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment_cycle(qb0, qb1): mapper = lm.LinearMapper(num_qubits=3, cyclic=True) segments = [[0, 1]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([1]), 1: set([0]), 2: set()} + active_qubits = {0, 1, 2} + neighbour_ids = {0: {1}, 1: {0}, 2: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -269,9 +268,9 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment_cycle(qb0, qb1): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([0, 1, 2]) - assert neighbour_ids[1] == set([0, 2]) - assert neighbour_ids[2] == set([1, 0]) + assert active_qubits == {0, 1, 2} + assert neighbour_ids[1] == {0, 2} + assert neighbour_ids[2] == {1, 0} @pytest.mark.parametrize("qb0, qb1", [(1, 2), (2, 1)]) @@ -279,8 +278,8 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_seg_cycle2(qb0, qb1): # not yet long enough segment for cycle mapper = lm.LinearMapper(num_qubits=4, cyclic=True) segments = [[0, 1]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([1]), 1: set([0]), 2: set()} + active_qubits = {0, 1, 2} + neighbour_ids = {0: {1}, 1: {0}, 2: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -291,16 +290,16 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_seg_cycle2(qb0, qb1): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([0, 1, 2]) - assert neighbour_ids[1] == set([0, 2]) - assert neighbour_ids[2] == set([1]) + assert active_qubits == {0, 1, 2} + assert neighbour_ids[1] == {0, 2} + assert neighbour_ids[2] == {1} def test_process_two_qubit_gate_one_qubit_in_middle_of_segment(): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) segments = [] - active_qubits = set([0, 1, 2, 3]) - neighbour_ids = {0: set([1]), 1: set([0, 2]), 2: set([1]), 3: set()} + active_qubits = {0, 1, 2, 3} + neighbour_ids = {0: {1}, 1: {0, 2}, 2: {1}, 3: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -311,14 +310,14 @@ def test_process_two_qubit_gate_one_qubit_in_middle_of_segment(): neighbour_ids=neighbour_ids, ) assert len(segments) == 0 - assert active_qubits == set([0, 2]) + assert active_qubits == {0, 2} def test_process_two_qubit_gate_both_in_same_segment(): mapper = lm.LinearMapper(num_qubits=3, cyclic=False) segments = [[0, 1, 2]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([1]), 1: set([0, 2]), 2: set([1])} + active_qubits = {0, 1, 2} + neighbour_ids = {0: {1}, 1: {0, 2}, 2: {1}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -329,14 +328,14 @@ def test_process_two_qubit_gate_both_in_same_segment(): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([1]) + assert active_qubits == {1} def test_process_two_qubit_gate_already_connected(): mapper = lm.LinearMapper(num_qubits=3, cyclic=False) segments = [[0, 1, 2]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([1]), 1: set([0, 2]), 2: set([1])} + active_qubits = {0, 1, 2} + neighbour_ids = {0: {1}, 1: {0, 2}, 2: {1}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -347,7 +346,7 @@ def test_process_two_qubit_gate_already_connected(): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([0, 1, 2]) + assert active_qubits == {0, 1, 2} @pytest.mark.parametrize( @@ -362,8 +361,8 @@ def test_process_two_qubit_gate_already_connected(): 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])} + active_qubits = {0, 1, 2, 3, 4} + neighbour_ids = {0: {1}, 1: {0}, 2: {3}, 3: {2}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -390,8 +389,8 @@ def test_process_two_qubit_gate_combine_segments(qb0, qb1, result_seg): 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])} + active_qubits = {0, 1, 2, 3, 4} + neighbour_ids = {0: {1}, 1: {0}, 2: {3}, 3: {2}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -421,8 +420,8 @@ 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])} + active_qubits = {0, 1, 2, 3, 4} + neighbour_ids = {0: {1}, 1: {0}, 2: {3}, 3: {2}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -450,7 +449,7 @@ def test_process_two_qubit_gate_combine_segments_cycle2(qb0, qb1, result_seg): ) 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() + current_mapping = {} for pos, logical_id in enumerate(current_chain): current_mapping[logical_id] = pos mapper.current_mapping = current_mapping @@ -460,7 +459,7 @@ def test_return_new_mapping_from_segments(segments, current_chain, correct_chain allocated_qubits=allocated_qubits, current_mapping=mapper.current_mapping, ) - correct_mapping = dict() + correct_mapping = {} for pos, logical_id in enumerate(correct_chain): if logical_id is not None: correct_mapping[logical_id] = pos @@ -478,8 +477,8 @@ def test_return_new_mapping_from_segments(segments, current_chain, correct_chain ) def test_odd_even_transposition_sort_swaps(old_chain, new_chain): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) - old_map = dict() - new_map = dict() + old_map = {} + new_map = {} for pos, logical_id in enumerate(old_chain): if logical_id is not None: old_map[logical_id] = pos @@ -510,9 +509,9 @@ def test_send_possible_commands_allocate(): qb0 = WeakQubitRef(engine=None, idx=0) cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] - mapper._currently_allocated_ids = set([10]) + mapper._currently_allocated_ids = {10} # not in mapping: - mapper.current_mapping = dict() + mapper.current_mapping = {} assert len(backend.received_commands) == 0 mapper._send_possible_commands() assert len(backend.received_commands) == 0 @@ -525,7 +524,7 @@ def test_send_possible_commands_allocate(): assert backend.received_commands[0].gate == Allocate assert backend.received_commands[0].qubits[0][0].id == 3 assert backend.received_commands[0].tags == [LogicalQubitIDTag(0)] - assert mapper._currently_allocated_ids == set([10, 0]) + assert mapper._currently_allocated_ids == {10, 0} def test_send_possible_commands_deallocate(): @@ -536,8 +535,8 @@ def test_send_possible_commands_deallocate(): qb0 = WeakQubitRef(engine=None, idx=0) cmd0 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] - mapper.current_mapping = dict() - mapper._currently_allocated_ids = set([10]) + mapper.current_mapping = {} + mapper._currently_allocated_ids = {10} # not yet allocated: mapper._send_possible_commands() assert len(backend.received_commands) == 0 @@ -551,8 +550,8 @@ def test_send_possible_commands_deallocate(): assert backend.received_commands[0].qubits[0][0].id == 3 assert backend.received_commands[0].tags == [LogicalQubitIDTag(0)] assert len(mapper._stored_commands) == 0 - assert mapper.current_mapping == dict() - assert mapper._currently_allocated_ids == set([10]) + assert mapper.current_mapping == {} + assert mapper._currently_allocated_ids == {10} def test_send_possible_commands_keep_remaining_gates(): @@ -581,7 +580,7 @@ def test_send_possible_commands_not_cyclic(): qb1 = WeakQubitRef(engine=None, idx=1) qb2 = WeakQubitRef(engine=None, idx=2) qb3 = WeakQubitRef(engine=None, idx=3) - mapper._currently_allocated_ids = set([0, 1, 2, 3]) + mapper._currently_allocated_ids = {0, 1, 2, 3} cmd0 = Command(None, CNOT, qubits=([qb0],), controls=[qb2]) cmd1 = Command(None, CNOT, qubits=([qb1],), controls=[qb2]) cmd2 = Command(None, CNOT, qubits=([qb1],), controls=[qb3]) @@ -609,7 +608,7 @@ def test_send_possible_commands_cyclic(): qb1 = WeakQubitRef(engine=None, idx=1) qb2 = WeakQubitRef(engine=None, idx=2) qb3 = WeakQubitRef(engine=None, idx=3) - mapper._currently_allocated_ids = set([0, 1, 2, 3]) + mapper._currently_allocated_ids = {0, 1, 2, 3} cmd0 = Command(None, CNOT, qubits=([qb0],), controls=[qb1]) cmd1 = Command(None, CNOT, qubits=([qb1],), controls=[qb2]) cmd2 = Command(None, CNOT, qubits=([qb1],), controls=[qb3]) @@ -649,7 +648,7 @@ def test_run_and_receive(): mapper.receive([cmd_flush]) assert mapper._stored_commands == [] assert len(backend.received_commands) == 7 - assert mapper._currently_allocated_ids == set([0, 2]) + assert mapper._currently_allocated_ids == {0, 2} assert mapper.current_mapping == {0: 2, 2: 0} or mapper.current_mapping == { 0: 0, 2: 2, @@ -657,7 +656,7 @@ def test_run_and_receive(): cmd6 = Command(None, X, qubits=([qb0],), controls=[qb2]) mapper.storage = 1 mapper.receive([cmd6]) - assert mapper._currently_allocated_ids == set([0, 2]) + assert mapper._currently_allocated_ids == {0, 2} assert mapper._stored_commands == [] assert len(mapper.current_mapping) == 2 assert 0 in mapper.current_mapping diff --git a/projectq/cengines/_main.py b/projectq/cengines/_main.py index ec6145ef5..6f13e25e0 100755 --- a/projectq/cengines/_main.py +++ b/projectq/cengines/_main.py @@ -12,21 +12,20 @@ # 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. -""" + +"""The main engine of every compiler engine pipeline, called MainEngine.""" import atexit import sys import traceback import weakref +from projectq.backends import Simulator from projectq.ops import Command, FlushGate from projectq.types import WeakQubitRef -from projectq.backends import Simulator -from ._basics import BasicEngine from ._basicmapper import BasicMapperEngine +from ._basics import BasicEngine class NotYetMeasuredError(Exception): @@ -34,17 +33,19 @@ class NotYetMeasuredError(Exception): class UnsupportedEngineError(Exception): - """Exception raised when a non-supported compiler engine is encountered""" + """Exception raised when a non-supported compiler engine is encountered.""" class _ErrorEngine: # pylint: disable=too-few-public-methods """ + Fake compiler engine class. + 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""" + """No-op.""" _N_ENGINES_THRESHOLD = 100 @@ -120,7 +121,7 @@ def __init__( # pylint: disable=too-many-statements,too-many-branches """ super().__init__() self.active_qubits = weakref.WeakSet() - self._measurements = dict() + self._measurements = {} self.dirty_qubits = set() self.verbose = verbose self.main_engine = self @@ -171,7 +172,7 @@ def __init__( # pylint: disable=too-many-statements,too-many-branches engine_list = engine_list + [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({id(item) for item in engine_list}) if len(engine_list) != num_different_engines: self.next_engine = _ErrorEngine() raise UnsupportedEngineError( @@ -224,7 +225,7 @@ def __del__(self): def set_measurement_result(self, qubit, value): """ - Register a measurement result + 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. @@ -237,8 +238,9 @@ def set_measurement_result(self, qubit, 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 also setMeasurementResult. Args: qubit (BasicQubit): Qubit of which to get the measurement result. @@ -266,7 +268,7 @@ def get_measurement_result(self, qubit): def get_new_qubit_id(self): """ - Returns a unique qubit id to be used for the next qubit allocation. + Return a unique qubit id to be used for the next qubit allocation. Returns: new_qubit_id (int): New unique qubit id. diff --git a/projectq/cengines/_main_test.py b/projectq/cengines/_main_test.py index b8ab365c4..6600fd865 100755 --- a/projectq/cengines/_main_test.py +++ b/projectq/cengines/_main_test.py @@ -18,12 +18,10 @@ import pytest -from projectq.cengines import DummyEngine, BasicMapperEngine, LocalOptimizer from projectq.backends import Simulator +from projectq.cengines import BasicMapperEngine, DummyEngine, LocalOptimizer, _main from projectq.ops import AllocateQubitGate, DeallocateQubitGate, FlushGate, H -from projectq.cengines import _main - def test_main_engine_init(): ceng1 = DummyEngine() diff --git a/projectq/cengines/_manualmapper.py b/projectq/cengines/_manualmapper.py index 4af0122ac..8699feb88 100755 --- a/projectq/cengines/_manualmapper.py +++ b/projectq/cengines/_manualmapper.py @@ -12,9 +12,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" -Contains a compiler engine to add mapping information -""" + +"""A compiler engine to add mapping information.""" + from ._basicmapper import BasicMapperEngine @@ -29,8 +29,9 @@ class ManualMapper(BasicMapperEngine): 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 @@ -38,7 +39,7 @@ def __init__(self, map_fun=lambda x: x): """ super().__init__() self.map = map_fun - self.current_mapping = dict() + self.current_mapping = {} def receive(self, command_list): """ diff --git a/projectq/cengines/_manualmapper_test.py b/projectq/cengines/_manualmapper_test.py index 64a04dfd6..c84301742 100755 --- a/projectq/cengines/_manualmapper_test.py +++ b/projectq/cengines/_manualmapper_test.py @@ -15,11 +15,9 @@ """Tests for projectq.cengines._manualmapper.py.""" from projectq import MainEngine -from projectq.cengines import DummyEngine -from projectq.ops import H, Measure, All +from projectq.cengines import DummyEngine, ManualMapper from projectq.meta import LogicalQubitIDTag - -from projectq.cengines import ManualMapper +from projectq.ops import All, H, Measure def test_manualmapper_mapping(): diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 39762bd6a..d8a63b2c4 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -12,19 +12,20 @@ # 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. -""" + +"""A local optimizer engine.""" import warnings -from projectq.ops import FlushGate, FastForwardingGate, NotMergeable +from projectq.ops import FastForwardingGate, FlushGate, NotMergeable from ._basics import BasicEngine class LocalOptimizer(BasicEngine): """ + Circuit optimization compiler engine. + LocalOptimizer is a compiler engine which optimizes locally (merging rotations, cancelling gates with their inverse) in a local window of user- defined size. @@ -42,7 +43,7 @@ def __init__(self, cache_size=5, m=None): # pylint: disable=invalid-name cache_size (int): Number of gates to cache per qubit, before sending on the first gate. """ super().__init__() - self._l = dict() # dict of lists containing operations for each qubit + self._l = {} # dict of lists containing operations for each qubit if m: warnings.warn( @@ -55,9 +56,7 @@ def __init__(self, cache_size=5, m=None): # pylint: disable=invalid-name # sends n gate operations of the qubit with index idx def _send_qubit_pipeline(self, idx, n_gates): - """ - Send n gate operations of the qubit with index idx to the next engine. - """ + """Send n gate operations of the qubit with index idx to the next engine.""" 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 @@ -89,8 +88,9 @@ def _send_qubit_pipeline(self, idx, n_gates): 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 @@ -116,6 +116,8 @@ def _get_gate_indices(self, idx, i, qubit_ids): def _optimize(self, idx, lim=None): """ + Gate cancellation routine. + 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). @@ -197,10 +199,7 @@ def _optimize(self, idx, lim=None): return limit def _check_and_send(self): - """ - Check whether a qubit pipeline must be sent on and, if so, - optimize the pipeline and then send it on. - """ + """Check whether a qubit pipeline must be sent on and, if so, optimize the pipeline and then send it on.""" for i in self._l: if ( len(self._l[i]) >= self._cache_size @@ -212,17 +211,14 @@ def _check_and_send(self): 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() + new_dict = {} for idx in self._l: if len(self._l[idx]) > 0: new_dict[idx] = self._l[idx] self._l = new_dict def _cache_cmd(self, cmd): - """ - Cache a command, i.e., inserts it into the command lists of all qubits - involved. - """ + """Cache a command, i.e., inserts it into the command lists of all qubits involved.""" # are there qubit ids that haven't been added to the list? idlist = [qubit.id for sublist in cmd.all_qubits for qubit in sublist] @@ -236,20 +232,22 @@ def _cache_cmd(self, cmd): def receive(self, command_list): """ - Receive commands from the previous engine and cache them. - If a flush gate arrives, the entire buffer is sent on. + Receive a list of commands. + + Receive commands from the previous engine and cache them. If a flush gate arrives, the entire buffer is sent + on. """ for cmd in command_list: if cmd.gate == FlushGate(): # flush gate --> optimize and flush for idx in self._l: self._optimize(idx) self._send_qubit_pipeline(idx, len(self._l[idx])) - new_dict = dict() + new_dict = {} for idx in self._l: if len(self._l[idx]) > 0: # pragma: no cover new_dict[idx] = self._l[idx] self._l = new_dict - if self._l != dict(): # pragma: no cover + if self._l != {}: # pragma: no cover raise RuntimeError('Internal compiler error: qubits remaining in LocalOptimizer after a flush!') self.send([cmd]) else: diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index fc2ac96c0..104dfcef3 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -19,20 +19,18 @@ import pytest from projectq import MainEngine -from projectq.cengines import DummyEngine +from projectq.cengines import DummyEngine, _optimize from projectq.ops import ( CNOT, + AllocateQubitGate, + ClassicalInstructionGate, + FastForwardingGate, H, Rx, Ry, - AllocateQubitGate, X, - FastForwardingGate, - ClassicalInstructionGate, ) -from projectq.cengines import _optimize - def test_local_optimizer_init_api_change(): with pytest.warns(DeprecationWarning): diff --git a/projectq/cengines/_replacer/_decomposition_rule.py b/projectq/cengines/_replacer/_decomposition_rule.py index f7bee3d67..812ca1e81 100755 --- a/projectq/cengines/_replacer/_decomposition_rule.py +++ b/projectq/cengines/_replacer/_decomposition_rule.py @@ -13,49 +13,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing the definition of a decomposition rule""" +"""Module containing the definition of a decomposition rule.""" from projectq.ops import BasicGate class ThisIsNotAGateClassError(TypeError): - """Exception raised when a gate instance is encountered instead of a gate class in a decomposition rule""" + """Exception raised when a gate instance is encountered instead of a gate class in a decomposition rule.""" class DecompositionRule: # pylint: disable=too-few-public-methods - """ - A rule for breaking down specific gates into sequences of simpler gates. - """ + """A rule for breaking down specific gates into sequences of simpler gates.""" def __init__(self, gate_class, gate_decomposer, gate_recognizer=lambda cmd: True): """ + Initialize a DecompositionRule object. + Args: gate_class (type): The type of gate that this rule decomposes. - The gate class is redundant information used to make lookups - faster when iterating over a circuit and deciding "which rules - apply to this gate?" again and again. + The gate class is redundant information used to make lookups faster when iterating over a circuit and + deciding "which rules apply to this gate?" again and again. - Note that this parameter is a gate type, not a gate instance. - You supply gate_class=MyGate or gate_class=MyGate().__class__, - not gate_class=MyGate(). + Note that this parameter is a gate type, not a gate instance. You supply gate_class=MyGate or + gate_class=MyGate().__class__, not gate_class=MyGate(). - gate_decomposer (function[projectq.ops.Command]): Function which, - given the command to decompose, applies a sequence of gates - corresponding to the high-level function of a gate of type - gate_class. + gate_decomposer (function[projectq.ops.Command]): Function which, given the command to decompose, applies + a sequence of gates corresponding to the high-level function of a gate of type gate_class. - gate_recognizer (function[projectq.ops.Command] : boolean): A - predicate that determines if the decomposition applies to the - given command (on top of the filtering by gate_class). + gate_recognizer (function[projectq.ops.Command] : boolean): A predicate that determines if the + decomposition applies to the given command (on top of the filtering by gate_class). - For example, a decomposition rule may only to apply rotation - gates that rotate by a specific angle. + For example, a decomposition rule may only to apply rotation gates that rotate by a specific angle. - If no gate_recognizer is given, the decomposition applies to - all gates matching the gate_class. + If no gate_recognizer is given, the decomposition applies to all gates matching the gate_class. """ - # Check for common gate_class type mistakes. if isinstance(gate_class, BasicGate): raise ThisIsNotAGateClassError( diff --git a/projectq/cengines/_replacer/_decomposition_rule_set.py b/projectq/cengines/_replacer/_decomposition_rule_set.py index aba4eba6b..738ea0420 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_set.py +++ b/projectq/cengines/_replacer/_decomposition_rule_set.py @@ -13,25 +13,24 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing the definition of a decomposition rule set""" +"""Module containing the definition of a decomposition rule set.""" from projectq.meta import Dagger class DecompositionRuleSet: - """ - A collection of indexed decomposition rules. - """ + """A collection of indexed decomposition rules.""" def __init__(self, rules=None, modules=None): """ + Initialize a DecompositionRuleSet object. + Args: rules list[DecompositionRule]: Initial decomposition rules. - modules (iterable[ModuleWithDecompositionRuleSet]): A list of - things with an "all_defined_decomposition_rules" property - containing decomposition rules to add to the rule set. + modules (iterable[ModuleWithDecompositionRuleSet]): A list of things with an + "all_defined_decomposition_rules" property containing decomposition rules to add to the rule set. """ - self.decompositions = dict() + self.decompositions = {} if rules: self.add_decomposition_rules(rules) @@ -42,9 +41,7 @@ def __init__(self, rules=None, modules=None): ) def add_decomposition_rules(self, rules): - """ - Add some decomposition rules to a decomposition rule set - """ + """Add some decomposition rules to a decomposition rule set.""" for rule in rules: self.add_decomposition_rule(rule) @@ -63,47 +60,37 @@ def add_decomposition_rule(self, rule): 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. - """ + """Interface type for explaining one of the parameters that can be given to DecompositionRuleSet.""" def __init__(self, all_defined_decomposition_rules): """ + Initialize a ModuleWithDecompositionRuleSet object. + Args: - all_defined_decomposition_rules (list[DecompositionRule]): - A list of decomposition rules. + all_defined_decomposition_rules (list[DecompositionRule]): A list of decomposition rules. """ self.all_defined_decomposition_rules = all_defined_decomposition_rules class _Decomposition: # pylint: disable=too-few-public-methods - """ - The Decomposition class can be used to register a decomposition rule (by - calling register_decomposition) - """ + """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. + Initialize a Decomposition object. Args: - replacement_fun: Function that, when called with a `Command` - object, decomposes this command. - recogn_fun: Function that, when called with a `Command` object, - returns True if and only if the replacement rule can handle - this command. - - Every Decomposition is registered with the gate class. The - Decomposition rule is then potentially valid for all objects which are - an instance of that same class - (i.e., instance of gate_object.__class__). All other parameters have - to be checked by the recogn_fun, i.e., it has to decide whether the - decomposition rule can indeed be applied to replace the given Command. - - As an example, consider recognizing the Toffoli gate, which is a - Pauli-X gate with 2 control qubits. The recognizer function would then - be: + replacement_fun: Function that, when called with a `Command` object, decomposes this command. + recogn_fun: Function that, when called with a `Command` object, returns True if and only if the + replacement rule can handle this command. + + Every Decomposition is registered with the gate class. The Decomposition rule is then potentially valid for + all objects which are an instance of that same class (i.e., instance of gate_object.__class__). All other + parameters have to be checked by the recogn_fun, i.e., it has to decide whether the decomposition rule can + indeed be applied to replace the given Command. + + As an example, consider recognizing the Toffoli gate, which is a Pauli-X gate with 2 control qubits. The + recognizer function would then be: .. code-block:: python @@ -128,14 +115,11 @@ def recogn_toffoli(cmd): def get_inverse_decomposition(self): """ - Return the Decomposition object which handles the inverse of the - original command. - - This simulates the user having added a decomposition rule for the - inverse as well. Since decomposing the inverse of a command can be - achieved by running the original decomposition inside a - `with Dagger(engine):` statement, this is not necessary - (and will be done automatically by the framework). + Return the Decomposition object which handles the inverse of the original command. + + This simulates the user having added a decomposition rule for the inverse as well. Since decomposing the + inverse of a command can be achieved by running the original decomposition inside a `with Dagger(engine):` + statement, this is not necessary (and will be done automatically by the framework). Returns: Decomposition handling the inverse of the original command. diff --git a/projectq/cengines/_replacer/_decomposition_rule_test.py b/projectq/cengines/_replacer/_decomposition_rule_test.py index c2ac1f75e..a67759dc7 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_test.py +++ b/projectq/cengines/_replacer/_decomposition_rule_test.py @@ -17,6 +17,7 @@ import pytest from projectq.ops import BasicRotationGate + from . import DecompositionRule, ThisIsNotAGateClassError diff --git a/projectq/cengines/_replacer/_replacer.py b/projectq/cengines/_replacer/_replacer.py index 0a9a4d684..a0a942196 100755 --- a/projectq/cengines/_replacer/_replacer.py +++ b/projectq/cengines/_replacer/_replacer.py @@ -12,25 +12,28 @@ # 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 -(e.g., default) to find an appropriate decomposition. +Definitions of a few compiler engines that handle command filtering and replacement. + +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 (e.g., default) to find an appropriate decomposition. -The InstructionFilter can be used to further specify which gates to -replace/keep. +The InstructionFilter can be used to further specify which gates to replace/keep. """ -from projectq.cengines import BasicEngine, ForwarderEngine, CommandModifier +from projectq.cengines import BasicEngine, CommandModifier, ForwarderEngine from projectq.ops import FlushGate, get_inverse class NoGateDecompositionError(Exception): - """Exception raised when no gate decomposition rule can be found""" + """Exception raised when no gate decomposition rule can be found.""" class InstructionFilter(BasicEngine): """ + A compiler engine that implements a user-defined is_available() method. + 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). @@ -38,6 +41,8 @@ class InstructionFilter(BasicEngine): def __init__(self, filterfun): """ + Initialize an InstructionFilter object. + Initializer: The provided filterfun returns True for all commands which do not need replacement and False for commands that do. @@ -45,11 +50,13 @@ def __init__(self, filterfun): filterfun (function): Filter function which returns True for available commands, and False otherwise. filterfun will be called as filterfun(self, cmd). """ - BasicEngine.__init__(self) + super().__init__() self._filterfun = filterfun def is_available(self, cmd): """ + Test whether a Command is supported by a compiler engine. + Specialized implementation of BasicBackend.is_available: Forwards this call to the filter function given to the constructor. @@ -60,7 +67,9 @@ def is_available(self, cmd): def receive(self, command_list): """ - Forward all commands to the next engine. + Receive a list of commands. + + This implementation simply forwards all commands to the next engine. Args: command_list (list): List of commands to receive. @@ -70,6 +79,8 @@ def receive(self, command_list): class AutoReplacer(BasicEngine): """ + A compiler engine to automatically replace certain commands. + 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). @@ -104,12 +115,14 @@ def decomposition_chooser(cmd, decomp_list): return decomp_list[0] repl = AutoReplacer(decomposition_chooser) """ - BasicEngine.__init__(self) + super().__init__() self._decomp_chooser = decomposition_chooser self.decomposition_rule_set = decomposition_rule_se def _process_command(self, cmd): # pylint: disable=too-many-locals,too-many-branches """ + Process a command. + 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). @@ -205,9 +218,10 @@ def cmd_mod_fun(cmd): # Adds the tags def receive(self, command_list): """ - Receive a list of commands from the previous compiler engine and, if - necessary, replace/decompose the gates according to the decomposition - rules in the loaded setup. + Receive a list of commands. + + Receive a list of commands from the previous compiler engine and, if necessary, replace/decompose the gates + according to the decomposition rules in the loaded setup. Args: command_list (list): List of commands to handle. diff --git a/projectq/cengines/_replacer/_replacer_test.py b/projectq/cengines/_replacer/_replacer_test.py index b2675f191..dbf68ea2e 100755 --- a/projectq/cengines/_replacer/_replacer_test.py +++ b/projectq/cengines/_replacer/_replacer_test.py @@ -17,18 +17,18 @@ import pytest from projectq import MainEngine -from projectq.cengines import DummyEngine, DecompositionRuleSet, DecompositionRule +from projectq.cengines import DecompositionRule, DecompositionRuleSet, DummyEngine +from projectq.cengines._replacer import _replacer from projectq.ops import ( BasicGate, ClassicalInstructionGate, Command, H, - S, NotInvertible, Rx, + S, X, ) -from projectq.cengines._replacer import _replacer def test_filter_engine(): diff --git a/projectq/cengines/_swapandcnotflipper.py b/projectq/cengines/_swapandcnotflipper.py index dfb87b7a3..2d7943abb 100755 --- a/projectq/cengines/_swapandcnotflipper.py +++ b/projectq/cengines/_swapandcnotflipper.py @@ -13,14 +13,14 @@ # 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 -necessary. +A compiler engine which flips the directionality of CNOTs according to the given connectivity graph. + +It also translates Swap gates to CNOTs if necessary. """ from copy import deepcopy from projectq.meta import get_control_count -from projectq.ops import All, NOT, CNOT, H, Swap +from projectq.ops import CNOT, NOT, All, H, Swap from ._basics import BasicEngine, ForwarderEngine from ._cmdmodifier import CommandModifier @@ -28,11 +28,10 @@ class SwapAndCNOTFlipper(BasicEngine): """ - Flips CNOTs and translates Swaps to CNOTs where necessary. + Flip CNOTs and translates Swaps to CNOTs where necessary. Warning: - This engine assumes that CNOT and Hadamard gates are supported by - the following engines. + This engine assumes that CNOT and Hadamard gates are supported by the following engines. Warning: This engine cannot be used as a backend. @@ -43,18 +42,15 @@ def __init__(self, connectivity): Initialize the engine. Args: - connectivity (set): Set of tuples (c, t) where if (c, t) is an - element of the set means that a CNOT can be performed between - the physical ids (c, t) with c being the control and t being - the target qubit. + connectivity (set): Set of tuples (c, t) where if (c, t) is an element of the set means that a CNOT can be + performed between the physical ids (c, t) with c being the control and t being the target qubit. """ super().__init__() self.connectivity = connectivity def is_available(self, cmd): """ - Check if the IBM backend can perform the Command cmd and return True - if so. + Check if the IBM backend can perform the Command cmd and return True if so. Args: cmd (Command): The command to check @@ -120,14 +116,13 @@ def cmd_mod(command): def receive(self, command_list): """ - Receives a command list and if the command is a CNOT gate, it flips - it using Hadamard gates if necessary; if it is a Swap gate, it - decomposes it using 3 CNOTs. All other gates are simply sent to the - next engine. + Receive a list of commands. + + Receive a command list and if the command is a CNOT gate, it flips it using Hadamard gates if necessary; if it + is a Swap gate, it decomposes it using 3 CNOTs. All other gates are simply sent to the next engine. 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 self._needs_flipping(cmd): diff --git a/projectq/cengines/_swapandcnotflipper_test.py b/projectq/cengines/_swapandcnotflipper_test.py index 61256aa52..dc9611a48 100755 --- a/projectq/cengines/_swapandcnotflipper_test.py +++ b/projectq/cengines/_swapandcnotflipper_test.py @@ -18,8 +18,8 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import All, H, CNOT, X, Swap, Command -from projectq.meta import Control, Compute, Uncompute, ComputeTag, UncomputeTag +from projectq.meta import Compute, ComputeTag, Control, Uncompute, UncomputeTag +from projectq.ops import CNOT, All, Command, H, Swap, X from projectq.types import WeakQubitRef from . import _swapandcnotflipper @@ -72,7 +72,7 @@ def test_swapandcnotflipper_is_available(): def test_swapandcnotflipper_flips_cnot(): backend = DummyEngine(save_commands=True) - connectivity = set([(0, 1)]) + connectivity = {(0, 1)} flipper = _swapandcnotflipper.SwapAndCNOTFlipper(connectivity) eng = MainEngine(backend=backend, engine_list=[flipper]) qb0 = eng.allocate_qubit() @@ -91,7 +91,7 @@ def test_swapandcnotflipper_flips_cnot(): def test_swapandcnotflipper_invalid_circuit(): backend = DummyEngine(save_commands=True) - connectivity = set([(0, 2)]) + connectivity = {(0, 2)} flipper = _swapandcnotflipper.SwapAndCNOTFlipper(connectivity) eng = MainEngine(backend=backend, engine_list=[flipper]) qb0 = eng.allocate_qubit() @@ -107,7 +107,7 @@ def test_swapandcnotflipper_invalid_circuit(): def test_swapandcnotflipper_optimize_swaps(): backend = DummyEngine(save_commands=True) - connectivity = set([(1, 0)]) + connectivity = {(1, 0)} flipper = _swapandcnotflipper.SwapAndCNOTFlipper(connectivity) eng = MainEngine(backend=backend, engine_list=[flipper]) qb0 = eng.allocate_qubit() @@ -123,7 +123,7 @@ def test_swapandcnotflipper_optimize_swaps(): assert hgates == 4 backend = DummyEngine(save_commands=True) - connectivity = set([(0, 1)]) + connectivity = {(0, 1)} flipper = _swapandcnotflipper.SwapAndCNOTFlipper(connectivity) eng = MainEngine(backend=backend, engine_list=[flipper]) qb0 = eng.allocate_qubit() @@ -141,7 +141,7 @@ def test_swapandcnotflipper_optimize_swaps(): def test_swapandcnotflipper_keeps_tags(): backend = DummyEngine(save_commands=True) - connectivity = set([(1, 0)]) + connectivity = {(1, 0)} flipper = _swapandcnotflipper.SwapAndCNOTFlipper(connectivity) eng = MainEngine(backend=backend, engine_list=[flipper]) qb0 = eng.allocate_qubit() diff --git a/projectq/cengines/_tagremover.py b/projectq/cengines/_tagremover.py index 60da9b0d8..2c6497ac5 100755 --- a/projectq/cengines/_tagremover.py +++ b/projectq/cengines/_tagremover.py @@ -13,8 +13,10 @@ # 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, ...) +The TagRemover compiler engine. + +A TagRemover engine removes temporary command tags (such as Compute/Uncompute), thus enabling optimization across meta +statements (loops after unrolling, compute/uncompute, ...) """ from projectq.meta import ComputeTag, UncomputeTag @@ -23,6 +25,8 @@ class TagRemover(BasicEngine): """ + Compiler engine that remove temporary command tags. + TagRemover is a compiler engine which removes temporary command tags (see the tag classes such as LoopTag in projectq.meta._loop). @@ -32,7 +36,7 @@ class TagRemover(BasicEngine): def __init__(self, tags=None): """ - Construct the TagRemover. + Initialize a TagRemover object. Args: tags: A list of meta tag classes (e.g., [ComputeTag, UncomputeTag]) @@ -48,6 +52,8 @@ def __init__(self, tags=None): def receive(self, command_list): """ + Receive a list of commands. + 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. diff --git a/projectq/cengines/_tagremover_test.py b/projectq/cengines/_tagremover_test.py index d22369762..c9679dcd0 100755 --- a/projectq/cengines/_tagremover_test.py +++ b/projectq/cengines/_tagremover_test.py @@ -17,11 +17,9 @@ import pytest from projectq import MainEngine +from projectq.cengines import DummyEngine, _tagremover from projectq.meta import ComputeTag, UncomputeTag from projectq.ops import Command, H -from projectq.cengines import DummyEngine - -from projectq.cengines import _tagremover def test_tagremover_default(): diff --git a/projectq/cengines/_testengine.py b/projectq/cengines/_testengine.py index eace5744b..1e2d6f934 100755 --- a/projectq/cengines/_testengine.py +++ b/projectq/cengines/_testengine.py @@ -15,13 +15,14 @@ """TestEngine and DummyEngine.""" from copy import deepcopy + from projectq.ops import FlushGate from ._basics import BasicEngine def _compare_cmds(cmd1, cmd2): - """Compare two command objects""" + """Compare two command objects.""" cmd2 = deepcopy(cmd2) cmd2.engine = cmd1.engine return cmd1 == cmd2 @@ -29,20 +30,23 @@ def _compare_cmds(cmd1, cmd2): class CompareEngine(BasicEngine): """ + Command list comparison compiler engine for testing purposes. + 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): + """Initialize a CompareEngine object.""" super().__init__() self._l = [[]] def is_available(self, cmd): - """All commands are accepted by this compiler engine""" + """All commands are accepted by this compiler engine.""" return True def cache_cmd(self, cmd): - """Cache a command""" + """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) @@ -61,7 +65,9 @@ def cache_cmd(self, 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 + Receive a list of commands. + + Receive a command list and, for each command, stores it inside the cache before sending it to the next compiler engine. Args: @@ -74,6 +80,7 @@ def receive(self, command_list): self.send(command_list) def __eq__(self, other): + """Equal operator.""" if not isinstance(other, CompareEngine) or len(self._l) != len(other._l): return False for i in range(len(self._l)): @@ -84,10 +91,8 @@ def __eq__(self, other): return False return True - def __ne__(self, other): - return not self.__eq__(other) - def __str__(self): + """Return a string representation of the object.""" string = "" for qubit_id in range(len(self._l)): string += "Qubit {0} : ".format(qubit_id) @@ -109,7 +114,7 @@ class DummyEngine(BasicEngine): def __init__(self, save_commands=False): """ - Initialize DummyEngine + Initialize a DummyEngine. Args: save_commands (default = False): If True, commands are saved in @@ -120,12 +125,14 @@ def __init__(self, save_commands=False): self.received_commands = [] def is_available(self, cmd): - """All commands are accepted by this compiler engine""" + """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 + Receive a list of commands. + + Receive a command list and, for each command, stores it internally if requested before sending it to the next compiler engine. Args: diff --git a/projectq/cengines/_testengine_test.py b/projectq/cengines/_testengine_test.py index baf4010de..fee0866e5 100755 --- a/projectq/cengines/_testengine_test.py +++ b/projectq/cengines/_testengine_test.py @@ -15,10 +15,8 @@ """Tests for projectq.cengines._testengine.py.""" from projectq import MainEngine -from projectq.cengines import DummyEngine -from projectq.ops import CNOT, H, Rx, Allocate, FlushGate - -from projectq.cengines import _testengine +from projectq.cengines import DummyEngine, _testengine +from projectq.ops import CNOT, Allocate, FlushGate, H, Rx def test_compare_engine_str(): diff --git a/projectq/cengines/_twodmapper.py b/projectq/cengines/_twodmapper.py index f3bf4b5d3..4b4bb18b9 100644 --- a/projectq/cengines/_twodmapper.py +++ b/projectq/cengines/_twodmapper.py @@ -20,10 +20,10 @@ 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 import math import random +from copy import deepcopy import networkx as nx @@ -37,7 +37,6 @@ ) from projectq.types import WeakQubitRef - from ._basicmapper import BasicMapperEngine from ._linearmapper import LinearMapper, return_swap_depth @@ -109,14 +108,14 @@ def __init__( # pylint: disable=too-many-arguments # Before sending we use this map to translate to backend ids: self._mapped_ids_to_backend_ids = mapped_ids_to_backend_ids if self._mapped_ids_to_backend_ids is None: - self._mapped_ids_to_backend_ids = dict() + self._mapped_ids_to_backend_ids = {} 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(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() + self._backend_ids_to_mapped_ids = {} for mapped_id, backend_id in self._mapped_ids_to_backend_ids.items(): self._backend_ids_to_mapped_ids[backend_id] = mapped_id # As we use internally the mapped ids which are in row-major order, @@ -132,7 +131,7 @@ def __init__( # pylint: disable=too-many-arguments # places. self._rng = random.Random(11) # Storing commands - self._stored_commands = list() + self._stored_commands = [] # Logical qubit ids for which the Allocate gate has already been # processed and sent to the next engine but which are not yet # deallocated: @@ -140,8 +139,8 @@ def __init__( # pylint: disable=too-many-arguments # Change between 2D and 1D mappings (2D is a snake like 1D chain) # Note it translates to our mapped ids in row major order and not # backend ids which might be different. - self._map_2d_to_1d = dict() - self._map_1d_to_2d = dict() + self._map_2d_to_1d = {} + self._map_1d_to_2d = {} for row_index in range(self.num_rows): for column_index in range(self.num_columns): if row_index % 2 == 0: @@ -155,11 +154,12 @@ def __init__( # pylint: disable=too-many-arguments self._map_1d_to_2d[mapped_id_1d] = mapped_id_2d # Statistics: self.num_mappings = 0 - self.depth_of_swaps = dict() - self.num_of_swaps_per_mapping = dict() + self.depth_of_swaps = {} + self.num_of_swaps_per_mapping = {} @property def current_mapping(self): + """Access to the mapping stored inside the mapper engine.""" return deepcopy(self._current_mapping) @current_mapping.setter @@ -168,14 +168,12 @@ def current_mapping(self, current_mapping): if current_mapping is None: self._current_row_major_mapping = None else: - self._current_row_major_mapping = dict() + self._current_row_major_mapping = {} for logical_id, backend_id in current_mapping.items(): self._current_row_major_mapping[logical_id] = self._backend_ids_to_mapped_ids[backend_id] def is_available(self, cmd): - """ - Only allows 1 or two qubit gates. - """ + """Only allow 1 or two qubit gates.""" num_qubits = 0 for qureg in cmd.all_qubits: num_qubits += len(qureg) @@ -183,7 +181,7 @@ def is_available(self, cmd): def _return_new_mapping(self): """ - Returns a new mapping of the qubits. + Return 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 @@ -195,7 +193,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() + old_mapping_1d = {} 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: @@ -209,15 +207,13 @@ def _return_new_mapping(self): current_mapping=old_mapping_1d, ) - new_mapping_2d = dict() + new_mapping_2d = {} for logical_id, mapped_id in new_mapping_1d.items(): new_mapping_2d[logical_id] = self._map_1d_to_2d[mapped_id] return new_mapping_2d 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 mapped_id1 = element1.current_column + element1.current_row * self.num_columns @@ -283,7 +279,7 @@ def return_swaps( # pylint: disable=too-many-locals,too-many-branches,too-many- self, old_mapping, new_mapping, permutation=None ): """ - Returns the swap operation to change mapping + Return the swap operation to change mapping. Args: old_mapping: dict: keys are logical ids and values are mapped qubit ids @@ -417,7 +413,7 @@ def __init__( # pylint: disable=too-many-arguments def _send_possible_commands(self): # pylint: disable=too-many-branches """ - Sends the stored commands possible without changing the mapping. + Send the stored commands possible without changing the mapping. Note: self._current_row_major_mapping (hence also self.current_mapping) must exist already """ @@ -475,7 +471,7 @@ def _send_possible_commands(self): # pylint: disable=too-many-branches 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)) + qb0, qb1 = sorted(mapped_ids) send_gate = False if qb1 - qb0 == self.num_columns: send_gate = True @@ -494,7 +490,7 @@ def _send_possible_commands(self): # pylint: disable=too-many-branches def _run(self): # pylint: disable=too-many-locals.too-many-branches,too-many-statements """ - Creates a new mapping and executes possible gates. + Create 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 @@ -502,7 +498,7 @@ def _run(self): # pylint: disable=too-many-locals.too-many-branches,too-many-st """ num_of_stored_commands_before = len(self._stored_commands) if not self.current_mapping: - self.current_mapping = dict() + self.current_mapping = {} else: self._send_possible_commands() if len(self._stored_commands) == 0: @@ -570,7 +566,7 @@ def _run(self): # pylint: disable=too-many-locals.too-many-branches,too-many-st self.send([cmd]) # Change to new map: self._current_row_major_mapping = new_row_major_mapping - new_mapping = dict() + new_mapping = {} for logical_id, mapped_id in new_row_major_mapping.items(): new_mapping[logical_id] = self._mapped_ids_to_backend_ids[mapped_id] self.current_mapping = new_mapping @@ -585,7 +581,9 @@ def _run(self): # pylint: disable=too-many-locals.too-many-branches,too-many-st 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 + Receive a list of commands. + + Receive a command list and, for each command, stores it until we do a mapping (FlushGate or Cache of stored commands is full). Args: diff --git a/projectq/cengines/_twodmapper_test.py b/projectq/cengines/_twodmapper_test.py index a21969a74..f86c4ee02 100644 --- a/projectq/cengines/_twodmapper_test.py +++ b/projectq/cengines/_twodmapper_test.py @@ -14,20 +14,19 @@ # limitations under the License. """Tests for projectq.cengines._2dmapper.py.""" -from copy import deepcopy import itertools import random +from copy import deepcopy import pytest import projectq from projectq.cengines import DummyEngine, LocalOptimizer +from projectq.cengines import _twodmapper as two_d from projectq.meta import LogicalQubitIDTag from projectq.ops import Allocate, BasicGate, Command, Deallocate, FlushGate, X from projectq.types import WeakQubitRef -from projectq.cengines import _twodmapper as two_d - def test_is_available(): mapper = two_d.GridMapper(num_rows=2, num_columns=2) @@ -125,10 +124,10 @@ def test_return_new_mapping(different_backend_ids): assert new_mapping == possible_solution_1 or new_mapping == possible_solution_2 eng.flush() if different_backend_ids: - transformed_sol1 = dict() + transformed_sol1 = {} for logical_id, mapped_id in possible_solution_1.items(): transformed_sol1[logical_id] = map_to_backend_ids[mapped_id] - transformed_sol2 = dict() + transformed_sol2 = {} 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 @@ -153,8 +152,8 @@ def test_return_swaps_random(num_rows, num_columns, seed, none_old, none_new): num_qubits = num_rows * num_columns old_chain = random.sample(range(num_qubits), num_qubits) new_chain = random.sample(range(num_qubits), num_qubits) - old_mapping = dict() - new_mapping = dict() + old_mapping = {} + new_mapping = {} for i in range(num_qubits): old_mapping[old_chain[i]] = i new_mapping[new_chain[i]] = i @@ -252,9 +251,9 @@ def test_send_possible_commands_allocate(different_backend_ids): qb0 = WeakQubitRef(engine=None, idx=0) cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] - mapper._currently_allocated_ids = set([10]) + mapper._currently_allocated_ids = {10} # not in mapping: - mapper.current_mapping = dict() + mapper.current_mapping = {} assert len(backend.received_commands) == 0 mapper._send_possible_commands() assert len(backend.received_commands) == 0 @@ -273,7 +272,7 @@ def test_send_possible_commands_allocate(different_backend_ids): tags=[LogicalQubitIDTag(0)], ) assert backend.received_commands[0] == received_cmd - assert mapper._currently_allocated_ids == set([10, 0]) + assert mapper._currently_allocated_ids == {10, 0} @pytest.mark.parametrize("different_backend_ids", [False, True]) @@ -289,8 +288,8 @@ def test_send_possible_commands_deallocate(different_backend_ids): qb0 = WeakQubitRef(engine=None, idx=0) cmd0 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] - mapper.current_mapping = dict() - mapper._currently_allocated_ids = set([10]) + mapper.current_mapping = {} + mapper._currently_allocated_ids = {10} # not yet allocated: mapper._send_possible_commands() assert len(backend.received_commands) == 0 @@ -301,8 +300,8 @@ def test_send_possible_commands_deallocate(different_backend_ids): mapper._send_possible_commands() assert len(backend.received_commands) == 1 assert len(mapper._stored_commands) == 0 - assert mapper.current_mapping == dict() - assert mapper._currently_allocated_ids == set([10]) + assert mapper.current_mapping == {} + assert mapper._currently_allocated_ids == {10} @pytest.mark.parametrize("different_backend_ids", [False, True]) @@ -391,7 +390,7 @@ def choose_last_permutation(swaps): mapper.receive([cmd_flush]) assert mapper._stored_commands == [] assert len(backend.received_commands) == 10 - assert mapper._currently_allocated_ids == set([0, 2, 3]) + assert mapper._currently_allocated_ids == {0, 2, 3} if different_backend_ids: assert ( mapper.current_mapping == {0: 21, 2: 3, 3: 0} @@ -409,7 +408,7 @@ def choose_last_permutation(swaps): cmd9 = Command(None, X, qubits=([qb0],), controls=[qb3]) mapper.storage = 1 mapper.receive([cmd9]) - assert mapper._currently_allocated_ids == set([0, 2, 3]) + assert mapper._currently_allocated_ids == {0, 2, 3} assert mapper._stored_commands == [] assert len(mapper.current_mapping) == 3 assert 0 in mapper.current_mapping diff --git a/projectq/libs/hist/_histogram.py b/projectq/libs/hist/_histogram.py index 85c0be1b3..18a674e23 100644 --- a/projectq/libs/hist/_histogram.py +++ b/projectq/libs/hist/_histogram.py @@ -13,9 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Functions to plot a histogram of measured data""" +"""Functions to plot a histogram of measured data.""" -from __future__ import print_function import matplotlib.pyplot as plt from projectq.backends import Simulator diff --git a/projectq/libs/hist/_histogram_test.py b/projectq/libs/hist/_histogram_test.py index c6cb78a95..6181a0161 100644 --- a/projectq/libs/hist/_histogram_test.py +++ b/projectq/libs/hist/_histogram_test.py @@ -13,15 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest import matplotlib import matplotlib.pyplot as plt # noqa: F401 +import pytest from projectq import MainEngine -from projectq.ops import H, C, X, Measure, All, AllocateQubitGate, FlushGate -from projectq.cengines import DummyEngine, BasicEngine from projectq.backends import Simulator +from projectq.cengines import BasicEngine, DummyEngine from projectq.libs.hist import histogram +from projectq.ops import All, AllocateQubitGate, C, FlushGate, H, Measure, X @pytest.fixture(scope="module") diff --git a/projectq/libs/math/__init__.py b/projectq/libs/math/__init__.py index f950ab3cf..06dc384c7 100755 --- a/projectq/libs/math/__init__.py +++ b/projectq/libs/math/__init__.py @@ -13,17 +13,16 @@ # 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, AddQuantum, - SubtractQuantum, ComparatorQuantum, DivideQuantum, + MultiplyByConstantModN, MultiplyQuantum, + SubConstant, + SubConstantModN, + SubtractQuantum, ) - -from ._default_rules import all_defined_decomposition_rules diff --git a/projectq/libs/math/_constantmath.py b/projectq/libs/math/_constantmath.py index 25eb6def7..1b502aabc 100755 --- a/projectq/libs/math/_constantmath.py +++ b/projectq/libs/math/_constantmath.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing constant math quantum operations""" +"""Module containing constant math quantum operations.""" import math @@ -22,21 +22,20 @@ except ImportError: # pragma: no cover from fractions import gcd -from projectq.ops import R, X, Swap, CNOT, QFT -from projectq.meta import Control, Compute, Uncompute, CustomUncompute -from ._gates import AddConstant, SubConstant, AddConstantModN, SubConstantModN +from projectq.meta import Compute, Control, CustomUncompute, Uncompute +from projectq.ops import CNOT, QFT, R, Swap, X + +from ._gates import AddConstant, AddConstantModN, SubConstant, SubConstantModN # Draper's addition by constant https://arxiv.org/abs/quant-ph/0008033 def add_constant(eng, constant, quint): """ - Adds a classical constant c to the quantum integer (qureg) quint using - Draper addition. + Add a classical constant c to the quantum integer (qureg) quint using Draper addition. - Note: Uses the Fourier-transform adder from - https://arxiv.org/abs/quant-ph/0008033. + Note: + Uses the Fourier-transform adder from https://arxiv.org/abs/quant-ph/0008033. """ - with Compute(eng): QFT | quint @@ -51,9 +50,9 @@ def add_constant(eng, constant, quint): # Modular adder by Beauregard https://arxiv.org/abs/quant-ph/0205095 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. + Add a classical constant c to a quantum integer (qureg) quint modulo N using Draper addition. + + This function uses Draper addition and the construction from https://arxiv.org/abs/quant-ph/0205095. """ if constant < 0 or constant > N: raise ValueError('Pre-condition failed: 0 <= constant < N') @@ -82,7 +81,9 @@ def add_constant_modN(eng, constant, N, quint): # pylint: disable=invalid-name # from https://arxiv.org/abs/quant-ph/0205095 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., + Multiply a quantum integer by a classical number a modulo N. + + i.e., |x> -> |a*x mod N> @@ -113,9 +114,7 @@ def mul_by_constant_modN(eng, constant, N, quint_in): # pylint: disable=invalid def inv_mod_N(a, N): # pylint: disable=invalid-name - """ - Calculate the inverse of a modulo N - """ + """Calculate the inverse of a modulo N.""" # pylint: disable=invalid-name s = 0 old_s = 1 diff --git a/projectq/libs/math/_constantmath_test.py b/projectq/libs/math/_constantmath_test.py index 83eaf060e..aafcff127 100755 --- a/projectq/libs/math/_constantmath_test.py +++ b/projectq/libs/math/_constantmath_test.py @@ -16,14 +16,13 @@ import pytest +import projectq.libs.math from projectq import MainEngine -from projectq.cengines import InstructionFilter, AutoReplacer, DecompositionRuleSet from projectq.backends import Simulator +from projectq.cengines import AutoReplacer, DecompositionRuleSet, InstructionFilter +from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN 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 def init(engine, quint, value): diff --git a/projectq/libs/math/_default_rules.py b/projectq/libs/math/_default_rules.py index 60e25dd8b..ca4ad4a4f 100755 --- a/projectq/libs/math/_default_rules.py +++ b/projectq/libs/math/_default_rules.py @@ -12,48 +12,37 @@ # 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 +"""Registers a few default replacement rules for Shor's algorithm to work (see Examples).""" + from projectq.cengines import DecompositionRule +from projectq.meta import Control +from ._constantmath import add_constant, add_constant_modN, mul_by_constant_modN from ._gates import ( AddConstant, AddConstantModN, - MultiplyByConstantModN, AddQuantum, - SubtractQuantum, ComparatorQuantum, DivideQuantum, + MultiplyByConstantModN, MultiplyQuantum, -) - -from ._gates import ( + SubtractQuantum, _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_add_quantum_carry, inverse_quantum_division, + inverse_quantum_multiplication, + quantum_conditional_add, quantum_conditional_add_carry, + quantum_division, quantum_multiplication, - inverse_quantum_multiplication, + subtract_quantum, ) diff --git a/projectq/libs/math/_gates.py b/projectq/libs/math/_gates.py index 45b2bc1fe..35777da05 100755 --- a/projectq/libs/math/_gates.py +++ b/projectq/libs/math/_gates.py @@ -12,15 +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. -"""Math gates for ProjectQ""" + +"""Quantum number math gates for ProjectQ.""" from projectq.ops import BasicMathGate class AddConstant(BasicMathGate): """ - Add a constant to a quantum number represented by a quantum register, - stored from low- to high-bit. + Add a constant to a quantum number represented by a quantum register, stored from low- to high-bit. Example: .. code-block:: python @@ -35,7 +35,7 @@ class AddConstant(BasicMathGate): def __init__(self, a): # pylint: disable=invalid-name """ - Initializes the gate to the number to add. + Initialize the gate to the number to add. Args: a (int): Number to add to a quantum register. @@ -43,32 +43,29 @@ def __init__(self, a): # pylint: disable=invalid-name It also initializes its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ - BasicMathGate.__init__(self, lambda x: ((x + a),)) + super().__init__(lambda x: ((x + a),)) self.a = a # pylint: disable=invalid-name def get_inverse(self): - """ - Return the inverse gate (subtraction of the same constant). - """ + """Return the inverse gate (subtraction of the same constant).""" return SubConstant(self.a) def __str__(self): + """Return a string representation of the object.""" return "AddConstant({})".format(self.a) def __eq__(self, other): + """Equal operator.""" return isinstance(other, AddConstant) and self.a == other.a def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - 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. + Subtract a constant from a quantum number represented by a quantum register, stored from low- to high-bit. Args: a (int): Constant to subtract @@ -85,8 +82,7 @@ def SubConstant(a): # pylint: disable=invalid-name class AddConstantModN(BasicMathGate): """ - Add a constant to a quantum number represented by a quantum register - modulo N. + Add a constant to a quantum number represented by a quantum register modulo N. The number is stored from low- to high-bit, i.e., qunum[0] is the LSB. @@ -108,50 +104,45 @@ class AddConstantModN(BasicMathGate): def __init__(self, a, N): """ - Initializes the gate to the number to add modulo N. + Initialize the gate to the number to add modulo N. Args: a (int): Number to add to a quantum register (0 <= a < N). N (int): Number modulo which the addition is carried out. - It also initializes its base class, BasicMathGate, with the - corresponding function, so it can be emulated efficiently. + 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,)) + super().__init__(lambda x: ((x + a) % N,)) self.a = a # pylint: disable=invalid-name self.N = N def __str__(self): + """Return a string representation of the object.""" return "AddConstantModN({}, {})".format(self.a, self.N) 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 SubConstantModN(self.a, self.N) def __eq__(self, other): + """Equal operator.""" return isinstance(other, AddConstantModN) and self.a == other.a and self.N == other.N def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - def SubConstantModN(a, N): # pylint: disable=invalid-name """ - Subtract a constant from a quantum number represented by a quantum - register modulo N. + Subtract a constant from a quantum number represented by a quantum register modulo N. The number is stored from low- to high-bit, i.e., qunum[0] is the LSB. Args: a (int): Constant to add - N (int): Constant modulo which the addition of a should be carried - out. + N (int): Constant modulo which the addition of a should be carried out. Example: .. code-block:: python @@ -173,8 +164,7 @@ def SubConstantModN(a, N): # pylint: disable=invalid-name class MultiplyByConstantModN(BasicMathGate): """ - Multiply a quantum number represented by a quantum register by a constant - modulo N. + Multiply a quantum number represented by a quantum register by a constant modulo N. The number is stored from low- to high-bit, i.e., qunum[0] is the LSB. @@ -197,7 +187,7 @@ class MultiplyByConstantModN(BasicMathGate): def __init__(self, a, N): # pylint: disable=invalid-name """ - Initializes the gate to the number to multiply with modulo N. + Initialize the gate to the number to multiply with modulo N. Args: a (int): Number by which to multiply a quantum register @@ -207,26 +197,27 @@ def __init__(self, a, N): # pylint: disable=invalid-name 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,)) + super().__init__(lambda x: ((a * x) % N,)) self.a = a # pylint: disable=invalid-name self.N = N def __str__(self): + """Return a string representation of the object.""" return "MultiplyByConstantModN({}, {})".format(self.a, self.N) def __eq__(self, other): + """Equal operator.""" return isinstance(other, MultiplyByConstantModN) and self.a == other.a and self.N == other.N def __hash__(self): + """Compute the hash of the object.""" 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. + Add 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: @@ -243,21 +234,23 @@ class AddQuantumGate(BasicMathGate): """ def __init__(self): - BasicMathGate.__init__(self, None) + """Initialize an AddQuantumGate object.""" + super().__init__(None) def __str__(self): + """Return a string representation of the object.""" return "AddQuantum" def __eq__(self, other): + """Equal operator.""" return isinstance(other, AddQuantumGate) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - def get_math_function(self, qubits): + """Get the math function associated with an AddQuantumGate.""" n_qubits = len(qubits[0]) def math_fun(a): # pylint: disable=invalid-name @@ -273,10 +266,7 @@ def math_fun(a): # pylint: disable=invalid-name return math_fun 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 _InverseAddQuantumGate() @@ -284,15 +274,14 @@ def get_inverse(self): class _InverseAddQuantumGate(BasicMathGate): - """ - Internal gate glass to support emulation for inverse - addition. - """ + """Internal gate glass to support emulation for inverse addition.""" def __init__(self): - BasicMathGate.__init__(self, None) + """Initialize an _InverseAddQuantumGate object.""" + super().__init__(None) def __str__(self): + """Return a string representation of the object.""" return "_InverseAddQuantum" def get_math_function(self, qubits): @@ -309,8 +298,7 @@ def math_fun(a): # pylint: disable=invalid-name class SubtractQuantumGate(BasicMathGate): """ - Subtract one quantum number represented by a quantum register from - another quantum number represented by a quantum register. + Subtract one quantum number from another quantum number both represented by quantum registers. Example: .. code-block:: python @@ -326,6 +314,8 @@ class SubtractQuantumGate(BasicMathGate): def __init__(self): """ + Initialize a SubtractQuantumGate object. + Initializes the gate to its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ @@ -333,24 +323,22 @@ def __init__(self): def subtract(a, b): # pylint: disable=invalid-name return (a, b - a) - BasicMathGate.__init__(self, subtract) + super().__init__(subtract) def __str__(self): + """Return a string representation of the object.""" return "SubtractQuantum" def __eq__(self, other): + """Equal operator.""" return isinstance(other, SubtractQuantumGate) def __hash__(self): + """Compute the hash of the object.""" 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 the inverse gate (subtraction of the same number a modulo the same number N).""" return AddQuantum @@ -359,7 +347,8 @@ def get_inverse(self): class ComparatorQuantumGate(BasicMathGate): """ - Flips a compare qubit if the binary value of first imput is higher than the second input. + Flip 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 @@ -377,7 +366,9 @@ class ComparatorQuantumGate(BasicMathGate): def __init__(self): """ - Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated + Initilize a ComparatorQuantumGate object. + + Initialize the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ @@ -390,24 +381,22 @@ def compare(a, b, c): # pylint: disable=invalid-name c = 0 return (a, b, c) - BasicMathGate.__init__(self, compare) + super().__init__(compare) def __str__(self): + """Return a string representation of the object.""" return "Comparator" def __eq__(self, other): + """Equal operator.""" return isinstance(other, ComparatorQuantumGate) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - def get_inverse(self): - """ - Return the inverse gate - """ + """Return the inverse of this gate.""" return AddQuantum @@ -416,7 +405,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 + Divide 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. @@ -441,7 +432,9 @@ class DivideQuantumGate(BasicMathGate): def __init__(self): """ - Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated + Initialize a DivideQuantumGate object. + + Initialize the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ @@ -452,33 +445,34 @@ def division(dividend, remainder, divisor): quotient = remainder + dividend // divisor return ((dividend - (quotient * divisor)), quotient, divisor) - BasicMathGate.__init__(self, division) + super().__init__(division) def get_inverse(self): + """Return the inverse of this gate.""" return _InverseDivideQuantumGate() def __str__(self): + """Return a string representation of the object.""" return "DivideQuantum" def __eq__(self, other): + """Equal operator.""" return isinstance(other, DivideQuantumGate) def __hash__(self): + """Compute the hash of the object.""" 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. - """ + """Internal gate glass to support emulation for inverse division.""" def __init__(self): + """Initialize an _InverseDivideQuantumGate object.""" + def inverse_division(remainder, quotient, divisor): if divisor == 0: return (quotient, remainder, divisor) @@ -487,18 +481,20 @@ def inverse_division(remainder, quotient, divisor): remainder = 0 return (dividend, remainder, divisor) - BasicMathGate.__init__(self, inverse_division) + super().__init__(inverse_division) def __str__(self): + """Return a string representation of the object.""" 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. + Multiply 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 @@ -514,28 +510,31 @@ class MultiplyQuantumGate(BasicMathGate): def __init__(self): """ - Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated + Initialize a MultiplyQuantumGate object. + + Initialize the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ def multiply(a, b, c): # pylint: disable=invalid-name return (a, b, c + a * b) - BasicMathGate.__init__(self, multiply) + super().__init__(multiply) def __str__(self): + """Return a string representation of the object.""" return "MultiplyQuantum" def __eq__(self, other): + """Equal operator.""" return isinstance(other, MultiplyQuantumGate) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - def get_inverse(self): + """Return the inverse of this gate.""" return _InverseMultiplyQuantumGate() @@ -543,15 +542,16 @@ 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): + """Initialize an _InverseMultiplyQuantumGate object.""" + def inverse_multiplication(a, b, c): # pylint: disable=invalid-name return (a, b, c - a * b) - BasicMathGate.__init__(self, inverse_multiplication) + super().__init__(inverse_multiplication) def __str__(self): + """Return a string representation of the object.""" return "_InverseMultiplyQuantum" diff --git a/projectq/libs/math/_gates_math_test.py b/projectq/libs/math/_gates_math_test.py index 35b265c8e..5640e7426 100644 --- a/projectq/libs/math/_gates_math_test.py +++ b/projectq/libs/math/_gates_math_test.py @@ -16,29 +16,28 @@ import pytest +import projectq.libs.math +import projectq.setups.decompositions +from projectq.backends import CommandPrinter from projectq.cengines import ( - MainEngine, - TagRemover, AutoReplacer, - InstructionFilter, DecompositionRuleSet, + InstructionFilter, + MainEngine, + TagRemover, ) -from projectq.meta import Control, Compute, Uncompute -from projectq.ops import All, Measure, X, BasicMathGate, ClassicalInstructionGate -import projectq.setups.decompositions +from projectq.meta import Compute, Control, Uncompute +from projectq.ops import All, BasicMathGate, ClassicalInstructionGate, Measure, X -import projectq.libs.math from . import ( AddConstant, AddQuantum, - SubtractQuantum, ComparatorQuantum, DivideQuantum, MultiplyQuantum, + SubtractQuantum, ) -from projectq.backends import CommandPrinter - def print_all_probabilities(eng, qureg): i = 0 diff --git a/projectq/libs/math/_gates_test.py b/projectq/libs/math/_gates_test.py index 2bc50a816..aa78ebf8e 100755 --- a/projectq/libs/math/_gates_test.py +++ b/projectq/libs/math/_gates_test.py @@ -17,22 +17,22 @@ from projectq.libs.math import ( AddConstant, AddConstantModN, - MultiplyByConstantModN, - SubConstant, - SubConstantModN, AddQuantum, - SubtractQuantum, ComparatorQuantum, DivideQuantum, + MultiplyByConstantModN, MultiplyQuantum, + SubConstant, + SubConstantModN, + SubtractQuantum, ) from ._gates import ( AddQuantumGate, - SubtractQuantumGate, - MultiplyQuantumGate, - DivideQuantumGate, ComparatorQuantumGate, + DivideQuantumGate, + MultiplyQuantumGate, + SubtractQuantumGate, ) diff --git a/projectq/libs/math/_quantummath.py b/projectq/libs/math/_quantummath.py index bb86329fd..8b66fbb69 100644 --- a/projectq/libs/math/_quantummath.py +++ b/projectq/libs/math/_quantummath.py @@ -13,16 +13,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Definition of some mathematical quantum operations""" +"""Definition of some mathematical quantum operations.""" -from projectq.ops import All, X, CNOT from projectq.meta import Control +from projectq.ops import CNOT, All, X + from ._gates import AddQuantum, SubtractQuantum def add_quantum(eng, quint_a, quint_b, carry=None): """ - Adds two quantum integers, i.e., + Add 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)> @@ -82,7 +85,9 @@ def add_quantum(eng, quint_a, quint_b, carry=None): def subtract_quantum(eng, quint_a, quint_b): """ - Subtracts two quantum integers, i.e., + Subtract two quantum integers. + + i.e., |a>|b> -> |a>|b-a> @@ -140,7 +145,7 @@ def subtract_quantum(eng, quint_a, quint_b): def inverse_add_quantum_carry(eng, quint_a, quint_b): """ - Inverse of quantum addition with carry + Inverse of quantum addition with carry. Args: eng (MainEngine): ProjectQ MainEngine @@ -164,7 +169,9 @@ def inverse_add_quantum_carry(eng, quint_a, quint_b): def comparator(eng, quint_a, quint_b, comp): """ - Compares the size of two quantum integers, i.e, + Compare the size of two quantum integers. + + i.e, if a>b: |a>|b>|c> -> |a>|b>|c+1> @@ -227,7 +234,9 @@ def comparator(eng, quint_a, quint_b, comp): def quantum_conditional_add(eng, quint_a, quint_b, conditional): """ - Adds up two quantum integers if conditional is high, i.e., + Add up two quantum integers if conditional is high. + + i.e., |a>|b>|c> -> |a>|b+a>|c> (without a carry out qubit) @@ -285,7 +294,9 @@ def quantum_conditional_add(eng, quint_a, quint_b, conditional): def quantum_division(eng, dividend, remainder, divisor): """ - Performs restoring integer division, i.e., + Perform restoring integer division. + + i.e., |dividend>|remainder>|divisor> -> |remainder>|quotient>|divisor> @@ -340,7 +351,9 @@ def quantum_division(eng, dividend, remainder, divisor): def inverse_quantum_division(eng, remainder, quotient, divisor): """ - Performs the inverse of a restoring integer division, i.e., + Perform the inverse of a restoring integer division. + + i.e., |remainder>|quotient>|divisor> -> |dividend>|remainder(0)>|divisor> @@ -373,7 +386,9 @@ def inverse_quantum_division(eng, remainder, quotient, divisor): 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., + Add 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) @@ -448,7 +463,9 @@ def quantum_conditional_add_carry(eng, quint_a, quint_b, ctrl, z): # pylint: di def quantum_multiplication(eng, quint_a, quint_b, product): """ - Multiplies two quantum integers, i.e, + Multiplies two quantum integers. + + i.e, |a>|b>|0> -> |a>|b>|a*b> @@ -498,7 +515,9 @@ def quantum_multiplication(eng, quint_a, quint_b, product): def inverse_quantum_multiplication(eng, quint_a, quint_b, product): """ - Inverse of the multiplication of two quantum integers, i.e, + Inverse of the multiplication of two quantum integers. + + i.e, |a>|b>|a*b> -> |a>|b>|0> diff --git a/projectq/libs/math/_quantummath_test.py b/projectq/libs/math/_quantummath_test.py index c51fd81b6..bd520f668 100644 --- a/projectq/libs/math/_quantummath_test.py +++ b/projectq/libs/math/_quantummath_test.py @@ -15,23 +15,20 @@ import pytest +import projectq.libs.math 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.cengines import AutoReplacer, DecompositionRuleSet, InstructionFilter from projectq.libs.math import ( AddQuantum, - SubtractQuantum, ComparatorQuantum, DivideQuantum, MultiplyQuantum, + SubtractQuantum, ) - -from projectq.meta import Control, Compute, Uncompute, Dagger +from projectq.meta import Compute, Control, Dagger, Uncompute +from projectq.ops import All, BasicMathGate, ClassicalInstructionGate, Measure, X +from projectq.setups.decompositions import swap2cnot def print_all_probabilities(eng, qureg): diff --git a/projectq/libs/revkit/__init__.py b/projectq/libs/revkit/__init__.py index 8cbf8bc17..a411ab55d 100644 --- a/projectq/libs/revkit/__init__.py +++ b/projectq/libs/revkit/__init__.py @@ -15,6 +15,6 @@ """Module containing code to interface with RevKit""" -from ._permutation import PermutationOracle from ._control_function import ControlFunctionOracle +from ._permutation import PermutationOracle from ._phase import PhaseOracle diff --git a/projectq/libs/revkit/_control_function.py b/projectq/libs/revkit/_control_function.py index d80613cf8..341cb5f9f 100644 --- a/projectq/libs/revkit/_control_function.py +++ b/projectq/libs/revkit/_control_function.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""RevKit support for control function oracles""" +"""RevKit support for control function oracles.""" from projectq.ops import BasicGate @@ -23,7 +23,7 @@ class ControlFunctionOracle: # pylint: disable=too-few-public-methods """ - Synthesizes a negation controlled by an arbitrary control function. + Synthesize a negation controlled by an arbitrary control function. This creates a circuit for a NOT gate which is controlled by an arbitrary Boolean control function. The control function is provided as integer @@ -32,7 +32,6 @@ class ControlFunctionOracle: # pylint: disable=too-few-public-methods the value for function can be, e.g., ``0b11101000``, ``0xe8``, or ``232``. Example: - This example creates a circuit that causes to invert qubit ``d``, the majority-of-three function evaluates to true for the control qubits ``a``, ``b``, and ``c``. @@ -44,7 +43,7 @@ class ControlFunctionOracle: # pylint: disable=too-few-public-methods def __init__(self, function, **kwargs): """ - Initializes a control function oracle. + Initialize a control function oracle. Args: function (int): Function truth table. @@ -75,7 +74,7 @@ def __init__(self, function, **kwargs): def __or__(self, qubits): """ - Applies control function to qubits (and synthesizes circuit). + Apply control function to qubits (and synthesizes circuit). Args: qubits (tuple): Qubits to which the control function is @@ -117,9 +116,7 @@ def __or__(self, qubits): _exec(revkit.to_projectq(mct=True), qs) def _check_function(self): - """ - Checks whether function is valid. - """ + """Check whether function is valid.""" # function must be positive. We check in __or__ whether function is # too large if self.function < 0: diff --git a/projectq/libs/revkit/_control_function_test.py b/projectq/libs/revkit/_control_function_test.py index 9c547a583..3ea58c58a 100644 --- a/projectq/libs/revkit/_control_function_test.py +++ b/projectq/libs/revkit/_control_function_test.py @@ -18,7 +18,6 @@ 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 diff --git a/projectq/libs/revkit/_permutation.py b/projectq/libs/revkit/_permutation.py index 71384f828..b5bc522b1 100644 --- a/projectq/libs/revkit/_permutation.py +++ b/projectq/libs/revkit/_permutation.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""RevKit support for permutation oracles""" +"""RevKit support for permutation oracles.""" from projectq.ops import BasicGate @@ -22,7 +22,7 @@ class PermutationOracle: # pylint: disable=too-few-public-methods """ - Synthesizes a permutation using RevKit. + Synthesize a permutation using RevKit. Given a permutation over `2**q` elements (starting from 0), this class helps to automatically find a reversible circuit over `q` qubits that @@ -36,16 +36,15 @@ class PermutationOracle: # pylint: disable=too-few-public-methods def __init__(self, permutation, **kwargs): """ - Initializes a permutation oracle. + Initialize a permutation oracle. Args: permutation (list): Permutation (starting from 0). Keyword Args: - synth: A RevKit synthesis command which creates a reversible - circuit based on a reversible truth table (e.g., - ``revkit.tbs`` or ``revkit.dbs``). Can also be a - nullary lambda that calls several RevKit commands. + synth: A RevKit synthesis command which creates a reversible circuit based on a reversible truth table + (e.g., ``revkit.tbs`` or ``revkit.dbs``). Can also be a nullary lambda that calls several RevKit + commands. **Default:** ``revkit.tbs`` """ self.permutation = permutation @@ -55,11 +54,10 @@ def __init__(self, permutation, **kwargs): def __or__(self, qubits): """ - Applies permutation to qubits (and synthesizes circuit). + Apply permutation to qubits (and synthesizes circuit). Args: - qubits (tuple): Qubits to which the permutation is being - applied. + qubits (tuple): Qubits to which the permutation is being applied. """ try: import revkit # pylint: disable=import-outside-toplevel @@ -88,10 +86,8 @@ def __or__(self, qubits): _exec(revkit.to_projectq(mct=True), qs) def _check_permutation(self): - """ - Checks whether permutation is valid. - """ + """Check whether permutation is valid.""" # 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(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 57c92721a..06c9e0d33 100644 --- a/projectq/libs/revkit/_permutation_test.py +++ b/projectq/libs/revkit/_permutation_test.py @@ -18,7 +18,6 @@ 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 diff --git a/projectq/libs/revkit/_phase.py b/projectq/libs/revkit/_phase.py index d50539d92..edfc0afef 100644 --- a/projectq/libs/revkit/_phase.py +++ b/projectq/libs/revkit/_phase.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""RevKit support for phase oracles""" +"""RevKit support for phase oracles.""" from projectq.ops import BasicGate @@ -22,23 +22,19 @@ class PhaseOracle: # pylint: disable=too-few-public-methods """ - Synthesizes phase circuit from an arbitrary Boolean function. + Synthesize phase circuit from an arbitrary Boolean function. - This creates a phase circuit from a Boolean function. It inverts the phase - of all amplitudes for which the function evaluates to 1. The Boolean - function is provided as integer representation of the function's truth - table in binary notation. For example, for the majority-of-three function, - which truth table 11101000, the value for function can be, e.g., - ``0b11101000``, ``0xe8``, or ``232``. + This creates a phase circuit from a Boolean function. It inverts the phase of all amplitudes for which the + function evaluates to 1. The Boolean function is provided as integer representation of the function's truth table + in binary notation. For example, for the majority-of-three function, which truth table 11101000, the value for + function can be, e.g., ``0b11101000``, ``0xe8``, or ``232``. - Note that a phase circuit can only accurately be found for a normal - function, i.e., a function that maps the input pattern 0, 0, ..., 0 to 0. - The circuits for a function and its inverse are the same. + Note that a phase circuit can only accurately be found for a normal function, i.e., a function that maps the input + pattern 0, 0, ..., 0 to 0. The circuits for a function and its inverse are the same. Example: - - This example creates a phase circuit based on the majority-of-three - function on qubits ``a``, ``b``, and ``c``. + This example creates a phase circuit based on the majority-of-three function on qubits ``a``, ``b``, and + ``c``. .. code-block:: python @@ -47,16 +43,15 @@ class PhaseOracle: # pylint: disable=too-few-public-methods def __init__(self, function, **kwargs): """ - Initializes a phase oracle. + Initialize a phase oracle. Args: function (int): Function truth table. Keyword Args: - synth: A RevKit synthesis command which creates a reversible - circuit based on a truth table and requires no additional - ancillae (e.g., ``revkit.esopps``). Can also be a nullary - lambda that calls several RevKit commands. + synth: A RevKit synthesis command which creates a reversible circuit based on a truth table and requires + no additional ancillae (e.g., ``revkit.esopps``). Can also be a nullary lambda that calls several + RevKit commands. **Default:** ``revkit.esopps`` """ if isinstance(function, int): @@ -78,11 +73,10 @@ def __init__(self, function, **kwargs): def __or__(self, qubits): """ - Applies phase circuit to qubits (and synthesizes circuit). + Apply phase circuit to qubits (and synthesizes circuit). Args: - qubits (tuple): Qubits to which the phase circuit is being - applied. + qubits (tuple): Qubits to which the phase circuit is being applied. """ try: import revkit # pylint: disable=import-outside-toplevel @@ -118,9 +112,7 @@ def __or__(self, qubits): _exec(revkit.to_projectq(mct=True), qs) def _check_function(self): - """ - Checks whether function is valid. - """ + """Check whether function is valid.""" # function must be positive. We check in __or__ whether function is # too large if self.function < 0: diff --git a/projectq/libs/revkit/_phase_test.py b/projectq/libs/revkit/_phase_test.py index aee988d11..5c06a9162 100644 --- a/projectq/libs/revkit/_phase_test.py +++ b/projectq/libs/revkit/_phase_test.py @@ -14,16 +14,14 @@ # limitations under the License. """Tests for libs.revkit._phase.""" +import numpy as np import pytest from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import DummyEngine -from projectq.ops import All, H, Measure - from projectq.libs.revkit import PhaseOracle - -import numpy as np +from projectq.ops import All, H, Measure # run this test only if RevKit Python module can be loaded revkit = pytest.importorskip('revkit') diff --git a/projectq/libs/revkit/_utils.py b/projectq/libs/revkit/_utils.py index 573451bf6..7fd44ca9a 100644 --- a/projectq/libs/revkit/_utils.py +++ b/projectq/libs/revkit/_utils.py @@ -28,6 +28,6 @@ def _exec(code, qs): code (string): ProjectQ code. qubits (tuple): Qubits to which the permutation is being applied. """ - from projectq.ops import C, X, Z, All + from projectq.ops import All, C, X, Z exec(code) diff --git a/projectq/meta/__init__.py b/projectq/meta/__init__.py index 27c20b835..634b046c7 100755 --- a/projectq/meta/__init__.py +++ b/projectq/meta/__init__.py @@ -22,10 +22,15 @@ * 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, has_negative_control, canonical_ctrl_state +from ._compute import Compute, ComputeTag, CustomUncompute, Uncompute, UncomputeTag +from ._control import ( + Control, + canonical_ctrl_state, + get_control_count, + has_negative_control, +) from ._dagger import Dagger -from ._util import insert_engine, drop_engine_after +from ._dirtyqubit import DirtyQubitTag from ._logicalqubit import LogicalQubitIDTag +from ._loop import Loop, LoopTag +from ._util import drop_engine_after, insert_engine diff --git a/projectq/meta/_compute.py b/projectq/meta/_compute.py index f09eb8514..dcf7932ab 100755 --- a/projectq/meta/_compute.py +++ b/projectq/meta/_compute.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Compute, Uncompute, CustomUncompute. +Definition of Compute, Uncompute and 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 @@ -25,42 +25,29 @@ from projectq.cengines import BasicEngine, CommandModifier from projectq.ops import Allocate, Deallocate -from ._util import insert_engine, drop_engine_after - - -class QubitManagementError(Exception): - """Exception raised when the lifetime of a qubit is problematic within a loop""" +from ._exceptions import QubitManagementError +from ._util import drop_engine_after, insert_engine class NoComputeSectionError(Exception): - """ - Exception raised if uncompute is called but no compute section found. - """ + """Exception raised if uncompute is called but no compute section found.""" -class ComputeTag: - """ - Compute meta tag. - """ +class ComputeTag: # pylint: disable=too-few-public-methods + """Compute meta tag.""" def __eq__(self, other): + """Equal operator.""" return isinstance(other, ComputeTag) - def __ne__(self, other): - return not self.__eq__(other) - -class UncomputeTag: - """ - Uncompute meta tag. - """ +class UncomputeTag: # pylint: disable=too-few-public-methods + """Uncompute meta tag.""" def __eq__(self, other): + """Equal operator.""" return isinstance(other, UncomputeTag) - def __ne__(self, other): - return not self.__eq__(other) - def _add_uncompute_tag(cmd): """ @@ -74,16 +61,11 @@ def _add_uncompute_tag(cmd): class ComputeEngine(BasicEngine): - """ - Adds Compute-tags to all commands and stores them (to later uncompute them - automatically) - """ + """Add Compute-tags to all commands and stores them (to later uncompute them automatically).""" def __init__(self): - """ - Initialize a ComputeEngine. - """ - BasicEngine.__init__(self) + """Initialize a ComputeEngine.""" + super().__init__() self._l = [] self._compute = True # Save all qubit ids from qubits which are created or destroyed. @@ -99,7 +81,6 @@ def run_uncompute(self): # pylint: disable=too-many-branches,too-many-statement 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([_add_uncompute_tag(cmd.get_inverse()) for cmd in reversed(self._l)]) @@ -133,7 +114,7 @@ def run_uncompute(self): # pylint: disable=too-many-branches,too-many-statement return # There was at least one qubit allocated and deallocated within # compute section. Handle uncompute in most general case - new_local_id = dict() + new_local_id = {} for cmd in reversed(self._l): if cmd.gate == Deallocate: if not cmd.qubits[0][0].id in ids_local_to_compute: # pragma: no cover @@ -217,6 +198,8 @@ def end_compute(self): def receive(self, command_list): """ + Receive a list of commands. + 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. @@ -238,21 +221,19 @@ def receive(self, command_list): class UncomputeEngine(BasicEngine): - """ - Adds Uncompute-tags to all commands. - """ + """Adds Uncompute-tags to all commands.""" def __init__(self): - """ - Initialize a UncomputeEngine. - """ - BasicEngine.__init__(self) + """Initialize a UncomputeEngine.""" + super().__init__() # Save all qubit ids from qubits which are created or destroyed. self._allocated_qubit_ids = set() self._deallocated_qubit_ids = set() def receive(self, command_list): """ + Receive a list of commands. + Receive commands and add an UncomputeTag to their tags. Args: @@ -328,10 +309,12 @@ def __init__(self, engine): self._compute_eng = None def __enter__(self): + """Context manager enter function.""" self._compute_eng = ComputeEngine() insert_engine(self.engine, self._compute_eng) def __exit__(self, exc_type, exc_value, exc_traceback): + """Context manager exit function.""" # notify ComputeEngine that the compute section is done self._compute_eng.end_compute() self._compute_eng = None @@ -369,6 +352,7 @@ def __init__(self, engine): self._uncompute_eng = None def __enter__(self): + """Context manager enter function.""" # first, remove the compute engine compute_eng = self.engine.next_engine if not isinstance(compute_eng, ComputeEngine): @@ -386,6 +370,7 @@ def __enter__(self): insert_engine(self.engine, self._uncompute_eng) def __exit__(self, exc_type, exc_value, exc_traceback): + """Context manager exit function.""" # 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. diff --git a/projectq/meta/_compute_test.py b/projectq/meta/_compute_test.py index 66eec753d..1a87d413f 100755 --- a/projectq/meta/_compute_test.py +++ b/projectq/meta/_compute_test.py @@ -14,16 +14,15 @@ # limitations under the License. """Tests for projectq.meta._compute.py""" -import pytest import types import weakref -from projectq import MainEngine -from projectq.cengines import DummyEngine, CompareEngine -from projectq.ops import H, Rx, Ry, Deallocate, Allocate, CNOT, NOT, FlushGate -from projectq.meta import DirtyQubitTag +import pytest -from projectq.meta import _compute +from projectq import MainEngine +from projectq.cengines import CompareEngine, DummyEngine +from projectq.meta import DirtyQubitTag, _compute +from projectq.ops import CNOT, NOT, Allocate, Deallocate, FlushGate, H, Rx, Ry def test_compute_tag(): diff --git a/projectq/meta/_control.py b/projectq/meta/_control.py index 97a25af90..9fa0803d8 100755 --- a/projectq/meta/_control.py +++ b/projectq/meta/_control.py @@ -28,12 +28,12 @@ from projectq.types import BasicQubit from ._compute import ComputeTag, UncomputeTag -from ._util import insert_engine, drop_engine_after +from ._util import drop_engine_after, insert_engine def canonical_ctrl_state(ctrl_state, num_qubits): """ - Return canonical form for control state + Return canonical form for control state. Args: ctrl_state (int,str,CtrlAll): Initial control state representation @@ -100,9 +100,7 @@ def _has_compute_uncompute_tag(cmd): class ControlEngine(BasicEngine): - """ - Adds control qubits to all commands that have no compute / uncompute tags. - """ + """Add control qubits to all commands that have no compute / uncompute tags.""" def __init__(self, qubits, ctrl_state=CtrlAll.One): """ @@ -112,7 +110,7 @@ def __init__(self, qubits, ctrl_state=CtrlAll.One): qubits (list of Qubit objects): qubits conditional on which the following operations are executed. """ - BasicEngine.__init__(self) + super().__init__() self._qubits = qubits self._state = ctrl_state @@ -122,7 +120,7 @@ def _handle_command(self, cmd): self.send([cmd]) def receive(self, command_list): - """Forward all commands to the next engine.""" + """Receive a list of commands.""" for cmd in command_list: self._handle_command(cmd) @@ -162,25 +160,23 @@ def __init__(self, engine, qubits, ctrl_state=CtrlAll.One): self._state = canonical_ctrl_state(ctrl_state, len(self._qubits)) def __enter__(self): + """Context manager enter function.""" if len(self._qubits) > 0: engine = ControlEngine(self._qubits, self._state) insert_engine(self.engine, engine) def __exit__(self, exc_type, exc_value, exc_traceback): + """Context manager exit function.""" # remove control handler from engine list (i.e. skip it) if len(self._qubits) > 0: drop_engine_after(self.engine) def get_control_count(cmd): - """ - Return the number of control qubits of the command object 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 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 eadadad34..5e3618cfb 100755 --- a/projectq/meta/_control_test.py +++ b/projectq/meta/_control_test.py @@ -17,10 +17,15 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -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.meta import ( + Compute, + ComputeTag, + DirtyQubitTag, + Uncompute, + UncomputeTag, + _control, +) +from projectq.ops import Command, CtrlAll, H, IncompatibleControlState, Rx, X from projectq.types import WeakQubitRef diff --git a/projectq/meta/_dagger.py b/projectq/meta/_dagger.py index 86d28857c..36ead9f48 100755 --- a/projectq/meta/_dagger.py +++ b/projectq/meta/_dagger.py @@ -24,29 +24,23 @@ from projectq.cengines import BasicEngine from projectq.ops import Allocate, Deallocate -from ._util import insert_engine, drop_engine_after - -class QubitManagementError(Exception): - """Exception raised when the lifetime of a qubit is problematic within a loop""" +from ._exceptions import QubitManagementError +from ._util import drop_engine_after, insert_engine class DaggerEngine(BasicEngine): - """ - Stores all commands and, when done, inverts the circuit & runs it. - """ + """Store all commands and, when done, inverts the circuit & runs it.""" def __init__(self): - BasicEngine.__init__(self) + """Initialize a DaggerEngine object.""" + super().__init__() self._commands = [] self._allocated_qubit_ids = set() self._deallocated_qubit_ids = set() def run(self): - """ - Run the stored circuit in reverse and check that local qubits - have been deallocated. - """ + """Run the stored circuit in reverse and check that local qubits have been deallocated.""" if self._deallocated_qubit_ids != self._allocated_qubit_ids: raise QubitManagementError( "\n Error. Qubits have been allocated in 'with " @@ -129,10 +123,12 @@ def __init__(self, engine): self._dagger_eng = None def __enter__(self): + """Context manager enter function.""" self._dagger_eng = DaggerEngine() insert_engine(self.engine, self._dagger_eng) def __exit__(self, exc_type, exc_value, exc_traceback): + """Context manager exit function.""" # 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. diff --git a/projectq/meta/_dagger_test.py b/projectq/meta/_dagger_test.py index a91e51cb4..a95beeda1 100755 --- a/projectq/meta/_dagger_test.py +++ b/projectq/meta/_dagger_test.py @@ -14,15 +14,14 @@ # limitations under the License. """Tests for projectq.meta._dagger.py""" -import pytest import types +import pytest + from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import CNOT, X, Rx, H, Allocate, Deallocate -from projectq.meta import DirtyQubitTag - -from projectq.meta import _dagger +from projectq.meta import DirtyQubitTag, _dagger +from projectq.ops import CNOT, Allocate, Deallocate, H, Rx, X def test_dagger_with_dirty_qubits(): diff --git a/projectq/meta/_dirtyqubit.py b/projectq/meta/_dirtyqubit.py index a26cc574a..3e9ba9833 100755 --- a/projectq/meta/_dirtyqubit.py +++ b/projectq/meta/_dirtyqubit.py @@ -12,18 +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. -""" -Defines the DirtyQubitTag meta tag. -""" +"""Define the DirtyQubitTag meta tag.""" -class DirtyQubitTag: - """ - Dirty qubit meta tag - """ + +class DirtyQubitTag: # pylint: disable=too-few-public-methods + """Dirty qubit meta tag.""" def __eq__(self, other): + """Equal operator.""" return isinstance(other, DirtyQubitTag) - - def __ne__(self, other): - return not self.__eq__(other) diff --git a/projectq/meta/_dirtyqubit_test.py b/projectq/meta/_dirtyqubit_test.py index 3cfe437a5..74e4d94f3 100755 --- a/projectq/meta/_dirtyqubit_test.py +++ b/projectq/meta/_dirtyqubit_test.py @@ -14,9 +14,7 @@ # limitations under the License. """Tests for projectq.meta._dirtyqubit.py""" -from projectq.meta import ComputeTag - -from projectq.meta import _dirtyqubit +from projectq.meta import ComputeTag, _dirtyqubit def test_dirty_qubit_tag(): diff --git a/projectq/meta/_exceptions.py b/projectq/meta/_exceptions.py new file mode 100644 index 000000000..2f12ff361 --- /dev/null +++ b/projectq/meta/_exceptions.py @@ -0,0 +1,24 @@ +# -*- 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. + +"""Exception classes for projectq.meta.""" + + +class QubitManagementError(Exception): + """ + Exception raised when the lifetime of a qubit is problematic within a context manager. + + This may occur within Loop, Dagger or Compute regions. + """ diff --git a/projectq/meta/_logicalqubit.py b/projectq/meta/_logicalqubit.py index 218456bae..2c6295235 100644 --- a/projectq/meta/_logicalqubit.py +++ b/projectq/meta/_logicalqubit.py @@ -12,12 +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. -""" -Defines LogicalQubitIDTag to annotate a MeasureGate for mapped qubits. -""" +"""Definition of LogicalQubitIDTag to annotate a MeasureGate for mapped qubits.""" -class LogicalQubitIDTag: + +class LogicalQubitIDTag: # pylint: disable=too-few-public-methods """ LogicalQubitIDTag for a mapped qubit to annotate a MeasureGate. @@ -26,10 +25,9 @@ class LogicalQubitIDTag: """ def __init__(self, logical_qubit_id): + """Initialize a LogicalQubitIDTag object.""" self.logical_qubit_id = logical_qubit_id def __eq__(self, other): + """Equal operator.""" 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/_loop.py b/projectq/meta/_loop.py index f563bb2f0..cc64034ca 100755 --- a/projectq/meta/_loop.py +++ b/projectq/meta/_loop.py @@ -27,37 +27,33 @@ from projectq.cengines import BasicEngine from projectq.ops import Allocate, Deallocate -from ._util import insert_engine, drop_engine_after +from ._exceptions import QubitManagementError +from ._util import drop_engine_after, insert_engine -class QubitManagementError(Exception): - """Exception raised when the lifetime of a qubit is problematic within a loop""" - -class LoopTag: - """ - Loop meta tag - """ +class LoopTag: # pylint: disable=too-few-public-methods + """Loop meta tag.""" def __init__(self, num): + """Initialize a LoopTag object.""" self.num = num self.id = LoopTag.loop_tag_id LoopTag.loop_tag_id += 1 def __eq__(self, other): + """Equal operator.""" return isinstance(other, LoopTag) and self.id == other.id and self.num == other.num - def __ne__(self, other): - return not self.__eq__(other) - loop_tag_id = 0 class LoopEngine(BasicEngine): """ - Stores all commands and, when done, executes them num times if no loop tag - handler engine is available. - If there is one, it adds a loop_tag to the commands and sends them on. + A compiler engine to represent executing part of the code multiple times. + + Stores all commands and, when done, executes them num times if no loop tag handler engine is available. If there + is one, it adds a loop_tag to the commands and sends them on. """ def __init__(self, num): @@ -67,7 +63,7 @@ def __init__(self, num): Args: num (int): Number of loop iterations. """ - BasicEngine.__init__(self) + super().__init__() self._tag = LoopTag(num) self._cmd_list = [] self._allocated_qubit_ids = set() @@ -76,7 +72,7 @@ def __init__(self, num): # and deallocated within the loop body. # value: list contain reference to each weakref qubit with this qubit # id either within control_qubits or qubits. - self._refs_to_local_qb = dict() + self._refs_to_local_qb = {} self._next_engines_support_loop_tag = False def run(self): @@ -245,11 +241,13 @@ def __init__(self, engine, num): self._loop_eng = None def __enter__(self): + """Context manager enter function.""" if self.num != 1: self._loop_eng = LoopEngine(self.num) insert_engine(self.engine, self._loop_eng) def __exit__(self, exc_type, exc_value, exc_traceback): + """Context manager exit function.""" if self.num != 1: # remove loop handler from engine list (i.e. skip it) self._loop_eng.run() diff --git a/projectq/meta/_loop_test.py b/projectq/meta/_loop_test.py index 3785f5891..618c5f16a 100755 --- a/projectq/meta/_loop_test.py +++ b/projectq/meta/_loop_test.py @@ -14,16 +14,15 @@ # limitations under the License. """Tests for projectq.meta._loop.py""" -import pytest import types - from copy import deepcopy + +import pytest + from projectq import MainEngine -from projectq.meta import ComputeTag from projectq.cengines import DummyEngine -from projectq.ops import H, CNOT, X, FlushGate, Allocate, Deallocate - -from projectq.meta import _loop +from projectq.meta import ComputeTag, _loop +from projectq.ops import CNOT, Allocate, Deallocate, FlushGate, H, X def test_loop_tag(): diff --git a/projectq/meta/_util.py b/projectq/meta/_util.py index 4a5300eb2..414930f50 100755 --- a/projectq/meta/_util.py +++ b/projectq/meta/_util.py @@ -13,22 +13,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -Tools to add/remove compiler engines to the MainEngine list -""" +"""Tools to add/remove compiler engines to the MainEngine list.""" def insert_engine(prev_engine, engine_to_insert): """ - Inserts an engine into the singly-linked list of engines. + Insert an engine into the singly-linked list of engines. It also sets the correct main_engine for engine_to_insert. Args: - prev_engine (projectq.cengines.BasicEngine): - The engine just before the insertion point. - engine_to_insert (projectq.cengines.BasicEngine): - The engine to insert at the insertion point. + prev_engine (projectq.cengines.BasicEngine): The engine just before the insertion point. + engine_to_insert (projectq.cengines.BasicEngine): The engine to insert at the insertion point. """ if prev_engine.main_engine is not None: prev_engine.main_engine.n_engines += 1 @@ -43,11 +39,11 @@ def insert_engine(prev_engine, engine_to_insert): def drop_engine_after(prev_engine): """ - Removes an engine from the singly-linked list of engines. + Remove an engine from the singly-linked list of engines. Args: - prev_engine (projectq.cengines.BasicEngine): - The engine just before the engine to drop. + prev_engine (projectq.cengines.BasicEngine): The engine just before the engine to drop. + Returns: Engine: The dropped engine. """ diff --git a/projectq/meta/_util_test.py b/projectq/meta/_util_test.py index a010d1b81..90b781b53 100755 --- a/projectq/meta/_util_test.py +++ b/projectq/meta/_util_test.py @@ -18,7 +18,6 @@ from projectq import MainEngine from projectq.cengines import DummyEngine - from . import _util diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 81b3313ac..67b570263 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -16,25 +16,33 @@ """ProjectQ module containing all basic gates (operations)""" from ._basics import ( - NotMergeable, - NotInvertible, BasicGate, - MatrixGate, - SelfInverseGate, + BasicMathGate, + BasicPhaseGate, BasicRotationGate, ClassicalInstructionGate, FastForwardingGate, - BasicMathGate, - BasicPhaseGate, + MatrixGate, + NotInvertible, + NotMergeable, + SelfInverseGate, ) -from ._command import apply_command, Command, CtrlAll, IncompatibleControlState -from ._metagates import DaggeredGate, get_inverse, is_identity, ControlledGate, C, Tensor, All +from ._command import Command, CtrlAll, IncompatibleControlState, apply_command from ._gates import * +from ._metagates import ( + All, + C, + ControlledGate, + DaggeredGate, + Tensor, + get_inverse, + is_identity, +) +from ._qaagate import QAA from ._qftgate import QFT, QFTGate +from ._qpegate import QPE from ._qubit_operator import QubitOperator from ._shortcuts import * +from ._state_prep import StatePreparation from ._time_evolution import TimeEvolution 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 6c9943c78..6cff8c18d 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -13,9 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Defines the BasicGate class, the base class of all gates, the -BasicRotationGate class, the SelfInverseGate, the FastForwardingGate, the -ClassicalInstruction gate, and the BasicMathGate class. +Definitions of some of the most basic quantum gates. + +Defines the BasicGate class, the base class of all gates, the BasicRotationGate class, the SelfInverseGate, the +FastForwardingGate, the ClassicalInstruction gate, and the BasicMathGate class. Gates overload the | operator to allow the following syntax: @@ -27,19 +28,19 @@ Gate | qubit Gate | (qubit,) -This means that for more than one quantum argument (right side of | ), a tuple -needs to be made explicitely, while for one argument it is optional. +This means that for more than one quantum argument (right side of | ), a tuple needs to be made explicitely, while for +one argument it is optional. """ -from copy import deepcopy import math import unicodedata +from copy import deepcopy import numpy as np from projectq.types import BasicQubit -from ._command import Command, apply_command +from ._command import Command, apply_command ANGLE_PRECISION = 12 ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION @@ -49,21 +50,22 @@ 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. + + This exception is also thrown if the merging is not implemented (yet)). """ 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. + + This exception is also thrown if the inverse is not implemented (yet). """ class BasicGate: - """ - Base class of all gates. (Don't use it directly but derive from it) - """ + """Base class of all gates. (Don't use it directly but derive from it).""" def __init__(self): """ @@ -164,8 +166,9 @@ def make_tuple_of_qureg(qubits): def generate_command(self, qubits): """ - Helper function to generate a command consisting of the gate and - the qubits being acted upon. + Generate a command. + + The command object created consists of the gate and the qubits being acted upon. Args: qubits: see BasicGate.make_tuple_of_qureg(qubits) @@ -201,7 +204,7 @@ def __or__(self, qubits): def __eq__(self, other): """ - Equality comparision + Equal operator. 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. @@ -212,40 +215,36 @@ def __eq__(self, other): return NotImplemented return False - def __ne__(self, other): - return not self.__eq__(other) - def __str__(self): + """Return a string representation of the object.""" raise NotImplementedError('This gate does not implement __str__.') def to_string(self, symbols): # pylint: disable=unused-argument """ - String representation + Return a string representation of the object. Achieve same function as str() but can be extended for configurable representation """ return str(self) def __hash__(self): + """Compute the hash of the object.""" return hash(str(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 True if the gate is an identity gate. In this base class, always returns False.""" return False class MatrixGate(BasicGate): """ - Defines a gate class whose instances are defined by a matrix. + A gate class whose instances are defined by a matrix. Note: Use this gate class only for gates acting on a small numbers of qubits. In general, consider instead using one of the provided ProjectQ gates or define a new class as this allows the compiler to work symbolically. Example: - .. code-block:: python gate = MatrixGate([[0, 1], [1, 0]]) @@ -254,31 +253,27 @@ class MatrixGate(BasicGate): def __init__(self, matrix=None): """ - Initialize MatrixGate + Initialize a MatrixGate object. Args: matrix(numpy.matrix): matrix which defines the gate. Default: None """ - BasicGate.__init__(self) + super().__init__() self._matrix = np.matrix(matrix) if matrix is not None else None @property def matrix(self): - """ - Access to the matrix property of this gate. - """ + """Access to the matrix property of this gate.""" return self._matrix @matrix.setter def matrix(self, matrix): - """ - Set the matrix property of this gate. - """ + """Set the matrix property of this gate.""" self._matrix = np.matrix(matrix) def __eq__(self, other): """ - Equality comparision + Equal operator. Return True only if both gates have a matrix respresentation and the matrices are (approximately) equal. Otherwise return False. @@ -294,12 +289,15 @@ def __eq__(self, other): return False def __str__(self): + """Return a string representation of the object.""" return "MatrixGate(" + str(self.matrix.tolist()) + ")" def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) def get_inverse(self): + """Return the inverse of this gate.""" return MatrixGate(np.linalg.inv(self.matrix)) @@ -307,7 +305,7 @@ 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 @@ -317,12 +315,13 @@ class SelfInverseGate(BasicGate): # pylint: disable=abstract-method """ def get_inverse(self): + """Return the inverse of this gate.""" return deepcopy(self) class BasicRotationGate(BasicGate): """ - Defines a base class of a rotation gate. + Base class of for all rotation gates. 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 @@ -336,7 +335,7 @@ def __init__(self, angle): Args: angle (float): Angle of rotation (saved modulo 4 * pi) """ - BasicGate.__init__(self) + super().__init__() rounded_angle = round(float(angle) % (4.0 * math.pi), ANGLE_PRECISION) if rounded_angle > 4 * math.pi - ANGLE_TOLERANCE: rounded_angle = 0.0 @@ -381,10 +380,7 @@ def tex_str(self): return str(self.__class__.__name__) + "$_{" + str(round(self.angle / math.pi, 3)) + "\\pi}$" 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) return self.__class__(-self.angle + 4 * math.pi) @@ -414,22 +410,18 @@ def __eq__(self, other): return self.angle == other.angle return False - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) def is_identity(self): - """ - Return True if the gate is equivalent to an Identity gate - """ + """Return True if the gate is equivalent to an Identity gate.""" return self.angle == 0.0 or self.angle == 4 * math.pi class BasicPhaseGate(BasicGate): """ - Defines a base class of a phase gate. + Base class for all phase gates. 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 @@ -443,7 +435,7 @@ def __init__(self, angle): Args: angle (float): Angle of rotation (saved modulo 2 * pi) """ - BasicGate.__init__(self) + super().__init__() rounded_angle = round(float(angle) % (2.0 * math.pi), ANGLE_PRECISION) if rounded_angle > 2 * math.pi - ANGLE_TOLERANCE: rounded_angle = 0.0 @@ -474,9 +466,7 @@ def tex_str(self): return str(self.__class__.__name__) + "$_{" + str(self.angle) + "}$" 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) return self.__class__(-self.angle + 2 * math.pi) @@ -507,10 +497,8 @@ def __eq__(self, other): return self.angle == other.angle return False - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) @@ -526,6 +514,8 @@ class ClassicalInstructionGate(BasicGate): # pylint: disable=abstract-method class FastForwardingGate(ClassicalInstructionGate): # pylint: disable=abstract-method """ + Base class for fast-forward gates. + 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. @@ -581,7 +571,7 @@ def __init__(self, math_fun): def add(a,b): return (a,a+b) - BasicMathGate.__init__(self, add) + super().__init__(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 @@ -603,7 +593,7 @@ def math_fun(a): return math_fun """ - BasicGate.__init__(self) + super().__init__() def math_function(arg): return list(math_fun(*arg)) @@ -611,10 +601,13 @@ def math_function(arg): self._math_function = math_function def __str__(self): + """Return a string representation of the object.""" return "MATH" def get_math_function(self, qubits): # pylint: disable=unused-argument """ + Get the math function associated with a BasicMathGate. + Return the math function which corresponds to the action of this math gate, given the input to the gate (a tuple of quantum registers). diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 7fa40643b..af3ab34b2 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -19,13 +19,10 @@ import numpy as np import pytest -from projectq.types import Qubit, Qureg -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 +from projectq.ops import Command, X, _basics +from projectq.types import Qubit, Qureg, WeakQubitRef @pytest.fixture @@ -314,7 +311,7 @@ def my_math_function(a, b, c): class MyMultiplyGate(_basics.BasicMathGate): def __init__(self): - _basics.BasicMathGate.__init__(self, my_math_function) + super().__init__(my_math_function) gate = MyMultiplyGate() assert str(gate) == 'MATH' diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 621bee320..c67de0683 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -This file defines the apply_command function and the Command class. +The apply_command function and the Command class. When a gate is applied to qubits, e.g., @@ -37,22 +37,20 @@ apply wrapper (apply_command). """ +import itertools from copy import deepcopy from enum import IntEnum -import itertools import projectq -from projectq.types import WeakQubitRef, Qureg +from projectq.types import Qureg, WeakQubitRef class IncompatibleControlState(Exception): - """ - Exception thrown when trying to set two incompatible states for a control qubit. - """ + """Exception thrown when trying to set two incompatible states for a control qubit.""" class CtrlAll(IntEnum): - """Enum type to initialise the control state of qubits""" + """Enum type to initialise the control state of qubits.""" Zero = 0 One = 1 @@ -73,9 +71,11 @@ def apply_command(cmd): 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 @@ -83,10 +83,10 @@ class Command: # pylint: disable=too-many-instance-attributes 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. + 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 """ @@ -97,8 +97,8 @@ def __init__( 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 + 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: @@ -109,7 +109,6 @@ def __init__( 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) self.gate = gate @@ -121,12 +120,12 @@ def __init__( @property def qubits(self): - """Qubits stored in a Command object""" + """Qubits stored in a Command object.""" return self._qubits @qubits.setter def qubits(self, qubits): - """Set the qubits stored in a Command object""" + """Set the qubits stored in a Command object.""" self._qubits = self._order_qubits(qubits) def __deepcopy__(self, memo): @@ -167,15 +166,13 @@ def is_identity(self): def get_merged(self, other): """ - Merge this command with another one and return the merged command - object. + Merge this command with another one and return the merged command object. Args: other: Other command to merge with this one (self) Raises: - NotMergeable: if the gates don't supply a get_merged()-function - or can't be merged for other reasons. + 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( @@ -189,8 +186,7 @@ def get_merged(self, other): def _order_qubits(self, qubits): """ - Order the given qubits according to their IDs (for unique comparison of - commands). + Order the given qubits according to their IDs (for unique comparison of commands). Args: qubits: Tuple of quantum registers (i.e., tuple of lists of qubits) @@ -212,25 +208,23 @@ def interchangeable_qubit_indices(self): """ Return nested list of qubit indices which are interchangeable. - Certain qubits can be interchanged (e.g., the qubit order for a Swap - gate). To ensure that only those are sorted when determining the - ordering (see _order_qubits), self.interchangeable_qubit_indices is - used. + Certain qubits can be interchanged (e.g., the qubit order for a Swap gate). To ensure that only those are sorted + when determining the ordering (see _order_qubits), self.interchangeable_qubit_indices is used. + Example: - If we can interchange qubits 0,1 and qubits 3,4,5, - then this function returns [[0,1],[3,4,5]] + If we can interchange qubits 0,1 and qubits 3,4,5, then this function returns [[0,1],[3,4,5]] """ return self.gate.interchangeable_qubit_indices @property def control_qubits(self): - """Returns Qureg of control qubits.""" + """Return a Qureg of control qubits.""" return self._control_qubits @control_qubits.setter def control_qubits(self, qubits): """ - Set control_qubits to qubits + Set control_qubits to qubits. Args: control_qubits (Qureg): quantum register @@ -240,19 +234,21 @@ def control_qubits(self, qubits): @property def control_state(self): - """Returns the state of the control qubits (ie. either positively- or negatively-controlled)""" + """Return the state of the control qubits (ie. either positively- or negatively-controlled).""" return self._control_state @control_state.setter def control_state(self, state): """ - Set control_state to 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 # pylint: disable=import-outside-toplevel + from projectq.meta import ( # pylint: disable=import-outside-toplevel + canonical_ctrl_state, + ) self._control_state = canonical_ctrl_state(state, len(self._control_qubits)) @@ -270,7 +266,9 @@ def add_control_qubits(self, qubits, state=CtrlAll.One): control qubits. """ # NB: avoid circular imports - from projectq.meta import canonical_ctrl_state # pylint: disable=import-outside-toplevel + from projectq.meta import ( # pylint: disable=import-outside-toplevel + canonical_ctrl_state, + ) if not isinstance(qubits, list): raise ValueError('Control qubits must be a list of qubits!') @@ -302,10 +300,7 @@ def all_qubits(self): @property def engine(self): - """ - Return engine to which the qubits belong / on which the gates are - executed. - """ + """Return engine to which the qubits belong / on which the gates are executed.""" return self._engine @engine.setter @@ -343,16 +338,12 @@ def __eq__(self, other): return True return False - def __ne__(self, other): - return not self.__eq__(other) - def __str__(self): + """Return a string representation of the object.""" return self.to_string() def to_string(self, symbols=False): - """ - Get string representation of this Command object. - """ + """Get string representation of this Command object.""" qubits = self.qubits ctrlqubits = self.control_qubits if len(ctrlqubits) > 0: diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index 1228f135a..30b555c71 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -14,19 +14,18 @@ # limitations under the License. """Tests for projectq.ops._command.""" -from copy import deepcopy -import sys import math +import sys +from copy import deepcopy + import pytest from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.meta import ComputeTag, canonical_ctrl_state -from projectq.ops import BasicGate, Rx, NotMergeable, CtrlAll +from projectq.ops import BasicGate, CtrlAll, NotMergeable, Rx, _command from projectq.types import Qubit, Qureg, WeakQubitRef -from projectq.ops import _command - @pytest.fixture def main_engine(): diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index a041c18f9..97acd12bc 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """ +Definition of the basic set of quantum gates. + Contains definitions of standard gates such as * Hadamard (H) * Pauli-X (X / NOT) @@ -41,32 +43,33 @@ * FlipBits """ -import math import cmath +import math import numpy as np from ._basics import ( BasicGate, - SelfInverseGate, - BasicRotationGate, BasicPhaseGate, + BasicRotationGate, ClassicalInstructionGate, FastForwardingGate, + SelfInverseGate, ) from ._command import apply_command from ._metagates import get_inverse class HGate(SelfInverseGate): - """Hadamard gate class""" + """Hadamard gate class.""" def __str__(self): + """Return a string representation of the object.""" return "H" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return 1.0 / cmath.sqrt(2.0) * np.matrix([[1, 1], [1, -1]]) @@ -75,14 +78,15 @@ def matrix(self): class XGate(SelfInverseGate): - """Pauli-X gate class""" + """Pauli-X gate class.""" def __str__(self): + """Return a string representation of the object.""" return "X" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[0, 1], [1, 0]]) @@ -91,14 +95,15 @@ def matrix(self): class YGate(SelfInverseGate): - """Pauli-Y gate class""" + """Pauli-Y gate class.""" def __str__(self): + """Return a string representation of the object.""" return "Y" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[0, -1j], [1j, 0]]) @@ -107,14 +112,15 @@ def matrix(self): class ZGate(SelfInverseGate): - """Pauli-Z gate class""" + """Pauli-Z gate class.""" def __str__(self): + """Return a string representation of the object.""" return "Z" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[1, 0], [0, -1]]) @@ -123,14 +129,15 @@ def matrix(self): class SGate(BasicGate): - """S gate class""" + """S gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[1, 0], [0, 1j]]) def __str__(self): + """Return a string representation of the object.""" return "S" @@ -141,14 +148,15 @@ def __str__(self): class TGate(BasicGate): - """T gate class""" + """T gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[1, 0], [0, cmath.exp(1j * cmath.pi / 4)]]) def __str__(self): + """Return a string representation of the object.""" return "T" @@ -159,20 +167,19 @@ def __str__(self): class SqrtXGate(BasicGate): - """Square-root X gate class""" + """Square-root X gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """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): # pylint: disable=no-self-use - """ - Return the Latex string representation of a SqrtXGate. - """ + """Return the Latex string representation of a SqrtXGate.""" return r'$\sqrt{X}$' def __str__(self): + """Return a string representation of the object.""" return "SqrtX" @@ -181,18 +188,20 @@ def __str__(self): class SwapGate(SelfInverseGate): - """Swap gate class (swaps 2 qubits)""" + """Swap gate class (swaps 2 qubits).""" def __init__(self): - SelfInverseGate.__init__(self) + """Initialize a Swap gate.""" + super().__init__() self.interchangeable_qubit_indices = [[0, 1]] def __str__(self): + """Return a string representation of the object.""" return "Swap" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" # fmt: off return np.matrix([[1, 0, 0, 0], [0, 0, 1, 0], @@ -206,18 +215,20 @@ def matrix(self): class SqrtSwapGate(BasicGate): - """Square-root Swap gate class""" + """Square-root Swap gate class.""" def __init__(self): - BasicGate.__init__(self) + """Initialize a SqrtSwap gate.""" + super().__init__() self.interchangeable_qubit_indices = [[0, 1]] def __str__(self): + """Return a string representation of the object.""" return "SqrtSwap" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [1, 0, 0, 0], @@ -234,11 +245,13 @@ def matrix(self): class EntangleGate(BasicGate): """ - Entangle gate (Hadamard on first qubit, followed by CNOTs applied to all - other qubits). + Entangle gate class. + + (Hadamard on first qubit, followed by CNOTs applied to all other qubits). """ def __str__(self): + """Return a string representation of the object.""" return "Entangle" @@ -247,20 +260,20 @@ def __str__(self): class Ph(BasicPhaseGate): - """Phase gate (global phase)""" + """Phase gate (global phase).""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" 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): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [math.cos(0.5 * self.angle), -1j * math.sin(0.5 * self.angle)], @@ -270,11 +283,11 @@ def matrix(self): class Ry(BasicRotationGate): - """RotationY gate class""" + """RotationY gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [math.cos(0.5 * self.angle), -math.sin(0.5 * self.angle)], @@ -284,11 +297,11 @@ def matrix(self): class Rz(BasicRotationGate): - """RotationZ gate class""" + """RotationZ gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [cmath.exp(-0.5 * 1j * self.angle), 0], @@ -298,11 +311,11 @@ def matrix(self): class Rxx(BasicRotationGate): - """RotationXX gate class""" + """RotationXX gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """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)], @@ -314,11 +327,11 @@ def matrix(self): class Ryy(BasicRotationGate): - """RotationYY gate class""" + """RotationYY gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """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)], @@ -330,11 +343,11 @@ def matrix(self): class Rzz(BasicRotationGate): - """RotationZZ gate class""" + """RotationZZ gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [cmath.exp(-0.5 * 1j * self.angle), 0, 0, 0], @@ -346,11 +359,11 @@ def matrix(self): 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): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[1, 0], [0, cmath.exp(1j * self.angle)]]) @@ -373,6 +386,7 @@ class FlushGate(FastForwardingGate): """ def __str__(self): + """Return a string representation of the object.""" return "" @@ -380,14 +394,18 @@ class MeasureGate(FastForwardingGate): """Measurement gate class (for single qubits).""" def __str__(self): + """Return a string representation of the object.""" return "Measure" def __or__(self, qubits): """ - Previously (ProjectQ <= v0.3.6) MeasureGate/Measure was allowed to be - applied to any number of quantum registers. Now the MeasureGate/Measure - is strictly a single qubit gate. In the coming releases the backward - compatibility will be removed! + Operator| overload which enables the syntax Gate | qubits. + + Previously (ProjectQ <= v0.3.6) MeasureGate/Measure was allowed to be applied to any number of quantum + registers. Now the MeasureGate/Measure is strictly a single qubit gate. + + Raises: + RuntimeError: Since ProjectQ v0.6.0 if the gate is applied to multiple qubits. """ num_qubits = 0 for qureg in self.make_tuple_of_qureg(qubits): @@ -404,12 +422,14 @@ def __or__(self, qubits): class AllocateQubitGate(ClassicalInstructionGate): - """Qubit allocation gate class""" + """Qubit allocation gate class.""" def __str__(self): + """Return a string representation of the object.""" return "Allocate" def get_inverse(self): + """Return the inverse of this gate.""" return DeallocateQubitGate() @@ -418,12 +438,14 @@ def get_inverse(self): class DeallocateQubitGate(FastForwardingGate): - """Qubit deallocation gate class""" + """Qubit deallocation gate class.""" def __str__(self): + """Return a string representation of the object.""" return "Deallocate" def get_inverse(self): + """Return the inverse of this gate.""" return Allocate @@ -432,12 +454,14 @@ def get_inverse(self): class AllocateDirtyQubitGate(ClassicalInstructionGate): - """Dirty qubit allocation gate class""" + """Dirty qubit allocation gate class.""" def __str__(self): + """Return a string representation of the object.""" return "AllocateDirty" def get_inverse(self): + """Return the inverse of this gate.""" return Deallocate @@ -446,12 +470,14 @@ def get_inverse(self): class BarrierGate(BasicGate): - """Barrier gate class""" + """Barrier gate class.""" def __str__(self): + """Return a string representation of the object.""" return "Barrier" def get_inverse(self): + """Return the inverse of this gate.""" return Barrier @@ -460,11 +486,11 @@ def get_inverse(self): 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. + Initialize a FlipBits gate. Example: .. code-block:: python @@ -473,15 +499,12 @@ def __init__(self, bits_to_flip): FlipBits([0, 1]) | qureg Args: - bits_to_flip(list[int]|list[bool]|str|int): int or array of 0/1, - True/False, or string of 0/1 identifying the qubits to flip. - In case of int, the bits to flip are determined from the - binary digits, with the least significant bit corresponding - to qureg[0]. If bits_to_flip is negative, exactly all qubits - which would not be flipped for the input -bits_to_flip-1 are - flipped, i.e., bits_to_flip=-1 flips all qubits. + bits_to_flip(list[int]|list[bool]|str|int): int or array of 0/1, True/False, or string of 0/1 identifying + the qubits to flip. In case of int, the bits to flip are determined from the binary digits, with the + least significant bit corresponding to qureg[0]. If bits_to_flip is negative, exactly all qubits which + would not be flipped for the input -bits_to_flip-1 are flipped, i.e., bits_to_flip=-1 flips all qubits. """ - SelfInverseGate.__init__(self) + super().__init__() if isinstance(bits_to_flip, int): self.bits_to_flip = bits_to_flip else: @@ -491,9 +514,11 @@ def __init__(self, bits_to_flip): self.bits_to_flip = (self.bits_to_flip << 1) | bit def __str__(self): + """Return a string representation of the object.""" return "FlipBits(" + str(self.bits_to_flip) + ")" def __or__(self, qubits): + """Operator| overload which enables the syntax Gate | qubits.""" quregs_tuple = self.make_tuple_of_qureg(qubits) if len(quregs_tuple) > 1: raise ValueError( @@ -507,9 +532,11 @@ def __or__(self, qubits): XGate() | qubit def __eq__(self, other): + """Equal operator.""" if isinstance(other, self.__class__): return self.bits_to_flip == other.bits_to_flip return False def __hash__(self): + """Compute the hash of the object.""" return hash(self.__str__()) diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index fb5977769..12b49f7a9 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -14,20 +14,14 @@ # limitations under the License. """Tests for projectq.ops._gates.""" -import math import cmath +import math + import numpy as np import pytest from projectq import MainEngine -from projectq.ops import ( - All, - FlipBits, - get_inverse, - Measure, -) - -from projectq.ops import _gates +from projectq.ops import All, FlipBits, Measure, _gates, get_inverse def test_h_gate(): diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index 59fafb7d3..f67e4927f 100644 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """ +Definition of some `meta` gates. + Contains meta gates, i.e., * DaggeredGate (Represents the inverse of an arbitrary gate) * ControlledGate (Represents a controlled version of an arbitrary gate) @@ -31,9 +33,7 @@ class ControlQubitError(Exception): - """ - Exception thrown when wrong number of control qubits are supplied. - """ + """Exception thrown when wrong number of control qubits are supplied.""" class DaggeredGate(BasicGate): @@ -64,7 +64,7 @@ def __init__(self, gate): Args: gate: Any gate object of which to represent the inverse. """ - BasicGate.__init__(self) + super().__init__() self._gate = gate try: @@ -74,33 +74,25 @@ def __init__(self, gate): pass def __str__(self): - r""" - Return string representation (str(gate) + \"^\dagger\"). - """ + r"""Return string representation (str(gate) + \"^\dagger\").""" return str(self._gate) + r"^\dagger" def tex_str(self): - """ - Return the Latex string representation of a Daggered gate. - """ + """Return the Latex string representation of a Daggered gate.""" if hasattr(self._gate, 'tex_str'): return self._gate.tex_str() + 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 def __eq__(self, other): - """ - Return True if self is equal to other, i.e., same type and - representing the inverse of the same gate. - """ + """Return True if self is equal to other, i.e., same type and representing the inverse of the same gate.""" return isinstance(other, self.__class__) and self._gate == other._gate def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) @@ -175,7 +167,7 @@ def __init__(self, gate, n=1): gate: Gate to wrap. n (int): Number of control qubits. """ - BasicGate.__init__(self) + super().__init__() if isinstance(gate, ControlledGate): self._gate = gate._gate self._n = gate._n + n @@ -184,14 +176,11 @@ def __init__(self, gate, n=1): self._n = n def __str__(self): - """Return string representation, i.e., CC...C(gate).""" + """Return a string representation of the object.""" return "C" * self._n + str(self._gate) def get_inverse(self): - """ - Return inverse of a controlled gate, which is the controlled inverse - gate. - """ + """Return inverse of a controlled gate, which is the controlled inverse gate.""" return ControlledGate(get_inverse(self._gate), self._n) def __or__(self, qubits): @@ -234,9 +223,6 @@ 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 - def __ne__(self, other): - return not self.__eq__(other) - def C(gate, n_qubits=1): """ @@ -256,8 +242,9 @@ def C(gate, n_qubits=1): 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 @@ -268,27 +255,23 @@ class Tensor(BasicGate): def __init__(self, gate): """Initialize a Tensor object for the gate.""" - BasicGate.__init__(self) + super().__init__() self._gate = gate def __str__(self): - """Return string representation.""" + """Return a string representation of the object.""" return "Tensor(" + str(self._gate) + ")" 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)) def __eq__(self, other): + """Equal operator.""" return isinstance(other, Tensor) and self._gate == other._gate - def __ne__(self, other): - return not self.__eq__(other) - def __or__(self, qubits): - """Applies the gate to every qubit in the quantum register qubits.""" + """Operator| overload which enables the syntax Gate | qubits.""" if isinstance(qubits, tuple): if len(qubits) != 1: raise ValueError('Tensor/All must be applied to a single quantum register!') diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index 3c99780fc..e00d4b617 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -16,27 +16,26 @@ import cmath import math + import numpy as np import pytest -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, + All, C, ClassicalInstructionGate, - All, + Command, + Entangle, + FastForwardingGate, + NotInvertible, + Rx, + T, + Y, + _metagates, ) -from projectq.types import WeakQubitRef - -from projectq.ops import _metagates +from projectq.types import Qubit, WeakQubitRef def test_tensored_gate_invalid(): diff --git a/projectq/ops/_qaagate.py b/projectq/ops/_qaagate.py index 9dea09bef..565d61498 100755 --- a/projectq/ops/_qaagate.py +++ b/projectq/ops/_qaagate.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains the definition of the quantum amplitude amplification gate""" +"""Definition of the quantum amplitude amplification gate.""" from ._basics import BasicGate @@ -71,9 +71,11 @@ def func_oracle(eng,system_qubits,qaa_ancilla): """ def __init__(self, algorithm, oracle): - BasicGate.__init__(self) + """Initialize a QAA object.""" + super().__init__() self.algorithm = algorithm self.oracle = oracle def __str__(self): + """Return a string representation of the object.""" 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 ccc224938..707fb2fd5 100755 --- a/projectq/ops/_qaagate_test.py +++ b/projectq/ops/_qaagate_test.py @@ -14,7 +14,7 @@ # limitations under the License. """Tests for projectq.ops._qaagate.""" -from projectq.ops import _qaagate, All, H, X +from projectq.ops import All, H, X, _qaagate def test_qaa_str(): diff --git a/projectq/ops/_qftgate.py b/projectq/ops/_qftgate.py index 45eaec9bf..0ea56c032 100755 --- a/projectq/ops/_qftgate.py +++ b/projectq/ops/_qftgate.py @@ -13,17 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains the definition of the QFT gate""" +"""Definition of the QFT gate.""" from ._basics import BasicGate class QFTGate(BasicGate): - """ - Quantum Fourier Transform gate. - """ + """Quantum Fourier Transform gate.""" def __str__(self): + """Return a string representation of the object.""" return "QFT" diff --git a/projectq/ops/_qpegate.py b/projectq/ops/_qpegate.py index c806c61a0..96ed00a5d 100755 --- a/projectq/ops/_qpegate.py +++ b/projectq/ops/_qpegate.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains the definition of the quantum phase estimation gate""" +"""Definition of the quantum phase estimation gate.""" from ._basics import BasicGate @@ -26,8 +26,10 @@ class QPE(BasicGate): """ def __init__(self, unitary): - BasicGate.__init__(self) + """Initialize a QPE gate.""" + super().__init__() self.unitary = unitary def __str__(self): + """Return a string representation of the object.""" return 'QPE({})'.format(str(self.unitary)) diff --git a/projectq/ops/_qpegate_test.py b/projectq/ops/_qpegate_test.py index 2b19cd4d0..557b980bd 100755 --- a/projectq/ops/_qpegate_test.py +++ b/projectq/ops/_qpegate_test.py @@ -14,7 +14,7 @@ # limitations under the License. """Tests for projectq.ops._qpegate.""" -from projectq.ops import _qpegate, X +from projectq.ops import X, _qpegate def test_qpe_str(): diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 19f1301e0..55323fab1 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -45,7 +45,7 @@ class QubitOperatorError(Exception): - """Exception raised when a QubitOperator is instantiated with some invalid data""" + """Exception raised when a QubitOperator is instantiated with some invalid data.""" class QubitOperator(BasicGate): @@ -97,7 +97,7 @@ class QubitOperator(BasicGate): def __init__(self, term=None, coefficient=1.0): # pylint: disable=too-many-branches """ - Inits a QubitOperator. + Initialize a QubitOperator object. 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: @@ -132,7 +132,7 @@ def __init__(self, term=None, coefficient=1.0): # pylint: disable=too-many-bran Raises: QubitOperatorError: Invalid operators provided to QubitOperator. """ - BasicGate.__init__(self) + super().__init__() if not isinstance(coefficient, (int, float, complex)): raise ValueError('Coefficient must be a numeric type.') self.terms = {} @@ -178,8 +178,10 @@ def __init__(self, term=None, coefficient=1.0): # pylint: disable=too-many-bran 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. + Compress the coefficient of a QubitOperator. + + Eliminate 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 @@ -195,7 +197,7 @@ def compress(self, abs_tol=1e-12): def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): """ - Returns True if other (QubitOperator) is close to self. + Return 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 @@ -224,7 +226,9 @@ def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): def __or__(self, qubits): # pylint: disable=too-many-locals """ - Operator| overload which enables the following syntax: + Operator| overload which enables the syntax Gate | qubits. + + In particular, enable the following syntax: .. code-block:: python @@ -314,8 +318,8 @@ def __or__(self, qubits): # pylint: disable=too-many-locals return # Create new QubitOperator gate with rescaled qubit indices in # 0,..., len(non_trivial_qubits) - 1 - new_index = dict() - non_trivial_qubits = sorted(list(non_trivial_qubits)) + new_index = {} + non_trivial_qubits = sorted(non_trivial_qubits) for i, qubit in enumerate(non_trivial_qubits): new_index[qubit] = i new_qubitoperator = QubitOperator() @@ -335,7 +339,6 @@ def get_inverse(self): multiple terms or a coefficient with absolute value not equal to 1. """ - if len(self.terms) == 1: ((term, coefficient),) = self.terms.items() if not abs(coefficient) < 1 - EQ_TOLERANCE and not abs(coefficient) > 1 + EQ_TOLERANCE: @@ -370,7 +373,7 @@ def __imul__(self, multiplier): # pylint: disable=too-many-locals,too-many-bran # Handle QubitOperator. if isinstance(multiplier, QubitOperator): # pylint: disable=too-many-nested-blocks - result_terms = dict() + result_terms = {} for left_term in self.terms: for right_term in multiplier.terms: new_coefficient = self.terms[left_term] * multiplier.terms[right_term] @@ -484,6 +487,7 @@ def __truediv__(self, divisor): return self * (1.0 / divisor) def __itruediv__(self, divisor): + """Perform self =/ divisor for a scalar.""" if not isinstance(divisor, (int, float, complex)): raise TypeError('Cannot divide QubitOperator by non-scalar type.') self *= 1.0 / divisor @@ -548,10 +552,15 @@ def __sub__(self, subtrahend): return minuend def __neg__(self): + """ + Opposite operator. + + Return -self for a QubitOperator. + """ return -1.0 * self def __str__(self): - """Return an easy-to-read string representation.""" + """Return a string representation of the object.""" if not self.terms: return '0' string_rep = '' @@ -572,7 +581,9 @@ def __str__(self): return string_rep[:-3] def __repr__(self): + """Repr method.""" return str(self) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index 8077a8583..ad86e0d64 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -22,11 +22,11 @@ from projectq import MainEngine from projectq.cengines import DummyEngine +from projectq.ops import _qubit_operator as qo + from ._basics import NotInvertible, NotMergeable from ._gates import Ph, T, X, Y, Z -from projectq.ops import _qubit_operator as qo - def test_pauli_operator_product_unchanged(): correct = { @@ -79,7 +79,7 @@ def test_init_str_identity(): def test_init_bad_term(): with pytest.raises(ValueError): - qo.QubitOperator(list()) + qo.QubitOperator([]) def test_init_bad_coefficient(): diff --git a/projectq/ops/_shortcuts.py b/projectq/ops/_shortcuts.py index 0defb4e22..afcc8bdf4 100755 --- a/projectq/ops/_shortcuts.py +++ b/projectq/ops/_shortcuts.py @@ -13,20 +13,20 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Defines a few shortcuts for certain gates such as +A few shortcuts for certain gates. + +These include: * CNOT = C(NOT) * CRz = C(Rz) * Toffoli = C(NOT,2) = C(CNOT) """ -from ._metagates import C from ._gates import NOT, Rz, Z +from ._metagates import C def CRz(angle): - """ - Shortcut for C(Rz(angle), n_qubits=1). - """ + """Shortcut for C(Rz(angle), n_qubits=1).""" return C(Rz(angle), n_qubits=1) diff --git a/projectq/ops/_shortcuts_test.py b/projectq/ops/_shortcuts_test.py index dd8a65d71..345f8396c 100755 --- a/projectq/ops/_shortcuts_test.py +++ b/projectq/ops/_shortcuts_test.py @@ -14,9 +14,7 @@ # limitations under the License. """Tests for projectq.ops._shortcuts.""" -from projectq.ops import ControlledGate, Rz - -from projectq.ops import _shortcuts +from projectq.ops import ControlledGate, Rz, _shortcuts def test_crz(): diff --git a/projectq/ops/_state_prep.py b/projectq/ops/_state_prep.py index d86824bf8..74c26d97a 100644 --- a/projectq/ops/_state_prep.py +++ b/projectq/ops/_state_prep.py @@ -13,19 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains the definition of the state preparation gate""" +"""Definition of the state preparation gate.""" from ._basics import BasicGate class StatePreparation(BasicGate): - """ - Gate for transforming qubits in state |0> to any desired quantum state. - """ + """Gate for transforming qubits in state |0> to any desired quantum state.""" def __init__(self, final_state): """ - Initialize StatePreparation gate. + Initialize a StatePreparation gate. Example: .. code-block:: python @@ -34,28 +32,26 @@ def __init__(self, final_state): StatePreparation([0.5, -0.5j, -0.5, 0.5]) | qureg Note: - final_state[k] is taken to be the amplitude of the computational - basis state whose string is equal to the binary representation - of k. + final_state[k] is taken to be the amplitude of the computational basis state whose string is equal to the + binary representation of k. Args: - final_state(list[complex]): wavefunction of the desired - quantum state. len(final_state) must - be 2**len(qureg). Must be normalized! + final_state(list[complex]): wavefunction of the desired quantum state. len(final_state) must be + 2**len(qureg). Must be normalized! """ - BasicGate.__init__(self) + super().__init__() self.final_state = list(final_state) def __str__(self): + """Return a string representation of the object.""" return "StatePreparation" def __eq__(self, other): + """Equal operator.""" if isinstance(other, self.__class__): return self.final_state == other.final_state return False - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): + """Compute the hash of the object.""" return hash("StatePreparation(" + str(self.final_state) + ")") diff --git a/projectq/ops/_state_prep_test.py b/projectq/ops/_state_prep_test.py index 5826d8e56..198ace500 100644 --- a/projectq/ops/_state_prep_test.py +++ b/projectq/ops/_state_prep_test.py @@ -14,7 +14,7 @@ # limitations under the License. """Tests for projectq.ops._state_prep.""" -from projectq.ops import _state_prep, X +from projectq.ops import X, _state_prep def test_equality_and_hash(): diff --git a/projectq/ops/_time_evolution.py b/projectq/ops/_time_evolution.py index 80e051f70..aa10944a9 100644 --- a/projectq/ops/_time_evolution.py +++ b/projectq/ops/_time_evolution.py @@ -13,11 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains the definition of the time evolution gate""" +"""Definition of the time evolution gate.""" import copy - from ._basics import BasicGate, NotMergeable from ._command import apply_command from ._gates import Ph @@ -25,7 +24,7 @@ class NotHermitianOperatorError(Exception): - """Error raised if an operator is non-hermitian""" + """Error raised if an operator is non-hermitian.""" class TimeEvolution(BasicGate): @@ -67,7 +66,7 @@ def __init__(self, time, hamiltonian): 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) + super().__init__() if not isinstance(time, (float, int)): raise TypeError("time needs to be a (real) numeric type.") if not isinstance(hamiltonian, QubitOperator): @@ -81,9 +80,7 @@ def __init__(self, time, hamiltonian): raise NotHermitianOperatorError("hamiltonian must be hermitian and hence only have real coefficients.") def get_inverse(self): - """ - Return the inverse gate. - """ + """Return the inverse gate.""" return TimeEvolution(self.time * -1.0, self.hamiltonian) def get_merged(self, other): @@ -130,7 +127,9 @@ def get_merged(self, other): def __or__(self, qubits): """ - Operator| overload which enables the following syntax: + Operator| overload which enables the syntax Gate | qubits. + + In particular, enable the following syntax: .. code-block:: python @@ -142,7 +141,6 @@ def __or__(self, qubits): Unlike other gates, this gate is only allowed to be applied to one quantum register or one qubit. Example: - .. code-block:: python wavefunction = eng.allocate_qureg(5) @@ -184,8 +182,8 @@ def __or__(self, qubits): # create new TimeEvolution gate with rescaled qubit indices in # self.hamiltonian which are ordered from # 0,...,len(non_trivial_qubits) - 1 - new_index = dict() - non_trivial_qubits = sorted(list(non_trivial_qubits)) + new_index = {} + non_trivial_qubits = sorted(non_trivial_qubits) for i, qubit in enumerate(non_trivial_qubits): new_index[qubit] = i new_hamiltonian = QubitOperator() @@ -202,9 +200,6 @@ def __eq__(self, other): """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.""" - return NotImplemented - def __str__(self): + """Return a string representation of the object.""" return "exp({0} * ({1}))".format(-1j * self.time, self.hamiltonian) diff --git a/projectq/ops/_time_evolution_test.py b/projectq/ops/_time_evolution_test.py index e4ee41a79..078c96f39 100644 --- a/projectq/ops/_time_evolution_test.py +++ b/projectq/ops/_time_evolution_test.py @@ -12,7 +12,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + """Tests for projectq.ops._time_evolution.""" + import cmath import copy @@ -21,8 +23,7 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import QubitOperator, BasicGate, NotMergeable, Ph - +from projectq.ops import BasicGate, NotMergeable, Ph, QubitOperator from projectq.ops import _time_evolution as te diff --git a/projectq/ops/_uniformly_controlled_rotation.py b/projectq/ops/_uniformly_controlled_rotation.py index c5ae74229..393467406 100644 --- a/projectq/ops/_uniformly_controlled_rotation.py +++ b/projectq/ops/_uniformly_controlled_rotation.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains uniformly controlled rotation gates""" +"""Definition of uniformly controlled Ry- and Rz-rotation gates.""" import math @@ -46,7 +46,8 @@ class UniformlyControlledRy(BasicGate): """ def __init__(self, angles): - BasicGate.__init__(self) + """Construct a UniformlyControlledRy gate.""" + super().__init__() rounded_angles = [] for angle in angles: new_angle = round(float(angle) % (4.0 * math.pi), ANGLE_PRECISION) @@ -56,15 +57,18 @@ def __init__(self, angles): self.angles = rounded_angles def get_inverse(self): + """Return the inverse of this rotation gate (negate the angles, return new object).""" return self.__class__([-1 * angle for angle in self.angles]) def get_merged(self, other): + """Return self merged with another gate.""" if isinstance(other, self.__class__): new_angles = [angle1 + angle2 for (angle1, angle2) in zip(self.angles, other.angles)] return self.__class__(new_angles) raise NotMergeable() def __str__(self): + """Return a string representation of the object.""" return "UniformlyControlledRy(" + str(self.angles) + ")" def __eq__(self, other): @@ -73,10 +77,8 @@ def __eq__(self, other): return self.angles == other.angles return False - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) @@ -101,12 +103,12 @@ class UniformlyControlledRz(BasicGate): list in which case the gate corresponds to an Rz. Args: - angles(list[float]): Rotation angles. Rz(angles[k]) is applied - conditioned on the control qubits being in state - k. + angles(list[float]): Rotation angles. Rz(angles[k]) is applied conditioned on the control qubits being in + state k. """ def __init__(self, angles): + """Construct a UniformlyControlledRz gate.""" super().__init__() rounded_angles = [] for angle in angles: @@ -117,15 +119,18 @@ def __init__(self, angles): self.angles = rounded_angles def get_inverse(self): + """Return the inverse of this rotation gate (negate the angles, return new object).""" return self.__class__([-1 * angle for angle in self.angles]) def get_merged(self, other): + """Return self merged with another gate.""" if isinstance(other, self.__class__): new_angles = [angle1 + angle2 for (angle1, angle2) in zip(self.angles, other.angles)] return self.__class__(new_angles) raise NotMergeable() def __str__(self): + """Return a string representation of the object.""" return "UniformlyControlledRz(" + str(self.angles) + ")" def __eq__(self, other): @@ -134,8 +139,6 @@ def __eq__(self, other): return self.angles == other.angles return False - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) diff --git a/projectq/ops/_uniformly_controlled_rotation_test.py b/projectq/ops/_uniformly_controlled_rotation_test.py index 14014da04..362932483 100644 --- a/projectq/ops/_uniformly_controlled_rotation_test.py +++ b/projectq/ops/_uniformly_controlled_rotation_test.py @@ -18,10 +18,10 @@ import pytest from projectq.ops import Rx -from ._basics import NotMergeable - from projectq.ops import _uniformly_controlled_rotation as ucr +from ._basics import NotMergeable + @pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, ucr.UniformlyControlledRz]) def test_init_rounding(gate_class): diff --git a/projectq/setups/_utils.py b/projectq/setups/_utils.py index a50110ec1..6f933aac6 100644 --- a/projectq/setups/_utils.py +++ b/projectq/setups/_utils.py @@ -12,11 +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. -""" -Some utility functions common to some setups -""" + +"""Some utility functions common to some setups.""" + import inspect +import projectq.libs.math +import projectq.setups.decompositions from projectq.cengines import ( AutoReplacer, DecompositionRuleSet, @@ -24,15 +26,19 @@ LocalOptimizer, TagRemover, ) -from projectq.ops import ClassicalInstructionGate, CNOT, ControlledGate, Swap, QFT, get_inverse, BasicMathGate -import projectq.libs.math -import projectq.setups.decompositions +from projectq.ops import ( + CNOT, + QFT, + BasicMathGate, + ClassicalInstructionGate, + ControlledGate, + Swap, + get_inverse, +) def one_and_two_qubit_gates(eng, cmd): # pylint: disable=unused-argument - """ - Filter out 1- and 2-qubit gates. - """ + """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 @@ -45,9 +51,7 @@ def one_and_two_qubit_gates(eng, cmd): # pylint: disable=unused-argument def high_level_gates(eng, cmd): # pylint: disable=unused-argument - """ - Remove any MathGates. - """ + """Remove any MathGates.""" gate = cmd.gate if eng.next_engine.is_available(cmd): return True @@ -60,7 +64,7 @@ def high_level_gates(eng, cmd): # pylint: disable=unused-argument 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. + Return 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 diff --git a/projectq/setups/aqt.py b/projectq/setups/aqt.py index bd4ff862e..02ac24b19 100644 --- a/projectq/setups/aqt.py +++ b/projectq/setups/aqt.py @@ -12,7 +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. + """ +A setup for AQT trapped ion devices. + Defines a setup allowing to compile code for the AQT trapped ion devices: ->The 4 qubits device ->The 11 qubits simulator @@ -23,17 +26,15 @@ translated in the backend in the Rx/Ry/MS gate set. """ -from projectq.setups import restrictedgateset -from projectq.ops import Rx, Ry, Rxx, Barrier -from projectq.cengines import BasicMapperEngine - from projectq.backends._aqt._aqt_http_client import show_devices +from projectq.backends._exceptions import DeviceNotHandledError, DeviceOfflineError +from projectq.cengines import BasicMapperEngine +from projectq.ops import Barrier, Rx, Rxx, Ry +from projectq.setups import restrictedgateset def get_engine_list(token=None, device=None): - """ - Return the default list of compiler engine for the AQT plaftorm - """ + """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.. @@ -49,7 +50,7 @@ def get_engine_list(token=None, device=None): # Note: Manual Mapper doesn't work, because its map is updated only if # gates are applied if gates in the register are not used, then it # will lead to state errors - res = dict() + res = {} for i in range(devices[device]['nq']): res[i] = i mapper.current_mapping = res @@ -65,11 +66,3 @@ def get_engine_list(token=None, device=None): setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry), two_qubit_gates=(Rxx,), other_gates=(Barrier,)) setup.extend(aqt_setup) return setup - - -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" - - -class DeviceNotHandledError(Exception): - """Exception raised if a selected device is cannot handle the circuit""" diff --git a/projectq/setups/aqt_test.py b/projectq/setups/aqt_test.py index d95ce2f2e..e36146e0b 100644 --- a/projectq/setups/aqt_test.py +++ b/projectq/setups/aqt_test.py @@ -21,21 +21,19 @@ 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), - ] - ) + connections = { + (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) @@ -47,21 +45,19 @@ 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), - ] - ) + connections = { + (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) diff --git a/projectq/setups/awsbraket.py b/projectq/setups/awsbraket.py index e9558a1d1..0268b834f 100644 --- a/projectq/setups/awsbraket.py +++ b/projectq/setups/awsbraket.py @@ -12,7 +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. + """ +A setup for AWS Braket devices. + Defines a setup allowing to compile code for the AWS Braket devices: ->The 11 qubits IonQ device ->The 32 qubits Rigetti device @@ -23,31 +26,30 @@ that will be used in the backend. """ -from projectq.setups import restrictedgateset +from projectq.backends._awsbraket._awsbraket_boto3_client import show_devices +from projectq.backends._exceptions import DeviceNotHandledError, DeviceOfflineError from projectq.ops import ( - R, - Swap, + Barrier, H, + R, Rx, Ry, Rz, S, Sdag, + SqrtX, + Swap, T, Tdag, X, Y, Z, - SqrtX, - Barrier, ) -from projectq.backends._awsbraket._awsbraket_boto3_client import show_devices +from projectq.setups import restrictedgateset def get_engine_list(credentials=None, device=None): - """ - Return the default list of compiler engine for the AWS Braket platform. - """ + """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.. @@ -84,8 +86,4 @@ 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): - """Exception raised if a selected device is currently offline""" + raise DeviceNotHandledError('Unsupported device type: {}!'.format(device)) # pragma: no cover diff --git a/projectq/setups/awsbraket_test.py b/projectq/setups/awsbraket_test.py index 2f78d8bc2..6f8ad41b7 100644 --- a/projectq/setups/awsbraket_test.py +++ b/projectq/setups/awsbraket_test.py @@ -14,9 +14,10 @@ # limitations under the License. """Tests for projectq.setup.awsbraket.""" -import pytest -from unittest.mock import patch import json +from unittest.mock import patch + +import pytest # ============================================================================== diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index cca7d08c9..908b04cc9 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -14,20 +14,22 @@ # limitations under the License. from . import ( + amplitudeamplification, arb1qubit2rzandry, barrier, carb1qubit2cnotrzandry, - crz2cxandrz, - cnot2rxx, cnot2cz, + cnot2rxx, cnu2toffoliandcu, controlstate, + crz2cxandrz, entangle, globalphase, h2rx, ph2r, - qubitop2onequbit, + phaseestimation, qft2crandhadamard, + qubitop2onequbit, r2rzandph, rx2rz, ry2rz, @@ -35,11 +37,9 @@ sqrtswap2cnot, stateprep2cnot, swap2cnot, - toffoli2cnotandtgate, time_evolution, + toffoli2cnotandtgate, uniformlycontrolledr2cnot, - phaseestimation, - amplitudeamplification, ) all_defined_decomposition_rules = [ diff --git a/projectq/setups/decompositions/_gates_test.py b/projectq/setups/decompositions/_gates_test.py index 3d03d2bc7..5ea730ff0 100755 --- a/projectq/setups/decompositions/_gates_test.py +++ b/projectq/setups/decompositions/_gates_test.py @@ -18,14 +18,15 @@ import pytest +from projectq.backends import Simulator from projectq.cengines import ( - MainEngine, - InstructionFilter, AutoReplacer, - DummyEngine, DecompositionRuleSet, + DummyEngine, + InstructionFilter, + MainEngine, ) -from projectq.backends import Simulator +from projectq.meta import Control from projectq.ops import ( All, ClassicalInstructionGate, @@ -41,7 +42,6 @@ Toffoli, X, ) -from projectq.meta import Control from projectq.setups.decompositions import ( crz2cxandrz, entangle, diff --git a/projectq/setups/decompositions/amplitudeamplification.py b/projectq/setups/decompositions/amplitudeamplification.py index 7e2a171b0..cbf2b0af7 100644 --- a/projectq/setups/decompositions/amplitudeamplification.py +++ b/projectq/setups/decompositions/amplitudeamplification.py @@ -71,10 +71,8 @@ def func_oracle(eng,system_qubits,qaa_ancilla): import math from projectq.cengines import DecompositionRule -from projectq.meta import Control, Compute, CustomUncompute, Dagger -from projectq.ops import X, Z, Ph, All - -from projectq.ops import QAA +from projectq.meta import Compute, Control, CustomUncompute, Dagger +from projectq.ops import QAA, All, Ph, X, Z def _decompose_QAA(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/amplitudeamplification_test.py b/projectq/setups/decompositions/amplitudeamplification_test.py index 9599c3982..adab7cd92 100644 --- a/projectq/setups/decompositions/amplitudeamplification_test.py +++ b/projectq/setups/decompositions/amplitudeamplification_test.py @@ -16,15 +16,13 @@ "Tests for projectq.setups.decompositions.amplitudeamplification.py." import math + import pytest from projectq.backends import Simulator from projectq.cengines import AutoReplacer, DecompositionRuleSet, MainEngine - -from projectq.ops import X, H, Ry, All, Measure -from projectq.meta import Loop, Control, Compute, Uncompute - -from projectq.ops import QAA +from projectq.meta import Compute, Control, Loop, Uncompute +from projectq.ops import QAA, All, H, Measure, Ry, X from projectq.setups.decompositions import amplitudeamplification as aa diff --git a/projectq/setups/decompositions/arb1qubit2rzandry.py b/projectq/setups/decompositions/arb1qubit2rzandry.py index fadc006d2..699b1fcfd 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry.py @@ -13,7 +13,7 @@ # 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. +Register the Z-Y decomposition for an arbitrary one qubit gate. See paper "Elementary gates for quantum computing" by Adriano Barenco et al., arXiv:quant-ph/9503016v1. (Note: They use different gate definitions!) @@ -55,8 +55,7 @@ def _recognize_arb1qubit(cmd): 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. + Build matrix U with parameters (a, b/2, c/2, d/2) and compares against matrix. U = [[exp(j*(a-b/2-d/2))*cos(c/2), -exp(j*(a-b/2+d/2))*sin(c/2)], [exp(j*(a+b/2-d/2))*sin(c/2), exp(j*(a+b/2+d/2))*cos(c/2)]] @@ -86,14 +85,14 @@ def _test_parameters(matrix, a, b_half, c_half, d_half): # pylint: disable=inva 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 + Find decomposition parameters. + + Given a 2x2 unitary matrix, find the parameters a, b/2, c/2, and d/2 such that matrix == [[exp(j*(a-b/2-d/2))*cos(c/2), -exp(j*(a-b/2+d/2))*sin(c/2)], [exp(j*(a+b/2-d/2))*sin(c/2), exp(j*(a+b/2+d/2))*cos(c/2)]] Note: - If the matrix is element of SU(2) (determinant == 1), then - we can choose a = 0. + If the matrix is element of SU(2) (determinant == 1), then we can choose a = 0. Args: matrix(list): 2x2 unitary matrix diff --git a/projectq/setups/decompositions/arb1qubit2rzandry_test.py b/projectq/setups/decompositions/arb1qubit2rzandry_test.py index 6d2daa52a..90a72057a 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry_test.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry_test.py @@ -15,8 +15,8 @@ "Tests for projectq.setups.decompositions.arb1qubit2rzandry.py." -from cmath import exp import math +from cmath import exp import numpy as np import pytest @@ -29,6 +29,7 @@ InstructionFilter, MainEngine, ) +from projectq.meta import Control from projectq.ops import ( BasicGate, ClassicalInstructionGate, @@ -41,7 +42,6 @@ Rz, X, ) -from projectq.meta import Control from . import arb1qubit2rzandry as arb1q diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py index b78d20556..b2f8b701a 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Registers the decomposition of an controlled arbitary single qubit gate. +Register the decomposition of an controlled arbitary single qubit gate. See paper "Elementary gates for quantum computing" by Adriano Barenco et al., arXiv:quant-ph/9503016v1. (Note: They use different gate definitions!) or @@ -27,7 +27,7 @@ import numpy from projectq.cengines import DecompositionRule -from projectq.meta import get_control_count, Control +from projectq.meta import Control, get_control_count from projectq.ops import BasicGate, Ph, Ry, Rz, X from projectq.setups.decompositions import arb1qubit2rzandry as arb1q @@ -46,8 +46,7 @@ def _recognize_carb1qubit(cmd): 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. + Build matrix V with parameters (a, b, c/2) and compares against matrix. V = [[-sin(c/2) * exp(j*a), exp(j*(a-b)) * cos(c/2)], [exp(j*(a+b)) * cos(c/2), exp(j*a) * sin(c/2)]] @@ -76,7 +75,9 @@ def _test_parameters(matrix, a, b, c_half): # pylint: disable=invalid-name def _recognize_v(matrix): # pylint: disable=too-many-branches """ - Recognizes a matrix which can be written in the following form: + Test whether a matrix has the correct form. + + Recognize a matrix which can be written in the following form: V = [[-sin(c/2) * exp(j*a), exp(j*(a-b)) * cos(c/2)], [exp(j*(a+b)) * cos(c/2), exp(j*a) * sin(c/2)]] diff --git a/projectq/setups/decompositions/cnot2cz.py b/projectq/setups/decompositions/cnot2cz.py index 3525e83b8..ea60ce2f3 100644 --- a/projectq/setups/decompositions/cnot2cz.py +++ b/projectq/setups/decompositions/cnot2cz.py @@ -12,12 +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. -""" -Registers a decomposition to for a CNOT gate in terms of CZ and Hadamard. -""" + +"""Registers a decomposition to for a CNOT gate in terms of CZ and Hadamard.""" from projectq.cengines import DecompositionRule -from projectq.meta import Compute, get_control_count, Uncompute +from projectq.meta import Compute, Uncompute, get_control_count from projectq.ops import CZ, H, X diff --git a/projectq/setups/decompositions/cnot2cz_test.py b/projectq/setups/decompositions/cnot2cz_test.py index 9f50959ab..ca9b3af2f 100644 --- a/projectq/setups/decompositions/cnot2cz_test.py +++ b/projectq/setups/decompositions/cnot2cz_test.py @@ -26,8 +26,7 @@ InstructionFilter, ) from projectq.meta import Control -from projectq.ops import All, CNOT, CZ, Measure, X, Z - +from projectq.ops import CNOT, CZ, All, Measure, X, Z from projectq.setups.decompositions import cnot2cz diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index 1a37ff045..7565ef062 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -16,15 +16,14 @@ # Module uses ideas from "Basic circuit compilation techniques # for an ion-trap quantum machine" by Dmitri Maslov (2017) at # https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 -""" -Registers a decomposition to for a CNOT gate in terms of Rxx, Rx and Ry gates. -""" + +"""Register 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 +from projectq.ops import Ph, Rx, Rxx, Ry, X def _decompose_cnot2rxx_M(cmd): # pylint: disable=invalid-name @@ -52,7 +51,7 @@ def _decompose_cnot2rxx_P(cmd): # pylint: disable=invalid-name 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 diff --git a/projectq/setups/decompositions/cnot2rxx_test.py b/projectq/setups/decompositions/cnot2rxx_test.py index 31fcbdd42..9cb72fc3e 100644 --- a/projectq/setups/decompositions/cnot2rxx_test.py +++ b/projectq/setups/decompositions/cnot2rxx_test.py @@ -26,7 +26,7 @@ InstructionFilter, ) from projectq.meta import Control -from projectq.ops import All, CNOT, CZ, Measure, X, Z +from projectq.ops import CNOT, CZ, All, Measure, X, Z from . import cnot2rxx diff --git a/projectq/setups/decompositions/cnu2toffoliandcu.py b/projectq/setups/decompositions/cnu2toffoliandcu.py index fd1d0e2b5..cd4b79901 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu.py @@ -12,8 +12,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + """ -Registers a decomposition rule for multi-controlled gates. +Register a decomposition rule for multi-controlled gates. Implements the decomposition of Nielsen and Chuang (Fig. 4.10) which decomposes a C^n(U) gate into a sequence of 2 * (n-1) Toffoli gates and one @@ -21,15 +22,12 @@ """ from projectq.cengines import DecompositionRule -from projectq.meta import get_control_count, Compute, Control, Uncompute +from projectq.meta import Compute, Control, Uncompute, get_control_count from projectq.ops import BasicGate, Toffoli, XGate def _recognize_CnU(cmd): # pylint: disable=invalid-name - """ - Recognize an arbitrary gate which has n>=2 control qubits, except a - Toffoli gate. - """ + """Recognize an arbitrary gate which has n>=2 control qubits, except a Toffoli gate.""" if get_control_count(cmd) == 2: if not isinstance(cmd.gate, XGate): return True @@ -40,11 +38,10 @@ def _recognize_CnU(cmd): # pylint: disable=invalid-name def _decompose_CnU(cmd): # pylint: disable=invalid-name """ - Decompose a multi-controlled gate U with n control qubits into a single- - controlled U. + Decompose a multi-controlled gate U with n control qubits into a single- controlled U. - It uses (n-1) work qubits and 2 * (n-1) Toffoli gates for general U - and (n-2) work qubits and 2n - 3 Toffoli gates if U is an X-gate. + It uses (n-1) work qubits and 2 * (n-1) Toffoli gates for general U and (n-2) work qubits and 2n - 3 Toffoli gates + if U is an X-gate. """ eng = cmd.engine qubits = cmd.qubits diff --git a/projectq/setups/decompositions/cnu2toffoliandcu_test.py b/projectq/setups/decompositions/cnu2toffoliandcu_test.py index e6798af7d..178813beb 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu_test.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu_test.py @@ -27,11 +27,11 @@ ) from projectq.meta import Control from projectq.ops import ( + QFT, All, ClassicalInstructionGate, Measure, Ph, - QFT, Rx, Ry, X, diff --git a/projectq/setups/decompositions/controlstate.py b/projectq/setups/decompositions/controlstate.py index 26a3e5ae7..f10a8add3 100755 --- a/projectq/setups/decompositions/controlstate.py +++ b/projectq/setups/decompositions/controlstate.py @@ -14,21 +14,20 @@ # limitations under the License. """ -Register a decomposition to replace turn negatively controlled qubits into positively controlled qubits by applying X -gates. +Register a decomposition to replace turn negatively controlled qubits into positively controlled qubits. + +This achived by applying X gates to selected qubits. """ 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') - """ + """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': diff --git a/projectq/setups/decompositions/controlstate_test.py b/projectq/setups/decompositions/controlstate_test.py index a74538b58..f50a9b01a 100755 --- a/projectq/setups/decompositions/controlstate_test.py +++ b/projectq/setups/decompositions/controlstate_test.py @@ -18,10 +18,15 @@ """ from projectq import MainEngine -from projectq.cengines import DummyEngine, AutoReplacer, InstructionFilter, DecompositionRuleSet +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, +) from projectq.meta import Control, has_negative_control from projectq.ops import X -from projectq.setups.decompositions import controlstate, cnot2cz +from projectq.setups.decompositions import cnot2cz, controlstate def filter_func(eng, cmd): diff --git a/projectq/setups/decompositions/crz2cxandrz.py b/projectq/setups/decompositions/crz2cxandrz.py index 013fdb978..b36f356ba 100755 --- a/projectq/setups/decompositions/crz2cxandrz.py +++ b/projectq/setups/decompositions/crz2cxandrz.py @@ -20,7 +20,7 @@ from projectq.cengines import DecompositionRule from projectq.meta import get_control_count -from projectq.ops import NOT, Rz, C +from projectq.ops import NOT, C, Rz def _decompose_CRz(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/entangle.py b/projectq/setups/decompositions/entangle.py index 918be886e..30d921a9d 100755 --- a/projectq/setups/decompositions/entangle.py +++ b/projectq/setups/decompositions/entangle.py @@ -21,7 +21,7 @@ from projectq.cengines import DecompositionRule from projectq.meta import Control -from projectq.ops import X, H, Entangle, All +from projectq.ops import All, Entangle, H, X def _decompose_entangle(cmd): diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py index c9f27092a..9a0eb4239 100644 --- a/projectq/setups/decompositions/h2rx.py +++ b/projectq/setups/decompositions/h2rx.py @@ -16,15 +16,14 @@ # Module uses ideas from "Basic circuit compilation techniques for an # ion-trap quantum machine" by Dmitri Maslov (2017) at # https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 -""" -Registers a decomposition for the H gate into an Ry and Rx gate. -""" + +"""Register a decomposition for the H gate into an Ry and Rx gate.""" import math from projectq.cengines import DecompositionRule from projectq.meta import get_control_count -from projectq.ops import Ph, Rx, Ry, H +from projectq.ops import H, Ph, Rx, Ry def _decompose_h2rx_M(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/h2rx_test.py b/projectq/setups/decompositions/h2rx_test.py index ff06b277e..551517e42 100644 --- a/projectq/setups/decompositions/h2rx_test.py +++ b/projectq/setups/decompositions/h2rx_test.py @@ -26,7 +26,7 @@ InstructionFilter, ) from projectq.meta import Control -from projectq.ops import Measure, H, HGate +from projectq.ops import H, HGate, Measure from . import h2rx diff --git a/projectq/setups/decompositions/phaseestimation.py b/projectq/setups/decompositions/phaseestimation.py index 5059ad1ec..58dea6e35 100644 --- a/projectq/setups/decompositions/phaseestimation.py +++ b/projectq/setups/decompositions/phaseestimation.py @@ -86,9 +86,7 @@ def two_qubit_gate(system_q, time): from projectq.cengines import DecompositionRule from projectq.meta import Control, Loop -from projectq.ops import H, Tensor, get_inverse, QFT - -from projectq.ops import QPE +from projectq.ops import QFT, QPE, H, Tensor, get_inverse def _decompose_QPE(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/phaseestimation_test.py b/projectq/setups/decompositions/phaseestimation_test.py index 118641d7e..151b8643a 100644 --- a/projectq/setups/decompositions/phaseestimation_test.py +++ b/projectq/setups/decompositions/phaseestimation_test.py @@ -16,23 +16,18 @@ "Tests for projectq.setups.decompositions.phaseestimation.py." import cmath + import numpy as np -from flaky import flaky import pytest +from flaky import flaky +import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot +import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot from projectq.backends import Simulator -from projectq.cengines import ( - AutoReplacer, - DecompositionRuleSet, - MainEngine, -) - -from projectq.ops import X, H, All, Measure, Tensor, Ph, CNOT, StatePreparation, QPE - +from projectq.cengines import AutoReplacer, DecompositionRuleSet, MainEngine +from projectq.ops import CNOT, QPE, All, H, Measure, Ph, StatePreparation, Tensor, X from projectq.setups.decompositions import phaseestimation as pe from projectq.setups.decompositions import qft2crandhadamard as dqft -import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot -import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot @flaky(max_runs=5, min_passes=2) @@ -44,8 +39,9 @@ def test_simple_test_X_eigenvectors(): AutoReplacer(rule_set), ], ) + N = 150 results = np.array([]) - for i in range(150): + for i in range(N): autovector = eng.allocate_qureg(1) X | autovector H | autovector @@ -62,8 +58,8 @@ def test_simple_test_X_eigenvectors(): eng.flush() num_phase = (results == 0.5).sum() - assert num_phase / 100.0 >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / 100.0, + assert num_phase / N >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( + num_phase / N, 0.35, ) @@ -77,8 +73,9 @@ def test_Ph_eigenvectors(): AutoReplacer(rule_set), ], ) + N = 150 results = np.array([]) - for i in range(150): + for i in range(N): autovector = eng.allocate_qureg(1) theta = cmath.pi * 2.0 * 0.125 unit = Ph(theta) @@ -94,8 +91,8 @@ def test_Ph_eigenvectors(): eng.flush() num_phase = (results == 0.125).sum() - assert num_phase / 100.0 >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / 100.0, + assert num_phase / N >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( + num_phase / N, 0.35, ) @@ -115,8 +112,9 @@ def test_2qubitsPh_andfunction_eigenvectors(): AutoReplacer(rule_set), ], ) + N = 150 results = np.array([]) - for i in range(150): + for i in range(N): autovector = eng.allocate_qureg(2) X | autovector[0] ancillas = eng.allocate_qureg(3) @@ -131,8 +129,8 @@ def test_2qubitsPh_andfunction_eigenvectors(): eng.flush() num_phase = (results == 0.125).sum() - assert num_phase / 100.0 >= 0.34, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / 100.0, + assert num_phase / N >= 0.34, "Statistics phase calculation are not correct (%f vs. %f)" % ( + num_phase / N, 0.34, ) @@ -145,10 +143,11 @@ def test_X_no_eigenvectors(): AutoReplacer(rule_set), ], ) + N = 100 results = np.array([]) results_plus = np.array([]) results_minus = np.array([]) - for i in range(100): + for i in range(N): autovector = eng.allocate_qureg(1) amplitude0 = (np.sqrt(2) + np.sqrt(6)) / 4.0 amplitude1 = (np.sqrt(2) - np.sqrt(6)) / 4.0 @@ -176,8 +175,8 @@ def test_X_no_eigenvectors(): eng.flush() total = len(results_plus) + len(results_minus) - plus_probability = len(results_plus) / 100.0 - assert total == pytest.approx(100, abs=5) + plus_probability = len(results_plus) / N + assert total == pytest.approx(N, abs=5) assert plus_probability == pytest.approx( 1.0 / 4.0, abs=1e-1 ), "Statistics on |+> probability are not correct (%f vs. %f)" % ( diff --git a/projectq/setups/decompositions/qft2crandhadamard.py b/projectq/setups/decompositions/qft2crandhadamard.py index 157f8e98a..4d8f9ebb1 100755 --- a/projectq/setups/decompositions/qft2crandhadamard.py +++ b/projectq/setups/decompositions/qft2crandhadamard.py @@ -25,8 +25,8 @@ import math from projectq.cengines import DecompositionRule -from projectq.ops import H, R, QFT from projectq.meta import Control +from projectq.ops import QFT, H, R def _decompose_QFT(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/qubitop2onequbit.py b/projectq/setups/decompositions/qubitop2onequbit.py index c4cc7873b..4109759ed 100644 --- a/projectq/setups/decompositions/qubitop2onequbit.py +++ b/projectq/setups/decompositions/qubitop2onequbit.py @@ -12,9 +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. -""" -Registers a decomposition rule for a unitary QubitOperator to one qubit gates. -""" + +"""Register a decomposition rule for a unitary QubitOperator to one qubit gates.""" import cmath diff --git a/projectq/setups/decompositions/qubitop2onequbit_test.py b/projectq/setups/decompositions/qubitop2onequbit_test.py index 91d95b4d3..44567fc9b 100644 --- a/projectq/setups/decompositions/qubitop2onequbit_test.py +++ b/projectq/setups/decompositions/qubitop2onequbit_test.py @@ -17,6 +17,7 @@ import pytest +import projectq.setups.decompositions.qubitop2onequbit as qubitop2onequbit from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import ( @@ -26,11 +27,9 @@ InstructionFilter, ) from projectq.meta import Control -from projectq.ops import All, Measure, Ph, QubitOperator, X, Y, Z, Command +from projectq.ops import All, Command, Measure, Ph, QubitOperator, X, Y, Z from projectq.types import WeakQubitRef -import projectq.setups.decompositions.qubitop2onequbit as qubitop2onequbit - def test_recognize(): saving_backend = DummyEngine(save_commands=True) diff --git a/projectq/setups/decompositions/r2rzandph.py b/projectq/setups/decompositions/r2rzandph.py index dbd204721..918038e59 100755 --- a/projectq/setups/decompositions/r2rzandph.py +++ b/projectq/setups/decompositions/r2rzandph.py @@ -21,7 +21,7 @@ from projectq.cengines import DecompositionRule from projectq.meta import Control -from projectq.ops import Ph, Rz, R +from projectq.ops import Ph, R, Rz def _decompose_R(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/rx2rz.py b/projectq/setups/decompositions/rx2rz.py index eb64f63bf..e7839461e 100644 --- a/projectq/setups/decompositions/rx2rz.py +++ b/projectq/setups/decompositions/rx2rz.py @@ -12,13 +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. -""" -Registers a decomposition for the Rx gate into an Rz gate and Hadamard. -""" + +"""Register a decomposition for the Rx gate into an Rz gate and Hadamard.""" from projectq.cengines import DecompositionRule -from projectq.meta import Compute, Control, get_control_count, Uncompute -from projectq.ops import Rx, Rz, H +from projectq.meta import Compute, Control, Uncompute, get_control_count +from projectq.ops import H, Rx, Rz def _decompose_rx(cmd): diff --git a/projectq/setups/decompositions/ry2rz.py b/projectq/setups/decompositions/ry2rz.py index 4dc3dca1c..040907b3f 100644 --- a/projectq/setups/decompositions/ry2rz.py +++ b/projectq/setups/decompositions/ry2rz.py @@ -12,14 +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. -""" -Registers a decomposition for the Ry gate into an Rz and Rx(pi/2) gate. -""" + +"""Register a decomposition for the Ry gate into an Rz and Rx(pi/2) gate.""" import math from projectq.cengines import DecompositionRule -from projectq.meta import Compute, Control, get_control_count, Uncompute +from projectq.meta import Compute, Control, Uncompute, get_control_count from projectq.ops import Rx, Ry, Rz diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index 380b14a19..f61c10a11 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -16,15 +16,13 @@ # Module uses ideas from "Basic circuit compilation techniques for an # ion-trap quantum machine" by Dmitri Maslov (2017) at # https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 -""" -Registers a decomposition for the Rz gate into an Rx and Ry(pi/2) or Ry(-pi/2) -gate -""" + +"""Registers a decomposition for the Rz gate into an Rx and Ry(pi/2) or Ry(-pi/2) gate.""" import math from projectq.cengines import DecompositionRule -from projectq.meta import Compute, Control, get_control_count, Uncompute +from projectq.meta import Compute, Control, Uncompute, get_control_count from projectq.ops import Rx, Ry, Rz diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py index f1ca4a826..63dbc1f60 100644 --- a/projectq/setups/decompositions/rz2rx_test.py +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -16,6 +16,7 @@ "Tests for projectq.setups.decompositions.rz2rx.py" import math + import numpy as np import pytest diff --git a/projectq/setups/decompositions/sqrtswap2cnot.py b/projectq/setups/decompositions/sqrtswap2cnot.py index 4c9ce919a..8e5392a51 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot.py +++ b/projectq/setups/decompositions/sqrtswap2cnot.py @@ -12,9 +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. -""" -Registers a decomposition to achieve a SqrtSwap gate. -""" + +"""Register a decomposition to achieve a SqrtSwap gate.""" from projectq.cengines import DecompositionRule from projectq.meta import Compute, Control, Uncompute @@ -23,7 +22,6 @@ def _decompose_sqrtswap(cmd): """Decompose (controlled) swap gates.""" - 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): diff --git a/projectq/setups/decompositions/sqrtswap2cnot_test.py b/projectq/setups/decompositions/sqrtswap2cnot_test.py index ace87a94e..0ec4706cd 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot_test.py +++ b/projectq/setups/decompositions/sqrtswap2cnot_test.py @@ -16,6 +16,7 @@ import pytest +import projectq.setups.decompositions.sqrtswap2cnot as sqrtswap2cnot from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import ( @@ -24,11 +25,9 @@ DummyEngine, InstructionFilter, ) -from projectq.ops import All, Measure, SqrtSwap, Command +from projectq.ops import All, Command, Measure, SqrtSwap from projectq.types import WeakQubitRef -import projectq.setups.decompositions.sqrtswap2cnot as sqrtswap2cnot - def _decomp_gates(eng, cmd): if isinstance(cmd.gate, SqrtSwap.__class__): diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py index c82bd62b9..671bb0097 100644 --- a/projectq/setups/decompositions/stateprep2cnot.py +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -12,9 +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. -""" -Registers decomposition for StatePreparation. -""" + +"""Register decomposition for StatePreparation.""" import cmath import math @@ -22,17 +21,15 @@ from projectq.cengines import DecompositionRule from projectq.meta import Control, Dagger from projectq.ops import ( + Ph, StatePreparation, UniformlyControlledRy, UniformlyControlledRz, - Ph, ) def _decompose_state_preparation(cmd): # pylint: disable=too-many-locals - """ - Implements state preparation based on arXiv:quant-ph/0407010v1. - """ + """Implement state preparation based on arXiv:quant-ph/0407010v1.""" eng = cmd.engine if len(cmd.qubits) != 1: raise ValueError('StatePreparation does not support multiple quantum registers!') diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index ffa510ce1..7e129419f 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -15,19 +15,18 @@ """Tests for projectq.setups.decompositions.stateprep2cnot.""" import cmath -from copy import deepcopy import math +from copy import deepcopy import numpy as np import pytest import projectq -from projectq.ops import All, Command, Measure, Ry, Rz, StatePreparation, Ph +import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot +from projectq.ops import All, Command, Measure, Ph, Ry, Rz, StatePreparation from projectq.setups import restrictedgateset from projectq.types import WeakQubitRef -import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot - def test_invalid_arguments(): qb0 = WeakQubitRef(engine=None, idx=0) diff --git a/projectq/setups/decompositions/swap2cnot.py b/projectq/setups/decompositions/swap2cnot.py index c7c438824..df17b4e4c 100755 --- a/projectq/setups/decompositions/swap2cnot.py +++ b/projectq/setups/decompositions/swap2cnot.py @@ -20,8 +20,8 @@ """ from projectq.cengines import DecompositionRule -from projectq.meta import Compute, Uncompute, Control -from projectq.ops import Swap, CNOT +from projectq.meta import Compute, Control, Uncompute +from projectq.ops import CNOT, Swap def _decompose_swap(cmd): diff --git a/projectq/setups/decompositions/time_evolution.py b/projectq/setups/decompositions/time_evolution.py index 1bce70fd5..9453f767e 100644 --- a/projectq/setups/decompositions/time_evolution.py +++ b/projectq/setups/decompositions/time_evolution.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Registers decomposition for the TimeEvolution gates. +Register decomposition for the TimeEvolution gates. An exact straight forward decomposition of a TimeEvolution gate is possible if the hamiltonian has only one term or if all the terms commute with each @@ -22,14 +22,12 @@ import math from projectq.cengines import DecompositionRule -from projectq.meta import Control, Compute, Uncompute -from projectq.ops import TimeEvolution, QubitOperator, H, CNOT, Rz, Rx, Ry +from projectq.meta import Compute, Control, Uncompute +from projectq.ops import CNOT, H, QubitOperator, Rx, Ry, Rz, TimeEvolution def _recognize_time_evolution_commuting_terms(cmd): - """ - Recognize all TimeEvolution gates with >1 terms but which all commute. - """ + """Recognize all TimeEvolution gates with >1 terms but which all commute.""" hamiltonian = cmd.gate.hamiltonian if len(hamiltonian.terms) == 1: return False @@ -61,7 +59,7 @@ def _recognize_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. + Implement a TimeEvolution gate with a hamiltonian having only one term. To implement exp(-i * t * hamiltonian), where the hamiltonian is only one term, e.g., hamiltonian = X0 x Y1 X Z2, we first perform local diff --git a/projectq/setups/decompositions/time_evolution_test.py b/projectq/setups/decompositions/time_evolution_test.py index 293aba089..1710d5791 100644 --- a/projectq/setups/decompositions/time_evolution_test.py +++ b/projectq/setups/decompositions/time_evolution_test.py @@ -19,19 +19,29 @@ import numpy import pytest import scipy -from scipy import sparse as sps import scipy.sparse.linalg +from scipy import sparse as sps from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import ( - DummyEngine, AutoReplacer, - InstructionFilter, DecompositionRuleSet, + DummyEngine, + InstructionFilter, ) from projectq.meta import Control -from projectq.ops import QubitOperator, TimeEvolution, ClassicalInstructionGate, Ph, Rx, Ry, All, Measure, Command +from projectq.ops import ( + All, + ClassicalInstructionGate, + Command, + Measure, + Ph, + QubitOperator, + Rx, + Ry, + TimeEvolution, +) from projectq.types import WeakQubitRef from . import time_evolution as te @@ -143,7 +153,7 @@ def test_decompose_individual_terms_invalid(): qb1 = WeakQubitRef(eng, idx=1) op1 = QubitOperator("X0 Y1", 0.5) op2 = op1 + QubitOperator("Y2 X4", -0.5) - op3 = QubitOperator(tuple(), 0.5) + op3 = QubitOperator((), 0.5) op4 = QubitOperator("X0 Y0", 0.5) with pytest.raises(ValueError): diff --git a/projectq/setups/decompositions/toffoli2cnotandtgate.py b/projectq/setups/decompositions/toffoli2cnotandtgate.py index c3e794d75..cdaaf5bd6 100755 --- a/projectq/setups/decompositions/toffoli2cnotandtgate.py +++ b/projectq/setups/decompositions/toffoli2cnotandtgate.py @@ -20,7 +20,7 @@ from projectq.cengines import DecompositionRule from projectq.meta import get_control_count -from projectq.ops import NOT, CNOT, T, Tdag, H +from projectq.ops import CNOT, NOT, H, T, Tdag def _decompose_toffoli(cmd): diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py index a04263b18..62571e82e 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py @@ -12,9 +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. -""" -Registers decomposition for UnformlyControlledRy and UnformlyControlledRz. -""" + +"""Register decomposition for UnformlyControlledRy and UnformlyControlledRz.""" from projectq.cengines import DecompositionRule from projectq.meta import Compute, Control, CustomUncompute diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py index 7361d4292..1623ce51c 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py @@ -16,6 +16,7 @@ import pytest +import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import ( @@ -24,7 +25,6 @@ DummyEngine, InstructionFilter, ) - from projectq.meta import Compute, Control, Uncompute from projectq.ops import ( All, @@ -36,8 +36,6 @@ X, ) -import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot - def slow_implementation(angles, control_qubits, target_qubit, eng, gate_class): """ diff --git a/projectq/setups/default.py b/projectq/setups/default.py index 942b66894..8ac280417 100755 --- a/projectq/setups/default.py +++ b/projectq/setups/default.py @@ -14,20 +14,23 @@ # limitations under the License. """ -Defines the default setup which provides an `engine_list` for the `MainEngine` +The default setup which provides an `engine_list` for the `MainEngine`. -It contains `LocalOptimizers` and an `AutoReplacer` which uses most of the -decompositions rules defined in projectq.setups.decompositions +It contains `LocalOptimizers` and an `AutoReplacer` which uses most of the decompositions rules defined in +projectq.setups.decompositions """ import projectq import projectq.setups.decompositions -from projectq.cengines import TagRemover, LocalOptimizer, AutoReplacer, DecompositionRuleSet +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + LocalOptimizer, + TagRemover, +) def get_engine_list(): - """ - Return the default list of compiler engine. - """ + """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 49dd393fb..1261a72d4 100644 --- a/projectq/setups/grid.py +++ b/projectq/setups/grid.py @@ -13,7 +13,7 @@ # 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. +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 @@ -30,7 +30,7 @@ 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. + Return 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 diff --git a/projectq/setups/grid_test.py b/projectq/setups/grid_test.py index 3794e33f0..20ed6a7f2 100644 --- a/projectq/setups/grid_test.py +++ b/projectq/setups/grid_test.py @@ -17,11 +17,10 @@ import pytest import projectq +import projectq.setups.grid as grid_setup from projectq.cengines import DummyEngine, GridMapper from projectq.libs.math import AddConstant -from projectq.ops import BasicGate, CNOT, H, Measure, Rx, Rz, Swap, X - -import projectq.setups.grid as grid_setup +from projectq.ops import CNOT, BasicGate, H, Measure, Rx, Rz, Swap, X def test_mapper_present_and_correct_params(): diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index 559f8efdc..bab8ee824 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -13,32 +13,34 @@ # See the License for the specific language governing permissions and # limitations under the License. """ +A setup for IBM quantum chips. + Defines a setup allowing to compile code for the IBM quantum chips: -->Any 5 qubit devices -->the ibmq online simulator -->the melbourne 15 qubit device +* Any 5 qubit devices +* the ibmq online simulator +* the melbourne 15 qubit device + +It provides the `engine_list` for the `MainEngine' based on the requested device. -It provides the `engine_list` for the `MainEngine' based on the requested -device. Decompose the circuit into a Rx/Ry/Rz/H/CNOT gate set that will be -translated in the backend in the U1/U2/U3/CX gate set. +Decompose the circuit into a Rx/Ry/Rz/H/CNOT gate set that will be translated in the backend in the U1/U2/U3/CX gate +set. """ -from projectq.setups import restrictedgateset -from projectq.ops import Rx, Ry, Rz, H, CNOT, Barrier +from projectq.backends._exceptions import DeviceNotHandledError, DeviceOfflineError +from projectq.backends._ibm._ibm_http_client import show_devices from projectq.cengines import ( - LocalOptimizer, - IBM5QubitMapper, - SwapAndCNOTFlipper, BasicMapperEngine, GridMapper, + IBM5QubitMapper, + LocalOptimizer, + SwapAndCNOTFlipper, ) -from projectq.backends._ibm._ibm_http_client import show_devices +from projectq.ops import CNOT, Barrier, H, Rx, Ry, Rz +from projectq.setups import restrictedgateset def get_engine_list(token=None, device=None): - """ - Return the default list of compiler engine for the IBM QE platform - """ + """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.. @@ -61,7 +63,7 @@ def get_engine_list(token=None, device=None): # Note: Manual Mapper doesn't work, because its map is updated only if # gates are applied if gates in the register are not used, then it # will lead to state errors - res = dict() + res = {} for i in range(devices[device]['nq']): res[i] = i mapper.current_mapping = res @@ -112,18 +114,8 @@ def get_engine_list(token=None, device=None): return setup -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" - - -class DeviceNotHandledError(Exception): - """Exception raised if a selected device is cannot handle the circuit""" - - def list2set(coupling_list): - """ - Convert a list() to a set() - """ + """Convert a list() to a set().""" result = [] for element in coupling_list: result.append(tuple(element)) diff --git a/projectq/setups/ibm_test.py b/projectq/setups/ibm_test.py index 4b3bebc19..d7ca80710 100644 --- a/projectq/setups/ibm_test.py +++ b/projectq/setups/ibm_test.py @@ -21,21 +21,19 @@ 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 = { + (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, @@ -67,21 +65,19 @@ 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), - ] - ) + connections = { + (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) diff --git a/projectq/setups/ionq.py b/projectq/setups/ionq.py index 985faa19d..492c16b1b 100644 --- a/projectq/setups/ionq.py +++ b/projectq/setups/ionq.py @@ -14,11 +14,13 @@ # limitations under the License. """ +A setup for IonQ trapped ion devices. + 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._exceptions import DeviceOfflineError from projectq.backends._ionq._ionq_http_client import show_devices from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper from projectq.ops import ( @@ -44,9 +46,7 @@ def get_engine_list(token=None, device=None): - """ - Return the default list of compiler engine for the IonQ platform - """ + """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/ionq_test.py b/projectq/setups/ionq_test.py index f7ce5a9c4..d319bc95f 100644 --- a/projectq/setups/ionq_test.py +++ b/projectq/setups/ionq_test.py @@ -16,7 +16,7 @@ import pytest -from projectq.backends._ionq._ionq_exc import DeviceOfflineError +from projectq.backends._exceptions import DeviceOfflineError from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper diff --git a/projectq/setups/linear.py b/projectq/setups/linear.py index b0ff5a7e8..223ac2d26 100644 --- a/projectq/setups/linear.py +++ b/projectq/setups/linear.py @@ -13,7 +13,7 @@ # 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. +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 @@ -29,7 +29,7 @@ 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. + Return 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 diff --git a/projectq/setups/linear_test.py b/projectq/setups/linear_test.py index 45c480475..7e838f460 100644 --- a/projectq/setups/linear_test.py +++ b/projectq/setups/linear_test.py @@ -17,11 +17,10 @@ import pytest import projectq +import projectq.setups.linear as linear_setup from projectq.cengines import DummyEngine, LinearMapper from projectq.libs.math import AddConstant -from projectq.ops import BasicGate, CNOT, H, Measure, Rx, Rz, Swap, X - -import projectq.setups.linear as linear_setup +from projectq.ops import CNOT, BasicGate, H, Measure, Rx, Rz, Swap, X def test_mapper_present_and_correct_params(): diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index c6d7f034f..86811b682 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -15,10 +15,9 @@ """ Defines a setup to compile to a restricted gate set. -It provides the `engine_list` for the `MainEngine`. This engine list contains -an AutoReplacer with most of the gate decompositions of ProjectQ, which are -used to decompose a circuit into a restricted gate set (with some limitions -on the choice of gates). +It provides the `engine_list` for the `MainEngine`. This engine list contains an AutoReplacer with most of the gate +decompositions of ProjectQ, which are used to decompose a circuit into a restricted gate set (with some limitions on +the choice of gates). """ import inspect @@ -33,20 +32,13 @@ LocalOptimizer, TagRemover, ) -from projectq.ops import ( - BasicGate, - ClassicalInstructionGate, - CNOT, - ControlledGate, -) +from projectq.ops import CNOT, BasicGate, ClassicalInstructionGate, ControlledGate -from ._utils import one_and_two_qubit_gates, high_level_gates +from ._utils import high_level_gates, one_and_two_qubit_gates def default_chooser(cmd, decomposition_list): # pylint: disable=unused-argument - """ - Default chooser function for the AutoReplacer compiler engine. - """ + """Provide the default chooser function for the AutoReplacer compiler engine.""" return decomposition_list[0] @@ -57,21 +49,17 @@ def get_engine_list( # pylint: disable=too-many-branches,too-many-statements compiler_chooser=default_chooser, ): """ - Returns an engine list to compile to a restricted gate set. + Return an engine list to compile to a restricted gate set. 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(one_qubit_gates=(Rz, Ry, Rx, H), @@ -79,28 +67,21 @@ def get_engine_list( # pylint: disable=too-many-branches,too-many-statements other_gates=(TimeEvolution,)) Args: - one_qubit_gates: "any" allows any one qubit gate, otherwise provide a - tuple of the allowed gates. If the gates are - 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. + 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,). - other_gates: A tuple of the allowed gates. If the gates are - instances of a class (e.g. QFT), it allows all gates - which are equal to it. If the gate is a class, it - allows all instances of this class. - compiler_chooser:function selecting the decomposition to use in the - Autoreplacer engine + other_gates: A tuple of the allowed gates. If the gates are instances of a class (e.g. QFT), it allows all + gates which are equal to it. If the gate is a class, it allows all instances of this class. + compiler_chooser:function selecting the decomposition to use in the Autoreplacer engine + Raises: - TypeError: If input is for the gates is not "any" or a tuple. Also if - element within tuple is not a class or instance of BasicGate - (e.g. CRz which is a shortcut function) + TypeError: If input is for the gates is not "any" or a tuple. Also if element within tuple is not a class or + instance of BasicGate (e.g. CRz which is a shortcut function) Returns: A list of suitable compiler engines. diff --git a/projectq/setups/restrictedgateset_test.py b/projectq/setups/restrictedgateset_test.py index bf2a7c8b4..163386902 100644 --- a/projectq/setups/restrictedgateset_test.py +++ b/projectq/setups/restrictedgateset_test.py @@ -17,15 +17,17 @@ import pytest import projectq +import projectq.setups.restrictedgateset as restrictedgateset from projectq.cengines import DummyEngine from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN +from projectq.meta import Control from projectq.ops import ( - BasicGate, CNOT, + QFT, + BasicGate, CRz, H, Measure, - QFT, QubitOperator, Rx, Rz, @@ -34,9 +36,6 @@ Toffoli, X, ) -from projectq.meta import Control - -import projectq.setups.restrictedgateset as restrictedgateset def test_parameter_any(): diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index 4472b7c65..0eda14f37 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -25,15 +25,14 @@ 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. +Note: + Because the decomposition chooser is only called when a gate has to be decomposed, this reduction will work better + when the entire circuit has to be decomposed. Otherwise, If the circuit has both superconding gates and native ion + trapped gates the decomposed circuit will not be optimal. """ +from projectq.ops import Rx, Rxx, Ry from projectq.setups import restrictedgateset -from projectq.ops import Rxx, Rx, Ry # ------------------chooser_Ry_reducer-------------------# # If the qubit is not in the prev_Ry_sign dictionary, then no decomposition @@ -43,7 +42,7 @@ # 1 then the last gate applied (during a decomposition!) was Ry(+math.pi/2) # 0 then the last gate applied (during a decomposition!) was a Rx -prev_Ry_sign = dict() # Keeps track of most recent Ry sign, i.e. +prev_Ry_sign = {} # Keeps track of most recent Ry sign, i.e. # whether we had Ry(-pi/2) or Ry(pi/2) # prev_Ry_sign[qubit_index] should hold -1 or # +1 @@ -51,8 +50,10 @@ 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. + Choose the decomposition to maximise Ry cancellations. + + Choose the decomposition so as to maximise Ry cancellations, based on the previous decomposition used for the + given qubit. Note: Classical instructions gates e.g. Flush and Measure are automatically @@ -61,7 +62,7 @@ def chooser_Ry_reducer(cmd, decomposition_list): # pylint: disable=invalid-name Returns: A decomposition object from the decomposition_list. """ - decomp_rule = dict() + decomp_rule = {} name = 'default' for decomp in decomposition_list: @@ -77,7 +78,7 @@ def chooser_Ry_reducer(cmd, decomposition_list): # pylint: disable=invalid-name except IndexError: pass - local_prev_Ry_sign = prev_Ry_sign.setdefault(cmd.engine, dict()) # pylint: disable=invalid-name + local_prev_Ry_sign = prev_Ry_sign.setdefault(cmd.engine, {}) # pylint: disable=invalid-name if name == 'cnot2rxx': ctrl_id = cmd.control_qubits[0].id @@ -124,16 +125,12 @@ def chooser_Ry_reducer(cmd, decomposition_list): # pylint: disable=invalid-name def get_engine_list(): """ - Returns an engine list compiling code into a trapped ion based compiled - circuit code. + Return an engine list compiling code into a trapped ion based compiled circuit code. Note: - - - Classical instructions gates such as e.g. Flush and Measure are - automatically allowed. - - The restricted gate set engine does not work with Rxx gates, as - ProjectQ will by default bounce back and forth between Cz gates and Cx - gates. An appropriate decomposition chooser needs to be used! + - Classical instructions gates such as e.g. Flush and Measure are automatically allowed. + - The restricted gate set engine does not work with Rxx gates, as ProjectQ will by default bounce back and + forth between Cz gates and Cx gates. An appropriate decomposition chooser needs to be used! Returns: A list of suitable compiler engines. diff --git a/projectq/setups/trapped_ion_decomposer_test.py b/projectq/setups/trapped_ion_decomposer_test.py index 7aaea0ff3..ba5d1518d 100644 --- a/projectq/setups/trapped_ion_decomposer_test.py +++ b/projectq/setups/trapped_ion_decomposer_test.py @@ -16,17 +16,17 @@ "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, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, + MainEngine, + TagRemover, ) from projectq.meta import get_control_count +from projectq.ops import CNOT, ClassicalInstructionGate, H, Measure, Rx, Rxx, Ry, Rz, X from . import restrictedgateset from .trapped_ion_decomposer import chooser_Ry_reducer, get_engine_list diff --git a/projectq/tests/_factoring_test.py b/projectq/tests/_factoring_test.py index 88ee0db4a..a25a344c9 100755 --- a/projectq/tests/_factoring_test.py +++ b/projectq/tests/_factoring_test.py @@ -19,16 +19,16 @@ import projectq.setups.decompositions from projectq.backends._sim._simulator_test import sim from projectq.cengines import ( - MainEngine, AutoReplacer, DecompositionRuleSet, InstructionFilter, LocalOptimizer, + MainEngine, 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 QFT, All, BasicMathGate, H, Measure, Swap, X, get_inverse rule_set = DecompositionRuleSet(modules=(projectq.libs.math, projectq.setups.decompositions)) diff --git a/projectq/types/_qubit.py b/projectq/types/_qubit.py index 207efd08d..74daedcac 100755 --- a/projectq/types/_qubit.py +++ b/projectq/types/_qubit.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -This file defines BasicQubit, Qubit, WeakQubit and Qureg. +Definition of BasicQubit, Qubit, WeakQubit and Qureg classes. A Qureg represents a list of Qubit or WeakQubit objects. A Qubit represents a (logical-level) qubit with a unique index provided by the MainEngine. Qubit objects are @@ -51,21 +51,15 @@ def __init__(self, engine, idx): self.engine = engine def __str__(self): - """ - Return string representation of this qubit. - """ + """Return string representation of this qubit.""" return str(self.id) 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 __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)) def __eq__(self, other): @@ -79,9 +73,6 @@ def __eq__(self, other): return self is other return isinstance(other, BasicQubit) and self.id == other.id and self.engine == other.engine - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): """ Return the hash of this qubit. @@ -104,9 +95,7 @@ class Qubit(BasicQubit): """ def __del__(self): - """ - Destroy the qubit and deallocate it (automatically). - """ + """Destroy the qubit and deallocate it (automatically).""" 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 @@ -185,9 +174,7 @@ def __int__(self): ) def __str__(self): - """ - Get string representation of a quantum register. - """ + """Get string representation of a quantum register.""" if len(self) == 0: return "Qureg[]" @@ -210,15 +197,11 @@ def __str__(self): @property def engine(self): - """ - Return owning engine. - """ + """Return owning engine.""" return self[0].engine @engine.setter def engine(self, eng): - """ - Set owning engine. - """ + """Set owning engine.""" for qb in self: qb.engine = eng diff --git a/projectq/types/_qubit_test.py b/projectq/types/_qubit_test.py index 54287749c..35fc961e6 100755 --- a/projectq/types/_qubit_test.py +++ b/projectq/types/_qubit_test.py @@ -73,7 +73,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({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 index 9f58329ba..b7e82e9ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,8 +57,6 @@ build-backend = "setuptools.build_meta" '.*_test.py', '.*_fixtures.py', '.*flycheck.*.py', - 'docs/.*', - 'examples/.*', ] extension-pkg-whitelist = [ @@ -102,6 +100,10 @@ testpaths = ['projectq'] ignore-glob = ['*flycheck*.py'] mock_use_standalone_module = true +[tool.isort] + +profile = "black" + [tool.setuptools_scm] write_to = 'VERSION.txt' diff --git a/setup.cfg b/setup.cfg index f01474cb7..15407bf54 100644 --- a/setup.cfg +++ b/setup.cfg @@ -66,10 +66,10 @@ max-line-length = 120 exclude = .git, __pycache__, - docs/conf.py, build, dist, __init__.py docstring-quotes = """ +eradicate-whitelist = # yapf: disable# yapf: enable # ============================================================================== diff --git a/setup.py b/setup.py index acb57eb05..bb9402ed1 100755 --- a/setup.py +++ b/setup.py @@ -36,26 +36,26 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. -"""Setup.py file""" +"""Setup.py file.""" import distutils.log +import os +import platform +import subprocess +import sys +import tempfile from distutils.cmd import Command -from distutils.spawn import find_executable, spawn from distutils.errors import ( - CompileError, - LinkError, CCompilerError, + CompileError, DistutilsExecError, DistutilsPlatformError, + LinkError, ) -import os -import platform -import subprocess -import sys -import tempfile +from distutils.spawn import find_executable, spawn -from setuptools import setup, Extension from setuptools import Distribution as _Distribution +from setuptools import Extension, setup from setuptools.command.build_ext import build_ext # ============================================================================== @@ -64,23 +64,25 @@ 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. + 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): + """Initialize a Pybind11Include object.""" self.user = user def __str__(self): + """Conversion to string.""" import pybind11 # pylint: disable=import-outside-toplevel return pybind11.get_include(self.user) def important_msgs(*msgs): - """ - Print an important message. - """ + """Print an important message.""" print('*' * 75) for msg in msgs: print(msg) @@ -88,9 +90,7 @@ def important_msgs(*msgs): def status_msgs(*msgs): - """ - Print a status message. - """ + """Print a status message.""" print('-' * 75) for msg in msgs: print('# INFO: ', msg) @@ -100,11 +100,7 @@ def status_msgs(*msgs): 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. - """ - + """Return a boolean indicating whether a flag name is supported on the specified compiler.""" 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)) @@ -167,9 +163,10 @@ def _fix_macosx_header_paths(*args): class BuildFailed(Exception): - """Extension raised if the build fails for any reason""" + """Extension raised if the build fails for any reason.""" def __init__(self): + """Initialize a BuildFailed exception object.""" super().__init__() self.cause = sys.exc_info()[1] # work around py 2/3 different syntax @@ -204,7 +201,7 @@ def __init__(self): class BuildExt(build_ext): - '''A custom build extension for adding compiler-specific options.''' + """A custom build extension for adding compiler-specific options.""" c_opts = { 'msvc': ['/EHsc'], @@ -222,21 +219,25 @@ class BuildExt(build_ext): boolean_options = build_ext.boolean_options + ['gen-compiledb'] def initialize_options(self): + """Initialize this command's options.""" build_ext.initialize_options(self) self.gen_compiledb = None def finalize_options(self): + """Finalize this command's options.""" build_ext.finalize_options(self) if self.gen_compiledb: self.dry_run = True # pylint: disable=attribute-defined-outside-init def run(self): + """Execute this command.""" try: build_ext.run(self) except DistutilsPlatformError as err: raise BuildFailed() from err def build_extensions(self): + """Build the individual C/C++ extensions.""" self._configure_compiler() for ext in self.extensions: @@ -492,7 +493,7 @@ def _cleanup_compiler_flags(self): class ClangTidy(Command): - """A custom command to run Clang-Tidy on all C/C++ source files""" + """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')] @@ -501,12 +502,14 @@ class ClangTidy(Command): sub_commands = [('build_ext', None)] def initialize_options(self): + """Initialize this command's options.""" self.warning_as_errors = None def finalize_options(self): - pass + """Finalize this command's options.""" def run(self): + """Execute this command.""" # 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 @@ -534,7 +537,7 @@ def run(self): class GenerateRequirementFile(Command): - """A custom command to list the dependencies of the current""" + """A custom command to list the dependencies of the current.""" description = 'List the dependencies of the current package' user_options = [ @@ -545,11 +548,13 @@ class GenerateRequirementFile(Command): boolean_options = ['include-all-extras'] def initialize_options(self): + """Initialize this command's options.""" self.include_extras = None self.include_all_extras = None self.extra_pkgs = [] def finalize_options(self): + """Finalize this command's options.""" include_extras = self.include_extras.split(',') try: @@ -563,6 +568,7 @@ def finalize_options(self): self.extra_pkgs.extend(pkgs) def run(self): + """Execute this command.""" with open('requirements.txt', 'w') as req_file: try: for pkg in self.distribution.install_requires: @@ -579,10 +585,10 @@ def run(self): class Distribution(_Distribution): - """Distribution class""" + """Distribution class.""" def has_ext_modules(self): # pylint: disable=no-self-use - """Return whether this distribution has some external modules""" + """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 @@ -596,7 +602,7 @@ def has_ext_modules(self): # pylint: disable=no-self-use def run_setup(with_cext): - """Run the setup() function""" + """Run the setup() function.""" kwargs = {} if with_cext: kwargs['ext_modules'] = ext_modules From a1c8d522721b79ef74a668813af82fccb16002ae Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Wed, 14 Jul 2021 17:24:44 +0000 Subject: [PATCH 067/113] Preparing release v0.7.0 --- CHANGELOG.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5adc9dbbf..83ebb573d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.0] - 2021-07-14 + ### Added -- UnitarySimulator backend for computing the unitary transformation corresponding to a quantum circuit. +- UnitarySimulator backend for computing the unitary transformation corresponding to a quantum circuit. ### Changed + ### Deprecated + ### Fixed - Prevent infinite recursion errors when too many compiler engines are added to the MainEngine @@ -34,7 +38,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix GitHub workflow for publishing a new release - ## [0.6.0] - 2021-06-23 ### Added @@ -66,17 +69,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository - 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 @@ -117,6 +126,8 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi 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 +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.7.0...HEAD + +[0.7.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.6.1...0.7.0 [0.6.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.5.1...0.6.0 From 8ef487d10265e3a76b722fd2918a459de4f36cfb Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Thu, 15 Jul 2021 17:05:23 +0200 Subject: [PATCH 068/113] Update CHANGELOG --- CHANGELOG.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ebb573d..530441a8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,20 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +### Changed +### Deprecated +### Fixed +### Removed +### Repository + ## [0.7.0] - 2021-07-14 ### Added -- UnitarySimulator backend for computing the unitary transformation corresponding to a quantum circuit. +- UnitarySimulator backend for computing the unitary transformation corresponding to a quantum circuit ### Changed +- Moved some exceptions classes into their own files to avoid code duplication + ### Deprecated ### Fixed - Prevent infinite recursion errors when too many compiler engines are added to the MainEngine - Error in testing the decomposition for the phase estimation gate +- Fixed small issue with matplotlib drawing backend - Make all docstrings PEP257 compliant ### Removed @@ -30,7 +40,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository - Added `isort` to the list of pre-commit hooks -- Added `flake8-docstrings` to the flake8 checks to ensure PEP257 compliance for docstrings +- Added some more flake8 plugins to the list used by `pre-commit`: + + flake8-breakpoint + + flake8-comprehensions + + flake8-docstrings + + flake8-eradicate + + flake8-mutable ## [0.6.1] - 2021-06-23 @@ -126,8 +141,12 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi 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.7.0...HEAD +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.0...HEAD + +[0.7.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.6.1...v0.7.0 + +[0.6.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.5.1...v0.6.0 -[0.7.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.6.1...0.7.0 +[0.5.1]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.5.0...v0.5.1 -[0.6.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.5.1...0.6.0 +[0.5.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.4.2...v0.5.0 From 379d0f98c0b22459a67dc23b8b5f152054e59205 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Aug 2021 12:31:09 +0200 Subject: [PATCH 069/113] Bump thomaseizinger/create-pull-request from 1.1.0 to 1.2.1 (#414) * Bump thomaseizinger/create-pull-request from 1.1.0 to 1.2.1 Bumps [thomaseizinger/create-pull-request](https://github.com/thomaseizinger/create-pull-request) from 1.1.0 to 1.2.1. - [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.1.0...1.2.1) --- 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: Nguyen Damien --- .github/workflows/draft_release.yml | 2 +- .github/workflows/publish_release.yml | 2 +- CHANGELOG.md | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index e83fc5c07..f6b586e1d 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.1.0 + uses: thomaseizinger/create-pull-request@1.2.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index d0f54099a..1ec5f6b34 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -188,7 +188,7 @@ jobs: - upload_to_pypi steps: - name: Merge master into develop branch - uses: thomaseizinger/create-pull-request@1.1.0 + uses: thomaseizinger/create-pull-request@1.2.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 530441a8e..541c82b73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### Repository +- Update `thomaseizinger/create-pull-request` GiHub action to v1.2.1 + ## [0.7.0] - 2021-07-14 ### Added From c4bed0e6bd1eed434a9da80b3dafd5e6ff8604a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 15:41:08 +0200 Subject: [PATCH 070/113] Bump thomaseizinger/keep-a-changelog-new-release from 1.2.1 to 1.3.0 (#416) * Bump thomaseizinger/keep-a-changelog-new-release from 1.2.1 to 1.3.0 Bumps [thomaseizinger/keep-a-changelog-new-release](https://github.com/thomaseizinger/keep-a-changelog-new-release) from 1.2.1 to 1.3.0. - [Release notes](https://github.com/thomaseizinger/keep-a-changelog-new-release/releases) - [Changelog](https://github.com/thomaseizinger/keep-a-changelog-new-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/thomaseizinger/keep-a-changelog-new-release/compare/1.2.1...1.3.0) --- updated-dependencies: - dependency-name: thomaseizinger/keep-a-changelog-new-release 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 +- CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index f6b586e1d..33f361e5f 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -26,7 +26,7 @@ jobs: run: git flow release start ${{ github.event.inputs.version }} - name: Update changelog - uses: thomaseizinger/keep-a-changelog-new-release@1.2.1 + uses: thomaseizinger/keep-a-changelog-new-release@1.3.0 with: version: ${{ github.event.inputs.version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 541c82b73..e4777aa1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### Repository -- Update `thomaseizinger/create-pull-request` GiHub action to v1.2.1 +- Update `thomaseizinger/create-pull-request` GiHub action to v1.3.0 ## [0.7.0] - 2021-07-14 From c26f70addd2c1d845429e7c1d256fda95f0fa3af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Dec 2021 15:31:21 +0100 Subject: [PATCH 071/113] Bump thomaseizinger/create-pull-request from 1.2.1 to 1.2.2 (#419) * Bump thomaseizinger/create-pull-request from 1.2.1 to 1.2.2 Bumps [thomaseizinger/create-pull-request](https://github.com/thomaseizinger/create-pull-request) from 1.2.1 to 1.2.2. - [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.2.1...1.2.2) --- updated-dependencies: - dependency-name: thomaseizinger/create-pull-request 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: Nguyen Damien --- .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 33f361e5f..0e8c4a82a 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.2.1 + uses: thomaseizinger/create-pull-request@1.2.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 1ec5f6b34..bdbbbae3e 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -188,7 +188,7 @@ jobs: - upload_to_pypi steps: - name: Merge master into develop branch - uses: thomaseizinger/create-pull-request@1.2.1 + uses: thomaseizinger/create-pull-request@1.2.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/CHANGELOG.md b/CHANGELOG.md index e4777aa1a..47eea8239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### Repository -- Update `thomaseizinger/create-pull-request` GiHub action to v1.3.0 +- Update `thomaseizinger/keep-a-changelog-new-release` GiHub action to v1.3.0 +- Update `thomaseizinger/create-pull-request` GiHub action to v1.2.2 ## [0.7.0] - 2021-07-14 From a2234259fb945691f11b3758439b340a466db8cb Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Wed, 5 Jan 2022 20:48:12 +0100 Subject: [PATCH 072/113] Update CI setup (#422) * Fix compiler flags cleanup routine * Update CHANGELOG * Use clang for linking on Clang CI workflows * Fix compiler_test(...) function in setup.py * Fix CI failure when testing locally * Remove LDSHARED for Clang build * Fix linter warnings in setup.py * Update CHANGELOG * Update more workflow files for local actions * Remove deprecated GitHub action for pre-commit * Update CHANGELOG * Update pre-commit hooks versions * Refrain from updating pylint pre-commit hook for now * Fix issue with running pre-commit on CI * Add cache support for pre-commit on CI * Fix name of caching step for pre-commit on CI * Add Python 3.10 for testing on CI * Add Python 3.10 in package metadata * Fix gen_reqfile command if --include-extras is not provided * Add setup option to avoid -march=native when building ProjectQ * Add support for CIBuildWheel in pyproject.toml * Tweak environment variables used during setup * Avoid Linux Musl in CIBuildWheel config * Update GitHub Workflow for publishing a release --- .github/workflows/ci.yml | 8 ++ .github/workflows/format.yml | 18 ++++- .github/workflows/publish_release.yml | 27 ++++--- .pre-commit-config.yaml | 10 +-- CHANGELOG.md | 16 ++++ docs/tutorials.rst | 2 +- pyproject.toml | 27 +++++++ setup.cfg | 1 + setup.py | 101 ++++++++++++++++++-------- 9 files changed, 161 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58440eca0..d4e916231 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: - 3.7 - 3.8 - 3.9 + - '3.10' name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" runs-on: ${{ matrix.runs-on }} @@ -28,6 +29,7 @@ jobs: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -125,6 +127,7 @@ jobs: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -134,6 +137,7 @@ jobs: 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 + libomp-dev --no-install-recommends - name: Prepare Python env @@ -169,6 +173,7 @@ jobs: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -236,6 +241,7 @@ jobs: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -285,6 +291,7 @@ jobs: restore-keys: ${{ runner.os }}-doc-pip- - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -318,6 +325,7 @@ jobs: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 0cd0b3e16..752d64fc9 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -16,14 +16,26 @@ jobs: steps: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} 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 + + - name: Install pre-commit + run: python3 -m pip install --upgrade pre-commit 'virtualenv!=20.11' + + - name: Cache pre-commit hooks + uses: actions/cache@v2 with: - # Slow hooks are marked with manual - slow is okay here, run them too - extra_args: --hook-stage manual --all-files + path: ~/.cache/pre-commit + key: pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} + restore-keys: pre-commit- + + - name: Run pre-commit + run: | + # Slow hooks are marked with manual - slow is okay here, run them too + pre-commit run --hook-stage manual --all-files clang-tidy: name: Clang-Tidy diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index bdbbbae3e..7deff89be 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -31,6 +31,7 @@ jobs: ref: 'master' - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -69,19 +70,24 @@ jobs: # ======================================================================== - - name: Build source distribution + - uses: actions/setup-python@v2 + + - name: Install Python packages + run: python3 -m pip install -U pip setuptools build wheel + + - name: Build source distribution (Linux) if: runner.os == 'Linux' - run: python3 setup.py sdist -d wheelhouse + run: python3 -m build --sdist - name: Check metadata run: | python3 -m pip install twine --prefer-binary - python3 -m twine check wheelhouse/* + python3 -m twine check dist/* - uses: actions/upload-artifact@v2 with: - name: packages - path: ./wheelhouse/* + name: pypy_wheels + path: ./dist/* release: @@ -134,8 +140,10 @@ jobs: ref: 'master' # ------------------------------------------------------------------------ - # Downloads all to directories matching the artifact names + - uses: actions/download-artifact@v2 + with: + name: pypy_wheels # Code below inspired from this action: # - uses: taiki-e/create-gh-release-action@v1 @@ -160,7 +168,7 @@ jobs: 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/* + gh release create "v${RELEASE_VERSION}" ${prerelease:-} --title "ProjectQ v${RELEASE_VERSION}" --notes "${notes:-}" pypy_wheels/* upload_to_pypi: @@ -170,15 +178,16 @@ jobs: steps: - uses: actions/setup-python@v2 - # Downloads all to directories matching the artifact names - uses: actions/download-artifact@v2 + with: + name: pypy_wheels - name: Publish standard package uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.pypi_password }} - packages_dir: packages/ + packages_dir: pypy_wheels/ master_to_develop_pr: name: Merge master back into develop diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d179c97e..45c9f8936 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -44,13 +44,13 @@ repos: - id: remove-tabs - repo: https://github.com/PyCQA/isort - rev: 5.9.1 + rev: 5.10.1 hooks: - id: isort name: isort (python) - repo: https://github.com/psf/black - rev: 21.5b1 + rev: 21.12b0 hooks: - id: black language_version: python3 @@ -58,7 +58,7 @@ repos: stages: [manual] - repo: https://gitlab.com/PyCQA/flake8 - rev: 3.9.2 + rev: 4.0.1 hooks: - id: flake8 name: flake8-strict @@ -82,7 +82,7 @@ repos: additional_dependencies: [pybind11>=2.6, numpy, requests, boto3, matplotlib, networkx, sympy] - repo: https://github.com/mgedmin/check-manifest - rev: '0.46' + rev: '0.47' hooks: - id: check-manifest additional_dependencies: ['setuptools-scm', 'pybind11>=2.6'] diff --git a/CHANGELOG.md b/CHANGELOG.md index 47eea8239..12a6a45f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added + +- Added environment variable to avoid -march=native when building ProjectQ +- Added environment variable to force build failure if extensions do not compile on CI + ### Changed ### Deprecated ### Fixed + +- Fix compiler flags cleanup function for use on CI +- Fix workflow YAML to allow execution of GitHub Actions locally using `act` +- GitHub action using deprecated and vulnerable `pre-commit` version +- Fixed issue with `gen_reqfile` command if `--include-extras` is not provided + ### Removed ### Repository +- Add configuration for CIBuildWheel in `pyproject.toml` - Update `thomaseizinger/keep-a-changelog-new-release` GiHub action to v1.3.0 - Update `thomaseizinger/create-pull-request` GiHub action to v1.2.2 +- Update pre-commit hook `pre-commit/pre-commit-hooks` to v4.1.0 +- Update pre-commit hook `PyCQA/isort` to v5.10.1 +- Update pre-commit hook `psf/black` to v21.12b0 +- Update pre-commit hook `PyCQA/flake8` to v4.0.1 +- Update pre-commit hook `mgedmin/check-manifest` to v0.47 ## [0.7.0] - 2021-07-14 diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 4df54c9fb..58d8de1c9 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -29,7 +29,7 @@ ProjectQ comes with a high-performance quantum simulator written in C++. Please .. 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). - 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 ``PROJECTQ_DISABLE_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: diff --git a/pyproject.toml b/pyproject.toml index b7e82e9ce..1b3b2c6ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,3 +113,30 @@ local_scheme = 'no-local-version' [tool.yapf] column_limit = 120 + +[tool.cibuildwheel] + +archs = ['auto64'] +build-frontend = 'build' +build-verbosity = 1 +skip = 'pp* *-musllinux*' +environment = { PROJECTQ_DISABLE_ARCH_NATIVE='1', PROJECTQ_CI_BUILD='1' } + +before-test = [ + 'cd {package}', + 'python setup.py gen_reqfile', + 'python -m pip install -r requirements.txt --only-binary :all:', +] + +test-command = 'python3 {package}/examples/grover.py' + +# Normal options, etc. +manylinux-x86_64-image = 'manylinux2014' + +[[tool.cibuildwheel.overrides]] +select = 'cp36-*' +manylinux-x86_64-image = 'manylinux1' + +[[tool.cibuildwheel.overrides]] +select = 'cp3{7,8,9}-*' +manylinux-x86_64-image = 'manylinux2010' diff --git a/setup.cfg b/setup.cfg index 15407bf54..0f5a22bfd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ classifier = Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 [options] diff --git a/setup.py b/setup.py index bb9402ed1..46c394d1b 100755 --- a/setup.py +++ b/setup.py @@ -98,33 +98,43 @@ def status_msgs(*msgs): def compiler_test( - compiler, flagname=None, link=False, include='', body='', postargs=None -): # pylint: disable=too-many-arguments + compiler, + flagname=None, + link_executable=False, + link_shared_lib=False, + include='', + body='', + compile_postargs=None, + link_postargs=None, +): # pylint: disable=too-many-arguments,too-many-branches """Return a boolean indicating whether a flag name is supported on the specified compiler.""" 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: - postargs = [flagname] if flagname is not None else None + if compile_postargs is None: + compile_postargs = [flagname] if flagname is not None else None elif flagname is not None: - postargs.append(flagname) + compile_postargs.append(flagname) try: - exec_name = os.path.join(tempfile.mkdtemp(), 'test') - if compiler.compiler_type == 'msvc': olderr = os.dup(sys.stderr.fileno()) err = open('err.txt', 'w') # pylint: disable=consider-using-with os.dup2(err.fileno(), sys.stderr.fileno()) - obj_file = compiler.compile([fname], extra_postargs=postargs) + obj_file = compiler.compile([fname], extra_postargs=compile_postargs) if not os.path.exists(obj_file[0]): raise RuntimeError('') - if link: - compiler.link_executable(obj_file, exec_name, extra_postargs=postargs) + if link_executable: + compiler.link_executable(obj_file, os.path.join(tempfile.mkdtemp(), 'test'), extra_postargs=link_postargs) + elif link_shared_lib: + if sys.platform == 'win32': + lib_name = os.path.join(tempfile.mkdtemp(), 'test.dll') + else: + lib_name = os.path.join(tempfile.mkdtemp(), 'test.so') + compiler.link_shared_lib(obj_file, lib_name, extra_postargs=link_postargs) if compiler.compiler_type == 'msvc': err.close() @@ -133,9 +143,11 @@ def compiler_test( if err_file.readlines(): raise RuntimeError('') except (CompileError, LinkError, RuntimeError): - ret = False - os.unlink(fname) - return ret + return False + else: + return True + finally: + os.unlink(fname) def _fix_macosx_header_paths(*args): @@ -362,13 +374,13 @@ def _configure_openmp(self): return kwargs = { - 'link': True, + 'link_shared_lib': True, 'include': '#include ', 'body': 'int a = omp_get_num_threads(); ++a;', } for flag in ['-openmp', '-fopenmp', '-qopenmp', '/Qopenmp']: - if compiler_test(self.compiler, flag, **kwargs): + if compiler_test(self.compiler, flag, link_postargs=[flag], **kwargs): self.opts.append(flag) self.link_opts.append(flag) return @@ -383,7 +395,7 @@ def _configure_openmp(self): # 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, link_postargs=[l_arg, flag], **kwargs): self.opts.append(flag) self.link_opts.extend((l_arg, flag)) return @@ -404,7 +416,7 @@ def _configure_openmp(self): c_arg = '-I' + inc_dir l_arg = '-L' + lib_dir - if compiler_test(self.compiler, flag, postargs=[c_arg, l_arg], **kwargs): + if compiler_test(self.compiler, flag, compile_postargs=[c_arg], link_postargs=[l_arg], **kwargs): self.compiler.add_include_dir(inc_dir) self.compiler.add_library_dir(lib_dir) return @@ -414,17 +426,21 @@ def _configure_openmp(self): important_msgs('WARNING: compiler does not support OpenMP!') def _configure_intrinsics(self): - for flag in [ + flags = [ '-march=native', '-mavx2', '/arch:AVX2', '/arch:CORE-AVX2', '/arch:AVX', - ]: + ] + + if os.environ.get('PROJECTQ_DISABLE_ARCH_NATIVE'): + flags = flags[1:] + + for flag in flags: if compiler_test( self.compiler, flagname=flag, - link=False, include='#include ', body='__m256d neg = _mm256_set1_pd(1.0); (void)neg;', ): @@ -465,20 +481,40 @@ def _configure_cxx_standard(self): raise BuildFailed() def _cleanup_compiler_flags(self): - compiler = self.compiler.compiler[0] - compiler_so = self.compiler.compiler_so[0] + status_msgs('INFO: Performing compiler flags cleanup') + compiler_exe = self.compiler.compiler[0] + compiler_exe_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) + all_common_flags = compiler_flags & compiler_so_flags & linker_so_flags + common_compiler_flags = (compiler_flags & compiler_so_flags) - all_common_flags + + compiler_flags = compiler_flags - common_compiler_flags - all_common_flags + compiler_so_flags = compiler_so_flags - common_compiler_flags - all_common_flags + + flags = [] + for flag in common_compiler_flags: + compiler = type(self.compiler)() + compiler.set_executables(compiler=compiler_exe, compiler_so=compiler_exe_so, linker_so=linker_so) + + compiler.debug_print(f'INFO: trying out {flag}') + if compiler_test(compiler, flag, link_shared_lib=True, compile_postargs=['-fPIC']): + flags.append(flag) + else: + important_msgs('WARNING: ignoring unsupported compiler flag: {}'.format(flag)) + + self.compiler.compiler = [compiler_exe] + list(compiler_flags) + self.compiler.compiler_so = [compiler_exe_so] + list(compiler_so_flags) + self.compiler.linker_so = [linker_so] + list(linker_so_flags - all_common_flags) + + self.compiler.compiler.extend(flags) + self.compiler.compiler_so.extend(flags) flags = [] - for flag in common_flags: + for flag in all_common_flags: if compiler_test(self.compiler, flag): flags.append(flag) else: @@ -555,7 +591,7 @@ def initialize_options(self): def finalize_options(self): """Finalize this command's options.""" - include_extras = self.include_extras.split(',') + include_extras = self.include_extras.split(',') if self.include_extras else [] try: for name, pkgs in self.distribution.extras_require.items(): @@ -630,10 +666,10 @@ def run_setup(with_cext): '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'): +elif os.environ.get('PROJECTQ_DISABLE_CEXT'): run_setup(False) important_msgs( - 'DISABLE_PROJECTQ_CEXT is set; not attempting to build C/C++ extensions.', + 'PROJECTQ_DISABLE_CEXT is set; not attempting to build C/C++ extensions.', 'Plain-Python build succeeded.', ) @@ -641,6 +677,9 @@ def run_setup(with_cext): try: run_setup(True) except BuildFailed as exc: + if os.environ.get('PROJECTQ_CI_BUILD'): + raise exc + important_msgs( exc.cause, 'WARNING: Some C/C++ extensions could not be compiled, ' From b1c7e2da2817adeb20742ab3b6ced1eb0d5c6e80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jan 2022 21:05:24 +0100 Subject: [PATCH 073/113] Bump dangoslen/changelog-enforcer from 2 to 3 (#423) * Bump dangoslen/changelog-enforcer from 2 to 3 Bumps [dangoslen/changelog-enforcer](https://github.com/dangoslen/changelog-enforcer) from 2 to 3. - [Release notes](https://github.com/dangoslen/changelog-enforcer/releases) - [Changelog](https://github.com/dangoslen/changelog-enforcer/blob/master/CHANGELOG.md) - [Commits](https://github.com/dangoslen/changelog-enforcer/compare/v2...v3) --- updated-dependencies: - dependency-name: dangoslen/changelog-enforcer dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update CHANGELOG Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nguyen Damien --- .github/workflows/pull_request.yml | 2 +- CHANGELOG.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index cfcb7d0a1..acabd22ce 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v2 - id: changelog-enforcer - uses: dangoslen/changelog-enforcer@v2 + uses: dangoslen/changelog-enforcer@v3 with: changeLogPath: 'CHANGELOG.md' skipLabels: 'Skip-Changelog' diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a6a45f9..aba52cef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository - Add configuration for CIBuildWheel in `pyproject.toml` +- Update `dangoslen/changelog-enforcer` GitHub action to v3 - Update `thomaseizinger/keep-a-changelog-new-release` GiHub action to v1.3.0 - Update `thomaseizinger/create-pull-request` GiHub action to v1.2.2 - Update pre-commit hook `pre-commit/pre-commit-hooks` to v4.1.0 From b0c2607d1fe256bc154973e184a249ddc3a991a3 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Mon, 10 Jan 2022 09:56:55 +0100 Subject: [PATCH 074/113] Remove windows-2016 from GitHub workflows (#424) --- .github/workflows/ci.yml | 52 ---------------------------------------- CHANGELOG.md | 1 + 2 files changed, 1 insertion(+), 52 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4e916231..11017536f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -307,55 +307,3 @@ jobs: - 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 - if: ${{ !env.ACT }} - 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=test,braket - python -m pip install -r requirements.txt --prefer-binary - - - name: Build and install package - run: python -m pip install -ve .[braket,test] - - - name: Run all checks - run: | - echo 'backend: Agg' > matplotlibrc - python3 -m pytest -p no:warnings diff --git a/CHANGELOG.md b/CHANGELOG.md index aba52cef1..5708e21a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository - Add configuration for CIBuildWheel in `pyproject.toml` +- Remove use of deprecated images `windows-2016` in GitHub workflows - Update `dangoslen/changelog-enforcer` GitHub action to v3 - Update `thomaseizinger/keep-a-changelog-new-release` GiHub action to v1.3.0 - Update `thomaseizinger/create-pull-request` GiHub action to v1.2.2 From 9222a52d789976503580b627b7cce31c23c2db2e Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Mon, 10 Jan 2022 10:32:56 +0100 Subject: [PATCH 075/113] Re-add build of binary wheels (#425) - For now without automatic upload of binaries to PyPi --- .github/workflows/publish_release.yml | 68 +++++++++++++++++++++------ CHANGELOG.md | 1 + pyproject.toml | 4 +- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 7deff89be..2898cea5e 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -18,10 +18,18 @@ jobs: if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || github.event_name == 'workflow_dispatch' strategy: matrix: - os: - - ubuntu-latest - + cibw_archs: ["auto64"] + os: [ubuntu-latest, windows-latest, macos-latest] + # include: + # - os: ubuntu-18.04 + # cibw_archs: "aarch64" steps: + - name: Set up QEMU + if: matrix.cibw_archs == 'aarch64' + uses: docker/setup-qemu-action@v1 + with: + platforms: arm64 + - uses: actions/checkout@v2 if: github.event_name != 'workflow_dispatch' @@ -73,21 +81,53 @@ jobs: - uses: actions/setup-python@v2 - name: Install Python packages - run: python3 -m pip install -U pip setuptools build wheel + run: python -m pip install -U --prefer-binary pip setuptools build wheel twine 'cibuildwheel<3,>=2' - name: Build source distribution (Linux) if: runner.os == 'Linux' - run: python3 -m build --sdist + id: src-dist + run: | + python -m build --sdist + python -m twine check dist/* - - name: Check metadata + - name: Build binary wheels + continue-on-error: true + id: binary-dist run: | - python3 -m pip install twine --prefer-binary - python3 -m twine check dist/* + python -m cibuildwheel --output-dir binary_dist + python -m twine check binary_dist/* + env: + CIBW_ARCHS: ${{ matrix.cibw_archs }} + + - name: Build binary wheels without (failing) testing + if: steps.binary-dist.outcome == 'failure' + id: failed-dist + run: | + python -m cibuildwheel --output-dir failed_dist + env: + CIBW_ARCHS: ${{ matrix.cibw_archs }} + CIBW_TEST_SKIP: '*' - - uses: actions/upload-artifact@v2 + - name: Files for Pypi upload + uses: actions/upload-artifact@v2 + if: steps.src-dist.outcome == 'success' with: name: pypy_wheels - path: ./dist/* + path: ./dist + + - name: Binary wheels + uses: actions/upload-artifact@v2 + if: steps.binary-dist.outcome == 'success' + with: + name: wheels + path: ./binary_dist + + - name: Binary wheels that failed tests + uses: actions/upload-artifact@v2 + if: steps.failed-dist.outcome == 'success' + with: + name: failed_wheels + path: ./failed_dist release: @@ -142,8 +182,6 @@ jobs: # ------------------------------------------------------------------------ - uses: actions/download-artifact@v2 - with: - name: pypy_wheels # Code below inspired from this action: # - uses: taiki-e/create-gh-release-action@v1 @@ -168,7 +206,9 @@ jobs: 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:-}" pypy_wheels/* + + mkdir -p wheels pypy_wheels + gh release create "v${RELEASE_VERSION}" ${prerelease:-} --title "ProjectQ v${RELEASE_VERSION}" --notes "${notes:-}" pypy_wheels/* wheels/* upload_to_pypi: @@ -179,8 +219,6 @@ jobs: - uses: actions/setup-python@v2 - uses: actions/download-artifact@v2 - with: - name: pypy_wheels - name: Publish standard package uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5708e21a6..9e323c3ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add configuration for CIBuildWheel in `pyproject.toml` - Remove use of deprecated images `windows-2016` in GitHub workflows +- Re-add build of Python binary wheels in release publishing GitHub workflow - Update `dangoslen/changelog-enforcer` GitHub action to v3 - Update `thomaseizinger/keep-a-changelog-new-release` GiHub action to v1.3.0 - Update `thomaseizinger/create-pull-request` GiHub action to v1.2.2 diff --git a/pyproject.toml b/pyproject.toml index 1b3b2c6ea..7752fa4bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,7 @@ archs = ['auto64'] build-frontend = 'build' build-verbosity = 1 skip = 'pp* *-musllinux*' -environment = { PROJECTQ_DISABLE_ARCH_NATIVE='1', PROJECTQ_CI_BUILD='1' } +environment = { PROJECTQ_DISABLE_ARCH_NATIVE='1', PROJECTQ_CI_BUILD='1', OMP_NUM_THREADS='1' } before-test = [ 'cd {package}', @@ -128,7 +128,7 @@ before-test = [ 'python -m pip install -r requirements.txt --only-binary :all:', ] -test-command = 'python3 {package}/examples/grover.py' +test-command = 'python {package}/examples/grover.py' # Normal options, etc. manylinux-x86_64-image = 'manylinux2014' From af0431362f42d9cd44e1adf4b84209417567f43b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 14:08:51 +0100 Subject: [PATCH 076/113] Release version 0.7.1 (#426) * Bump thomaseizinger/create-pull-request from 1.1.0 to 1.2.1 (#414) * Bump thomaseizinger/create-pull-request from 1.1.0 to 1.2.1 Bumps [thomaseizinger/create-pull-request](https://github.com/thomaseizinger/create-pull-request) from 1.1.0 to 1.2.1. - [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.1.0...1.2.1) --- 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: Nguyen Damien * Bump thomaseizinger/keep-a-changelog-new-release from 1.2.1 to 1.3.0 (#416) * Bump thomaseizinger/keep-a-changelog-new-release from 1.2.1 to 1.3.0 Bumps [thomaseizinger/keep-a-changelog-new-release](https://github.com/thomaseizinger/keep-a-changelog-new-release) from 1.2.1 to 1.3.0. - [Release notes](https://github.com/thomaseizinger/keep-a-changelog-new-release/releases) - [Changelog](https://github.com/thomaseizinger/keep-a-changelog-new-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/thomaseizinger/keep-a-changelog-new-release/compare/1.2.1...1.3.0) --- updated-dependencies: - dependency-name: thomaseizinger/keep-a-changelog-new-release 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 * Bump thomaseizinger/create-pull-request from 1.2.1 to 1.2.2 (#419) * Bump thomaseizinger/create-pull-request from 1.2.1 to 1.2.2 Bumps [thomaseizinger/create-pull-request](https://github.com/thomaseizinger/create-pull-request) from 1.2.1 to 1.2.2. - [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.2.1...1.2.2) --- updated-dependencies: - dependency-name: thomaseizinger/create-pull-request 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: Nguyen Damien * Update CI setup (#422) * Fix compiler flags cleanup routine * Update CHANGELOG * Use clang for linking on Clang CI workflows * Fix compiler_test(...) function in setup.py * Fix CI failure when testing locally * Remove LDSHARED for Clang build * Fix linter warnings in setup.py * Update CHANGELOG * Update more workflow files for local actions * Remove deprecated GitHub action for pre-commit * Update CHANGELOG * Update pre-commit hooks versions * Refrain from updating pylint pre-commit hook for now * Fix issue with running pre-commit on CI * Add cache support for pre-commit on CI * Fix name of caching step for pre-commit on CI * Add Python 3.10 for testing on CI * Add Python 3.10 in package metadata * Fix gen_reqfile command if --include-extras is not provided * Add setup option to avoid -march=native when building ProjectQ * Add support for CIBuildWheel in pyproject.toml * Tweak environment variables used during setup * Avoid Linux Musl in CIBuildWheel config * Update GitHub Workflow for publishing a release * Bump dangoslen/changelog-enforcer from 2 to 3 (#423) * Bump dangoslen/changelog-enforcer from 2 to 3 Bumps [dangoslen/changelog-enforcer](https://github.com/dangoslen/changelog-enforcer) from 2 to 3. - [Release notes](https://github.com/dangoslen/changelog-enforcer/releases) - [Changelog](https://github.com/dangoslen/changelog-enforcer/blob/master/CHANGELOG.md) - [Commits](https://github.com/dangoslen/changelog-enforcer/compare/v2...v3) --- updated-dependencies: - dependency-name: dangoslen/changelog-enforcer dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update CHANGELOG Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nguyen Damien * Remove windows-2016 from GitHub workflows (#424) * Re-add build of binary wheels (#425) - For now without automatic upload of binaries to PyPi * Preparing release v0.7.1 * Update CHANGELOG.md * Last minute fix for release drafting workflow Co-authored-by: Nguyen Damien Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: GitHub actions --- .github/workflows/ci.yml | 58 ++------------- .github/workflows/draft_release.yml | 21 +++--- .github/workflows/format.yml | 18 ++++- .github/workflows/publish_release.yml | 79 ++++++++++++++++---- .github/workflows/pull_request.yml | 2 +- .pre-commit-config.yaml | 10 +-- CHANGELOG.md | 41 +++++++++-- docs/tutorials.rst | 2 +- pyproject.toml | 27 +++++++ setup.cfg | 1 + setup.py | 101 ++++++++++++++++++-------- 11 files changed, 235 insertions(+), 125 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58440eca0..11017536f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: - 3.7 - 3.8 - 3.9 + - '3.10' name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" runs-on: ${{ matrix.runs-on }} @@ -28,6 +29,7 @@ jobs: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -125,6 +127,7 @@ jobs: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -134,6 +137,7 @@ jobs: 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 + libomp-dev --no-install-recommends - name: Prepare Python env @@ -169,6 +173,7 @@ jobs: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -236,6 +241,7 @@ jobs: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -285,6 +291,7 @@ jobs: restore-keys: ${{ runner.os }}-doc-pip- - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -300,54 +307,3 @@ jobs: - 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=test,braket - python -m pip install -r requirements.txt --prefer-binary - - - name: Build and install package - run: python -m pip install -ve .[braket,test] - - - 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 index e83fc5c07..fa5032fe1 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -3,10 +3,9 @@ name: "Draft new release" on: workflow_dispatch: inputs: - version: - description: 'Version to release' + tag: + description: 'Tag to prepare (format: vXX.YY.ZZ)' required: true - jobs: new-release: name: "Draft a new release" @@ -23,12 +22,12 @@ jobs: git flow init --default --tag v - name: Create release branch - run: git flow release start ${{ github.event.inputs.version }} + run: git flow release start ${{ github.event.inputs.tag }} - name: Update changelog - uses: thomaseizinger/keep-a-changelog-new-release@1.2.1 + uses: thomaseizinger/keep-a-changelog-new-release@1.3.0 with: - version: ${{ github.event.inputs.version }} + version: ${{ github.event.inputs.tag }} - name: Initialize mandatory git config run: | @@ -39,21 +38,21 @@ jobs: id: make-commit run: | git add CHANGELOG.md - git commit --message "Preparing release v${{ github.event.inputs.version }}" + git commit --message "Preparing release v${{ github.event.inputs.tag }}" echo "::set-output name=commit::$(git rev-parse HEAD)" - name: Push new branch - run: git flow release publish ${{ github.event.inputs.version }} + run: git flow release publish ${{ github.event.inputs.tag }} - name: Create pull request - uses: thomaseizinger/create-pull-request@1.1.0 + uses: thomaseizinger/create-pull-request@1.2.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - head: release/${{ github.event.inputs.version }} + head: release/${{ github.event.inputs.tag }} base: master - title: Release version ${{ github.event.inputs.version }} + title: Release version ${{ github.event.inputs.tag }} reviewers: ${{ github.actor }} # Write a nice message to the user. # We are claiming things here based on the `publish-new-release.yml` workflow. diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 0cd0b3e16..752d64fc9 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -16,14 +16,26 @@ jobs: steps: - uses: actions/checkout@v2 - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} 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 + + - name: Install pre-commit + run: python3 -m pip install --upgrade pre-commit 'virtualenv!=20.11' + + - name: Cache pre-commit hooks + uses: actions/cache@v2 with: - # Slow hooks are marked with manual - slow is okay here, run them too - extra_args: --hook-stage manual --all-files + path: ~/.cache/pre-commit + key: pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} + restore-keys: pre-commit- + + - name: Run pre-commit + run: | + # Slow hooks are marked with manual - slow is okay here, run them too + pre-commit run --hook-stage manual --all-files clang-tidy: name: Clang-Tidy diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index d0f54099a..2898cea5e 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -18,10 +18,18 @@ jobs: if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || github.event_name == 'workflow_dispatch' strategy: matrix: - os: - - ubuntu-latest - + cibw_archs: ["auto64"] + os: [ubuntu-latest, windows-latest, macos-latest] + # include: + # - os: ubuntu-18.04 + # cibw_archs: "aarch64" steps: + - name: Set up QEMU + if: matrix.cibw_archs == 'aarch64' + uses: docker/setup-qemu-action@v1 + with: + platforms: arm64 + - uses: actions/checkout@v2 if: github.event_name != 'workflow_dispatch' @@ -31,6 +39,7 @@ jobs: ref: 'master' - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* @@ -69,19 +78,56 @@ jobs: # ======================================================================== - - name: Build source distribution + - uses: actions/setup-python@v2 + + - name: Install Python packages + run: python -m pip install -U --prefer-binary pip setuptools build wheel twine 'cibuildwheel<3,>=2' + + - name: Build source distribution (Linux) if: runner.os == 'Linux' - run: python3 setup.py sdist -d wheelhouse + id: src-dist + run: | + python -m build --sdist + python -m twine check dist/* + + - name: Build binary wheels + continue-on-error: true + id: binary-dist + run: | + python -m cibuildwheel --output-dir binary_dist + python -m twine check binary_dist/* + env: + CIBW_ARCHS: ${{ matrix.cibw_archs }} - - name: Check metadata + - name: Build binary wheels without (failing) testing + if: steps.binary-dist.outcome == 'failure' + id: failed-dist run: | - python3 -m pip install twine --prefer-binary - python3 -m twine check wheelhouse/* + python -m cibuildwheel --output-dir failed_dist + env: + CIBW_ARCHS: ${{ matrix.cibw_archs }} + CIBW_TEST_SKIP: '*' + + - name: Files for Pypi upload + uses: actions/upload-artifact@v2 + if: steps.src-dist.outcome == 'success' + with: + name: pypy_wheels + path: ./dist - - uses: actions/upload-artifact@v2 + - name: Binary wheels + uses: actions/upload-artifact@v2 + if: steps.binary-dist.outcome == 'success' with: - name: packages - path: ./wheelhouse/* + name: wheels + path: ./binary_dist + + - name: Binary wheels that failed tests + uses: actions/upload-artifact@v2 + if: steps.failed-dist.outcome == 'success' + with: + name: failed_wheels + path: ./failed_dist release: @@ -134,7 +180,7 @@ jobs: ref: 'master' # ------------------------------------------------------------------------ - # Downloads all to directories matching the artifact names + - uses: actions/download-artifact@v2 # Code below inspired from this action: @@ -160,7 +206,9 @@ jobs: 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/* + + mkdir -p wheels pypy_wheels + gh release create "v${RELEASE_VERSION}" ${prerelease:-} --title "ProjectQ v${RELEASE_VERSION}" --notes "${notes:-}" pypy_wheels/* wheels/* upload_to_pypi: @@ -170,7 +218,6 @@ jobs: steps: - uses: actions/setup-python@v2 - # Downloads all to directories matching the artifact names - uses: actions/download-artifact@v2 - name: Publish standard package @@ -178,7 +225,7 @@ jobs: with: user: __token__ password: ${{ secrets.pypi_password }} - packages_dir: packages/ + packages_dir: pypy_wheels/ master_to_develop_pr: name: Merge master back into develop @@ -188,7 +235,7 @@ jobs: - upload_to_pypi steps: - name: Merge master into develop branch - uses: thomaseizinger/create-pull-request@1.1.0 + uses: thomaseizinger/create-pull-request@1.2.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index cfcb7d0a1..acabd22ce 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v2 - id: changelog-enforcer - uses: dangoslen/changelog-enforcer@v2 + uses: dangoslen/changelog-enforcer@v3 with: changeLogPath: 'CHANGELOG.md' skipLabels: 'Skip-Changelog' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d179c97e..45c9f8936 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -44,13 +44,13 @@ repos: - id: remove-tabs - repo: https://github.com/PyCQA/isort - rev: 5.9.1 + rev: 5.10.1 hooks: - id: isort name: isort (python) - repo: https://github.com/psf/black - rev: 21.5b1 + rev: 21.12b0 hooks: - id: black language_version: python3 @@ -58,7 +58,7 @@ repos: stages: [manual] - repo: https://gitlab.com/PyCQA/flake8 - rev: 3.9.2 + rev: 4.0.1 hooks: - id: flake8 name: flake8-strict @@ -82,7 +82,7 @@ repos: additional_dependencies: [pybind11>=2.6, numpy, requests, boto3, matplotlib, networkx, sympy] - repo: https://github.com/mgedmin/check-manifest - rev: '0.46' + rev: '0.47' hooks: - id: check-manifest additional_dependencies: ['setuptools-scm', 'pybind11>=2.6'] diff --git a/CHANGELOG.md b/CHANGELOG.md index 530441a8e..e1c33030a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.1] - 2022-01-10 + ### Added + +- Added environment variable to avoid -march=native when building ProjectQ +- Added environment variable to force build failure if extensions do not compile on CI + ### Changed + ### Deprecated + ### Fixed + +- Fix compiler flags cleanup function for use on CI +- Fix workflow YAML to allow execution of GitHub Actions locally using `act` +- GitHub action using deprecated and vulnerable `pre-commit` version +- Fixed issue with `gen_reqfile` command if `--include-extras` is not provided + ### Removed + ### Repository +- Add configuration for CIBuildWheel in `pyproject.toml` +- Remove use of deprecated images `windows-2016` in GitHub workflows +- Re-add build of Python binary wheels in release publishing GitHub workflow +- Update `dangoslen/changelog-enforcer` GitHub action to v3 +- Update `thomaseizinger/keep-a-changelog-new-release` GiHub action to v1.3.0 +- Update `thomaseizinger/create-pull-request` GiHub action to v1.2.2 +- Update pre-commit hook `pre-commit/pre-commit-hooks` to v4.1.0 +- Update pre-commit hook `PyCQA/isort` to v5.10.1 +- Update pre-commit hook `psf/black` to v21.12b0 +- Update pre-commit hook `PyCQA/flake8` to v4.0.1 +- Update pre-commit hook `mgedmin/check-manifest` to v0.47 + ## [0.7.0] - 2021-07-14 ### Added @@ -41,11 +68,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `isort` to the list of pre-commit hooks - Added some more flake8 plugins to the list used by `pre-commit`: - + flake8-breakpoint - + flake8-comprehensions - + flake8-docstrings - + flake8-eradicate - + flake8-mutable + - flake8-breakpoint + - flake8-comprehensions + - flake8-docstrings + - flake8-eradicate + - flake8-mutable ## [0.6.1] - 2021-06-23 @@ -141,7 +168,9 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi 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/v0.7.0...HEAD +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.1...HEAD + +[0.7.1]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.0...v0.7.1 [0.7.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.6.1...v0.7.0 diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 4df54c9fb..58d8de1c9 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -29,7 +29,7 @@ ProjectQ comes with a high-performance quantum simulator written in C++. Please .. 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). - 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 ``PROJECTQ_DISABLE_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: diff --git a/pyproject.toml b/pyproject.toml index b7e82e9ce..7752fa4bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,3 +113,30 @@ local_scheme = 'no-local-version' [tool.yapf] column_limit = 120 + +[tool.cibuildwheel] + +archs = ['auto64'] +build-frontend = 'build' +build-verbosity = 1 +skip = 'pp* *-musllinux*' +environment = { PROJECTQ_DISABLE_ARCH_NATIVE='1', PROJECTQ_CI_BUILD='1', OMP_NUM_THREADS='1' } + +before-test = [ + 'cd {package}', + 'python setup.py gen_reqfile', + 'python -m pip install -r requirements.txt --only-binary :all:', +] + +test-command = 'python {package}/examples/grover.py' + +# Normal options, etc. +manylinux-x86_64-image = 'manylinux2014' + +[[tool.cibuildwheel.overrides]] +select = 'cp36-*' +manylinux-x86_64-image = 'manylinux1' + +[[tool.cibuildwheel.overrides]] +select = 'cp3{7,8,9}-*' +manylinux-x86_64-image = 'manylinux2010' diff --git a/setup.cfg b/setup.cfg index 15407bf54..0f5a22bfd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ classifier = Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 [options] diff --git a/setup.py b/setup.py index bb9402ed1..46c394d1b 100755 --- a/setup.py +++ b/setup.py @@ -98,33 +98,43 @@ def status_msgs(*msgs): def compiler_test( - compiler, flagname=None, link=False, include='', body='', postargs=None -): # pylint: disable=too-many-arguments + compiler, + flagname=None, + link_executable=False, + link_shared_lib=False, + include='', + body='', + compile_postargs=None, + link_postargs=None, +): # pylint: disable=too-many-arguments,too-many-branches """Return a boolean indicating whether a flag name is supported on the specified compiler.""" 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: - postargs = [flagname] if flagname is not None else None + if compile_postargs is None: + compile_postargs = [flagname] if flagname is not None else None elif flagname is not None: - postargs.append(flagname) + compile_postargs.append(flagname) try: - exec_name = os.path.join(tempfile.mkdtemp(), 'test') - if compiler.compiler_type == 'msvc': olderr = os.dup(sys.stderr.fileno()) err = open('err.txt', 'w') # pylint: disable=consider-using-with os.dup2(err.fileno(), sys.stderr.fileno()) - obj_file = compiler.compile([fname], extra_postargs=postargs) + obj_file = compiler.compile([fname], extra_postargs=compile_postargs) if not os.path.exists(obj_file[0]): raise RuntimeError('') - if link: - compiler.link_executable(obj_file, exec_name, extra_postargs=postargs) + if link_executable: + compiler.link_executable(obj_file, os.path.join(tempfile.mkdtemp(), 'test'), extra_postargs=link_postargs) + elif link_shared_lib: + if sys.platform == 'win32': + lib_name = os.path.join(tempfile.mkdtemp(), 'test.dll') + else: + lib_name = os.path.join(tempfile.mkdtemp(), 'test.so') + compiler.link_shared_lib(obj_file, lib_name, extra_postargs=link_postargs) if compiler.compiler_type == 'msvc': err.close() @@ -133,9 +143,11 @@ def compiler_test( if err_file.readlines(): raise RuntimeError('') except (CompileError, LinkError, RuntimeError): - ret = False - os.unlink(fname) - return ret + return False + else: + return True + finally: + os.unlink(fname) def _fix_macosx_header_paths(*args): @@ -362,13 +374,13 @@ def _configure_openmp(self): return kwargs = { - 'link': True, + 'link_shared_lib': True, 'include': '#include ', 'body': 'int a = omp_get_num_threads(); ++a;', } for flag in ['-openmp', '-fopenmp', '-qopenmp', '/Qopenmp']: - if compiler_test(self.compiler, flag, **kwargs): + if compiler_test(self.compiler, flag, link_postargs=[flag], **kwargs): self.opts.append(flag) self.link_opts.append(flag) return @@ -383,7 +395,7 @@ def _configure_openmp(self): # 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, link_postargs=[l_arg, flag], **kwargs): self.opts.append(flag) self.link_opts.extend((l_arg, flag)) return @@ -404,7 +416,7 @@ def _configure_openmp(self): c_arg = '-I' + inc_dir l_arg = '-L' + lib_dir - if compiler_test(self.compiler, flag, postargs=[c_arg, l_arg], **kwargs): + if compiler_test(self.compiler, flag, compile_postargs=[c_arg], link_postargs=[l_arg], **kwargs): self.compiler.add_include_dir(inc_dir) self.compiler.add_library_dir(lib_dir) return @@ -414,17 +426,21 @@ def _configure_openmp(self): important_msgs('WARNING: compiler does not support OpenMP!') def _configure_intrinsics(self): - for flag in [ + flags = [ '-march=native', '-mavx2', '/arch:AVX2', '/arch:CORE-AVX2', '/arch:AVX', - ]: + ] + + if os.environ.get('PROJECTQ_DISABLE_ARCH_NATIVE'): + flags = flags[1:] + + for flag in flags: if compiler_test( self.compiler, flagname=flag, - link=False, include='#include ', body='__m256d neg = _mm256_set1_pd(1.0); (void)neg;', ): @@ -465,20 +481,40 @@ def _configure_cxx_standard(self): raise BuildFailed() def _cleanup_compiler_flags(self): - compiler = self.compiler.compiler[0] - compiler_so = self.compiler.compiler_so[0] + status_msgs('INFO: Performing compiler flags cleanup') + compiler_exe = self.compiler.compiler[0] + compiler_exe_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) + all_common_flags = compiler_flags & compiler_so_flags & linker_so_flags + common_compiler_flags = (compiler_flags & compiler_so_flags) - all_common_flags + + compiler_flags = compiler_flags - common_compiler_flags - all_common_flags + compiler_so_flags = compiler_so_flags - common_compiler_flags - all_common_flags + + flags = [] + for flag in common_compiler_flags: + compiler = type(self.compiler)() + compiler.set_executables(compiler=compiler_exe, compiler_so=compiler_exe_so, linker_so=linker_so) + + compiler.debug_print(f'INFO: trying out {flag}') + if compiler_test(compiler, flag, link_shared_lib=True, compile_postargs=['-fPIC']): + flags.append(flag) + else: + important_msgs('WARNING: ignoring unsupported compiler flag: {}'.format(flag)) + + self.compiler.compiler = [compiler_exe] + list(compiler_flags) + self.compiler.compiler_so = [compiler_exe_so] + list(compiler_so_flags) + self.compiler.linker_so = [linker_so] + list(linker_so_flags - all_common_flags) + + self.compiler.compiler.extend(flags) + self.compiler.compiler_so.extend(flags) flags = [] - for flag in common_flags: + for flag in all_common_flags: if compiler_test(self.compiler, flag): flags.append(flag) else: @@ -555,7 +591,7 @@ def initialize_options(self): def finalize_options(self): """Finalize this command's options.""" - include_extras = self.include_extras.split(',') + include_extras = self.include_extras.split(',') if self.include_extras else [] try: for name, pkgs in self.distribution.extras_require.items(): @@ -630,10 +666,10 @@ def run_setup(with_cext): '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'): +elif os.environ.get('PROJECTQ_DISABLE_CEXT'): run_setup(False) important_msgs( - 'DISABLE_PROJECTQ_CEXT is set; not attempting to build C/C++ extensions.', + 'PROJECTQ_DISABLE_CEXT is set; not attempting to build C/C++ extensions.', 'Plain-Python build succeeded.', ) @@ -641,6 +677,9 @@ def run_setup(with_cext): try: run_setup(True) except BuildFailed as exc: + if os.environ.get('PROJECTQ_CI_BUILD'): + raise exc + important_msgs( exc.cause, 'WARNING: Some C/C++ extensions could not be compiled, ' From 6f2c2be9536a1138ce48bbd9f71d3118304ade96 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Mon, 11 Apr 2022 10:23:57 +0200 Subject: [PATCH 077/113] Update pre commit hooks and fix failing CentOS CI builds (#429) * Update pre-commit hook versions * Fix linter/formatter warnings * Fix issues with CentOS build on the CI --- .github/workflows/ci.yml | 8 +- .pre-commit-config.yaml | 10 +- CHANGELOG.md | 9 ++ examples/unitary_simulator.py | 2 +- projectq/backends/_aqt/_aqt.py | 3 +- projectq/backends/_awsbraket/_awsbraket.py | 110 ++++++++---------- projectq/backends/_circuits/_drawer.py | 4 +- .../backends/_circuits/_drawer_matplotlib.py | 8 +- projectq/backends/_circuits/_to_latex.py | 2 +- projectq/backends/_ibm/_ibm.py | 3 +- projectq/backends/_ionq/_ionq.py | 3 +- projectq/backends/_sim/_pysim.py | 14 +-- projectq/backends/_sim/_simulator.py | 4 +- projectq/backends/_sim/_simulator_test.py | 6 +- projectq/backends/_unitary.py | 8 +- projectq/backends/_unitary_test.py | 8 +- projectq/cengines/_ibm5qubitmapper.py | 4 +- projectq/cengines/_linearmapper.py | 3 +- projectq/cengines/_optimize.py | 19 +-- projectq/cengines/_testengine.py | 10 +- projectq/cengines/_twodmapper.py | 3 +- projectq/libs/math/_gates.py | 2 +- projectq/libs/math/_gates_math_test.py | 2 +- projectq/libs/math/_quantummath_test.py | 2 +- projectq/meta/_control.py | 2 +- projectq/meta/_control_test.py | 2 +- projectq/ops/_basics.py | 2 +- projectq/ops/_command_test.py | 12 +- projectq/ops/_qubit_operator.py | 13 +-- projectq/ops/_qubit_operator_test.py | 2 +- .../setups/decompositions/phaseestimation.py | 4 +- .../setups/decompositions/stateprep2cnot.py | 6 +- .../decompositions/stateprep2cnot_test.py | 2 +- .../decompositions/time_evolution_test.py | 2 +- .../uniformlycontrolledr2cnot_test.py | 2 +- projectq/tests/_factoring_test.py | 2 +- 36 files changed, 147 insertions(+), 151 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11017536f..5a5939e11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -228,12 +228,18 @@ jobs: /var/cache/dnf/ key: ${{ runner.os }}-centos${{ matrix.centos }}-yum-${{ secrets.yum_cache }} + - name: Fix repository URLs (CentOS 8 only) + if: matrix.centos == 8 + run: | + sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* + sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* + - 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 + run: yum -y install https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm - name: Install Git > 2.18 run: yum install -y git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 45c9f8936..f535f8345 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,7 @@ repos: # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.10 + rev: v1.1.13 hooks: - id: remove-tabs @@ -50,7 +50,7 @@ repos: name: isort (python) - repo: https://github.com/psf/black - rev: 21.12b0 + rev: 22.3.0 hooks: - id: black language_version: python3 @@ -58,7 +58,7 @@ repos: stages: [manual] - repo: https://gitlab.com/PyCQA/flake8 - rev: 4.0.1 + rev: 3.9.2 hooks: - id: flake8 name: flake8-strict @@ -73,7 +73,7 @@ repos: files: ^(.*_test\.py)$ - repo: https://github.com/pre-commit/mirrors-pylint - rev: 'v3.0.0a3' + rev: 'v3.0.0a4' hooks: - id: pylint args: ['--score=n'] @@ -82,7 +82,7 @@ repos: additional_dependencies: [pybind11>=2.6, numpy, requests, boto3, matplotlib, networkx, sympy] - repo: https://github.com/mgedmin/check-manifest - rev: '0.47' + rev: '0.48' hooks: - id: check-manifest additional_dependencies: ['setuptools-scm', 'pybind11>=2.6'] diff --git a/CHANGELOG.md b/CHANGELOG.md index e1c33030a..a88e66370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Repository + +- Fix issues with building on CentOS 7 & 8 +- Update `Lucas-C/pre-commit-hooks` hook to v1.1.13 +- Update `flake8` hook to v4.0.1 +- Update `pylint` hook to v3.0.0a4 +- Update `black` hook to v22.3.0 +- Update `check-manifest` to v0.48 + ## [0.7.1] - 2022-01-10 ### Added diff --git a/examples/unitary_simulator.py b/examples/unitary_simulator.py index 4a840dc70..d2fd4ecdf 100644 --- a/examples/unitary_simulator.py +++ b/examples/unitary_simulator.py @@ -70,7 +70,7 @@ def main(): # Output the final state of the qubits (assuming they all start in state |0>) print('The final state of the qubits is:') - print(eng.backend.unitary @ np.array([1] + ([0] * (2 ** n_qubits - 1)))) + print(eng.backend.unitary @ np.array([1] + ([0] * (2**n_qubits - 1)))) print('\n') # Show the unitaries separated by measurement: diff --git a/projectq/backends/_aqt/_aqt.py b/projectq/backends/_aqt/_aqt.py index 245b074f6..709a97bb8 100644 --- a/projectq/backends/_aqt/_aqt.py +++ b/projectq/backends/_aqt/_aqt.py @@ -222,11 +222,10 @@ def get_probabilities(self, qureg): raise RuntimeError("Please, run the circuit first!") probability_dict = {} - for state in self._probabilities: + for state, probability in self._probabilities.items(): mapped_state = ['0'] * len(qureg) for i, qubit in enumerate(qureg): mapped_state[i] = state[self._logical_to_physical(qubit.id)] - probability = self._probabilities[state] mapped_state = "".join(mapped_state) probability_dict[mapped_state] = probability_dict.get(mapped_state, 0) + probability diff --git a/projectq/backends/_awsbraket/_awsbraket.py b/projectq/backends/_awsbraket/_awsbraket.py index ba27e64f7..de7c82ecc 100755 --- a/projectq/backends/_awsbraket/_awsbraket.py +++ b/projectq/backends/_awsbraket/_awsbraket.py @@ -172,49 +172,43 @@ def is_available(self, cmd): # pylint: disable=too-many-return-statements,too-m 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: @@ -224,26 +218,23 @@ def is_available(self, cmd): # pylint: disable=too-many-return-statements,too-m 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): @@ -372,13 +363,12 @@ def get_probabilities(self, qureg): raise RuntimeError("Please, run the circuit first!") probability_dict = {} - for state in self._probabilities: + for state, probability in self._probabilities.items(): mapped_state = ['0'] * len(qureg) 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: probability_dict[mapped_state] = probability diff --git a/projectq/backends/_circuits/_drawer.py b/projectq/backends/_circuits/_drawer.py index bb19408fa..7d67f6190 100755 --- a/projectq/backends/_circuits/_drawer.py +++ b/projectq/backends/_circuits/_drawer.py @@ -265,10 +265,10 @@ def get_latex(self, ordered=False, draw_gates_in_parallel=True): """ qubit_lines = {} - for line in range(len(self._qubit_lines)): + for line, qubit_line in self._qubit_lines.items(): new_line = self._map[line] qubit_lines[new_line] = [] - for cmd in self._qubit_lines[line]: + for cmd in qubit_line: lines = [self._map[qb_id] for qb_id in cmd.lines] ctrl_lines = [self._map[qb_id] for qb_id in cmd.ctrl_lines] gate = cmd.gate diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py index 37d42f43a..6098ebfde 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib.py +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -144,7 +144,7 @@ def _process(self, cmd): # pylint: disable=too-many-branches # 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(line) for qubit_id, line in self._qubit_lines.items()) for qb_id in itertools.chain(targets, controls): depth = len(self._qubit_lines[qb_id]) @@ -206,9 +206,9 @@ 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) - for qubit_id in self._qubit_lines: - depth = len(self._qubit_lines[qubit_id]) + max_depth = max(len(line) for qubit_id, line in self._qubit_lines.items()) + for qubit_id, line in self._qubit_lines.items(): + depth = len(line) if depth < max_depth: self._qubit_lines[qubit_id] += [None] * (max_depth - depth) diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index ca64cf407..ae3f05cd5 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -423,7 +423,7 @@ def to_tikz( # pylint: disable=too-many-branches,too-many-locals,too-many-state tikz_code.append(connections) if not draw_gates_in_parallel: - for _line in range(len(self.pos)): + for _line, _ in enumerate(self.pos): if _line != line: self.pos[_line] = self.pos[line] diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index ad070b875..238f11bae 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -247,11 +247,10 @@ def get_probabilities(self, qureg): raise RuntimeError("Please, run the circuit first!") probability_dict = {} - for state in self._probabilities: + for state, probability in self._probabilities.items(): mapped_state = ['0'] * len(qureg) 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: probability_dict[mapped_state] = probability diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py index e6480ba74..7645f3531 100644 --- a/projectq/backends/_ionq/_ionq.py +++ b/projectq/backends/_ionq/_ionq.py @@ -283,7 +283,7 @@ def get_probabilities(self, qureg): raise RuntimeError("Please, run the circuit first!") probability_dict = {} - for state in self._probabilities: + for state, probability in self._probabilities.items(): mapped_state = ['0'] * len(qureg) for i, qubit in enumerate(qureg): try: @@ -291,7 +291,6 @@ def get_probabilities(self, qureg): 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 diff --git a/projectq/backends/_sim/_pysim.py b/projectq/backends/_sim/_pysim.py index dc4687dbc..a98bad4ca 100755 --- a/projectq/backends/_sim/_pysim.py +++ b/projectq/backends/_sim/_pysim.py @@ -205,7 +205,7 @@ def emulate_math(self, func, qubit_ids, ctrlqubit_ids): # pylint: disable=too-m qb_locs[-1].append(self._map[qubit_id]) newstate = _np.zeros_like(self._state) - for i in range(0, len(self._state)): + for i, state in enumerate(self._state): if (mask & i) == mask: arg_list = [0] * len(qb_locs) for qr_i, qr_loc in enumerate(qb_locs): @@ -218,9 +218,9 @@ def emulate_math(self, func, qubit_ids, ctrlqubit_ids): # pylint: disable=too-m 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] + newstate[new_i] = state else: - newstate[i] = self._state[i] + newstate[i] = state self._state = newstate @@ -286,7 +286,7 @@ def get_probability(self, bit_string, ids): probability = 0.0 for i, state in enumerate(self._state): if (i & mask) == bit_str: - probability += state.real ** 2 + state.imag ** 2 + probability += state.real**2 + state.imag**2 return probability def get_amplitude(self, bit_string, ids): @@ -482,13 +482,13 @@ def collapse_wavefunction(self, ids, values): mask |= 1 << pos val |= int(values[i]) << pos nrm = 0.0 - for i in range(len(self._state)): + for i, state in enumerate(self._state): if (mask & i) == val: - nrm += _np.abs(self._state[i]) ** 2 + nrm += _np.abs(state) ** 2 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)): + for i in range(len(self._state)): # pylint: disable=consider-using-enumerate if (mask & i) != val: self._state[i] = 0.0 else: diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index f7b6f8144..ff5c4f4fc 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -110,7 +110,7 @@ def is_available(self, cmd): try: matrix = cmd.gate.matrix # Allow up to 5-qubit gates - if len(matrix) > 2 ** 5: + if len(matrix) > 2**5: return False return True except AttributeError: @@ -402,7 +402,7 @@ def _handle(self, cmd): # pylint: disable=too-many-branches,too-many-locals,too qubitids = [qb.id for qb in cmd.qubits[0]] ctrlids = [qb.id for qb in cmd.control_qubits] self._simulator.emulate_time_evolution(op, time, qubitids, ctrlids) - elif len(cmd.gate.matrix) <= 2 ** 5: + elif len(cmd.gate.matrix) <= 2**5: matrix = cmd.gate.matrix ids = [qb.id for qureg in cmd.qubits for qb in qureg] if not 2 ** len(ids) == len(cmd.gate.matrix): diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index b386ec48d..67f14eb9a 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -142,7 +142,7 @@ def __init__(self): @property def matrix(self): self.cnt += 1 - return numpy.eye(2 ** 6) + return numpy.eye(2**6) class MockNoMatrixGate(BasicGate): @@ -317,7 +317,7 @@ def matrix(self): class LargerGate(BasicGate): @property def matrix(self): - return numpy.eye(2 ** 6) + return numpy.eye(2**6) with pytest.raises(Exception): LargerGate() | (qureg + qubit) @@ -353,7 +353,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) diff --git a/projectq/backends/_unitary.py b/projectq/backends/_unitary.py index 625fc50c2..74fc47f17 100644 --- a/projectq/backends/_unitary.py +++ b/projectq/backends/_unitary.py @@ -145,7 +145,7 @@ def is_available(self, cmd): try: gate_mat = cmd.gate.matrix - if len(gate_mat) > 2 ** 6: + if len(gate_mat) > 2**6: warnings.warn("Potentially large matrix gate encountered! ({} qubits)".format(math.log2(len(gate_mat)))) return True except AttributeError: @@ -231,8 +231,8 @@ def _handle(self, cmd): "previous unitary can be accessed in history" ) self._history.append(self._unitary) - self._unitary = np.identity(2 ** self._num_qubits, dtype=complex) - self._state = np.array([1] + ([0] * (2 ** self._num_qubits - 1)), dtype=complex) + self._unitary = np.identity(2**self._num_qubits, dtype=complex) + self._state = np.array([1] + ([0] * (2**self._num_qubits - 1)), dtype=complex) self._is_valid = True self._is_flushed = False @@ -242,7 +242,7 @@ def _handle(self, cmd): self._num_qubits, ) for mask in mask_list: - cache = np.identity(2 ** self._num_qubits, dtype=complex) + cache = np.identity(2**self._num_qubits, dtype=complex) cache[np.ix_(mask, mask)] = cmd.gate.matrix self._unitary = cache @ self._unitary diff --git a/projectq/backends/_unitary_test.py b/projectq/backends/_unitary_test.py index 27c3fc850..bd150697a 100644 --- a/projectq/backends/_unitary_test.py +++ b/projectq/backends/_unitary_test.py @@ -71,7 +71,7 @@ def test_unitary_is_available(): assert sim.is_available( Command( None, - MatrixGate(np.identity(2 ** 7)), + MatrixGate(np.identity(2**7)), qubits=([qb0, qb1, qb2, qb3, qb4, qb5, qb6],), ) ) @@ -199,7 +199,7 @@ def test_unitary_after_deallocation_or_measurement(): def test_unitary_simulator(): def create_random_unitary(n): - return unitary_group.rvs(2 ** n) + return unitary_group.rvs(2**n) mat1 = create_random_unitary(1) mat2 = create_random_unitary(2) @@ -222,7 +222,7 @@ def apply_gates(eng, qureg): with Control(eng, qureg[2], ctrl_state='0'): MatrixGate(mat1) | qureg[0] - for basis_state in [list(x[::-1]) for x in itertools.product([0, 1], repeat=2 ** n_qubits)][1:]: + for basis_state in [list(x[::-1]) for x in itertools.product([0, 1], repeat=2**n_qubits)][1:]: ref_eng = MainEngine(engine_list=[], verbose=True) ref_qureg = ref_eng.allocate_qureg(n_qubits) ref_eng.backend.set_wavefunction(basis_state, ref_qureg) @@ -231,7 +231,7 @@ def apply_gates(eng, qureg): test_eng = MainEngine(backend=UnitarySimulator(), engine_list=[], verbose=True) test_qureg = test_eng.allocate_qureg(n_qubits) - assert np.allclose(test_eng.backend.unitary, np.identity(2 ** n_qubits)) + assert np.allclose(test_eng.backend.unitary, np.identity(2**n_qubits)) apply_gates(test_eng, test_qureg) diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index 6e44f5117..8c121ac5d 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -94,14 +94,14 @@ def _determine_cost(self, mapping): mapping. """ cost = 0 - for tpl in self._interactions: + for tpl, interaction in self._interactions.items(): ctrl_id = tpl[0] target_id = tpl[1] ctrl_pos = mapping[ctrl_id] target_pos = mapping[target_id] if not (ctrl_pos, target_pos) in self.connections: if (target_pos, ctrl_pos) in self.connections: - cost += self._interactions[tpl] + cost += interaction else: return None return cost diff --git a/projectq/cengines/_linearmapper.py b/projectq/cengines/_linearmapper.py index 8546e3774..098d464a1 100644 --- a/projectq/cengines/_linearmapper.py +++ b/projectq/cengines/_linearmapper.py @@ -458,8 +458,7 @@ def _send_possible_commands(self): # pylint: disable=too-many-branches active_ids.add(logical_id) new_stored_commands = [] - for i in range(len(self._stored_commands)): - cmd = self._stored_commands[i] + for i, cmd in enumerate(self._stored_commands): if len(active_ids) == 0: new_stored_commands += self._stored_commands[i:] break diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index d8a63b2c4..f4da9921d 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -200,7 +200,8 @@ def _optimize(self, idx, lim=None): def _check_and_send(self): """Check whether a qubit pipeline must be sent on and, if so, optimize the pipeline and then send it on.""" - for i in self._l: + # NB: self.optimize(i) modifies self._l + for i in self._l: # pylint: disable=consider-using-dict-items if ( len(self._l[i]) >= self._cache_size or len(self._l[i]) > 0 @@ -212,9 +213,9 @@ def _check_and_send(self): 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 = {} - for idx in self._l: - if len(self._l[idx]) > 0: - new_dict[idx] = self._l[idx] + for idx, _l in self._l.items(): + if len(_l) > 0: + new_dict[idx] = _l self._l = new_dict def _cache_cmd(self, cmd): @@ -239,13 +240,13 @@ def receive(self, command_list): """ for cmd in command_list: if cmd.gate == FlushGate(): # flush gate --> optimize and flush - for idx in self._l: + for idx, _l in self._l.items(): self._optimize(idx) - self._send_qubit_pipeline(idx, len(self._l[idx])) + self._send_qubit_pipeline(idx, len(_l)) new_dict = {} - for idx in self._l: - if len(self._l[idx]) > 0: # pragma: no cover - new_dict[idx] = self._l[idx] + for idx, _l in self._l.items(): + if len(_l) > 0: # pragma: no cover + new_dict[idx] = _l self._l = new_dict if self._l != {}: # pragma: no cover raise RuntimeError('Internal compiler error: qubits remaining in LocalOptimizer after a flush!') diff --git a/projectq/cengines/_testengine.py b/projectq/cengines/_testengine.py index 1e2d6f934..c89b42095 100755 --- a/projectq/cengines/_testengine.py +++ b/projectq/cengines/_testengine.py @@ -83,18 +83,18 @@ def __eq__(self, other): """Equal operator.""" 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]): + for i, _li in enumerate(self._l): + if len(_li) != len(other._l[i]): return False - for j in range(len(self._l[i])): - if not _compare_cmds(self._l[i][j], other._l[i][j]): + for j, _lij in enumerate(_li): + if not _compare_cmds(_lij, other._l[i][j]): return False return True def __str__(self): """Return a string representation of the object.""" string = "" - for qubit_id in range(len(self._l)): + for qubit_id, _l in enumerate(self._l): string += "Qubit {0} : ".format(qubit_id) for command in self._l[qubit_id]: string += str(command) + ", " diff --git a/projectq/cengines/_twodmapper.py b/projectq/cengines/_twodmapper.py index 4b4bb18b9..5aa1dc26f 100644 --- a/projectq/cengines/_twodmapper.py +++ b/projectq/cengines/_twodmapper.py @@ -423,8 +423,7 @@ def _send_possible_commands(self): # pylint: disable=too-many-branches active_ids.add(logical_id) new_stored_commands = [] - for i in range(len(self._stored_commands)): - cmd = self._stored_commands[i] + for i, cmd in enumerate(self._stored_commands): if len(active_ids) == 0: new_stored_commands += self._stored_commands[i:] break diff --git a/projectq/libs/math/_gates.py b/projectq/libs/math/_gates.py index 35777da05..e541a34ea 100755 --- a/projectq/libs/math/_gates.py +++ b/projectq/libs/math/_gates.py @@ -256,7 +256,7 @@ def get_math_function(self, qubits): def math_fun(a): # pylint: disable=invalid-name a[1] = a[0] + a[1] if len(bin(a[1])[2:]) > n_qubits: - a[1] = a[1] % (2 ** n_qubits) + a[1] = a[1] % (2**n_qubits) if len(a) == 3: # Flip the last bit of the carry register diff --git a/projectq/libs/math/_gates_math_test.py b/projectq/libs/math/_gates_math_test.py index 5640e7426..8463fa75b 100644 --- a/projectq/libs/math/_gates_math_test.py +++ b/projectq/libs/math/_gates_math_test.py @@ -42,7 +42,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) diff --git a/projectq/libs/math/_quantummath_test.py b/projectq/libs/math/_quantummath_test.py index bd520f668..c71ae0ef7 100644 --- a/projectq/libs/math/_quantummath_test.py +++ b/projectq/libs/math/_quantummath_test.py @@ -34,7 +34,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) diff --git a/projectq/meta/_control.py b/projectq/meta/_control.py index 9fa0803d8..90c97e503 100755 --- a/projectq/meta/_control.py +++ b/projectq/meta/_control.py @@ -66,7 +66,7 @@ def canonical_ctrl_state(ctrl_state, num_qubits): 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 + ctrl_state, converted_str, num_qubits, 2**num_qubits - 1 ) ) return converted_str diff --git a/projectq/meta/_control_test.py b/projectq/meta/_control_test.py index 5e3618cfb..e30bda335 100755 --- a/projectq/meta/_control_test.py +++ b/projectq/meta/_control_test.py @@ -35,7 +35,7 @@ def test_canonical_representation(): assert _control.canonical_ctrl_state(0, num_qubits) == '0' * num_qubits num_qubits = 4 - for i in range(2 ** num_qubits): + 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 diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 6cff8c18d..17485e921 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -43,7 +43,7 @@ from ._command import Command, apply_command ANGLE_PRECISION = 12 -ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION +ANGLE_TOLERANCE = 10**-ANGLE_PRECISION RTOL = 1e-10 ATOL = 1e-12 diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index 30b555c71..27377a558 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -172,14 +172,10 @@ 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]] @pytest.mark.parametrize( diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 55323fab1..6e2abda8c 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -187,8 +187,7 @@ def compress(self, abs_tol=1e-12): abs_tol(float): Absolute tolerance, must be at least 0.0 """ new_terms = {} - for term in self.terms: - coeff = self.terms[term] + for term, coeff in self.terms.items(): if abs(coeff.imag) <= abs_tol: coeff = coeff.real if abs(coeff) > abs_tol: @@ -374,9 +373,9 @@ def __imul__(self, multiplier): # pylint: disable=too-many-locals,too-many-bran # Handle QubitOperator. if isinstance(multiplier, QubitOperator): # pylint: disable=too-many-nested-blocks result_terms = {} - for left_term in self.terms: - for right_term in multiplier.terms: - new_coefficient = self.terms[left_term] * multiplier.terms[right_term] + for left_term, left_coeff in self.terms.items(): + for right_term, right_coeff in multiplier.terms.items(): + new_coefficient = left_coeff * right_coeff # Loop through local operators and create new sorted list # of representing the product local operator: @@ -564,8 +563,8 @@ def __str__(self): if not self.terms: return '0' string_rep = '' - for term in self.terms: - tmp_string = '{}'.format(self.terms[term]) + for term, coeff in self.terms.items(): + tmp_string = '{}'.format(coeff) if term == (): tmp_string += ' I' for operator in term: diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index ad86e0d64..3bbd09e2b 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -367,7 +367,7 @@ 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((), 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) diff --git a/projectq/setups/decompositions/phaseestimation.py b/projectq/setups/decompositions/phaseestimation.py index 58dea6e35..efc788bdb 100644 --- a/projectq/setups/decompositions/phaseestimation.py +++ b/projectq/setups/decompositions/phaseestimation.py @@ -108,10 +108,10 @@ def _decompose_QPE(cmd): # pylint: disable=invalid-name # If U is a function for i, ancilla in enumerate(qpe_ancillas): with Control(eng, ancilla): - unitary(system_qubits, time=2 ** i) + unitary(system_qubits, time=2**i) else: for i, ancilla in enumerate(qpe_ancillas): - ipower = int(2 ** i) + ipower = int(2**i) with Loop(eng, ipower): with Control(eng, ancilla): unitary | system_qubits diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py index 671bb0097..6b95bc30d 100644 --- a/projectq/setups/decompositions/stateprep2cnot.py +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -36,7 +36,7 @@ def _decompose_state_preparation(cmd): # pylint: disable=too-many-locals 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.0 for amplitude in final_state: @@ -78,8 +78,8 @@ def _decompose_state_preparation(cmd): # pylint: disable=too-many-locals 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)) + 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[(qubit_idx + 1) :], # noqa: E203 qubit, diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index 7e129419f..72372e77b 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -55,7 +55,7 @@ def test_state_preparation(n_qubits, zeros): 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)): f_state[i] = 0 diff --git a/projectq/setups/decompositions/time_evolution_test.py b/projectq/setups/decompositions/time_evolution_test.py index 1710d5791..e8eecaa1d 100644 --- a/projectq/setups/decompositions/time_evolution_test.py +++ b/projectq/setups/decompositions/time_evolution_test.py @@ -227,7 +227,7 @@ def build_matrix(list_single_matrices): 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/uniformlycontrolledr2cnot_test.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py index 1623ce51c..dbc5e75f9 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py @@ -102,7 +102,7 @@ def test_uniformly_controlled_ry(n, gate_classes): 0.01, 0.99, ] - angles = random_angles[: 2 ** n] + 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 diff --git a/projectq/tests/_factoring_test.py b/projectq/tests/_factoring_test.py index a25a344c9..5cb409d4c 100755 --- a/projectq/tests/_factoring_test.py +++ b/projectq/tests/_factoring_test.py @@ -70,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() From b22a278675cb0003c1c72aa973dca390afa9b73f Mon Sep 17 00:00:00 2001 From: Jon Donovan Date: Mon, 11 Apr 2022 01:48:33 -0700 Subject: [PATCH 078/113] Add support for dynamic backends for IonQ (#428) * Add support for dynamic backends for IonQ Using the backends endpoint, add available backends to the list. * Review comments * Formatting fixes * changelog * Remove unnecessary element in docstring Co-authored-by: Nguyen Damien --- CHANGELOG.md | 3 + projectq/backends/_ionq/_ionq_http_client.py | 27 ++++---- .../backends/_ionq/_ionq_http_client_test.py | 62 ++++++++++++++++++- projectq/setups/ionq.py | 7 ++- projectq/setups/ionq_test.py | 13 +++- 5 files changed, 92 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a88e66370..ddfa877e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Added IonQ dynamic backends fetch. + ### Repository - Fix issues with building on CentOS 7 & 8 diff --git a/projectq/backends/_ionq/_ionq_http_client.py b/projectq/backends/_ionq/_ionq_http_client.py index 2cb181158..1eb5e573c 100644 --- a/projectq/backends/_ionq/_ionq_http_client.py +++ b/projectq/backends/_ionq/_ionq_http_client.py @@ -47,6 +47,11 @@ def __init__(self, verbose=False): def update_devices_list(self): """Update the list of devices this backend can support.""" + self.authenticate(self.token) + req = super().get(urljoin(_API_URL, 'backends')) + req.raise_for_status() + r_json = req.json() + # Legacy backends, kept for backward compatibility. self.backends = { 'ionq_simulator': { 'nq': 29, @@ -57,6 +62,8 @@ def update_devices_list(self): 'target': 'qpu', }, } + for backend in r_json: + self.backends[backend["backend"]] = {"nq": backend["qubits"], "target": backend["backend"]} if self._verbose: # pragma: no cover print('- List of IonQ devices available:') print(self.backends) @@ -240,19 +247,14 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover raise RequestTimeoutError("Timeout. The ID of your submitted job is {}.".format(execution_id)) + def show_devices(self): + """Show the currently available device list for the IonQ provider. -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 + Returns: + list: list of available devices and their properties. + """ + self.update_devices_list() + return self.backends def retrieve( @@ -398,6 +400,5 @@ def send( __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 index 777c5d7f0..d24340d27 100644 --- a/projectq/backends/_ionq/_ionq_http_client_test.py +++ b/projectq/backends/_ionq/_ionq_http_client_test.py @@ -53,18 +53,74 @@ def user_password_input(prompt): assert str(excinfo.value) == 'An authentication token is required!' -def test_is_online(): +def test_is_online(monkeypatch): + def mock_get(_self, path, *args, **kwargs): + assert urljoin(_api_url, 'backends') == path + mock_response = mock.MagicMock() + mock_response.json = mock.MagicMock( + return_value=[ + { + "backend": "qpu.s11", + "status": "available", + "qubits": 11, + "average_queue_time": 3253287, + "last_updated": 1647863473555, + "characterization_url": "/characterizations/48ccd423-2913-45e0-a669-e0f676abeb82", + }, + { + "backend": "simulator", + "status": "available", + "qubits": 19, + "average_queue_time": 1499, + "last_updated": 1627065490042, + }, + ], + ) + return mock_response + + monkeypatch.setattr('requests.sessions.Session.get', mock_get) + 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 ionq_session.is_online('qpu.s11') assert not ionq_session.is_online('ionq_unknown') -def test_show_devices(): - device_list = _ionq_http_client.show_devices() +def test_show_devices(monkeypatch): + def mock_get(_self, path, *args, **kwargs): + assert urljoin(_api_url, 'backends') == path + mock_response = mock.MagicMock() + mock_response.json = mock.MagicMock( + return_value=[ + { + "backend": "qpu.s11", + "status": "available", + "qubits": 11, + "average_queue_time": 3253287, + "last_updated": 1647863473555, + "characterization_url": "/characterizations/48ccd423-2913-45e0-a669-e0f676abeb82", + }, + { + "backend": "simulator", + "status": "available", + "qubits": 19, + "average_queue_time": 1499, + "last_updated": 1627065490042, + }, + ], + ) + return mock_response + + monkeypatch.setattr('requests.sessions.Session.get', mock_get) + + ionq_session = _ionq_http_client.IonQ() + ionq_session.authenticate('not none') + device_list = ionq_session.show_devices() assert isinstance(device_list, dict) + assert len(device_list) == 4 for info in device_list.values(): assert 'nq' in info assert 'target' in info diff --git a/projectq/setups/ionq.py b/projectq/setups/ionq.py index 492c16b1b..e0c678a02 100644 --- a/projectq/setups/ionq.py +++ b/projectq/setups/ionq.py @@ -21,7 +21,7 @@ ->The 29 qubits simulator """ from projectq.backends._exceptions import DeviceOfflineError -from projectq.backends._ionq._ionq_http_client import show_devices +from projectq.backends._ionq._ionq_http_client import IonQ from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper from projectq.ops import ( Barrier, @@ -47,7 +47,10 @@ def get_engine_list(token=None, device=None): """Return the default list of compiler engine for the IonQ platform.""" - devices = show_devices(token) + service = IonQ() + if token is not None: + service.authenticate(token=token) + devices = service.show_devices() if not device or device not in devices: raise DeviceOfflineError("Error checking engine list: no '{}' devices available".format(device)) diff --git a/projectq/setups/ionq_test.py b/projectq/setups/ionq_test.py index d319bc95f..6a3cde827 100644 --- a/projectq/setups/ionq_test.py +++ b/projectq/setups/ionq_test.py @@ -17,6 +17,7 @@ import pytest from projectq.backends._exceptions import DeviceOfflineError +from projectq.backends._ionq._ionq_http_client import IonQ from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper @@ -26,7 +27,11 @@ def test_basic_ionq_mapper(monkeypatch): def mock_show_devices(*args, **kwargs): return {'dummy': {'nq': 3, 'target': 'dummy'}} - monkeypatch.setattr(projectq.setups.ionq, 'show_devices', mock_show_devices) + monkeypatch.setattr( + 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] @@ -41,7 +46,11 @@ def test_ionq_errors(monkeypatch): def mock_show_devices(*args, **kwargs): return {'dummy': {'nq': 3, 'target': 'dummy'}} - monkeypatch.setattr(projectq.setups.ionq, 'show_devices', mock_show_devices) + monkeypatch.setattr( + IonQ, + 'show_devices', + mock_show_devices, + ) with pytest.raises(DeviceOfflineError): projectq.setups.ionq.get_engine_list(device='simulator') From 4bee4d0f749b1fea02d2f6b6f2c369c0b69fcea7 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Mon, 11 Apr 2022 09:22:50 +0000 Subject: [PATCH 079/113] Preparing release vv0.7.2 --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddfa877e6..882f0e788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.7.2] - 2022-04-11 + ### Changed -- Added IonQ dynamic backends fetch. + +- Added IonQ dynamic backends fetch. ### Repository @@ -180,7 +183,9 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi 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/v0.7.1...HEAD +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.2...HEAD + +[v0.7.2]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.1...v0.7.2 [0.7.1]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.0...v0.7.1 From b459f90c8e0668105d5b5f55bd45ca18d8177afe Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Mon, 11 Apr 2022 11:49:12 +0200 Subject: [PATCH 080/113] Fix missed bug in optimize.py --- projectq/cengines/_optimize.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index f4da9921d..c62cb81c2 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -240,9 +240,10 @@ def receive(self, command_list): """ for cmd in command_list: if cmd.gate == FlushGate(): # flush gate --> optimize and flush - for idx, _l in self._l.items(): + # NB: self.optimize(i) modifies self._l + for i in self._l: # pylint: disable=consider-using-dict-items self._optimize(idx) - self._send_qubit_pipeline(idx, len(_l)) + self._send_qubit_pipeline(idx, len(self._l[idx])) new_dict = {} for idx, _l in self._l.items(): if len(_l) > 0: # pragma: no cover From 7189295dba345fc71dd20237c932eaaf5729a046 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Mon, 11 Apr 2022 11:56:39 +0200 Subject: [PATCH 081/113] Fix typo in _optimize.py --- projectq/cengines/_optimize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index c62cb81c2..04a446209 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -241,7 +241,7 @@ def receive(self, command_list): for cmd in command_list: if cmd.gate == FlushGate(): # flush gate --> optimize and flush # NB: self.optimize(i) modifies self._l - for i in self._l: # pylint: disable=consider-using-dict-items + for idx in self._l: # pylint: disable=consider-using-dict-items self._optimize(idx) self._send_qubit_pipeline(idx, len(self._l[idx])) new_dict = {} From bd3433cedd1901a40783dc664bc6948f70f1e15c Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 11 Apr 2022 12:29:46 +0200 Subject: [PATCH 082/113] Fix linters/formatters warnings --- projectq/cengines/_optimize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 04a446209..957ec63f8 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -241,7 +241,7 @@ def receive(self, command_list): for cmd in command_list: if cmd.gate == FlushGate(): # flush gate --> optimize and flush # NB: self.optimize(i) modifies self._l - for idx in self._l: # pylint: disable=consider-using-dict-items + for idx in self._l: # pylint: disable=consider-using-dict-items self._optimize(idx) self._send_qubit_pipeline(idx, len(self._l[idx])) new_dict = {} From 4d01582535e9deba11fea3278989e963cf595cad Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 11 Apr 2022 12:30:24 +0200 Subject: [PATCH 083/113] Update missed hooks --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f535f8345..2d74edd3f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: - id: check-added-large-files - id: check-case-conflict diff --git a/CHANGELOG.md b/CHANGELOG.md index 882f0e788..919917f69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository - Fix issues with building on CentOS 7 & 8 +- Update `pre-commit/pre-commit-hooks` to v4.2.0 - Update `Lucas-C/pre-commit-hooks` hook to v1.1.13 - Update `flake8` hook to v4.0.1 - Update `pylint` hook to v3.0.0a4 From 2b48dd4cbb5a55c1afcb8bddf78a89aa30f0377c Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Mon, 11 Apr 2022 12:47:04 +0200 Subject: [PATCH 084/113] Hotfix: fix GitHub action workflows --- .github/workflows/draft_release.yml | 2 +- .github/workflows/publish_release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index fa5032fe1..6594b2253 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -38,7 +38,7 @@ jobs: id: make-commit run: | git add CHANGELOG.md - git commit --message "Preparing release v${{ github.event.inputs.tag }}" + git commit --message "Preparing release ${{ github.event.inputs.tag }}" echo "::set-output name=commit::$(git rev-parse HEAD)" diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 2898cea5e..9865fee58 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -51,7 +51,7 @@ jobs: run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#release/} - git tag v${VERSION} master + git tag ${VERSION} master - name: Extract version from branch name (for hotfix branches) (Unix) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os != 'Windows' @@ -156,7 +156,7 @@ jobs: 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/} + VERSION=${BRANCH_NAME#release/v} echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV From cef343d93e8aeafa92376f3aa1121fbcea29a4b6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 14:38:15 +0200 Subject: [PATCH 085/113] Merge master into develop branch (#431) * Preparing release vv0.7.2 * Fix missed bug in optimize.py * Fix typo in _optimize.py * Fix linters/formatters warnings * Update missed hooks * Hotfix: fix GitHub action workflows Co-authored-by: GitHub actions Co-authored-by: Nguyen Damien --- .github/workflows/draft_release.yml | 2 +- .github/workflows/publish_release.yml | 4 ++-- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 10 ++++++++-- projectq/cengines/_optimize.py | 5 +++-- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index fa5032fe1..6594b2253 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -38,7 +38,7 @@ jobs: id: make-commit run: | git add CHANGELOG.md - git commit --message "Preparing release v${{ github.event.inputs.tag }}" + git commit --message "Preparing release ${{ github.event.inputs.tag }}" echo "::set-output name=commit::$(git rev-parse HEAD)" diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 2898cea5e..9865fee58 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -51,7 +51,7 @@ jobs: run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#release/} - git tag v${VERSION} master + git tag ${VERSION} master - name: Extract version from branch name (for hotfix branches) (Unix) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os != 'Windows' @@ -156,7 +156,7 @@ jobs: 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/} + VERSION=${BRANCH_NAME#release/v} echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f535f8345..2d74edd3f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: - id: check-added-large-files - id: check-case-conflict diff --git a/CHANGELOG.md b/CHANGELOG.md index ddfa877e6..919917f69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.7.2] - 2022-04-11 + ### Changed -- Added IonQ dynamic backends fetch. + +- Added IonQ dynamic backends fetch. ### Repository - Fix issues with building on CentOS 7 & 8 +- Update `pre-commit/pre-commit-hooks` to v4.2.0 - Update `Lucas-C/pre-commit-hooks` hook to v1.1.13 - Update `flake8` hook to v4.0.1 - Update `pylint` hook to v3.0.0a4 @@ -180,7 +184,9 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi 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/v0.7.1...HEAD +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.2...HEAD + +[v0.7.2]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.1...v0.7.2 [0.7.1]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.0...v0.7.1 diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index f4da9921d..957ec63f8 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -240,9 +240,10 @@ def receive(self, command_list): """ for cmd in command_list: if cmd.gate == FlushGate(): # flush gate --> optimize and flush - for idx, _l in self._l.items(): + # NB: self.optimize(i) modifies self._l + for idx in self._l: # pylint: disable=consider-using-dict-items self._optimize(idx) - self._send_qubit_pipeline(idx, len(_l)) + self._send_qubit_pipeline(idx, len(self._l[idx])) new_dict = {} for idx, _l in self._l.items(): if len(_l) > 0: # pragma: no cover From 41e608dea3e3c6d008647b87540be9eb145c8ff3 Mon Sep 17 00:00:00 2001 From: Jon Donovan Date: Wed, 27 Apr 2022 02:04:20 -0700 Subject: [PATCH 086/113] IonQ API: Move to v0.2, and fixup backends path (#433) * Move to v0.2, and fixup backends path The previous path was mistakenly incorrect, could we release this as a patch release of project-Q? Happy to help how I can. * changelog * fix * One more fixup * remove urljoin * fmt --- CHANGELOG.md | 2 ++ projectq/backends/_ionq/_ionq_http_client.py | 7 +++--- .../backends/_ionq/_ionq_http_client_test.py | 22 ++++++++----------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 919917f69..e66940441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Fixed IonQ dynamic backends fetch, which relied on an incorrect path. + ## [v0.7.2] - 2022-04-11 ### Changed diff --git a/projectq/backends/_ionq/_ionq_http_client.py b/projectq/backends/_ionq/_ionq_http_client.py index 1eb5e573c..4a92ecc57 100644 --- a/projectq/backends/_ionq/_ionq_http_client.py +++ b/projectq/backends/_ionq/_ionq_http_client.py @@ -31,7 +31,8 @@ RequestTimeoutError, ) -_API_URL = 'https://api.ionq.co/v0.1/jobs/' +_API_URL = 'https://api.ionq.co/v0.2/' +_JOB_API_URL = urljoin(_API_URL, 'jobs/') class IonQ(Session): @@ -148,7 +149,7 @@ def run(self, info, device): # _API_URL[:-1] strips the trailing slash. # TODO: Add comprehensive error parsing for non-200 responses. - req = super().post(_API_URL[:-1], json=argument) + req = super().post(_JOB_API_URL[:-1], json=argument) req.raise_for_status() # Process the response. @@ -211,7 +212,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover try: for retries in range(num_retries): - req = super().get(urljoin(_API_URL, execution_id)) + req = super().get(urljoin(_JOB_API_URL, execution_id)) req.raise_for_status() r_json = req.json() status = r_json['status'] diff --git a/projectq/backends/_ionq/_ionq_http_client_test.py b/projectq/backends/_ionq/_ionq_http_client_test.py index d24340d27..d92fb88f4 100644 --- a/projectq/backends/_ionq/_ionq_http_client_test.py +++ b/projectq/backends/_ionq/_ionq_http_client_test.py @@ -18,7 +18,6 @@ import pytest import requests -from requests.compat import urljoin from projectq.backends._exceptions import JobSubmissionError, RequestTimeoutError from projectq.backends._ionq import _ionq_http_client @@ -30,9 +29,6 @@ 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') @@ -55,7 +51,7 @@ def user_password_input(prompt): def test_is_online(monkeypatch): def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'backends') == path + assert 'https://api.ionq.co/v0.2/backends' == path mock_response = mock.MagicMock() mock_response.json = mock.MagicMock( return_value=[ @@ -91,7 +87,7 @@ def mock_get(_self, path, *args, **kwargs): def test_show_devices(monkeypatch): def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'backends') == path + assert 'https://api.ionq.co/v0.2/backends' == path mock_response = mock.MagicMock() mock_response.json = mock.MagicMock( return_value=[ @@ -187,7 +183,7 @@ def _dummy_update(_self): } def mock_post(_self, path, *args, **kwargs): - assert path == _api_url[:-1] + assert path == 'https://api.ionq.co/v0.2/jobs' assert 'json' in kwargs assert expected_request == kwargs['json'] mock_response = mock.MagicMock() @@ -201,7 +197,7 @@ def mock_post(_self, path, *args, **kwargs): return mock_response def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'new-job-id') == path + assert path == 'https://api.ionq.co/v0.2/jobs/new-job-id' mock_response = mock.MagicMock() mock_response.json = mock.MagicMock( return_value={ @@ -433,7 +429,7 @@ def _dummy_update(_self): ) def mock_post(_self, path, **kwargs): - assert _api_url[:-1] == path + assert path == 'https://api.ionq.co/v0.2/jobs' mock_response = mock.MagicMock() mock_response.json = mock.MagicMock(return_value=err_data) return mock_response @@ -472,7 +468,7 @@ def _dummy_update(_self): ) def mock_post(_self, path, *args, **kwargs): - assert path == _api_url[:-1] + assert path == 'https://api.ionq.co/v0.2/jobs' mock_response = mock.MagicMock() mock_response.json = mock.MagicMock( return_value={ @@ -483,7 +479,7 @@ def mock_post(_self, path, *args, **kwargs): return mock_response def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'new-job-id') == path + assert path == 'https://api.ionq.co/v0.2/jobs/new-job-id' mock_response = mock.MagicMock() mock_response.json = mock.MagicMock( return_value={ @@ -533,7 +529,7 @@ def _dummy_update(_self): request_num = [0] def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'old-job-id') == path + assert path == 'https://api.ionq.co/v0.2/jobs/old-job-id' json_response = { 'id': 'old-job-id', 'status': 'running', @@ -591,7 +587,7 @@ def _dummy_update(_self): request_num = [0] def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'old-job-id') == path + assert path == 'https://api.ionq.co/v0.2/jobs/old-job-id' json_response = { 'id': 'old-job-id', 'status': 'running', From 714567663b0611f37e27ead8bb4cf1bb8b10fdd8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Apr 2022 11:32:21 +0200 Subject: [PATCH 087/113] Release version v0.7.3 (#435) * Merge master into develop branch (#431) * Preparing release vv0.7.2 * Fix missed bug in optimize.py * Fix typo in _optimize.py * Fix linters/formatters warnings * Update missed hooks * Hotfix: fix GitHub action workflows Co-authored-by: GitHub actions Co-authored-by: Nguyen Damien * IonQ API: Move to v0.2, and fixup backends path (#433) * Move to v0.2, and fixup backends path The previous path was mistakenly incorrect, could we release this as a patch release of project-Q? Happy to help how I can. * changelog * fix * One more fixup * remove urljoin * fmt * Preparing release v0.7.3 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub actions Co-authored-by: Nguyen Damien Co-authored-by: Jon Donovan --- .github/workflows/publish_release.yml | 12 ++++++---- CHANGELOG.md | 10 ++++++++- projectq/backends/_ionq/_ionq_http_client.py | 7 +++--- .../backends/_ionq/_ionq_http_client_test.py | 22 ++++++++----------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 9865fee58..0849ee1fa 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -51,6 +51,7 @@ jobs: run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#release/} + echo "VERSION = ${VERSION}" git tag ${VERSION} master - name: Extract version from branch name (for hotfix branches) (Unix) @@ -58,7 +59,8 @@ jobs: run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#hotfix/} - git tag v${VERSION} master + echo "VERSION = ${VERSION}" + git tag ${VERSION} master # ------------------------------------------------------------------------ @@ -67,14 +69,16 @@ jobs: run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "release/","" - git tag v${VERSION} master + Write-Output "VERSION = ${VERSION}" + git tag ${VERSION} master - name: Extract version from branch name (for hotfix branches) (Windows) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os == 'Windows' run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "hotfix/","" - git tag v${VERSION} master + Write-Output "VERSION = ${VERSION}" + git tag ${VERSION} master # ======================================================================== @@ -164,7 +168,7 @@ jobs: 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/} + VERSION=${BRANCH_NAME#hotfix/v} echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV diff --git a/CHANGELOG.md b/CHANGELOG.md index 919917f69..31e3d2685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.7.3] - 2022-04-27 + +### Fixed + +- Fixed IonQ dynamic backends fetch, which relied on an incorrect path. + ## [v0.7.2] - 2022-04-11 ### Changed @@ -184,7 +190,9 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi 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/v0.7.2...HEAD +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.3...HEAD + +[v0.7.3]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.2...v0.7.3 [v0.7.2]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.1...v0.7.2 diff --git a/projectq/backends/_ionq/_ionq_http_client.py b/projectq/backends/_ionq/_ionq_http_client.py index 1eb5e573c..4a92ecc57 100644 --- a/projectq/backends/_ionq/_ionq_http_client.py +++ b/projectq/backends/_ionq/_ionq_http_client.py @@ -31,7 +31,8 @@ RequestTimeoutError, ) -_API_URL = 'https://api.ionq.co/v0.1/jobs/' +_API_URL = 'https://api.ionq.co/v0.2/' +_JOB_API_URL = urljoin(_API_URL, 'jobs/') class IonQ(Session): @@ -148,7 +149,7 @@ def run(self, info, device): # _API_URL[:-1] strips the trailing slash. # TODO: Add comprehensive error parsing for non-200 responses. - req = super().post(_API_URL[:-1], json=argument) + req = super().post(_JOB_API_URL[:-1], json=argument) req.raise_for_status() # Process the response. @@ -211,7 +212,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover try: for retries in range(num_retries): - req = super().get(urljoin(_API_URL, execution_id)) + req = super().get(urljoin(_JOB_API_URL, execution_id)) req.raise_for_status() r_json = req.json() status = r_json['status'] diff --git a/projectq/backends/_ionq/_ionq_http_client_test.py b/projectq/backends/_ionq/_ionq_http_client_test.py index d24340d27..d92fb88f4 100644 --- a/projectq/backends/_ionq/_ionq_http_client_test.py +++ b/projectq/backends/_ionq/_ionq_http_client_test.py @@ -18,7 +18,6 @@ import pytest import requests -from requests.compat import urljoin from projectq.backends._exceptions import JobSubmissionError, RequestTimeoutError from projectq.backends._ionq import _ionq_http_client @@ -30,9 +29,6 @@ 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') @@ -55,7 +51,7 @@ def user_password_input(prompt): def test_is_online(monkeypatch): def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'backends') == path + assert 'https://api.ionq.co/v0.2/backends' == path mock_response = mock.MagicMock() mock_response.json = mock.MagicMock( return_value=[ @@ -91,7 +87,7 @@ def mock_get(_self, path, *args, **kwargs): def test_show_devices(monkeypatch): def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'backends') == path + assert 'https://api.ionq.co/v0.2/backends' == path mock_response = mock.MagicMock() mock_response.json = mock.MagicMock( return_value=[ @@ -187,7 +183,7 @@ def _dummy_update(_self): } def mock_post(_self, path, *args, **kwargs): - assert path == _api_url[:-1] + assert path == 'https://api.ionq.co/v0.2/jobs' assert 'json' in kwargs assert expected_request == kwargs['json'] mock_response = mock.MagicMock() @@ -201,7 +197,7 @@ def mock_post(_self, path, *args, **kwargs): return mock_response def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'new-job-id') == path + assert path == 'https://api.ionq.co/v0.2/jobs/new-job-id' mock_response = mock.MagicMock() mock_response.json = mock.MagicMock( return_value={ @@ -433,7 +429,7 @@ def _dummy_update(_self): ) def mock_post(_self, path, **kwargs): - assert _api_url[:-1] == path + assert path == 'https://api.ionq.co/v0.2/jobs' mock_response = mock.MagicMock() mock_response.json = mock.MagicMock(return_value=err_data) return mock_response @@ -472,7 +468,7 @@ def _dummy_update(_self): ) def mock_post(_self, path, *args, **kwargs): - assert path == _api_url[:-1] + assert path == 'https://api.ionq.co/v0.2/jobs' mock_response = mock.MagicMock() mock_response.json = mock.MagicMock( return_value={ @@ -483,7 +479,7 @@ def mock_post(_self, path, *args, **kwargs): return mock_response def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'new-job-id') == path + assert path == 'https://api.ionq.co/v0.2/jobs/new-job-id' mock_response = mock.MagicMock() mock_response.json = mock.MagicMock( return_value={ @@ -533,7 +529,7 @@ def _dummy_update(_self): request_num = [0] def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'old-job-id') == path + assert path == 'https://api.ionq.co/v0.2/jobs/old-job-id' json_response = { 'id': 'old-job-id', 'status': 'running', @@ -591,7 +587,7 @@ def _dummy_update(_self): request_num = [0] def mock_get(_self, path, *args, **kwargs): - assert urljoin(_api_url, 'old-job-id') == path + assert path == 'https://api.ionq.co/v0.2/jobs/old-job-id' json_response = { 'id': 'old-job-id', 'status': 'running', From ab2211eebd052f27df79b21e7bfc25fa3117c480 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Apr 2022 12:35:17 +0200 Subject: [PATCH 088/113] Merge master into develop branch (#436) * Preparing release vv0.7.2 * Fix missed bug in optimize.py * Fix typo in _optimize.py * Fix linters/formatters warnings * Update missed hooks * Hotfix: fix GitHub action workflows * Release version v0.7.3 (#435) * Merge master into develop branch (#431) * Preparing release vv0.7.2 * Fix missed bug in optimize.py * Fix typo in _optimize.py * Fix linters/formatters warnings * Update missed hooks * Hotfix: fix GitHub action workflows Co-authored-by: GitHub actions Co-authored-by: Nguyen Damien * IonQ API: Move to v0.2, and fixup backends path (#433) * Move to v0.2, and fixup backends path The previous path was mistakenly incorrect, could we release this as a patch release of project-Q? Happy to help how I can. * changelog * fix * One more fixup * remove urljoin * fmt * Preparing release v0.7.3 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub actions Co-authored-by: Nguyen Damien Co-authored-by: Jon Donovan Co-authored-by: GitHub actions Co-authored-by: Nguyen Damien Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Jon Donovan --- .github/workflows/publish_release.yml | 12 ++++++++---- CHANGELOG.md | 8 +++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 9865fee58..0849ee1fa 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -51,6 +51,7 @@ jobs: run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#release/} + echo "VERSION = ${VERSION}" git tag ${VERSION} master - name: Extract version from branch name (for hotfix branches) (Unix) @@ -58,7 +59,8 @@ jobs: run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#hotfix/} - git tag v${VERSION} master + echo "VERSION = ${VERSION}" + git tag ${VERSION} master # ------------------------------------------------------------------------ @@ -67,14 +69,16 @@ jobs: run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "release/","" - git tag v${VERSION} master + Write-Output "VERSION = ${VERSION}" + git tag ${VERSION} master - name: Extract version from branch name (for hotfix branches) (Windows) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os == 'Windows' run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "hotfix/","" - git tag v${VERSION} master + Write-Output "VERSION = ${VERSION}" + git tag ${VERSION} master # ======================================================================== @@ -164,7 +168,7 @@ jobs: 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/} + VERSION=${BRANCH_NAME#hotfix/v} echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV diff --git a/CHANGELOG.md b/CHANGELOG.md index e66940441..31e3d2685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.7.3] - 2022-04-27 + +### Fixed + - Fixed IonQ dynamic backends fetch, which relied on an incorrect path. ## [v0.7.2] - 2022-04-11 @@ -186,7 +190,9 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi 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/v0.7.2...HEAD +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.3...HEAD + +[v0.7.3]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.2...v0.7.3 [v0.7.2]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.1...v0.7.2 From 78afc11353387ff9657bf65ac9dfdb834c966792 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 23:32:05 +0200 Subject: [PATCH 089/113] Bump docker/setup-qemu-action from 1 to 2 (#437) * Bump docker/setup-qemu-action from 1 to 2 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 1 to 2. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v1...v2) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update CHANGELOG * Fix CentOS 7 build Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nguyen Damien --- .github/workflows/ci.yml | 4 +++- .github/workflows/publish_release.yml | 2 +- CHANGELOG.md | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a5939e11..73db64348 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -242,7 +242,9 @@ jobs: run: yum -y install https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm - name: Install Git > 2.18 - run: yum install -y git + run: | + yum install -y git + git config --global --add safe.directory /__w/ProjectQ/ProjectQ - uses: actions/checkout@v2 diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 0849ee1fa..ff4bcd221 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Set up QEMU if: matrix.cibw_archs == 'aarch64' - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 with: platforms: arm64 diff --git a/CHANGELOG.md b/CHANGELOG.md index 31e3d2685..0b85aab65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Repository + +- Update `docker/setup-qemu-action` GitHub action to v2 +- Fixed CentOS 7 configuration issue + ## [v0.7.3] - 2022-04-27 ### Fixed From 8d9e4652f5f98f4157043c408b3dea25dc6ada2d Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Sun, 10 Jul 2022 22:37:53 +0200 Subject: [PATCH 090/113] Fix setuptools-scm version for Python 3.6 (#440) --- CHANGELOG.md | 2 ++ pyproject.toml | 4 +++- setup.cfg | 3 --- setup.py | 15 +++++++++++++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b85aab65..601765f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update `docker/setup-qemu-action` GitHub action to v2 - Fixed CentOS 7 configuration issue +- Remove deprecated `setup_requires` field in setup.cfg +- Fixed installation issue with newer versions of `setuptool-scm`and Python¨ 3.6 ## [v0.7.3] - 2022-04-27 diff --git a/pyproject.toml b/pyproject.toml index 7752fa4bf..db6087272 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,7 @@ [build-system] -requires = ["setuptools>=42", "wheel", "pybind11>=2", "setuptools_scm[toml]>=3.4"] +requires = ["setuptools>=42", "wheel", "pybind11>=2", + "setuptools_scm[toml]<7;python_version<'3.7'", + "setuptools_scm[toml]>6;python_version>='3.7'"] build-backend = "setuptools.build_meta" # ============================================================================== diff --git a/setup.cfg b/setup.cfg index 0f5a22bfd..4bea68081 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,9 +31,6 @@ classifier = zip_safe = False packages = find: python_requires = >= 3 -setup_requires = - setuptools_scm[toml] - pybind11 >= 2 install_requires = matplotlib >= 2.2.3 networkx >= 2 diff --git a/setup.py b/setup.py index 46c394d1b..7b3c66489 100755 --- a/setup.py +++ b/setup.py @@ -58,6 +58,14 @@ from setuptools import Extension, setup from setuptools.command.build_ext import build_ext +try: + import setuptools_scm # noqa: F401 # pylint: disable=unused-import + + _HAS_SETUPTOOLS_SCM = True +except ImportError: + _HAS_SETUPTOOLS_SCM = False + + # ============================================================================== # Helper functions and classes @@ -645,9 +653,12 @@ def run_setup(with_cext): else: kwargs['ext_modules'] = [] + # NB: Workaround for people calling setup.py without a proper environment containing setuptools-scm + # This can typically be the case when calling the `gen_reqfile` or `clang_tidy`commands + if not _HAS_SETUPTOOLS_SCM: + kwargs['version'] = '0.0.0' + setup( - use_scm_version={'local_scheme': 'no-local-version'}, - setup_requires=['setuptools_scm'], cmdclass={ 'build_ext': BuildExt, 'clang_tidy': ClangTidy, From 6bbda5343291d81e33a4cf10ac015679c9aefdc7 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Tue, 19 Jul 2022 10:47:25 +0200 Subject: [PATCH 091/113] Add `blacken-docs` and `pyupgrade` to pre-commit configuration (#441) * Add `blacken-docs` and `pyupgrade` to pre-commit configuration * Apply new pre-commit hooks and fix related issues. * Update CHANGELOG * Fix unit tests failures * Fix pre-commit failure * Generalise the use of f-strings * Fix IonQ tests * Run `pyupgrade` before `isort` and `black` --- .pre-commit-config.yaml | 14 +- CHANGELOG.md | 1 + README.rst | 55 +++--- docs/conf.py | 6 +- docs/package_description.py | 25 ++- docs/tutorials.rst | 7 +- examples/aqt.py | 3 +- examples/bellpair_circuit.py | 1 - examples/control_tester.py | 1 - examples/gate_zoo.py | 7 +- examples/grover.py | 1 - examples/hws4.py | 3 +- examples/hws6.py | 3 +- examples/ibm.py | 3 +- examples/ibmq_tutorial.ipynb | 2 +- examples/ionq.py | 3 +- examples/ionq_bv.py | 3 +- examples/ionq_half_adder.py | 1 - examples/mapper_tutorial.ipynb | 20 +- examples/quantum_random_numbers.py | 3 +- examples/quantum_random_numbers_ibm.py | 3 +- examples/shor.py | 14 +- examples/simulator_tutorial.ipynb | 32 ++-- examples/spectral_measurement.ipynb | 2 +- examples/teleport.py | 3 +- examples/teleport_circuit.py | 1 - examples/unitary_simulator.py | 1 - projectq/__init__.py | 1 - projectq/backends/__init__.py | 1 - projectq/backends/_aqt/__init__.py | 1 - projectq/backends/_aqt/_aqt.py | 15 +- projectq/backends/_aqt/_aqt_http_client.py | 20 +- .../backends/_aqt/_aqt_http_client_test.py | 1 - projectq/backends/_aqt/_aqt_test.py | 1 - projectq/backends/_awsbraket/__init__.py | 1 - projectq/backends/_awsbraket/_awsbraket.py | 17 +- .../_awsbraket/_awsbraket_boto3_client.py | 34 ++-- .../_awsbraket_boto3_client_test.py | 5 +- .../_awsbraket_boto3_client_test_fixtures.py | 1 - .../backends/_awsbraket/_awsbraket_test.py | 4 +- .../_awsbraket/_awsbraket_test_fixtures.py | 1 - projectq/backends/_circuits/__init__.py | 1 - projectq/backends/_circuits/_drawer.py | 77 ++++---- .../backends/_circuits/_drawer_matplotlib.py | 10 +- .../_circuits/_drawer_matplotlib_test.py | 34 ++-- projectq/backends/_circuits/_drawer_test.py | 28 ++- projectq/backends/_circuits/_plot.py | 10 +- projectq/backends/_circuits/_plot_test.py | 13 +- projectq/backends/_circuits/_to_latex.py | 172 +++++++----------- projectq/backends/_circuits/_to_latex_test.py | 9 +- projectq/backends/_exceptions.py | 1 - projectq/backends/_ibm/__init__.py | 1 - projectq/backends/_ibm/_ibm.py | 22 +-- projectq/backends/_ibm/_ibm_http_client.py | 20 +- .../backends/_ibm/_ibm_http_client_test.py | 45 ++--- projectq/backends/_ibm/_ibm_test.py | 1 - projectq/backends/_ionq/__init__.py | 1 - projectq/backends/_ionq/_ionq.py | 11 +- projectq/backends/_ionq/_ionq_http_client.py | 37 ++-- .../backends/_ionq/_ionq_http_client_test.py | 1 - projectq/backends/_ionq/_ionq_mapper.py | 5 +- projectq/backends/_ionq/_ionq_mapper_test.py | 1 - projectq/backends/_ionq/_ionq_test.py | 1 - projectq/backends/_printer.py | 6 +- projectq/backends/_printer_test.py | 11 +- projectq/backends/_resource.py | 1 - projectq/backends/_resource_test.py | 3 +- projectq/backends/_sim/__init__.py | 1 - .../backends/_sim/_classical_simulator.py | 1 - .../_sim/_classical_simulator_test.py | 1 - projectq/backends/_sim/_pysim.py | 5 +- projectq/backends/_sim/_simulator.py | 10 +- projectq/backends/_sim/_simulator_test.py | 5 +- .../backends/_sim/_simulator_test_fixtures.py | 1 - projectq/backends/_unitary.py | 3 +- projectq/backends/_unitary_test.py | 3 +- projectq/cengines/__init__.py | 1 - projectq/cengines/_basicmapper.py | 1 - projectq/cengines/_basicmapper_test.py | 1 - projectq/cengines/_basics.py | 7 +- projectq/cengines/_basics_test.py | 1 - projectq/cengines/_cmdmodifier.py | 3 +- projectq/cengines/_cmdmodifier_test.py | 1 - projectq/cengines/_ibm5qubitmapper.py | 1 - projectq/cengines/_ibm5qubitmapper_test.py | 1 - projectq/cengines/_linearmapper.py | 3 +- projectq/cengines/_linearmapper_test.py | 1 - projectq/cengines/_main.py | 21 ++- projectq/cengines/_main_test.py | 1 - projectq/cengines/_manualmapper.py | 1 - projectq/cengines/_manualmapper_test.py | 1 - projectq/cengines/_optimize.py | 1 - projectq/cengines/_optimize_test.py | 1 - projectq/cengines/_replacer/__init__.py | 1 - .../cengines/_replacer/_decomposition_rule.py | 1 - .../_replacer/_decomposition_rule_set.py | 4 +- .../_replacer/_decomposition_rule_test.py | 1 - projectq/cengines/_replacer/_replacer.py | 5 +- projectq/cengines/_replacer/_replacer_test.py | 1 - projectq/cengines/_swapandcnotflipper.py | 7 +- projectq/cengines/_swapandcnotflipper_test.py | 1 - projectq/cengines/_tagremover.py | 3 +- projectq/cengines/_tagremover_test.py | 3 +- projectq/cengines/_testengine.py | 7 +- projectq/cengines/_testengine_test.py | 6 +- projectq/cengines/_twodmapper.py | 1 - projectq/cengines/_twodmapper_test.py | 1 - projectq/libs/__init__.py | 1 - projectq/libs/hist/__init__.py | 1 - projectq/libs/hist/_histogram.py | 3 +- projectq/libs/hist/_histogram_test.py | 1 - projectq/libs/math/__init__.py | 1 - projectq/libs/math/_constantmath.py | 1 - projectq/libs/math/_constantmath_test.py | 1 - projectq/libs/math/_default_rules.py | 1 - projectq/libs/math/_gates.py | 93 +++++----- projectq/libs/math/_gates_math_test.py | 3 +- projectq/libs/math/_gates_test.py | 1 - projectq/libs/math/_quantummath.py | 1 - projectq/libs/math/_quantummath_test.py | 3 +- projectq/libs/revkit/__init__.py | 1 - projectq/libs/revkit/_control_function.py | 5 +- .../libs/revkit/_control_function_test.py | 1 - projectq/libs/revkit/_permutation.py | 1 - projectq/libs/revkit/_permutation_test.py | 1 - projectq/libs/revkit/_phase.py | 5 +- projectq/libs/revkit/_phase_test.py | 1 - projectq/libs/revkit/_utils.py | 1 - projectq/meta/__init__.py | 1 - projectq/meta/_compute.py | 7 +- projectq/meta/_compute_test.py | 5 +- projectq/meta/_control.py | 14 +- projectq/meta/_control_test.py | 3 +- projectq/meta/_dagger.py | 8 +- projectq/meta/_dagger_test.py | 1 - projectq/meta/_dirtyqubit.py | 1 - projectq/meta/_dirtyqubit_test.py | 1 - projectq/meta/_exceptions.py | 1 - projectq/meta/_logicalqubit.py | 1 - projectq/meta/_logicalqubit_test.py | 1 - projectq/meta/_loop.py | 12 +- projectq/meta/_loop_test.py | 1 - projectq/meta/_util.py | 1 - projectq/meta/_util_test.py | 1 - projectq/ops/__init__.py | 1 - projectq/ops/_basics.py | 37 ++-- projectq/ops/_basics_test.py | 3 +- projectq/ops/_command.py | 7 +- projectq/ops/_command_test.py | 7 +- projectq/ops/_gates.py | 10 +- projectq/ops/_gates_test.py | 1 - projectq/ops/_metagates.py | 29 ++- projectq/ops/_metagates_test.py | 11 +- projectq/ops/_qaagate.py | 15 +- projectq/ops/_qaagate_test.py | 1 - projectq/ops/_qftgate.py | 1 - projectq/ops/_qftgate_test.py | 1 - projectq/ops/_qpegate.py | 3 +- projectq/ops/_qpegate_test.py | 1 - projectq/ops/_qubit_operator.py | 26 ++- projectq/ops/_qubit_operator_test.py | 1 - projectq/ops/_shortcuts.py | 1 - projectq/ops/_shortcuts_test.py | 1 - projectq/ops/_state_prep.py | 3 +- projectq/ops/_state_prep_test.py | 1 - projectq/ops/_time_evolution.py | 3 +- projectq/ops/_time_evolution_test.py | 1 - .../ops/_uniformly_controlled_rotation.py | 17 +- .../_uniformly_controlled_rotation_test.py | 1 - projectq/setups/__init__.py | 1 - projectq/setups/_utils.py | 1 - projectq/setups/aqt.py | 1 - projectq/setups/aqt_test.py | 1 - projectq/setups/awsbraket.py | 3 +- projectq/setups/awsbraket_test.py | 1 - projectq/setups/decompositions/__init__.py | 1 - projectq/setups/decompositions/_gates_test.py | 1 - .../decompositions/amplitudeamplification.py | 15 +- .../amplitudeamplification_test.py | 11 +- .../decompositions/arb1qubit2rzandry.py | 7 +- .../decompositions/arb1qubit2rzandry_test.py | 1 - projectq/setups/decompositions/barrier.py | 1 - .../setups/decompositions/barrier_test.py | 1 - .../decompositions/carb1qubit2cnotrzandry.py | 1 - .../carb1qubit2cnotrzandry_test.py | 1 - projectq/setups/decompositions/cnot2cz.py | 1 - .../setups/decompositions/cnot2cz_test.py | 1 - projectq/setups/decompositions/cnot2rxx.py | 1 - .../setups/decompositions/cnot2rxx_test.py | 1 - .../setups/decompositions/cnu2toffoliandcu.py | 1 - .../decompositions/cnu2toffoliandcu_test.py | 1 - .../setups/decompositions/controlstate.py | 1 - .../decompositions/controlstate_test.py | 1 - projectq/setups/decompositions/crz2cxandrz.py | 1 - projectq/setups/decompositions/entangle.py | 1 - projectq/setups/decompositions/globalphase.py | 1 - projectq/setups/decompositions/h2rx.py | 1 - projectq/setups/decompositions/h2rx_test.py | 1 - projectq/setups/decompositions/ph2r.py | 1 - .../setups/decompositions/phaseestimation.py | 25 +-- .../decompositions/phaseestimation_test.py | 21 +-- .../decompositions/qft2crandhadamard.py | 1 - .../setups/decompositions/qubitop2onequbit.py | 1 - .../decompositions/qubitop2onequbit_test.py | 3 +- projectq/setups/decompositions/r2rzandph.py | 1 - projectq/setups/decompositions/rx2rz.py | 1 - projectq/setups/decompositions/rx2rz_test.py | 1 - projectq/setups/decompositions/ry2rz.py | 1 - projectq/setups/decompositions/ry2rz_test.py | 1 - projectq/setups/decompositions/rz2rx.py | 1 - projectq/setups/decompositions/rz2rx_test.py | 1 - .../setups/decompositions/sqrtswap2cnot.py | 1 - .../decompositions/sqrtswap2cnot_test.py | 1 - .../setups/decompositions/stateprep2cnot.py | 1 - .../decompositions/stateprep2cnot_test.py | 1 - projectq/setups/decompositions/swap2cnot.py | 1 - .../setups/decompositions/time_evolution.py | 1 - .../decompositions/time_evolution_test.py | 1 - .../decompositions/toffoli2cnotandtgate.py | 1 - .../uniformlycontrolledr2cnot.py | 1 - .../uniformlycontrolledr2cnot_test.py | 3 +- projectq/setups/default.py | 1 - projectq/setups/grid.py | 1 - projectq/setups/grid_test.py | 1 - projectq/setups/ibm.py | 1 - projectq/setups/ibm_test.py | 1 - projectq/setups/ionq.py | 3 +- projectq/setups/ionq_test.py | 3 +- projectq/setups/linear.py | 1 - projectq/setups/linear_test.py | 1 - projectq/setups/restrictedgateset.py | 1 - projectq/setups/restrictedgateset_test.py | 1 - projectq/setups/trapped_ion_decomposer.py | 1 - .../setups/trapped_ion_decomposer_test.py | 1 - projectq/tests/__init__.py | 1 - projectq/tests/_factoring_test.py | 1 - projectq/types/__init__.py | 1 - projectq/types/_qubit.py | 6 +- projectq/types/_qubit_test.py | 3 +- setup.py | 27 ++- 240 files changed, 625 insertions(+), 894 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d74edd3f..804d6e47f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,7 +35,6 @@ repos: - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace - - id: fix-encoding-pragma # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks @@ -43,6 +42,12 @@ repos: hooks: - id: remove-tabs + - repo: https://github.com/asottile/pyupgrade + rev: v2.34.0 + hooks: + - id: pyupgrade + args: [--py36-plus, --keep-mock] + - repo: https://github.com/PyCQA/isort rev: 5.10.1 hooks: @@ -57,6 +62,13 @@ repos: # This is a slow hook, so only run this if --hook-stage manual is passed stages: [manual] + - repo: https://github.com/asottile/blacken-docs + rev: v1.12.1 + hooks: + - id: blacken-docs + args: [-S, -l, '120'] + additional_dependencies: [black==22.3.0] + - repo: https://gitlab.com/PyCQA/flake8 rev: 3.9.2 hooks: diff --git a/CHANGELOG.md b/CHANGELOG.md index 601765f00..4228cb015 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update `docker/setup-qemu-action` GitHub action to v2 - Fixed CentOS 7 configuration issue +- Added two new pre-commit hooks: `blacken-docs` and `pyupgrade` - Remove deprecated `setup_requires` field in setup.cfg - Fixed installation issue with newer versions of `setuptool-scm`and Python¨ 3.6 diff --git a/README.rst b/README.rst index 3e24d7332..1f85c2cc2 100755 --- a/README.rst +++ b/README.rst @@ -43,7 +43,10 @@ Examples .. 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) + 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 a quantum register with 1 qubit @@ -52,7 +55,7 @@ Examples Measure | qubit # measure the qubit eng.flush() # flush all gates (and execute measurements) - print("Measured {}".format(int(qubit))) # converting a qubit to int or bool gives access to the measurement result + print(f"Measured {int(qubit)}") # converting a qubit to int or bool gives access to the measurement result ProjectQ features a lean syntax which is close to the mathematical notation used in quantum physics. For example, a rotation of a qubit around the x-axis is usually specified as: @@ -80,9 +83,7 @@ Instead of simulating a quantum program, one can use our resource counter (as a from projectq.ops import QFT from projectq.setups import linear - compiler_engines = linear.get_engine_list(num_qubits=16, - one_qubit_gates='any', - two_qubit_gates=(CNOT, Swap)) + compiler_engines = linear.get_engine_list(num_qubits=16, one_qubit_gates='any', two_qubit_gates=(CNOT, Swap)) resource_counter = ResourceCounter() eng = MainEngine(backend=resource_counter, engine_list=compiler_engines) qureg = eng.allocate_qureg(16) @@ -112,12 +113,13 @@ 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) - eng = MainEngine(IBMBackend(token=token, use_hardware=True, num_runs=1024, - verbose=False, device=device), - engine_list=compiler_engines) + token = 'MY_TOKEN' + device = 'ibmq_16_melbourne' + compiler_engines = projectq.setups.ibm.get_engine_list(token=token, device=device) + eng = MainEngine( + IBMBackend(token=token, use_hardware=True, num_runs=1024, verbose=False, device=device), + engine_list=compiler_engines, + ) **Running a quantum program on AQT devices** @@ -129,12 +131,13 @@ 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) - eng = MainEngine(AQTBackend(token=token,use_hardware=True, num_runs=1024, - verbose=False, device=device), - engine_list=compiler_engines) + token = 'MY_TOKEN' + device = 'aqt_device' + compiler_engines = projectq.setups.aqt.get_engine_list(token=token, device=device) + eng = MainEngine( + AQTBackend(token=token, use_hardware=True, num_runs=1024, verbose=False, device=device), + engine_list=compiler_engines, + ) **Running a quantum program on a AWS Braket provided device** @@ -150,13 +153,21 @@ IonQ from IonQ and the state vector simulator SV1: 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=[]) + device = 'IonQ' + eng = MainEngine( + AWSBraketBackend( + use_hardware=True, + credentials=creds, + s3_folder=s3_folder, + num_runs=1024, + verbose=False, + device=device, + ), + engine_list=[], + ) .. note:: diff --git a/docs/conf.py b/docs/conf.py index 4b2b99fa2..a9d5efc09 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # projectq documentation build configuration file, created by # sphinx-quickstart on Tue Nov 29 11:51:46 2016. @@ -533,6 +532,7 @@ def linkcode_resolve(domain, info): import projectq.setups.ibm as ibm_setup from projectq import MainEngine + eng = MainEngine(engine_list=ibm_setup.get_engine_list()) # eng uses the default Simulator backend @@ -560,10 +560,10 @@ def linkcode_resolve(domain, info): os.mkdir(docgen_path) for desc in descriptions: - fname = os.path.join(docgen_path, 'projectq.{}.rst'.format(desc.name)) + fname = os.path.join(docgen_path, f'projectq.{desc.name}.rst') lines = None if os.path.exists(fname): - with open(fname, 'r') as fd: + with open(fname) as fd: lines = [line[:-1] for line in fd.readlines()] new_lines = desc.get_ReST() diff --git a/docs/package_description.py b/docs/package_description.py index fcfbb5b21..d04ba67c3 100644 --- a/docs/package_description.py +++ b/docs/package_description.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,7 +51,7 @@ def __init__( # pylint: disable=too-many-arguments if pkg_name not in PackageDescription.package_list: PackageDescription.package_list.append(pkg_name) - self.module = importlib.import_module('projectq.{}'.format(self.name)) + self.module = importlib.import_module(f'projectq.{self.name}') self.module_special_members = module_special_members self.submodule_special_members = submodule_special_members @@ -72,7 +71,7 @@ def __init__( # pylint: disable=too-many-arguments self.subpackages = [] self.submodules = [] for name, obj in sub: - if '{}.{}'.format(self.name, name) in PackageDescription.package_list: + if f'{self.name}.{name}' in PackageDescription.package_list: self.subpackages.append((name, obj)) else: self.submodules.append((name, obj)) @@ -115,7 +114,7 @@ def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-s new_lines.append(' :maxdepth: 1') new_lines.append('') for name, _ in self.subpackages: - new_lines.append(' projectq.{}.{}'.format(self.name, name)) + new_lines.append(f' projectq.{self.name}.{name}') new_lines.append('') else: submodule_has_index = True @@ -123,11 +122,11 @@ def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-s new_lines.append('') if self.submodules: for name, _ in self.submodules: - new_lines.append('\tprojectq.{}.{}'.format(self.name, name)) + new_lines.append(f'\tprojectq.{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(f'\tprojectq.{self.name}.{name}') new_lines.append('') if self.submodules: @@ -142,27 +141,27 @@ def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-s new_lines.append('.. autosummary::') new_lines.append('') for name, _ in self.submodules: - new_lines.append(' projectq.{}.{}'.format(self.name, name)) + new_lines.append(f' projectq.{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(f'.. automodule:: projectq.{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(f' :special-members: {self.submodule_special_members}') new_lines.append(' :undoc-members:') new_lines.append('') new_lines.append('Module contents') new_lines.append('-' * len(new_lines[-1])) new_lines.append('') - new_lines.append('.. automodule:: projectq.{}'.format(self.name)) + new_lines.append(f'.. automodule:: projectq.{self.name}') new_lines.append(' :members:') new_lines.append(' :undoc-members:') - new_lines.append(' :special-members: {}'.format(self.module_special_members)) + new_lines.append(f' :special-members: {self.module_special_members}') new_lines.append(' :imported-members:') new_lines.append('') @@ -174,9 +173,9 @@ def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-s new_lines.append(title) new_lines.append('^' * len(title)) new_lines.append('') - new_lines.append('.. automodule:: projectq.{}.{}'.format(self.name, name)) + new_lines.append(f'.. automodule:: projectq.{self.name}.{name}') for param in params: - new_lines.append(' {}'.format(param)) + new_lines.append(f' {param}') new_lines.append('') return new_lines[:-1] diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 58d8de1c9..669ec9eb0 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -239,7 +239,10 @@ 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) + 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 @@ -248,6 +251,6 @@ To check out the ProjectQ syntax in action and to see whether the installation w Measure | qubit # measure the qubit eng.flush() # flush all gates (and execute measurements) - print("Measured {}".format(int(qubit))) # output measurement result + print(f"Measured {int(qubit)}") # output measurement result Which creates random bits (0 or 1). diff --git a/examples/aqt.py b/examples/aqt.py index 68da13606..1be765189 100644 --- a/examples/aqt.py +++ b/examples/aqt.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of running a quantum circuit using the AQT APIs.""" @@ -40,7 +39,7 @@ def run_entangle(eng, num_qubits=3): # 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])) + # print(f"Measured {state} with p = {results[state]}.") # or plot them directly: histogram(eng.backend, qureg) plt.show() diff --git a/examples/bellpair_circuit.py b/examples/bellpair_circuit.py index c4343f801..611e1ffe5 100755 --- a/examples/bellpair_circuit.py +++ b/examples/bellpair_circuit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example implementation of a quantum circuit generating a Bell pair state.""" diff --git a/examples/control_tester.py b/examples/control_tester.py index 8f9463735..3175404a7 100755 --- a/examples/control_tester.py +++ b/examples/control_tester.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/examples/gate_zoo.py b/examples/gate_zoo.py index c8e97ed97..684addfa4 100644 --- a/examples/gate_zoo.py +++ b/examples/gate_zoo.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Showcase most of the quantum gates available in ProjectQ.""" @@ -95,12 +94,12 @@ def add(x, y): # generate latex code to draw the circuit s = drawing_engine.get_latex() prefix = 'zoo' - with open('{}.tex'.format(prefix), 'w') as f: + with open(f'{prefix}.tex', 'w') as f: f.write(s) # compile latex source code and open pdf file - os.system('pdflatex {}.tex'.format(prefix)) - openfile('{}.pdf'.format(prefix)) + os.system(f'pdflatex {prefix}.tex') + openfile(f'{prefix}.pdf') def openfile(filename): diff --git a/examples/grover.py b/examples/grover.py index 539fc525a..1d1511db1 100755 --- a/examples/grover.py +++ b/examples/grover.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example implementation of Grover's algorithm.""" diff --git a/examples/hws4.py b/examples/hws4.py index 8ba5b6174..4225480b2 100644 --- a/examples/hws4.py +++ b/examples/hws4.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of a 4-qubit phase function.""" @@ -30,4 +29,4 @@ def f(a, b, c, d): eng.flush() -print("Shift is {}".format(8 * int(x4) + 4 * int(x3) + 2 * int(x2) + int(x1))) +print(f"Shift is {8 * int(x4) + 4 * int(x3) + 2 * int(x2) + int(x1)}") diff --git a/examples/hws6.py b/examples/hws6.py index bf5540d07..4aa9a30e4 100644 --- a/examples/hws6.py +++ b/examples/hws6.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of a 6-qubit phase function.""" @@ -44,4 +43,4 @@ def f(a, b, c, d, e, f): All(Measure) | qubits # measurement result -print("Shift is {}".format(sum(int(q) << i for i, q in enumerate(qubits)))) +print(f"Shift is {sum(int(q) << i for i, q in enumerate(qubits))}") diff --git a/examples/ibm.py b/examples/ibm.py index 24bd0c097..ce19e5491 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of running a quantum circuit using the IBM QE APIs.""" @@ -40,7 +39,7 @@ def run_entangle(eng, num_qubits=3): # 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])) + # print(f"Measured {state} with p = {results[state]}.") # or plot them directly: histogram(eng.backend, qureg) plt.show() diff --git a/examples/ibmq_tutorial.ipynb b/examples/ibmq_tutorial.ipynb index 358bdcf9e..9721b71fb 100644 --- a/examples/ibmq_tutorial.ipynb +++ b/examples/ibmq_tutorial.ipynb @@ -133,7 +133,7 @@ " # access the probabilities via the back-end:\n", " # results = eng.backend.get_probabilities(qureg)\n", " # for state in results:\n", - " # print(\"Measured {} with p = {}.\".format(state, results[state]))\n", + " # print(f\"Measured {state} with p = {results[state]}.\")\n", " # or plot them directly:\n", " histogram(eng.backend, qureg)\n", " plt.show()\n", diff --git a/examples/ionq.py b/examples/ionq.py index 71f0c6831..472f45281 100644 --- a/examples/ionq.py +++ b/examples/ionq.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,7 +52,7 @@ def run_entangle(eng, num_qubits=3): # 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])) + # print(f"Measured {state} with p = {results[state]}.") # or plot them directly: histogram(eng.backend, qureg) plt.show() diff --git a/examples/ionq_bv.py b/examples/ionq_bv.py index afaf48c78..5cbe839fa 100644 --- a/examples/ionq_bv.py +++ b/examples/ionq_bv.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +36,7 @@ def oracle(qureg, input_size, s): def run_bv_circuit(eng, input_size, s_int): """Run the quantum circuit.""" - s = ('{0:0' + str(input_size) + 'b}').format(s_int) + s = f"{s_int:0{input_size}b}" print("Secret string: ", s) print("Number of qubits: ", str(input_size + 1)) circuit = eng.allocate_qureg(input_size + 1) diff --git a/examples/ionq_half_adder.py b/examples/ionq_half_adder.py index 905fa38e9..2efbe7a45 100644 --- a/examples/ionq_half_adder.py +++ b/examples/ionq_half_adder.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/examples/mapper_tutorial.ipynb b/examples/mapper_tutorial.ipynb index 5027819b2..b5c50881d 100644 --- a/examples/mapper_tutorial.ipynb +++ b/examples/mapper_tutorial.ipynb @@ -361,9 +361,9 @@ "\n", "# Remember that allocate_qubit returns a quantum register (Qureg) of size 1,\n", "# so accessing the qubit requires qubit[0]\n", - "print(\"This logical qubit0 has the unique ID: {}\".format(qubit0[0].id))\n", - "print(\"This logical qubit1 has the unique ID: {}\".format(qubit1[0].id))\n", - "print(\"This logical qubit2 has the unique ID: {}\".format(qubit2[0].id)) \n", + "print(f\"This logical qubit0 has the unique ID: {qubit0[0].id}\")\n", + "print(f\"This logical qubit1 has the unique ID: {qubit1[0].id}\")\n", + "print(f\"This logical qubit2 has the unique ID: {qubit2[0].id}\") \n", "\n", "eng4.flush()" ] @@ -400,9 +400,9 @@ "# current_mapping is a dictionary with keys being the\n", "# logical qubit ids and the values being the physical ids on\n", "# on the linear chain\n", - "print(\"Physical location of qubit0: {}\".format(current_mapping[qubit0[0].id]))\n", - "print(\"Physical location of qubit1: {}\".format(current_mapping[qubit1[0].id]))\n", - "print(\"Physical location of qubit2: {}\".format(current_mapping[qubit2[0].id]))" + "print(f\"Physical location of qubit0: {current_mapping[qubit0[0].id]}\")\n", + "print(f\"Physical location of qubit1: {current_mapping[qubit1[0].id]}\")\n", + "print(f\"Physical location of qubit2: {current_mapping[qubit2[0].id]}\")" ] }, { @@ -435,9 +435,9 @@ "eng4.flush()\n", "# Get current mapping:\n", "current_mapping = eng4.mapper.current_mapping\n", - "print(\"\\nPhysical location of qubit0: {}\".format(current_mapping[qubit0[0].id]))\n", - "print(\"Physical location of qubit1: {}\".format(current_mapping[qubit1[0].id]))\n", - "print(\"Physical location of qubit2: {}\".format(current_mapping[qubit2[0].id]))" + "print(f\"\\nPhysical location of qubit0: {current_mapping[qubit0[0].id]}\")\n", + "print(f\"Physical location of qubit1: {current_mapping[qubit1[0].id]}\")\n", + "print(f\"Physical location of qubit2: {current_mapping[qubit2[0].id]}\")" ] }, { @@ -491,7 +491,7 @@ "Measure | qubit0\n", "eng5.flush()\n", "\n", - "print(\"qubit0 was measured in state: {}\".format(int(qubit0)))" + "print(f\"qubit0 was measured in state: {int(qubit0)}\")" ] }, { diff --git a/examples/quantum_random_numbers.py b/examples/quantum_random_numbers.py index afa09dc4f..34dc61acc 100755 --- a/examples/quantum_random_numbers.py +++ b/examples/quantum_random_numbers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of a simple quantum random number generator.""" @@ -20,4 +19,4 @@ eng.flush() # print the result: -print("Measured: {}".format(int(q1))) +print(f"Measured: {int(q1)}") diff --git a/examples/quantum_random_numbers_ibm.py b/examples/quantum_random_numbers_ibm.py index adfcb7101..2cbf35b93 100755 --- a/examples/quantum_random_numbers_ibm.py +++ b/examples/quantum_random_numbers_ibm.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of a simple quantum random number generator using IBM's API.""" @@ -22,4 +21,4 @@ eng.flush() # print the result: -print("Measured: {}".format(int(q1))) +print(f"Measured: {int(q1)}") diff --git a/examples/shor.py b/examples/shor.py index 39dce16dc..76ef9fd57 100755 --- a/examples/shor.py +++ b/examples/shor.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example implementation of Shor's algorithm.""" @@ -13,7 +12,6 @@ except ImportError: from fractions import gcd -from builtins import input import projectq.libs.math import projectq.setups.decompositions @@ -75,12 +73,12 @@ def run_shor(eng, N, a, verbose=False): X | ctrl_qubit if verbose: - print("\033[95m{}\033[0m".format(measurements[k]), end="") + print(f"\033[95m{measurements[k]}\033[0m", end="") sys.stdout.flush() All(Measure) | x # turn the measured values into a number in [0,1) - y = sum([(measurements[2 * n - 1 - i] * 1.0 / (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 @@ -130,13 +128,13 @@ def high_level_gates(eng, cmd): end="", ) N = int(input('\n\tNumber to factor: ')) - print("\n\tFactoring N = {}: \033[0m".format(N), end="") + print(f"\n\tFactoring N = {N}: \033[0m", end="") # choose a base at random: 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("\tFactor: {}\033[0m".format(gcd(a, N))) + print(f"\tFactor: {gcd(a, N)}\033[0m") else: # run the quantum subroutine r = run_shor(eng, N, a, True) @@ -150,8 +148,8 @@ def high_level_gates(eng, cmd): 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(f"\n\n\t\033[92mFactors found :-) : {f1} * {f2} = {N}\033[0m") else: - print("\n\n\t\033[91mBad luck: Found {} and {}\033[0m".format(f1, f2)) + print(f"\n\n\t\033[91mBad luck: Found {f1} and {f2}\033[0m") print(resource_counter) # print resource usage diff --git a/examples/simulator_tutorial.ipynb b/examples/simulator_tutorial.ipynb index f95ac662b..437011ad8 100644 --- a/examples/simulator_tutorial.ipynb +++ b/examples/simulator_tutorial.ipynb @@ -301,14 +301,14 @@ "\n", "# Amplitude will be 1 as Hadamard gate is not yet executed on the simulator backend\n", "# We forgot the eng.flush()!\n", - "print(\"Amplitude saved in amp_before: {}\".format(amp_before))\n", + "print(f\"Amplitude saved in amp_before: {amp_before}\")\n", "\n", "eng.flush() # Makes sure that all the gates are sent to the backend and executed\n", "\n", "amp_after = eng.backend.get_amplitude('00', qubit + qubit2)\n", "\n", "# Amplitude will be 1/sqrt(2) as Hadamard gate was executed on the simulator backend\n", - "print(\"Amplitude saved in amp_after: {}\".format(amp_after))\n", + "print(f\"Amplitude saved in amp_after: {amp_after}\")\n", "\n", "# To avoid triggering the warning of deallocating qubits which are in a superposition\n", "Measure | qubit\n", @@ -363,11 +363,11 @@ "prob00 = eng.backend.get_probability('00', qureg)\n", "prob_second_0 = eng.backend.get_probability('0', [qureg[1]])\n", "\n", - "print(\"Probability to measure 11: {}\".format(prob11))\n", - "print(\"Probability to measure 00: {}\".format(prob00))\n", - "print(\"Probability to measure 01: {}\".format(prob01))\n", - "print(\"Probability to measure 10: {}\".format(prob10))\n", - "print(\"Probability that second qubit is in state 0: {}\".format(prob_second_0))\n", + "print(f\"Probability to measure 11: {prob11}\")\n", + "print(f\"Probability to measure 00: {prob00}\")\n", + "print(f\"Probability to measure 01: {prob01}\")\n", + "print(f\"Probability to measure 10: {prob10}\")\n", + "print(f\"Probability that second qubit is in state 0: {prob_second_0}\")\n", "\n", "All(Measure) | qureg" ] @@ -406,11 +406,11 @@ "eng.flush()\n", "op0 = QubitOperator('Z0') # Z applied to qureg[0] tensor identity on qureg[1], qureg[2]\n", "expectation = eng.backend.get_expectation_value(op0, qureg)\n", - "print(\"Expectation value = = {}\".format(expectation))\n", + "print(f\"Expectation value = = {expectation}\")\n", "\n", "op_sum = QubitOperator('Z0 X1') - 0.5 * QubitOperator('X1')\n", "expectation2 = eng.backend.get_expectation_value(op_sum, qureg)\n", - "print(\"Expectation value = = {}\".format(expectation2))\n", + "print(f\"Expectation value = = {expectation2}\")\n", "\n", "All(Measure) | qureg # To avoid error message of deallocating qubits in a superposition" ] @@ -452,7 +452,7 @@ "Measure | qureg[1]\n", "eng.flush() # required such that all above gates are executed before accessing the measurement result\n", "\n", - "print(\"First qubit measured in state: {} and second qubit in state: {}\".format(int(qureg[0]), int(qureg[1])))" + "print(f\"First qubit measured in state: {int(qureg[0])} and second qubit in state: {int(qureg[1])}\")" ] }, { @@ -496,7 +496,7 @@ "prob_0 = eng.backend.get_probability('0', [qureg[1]])\n", "\n", "print(\"After forcing a measurement outcome of the first qubit to be 0, \\n\"\n", - " \"the second qubit is in state 0 with probability: {}\".format(prob_0))" + " f\"the second qubit is in state 0 with probability: {prob_0}\")" ] }, { @@ -573,18 +573,18 @@ "# also if the Python simulator is used, one should make a deepcopy:\n", "mapping, wavefunction = copy.deepcopy(eng.backend.cheat())\n", "\n", - "print(\"The full wavefunction is: {}\".format(wavefunction))\n", + "print(f\"The full wavefunction is: {wavefunction}\")\n", "# Note: qubit1 is a qureg of length 1, i.e. a list containing one qubit objects, therefore the\n", "# unique qubit id can be accessed via qubit1[0].id\n", - "print(\"qubit1 has bit-location {}\".format(mapping[qubit1[0].id]))\n", - "print(\"qubit2 has bit-location {}\".format(mapping[qubit2[0].id]))\n", + "print(f\"qubit1 has bit-location {mapping[qubit1[0].id]}\")\n", + "print(f\"qubit2 has bit-location {mapping[qubit2[0].id]}\")\n", "\n", "# Suppose we want to know the amplitude of the qubit1 in state 0 and qubit2 in state 1:\n", "state = 0 + (1 << mapping[qubit2[0].id])\n", - "print(\"Amplitude of state qubit1 in state 0 and qubit2 in state 1: {}\".format(wavefunction[state]))\n", + "print(f\"Amplitude of state qubit1 in state 0 and qubit2 in state 1: {wavefunction[state]}\")\n", "# If one only wants to access one (or a few) amplitudes, get_amplitude provides an easier interface:\n", "amplitude = eng.backend.get_amplitude('01', qubit1 + qubit2)\n", - "print(\"Accessing same amplitude but using get_amplitude instead: {}\".format(amplitude))\n", + "print(f\"Accessing same amplitude but using get_amplitude instead: {amplitude}\")\n", "\n", "All(Measure) | qubit1 + qubit2 # In order to not deallocate a qubit in a superposition state" ] diff --git a/examples/spectral_measurement.ipynb b/examples/spectral_measurement.ipynb index 0c791ecc0..825d2c7a5 100644 --- a/examples/spectral_measurement.ipynb +++ b/examples/spectral_measurement.ipynb @@ -334,7 +334,7 @@ } ], "source": [ - "print(\"We measured {} corresponding to energy {}\".format(est_phase, math.cos(2*math.pi*est_phase)))" + "print(f\"We measured {est_phase} corresponding to energy {math.cos(2*math.pi*est_phase)}\")" ] }, { diff --git a/examples/teleport.py b/examples/teleport.py index 4d2e684aa..e662aef49 100755 --- a/examples/teleport.py +++ b/examples/teleport.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of a quantum teleportation circuit.""" @@ -65,7 +64,7 @@ def run_teleport(eng, state_creation_function, verbose=False): Measure | b1 msg_to_bob = [int(psi), int(b1)] if verbose: - print("Alice is sending the message {} to Bob.".format(msg_to_bob)) + print(f"Alice is sending the message {msg_to_bob} to Bob.") # Bob may have to apply up to two operation depending on the message sent # by Alice: diff --git a/examples/teleport_circuit.py b/examples/teleport_circuit.py index 1dfd3a485..f626f595e 100755 --- a/examples/teleport_circuit.py +++ b/examples/teleport_circuit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example if drawing of a quantum teleportation circuit.""" diff --git a/examples/unitary_simulator.py b/examples/unitary_simulator.py index d2fd4ecdf..d91b0ede9 100644 --- a/examples/unitary_simulator.py +++ b/examples/unitary_simulator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/__init__.py b/projectq/__init__.py index d09243a4a..2da4bc1f8 100755 --- a/projectq/__init__.py +++ b/projectq/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/__init__.py b/projectq/backends/__init__.py index 8a8a50646..0b72a8b71 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/_aqt/__init__.py b/projectq/backends/_aqt/__init__.py index 7c5dcb45e..d971c1b35 100644 --- a/projectq/backends/_aqt/__init__.py +++ b/projectq/backends/_aqt/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 709a97bb8..48942b475 100644 --- a/projectq/backends/_aqt/_aqt.py +++ b/projectq/backends/_aqt/_aqt.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -167,7 +166,7 @@ def _store(self, cmd): return if gate == Barrier: return - raise InvalidCommandError('Invalid command: ' + str(cmd)) + raise InvalidCommandError(f"Invalid command: {str(cmd)}") def _logical_to_physical(self, qb_id): """ @@ -182,17 +181,15 @@ 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 {}. Please make sure " - "eng.flush() was called and that the qubit " - "was eliminated during optimization.".format(qb_id) + f"Unknown qubit id {qb_id}. " + "Please make sure eng.flush() was called and that the qubit was eliminated during optimization." ) return mapping[qb_id] 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) + f"Unknown qubit id {qb_id}. Please make sure eng.flush() was called and that the qubit was " + "eliminated during optimization." ) from err return qb_id @@ -288,7 +285,7 @@ def _run(self): star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: - print(str(state) + " with p = " + str(probability) + star) + print(f"{str(state)} with p = {probability}{star}") # register measurement result from AQT for qubit_id in self._measured_ids: diff --git a/projectq/backends/_aqt/_aqt_http_client.py b/projectq/backends/_aqt/_aqt_http_client.py index 7f869d254..ac1a5094c 100644 --- a/projectq/backends/_aqt/_aqt_http_client.py +++ b/projectq/backends/_aqt/_aqt_http_client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -126,12 +125,12 @@ def get_result( # pylint: disable=too-many-arguments ): """Get the result of an execution.""" if verbose: - print("Waiting for results. [Job ID: {}]".format(execution_id)) + print(f"Waiting for results. [Job ID: {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)) + raise Exception(f"Interrupted. The ID of your submitted job is {execution_id}.") try: signal.signal(signal.SIGINT, _handle_sigint_during_get_result) @@ -145,7 +144,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover 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(f"Error while running the code: {r_json['status']}.") time.sleep(interval) if self.is_online(device) and retries % 60 == 0: self.update_devices_list() @@ -154,14 +153,14 @@ 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) + f"Device went offline. The ID of your submitted job is {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)) + raise RequestTimeoutError(f"Timeout. The ID of your submitted job is {execution_id}.") def show_devices(verbose=False): @@ -226,7 +225,7 @@ def send( if verbose: print("- Authenticating...") if token is not None: - print('user API token: ' + token) + print(f"user API token: {token}") aqt_session.authenticate(token) # check if the device is online @@ -241,13 +240,12 @@ def send( runnable, qmax, qneeded = aqt_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) + f"The device is too small ({qmax} qubits available) for the code requested({qneeded} qubits needed).", + "Try to look for another device with more qubits", ) raise DeviceTooSmall("Device is too small.") if verbose: - print("- Running code: {}".format(info)) + print(f"- Running code: {info}") execution_id = aqt_session.run(info, device) if verbose: print("- Waiting for results...") diff --git a/projectq/backends/_aqt/_aqt_http_client_test.py b/projectq/backends/_aqt/_aqt_http_client_test.py index 64e50fee2..1159bc02b 100644 --- a/projectq/backends/_aqt/_aqt_http_client_test.py +++ b/projectq/backends/_aqt/_aqt_http_client_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_aqt/_aqt_test.py b/projectq/backends/_aqt/_aqt_test.py index 397f6ffd9..d35bcaf6a 100644 --- a/projectq/backends/_aqt/_aqt_test.py +++ b/projectq/backends/_aqt/_aqt_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_awsbraket/__init__.py b/projectq/backends/_awsbraket/__init__.py index 2d01597e0..7e2ed0f91 100644 --- a/projectq/backends/_awsbraket/__init__.py +++ b/projectq/backends/_awsbraket/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_awsbraket/_awsbraket.py b/projectq/backends/_awsbraket/_awsbraket.py index de7c82ecc..56f1a4be6 100755 --- a/projectq/backends/_awsbraket/_awsbraket.py +++ b/projectq/backends/_awsbraket/_awsbraket.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -299,13 +298,13 @@ def _store(self, cmd): # pylint: disable=too-many-branches json_cmd['angle'] = gate.angle if isinstance(gate, DaggeredGate): - json_cmd['type'] = 'c' * num_controls + self._gationary[gate_type] + 'i' + json_cmd['type'] = f"{'c' * num_controls + self._gationary[gate_type]}i" elif isinstance(gate, (XGate)) and num_controls > 0: - json_cmd['type'] = 'c' * (num_controls - 1) + 'cnot' + json_cmd['type'] = f"{'c' * (num_controls - 1)}cnot" else: json_cmd['type'] = 'c' * num_controls + self._gationary[gate_type] - self._circuit += json.dumps(json_cmd) + ", " + self._circuit += f"{json.dumps(json_cmd)}, " # TODO: Add unitary for the SV1 simulator as MatrixGate @@ -320,10 +319,8 @@ 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) + f"Unknown qubit id {qb_id} in current mapping. " + "Please make sure eng.flush() was called and that the qubit was eliminated during optimization." ) return mapping[qb_id] return qb_id @@ -367,7 +364,7 @@ def get_probabilities(self, qureg): mapped_state = ['0'] * len(qureg) 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)) + raise IndexError(f'Physical ID {qubit.id} > length of internal probabilities array') mapped_state[i] = state[self._logical_to_physical(qubit.id)] mapped_state = "".join(mapped_state) if mapped_state not in probability_dict: @@ -437,7 +434,7 @@ def _run(self): star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: - print(state + " with p = " + str(probability) + star) + print(f"{state} with p = {probability}{star}") # register measurement result for qubit_id in self._measured_ids: diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py index 39ad3b265..bc7d531a4 100755 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -218,12 +217,12 @@ def run(self, info, device): 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)) + print(f"Waiting for results. [Job Arn: {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)) + raise Exception(f"Interrupted. The Arn of your submitted job is {execution_id}.") def _calculate_measurement_probs(measurements): """ @@ -269,7 +268,7 @@ def _calculate_measurement_probs(measurements): status = quantum_task['status'] bucket = quantum_task['outputS3Bucket'] directory = quantum_task['outputS3Directory'] - resultsojectname = directory + '/results.json' + resultsojectname = f"{directory}/results.json" if status == 'COMPLETED': # Get the device type to obtian the correct measurement # structure @@ -282,7 +281,7 @@ def _calculate_measurement_probs(measurements): ) s3result = client_s3.get_object(Bucket=bucket, Key=resultsojectname) if verbose: - print("Results obtained. [Status: {}]".format(status)) + print(f"Results obtained. [Status: {status}]") result_content = json.loads(s3result['Body'].read()) if devicetype_used == 'QPU': @@ -291,11 +290,11 @@ def _calculate_measurement_probs(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']) + f'Error while running the code: {status}. ' + f'The failure reason was: {quantum_task["failureReason"]}.' ) if status == 'CANCELLING': - raise Exception("The job received a CANCEL operation: {}.".format(status)) + raise Exception(f"The job received a CANCEL operation: {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 @@ -311,9 +310,7 @@ def _calculate_measurement_probs(measurements): signal.signal(signal.SIGINT, original_sigint_handler) raise RequestTimeoutError( - "Timeout. " - "The Arn of your submitted job is {} and the status " - "of the job is {}.".format(execution_id, status) + f"Timeout. The Arn of your submitted job is {execution_id} and the status of the job is {status}." ) @@ -352,7 +349,7 @@ def retrieve(credentials, task_arn, 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(f"AWS credentials: {credentials['AWS_ACCESS_KEY_ID']}, {credentials['AWS_SECRET_KEY']}") awsbraket_session.authenticate(credentials=credentials) res = awsbraket_session.get_result(task_arn, num_retries=num_retries, interval=interval, verbose=verbose) return res @@ -386,7 +383,7 @@ def send( # pylint: disable=too-many-branches,too-many-arguments,too-many-local if verbose: print("- Authenticating...") if credentials is not None: - print("AWS credentials: " + credentials['AWS_ACCESS_KEY_ID'] + ", " + credentials['AWS_SECRET_KEY']) + print(f"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) @@ -403,17 +400,14 @@ def send( # pylint: disable=too-many-branches,too-many-arguments,too-many-local 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) + f"The device is too small ({qmax} qubits available) for the code", + f"requested({qneeded} qubits needed). Try to look for another device with more qubits", ) raise DeviceTooSmall("Device is too small.") if verbose: - print("- Running code: {}".format(info)) + print(f"- Running code: {info}") task_arn = awsbraket_session.run(info, device) - print("Your task Arn is: {}. Make note of that for future reference".format(task_arn)) + print(f"Your task Arn is: {task_arn}. Make note of that for future reference") if verbose: print("- Waiting for results...") diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py index 795d9e308..93cc994de 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -262,7 +261,7 @@ def test_send_that_errors_are_caught(mocker, var_error, send_that_error_setup): 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}}, + {"Error": {"Code": var_error, "Message": f"Msg error for {var_error}"}}, "create_quantum_task", ) mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) @@ -289,7 +288,7 @@ def test_retrieve_error_arn_not_exist(mocker, var_error, arntask, creds): 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}}, + {"Error": {"Code": var_error, "Message": f"Msg error for {var_error}"}}, "get_quantum_task", ) mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py index 0fa5cb2b9..ba38906b3 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_awsbraket/_awsbraket_test.py b/projectq/backends/_awsbraket/_awsbraket_test.py index 22205a2f1..1a5288c88 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -319,6 +318,7 @@ def test_awsbraket_backend_is_available_control_singlequbit_sv1(ctrl_singlequbit assert aws_backend.is_available(cmd) == is_available_sv1 +@has_boto3 def test_awsbraket_backend_is_available_negative_control(): backend = _awsbraket.AWSBraketBackend() @@ -426,7 +426,7 @@ def test_awsbraket_sent_error(mocker, sent_error_setup): 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}}, + {"Error": {"Code": var_error, "Message": f"Msg error for {var_error}"}}, "create_quantum_task", ) mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) diff --git a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py index 71968697d..7ee1dd7b9 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py +++ b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_circuits/__init__.py b/projectq/backends/_circuits/__init__.py index 638ddfbbc..eaf0abb56 100755 --- a/projectq/backends/_circuits/__init__.py +++ b/projectq/backends/_circuits/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/_circuits/_drawer.py b/projectq/backends/_circuits/_drawer.py index 7d67f6190..071ad2cea 100755 --- a/projectq/backends/_circuits/_drawer.py +++ b/projectq/backends/_circuits/_drawer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +14,6 @@ """Contain a compiler engine which generates TikZ Latex code describing the circuit.""" -from builtins import input from projectq.cengines import BasicEngine, LastEngineException from projectq.meta import get_control_count @@ -64,65 +62,66 @@ class CircuitDrawer(BasicEngine): .. code-block:: python circuit_backend = CircuitDrawer() - circuit_backend.set_qubit_locations({0: 1, 1: 0}) # swap lines 0 and 1 + circuit_backend.set_qubit_locations({0: 1, 1: 0}) # swap lines 0 and 1 eng = MainEngine(circuit_backend) - ... # run quantum algorithm on this main engine + ... # run quantum algorithm on this main engine - print(circuit_backend.get_latex()) # prints LaTeX code + print(circuit_backend.get_latex()) # prints LaTeX code To see the qubit IDs in the generated circuit, simply set the `draw_id` option in the settings.json file under "gates":"AllocateQubitGate" to True: .. code-block:: python - "gates": { - "AllocateQubitGate": { - "draw_id": True, - "height": 0.15, - "width": 0.2, - "pre_offset": 0.1, - "offset": 0.1 - }, - ... + { + "gates": { + "AllocateQubitGate": { + "draw_id": True, + "height": 0.15, + "width": 0.2, + "pre_offset": 0.1, + "offset": 0.1, + }, + # ... + } + } The settings.json file has the following structure: .. code-block:: python { - "control": { # settings for control "circle" - "shadow": false, - "size": 0.1 - }, - "gate_shadow": true, # enable/disable shadows for all gates + "control": {"shadow": false, "size": 0.1}, # settings for control "circle" + "gate_shadow": true, # enable/disable shadows for all gates "gates": { - "GateClassString": { - GATE_PROPERTIES - } - "GateClassString2": { - ... + "GateClassString": {GATE_PROPERTIES}, + "GateClassString2": { + # ... + }, + }, + "lines": { # settings for qubit lines + "double_classical": true, # draw double-lines for + # classical bits + "double_lines_sep": 0.04, # gap between the two lines + # for double lines + "init_quantum": true, # start out with quantum bits + "style": "very thin", # line style }, - "lines": { # settings for qubit lines - "double_classical": true, # draw double-lines for - # classical bits - "double_lines_sep": 0.04, # gap between the two lines - # for double lines - "init_quantum": true, # start out with quantum bits - "style": "very thin" # line style - } } All gates (except for the ones requiring special treatment) support the following properties: .. code-block:: python - "GateClassString": { - "height": GATE_HEIGHT, - "width": GATE_WIDTH - "pre_offset": OFFSET_BEFORE_PLACEMENT, - "offset": OFFSET_AFTER_PLACEMENT, - }, + { + "GateClassString": { + "height": GATE_HEIGHT, + "width": GATE_WIDTH, + "pre_offset": OFFSET_BEFORE_PLACEMENT, + "offset": OFFSET_AFTER_PLACEMENT, + } + } """ @@ -228,7 +227,7 @@ def _print_cmd(self, cmd): if self._accept_input: meas = None while meas not in ('0', '1', 1, 0): - prompt = "Input measurement result (0 or 1) for qubit " + str(qubit) + ": " + prompt = f"Input measurement result (0 or 1) for qubit {str(qubit)}: " meas = input(prompt) else: meas = self._default_measure diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py index 6098ebfde..7c6b58555 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib.py +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +16,6 @@ import itertools import re -from builtins import input from projectq.cengines import BasicEngine, LastEngineException from projectq.meta import get_control_count @@ -37,14 +35,14 @@ def _format_gate_str(cmd): params_str_list = [] for param in params: try: - params_str_list.append('{0:.2f}'.format(float(param))) + params_str_list.append(f'{float(param):.2f}') except ValueError: if len(param) < 8: params_str_list.append(param) else: - params_str_list.append(param[:5] + '...') + params_str_list.append(f"{param[:5]}...") - gate_name += '(' + ','.join(params_str_list) + ')' + gate_name += f"({','.join(params_str_list)})" return gate_name @@ -120,7 +118,7 @@ def _process(self, cmd): # pylint: disable=too-many-branches if self._accept_input: measurement = None while measurement not in ('0', '1', 1, 0): - prompt = "Input measurement result (0 or 1) for qubit {}: ".format(qubit) + prompt = f"Input measurement result (0 or 1) for qubit {qubit}: " measurement = input(prompt) else: measurement = self._default_measure diff --git a/projectq/backends/_circuits/_drawer_matplotlib_test.py b/projectq/backends/_circuits/_drawer_matplotlib_test.py index 885d835fa..9744599a4 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib_test.py +++ b/projectq/backends/_circuits/_drawer_matplotlib_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,6 +26,22 @@ from ._drawer_matplotlib import CircuitDrawerMatplotlib +class MockInputFunction: + def __init__(self, return_value=None): + self.return_value = return_value + self._orig_input_fn = __builtins__['input'] + + def _mock_input_fn(self, prompt): + print(prompt + str(self.return_value)) + return self.return_value + + def __enter__(self): + __builtins__['input'] = self._mock_input_fn + + def __exit__(self, type, value, traceback): + __builtins__['input'] = self._orig_input_fn + + def test_drawer_measurement(): drawer = CircuitDrawerMatplotlib(default_measure=0) eng = MainEngine(drawer, []) @@ -44,12 +59,9 @@ def test_drawer_measurement(): eng = MainEngine(drawer, []) qubit = eng.allocate_qubit() - old_input = _drawer.input - - _drawer.input = lambda x: '1' - Measure | qubit - assert int(qubit) == 1 - _drawer.input = old_input + with MockInputFunction(return_value='1'): + Measure | qubit + assert int(qubit) == 1 qb1 = WeakQubitRef(engine=eng, idx=1) qb2 = WeakQubitRef(engine=eng, idx=2) @@ -57,7 +69,7 @@ def test_drawer_measurement(): eng.backend._process(Command(engine=eng, gate=Measure, qubits=([qb1],), controls=[qb2])) -class MockEngine(object): +class MockEngine: def is_available(self, cmd): self.cmd = cmd self.called = True @@ -112,10 +124,10 @@ def __init__(self, *args): self.params = args def __str__(self): - param_str = '{}'.format(self.params[0]) + param_str = f'{self.params[0]}' for param in self.params[1:]: - param_str += ',{}'.format(param) - return str(self.__class__.__name__) + "(" + param_str + ")" + param_str += f',{param}' + return f"{str(self.__class__.__name__)}({param_str})" def test_drawer_draw(): diff --git a/projectq/backends/_circuits/_drawer_test.py b/projectq/backends/_circuits/_drawer_test.py index d9b94b42c..cec9488a0 100755 --- a/projectq/backends/_circuits/_drawer_test.py +++ b/projectq/backends/_circuits/_drawer_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +24,22 @@ from projectq.types import WeakQubitRef +class MockInputFunction: + def __init__(self, return_value=None): + self.return_value = return_value + self._orig_input_fn = __builtins__['input'] + + def _mock_input_fn(self, prompt): + print(prompt + str(self.return_value)) + return self.return_value + + def __enter__(self): + __builtins__['input'] = self._mock_input_fn + + def __exit__(self, type, value, traceback): + __builtins__['input'] = self._orig_input_fn + + @pytest.mark.parametrize("ordered", [False, True]) def test_drawer_getlatex(ordered): old_latex = _drawer.to_latex @@ -73,12 +88,9 @@ def test_drawer_measurement(): eng = MainEngine(drawer, []) qubit = eng.allocate_qubit() - old_input = _drawer.input - - _drawer.input = lambda x: '1' - Measure | qubit - assert int(qubit) == 1 - _drawer.input = old_input + with MockInputFunction(return_value='1'): + Measure | qubit + assert int(qubit) == 1 qb1 = WeakQubitRef(engine=eng, idx=1) qb2 = WeakQubitRef(engine=eng, idx=2) @@ -108,7 +120,7 @@ def test_drawer_qubitmapping(): drawer.set_qubit_locations({0: 1, 1: 0}) -class MockEngine(object): +class MockEngine: def is_available(self, cmd): self.cmd = cmd self.called = True diff --git a/projectq/backends/_circuits/_plot.py b/projectq/backends/_circuits/_plot.py index 7be85711d..b450309f4 100644 --- a/projectq/backends/_circuits/_plot.py +++ b/projectq/backends/_circuits/_plot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -109,11 +108,9 @@ def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): drawing_order = {qubit_id: n_qubits - qubit_id - 1 for qubit_id in list(qubit_lines)} else: if set(drawing_order) != set(qubit_lines): - raise RuntimeError('Qubit IDs in drawing_order do not match ' + 'qubit IDs in qubit_lines!') + raise RuntimeError("Qubit IDs in drawing_order do not match qubit IDs in qubit_lines!") if set(drawing_order.values()) != set(range(len(drawing_order))): - raise RuntimeError( - 'Indices of qubit wires in drawing_order must be between 0 and {}!'.format(len(drawing_order)) - ) + raise RuntimeError(f'Indices of qubit wires in drawing_order must be between 0 and {len(drawing_order)}!') plot_params = deepcopy(_DEFAULT_PLOT_PARAMS) plot_params.update(kwargs) @@ -357,8 +354,7 @@ def draw_gate( else: 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) + f"Multi-qubit gate with non-neighbouring qubits!\nGate: {gate_str} on wires {targets_order}" ) multi_qubit_gate( diff --git a/projectq/backends/_circuits/_plot_test.py b/projectq/backends/_circuits/_plot_test.py index d5f3f4f64..3e81d23d1 100644 --- a/projectq/backends/_circuits/_plot_test.py +++ b/projectq/backends/_circuits/_plot_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +28,7 @@ # ============================================================================== -class PseudoCanvas(object): +class PseudoCanvas: def __init__(self): pass @@ -40,19 +39,19 @@ def get_renderer(self): return -class PseudoFigure(object): +class PseudoFigure: def __init__(self): self.canvas = PseudoCanvas() self.dpi = 1 -class PseudoBBox(object): +class PseudoBBox: def __init__(self, width, height): self.width = width self.height = height -class PseudoText(object): +class PseudoText: def __init__(self, text): self.text = text self.figure = PseudoFigure() @@ -64,7 +63,7 @@ def remove(self): pass -class PseudoTransform(object): +class PseudoTransform: def __init__(self): pass @@ -75,7 +74,7 @@ def transform_bbox(self, bbox): return bbox -class PseudoAxes(object): +class PseudoAxes: def __init__(self): self.figure = PseudoFigure() self.transData = PseudoTransform() diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index ae3f05cd5..45499526a 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,7 +61,7 @@ def to_latex(circuit, drawing_order=None, draw_gates_in_parallel=True): Example: .. code-block:: python - settings['gates']['HGate'] = {'width': .5, 'offset': .15} + settings['gates']['HGate'] = {'width': 0.5, 'offset': 0.15} The default settings can be acquired using the get_default_settings() function, and written using write_settings(). @@ -182,27 +181,21 @@ def _header(settings): 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" + "\\tikzstyle{{operator}}=[basic,minimum size=1.5em]\n" + f"\\tikzstyle{{phase}}=[fill=black,shape=circle,minimum size={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=" + "]\n\\tikzstyle{{none}}=[inner sep=0pt,outer sep=-.5pt,minimum height=0.5cm+1pt]\n" + "\\tikzstyle{{measure}}=[operator,inner sep=0pt," + f"minimum height={settings['gates']['MeasureGate']['height']}cm," + f"minimum width={settings['gates']['MeasureGate']['width']}cm]\n" + "\\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 += f"{x_gate_radius}cm,minimum width={x_gate_radius}cm,inner sep=-1pt,{settings['lines']['style']}]\n" if settings['gate_shadow']: gate_style += ( "\\tikzset{\nshadowed/.style={preaction={transform " @@ -211,7 +204,7 @@ def _header(settings): ) 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 = f"\\tikzstyle{{edgestyle}}=[{settings['lines']['style']}]\n" return packages + init + gate_style + edge_style @@ -327,7 +320,7 @@ def to_tikz( # pylint: disable=too-many-branches,too-many-locals,too-many-state 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)]) + pos = max(self.pos[ll] for ll in range(min(all_lines), max(all_lines) + 1)) for _line in range(min(all_lines), max(all_lines) + 1): self.pos[_line] = pos + self._gate_pre_offset(gate) @@ -340,7 +333,7 @@ def to_tikz( # pylint: disable=too-many-branches,too-many-locals,too-many-state add_str = self._x_gate(lines, ctrl_lines) # and make the target qubit quantum if one of the controls is if not self.is_quantum[lines[0]]: - if sum([self.is_quantum[i] for i in ctrl_lines]) > 0: + if sum(self.is_quantum[i] for i in ctrl_lines) > 0: self.is_quantum[lines[0]] = True elif gate == Z and len(ctrl_lines) > 0: add_str = self._cz_gate(lines + ctrl_lines) @@ -360,32 +353,24 @@ def to_tikz( # pylint: disable=too-many-branches,too-many-locals,too-many-state 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[_line], - line=_line, - shift0=shift0, - shift1=shift1, - shift2=shift2, + f"\n\\node[measure,edgestyle] ({op}) at ({self.pos[_line]}" + f",-{_line}) {{}};\n\\draw[edgestyle] ([yshift=" + f"-{shift1}cm,xshift={shift2}cm]{op}.west) to " + f"[out=60,in=180] ([yshift={shift0}cm]{op}." + f"center) to [out=0, in=120] ([yshift=-{shift1}" + f"cm,xshift=-{shift2}cm]{op}.east);\n" + f"\\draw[edgestyle] ([yshift=-{shift1}cm]{op}." + f"center) to ([yshift=-{shift2}cm,xshift=-" + f"{shift1}cm]{op}.north east);" ) 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}}{}$}};" id_str = "" if self.settings['gates']['AllocateQubitGate']['draw_id']: - id_str = "^{{\\textcolor{{red}}{{{}}}}}".format(cmds[i].id) + id_str = f"^{{\\textcolor{{red}}{{{cmds[i].id}}}}}" xpos = self.pos[line] try: if self.settings['gates']['AllocateQubitGate']['allocate_at_zero']: @@ -397,18 +382,15 @@ def to_tikz( # pylint: disable=too-many-branches,too-many-locals,too-many-state xpos + self._gate_offset(gate) + self._gate_width(gate), self.pos[line], ) - add_str = add_str.format(self._op(line), xpos, line, id_str) + add_str = f"\n\\node[none] ({self._op(line)}) at ({xpos},-{line}) {{$\\Ket{{0}}{id_str}$}};" self.op_count[line] += 1 self.is_quantum[line] = self.settings['lines']['init_quantum'] elif gate == Deallocate: # draw 'end of line' op = self._op(line) - add_str = "\n\\node[none] ({}) at ({},-{}) {{}};" - 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) + add_str = f"\n\\node[none] ({op}) at ({self.pos[line]},-{line}) {{}};" + yshift = f"{str(self._gate_height(gate))}cm]" + add_str += f"\n\\draw ([yshift={yshift}{op}.center) edge [edgestyle] ([yshift=-{yshift}{op}.center);" self.op_count[line] += 1 self.pos[line] += self._gate_width(gate) + self._gate_offset(gate) else: @@ -449,46 +431,29 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): # pylint: disable=too-ma gate_str = "" for line in lines: op = self._op(line) - 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) + width = f"{0.5 * gate_width}cm" + blc = f"[xshift=-{width},yshift=-{width}]{op}.center" + trc = f"[xshift={width},yshift={width}]{op}.center" + tlc = f"[xshift=-{width},yshift={width}]{op}.center" + brc = f"[xshift={width},yshift=-{width}]{op}.center" 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=blc, - s2=trc, - s3=tlc, - s4=brc, - line=line, - pos=self.pos[line], - swap_style=swap_style, + f"\n\\node[swapstyle] ({op}) at ({self.pos[line]},-{line}) {{}};" + f"\n\\draw[{swap_style}] ({blc})--({trc});\n" + f"\\draw[{swap_style}] ({tlc})--({brc});" ) - # add a circled 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=op_mid, - line=midpoint, - pos=pos, - dagger='^{{\\dagger}}' if daggered else '', - ) + op_mid = f"line{'{}-{}'.format(*lines)}_gate{self.op_count[lines[0]]}" + dagger = '^{{\\dagger}}' if daggered else '' + gate_str += f"\n\\node[xstyle] ({op}) at ({pos},-{midpoint}){{\\scriptsize $\\frac{{1}}{{2}}{dagger}$}};" # 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 += f"\n\\draw ({self._op(lines[0])}) edge[edgestyle] ({op_mid});" + gate_str += f"\n\\draw ({op_mid}) edge[edgestyle] ({self._op(lines[1])});" if len(ctrl_lines) > 0: for ctrl in ctrl_lines: @@ -526,27 +491,18 @@ def _swap_gate(self, lines, ctrl_lines): # pylint: disable=too-many-locals gate_str = "" for line in lines: op = self._op(line) - 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) + width = f"{0.5 * gate_width}cm" + blc = f"[xshift=-{width},yshift=-{width}]{op}.center" + trc = f"[xshift={width},yshift={width}]{op}.center" + tlc = f"[xshift=-{width},yshift={width}]{op}.center" + brc = f"[xshift={width},yshift=-{width}]{op}.center" 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=blc, - s2=trc, - s3=tlc, - s4=brc, - line=line, - pos=self.pos[line], - swap_style=swap_style, + f"\n\\node[swapstyle] ({op}) at ({self.pos[line]},-{line}) {{}};" + f"\n\\draw[{swap_style}] ({blc})--({trc});\n" + f"\\draw[{swap_style}] ({tlc})--({brc});" ) gate_str += self._line(lines[0], lines[1]) @@ -584,10 +540,10 @@ def _x_gate(self, lines, ctrl_lines): 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]) + f"\n\\node[xstyle] ({op}) at ({self.pos[line]},-{line}) {{}};\n\\draw" + f"[edgestyle] ({op}.north)--({op}.south);\n\\draw" + f"[edgestyle] ({op}.west)--({op}.east);" + ) if len(ctrl_lines) > 0: for ctrl in ctrl_lines: @@ -705,8 +661,7 @@ def _phase(self, line, pos): tex_str (string): Latex string representing a control circle at the given position. """ - phase_str = "\n\\node[phase] ({}) at ({},-{}) {{}};" - return phase_str.format(self._op(line), pos, line) + return f"\n\\node[phase] ({self._op(line)}) at ({pos},-{line}) {{}};" def _op(self, line, op=None, offset=0): """ @@ -722,7 +677,7 @@ def _op(self, line, op=None, offset=0): """ if op is None: op = self.op_count[line] - return "line{}_gate{}".format(line, op + offset) + return f"line{line}_gate{op + offset}" def _line(self, point1, point2, double=False, line=None): # pylint: disable=too-many-locals,unused-argument """ @@ -755,7 +710,7 @@ def _line(self, point1, point2, double=False, line=None): # pylint: disable=too shift = "yshift={}cm" if quantum: - return "\n\\draw ({}) edge[edgestyle] ({});".format(op1, op2) + return f"\n\\draw ({op1}) edge[edgestyle] ({op2});" if point2 > point1: loc1, loc2 = loc2, loc1 @@ -799,8 +754,9 @@ def _regular_gate(self, gate, lines, ctrl_lines): # pylint: disable=too-many-lo node_str = "\n\\node[none] ({}) at ({},-{}) {{}};" 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 + node2 = ( + "\n\\node[none,minimum height={gate_height}cm,outer sep=0] ({self._op(line, offset=1)}) " + f"at ({pos + gate_width / 2.0},-{line}) {{}};" ) node3 = node_str.format(self._op(line, offset=2), pos + gate_width, line) tex_str += node1 + node2 + node3 @@ -808,15 +764,9 @@ def _regular_gate(self, gate, lines, ctrl_lines): # pylint: disable=too-many-lo 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, + f"\n\\draw[operator,edgestyle,outer sep={gate_width}cm] ([" + f"yshift={0.5 * gate_height}cm]{self._op(imin)}) rectangle ([yshift=-" + f"{0.5 * gate_height}cm]{self._op(imax, offset=2)}) node[pos=.5] {{{name}}};" ) for line in lines: diff --git a/projectq/backends/_circuits/_to_latex_test.py b/projectq/backends/_circuits/_to_latex_test.py index 0ebdc1054..3369e681a 100755 --- a/projectq/backends/_circuits/_to_latex_test.py +++ b/projectq/backends/_circuits/_to_latex_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -175,12 +174,12 @@ def test_body(): assert code.count("swapstyle") == 36 # CZ is two phases plus 2 from CNOTs + 2 from cswap + 2 from csqrtswap assert code.count("phase") == 8 - assert code.count("{{{}}}".format(str(H))) == 2 # 2 hadamard gates + assert code.count(f"{{{str(H)}}}") == 2 # 2 hadamard gates assert code.count("{$\\Ket{0}") == 3 # 3 qubits allocated # 1 cnot, 1 not gate, 3 SqrtSwap, 1 inv(SqrtSwap) assert code.count("xstyle") == 7 assert code.count("measure") == 1 # 1 measurement - assert code.count("{{{}}}".format(str(Z))) == 1 # 1 Z gate + assert code.count(f"{{{str(Z)}}}") == 1 # 1 Z gate assert code.count("{red}") == 3 @@ -378,7 +377,7 @@ def test_qubit_lines_classicalvsquantum2(): H | action code = drawer.get_latex() - assert code.count("{{{}}}".format(str(H))) == 1 # 1 Hadamard + assert code.count(f"{{{str(H)}}}") == 1 # 1 Hadamard assert code.count("{$") == 4 # four allocate gates assert code.count("node[phase]") == 3 # 3 controls @@ -397,7 +396,7 @@ def test_qubit_lines_classicalvsquantum3(): H | (action1, action2) code = drawer.get_latex() - assert code.count("{{{}}}".format(str(H))) == 1 # 1 Hadamard + assert code.count(f"{{{str(H)}}}") == 1 # 1 Hadamard assert code.count("{$") == 7 # 8 allocate gates assert code.count("node[phase]") == 3 # 1 control # (other controls are within the gate -> are not drawn) diff --git a/projectq/backends/_exceptions.py b/projectq/backends/_exceptions.py index 8626df65c..9ebd6390a 100644 --- a/projectq/backends/_exceptions.py +++ b/projectq/backends/_exceptions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_ibm/__init__.py b/projectq/backends/_ibm/__init__.py index 21c3b1789..db7bbddb4 100755 --- a/projectq/backends/_ibm/__init__.py +++ b/projectq/backends/_ibm/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 238f11bae..2a11aa5a7 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -169,15 +168,15 @@ def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements elif gate == NOT and get_control_count(cmd) == 1: 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.qasm += f"\ncx q[{ctrl_pos}], q[{qb_pos}];" self._json.append({'qubits': [ctrl_pos, qb_pos], 'name': 'cx'}) elif gate == Barrier: qb_pos = [qb.id for qr in cmd.qubits for qb in qr] self.qasm += "\nbarrier " qb_str = "" for pos in qb_pos: - qb_str += "q[{}], ".format(pos) - self.qasm += qb_str[:-2] + ";" + qb_str += f"q[{pos}], " + self.qasm += f"{qb_str[:-2]};" self._json.append({'qubits': qb_pos, 'name': 'barrier'}) elif isinstance(gate, (Rx, Ry, Rz)): qb_pos = cmd.qubits[0][0].id @@ -191,11 +190,11 @@ def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements gate_qasm = u_strs[str(gate)[0:2]].format(gate.angle) gate_name = u_name[str(gate)[0:2]] params = u_angle[str(gate)[0:2]] - self.qasm += "\n{} q[{}];".format(gate_qasm, qb_pos) + self.qasm += f"\n{gate_qasm} q[{qb_pos}];" self._json.append({'qubits': [qb_pos], 'name': gate_name, 'params': params}) elif gate == H: qb_pos = cmd.qubits[0][0].id - self.qasm += "\nu2(0,pi/2) q[{}];".format(qb_pos) + self.qasm += f"\nu2(0,pi/2) q[{qb_pos}];" self._json.append({'qubits': [qb_pos], 'name': 'u2', 'params': [0, 3.141592653589793]}) else: raise InvalidCommandError( @@ -213,9 +212,8 @@ 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 {}. Please make sure " - "eng.flush() was called and that the qubit " - "was eliminated during optimization.".format(qb_id) + f"Unknown qubit id {qb_id}. " + "Please make sure eng.flush() was called and that the qubit was eliminated during optimization." ) return mapping[qb_id] @@ -268,7 +266,7 @@ def _run(self): # pylint: disable=too-many-locals # 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[{0}] -> c[{0}];".format(qb_loc) + self.qasm += f"\nmeasure q[{qb_loc}] -> c[{qb_loc}];" self._json.append({'qubits': [qb_loc], 'name': 'measure', 'memory': [qb_loc]}) # return if no operations / measurements have been performed. if self.qasm == "": @@ -307,7 +305,7 @@ def _run(self): # pylint: disable=too-many-locals measured = "" for state in counts: probability = counts[state] * 1.0 / self._num_runs - state = "{0:b}".format(int(state, 0)) + state = f"{int(state, 0):b}" state = state.zfill(max_qubit_id) # states in ibmq are right-ordered, so need to reverse state string state = state[::-1] @@ -318,7 +316,7 @@ def _run(self): # pylint: disable=too-many-locals star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: - print(str(state) + " with p = " + str(probability) + star) + print(f"{str(state)} with p = {probability}{star}") # register measurement result from IBM for qubit_id in self._measured_ids: diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index a30233b44..400e235ee 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -219,12 +218,12 @@ def get_result( job_status_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/' + execution_id if verbose: - print("Waiting for results. [Job ID: {}]".format(execution_id)) + print(f"Waiting for results. [Job ID: {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)) + raise Exception(f"Interrupted. The ID of your submitted job is {execution_id}.") try: signal.signal(signal.SIGINT, _handle_sigint_during_get_result) @@ -271,20 +270,20 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover # 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(f"Error while running the code. Last status: {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) + f"Device went offline. The ID of your submitted job is {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(f"Timeout. The ID of your submitted job is {execution_id}.") def show_devices(token=None, verbose=False): @@ -368,15 +367,12 @@ def send( 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) + f"The device is too small ({qmax} qubits available) for the code " + f"requested({qneeded} qubits needed) Try to look for another device with more qubits" ) raise DeviceTooSmall("Device is too small.") if verbose: - print("- Running code: {}".format(info)) + print(f"- Running code: {info}") execution_id = ibmq_session.run(info, device) if verbose: print("- Waiting for results...") diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 69b067723..655a698e3 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -96,7 +95,7 @@ def raise_for_status(self): 200, ) # STEP2 - elif args[1] == "/" + execution_id + "/jobUploadUrl" and request_num[0] == 3: + elif args[1] == f"/{execution_id}/jobUploadUrl" and request_num[0] == 3: request_num[0] += 1 return MockResponse({"url": "s3_url"}, 200) # STEP5 @@ -104,7 +103,7 @@ def raise_for_status(self): args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}", ) and not result_ready[0] and request_num[0] == 5 @@ -116,7 +115,7 @@ def raise_for_status(self): args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}", ) and result_ready[0] and request_num[0] == 7 @@ -128,9 +127,7 @@ def raise_for_status(self): args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl".format( - execution_id=execution_id - ), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl", ) and request_num[0] == 8 ): @@ -170,7 +167,7 @@ def raise_for_status(self): answer1 = { 'objectStorageInfo': { 'downloadQObjectUrlEndpoint': 'url_dld_endpoint', - 'uploadQobjectUrlEndpoint': '/' + execution_id + '/jobUploadUrl', + 'uploadQobjectUrlEndpoint': f"/{execution_id}/jobUploadUrl", 'uploadUrl': 'url_upld', }, 'id': execution_id, @@ -178,7 +175,7 @@ def raise_for_status(self): 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, f"{jobs_url}/{execution_id}/jobDataUploaded") and request_num[0] == 4: request_num[0] += 1 return MockPostResponse({}, 200) @@ -187,9 +184,7 @@ def raise_for_status(self): args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded".format( - execution_id=execution_id - ), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded", ) and request_num[0] == 10 ): @@ -552,18 +547,18 @@ def raise_for_status(self): ], 200, ) - job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format(execution_id) + job_url = f"Network/ibm-q/Groups/open/Projects/main/Jobs/{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": + elif args[1] == f"/{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), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}", ): return MockResponse({"status": "RUNNING"}, 200) @@ -593,7 +588,7 @@ def raise_for_status(self): answer1 = { 'objectStorageInfo': { 'downloadQObjectUrlEndpoint': 'url_dld_endpoint', - 'uploadQobjectUrlEndpoint': '/' + execution_id + '/jobUploadUrl', + 'uploadQobjectUrlEndpoint': f"/{execution_id}/jobUploadUrl", 'uploadUrl': 'url_upld', }, 'id': execution_id, @@ -601,7 +596,7 @@ def raise_for_status(self): return MockPostResponse(answer1, 200) # STEP4 - elif args[1] == urljoin(_API_URL, jobs_url + "/" + execution_id + "/jobDataUploaded"): + elif args[1] == urljoin(_API_URL, f"{jobs_url}/{execution_id}/jobDataUploaded"): return MockPostResponse({}, 200) def mocked_requests_put(*args, **kwargs): @@ -685,8 +680,8 @@ def raise_for_status(self): ], 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") + job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/123e" + err_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/123ee" if args[1] == urljoin(_API_URL, job_url): request_num[0] += 1 return MockResponse({"status": "RUNNING", 'iteration': request_num[0]}, 200) @@ -760,7 +755,7 @@ def raise_for_status(self): args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}", ) and request_num[0] < 1 ): @@ -768,15 +763,13 @@ def raise_for_status(self): 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), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{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 - ), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl", ): return MockResponse({"url": "result_download_url"}, 200) # STEP7 @@ -806,9 +799,7 @@ def raise_for_status(self): # STEP8 elif args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded".format( - execution_id=execution_id - ), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded", ): return MockPostResponse({}, 200) diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index 8d744222f..dc9e2c2c7 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -1,4 +1,3 @@ -# -*- 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/_ionq/__init__.py b/projectq/backends/_ionq/__init__.py index dfc37dc08..9f1b6b9ea 100644 --- a/projectq/backends/_ionq/__init__.py +++ b/projectq/backends/_ionq/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py index 7645f3531..089b26ce4 100644 --- a/projectq/backends/_ionq/_ionq.py +++ b/projectq/backends/_ionq/_ionq.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -208,11 +207,11 @@ 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' # pylint: disable=protected-access + gate_name = f"{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: - raise InvalidCommandError('Invalid command: ' + str(cmd)) + raise InvalidCommandError(f"Invalid command: {str(cmd)}") # Now make sure there are no existing measurements on qubits involved # in this operation. @@ -230,7 +229,7 @@ def _store(self, cmd): if len(already_measured) > 0: err = ( 'Mid-circuit measurement is not supported. ' - 'The following qubits have already been measured: {}.'.format(list(already_measured)) + f'The following qubits have already been measured: {list(already_measured)}.' ) raise MidCircuitMeasurementError(err) @@ -331,7 +330,7 @@ def _run(self): # pylint: disable=too-many-locals verbose=self._verbose, ) if res is None: - raise RuntimeError("Failed to retrieve job with id: '{}'!".format(self._retrieve_execution)) + raise RuntimeError(f"Failed to retrieve job with id: '{self._retrieve_execution}'!") self._measured_ids = measured_ids = res['meas_qubit_ids'] # Determine random outcome from probable states. @@ -352,7 +351,7 @@ def _run(self): # pylint: disable=too-many-locals star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: # pragma: no cover - print(state + " with p = " + str(probability) + star) + print(f"{state} with p = {probability}{star}") # Register measurement results for idx, qubit_id in enumerate(measured_ids): diff --git a/projectq/backends/_ionq/_ionq_http_client.py b/projectq/backends/_ionq/_ionq_http_client.py index 4a92ecc57..b7f90cd82 100644 --- a/projectq/backends/_ionq/_ionq_http_client.py +++ b/projectq/backends/_ionq/_ionq_http_client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -114,7 +113,7 @@ def authenticate(self, token=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.headers.update({'Authorization': f'apiKey {token}'}) self.token = token def run(self, info, device): @@ -165,13 +164,7 @@ def run(self, info, device): 'code': 'UnknownError', 'error': 'An unknown error occurred!', } - raise JobSubmissionError( - "{}: {} (status={})".format( - failure['code'], - failure['error'], - status, - ) - ) + raise JobSubmissionError(f"{failure['code']}: {failure['error']} (status={status})") def get_result(self, device, execution_id, num_retries=3000, interval=1): """ @@ -201,12 +194,12 @@ def get_result(self, device, execution_id, num_retries=3000, interval=1): 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)) + print(f"Waiting for results. [Job ID: {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)) + raise Exception(f"Interrupted. The ID of your submitted job is {execution_id}.") signal.signal(signal.SIGINT, _handle_sigint_during_get_result) @@ -232,7 +225,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover # 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)) + raise Exception(f"Error while running the code: {status}.") # Sleep, then check availability before trying again. time.sleep(interval) @@ -240,13 +233,13 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover 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) + f"Device went offline. The ID of your submitted job is {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)) + raise RequestTimeoutError(f"Timeout. The ID of your submitted job is {execution_id}.") def show_devices(self): """Show the currently available device list for the IonQ provider. @@ -337,7 +330,7 @@ def send( if verbose: # pragma: no cover print("- Authenticating...") if verbose and token is not None: # pragma: no cover - print('user API token: ' + token) + print(f"user API token: {token}") ionq_session.authenticate(token) # check if the device is online @@ -353,13 +346,12 @@ def send( 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) + f'The device is too small ({qmax} qubits available) for the code requested({qneeded} qubits needed).', + 'Try to look for another device with more qubits', ) raise DeviceTooSmall("Device is too small.") if verbose: # pragma: no cover - print("- Running code: {}".format(info)) + print(f"- Running code: {info}") execution_id = ionq_session.run(info, device) if verbose: # pragma: no cover print("- Waiting for results...") @@ -382,12 +374,7 @@ def send( # Try to parse client errors if status_code == 400: err_json = err.response.json() - raise JobSubmissionError( - '{}: {}'.format( - err_json['error'], - err_json['message'], - ) - ) from err + raise JobSubmissionError(f"{err_json['error']}: {err_json['message']}") from err # Else, just print: print("- There was an error running your code:") diff --git a/projectq/backends/_ionq/_ionq_http_client_test.py b/projectq/backends/_ionq/_ionq_http_client_test.py index d92fb88f4..d1df8fb68 100644 --- a/projectq/backends/_ionq/_ionq_http_client_test.py +++ b/projectq/backends/_ionq/_ionq_http_client_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the 'License'); diff --git a/projectq/backends/_ionq/_ionq_mapper.py b/projectq/backends/_ionq/_ionq_mapper.py index 3a5eb5a57..e77b92c17 100644 --- a/projectq/backends/_ionq/_ionq_mapper.py +++ b/projectq/backends/_ionq/_ionq_mapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,10 +41,10 @@ def _process_cmd(self, cmd): 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)) + raise RuntimeError(f"Qubit with id {qubit_id} has already been allocated!") if self._qubit_idx >= self.max_qubits: - raise RuntimeError("Cannot allocate more than {} qubits!".format(self.max_qubits)) + raise RuntimeError(f"Cannot allocate more than {self.max_qubits} qubits!") new_id = self._qubit_idx self._qubit_idx += 1 diff --git a/projectq/backends/_ionq/_ionq_mapper_test.py b/projectq/backends/_ionq/_ionq_mapper_test.py index f86fb10d9..634f7d14a 100644 --- a/projectq/backends/_ionq/_ionq_mapper_test.py +++ b/projectq/backends/_ionq/_ionq_mapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_ionq/_ionq_test.py b/projectq/backends/_ionq/_ionq_test.py index b23450353..5a846b033 100644 --- a/projectq/backends/_ionq/_ionq_test.py +++ b/projectq/backends/_ionq/_ionq_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_printer.py b/projectq/backends/_printer.py index 9016b4753..50c20eb5c 100755 --- a/projectq/backends/_printer.py +++ b/projectq/backends/_printer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +15,6 @@ """Contains a compiler engine which prints commands to stdout prior to sending them on to the next engines.""" import sys -from builtins import input from projectq.cengines import BasicEngine, LastEngineException from projectq.meta import LogicalQubitIDTag, get_control_count @@ -85,7 +83,7 @@ def _print_cmd(self, cmd): if self._accept_input: meas = None while meas not in ('0', '1', 1, 0): - prompt = "Input measurement result (0 or 1) for qubit " + str(qubit) + ": " + prompt = f"Input measurement result (0 or 1) for qubit {str(qubit)}: " meas = input(prompt) else: meas = self._default_measure @@ -100,7 +98,7 @@ def _print_cmd(self, cmd): 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") + sys.stdout.write(f'\x00\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 8d81ffc1b..658fb78c7 100755 --- a/projectq/backends/_printer_test.py +++ b/projectq/backends/_printer_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +15,8 @@ Tests for projectq.backends._printer.py. """ +import io + import pytest from projectq import MainEngine @@ -47,11 +48,15 @@ def available_cmd(self, cmd): def test_command_printer_accept_input(monkeypatch): cmd_printer = _printer.CommandPrinter() eng = MainEngine(backend=cmd_printer, engine_list=[DummyEngine()]) - monkeypatch.setattr(_printer, "input", lambda x: 1) + + number_input = io.StringIO('1\n') + monkeypatch.setattr('sys.stdin', number_input) qubit = eng.allocate_qubit() Measure | qubit assert int(qubit) == 1 - monkeypatch.setattr(_printer, "input", lambda x: 0) + + number_input = io.StringIO('0\n') + monkeypatch.setattr('sys.stdin', number_input) qubit = eng.allocate_qubit() NOT | qubit Measure | qubit diff --git a/projectq/backends/_resource.py b/projectq/backends/_resource.py index e279d579f..ec741746c 100755 --- a/projectq/backends/_resource.py +++ b/projectq/backends/_resource.py @@ -1,4 +1,3 @@ -# -*- 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/_resource_test.py b/projectq/backends/_resource_test.py index 89ebeef0f..426d2916e 100755 --- a/projectq/backends/_resource_test.py +++ b/projectq/backends/_resource_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +24,7 @@ from projectq.types import WeakQubitRef -class MockEngine(object): +class MockEngine: def is_available(self, cmd): return False diff --git a/projectq/backends/_sim/__init__.py b/projectq/backends/_sim/__init__.py index 1557d03a1..3cdb3731b 100755 --- a/projectq/backends/_sim/__init__.py +++ b/projectq/backends/_sim/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 eb270e219..f873d1096 100755 --- a/projectq/backends/_sim/_classical_simulator.py +++ b/projectq/backends/_sim/_classical_simulator.py @@ -1,4 +1,3 @@ -# -*- 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_test.py b/projectq/backends/_sim/_classical_simulator_test.py index f8e9121bb..67db700ec 100755 --- a/projectq/backends/_sim/_classical_simulator_test.py +++ b/projectq/backends/_sim/_classical_simulator_test.py @@ -1,4 +1,3 @@ -# -*- 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/_pysim.py b/projectq/backends/_sim/_pysim.py index a98bad4ca..05b7477c0 100755 --- a/projectq/backends/_sim/_pysim.py +++ b/projectq/backends/_sim/_pysim.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -331,9 +330,9 @@ def emulate_time_evolution(self, terms_dict, time, ids, ctrlids): # pylint: dis ctrlids (list): A list of control qubit IDs. """ # Determine the (normalized) trace, which is nonzero only for identity terms: - trace = sum([c for (t, c) in terms_dict if len(t) == 0]) + 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]) + op_nrm = abs(time) * sum(abs(c) for (_, c) in terms_dict) # rescale the operator by s: scale = int(op_nrm + 1.0) correction = _np.exp(-1j * time * trace / float(scale)) diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index ff5c4f4fc..9752ea669 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -407,10 +406,8 @@ def _handle(self, cmd): # pylint: disable=too-many-branches,too-many-locals,too 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: " - "{}-qubit gate applied to {} qubits.".format( - str(cmd.gate), int(math.log(len(cmd.gate.matrix), 2)), len(ids) - ) + f"Simulator: Error applying {str(cmd.gate)} gate: {int(math.log(len(cmd.gate.matrix), 2))}-qubit" + f" gate applied to {len(ids)} qubits." ) self._simulator.apply_controlled_gate(matrix.tolist(), ids, [qb.id for qb in cmd.control_qubits]) @@ -418,8 +415,7 @@ def _handle(self, cmd): # pylint: disable=too-many-branches,too-many-locals,too self._simulator.run() else: raise Exception( - "This simulator only supports controlled k-qubit" - " gates with k < 6!\nPlease add an auto-replacer" + "This simulator only supports controlled k-qubit gates with k < 6!\nPlease add an auto-replacer" " engine to your list of compiler engines." ) diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 67f14eb9a..1b4b6b7bf 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -228,7 +227,7 @@ def test_simulator_functional_measurement(sim): All(Measure) | qubits - bit_value_sum = sum([int(qubit) for qubit in qubits]) + 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) @@ -644,7 +643,7 @@ def test_simulator_no_uncompute_exception(sim): assert qubit[0].id == -1 -class MockSimulatorBackend(object): +class MockSimulatorBackend: def __init__(self): self.run_cnt = 0 diff --git a/projectq/backends/_sim/_simulator_test_fixtures.py b/projectq/backends/_sim/_simulator_test_fixtures.py index 8d256fbc2..e04f9084d 100644 --- a/projectq/backends/_sim/_simulator_test_fixtures.py +++ b/projectq/backends/_sim/_simulator_test_fixtures.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_unitary.py b/projectq/backends/_unitary.py index 74fc47f17..a8941b1e3 100644 --- a/projectq/backends/_unitary.py +++ b/projectq/backends/_unitary.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -146,7 +145,7 @@ def is_available(self, cmd): try: gate_mat = cmd.gate.matrix if len(gate_mat) > 2**6: - warnings.warn("Potentially large matrix gate encountered! ({} qubits)".format(math.log2(len(gate_mat)))) + warnings.warn(f"Potentially large matrix gate encountered! ({math.log2(len(gate_mat))} qubits)") return True except AttributeError: return False diff --git a/projectq/backends/_unitary_test.py b/projectq/backends/_unitary_test.py index bd150697a..4053bd413 100644 --- a/projectq/backends/_unitary_test.py +++ b/projectq/backends/_unitary_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -258,7 +257,7 @@ def test_unitary_functional_measurement(): eng.flush() All(Measure) | qubits - bit_value_sum = sum([int(qubit) for qubit in qubits]) + 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) diff --git a/projectq/cengines/__init__.py b/projectq/cengines/__init__.py index e9ca6e132..aa36092f2 100755 --- a/projectq/cengines/__init__.py +++ b/projectq/cengines/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_basicmapper.py b/projectq/cengines/_basicmapper.py index e81a96c19..45f477d30 100644 --- a/projectq/cengines/_basicmapper.py +++ b/projectq/cengines/_basicmapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_basicmapper_test.py b/projectq/cengines/_basicmapper_test.py index 42c6e3cbf..2d319702d 100644 --- a/projectq/cengines/_basicmapper_test.py +++ b/projectq/cengines/_basicmapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_basics.py b/projectq/cengines/_basics.py index a8f393a81..8737d36d9 100755 --- a/projectq/cengines/_basics.py +++ b/projectq/cengines/_basics.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,9 +30,9 @@ def __init__(self, engine): """Initialize the exception.""" super().__init__( ( - "\nERROR: Sending to next engine failed. {} as last engine?\nIf this is legal, please override" - "'isAvailable' to adapt its behavior." - ).format(engine.__class__.__name__), + f"\nERROR: Sending to next engine failed. {engine.__class__.__name__} as last engine?" + "\nIf this is legal, please override 'isAvailable' to adapt its behavior." + ), ) diff --git a/projectq/cengines/_basics_test.py b/projectq/cengines/_basics_test.py index cb4b2e0b2..b521abf28 100755 --- a/projectq/cengines/_basics_test.py +++ b/projectq/cengines/_basics_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_cmdmodifier.py b/projectq/cengines/_cmdmodifier.py index f5deae94a..1f8769304 100755 --- a/projectq/cengines/_cmdmodifier.py +++ b/projectq/cengines/_cmdmodifier.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +41,8 @@ def __init__(self, cmd_mod_fun): def cmd_mod_fun(cmd): cmd.tags += [MyOwnTag()] + + compiler_engine = CommandModifier(cmd_mod_fun) ... """ diff --git a/projectq/cengines/_cmdmodifier_test.py b/projectq/cengines/_cmdmodifier_test.py index 5b7ed9fb6..06fd5ae82 100755 --- a/projectq/cengines/_cmdmodifier_test.py +++ b/projectq/cengines/_cmdmodifier_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index 8c121ac5d..bb7549ea6 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_ibm5qubitmapper_test.py b/projectq/cengines/_ibm5qubitmapper_test.py index d56e9e7ed..f0b3285b4 100755 --- a/projectq/cengines/_ibm5qubitmapper_test.py +++ b/projectq/cengines/_ibm5qubitmapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_linearmapper.py b/projectq/cengines/_linearmapper.py index 098d464a1..ca4b45320 100644 --- a/projectq/cengines/_linearmapper.py +++ b/projectq/cengines/_linearmapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -162,7 +161,7 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_comma 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(f"Invalid command (number of qubits): {str(cmd)}") if isinstance(cmd.gate, AllocateQubitGate): qubit_id = cmd.qubits[0][0].id diff --git a/projectq/cengines/_linearmapper_test.py b/projectq/cengines/_linearmapper_test.py index 0b414fd12..5a5657adf 100644 --- a/projectq/cengines/_linearmapper_test.py +++ b/projectq/cengines/_linearmapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_main.py b/projectq/cengines/_main.py index 6f13e25e0..dfbf5b7e4 100755 --- a/projectq/cengines/_main.py +++ b/projectq/cengines/_main.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -90,7 +89,8 @@ def __init__( # pylint: disable=too-many-statements,too-many-branches .. code-block:: python from projectq import MainEngine - eng = MainEngine() # uses default engine_list and the Simulator + + eng = MainEngine() # uses default engine_list and the Simulator Instead of the default `engine_list` one can use, e.g., one of the IBM setups which defines a custom `engine_list` useful for one of the IBM @@ -101,6 +101,7 @@ def __init__( # pylint: disable=too-many-statements,too-many-branches import projectq.setups.ibm as ibm_setup from projectq import MainEngine + eng = MainEngine(engine_list=ibm_setup.get_engine_list()) # eng uses the default Simulator backend @@ -109,14 +110,17 @@ def __init__( # pylint: disable=too-many-statements,too-many-branches Example: .. code-block:: python - from projectq.cengines import (TagRemover, AutoReplacer, - LocalOptimizer, - DecompositionRuleSet) + from projectq.cengines import ( + TagRemover, + AutoReplacer, + LocalOptimizer, + DecompositionRuleSet, + ) from projectq.backends import Simulator from projectq import MainEngine + rule_set = DecompositionRuleSet() - engines = [AutoReplacer(rule_set), TagRemover(), - LocalOptimizer(3)] + engines = [AutoReplacer(rule_set), TagRemover(), LocalOptimizer(3)] eng = MainEngine(Simulator(), engines) """ super().__init__() @@ -250,8 +254,9 @@ def get_measurement_result(self, qubit): from projectq.ops import H, Measure from projectq import MainEngine + eng = MainEngine() - qubit = eng.allocate_qubit() # quantum register of size 1 + qubit = eng.allocate_qubit() # quantum register of size 1 H | qubit Measure | qubit eng.get_measurement_result(qubit[0]) == int(qubit) diff --git a/projectq/cengines/_main_test.py b/projectq/cengines/_main_test.py index 6600fd865..392fff73a 100755 --- a/projectq/cengines/_main_test.py +++ b/projectq/cengines/_main_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_manualmapper.py b/projectq/cengines/_manualmapper.py index 8699feb88..4e7701021 100755 --- a/projectq/cengines/_manualmapper.py +++ b/projectq/cengines/_manualmapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_manualmapper_test.py b/projectq/cengines/_manualmapper_test.py index c84301742..f627bea2f 100755 --- a/projectq/cengines/_manualmapper_test.py +++ b/projectq/cengines/_manualmapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 957ec63f8..8d6b83678 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index 104dfcef3..72aa4656e 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_replacer/__init__.py b/projectq/cengines/_replacer/__init__.py index bae476ff6..312e4b22e 100755 --- a/projectq/cengines/_replacer/__init__.py +++ b/projectq/cengines/_replacer/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_replacer/_decomposition_rule.py b/projectq/cengines/_replacer/_decomposition_rule.py index 812ca1e81..24392fe24 100755 --- a/projectq/cengines/_replacer/_decomposition_rule.py +++ b/projectq/cengines/_replacer/_decomposition_rule.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_replacer/_decomposition_rule_set.py b/projectq/cengines/_replacer/_decomposition_rule_set.py index 738ea0420..9cdb5d9da 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_set.py +++ b/projectq/cengines/_replacer/_decomposition_rule_set.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -103,8 +102,7 @@ def recogn_toffoli(cmd): .. code-block:: python - register_decomposition(X.__class__, decompose_toffoli, - recogn_toffoli) + register_decomposition(X.__class__, decompose_toffoli, recogn_toffoli) Note: See projectq.setups.decompositions for more example codes. diff --git a/projectq/cengines/_replacer/_decomposition_rule_test.py b/projectq/cengines/_replacer/_decomposition_rule_test.py index a67759dc7..1989275d6 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_test.py +++ b/projectq/cengines/_replacer/_decomposition_rule_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_replacer/_replacer.py b/projectq/cengines/_replacer/_replacer.py index a0a942196..69cedf7ac 100755 --- a/projectq/cengines/_replacer/_replacer.py +++ b/projectq/cengines/_replacer/_replacer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -113,6 +112,8 @@ def __init__( def decomposition_chooser(cmd, decomp_list): return decomp_list[0] + + repl = AutoReplacer(decomposition_chooser) """ super().__init__() @@ -186,7 +187,7 @@ def _process_command(self, cmd): # pylint: disable=too-many-locals,too-many-bra break if len(decomp_list) == 0: - raise NoGateDecompositionError("\nNo replacement found for " + str(cmd) + "!") + raise NoGateDecompositionError(f"\nNo replacement found for {str(cmd)}!") # use decomposition chooser to determine the best decomposition chosen_decomp = self._decomp_chooser(cmd, decomp_list) diff --git a/projectq/cengines/_replacer/_replacer_test.py b/projectq/cengines/_replacer/_replacer_test.py index dbf68ea2e..b6dabffa2 100755 --- a/projectq/cengines/_replacer/_replacer_test.py +++ b/projectq/cengines/_replacer/_replacer_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_swapandcnotflipper.py b/projectq/cengines/_swapandcnotflipper.py index 2d7943abb..d25ac7222 100755 --- a/projectq/cengines/_swapandcnotflipper.py +++ b/projectq/cengines/_swapandcnotflipper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -89,7 +88,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(f"The provided connectivity does not allow to execute the CNOT gate {str(cmd)}.") return not is_possible def _send_cnot(self, cmd, control, target, flip=False): @@ -139,9 +138,7 @@ 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(f"The provided connectivity does not allow to execute the Swap gate {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 dc9611a48..c00fe0805 100755 --- a/projectq/cengines/_swapandcnotflipper_test.py +++ b/projectq/cengines/_swapandcnotflipper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_tagremover.py b/projectq/cengines/_tagremover.py index 2c6497ac5..a939366ef 100755 --- a/projectq/cengines/_tagremover.py +++ b/projectq/cengines/_tagremover.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -48,7 +47,7 @@ def __init__(self, tags=None): elif isinstance(tags, list): self._tags = tags else: - raise TypeError('tags should be a list! Got: {}'.format(tags)) + raise TypeError(f'tags should be a list! Got: {tags}') def receive(self, command_list): """ diff --git a/projectq/cengines/_tagremover_test.py b/projectq/cengines/_tagremover_test.py index c9679dcd0..6d61dad8a 100755 --- a/projectq/cengines/_tagremover_test.py +++ b/projectq/cengines/_tagremover_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,7 +33,7 @@ def test_tagremover_invalid(): def test_tagremover(): backend = DummyEngine(save_commands=True) - tag_remover = _tagremover.TagRemover([type("")]) + tag_remover = _tagremover.TagRemover([str]) eng = MainEngine(backend=backend, engine_list=[tag_remover]) # Create a command_list and check if "NewTag" is removed qubit = eng.allocate_qubit() diff --git a/projectq/cengines/_testengine.py b/projectq/cengines/_testengine.py index c89b42095..2933bde82 100755 --- a/projectq/cengines/_testengine.py +++ b/projectq/cengines/_testengine.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -95,10 +94,10 @@ def __str__(self): """Return a string representation of the object.""" string = "" for qubit_id, _l in enumerate(self._l): - string += "Qubit {0} : ".format(qubit_id) + string += f"Qubit {qubit_id} : " for command in self._l[qubit_id]: - string += str(command) + ", " - string = string[:-2] + "\n" + string += f"{str(command)}, " + string = f"{string[:-2]}\n" return string diff --git a/projectq/cengines/_testengine_test.py b/projectq/cengines/_testengine_test.py index fee0866e5..ff0786ff0 100755 --- a/projectq/cengines/_testengine_test.py +++ b/projectq/cengines/_testengine_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,9 +27,8 @@ def test_compare_engine_str(): 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" + "Qubit 0 : Allocate | Qureg[0], H | Qureg[0], CX | ( Qureg[0], Qureg[1] )\n" + "Qubit 1 : Allocate | Qureg[1], CX | ( Qureg[0], Qureg[1] )\n" ) assert str(compare_engine) == expected diff --git a/projectq/cengines/_twodmapper.py b/projectq/cengines/_twodmapper.py index 5aa1dc26f..7a54a0291 100644 --- a/projectq/cengines/_twodmapper.py +++ b/projectq/cengines/_twodmapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_twodmapper_test.py b/projectq/cengines/_twodmapper_test.py index f86c4ee02..5722b21c9 100644 --- a/projectq/cengines/_twodmapper_test.py +++ b/projectq/cengines/_twodmapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/__init__.py b/projectq/libs/__init__.py index 0690f2b10..a60e7ce84 100755 --- a/projectq/libs/__init__.py +++ b/projectq/libs/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 b4e9085db..07911534a 100644 --- a/projectq/libs/hist/__init__.py +++ b/projectq/libs/hist/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/hist/_histogram.py b/projectq/libs/hist/_histogram.py index 18a674e23..d2bf255aa 100644 --- a/projectq/libs/hist/_histogram.py +++ b/projectq/libs/hist/_histogram.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,7 +46,7 @@ def histogram(backend, qureg): qubit_list.append(qb) if len(qubit_list) > 5: - print('Warning: For {0} qubits there are 2^{0} different outcomes'.format(len(qubit_list))) + print(f'Warning: For {len(qubit_list)} qubits there are 2^{len(qubit_list)} different outcomes') print("The resulting histogram may look bad and/or take too long.") print("Consider calling histogram() with a sublist of the qubits.") diff --git a/projectq/libs/hist/_histogram_test.py b/projectq/libs/hist/_histogram_test.py index 6181a0161..55d6fb470 100644 --- a/projectq/libs/hist/_histogram_test.py +++ b/projectq/libs/hist/_histogram_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/__init__.py b/projectq/libs/math/__init__.py index 06dc384c7..c2433cf34 100755 --- a/projectq/libs/math/__init__.py +++ b/projectq/libs/math/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/math/_constantmath.py b/projectq/libs/math/_constantmath.py index 1b502aabc..14c87e7b4 100755 --- a/projectq/libs/math/_constantmath.py +++ b/projectq/libs/math/_constantmath.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/_constantmath_test.py b/projectq/libs/math/_constantmath_test.py index aafcff127..d4ff4cf4d 100755 --- a/projectq/libs/math/_constantmath_test.py +++ b/projectq/libs/math/_constantmath_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/_default_rules.py b/projectq/libs/math/_default_rules.py index ca4ad4a4f..7243feae4 100755 --- a/projectq/libs/math/_default_rules.py +++ b/projectq/libs/math/_default_rules.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/_gates.py b/projectq/libs/math/_gates.py index e541a34ea..ed6d9d26c 100755 --- a/projectq/libs/math/_gates.py +++ b/projectq/libs/math/_gates.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,9 +24,9 @@ class AddConstant(BasicMathGate): Example: .. code-block:: python - 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 + 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. @@ -52,7 +51,7 @@ def get_inverse(self): def __str__(self): """Return a string representation of the object.""" - return "AddConstant({})".format(self.a) + return f"AddConstant({self.a})" def __eq__(self, other): """Equal operator.""" @@ -73,9 +72,9 @@ def SubConstant(a): # pylint: disable=invalid-name Example: .. code-block:: python - qunum = eng.allocate_qureg(5) # 5-qubit number - X | qunum[2] # qunum is now equal to 4 - SubConstant(3) | qunum # qunum is now equal to 1 + qunum = eng.allocate_qureg(5) # 5-qubit number + X | qunum[2] # qunum is now equal to 4 + SubConstant(3) | qunum # qunum is now equal to 1 """ return AddConstant(-a) @@ -89,9 +88,9 @@ class AddConstantModN(BasicMathGate): Example: .. code-block:: python - qunum = eng.allocate_qureg(5) # 5-qubit number - X | qunum[1] # qunum is now equal to 2 - AddConstantModN(3, 4) | qunum # qunum is now equal to 1 + qunum = eng.allocate_qureg(5) # 5-qubit number + X | qunum[1] # qunum is now equal to 2 + AddConstantModN(3, 4) | qunum # qunum is now equal to 1 .. note:: @@ -119,7 +118,7 @@ def __init__(self, a, N): def __str__(self): """Return a string representation of the object.""" - return "AddConstantModN({}, {})".format(self.a, self.N) + return f"AddConstantModN({self.a}, {self.N})" def get_inverse(self): """Return the inverse gate (subtraction of the same number a modulo the same number N).""" @@ -147,9 +146,9 @@ def SubConstantModN(a, N): # pylint: disable=invalid-name Example: .. code-block:: python - qunum = eng.allocate_qureg(3) # 3-qubit number - X | qunum[1] # qunum is now equal to 2 - SubConstantModN(4,5) | qunum # qunum is now -2 = 6 = 1 (mod 5) + qunum = eng.allocate_qureg(3) # 3-qubit number + X | qunum[1] # qunum is now equal to 2 + SubConstantModN(4, 5) | qunum # qunum is now -2 = 6 = 1 (mod 5) .. note:: @@ -171,9 +170,9 @@ class MultiplyByConstantModN(BasicMathGate): Example: .. code-block:: python - qunum = eng.allocate_qureg(5) # 5-qubit number - X | qunum[2] # qunum is now equal to 4 - MultiplyByConstantModN(3,5) | qunum # qunum is now 2. + qunum = eng.allocate_qureg(5) # 5-qubit number + X | qunum[2] # qunum is now equal to 4 + MultiplyByConstantModN(3, 5) | qunum # qunum is now 2. .. note:: @@ -203,7 +202,7 @@ def __init__(self, a, N): # pylint: disable=invalid-name def __str__(self): """Return a string representation of the object.""" - return "MultiplyByConstantModN({}, {})".format(self.a, self.N) + return f"MultiplyByConstantModN({self.a}, {self.N})" def __eq__(self, other): """Equal operator.""" @@ -223,12 +222,12 @@ class AddQuantumGate(BasicMathGate): Example: .. code-block:: python - qunum_a = eng.allocate_qureg(5) # 5-qubit number - qunum_b = eng.allocate_qureg(5) # 5-qubit number + 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 + 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 """ @@ -303,10 +302,10 @@ class SubtractQuantumGate(BasicMathGate): 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 + 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 @@ -353,15 +352,13 @@ class ComparatorQuantumGate(BasicMathGate): Example: .. code-block:: python - qunum_a = eng.allocate_qureg(5) # 5-qubit number - qunum_b = eng.allocate_qureg(5) # 5-qubit number + 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 + 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 - + # qunum_a and qunum_b remain 16 and 8, qunum_b is now 12 and compare bit is now 1 """ def __init__(self): @@ -416,18 +413,18 @@ class DivideQuantumGate(BasicMathGate): 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 + 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 + 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) + 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> + # |dividend>|remainder>|divisor> -> |remainder>|quotient>|divisor> """ def __init__(self): @@ -499,13 +496,13 @@ 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) - 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 + 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): diff --git a/projectq/libs/math/_gates_math_test.py b/projectq/libs/math/_gates_math_test.py index 8463fa75b..6bf28f77c 100644 --- a/projectq/libs/math/_gates_math_test.py +++ b/projectq/libs/math/_gates_math_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,7 +42,7 @@ 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 = [int(x) for x in list((f'{i:0b}').zfill(y))] qubit_list = qubit_list[::-1] prob = eng.backend.get_probability(qubit_list, qureg) if prob != 0.0: diff --git a/projectq/libs/math/_gates_test.py b/projectq/libs/math/_gates_test.py index aa78ebf8e..6eb9613e9 100755 --- a/projectq/libs/math/_gates_test.py +++ b/projectq/libs/math/_gates_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/_quantummath.py b/projectq/libs/math/_quantummath.py index 8b66fbb69..fb3dcbdc6 100644 --- a/projectq/libs/math/_quantummath.py +++ b/projectq/libs/math/_quantummath.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/_quantummath_test.py b/projectq/libs/math/_quantummath_test.py index c71ae0ef7..d32715ca6 100644 --- a/projectq/libs/math/_quantummath_test.py +++ b/projectq/libs/math/_quantummath_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +34,7 @@ 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 = [int(x) for x in list((f'{i:0b}').zfill(y))] qubit_list = qubit_list[::-1] prob = eng.backend.get_probability(qubit_list, qureg) if prob != 0.0: diff --git a/projectq/libs/revkit/__init__.py b/projectq/libs/revkit/__init__.py index a411ab55d..e527091be 100644 --- a/projectq/libs/revkit/__init__.py +++ b/projectq/libs/revkit/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 341cb5f9f..c20db35f5 100644 --- a/projectq/libs/revkit/_control_function.py +++ b/projectq/libs/revkit/_control_function.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +37,7 @@ class ControlFunctionOracle: # pylint: disable=too-few-public-methods .. code-block:: python - ControlFunctionOracle(0x8e) | ([a, b, c], d) + ControlFunctionOracle(0x8E) | ([a, b, c], d) """ def __init__(self, function, **kwargs): @@ -103,7 +102,7 @@ def __or__(self, qubits): # create truth table from function integer hex_length = max(2 ** (len(qs) - 1) // 4, 1) - revkit.tt(table="{0:#0{1}x}".format(self.function, hex_length)) + revkit.tt(table=f"{self.function:#0{hex_length}x}") # create reversible circuit from truth table self.kwargs.get("synth", revkit.esopbs)() diff --git a/projectq/libs/revkit/_control_function_test.py b/projectq/libs/revkit/_control_function_test.py index 3ea58c58a..ff4d24fe5 100644 --- a/projectq/libs/revkit/_control_function_test.py +++ b/projectq/libs/revkit/_control_function_test.py @@ -1,4 +1,3 @@ -# -*- 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/_permutation.py b/projectq/libs/revkit/_permutation.py index b5bc522b1..87f6e09f1 100644 --- a/projectq/libs/revkit/_permutation.py +++ b/projectq/libs/revkit/_permutation.py @@ -1,4 +1,3 @@ -# -*- 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/_permutation_test.py b/projectq/libs/revkit/_permutation_test.py index 06c9e0d33..19d7be0ac 100644 --- a/projectq/libs/revkit/_permutation_test.py +++ b/projectq/libs/revkit/_permutation_test.py @@ -1,4 +1,3 @@ -# -*- 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/_phase.py b/projectq/libs/revkit/_phase.py index edfc0afef..399697449 100644 --- a/projectq/libs/revkit/_phase.py +++ b/projectq/libs/revkit/_phase.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +37,7 @@ class PhaseOracle: # pylint: disable=too-few-public-methods .. code-block:: python - PhaseOracle(0x8e) | (a, b, c) + PhaseOracle(0x8E) | (a, b, c) """ def __init__(self, function, **kwargs): @@ -99,7 +98,7 @@ def __or__(self, qubits): # create truth table from function integer hex_length = max(2 ** (len(qs) - 1) // 4, 1) - revkit.tt(table="{0:#0{1}x}".format(self.function, hex_length)) + revkit.tt(table=f"{self.function:#0{hex_length}x}") # create phase circuit from truth table self.kwargs.get("synth", revkit.esopps)() diff --git a/projectq/libs/revkit/_phase_test.py b/projectq/libs/revkit/_phase_test.py index 5c06a9162..37152e027 100644 --- a/projectq/libs/revkit/_phase_test.py +++ b/projectq/libs/revkit/_phase_test.py @@ -1,4 +1,3 @@ -# -*- 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/_utils.py b/projectq/libs/revkit/_utils.py index 7fd44ca9a..e1f47c025 100644 --- a/projectq/libs/revkit/_utils.py +++ b/projectq/libs/revkit/_utils.py @@ -1,4 +1,3 @@ -# -*- 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/__init__.py b/projectq/meta/__init__.py index 634b046c7..764081800 100755 --- a/projectq/meta/__init__.py +++ b/projectq/meta/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/_compute.py b/projectq/meta/_compute.py index dcf7932ab..295c4b7a4 100755 --- a/projectq/meta/_compute.py +++ b/projectq/meta/_compute.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -259,7 +258,7 @@ class Compute: with Compute(eng): do_something(qubits) action(qubits) - Uncompute(eng) # runs inverse of the compute section + 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 @@ -279,7 +278,7 @@ class Compute: do_something_else(qubits) Uncompute(eng) # will allocate a new ancilla (with a different id) - # and then deallocate it again + # and then deallocate it again .. code-block:: python @@ -400,7 +399,7 @@ def Uncompute(engine): # pylint: disable=invalid-name with Compute(eng): do_something(qubits) action(qubits) - Uncompute(eng) # runs inverse of the compute section + Uncompute(eng) # runs inverse of the compute section """ compute_eng = engine.next_engine if not isinstance(compute_eng, ComputeEngine): diff --git a/projectq/meta/_compute_test.py b/projectq/meta/_compute_test.py index 1a87d413f..b32cd1f2f 100755 --- a/projectq/meta/_compute_test.py +++ b/projectq/meta/_compute_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +28,7 @@ def test_compute_tag(): tag0 = _compute.ComputeTag() tag1 = _compute.ComputeTag() - class MyTag(object): + class MyTag: pass assert not tag0 == MyTag() @@ -41,7 +40,7 @@ def test_uncompute_tag(): tag0 = _compute.UncomputeTag() tag1 = _compute.UncomputeTag() - class MyTag(object): + class MyTag: pass assert not tag0 == MyTag() diff --git a/projectq/meta/_control.py b/projectq/meta/_control.py index 90c97e503..543ebfe80 100755 --- a/projectq/meta/_control.py +++ b/projectq/meta/_control.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,12 +61,11 @@ def canonical_ctrl_state(ctrl_state, 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] + converted_str = f'{ctrl_state:b}'.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 - ) + f'Control state specified as {ctrl_state} ({converted_str}) is higher than maximum for {num_qubits} ' + f'qubits: {2 ** num_qubits - 1}' ) return converted_str @@ -75,12 +73,10 @@ def canonical_ctrl_state(ctrl_state, num_qubits): # 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 - ) + f'Control state {ctrl_state} has different length than the number of control qubits {num_qubits}' ) if not set(ctrl_state).issubset({'0', '1'}): - raise ValueError('Control state {} has string other than 1 and 0'.format(ctrl_state)) + raise ValueError(f'Control state {ctrl_state} has string other than 1 and 0') return ctrl_state raise TypeError('Input must be a string, an integer or an enum value of class State') diff --git a/projectq/meta/_control_test.py b/projectq/meta/_control_test.py index e30bda335..73810b95e 100755 --- a/projectq/meta/_control_test.py +++ b/projectq/meta/_control_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +35,7 @@ def test_canonical_representation(): num_qubits = 4 for i in range(2**num_qubits): - state = '{0:0b}'.format(i).zfill(num_qubits) + state = f'{i:0b}'.zfill(num_qubits) assert _control.canonical_ctrl_state(i, num_qubits) == state[::-1] assert _control.canonical_ctrl_state(state, num_qubits) == state diff --git a/projectq/meta/_dagger.py b/projectq/meta/_dagger.py index 36ead9f48..8987b1b2a 100755 --- a/projectq/meta/_dagger.py +++ b/projectq/meta/_dagger.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -81,7 +80,8 @@ class Dagger: .. code-block:: python with Dagger(eng): - [code to invert] + # [code to invert] + pass Warning: If the code to invert contains allocation of qubits, those qubits have @@ -93,7 +93,7 @@ class Dagger: with Dagger(eng): qb = eng.allocate_qubit() - H | qb # qb is still available!!! + H | qb # qb is still available!!! The **correct way** of handling qubit (de-)allocation is as follows: @@ -102,7 +102,7 @@ class Dagger: with Dagger(eng): qb = eng.allocate_qubit() ... - del qb # sends deallocate gate (which becomes an allocate) + del qb # sends deallocate gate (which becomes an allocate) """ def __init__(self, engine): diff --git a/projectq/meta/_dagger_test.py b/projectq/meta/_dagger_test.py index a95beeda1..330e0d106 100755 --- a/projectq/meta/_dagger_test.py +++ b/projectq/meta/_dagger_test.py @@ -1,4 +1,3 @@ -# -*- 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/_dirtyqubit.py b/projectq/meta/_dirtyqubit.py index 3e9ba9833..c13ed2afb 100755 --- a/projectq/meta/_dirtyqubit.py +++ b/projectq/meta/_dirtyqubit.py @@ -1,4 +1,3 @@ -# -*- 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/_dirtyqubit_test.py b/projectq/meta/_dirtyqubit_test.py index 74e4d94f3..09e942c1e 100755 --- a/projectq/meta/_dirtyqubit_test.py +++ b/projectq/meta/_dirtyqubit_test.py @@ -1,4 +1,3 @@ -# -*- 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/_exceptions.py b/projectq/meta/_exceptions.py index 2f12ff361..b9c5858b1 100644 --- a/projectq/meta/_exceptions.py +++ b/projectq/meta/_exceptions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/meta/_logicalqubit.py b/projectq/meta/_logicalqubit.py index 2c6295235..0aaf4ddd3 100644 --- a/projectq/meta/_logicalqubit.py +++ b/projectq/meta/_logicalqubit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/meta/_logicalqubit_test.py b/projectq/meta/_logicalqubit_test.py index c64a79837..43ed2943a 100644 --- a/projectq/meta/_logicalqubit_test.py +++ b/projectq/meta/_logicalqubit_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/meta/_loop.py b/projectq/meta/_loop.py index cc64034ca..47681b764 100755 --- a/projectq/meta/_loop.py +++ b/projectq/meta/_loop.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +19,7 @@ with Loop(eng, 4): H | qb - Rz(M_PI/3.) | qb + Rz(M_PI / 3.0) | qb """ from copy import deepcopy @@ -191,6 +190,7 @@ class Loop: with Loop(eng, 4): # [quantum gates to be executed 4 times] + pass Warning: If the code in the loop contains allocation of qubits, those qubits have to be deleted prior to exiting the @@ -202,7 +202,7 @@ class Loop: with Loop(eng, 4): qb = eng.allocate_qubit() - H | qb # qb is still available!!! + H | qb # qb is still available!!! The **correct way** of handling qubit (de-)allocation is as follows: @@ -210,8 +210,8 @@ class Loop: with Loop(eng, 4): qb = eng.allocate_qubit() - ... - del qb # sends deallocate gate + # ... + del qb # sends deallocate gate """ def __init__(self, engine, num): @@ -227,7 +227,7 @@ def __init__(self, engine, num): with Loop(eng, 4): H | qb - Rz(M_PI/3.) | qb + Rz(M_PI / 3.0) | qb Raises: TypeError: If number of iterations (num) is not an integer ValueError: If number of iterations (num) is not >= 0 diff --git a/projectq/meta/_loop_test.py b/projectq/meta/_loop_test.py index 618c5f16a..01ef872ce 100755 --- a/projectq/meta/_loop_test.py +++ b/projectq/meta/_loop_test.py @@ -1,4 +1,3 @@ -# -*- 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.py b/projectq/meta/_util.py index 414930f50..79f4dc43c 100755 --- a/projectq/meta/_util.py +++ b/projectq/meta/_util.py @@ -1,4 +1,3 @@ -# -*- 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 90b781b53..a60d4ac51 100755 --- a/projectq/meta/_util_test.py +++ b/projectq/meta/_util_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 67b570263..13d23ebaf 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 17485e921..c07563fca 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -79,26 +78,26 @@ def __init__(self): .. code-block:: python - ExampleGate | (a,b,c,d,e) + ExampleGate | (a, b, c, d, e) where a and b are interchangeable. Then, call this function as follows: .. code-block:: python - self.set_interchangeable_qubit_indices([[0,1]]) + self.set_interchangeable_qubit_indices([[0, 1]]) As another example, consider .. code-block:: python - ExampleGate2 | (a,b,c,d,e) + 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 .. code-block:: python - self.set_interchangeable_qubit_indices([[0,1],[2,3,4]]) + self.set_interchangeable_qubit_indices([[0, 1], [2, 3, 4]]) """ self.interchangeable_qubit_indices = [] @@ -290,7 +289,7 @@ def __eq__(self, other): def __str__(self): """Return a string representation of the object.""" - return "MatrixGate(" + str(self.matrix.tolist()) + ")" + return f"MatrixGate({str(self.matrix.tolist())})" def __hash__(self): """Compute the hash of the object.""" @@ -362,9 +361,9 @@ def to_string(self, symbols=False): angle written in radian otherwise. """ if symbols: - angle = "(" + str(round(self.angle / math.pi, 3)) + unicodedata.lookup("GREEK SMALL LETTER PI") + ")" + angle = f"({str(round(self.angle / math.pi, 3))}{unicodedata.lookup('GREEK SMALL LETTER PI')})" else: - angle = "(" + str(self.angle) + ")" + angle = f"({str(self.angle)})" return str(self.__class__.__name__) + angle def tex_str(self): @@ -377,7 +376,7 @@ def tex_str(self): [CLASSNAME]$_[ANGLE]$ """ - return str(self.__class__.__name__) + "$_{" + str(round(self.angle / math.pi, 3)) + "\\pi}$" + return f"{str(self.__class__.__name__)}$_{{{str(round(self.angle / math.pi, 3))}\\pi}}$" def get_inverse(self): """Return the inverse of this rotation gate (negate the angle, return new object).""" @@ -451,7 +450,7 @@ def __str__(self): [CLASSNAME]([ANGLE]) """ - return str(self.__class__.__name__) + "(" + str(self.angle) + ")" + return f"{str(self.__class__.__name__)}({str(self.angle)})" def tex_str(self): """ @@ -463,7 +462,7 @@ def tex_str(self): [CLASSNAME]$_[ANGLE]$ """ - return str(self.__class__.__name__) + "$_{" + str(self.angle) + "}$" + return f"{str(self.__class__.__name__)}$_{{{str(self.angle)}}}$" def get_inverse(self): """Return the inverse of this rotation gate (negate the angle, return new object).""" @@ -545,7 +544,7 @@ class BasicMathGate(BasicGate): .. code-block:: python def add(x): - return (x+a,) + 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, @@ -554,8 +553,8 @@ def add(x): .. code-block:: python - def multiply(a,b,c) - return (a,b,c+a*b) + def multiply(a, b, c): + return (a, b, c + a * b) """ def __init__(self, math_fun): @@ -569,8 +568,10 @@ def __init__(self, math_fun): Example: .. code-block:: python - def add(a,b): - return (a,a+b) + def add(a, b): + return (a, a + b) + + super().__init__(add) If the gate acts on, e.g., fixed point numbers, the number of bits per register is also required in order to @@ -587,9 +588,11 @@ def add(a,b): def get_math_function(self, qubits): n = len(qubits[0]) - scal = 2.**n + scal = 2.0**n + def math_fun(a): return (int(scal * (math.sin(math.pi * a / scal))),) + return math_fun """ diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index af3ab34b2..35b19986d 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -170,7 +169,7 @@ def test_basic_rotation_gate_init(input_angle, modulo_angle): def test_basic_rotation_gate_str(): gate = _basics.BasicRotationGate(math.pi) assert str(gate) == "BasicRotationGate(3.14159265359)" - assert gate.to_string(symbols=True) == u"BasicRotationGate(1.0π)" + assert gate.to_string(symbols=True) == "BasicRotationGate(1.0π)" assert gate.to_string(symbols=False) == "BasicRotationGate(3.14159265359)" diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index c67de0683..481efcc16 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -284,7 +283,7 @@ def add_control_qubits(self, qubits, state=CtrlAll.One): qubits, states = list(zip(*data)) if len(set(states)) != 1: raise IncompatibleControlState( - 'Control qubits {} cannot have conflicting control states: {}'.format(list(qubits), states) + f'Control qubits {list(qubits)} cannot have conflicting control states: {states}' ) @property @@ -356,6 +355,6 @@ def to_string(self, symbols=False): for qreg in qubits: qstring += str(Qureg(qreg)) qstring += ", " - qstring = qstring[:-2] + " )" + qstring = f"{qstring[:-2]} )" cstring = "C" * len(ctrlqubits) - return cstring + self.gate.to_string(symbols) + " | " + qstring + return f"{cstring + self.gate.to_string(symbols)} | {qstring}" diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index 27377a558..9fcbf544e 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -219,7 +218,7 @@ def test_commmand_add_control_qubits_two(main_engine, state): 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_state == '1' + canonical_ctrl_state(state, 2) + assert cmd.control_state == f"1{canonical_ctrl_state(state, 2)}" def test_command_all_qubits(main_engine): @@ -304,8 +303,8 @@ def test_command_to_string(main_engine): cmd.add_control_qubits(ctrl_qubit) cmd2 = _command.Command(main_engine, Rx(0.5 * math.pi), (qubit,)) - assert cmd.to_string(symbols=True) == u"CRx(0.5π) | ( Qureg[1], Qureg[0] )" - assert cmd2.to_string(symbols=True) == u"Rx(0.5π) | Qureg[0]" + assert cmd.to_string(symbols=True) == "CRx(0.5π) | ( Qureg[1], Qureg[0] )" + assert cmd2.to_string(symbols=True) == "Rx(0.5π) | Qureg[0]" if sys.version_info.major == 3: assert cmd.to_string(symbols=False) == "CRx(1.570796326795) | ( Qureg[1], Qureg[0] )" assert cmd2.to_string(symbols=False) == "Rx(1.570796326795) | Qureg[0]" diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 97acd12bc..88024b7cf 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -515,16 +514,15 @@ def __init__(self, bits_to_flip): def __str__(self): """Return a string representation of the object.""" - return "FlipBits(" + str(self.bits_to_flip) + ")" + return f"FlipBits({self.bits_to_flip})" def __or__(self, qubits): """Operator| overload which enables the syntax Gate | 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' + f"{str(self)} 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): @@ -539,4 +537,4 @@ def __eq__(self, other): def __hash__(self): """Compute the hash of the object.""" - return hash(self.__str__()) + return hash(str(self)) diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index 12b49f7a9..ab431b923 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index f67e4927f..0c1f89f3f 100644 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +21,7 @@ Example: .. code-block:: python - Tensor(H) | (qubit1, qubit2) # apply H to qubit #1 and #2 + 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) @@ -75,13 +74,13 @@ def __init__(self, gate): def __str__(self): r"""Return string representation (str(gate) + \"^\dagger\").""" - return str(self._gate) + r"^\dagger" + return f"{str(self._gate)}^\\dagger" def tex_str(self): """Return the Latex string representation of a Daggered gate.""" if hasattr(self._gate, 'tex_str'): - return self._gate.tex_str() + r"${}^\dagger$" - return str(self._gate) + r"${}^\dagger$" + return f"{self._gate.tex_str()}${{}}^\\dagger$" + return f"{str(self._gate)}${{}}^\\dagger$" def get_inverse(self): """Return the inverse gate (the inverse of the inverse of a gate is the gate itself).""" @@ -108,7 +107,7 @@ def get_inverse(gate): Example: .. code-block:: python - get_inverse(H) # returns a Hadamard gate (HGate object) + get_inverse(H) # returns a Hadamard gate (HGate object) """ try: return gate.get_inverse() @@ -128,8 +127,8 @@ def is_identity(gate): Example: .. code-block:: python - get_inverse(Rx(2*math.pi)) # returns True - get_inverse(Rx(math.pi)) # returns False + get_inverse(Rx(2 * math.pi)) # returns True + get_inverse(Rx(math.pi)) # returns False """ return gate.is_identity() @@ -147,9 +146,9 @@ class ControlledGate(BasicGate): Example: .. code-block:: python - ControlledGate(gate, 2) | (qb0, qb2, qb3) # qb0 & qb2 are controls - C(gate, 2) | (qb0, qb2, qb3) # This is much nicer. - C(gate, 2) | ([qb0,qb2], qb3) # Is equivalent + ControlledGate(gate, 2) | (qb0, qb2, qb3) # qb0 & qb2 are controls + C(gate, 2) | (qb0, qb2, qb3) # This is much nicer. + C(gate, 2) | ([qb0, qb2], qb3) # Is equivalent Note: Use :func:`C` rather than ControlledGate, i.e., @@ -235,7 +234,7 @@ def C(gate, n_qubits=1): Example: .. code-block:: python - C(NOT) | (c, q) # equivalent to CNOT | (c, q) + C(NOT) | (c, q) # equivalent to CNOT | (c, q) """ return ControlledGate(gate, n_qubits) @@ -249,8 +248,8 @@ class Tensor(BasicGate): Example: .. code-block:: python - Tensor(H) | x # applies H to every qubit in the list of qubits x - Tensor(H) | (x,) # alternative to be consistent with other syntax + Tensor(H) | x # applies H to every qubit in the list of qubits x + Tensor(H) | (x,) # alternative to be consistent with other syntax """ def __init__(self, gate): @@ -260,7 +259,7 @@ def __init__(self, gate): def __str__(self): """Return a string representation of the object.""" - return "Tensor(" + str(self._gate) + ")" + return f"Tensor({str(self._gate)})" def get_inverse(self): """Return the inverse of this tensored gate (which is the tensored inverse of the gate).""" diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index e00d4b617..cacd1fb8c 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -89,7 +88,7 @@ def test_daggered_gate_init(): def test_daggered_gate_str(): daggered_gate = _metagates.DaggeredGate(Y) - assert str(daggered_gate) == str(Y) + r"^\dagger" + assert str(daggered_gate) == f"{str(Y)}^\\dagger" def test_daggered_gate_hashable(): @@ -104,13 +103,13 @@ def test_daggered_gate_hashable(): def test_daggered_gate_tex_str(): daggered_gate = _metagates.DaggeredGate(Y) str_Y = Y.tex_str() if hasattr(Y, 'tex_str') else str(Y) - assert daggered_gate.tex_str() == str_Y + r"${}^\dagger$" + assert daggered_gate.tex_str() == f"{str_Y}${{}}^\\dagger$" # test for a gate with tex_str method rx = Rx(0.5) daggered_rx = _metagates.DaggeredGate(rx) str_rx = rx.tex_str() if hasattr(rx, 'tex_str') else str(rx) - assert daggered_rx.tex_str() == str_rx + r"${}^\dagger$" + assert daggered_rx.tex_str() == f"{str_rx}${{}}^\\dagger$" def test_daggered_gate_get_inverse(): @@ -164,7 +163,7 @@ def test_controlled_gate_init(): def test_controlled_gate_str(): one_control = _metagates.ControlledGate(Y, 2) - assert str(one_control) == "CC" + str(Y) + assert str(one_control) == f"CC{str(Y)}" def test_controlled_gate_get_inverse(): @@ -234,7 +233,7 @@ def test_tensor_init(): def test_tensor_str(): gate = _metagates.Tensor(Y) - assert str(gate) == "Tensor(" + str(Y) + ")" + assert str(gate) == f"Tensor({str(Y)})" def test_tensor_get_inverse(): diff --git a/projectq/ops/_qaagate.py b/projectq/ops/_qaagate.py index 565d61498..5e2d889c4 100755 --- a/projectq/ops/_qaagate.py +++ b/projectq/ops/_qaagate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,17 +31,19 @@ class QAA(BasicGate): Example: .. code-block:: python - def func_algorithm(eng,system_qubits): + def func_algorithm(eng, system_qubits): All(H) | system_qubits - def func_oracle(eng,system_qubits,qaa_ancilla): + + def func_oracle(eng, system_qubits, qaa_ancilla): # This oracle selects the state |010> as the one marked with Compute(eng): - All(X) | system_qubits[0::2] + All(X) | system_qubits[0::2] with Control(eng, system_qubits): - X | qaa_ancilla + X | qaa_ancilla Uncompute(eng) + system_qubits = eng.allocate_qureg(3) # Prepare the qaa_ancilla qubit in the |-> state qaa_ancilla = eng.allocate_qubit() @@ -52,7 +53,7 @@ def func_oracle(eng,system_qubits,qaa_ancilla): # Creates the initial state form the Algorithm func_algorithm(eng, system_qubits) # Apply Quantum Amplitude Amplification the correct number of times - num_it = int(math.pi/4.*math.sqrt(1 << 3)) + num_it = int(math.pi / 4.0 * math.sqrt(1 << 3)) with Loop(eng, num_it): QAA(func_algorithm, func_oracle) | (system_qubits, qaa_ancilla) @@ -78,4 +79,4 @@ def __init__(self, algorithm, oracle): def __str__(self): """Return a string representation of the object.""" - return 'QAA(Algorithm = {0}, Oracle = {1})'.format(str(self.algorithm.__name__), str(self.oracle.__name__)) + return f'QAA(Algorithm = {str(self.algorithm.__name__)}, Oracle = {str(self.oracle.__name__)})' diff --git a/projectq/ops/_qaagate_test.py b/projectq/ops/_qaagate_test.py index 707fb2fd5..e6d68688b 100755 --- a/projectq/ops/_qaagate_test.py +++ b/projectq/ops/_qaagate_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_qftgate.py b/projectq/ops/_qftgate.py index 0ea56c032..3a14de37a 100755 --- a/projectq/ops/_qftgate.py +++ b/projectq/ops/_qftgate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_qftgate_test.py b/projectq/ops/_qftgate_test.py index 8fa43058f..a790506f0 100755 --- a/projectq/ops/_qftgate_test.py +++ b/projectq/ops/_qftgate_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_qpegate.py b/projectq/ops/_qpegate.py index 96ed00a5d..3eab6e84c 100755 --- a/projectq/ops/_qpegate.py +++ b/projectq/ops/_qpegate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,4 +31,4 @@ def __init__(self, unitary): def __str__(self): """Return a string representation of the object.""" - return 'QPE({})'.format(str(self.unitary)) + return f'QPE({str(self.unitary)})' diff --git a/projectq/ops/_qpegate_test.py b/projectq/ops/_qpegate_test.py index 557b980bd..d404127ff 100755 --- a/projectq/ops/_qpegate_test.py +++ b/projectq/ops/_qpegate_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 6e2abda8c..f15a4ed40 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -82,7 +81,7 @@ class QubitOperator(BasicGate): 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.0j) | qureg # Applies X to qubit 0 and 5 with an additional global phase of 1.j Attributes: @@ -105,8 +104,7 @@ def __init__(self, term=None, coefficient=1.0): # pylint: disable=too-many-bran Example: .. code-block:: python - ham = ((QubitOperator('X0 Y3', 0.5) - + 0.6 * QubitOperator('X0 Y3'))) + ham = QubitOperator('X0 Y3', 0.5) + 0.6 * QubitOperator('X0 Y3') # Equivalently ham2 = QubitOperator('X0 Y3', 0.5) ham2 += 0.6 * QubitOperator('X0 Y3') @@ -247,9 +245,9 @@ def __or__(self, qubits): # pylint: disable=too-many-locals 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.0j) | qureg # Applies X to qubit 0 and 5 + # with an additional global + # phase of 1.j While in the above example the QubitOperator gate is applied to 6 qubits, it only acts non-trivially on the two qubits qureg[0] and @@ -259,7 +257,7 @@ def __or__(self, qubits): # pylint: disable=too-many-locals .. code-block:: python - QubitOperator('X0 X1', 1.j) | [qureg[0], qureg[5]] + QubitOperator('X0 X1', 1.0j) | [qureg[0], qureg[5]] which is only a two qubit gate. @@ -423,7 +421,7 @@ def __imul__(self, multiplier): # pylint: disable=too-many-locals,too-many-bran result_terms[tmp_key] = new_coefficient self.terms = result_terms return self - 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): """ @@ -564,19 +562,19 @@ def __str__(self): return '0' string_rep = '' for term, coeff in self.terms.items(): - tmp_string = '{}'.format(coeff) + tmp_string = f'{coeff}' if term == (): tmp_string += ' I' for operator in term: if operator[1] == 'X': - tmp_string += ' X{}'.format(operator[0]) + tmp_string += f' X{operator[0]}' elif operator[1] == 'Y': - tmp_string += ' Y{}'.format(operator[0]) + tmp_string += f' Y{operator[0]}' elif operator[1] == 'Z': - tmp_string += ' Z{}'.format(operator[0]) + tmp_string += f' Z{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) + string_rep += f'{tmp_string} +\n' return string_rep[:-3] def __repr__(self): diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index 3bbd09e2b..3b5e30508 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_shortcuts.py b/projectq/ops/_shortcuts.py index afcc8bdf4..bc80c6fcc 100755 --- a/projectq/ops/_shortcuts.py +++ b/projectq/ops/_shortcuts.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_shortcuts_test.py b/projectq/ops/_shortcuts_test.py index 345f8396c..8c55d6625 100755 --- a/projectq/ops/_shortcuts_test.py +++ b/projectq/ops/_shortcuts_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_state_prep.py b/projectq/ops/_state_prep.py index 74c26d97a..a2f3564df 100644 --- a/projectq/ops/_state_prep.py +++ b/projectq/ops/_state_prep.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -54,4 +53,4 @@ def __eq__(self, other): def __hash__(self): """Compute the hash of the object.""" - return hash("StatePreparation(" + str(self.final_state) + ")") + return hash(f"StatePreparation({str(self.final_state)})") diff --git a/projectq/ops/_state_prep_test.py b/projectq/ops/_state_prep_test.py index 198ace500..f6cac5358 100644 --- a/projectq/ops/_state_prep_test.py +++ b/projectq/ops/_state_prep_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_time_evolution.py b/projectq/ops/_time_evolution.py index aa10944a9..957a1d21d 100644 --- a/projectq/ops/_time_evolution.py +++ b/projectq/ops/_time_evolution.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -202,4 +201,4 @@ def __eq__(self, other): def __str__(self): """Return a string representation of the object.""" - return "exp({0} * ({1}))".format(-1j * self.time, self.hamiltonian) + return f"exp({-1j * self.time} * ({self.hamiltonian}))" diff --git a/projectq/ops/_time_evolution_test.py b/projectq/ops/_time_evolution_test.py index 078c96f39..af7914fa1 100644 --- a/projectq/ops/_time_evolution_test.py +++ b/projectq/ops/_time_evolution_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_uniformly_controlled_rotation.py b/projectq/ops/_uniformly_controlled_rotation.py index 393467406..46cf023f7 100644 --- a/projectq/ops/_uniformly_controlled_rotation.py +++ b/projectq/ops/_uniformly_controlled_rotation.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,9 +30,9 @@ class UniformlyControlledRy(BasicGate): Example: .. code-block:: python - controls = eng.allocate_qureg(2) - target = eng.allocate_qubit() - UniformlyControlledRy(angles=[0.1, 0.2, 0.3, 0.4]) | (controls, target) + controls = eng.allocate_qureg(2) + target = eng.allocate_qubit() + 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 @@ -69,7 +68,7 @@ def get_merged(self, other): def __str__(self): """Return a string representation of the object.""" - return "UniformlyControlledRy(" + str(self.angles) + ")" + return f"UniformlyControlledRy({str(self.angles)})" def __eq__(self, other): """Return True if same class, same rotation angles.""" @@ -93,9 +92,9 @@ class UniformlyControlledRz(BasicGate): Example: .. code-block:: python - controls = eng.allocate_qureg(2) - target = eng.allocate_qubit() - UniformlyControlledRz(angles=[0.1, 0.2, 0.3, 0.4]) | (controls, target) + controls = eng.allocate_qureg(2) + target = eng.allocate_qubit() + 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 @@ -131,7 +130,7 @@ def get_merged(self, other): def __str__(self): """Return a string representation of the object.""" - return "UniformlyControlledRz(" + str(self.angles) + ")" + return f"UniformlyControlledRz({str(self.angles)})" def __eq__(self, other): """Return True if same class, same rotation angles.""" diff --git a/projectq/ops/_uniformly_controlled_rotation_test.py b/projectq/ops/_uniformly_controlled_rotation_test.py index 362932483..49bef25ed 100644 --- a/projectq/ops/_uniformly_controlled_rotation_test.py +++ b/projectq/ops/_uniformly_controlled_rotation_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/__init__.py b/projectq/setups/__init__.py index f279b3d1d..dc799ab87 100755 --- a/projectq/setups/__init__.py +++ b/projectq/setups/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/_utils.py b/projectq/setups/_utils.py index 6f933aac6..434eb5424 100644 --- a/projectq/setups/_utils.py +++ b/projectq/setups/_utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 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 02ac24b19..5b668aaf5 100644 --- a/projectq/setups/aqt.py +++ b/projectq/setups/aqt.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/aqt_test.py b/projectq/setups/aqt_test.py index e36146e0b..415b5a4a8 100644 --- a/projectq/setups/aqt_test.py +++ b/projectq/setups/aqt_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/awsbraket.py b/projectq/setups/awsbraket.py index 0268b834f..46247ca9d 100644 --- a/projectq/setups/awsbraket.py +++ b/projectq/setups/awsbraket.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -86,4 +85,4 @@ def get_engine_list(credentials=None, device=None): other_gates=(Barrier,), ) return setup - raise DeviceNotHandledError('Unsupported device type: {}!'.format(device)) # pragma: no cover + raise DeviceNotHandledError(f'Unsupported device type: {device}!') # pragma: no cover diff --git a/projectq/setups/awsbraket_test.py b/projectq/setups/awsbraket_test.py index 6f8ad41b7..a0455258f 100644 --- a/projectq/setups/awsbraket_test.py +++ b/projectq/setups/awsbraket_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index 908b04cc9..ec5fcb277 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/_gates_test.py b/projectq/setups/decompositions/_gates_test.py index 5ea730ff0..12c05cdc8 100755 --- a/projectq/setups/decompositions/_gates_test.py +++ b/projectq/setups/decompositions/_gates_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/amplitudeamplification.py b/projectq/setups/decompositions/amplitudeamplification.py index cbf2b0af7..bd38f8086 100644 --- a/projectq/setups/decompositions/amplitudeamplification.py +++ b/projectq/setups/decompositions/amplitudeamplification.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,17 +27,19 @@ Example: .. code-block:: python - def func_algorithm(eng,system_qubits): + def func_algorithm(eng, system_qubits): All(H) | system_qubits - def func_oracle(eng,system_qubits,qaa_ancilla): + + def func_oracle(eng, system_qubits, qaa_ancilla): # This oracle selects the state |010> as the one marked with Compute(eng): - All(X) | system_qubits[0::2] + All(X) | system_qubits[0::2] with Control(eng, system_qubits): - X | qaa_ancilla + X | qaa_ancilla Uncompute(eng) + system_qubits = eng.allocate_qureg(3) # Prepare the qaa_ancilla qubit in the |-> state qaa_ancilla = eng.allocate_qubit() @@ -48,9 +49,9 @@ def func_oracle(eng,system_qubits,qaa_ancilla): # Creates the initial state form the Algorithm func_algorithm(eng, system_qubits) # Apply Quantum Amplitude Amplification the correct number of times - num_it = int(math.pi/4.*math.sqrt(1 << 3)) + num_it = int(math.pi / 4.0 * math.sqrt(1 << 3)) with Loop(eng, num_it): - QAA(func_algorithm, func_oracle) | (system_qubits, qaa_ancilla) + QAA(func_algorithm, func_oracle) | (system_qubits, qaa_ancilla) All(Measure) | system_qubits diff --git a/projectq/setups/decompositions/amplitudeamplification_test.py b/projectq/setups/decompositions/amplitudeamplification_test.py index adab7cd92..34b490de9 100644 --- a/projectq/setups/decompositions/amplitudeamplification_test.py +++ b/projectq/setups/decompositions/amplitudeamplification_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -90,10 +89,7 @@ def test_simple_grover(): 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, - ) + ), f"The obtained probability is less than expected {total_prob_after:f} vs. {theoretical_prob:f}" def complex_algorithm(eng, qreg): @@ -172,10 +168,7 @@ def test_complex_aa(): 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, - ) + ), f"The obtained probability is less than expected {total_prob_after:f} vs. {theoretical_prob:f}" def test_string_functions(): diff --git a/projectq/setups/decompositions/arb1qubit2rzandry.py b/projectq/setups/decompositions/arb1qubit2rzandry.py index 699b1fcfd..e7392da9d 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -129,7 +128,7 @@ def _find_parameters(matrix): # pylint: disable=too-many-branches,too-many-stat raise Exception( "Couldn't find parameters for matrix ", matrix, - "This shouldn't happen. Maybe the matrix is " + "not unitary?", + "This shouldn't happen. Maybe the matrix is not unitary?", ) # Case 2: cos(c/2) == 0: elif abs(matrix[0][0]) < TOLERANCE: @@ -158,7 +157,7 @@ def _find_parameters(matrix): # pylint: disable=too-many-branches,too-many-stat raise Exception( "Couldn't find parameters for matrix ", matrix, - "This shouldn't happen. Maybe the matrix is " + "not unitary?", + "This shouldn't happen. Maybe the matrix is not unitary?", ) # Case 3: sin(c/2) != 0 and cos(c/2) !=0: else: @@ -196,7 +195,7 @@ def _find_parameters(matrix): # pylint: disable=too-many-branches,too-many-stat raise Exception( "Couldn't find parameters for matrix ", matrix, - "This shouldn't happen. Maybe the matrix is " + "not unitary?", + "This shouldn't happen. Maybe the matrix is not unitary?", ) return (a, b_half, c_half, d_half) diff --git a/projectq/setups/decompositions/arb1qubit2rzandry_test.py b/projectq/setups/decompositions/arb1qubit2rzandry_test.py index 90a72057a..452e4d9c9 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry_test.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/barrier.py b/projectq/setups/decompositions/barrier.py index f3e94f408..6711e838b 100755 --- a/projectq/setups/decompositions/barrier.py +++ b/projectq/setups/decompositions/barrier.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/barrier_test.py b/projectq/setups/decompositions/barrier_test.py index c7b3ca158..a09bb2f70 100755 --- a/projectq/setups/decompositions/barrier_test.py +++ b/projectq/setups/decompositions/barrier_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # -*- codingf53: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py index b2f8b701a..6f3f35d9e 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py index 280df747b..e3464bdf3 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/cnot2cz.py b/projectq/setups/decompositions/cnot2cz.py index ea60ce2f3..950b12b68 100644 --- a/projectq/setups/decompositions/cnot2cz.py +++ b/projectq/setups/decompositions/cnot2cz.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/cnot2cz_test.py b/projectq/setups/decompositions/cnot2cz_test.py index ca9b3af2f..6738f0483 100644 --- a/projectq/setups/decompositions/cnot2cz_test.py +++ b/projectq/setups/decompositions/cnot2cz_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index 7565ef062..f60ee89ed 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/cnot2rxx_test.py b/projectq/setups/decompositions/cnot2rxx_test.py index 9cb72fc3e..e131ab928 100644 --- a/projectq/setups/decompositions/cnot2rxx_test.py +++ b/projectq/setups/decompositions/cnot2rxx_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/cnu2toffoliandcu.py b/projectq/setups/decompositions/cnu2toffoliandcu.py index cd4b79901..a463de0c2 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/cnu2toffoliandcu_test.py b/projectq/setups/decompositions/cnu2toffoliandcu_test.py index 178813beb..b64203bfd 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu_test.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/controlstate.py b/projectq/setups/decompositions/controlstate.py index f10a8add3..7aa0b5924 100755 --- a/projectq/setups/decompositions/controlstate.py +++ b/projectq/setups/decompositions/controlstate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/controlstate_test.py b/projectq/setups/decompositions/controlstate_test.py index f50a9b01a..02512e5e7 100755 --- a/projectq/setups/decompositions/controlstate_test.py +++ b/projectq/setups/decompositions/controlstate_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/crz2cxandrz.py b/projectq/setups/decompositions/crz2cxandrz.py index b36f356ba..b56603cc9 100755 --- a/projectq/setups/decompositions/crz2cxandrz.py +++ b/projectq/setups/decompositions/crz2cxandrz.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/entangle.py b/projectq/setups/decompositions/entangle.py index 30d921a9d..0f692f669 100755 --- a/projectq/setups/decompositions/entangle.py +++ b/projectq/setups/decompositions/entangle.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/globalphase.py b/projectq/setups/decompositions/globalphase.py index 69e38f081..679c08a51 100755 --- a/projectq/setups/decompositions/globalphase.py +++ b/projectq/setups/decompositions/globalphase.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py index 9a0eb4239..608629b10 100644 --- a/projectq/setups/decompositions/h2rx.py +++ b/projectq/setups/decompositions/h2rx.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/h2rx_test.py b/projectq/setups/decompositions/h2rx_test.py index 551517e42..fb3d2045b 100644 --- a/projectq/setups/decompositions/h2rx_test.py +++ b/projectq/setups/decompositions/h2rx_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/ph2r.py b/projectq/setups/decompositions/ph2r.py index c72f459ee..2fefe0dcd 100755 --- a/projectq/setups/decompositions/ph2r.py +++ b/projectq/setups/decompositions/ph2r.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/phaseestimation.py b/projectq/setups/decompositions/phaseestimation.py index efc788bdb..50d02253f 100644 --- a/projectq/setups/decompositions/phaseestimation.py +++ b/projectq/setups/decompositions/phaseestimation.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,29 +34,31 @@ n_qpe_ancillas = 3 qpe_ancillas = eng.allocate_qureg(n_qpe_ancillas) system_qubits = eng.allocate_qureg(1) - angle = cmath.pi*2.*0.125 - U = Ph(angle) # unitary_specfic_to_the_problem() + angle = cmath.pi * 2.0 * 0.125 + U = Ph(angle) # unitary_specfic_to_the_problem() # Apply Quantum Phase Estimation QPE(U) | (qpe_ancillas, system_qubits) All(Measure) | qpe_ancillas # Compute the phase from the ancilla measurement - #(https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) + # (https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) phasebinlist = [int(q) for q in qpe_ancillas] phase_in_bin = ''.join(str(j) for j in phasebinlist) - phase_int = int(phase_in_bin,2) - phase = phase_int / (2 ** n_qpe_ancillas) - print (phase) + phase_int = int(phase_in_bin, 2) + phase = phase_int / (2**n_qpe_ancillas) + print(phase) # Example using a function (two_qubit_gate). # Instead of applying QPE on a gate U one could provide a function + 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]) + n_qpe_ancillas = 3 qpe_ancillas = eng.allocate_qureg(n_qpe_ancillas) system_qubits = eng.allocate_qureg(2) @@ -68,12 +69,12 @@ def two_qubit_gate(system_q, time): All(Measure) | qpe_ancillas # Compute the phase from the ancilla measurement - #(https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) + # (https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) phasebinlist = [int(q) for q in qpe_ancillas] phase_in_bin = ''.join(str(j) for j in phasebinlist) - phase_int = int(phase_in_bin,2) - phase = phase_int / (2 ** n_qpe_ancillas) - print (phase) + phase_int = int(phase_in_bin, 2) + phase = phase_int / (2**n_qpe_ancillas) + print(phase) Attributes: unitary (BasicGate): Unitary Operation either a ProjectQ gate or a function f. diff --git a/projectq/setups/decompositions/phaseestimation_test.py b/projectq/setups/decompositions/phaseestimation_test.py index 151b8643a..08e33e587 100644 --- a/projectq/setups/decompositions/phaseestimation_test.py +++ b/projectq/setups/decompositions/phaseestimation_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,10 +57,7 @@ def test_simple_test_X_eigenvectors(): eng.flush() num_phase = (results == 0.5).sum() - assert num_phase / N >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / N, - 0.35, - ) + assert num_phase / N >= 0.35, f"Statistics phase calculation are not correct ({num_phase / N:f} vs. {0.35:f})" @flaky(max_runs=5, min_passes=2) @@ -91,10 +87,7 @@ def test_Ph_eigenvectors(): eng.flush() num_phase = (results == 0.125).sum() - assert num_phase / N >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / N, - 0.35, - ) + assert num_phase / N >= 0.35, f"Statistics phase calculation are not correct ({num_phase / N:f} vs. {0.35:f})" def two_qubit_gate(system_q, time): @@ -129,10 +122,7 @@ def test_2qubitsPh_andfunction_eigenvectors(): eng.flush() num_phase = (results == 0.125).sum() - assert num_phase / N >= 0.34, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / N, - 0.34, - ) + assert num_phase / N >= 0.34, f"Statistics phase calculation are not correct ({num_phase / N:f} vs. {0.34:f})" def test_X_no_eigenvectors(): @@ -179,10 +169,7 @@ def test_X_no_eigenvectors(): assert total == pytest.approx(N, abs=5) 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, - ) + ), f"Statistics on |+> probability are not correct ({plus_probability:f} vs. {1.0 / 4.0:f})" def test_string(): diff --git a/projectq/setups/decompositions/qft2crandhadamard.py b/projectq/setups/decompositions/qft2crandhadamard.py index 4d8f9ebb1..de7346bbf 100755 --- a/projectq/setups/decompositions/qft2crandhadamard.py +++ b/projectq/setups/decompositions/qft2crandhadamard.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/qubitop2onequbit.py b/projectq/setups/decompositions/qubitop2onequbit.py index 4109759ed..5fd68e345 100644 --- a/projectq/setups/decompositions/qubitop2onequbit.py +++ b/projectq/setups/decompositions/qubitop2onequbit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/qubitop2onequbit_test.py b/projectq/setups/decompositions/qubitop2onequbit_test.py index 44567fc9b..ed124c963 100644 --- a/projectq/setups/decompositions/qubitop2onequbit_test.py +++ b/projectq/setups/decompositions/qubitop2onequbit_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -96,7 +95,7 @@ def test_qubitop2singlequbit(): correct_eng.flush() for fstate in range(2 ** (num_qubits + 1)): - binary_state = format(fstate, '0' + str(num_qubits + 1) + 'b') + binary_state = format(fstate, f"0{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) diff --git a/projectq/setups/decompositions/r2rzandph.py b/projectq/setups/decompositions/r2rzandph.py index 918038e59..9fca9ed52 100755 --- a/projectq/setups/decompositions/r2rzandph.py +++ b/projectq/setups/decompositions/r2rzandph.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/rx2rz.py b/projectq/setups/decompositions/rx2rz.py index e7839461e..6fe1f22be 100644 --- a/projectq/setups/decompositions/rx2rz.py +++ b/projectq/setups/decompositions/rx2rz.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/rx2rz_test.py b/projectq/setups/decompositions/rx2rz_test.py index 35896f29f..767c805a0 100644 --- a/projectq/setups/decompositions/rx2rz_test.py +++ b/projectq/setups/decompositions/rx2rz_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/ry2rz.py b/projectq/setups/decompositions/ry2rz.py index 040907b3f..089d9c018 100644 --- a/projectq/setups/decompositions/ry2rz.py +++ b/projectq/setups/decompositions/ry2rz.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/ry2rz_test.py b/projectq/setups/decompositions/ry2rz_test.py index e5e0c909f..61f9e1cfd 100644 --- a/projectq/setups/decompositions/ry2rz_test.py +++ b/projectq/setups/decompositions/ry2rz_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index f61c10a11..d3116ed23 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py index 63dbc1f60..7418c5a1d 100644 --- a/projectq/setups/decompositions/rz2rx_test.py +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/sqrtswap2cnot.py b/projectq/setups/decompositions/sqrtswap2cnot.py index 8e5392a51..e2b645eaa 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot.py +++ b/projectq/setups/decompositions/sqrtswap2cnot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/sqrtswap2cnot_test.py b/projectq/setups/decompositions/sqrtswap2cnot_test.py index 0ec4706cd..2e920a70f 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot_test.py +++ b/projectq/setups/decompositions/sqrtswap2cnot_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py index 6b95bc30d..80e399acd 100644 --- a/projectq/setups/decompositions/stateprep2cnot.py +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index 72372e77b..dd1c1aced 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/swap2cnot.py b/projectq/setups/decompositions/swap2cnot.py index df17b4e4c..23ffa8c69 100755 --- a/projectq/setups/decompositions/swap2cnot.py +++ b/projectq/setups/decompositions/swap2cnot.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/time_evolution.py b/projectq/setups/decompositions/time_evolution.py index 9453f767e..9e029391e 100644 --- a/projectq/setups/decompositions/time_evolution.py +++ b/projectq/setups/decompositions/time_evolution.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/time_evolution_test.py b/projectq/setups/decompositions/time_evolution_test.py index e8eecaa1d..8d0197a56 100644 --- a/projectq/setups/decompositions/time_evolution_test.py +++ b/projectq/setups/decompositions/time_evolution_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/toffoli2cnotandtgate.py b/projectq/setups/decompositions/toffoli2cnotandtgate.py index cdaaf5bd6..b46c92ba8 100755 --- a/projectq/setups/decompositions/toffoli2cnotandtgate.py +++ b/projectq/setups/decompositions/toffoli2cnotandtgate.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/uniformlycontrolledr2cnot.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py index 62571e82e..e865e939d 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py index dbc5e75f9..08564ecd9 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -142,7 +141,7 @@ def test_uniformly_controlled_ry(n, gate_classes): correct_eng.flush() for fstate in range(2 ** (n + 1)): - binary_state = format(fstate, '0' + str(n + 1) + 'b') + binary_state = format(fstate, f"0{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) diff --git a/projectq/setups/default.py b/projectq/setups/default.py index 8ac280417..109f9c10b 100755 --- a/projectq/setups/default.py +++ b/projectq/setups/default.py @@ -1,4 +1,3 @@ -# -*- 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/grid.py b/projectq/setups/grid.py index 1261a72d4..65021b930 100644 --- a/projectq/setups/grid.py +++ b/projectq/setups/grid.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/grid_test.py b/projectq/setups/grid_test.py index 20ed6a7f2..7760d487f 100644 --- a/projectq/setups/grid_test.py +++ b/projectq/setups/grid_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index bab8ee824..122febaa6 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -1,4 +1,3 @@ -# -*- 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/ibm_test.py b/projectq/setups/ibm_test.py index d7ca80710..1cdef3425 100644 --- a/projectq/setups/ibm_test.py +++ b/projectq/setups/ibm_test.py @@ -1,4 +1,3 @@ -# -*- 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/ionq.py b/projectq/setups/ionq.py index e0c678a02..3c451048c 100644 --- a/projectq/setups/ionq.py +++ b/projectq/setups/ionq.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,7 +51,7 @@ def get_engine_list(token=None, device=None): service.authenticate(token=token) devices = service.show_devices() if not device or device not in devices: - raise DeviceOfflineError("Error checking engine list: no '{}' devices available".format(device)) + raise DeviceOfflineError(f"Error checking engine list: no '{device}' devices available") # # Qubit mapper diff --git a/projectq/setups/ionq_test.py b/projectq/setups/ionq_test.py index 6a3cde827..2b632bef7 100644 --- a/projectq/setups/ionq_test.py +++ b/projectq/setups/ionq_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,4 +52,4 @@ def mock_show_devices(*args, **kwargs): ) with pytest.raises(DeviceOfflineError): - projectq.setups.ionq.get_engine_list(device='simulator') + projectq.setups.ionq.get_engine_list(token='TOKEN', device='simulator') diff --git a/projectq/setups/linear.py b/projectq/setups/linear.py index 223ac2d26..299a64f80 100644 --- a/projectq/setups/linear.py +++ b/projectq/setups/linear.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/linear_test.py b/projectq/setups/linear_test.py index 7e838f460..a5e1ed773 100644 --- a/projectq/setups/linear_test.py +++ b/projectq/setups/linear_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index 86811b682..9e8049d4e 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/restrictedgateset_test.py b/projectq/setups/restrictedgateset_test.py index 163386902..92144bde7 100644 --- a/projectq/setups/restrictedgateset_test.py +++ b/projectq/setups/restrictedgateset_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index 0eda14f37..3841b638a 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/trapped_ion_decomposer_test.py b/projectq/setups/trapped_ion_decomposer_test.py index ba5d1518d..2638aac83 100644 --- a/projectq/setups/trapped_ion_decomposer_test.py +++ b/projectq/setups/trapped_ion_decomposer_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/tests/__init__.py b/projectq/tests/__init__.py index 16fc4afdf..ee1451dcd 100755 --- a/projectq/tests/__init__.py +++ b/projectq/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 5cb409d4c..541c1492c 100755 --- a/projectq/tests/_factoring_test.py +++ b/projectq/tests/_factoring_test.py @@ -1,4 +1,3 @@ -# -*- 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/__init__.py b/projectq/types/__init__.py index d81996907..ad912d2ba 100755 --- a/projectq/types/__init__.py +++ b/projectq/types/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 74daedcac..41021fe1c 100755 --- a/projectq/types/_qubit.py +++ b/projectq/types/_qubit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +22,7 @@ .. code-block:: python from projectq import MainEngine + eng = MainEngine() qubit = eng.allocate_qubit() @@ -189,11 +189,11 @@ 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(f'{start_id}-{start_id + count - 1}' if count > 1 else f'{start_id}') start_id = qubit_id count = 1 - return "Qureg[{}]".format(', '.join(out_list)) + return f"Qureg[{', '.join(out_list)}]" @property def engine(self): diff --git a/projectq/types/_qubit_test.py b/projectq/types/_qubit_test.py index 35fc961e6..fb5fb394b 100755 --- a/projectq/types/_qubit_test.py +++ b/projectq/types/_qubit_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -85,7 +84,7 @@ def test_basic_qubit_hash(): @pytest.fixture def mock_main_engine(): - class MockMainEngine(object): + class MockMainEngine: def __init__(self): self.num_calls = 0 self.active_qubits = set() diff --git a/setup.py b/setup.py index 7b3c66489..a1fb2056e 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Some of the setup.py code is inspired or copied from SQLAlchemy # SQLAlchemy was created by Michael Bayer. @@ -118,7 +117,7 @@ def compiler_test( """Return a boolean indicating whether a flag name is supported on the specified compiler.""" 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)) + temp.write(f'{include}\nint main (int argc, char **argv) {{ {body} return 0; }}') fname = temp.name if compile_postargs is None: @@ -147,7 +146,7 @@ def compiler_test( if compiler.compiler_type == 'msvc': err.close() os.dup2(olderr, sys.stderr.fileno()) - with open('err.txt', 'r') as err_file: + with open('err.txt') as err_file: if err_file.readlines(): raise RuntimeError('') except (CompileError, LinkError, RuntimeError): @@ -370,7 +369,7 @@ def _configure_compiler(self): # Other compiler tests status_msgs('Other compiler tests') - self.compiler.define_macro('VERSION_INFO', '"{}"'.format(self.distribution.get_version())) + self.compiler.define_macro('VERSION_INFO', f'"{self.distribution.get_version()}"') if compiler_type == 'unix' and compiler_test(self.compiler, '-fvisibility=hidden'): self.opts.append('-fvisibility=hidden') @@ -402,7 +401,7 @@ def _configure_openmp(self): # 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) + l_arg = f'-L{llvm_root}/lib' if compiler_test(self.compiler, flag, link_postargs=[l_arg, flag], **kwargs): self.opts.append(flag) self.link_opts.extend((l_arg, flag)) @@ -419,8 +418,8 @@ def _configure_openmp(self): # Only add the flag if the compiler we are using is the one # from MacPorts if macports_root in compiler_root: - inc_dir = '{}/include/libomp'.format(macports_root) - lib_dir = '{}/lib/libomp'.format(macports_root) + inc_dir = f'{macports_root}/include/libomp' + lib_dir = f'{macports_root}/lib/libomp' c_arg = '-I' + inc_dir l_arg = '-L' + lib_dir @@ -476,11 +475,11 @@ def _configure_cxx_standard(self): cxx_standards = [year for year in cxx_standards if year < 17] for year in cxx_standards: - flag = '-std=c++{}'.format(year) + flag = f'-std=c++{year}' if compiler_test(self.compiler, flag): self.opts.append(flag) return - flag = '/Qstd=c++{}'.format(year) + flag = f'/Qstd=c++{year}' if compiler_test(self.compiler, flag): self.opts.append(flag) return @@ -512,7 +511,7 @@ def _cleanup_compiler_flags(self): if compiler_test(compiler, flag, link_shared_lib=True, compile_postargs=['-fPIC']): flags.append(flag) else: - important_msgs('WARNING: ignoring unsupported compiler flag: {}'.format(flag)) + important_msgs(f'WARNING: ignoring unsupported compiler flag: {flag}') self.compiler.compiler = [compiler_exe] + list(compiler_flags) self.compiler.compiler_so = [compiler_exe_so] + list(compiler_so_flags) @@ -526,7 +525,7 @@ def _cleanup_compiler_flags(self): if compiler_test(self.compiler, flag): flags.append(flag) else: - important_msgs('WARNING: ignoring unsupported compiler flag: {}'.format(flag)) + important_msgs(f'WARNING: ignoring unsupported compiler flag: {flag}') self.compiler.compiler.extend(flags) self.compiler.compiler_so.extend(flags) @@ -616,13 +615,13 @@ def run(self): with open('requirements.txt', 'w') as req_file: try: for pkg in self.distribution.install_requires: - req_file.write('{}\n'.format(pkg)) + req_file.write(f'{pkg}\n') except TypeError: # Mostly for old setuptools (< 30.x) for pkg in self.distribution.command_options['options']['install_requires']: - req_file.write('{}\n'.format(pkg)) + req_file.write(f'{pkg}\n') req_file.write('\n') for pkg in self.extra_pkgs: - req_file.write('{}\n'.format(pkg)) + req_file.write(f'{pkg}\n') # ------------------------------------------------------------------------------ From 74442132ee228d22c76cd4304b03d9e55db149c2 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Fri, 29 Jul 2022 10:24:40 +0200 Subject: [PATCH 092/113] Deprecate support for Python 3.6 (#442) --- .github/workflows/ci.yml | 9 +- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 7 +- examples/shor.py | 1 - .../decompositions/arb1qubit2rzandry.py | 34 +++--- .../decompositions/carb1qubit2cnotrzandry.py | 12 +- .../carb1qubit2cnotrzandry_test.py | 5 +- pyproject.toml | 75 ++++++++++-- setup.cfg | 55 +-------- setup.py | 112 +++++++++++++++--- 10 files changed, 201 insertions(+), 111 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73db64348..a209f9c9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,6 @@ jobs: matrix: runs-on: [ubuntu-latest, windows-latest, macos-latest] python: - - 3.6 - 3.7 - 3.8 - 3.9 @@ -64,6 +63,8 @@ jobs: - name: Prepare env run: | + python -m pip install -U pip setuptools wheel + cat requirements.txt python -m pip install -r requirements.txt --prefer-binary python -m pip install coveralls @@ -143,6 +144,8 @@ jobs: - name: Prepare Python env run: | python3 setup.py gen_reqfile --include-extras=test,braket + python3 -m pip install -U pip setuptools wheel + cat requirements.txt python3 -m pip install -r requirements.txt --prefer-binary - name: Upgrade pybind11 and flaky @@ -188,6 +191,8 @@ jobs: - name: Prepare Python env run: | python3 setup.py gen_reqfile --include-extras=test,braket + python3 -m pip install -U pip setuptools wheel + cat requirements.txt python3 -m pip install -r requirements.txt --prefer-binary - name: Upgrade pybind11 and flaky @@ -270,6 +275,8 @@ jobs: - name: Install dependencies run: | python3 setup.py gen_reqfile --include-extras=test,braket + python3 -m pip install -U pip setuptools wheel + cat requirements.txt python3 -m pip install -r requirements.txt --prefer-binary - name: Build and install package diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 804d6e47f..7b1ba27a8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,7 +46,7 @@ repos: rev: v2.34.0 hooks: - id: pyupgrade - args: [--py36-plus, --keep-mock] + args: [--py37-plus, --keep-mock] - repo: https://github.com/PyCQA/isort rev: 5.10.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4228cb015..ec2209730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Support for Python 3.6 and earlier is now deprecated +- Moved package metadata into pyproject.toml + ### Repository - Update `docker/setup-qemu-action` GitHub action to v2 - Fixed CentOS 7 configuration issue - Added two new pre-commit hooks: `blacken-docs` and `pyupgrade` -- Remove deprecated `setup_requires` field in setup.cfg -- Fixed installation issue with newer versions of `setuptool-scm`and Python¨ 3.6 ## [v0.7.3] - 2022-04-27 diff --git a/examples/shor.py b/examples/shor.py index 76ef9fd57..cb5d41bd8 100755 --- a/examples/shor.py +++ b/examples/shor.py @@ -12,7 +12,6 @@ except ImportError: from fractions import gcd - import projectq.libs.math import projectq.setups.decompositions from projectq.backends import ResourceCounter, Simulator diff --git a/projectq/setups/decompositions/arb1qubit2rzandry.py b/projectq/setups/decompositions/arb1qubit2rzandry.py index e7392da9d..33da891ea 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry.py @@ -170,22 +170,26 @@ def _find_parameters(matrix): # pylint: disable=too-many-branches,too-many-stat else: 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), - (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]) - 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), - (two_b/4.+3./2.*math.pi) % (2*math.pi)] + possible_d_half = [ + two_d / 4.0 % (2 * math.pi), + (two_d / 4.0 + math.pi / 2.0) % (2 * math.pi), + (two_d / 4.0 + math.pi) % (2 * math.pi), + (two_d / 4.0 + 3.0 / 2.0 * math.pi) % (2 * math.pi), + ] + two_b = 2.0 * cmath.phase(matrix[1][0]) - 2.0 * cmath.phase(matrix[0][0]) + possible_b_half = [ + two_b / 4.0 % (2 * math.pi), + (two_b / 4.0 + math.pi / 2.0) % (2 * math.pi), + (two_b / 4.0 + math.pi) % (2 * math.pi), + (two_b / 4.0 + 3.0 / 2.0 * math.pi) % (2 * math.pi), + ] tmp = math.acos(abs(matrix[1][1])) - 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 + possible_c_half = [ + tmp % (2 * math.pi), + (tmp + math.pi) % (2 * math.pi), + (-1.0 * tmp) % (2 * math.pi), + (-1.0 * tmp + math.pi) % (2 * math.pi), + ] found = False 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): diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py index 6f3f35d9e..48490a814 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py @@ -138,12 +138,12 @@ def _recognize_v(matrix): # pylint: disable=too-many-branches (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 + possible_c_half = [ + tmp % (2 * math.pi), + (tmp + math.pi) % (2 * math.pi), + (-1.0 * tmp) % (2 * math.pi), + (-1.0 * tmp + math.pi) % (2 * math.pi), + ] 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) diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py index e3464bdf3..f402f7027 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py @@ -98,12 +98,9 @@ def _decomp_gates(eng, cmd): return False -# yapf: disable -@pytest.mark.parametrize("gate_matrix", [[[1, 0], [0, -1]], - [[0, -1j], [1j, 0]]]) +@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()) diff --git a/pyproject.toml b/pyproject.toml index db6087272..82f728b54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,20 +1,81 @@ [build-system] -requires = ["setuptools>=42", "wheel", "pybind11>=2", - "setuptools_scm[toml]<7;python_version<'3.7'", - "setuptools_scm[toml]>6;python_version>='3.7'"] +requires = [ + 'setuptools>=61;python_version>="3.7"', + 'setuptools>=59;python_version<"3.7"', + 'wheel', + 'pybind11>=2', + 'setuptools_scm[toml]>6;python_version>="3.7"' +] build-backend = "setuptools.build_meta" +[project] +name = 'projectq' +authors = [ + {name = 'ProjectQ', email = 'info@projectq.ch'} +] +description = 'ProjectQ - An open source software framework for quantum computing' +requires-python = '>= 3.7' +license = {text= 'Apache License Version 2.0'} +readme = 'README.rst' +classifiers = [ + 'License :: OSI Approved :: Apache Software License', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10' +] +dynamic = ["version"] + +dependencies = [ + 'matplotlib >= 2.2.3', + 'networkx >= 2', + 'numpy', + 'requests', + 'scipy' +] + +[project.urls] +'Homepage' = 'http://www.projectq.ch' +'Documentation' = 'https://projectq.readthedocs.io/en/latest/' +'Issue Tracker' = 'https://github.com/ProjectQ-Framework/ProjectQ/' + +[project.optional-dependencies] + +braket = [ + 'boto3' +] + +revkit = [ + 'revkit == 3.0a2.dev2', + 'dormouse' +] + +test = [ + 'flaky', + 'mock', + 'pytest >= 6.0', + 'pytest-cov', + 'pytest-mock' +] + +docs = [ + 'sphinx', + 'sphinx_rtd_theme' +] + # ============================================================================== [tool.black] line-length = 120 - target-version = ['py36','py37','py38'] + target-version = ['py37','py38','py39','py310'] skip-string-normalization = true [tool.check-manifest] - ignore = [ +ignore = [ 'PKG-INFO', '*.egg-info', '*.egg-info/*', @@ -112,10 +173,6 @@ write_to = 'VERSION.txt' write_to_template = '{version}' local_scheme = 'no-local-version' -[tool.yapf] - -column_limit = 120 - [tool.cibuildwheel] archs = ['auto64'] diff --git a/setup.cfg b/setup.cfg index 4bea68081..c5248b57f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,66 +1,14 @@ -[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 - Programming Language :: Python :: 3.10 - [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 -revkit = - revkit == 3.0a2.dev2 - dormouse -test = - flaky - mock - pytest >= 6.0 - pytest-cov - pytest-mock - -docs = - sphinx - sphinx_rtd_theme - # ============================================================================== [flake8] max-line-length = 120 +ignore = E203,W503,E800 exclude = .git, __pycache__, @@ -68,6 +16,5 @@ exclude = dist, __init__.py docstring-quotes = """ -eradicate-whitelist = # yapf: disable# yapf: enable # ============================================================================== diff --git a/setup.py b/setup.py index a1fb2056e..9d23f1b60 100755 --- a/setup.py +++ b/setup.py @@ -52,6 +52,8 @@ LinkError, ) from distutils.spawn import find_executable, spawn +from operator import itemgetter +from pathlib import Path from setuptools import Distribution as _Distribution from setuptools import Extension, setup @@ -64,6 +66,86 @@ except ImportError: _HAS_SETUPTOOLS_SCM = False +try: + import tomllib + + def parse_toml(filename): + """Parse a TOML file.""" + with open(str(filename), 'rb') as toml_file: + return tomllib.load(toml_file) + +except ImportError: + try: + import toml + + def parse_toml(filename): + """Parse a TOML file.""" + return toml.load(filename) + + except ImportError: + + def _find_toml_section_end(lines, start): + """Find the index of the start of the next section.""" + return ( + next(filter(itemgetter(1), enumerate(line.startswith('[') for line in lines[start + 1 :])))[0] + + start + + 1 + ) + + def _parse_list(lines): + """Parse a TOML list into a Python list.""" + # NB: This function expects the TOML list to be formatted like so (ignoring leading and trailing spaces): + # name = [ + # '...', + # ] + # Any other format is not supported. + name = None + elements = [] + + for idx, line in enumerate(lines): + if name is None and not line.startswith("'"): + name = line.split('=')[0].strip() + continue + if line.startswith("]"): + return (name, elements, idx + 1) + elements.append(line.rstrip(',').strip("'").strip('"')) + + raise RuntimeError(f'Failed to locate closing "]" for {name}') + + def parse_toml(filename): + """Very simple parser routine for pyproject.toml.""" + result = {'project': {'optional-dependencies': {}}} + with open(filename) as toml_file: + lines = [line.strip() for line in toml_file.readlines()] + lines = [line for line in lines if line and not line.startswith('#')] + + start = lines.index('[project]') + project_data = lines[start : _find_toml_section_end(lines, start)] + + start = lines.index('[project.optional-dependencies]') + optional_dependencies = lines[start + 1 : _find_toml_section_end(lines, start)] + + idx = 0 + N = len(project_data) + while idx < N: + line = project_data[idx] + shift = 1 + if line.startswith('name'): + result['project']['name'] = line.split('=')[1].strip().strip("'") + elif line.startswith('dependencies'): + (name, pkgs, shift) = _parse_list(project_data[idx:]) + result['project'][name] = pkgs + idx += shift + + idx = 0 + N = len(optional_dependencies) + while idx < N: + (opt_name, opt_pkgs, shift) = _parse_list(optional_dependencies[idx:]) + result['project']['optional-dependencies'][opt_name] = opt_pkgs + idx += shift + + return result + # ============================================================================== # Helper functions and classes @@ -178,7 +260,7 @@ def _fix_macosx_header_paths(*args): compiler_args[idx] = item.replace(_MACOSX_DEVTOOLS_REF_PATH, _MACOSX_XCODE_REF_PATH) -# ------------------------------------------------------------------------------ +# ============================================================================== class BuildFailed(Exception): @@ -465,9 +547,6 @@ def _configure_cxx_standard(self): return cxx_standards = [17, 14, 11] - if sys.version_info[0] < 3: - cxx_standards = [year for year in cxx_standards if year < 17] - if sys.platform == 'darwin': major_version = int(platform.mac_ver()[0].split('.')[0]) minor_version = int(platform.mac_ver()[0].split('.')[1]) @@ -595,30 +674,27 @@ def initialize_options(self): self.include_extras = None self.include_all_extras = None self.extra_pkgs = [] + self.dependencies = [] def finalize_options(self): """Finalize this command's options.""" include_extras = self.include_extras.split(',') if self.include_extras else [] + pyproject_toml = parse_toml(Path(__file__).parent / 'pyproject.toml') - 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) + for name, pkgs in pyproject_toml['project']['optional-dependencies'].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) + # pylint: disable=attribute-defined-outside-init + self.dependencies = self.distribution.install_requires + if not self.dependencies: + self.dependencies = pyproject_toml['project']['dependencies'] def run(self): """Execute this command.""" with open('requirements.txt', 'w') as req_file: - try: - for pkg in self.distribution.install_requires: - req_file.write(f'{pkg}\n') - except TypeError: # Mostly for old setuptools (< 30.x) - for pkg in self.distribution.command_options['options']['install_requires']: - req_file.write(f'{pkg}\n') + for pkg in self.dependencies: + req_file.write(f'{pkg}\n') req_file.write('\n') for pkg in self.extra_pkgs: req_file.write(f'{pkg}\n') From 739f78231c003f0b6ab2697d49e3a7ff9440bd0e Mon Sep 17 00:00:00 2001 From: Sai Seshu Chadaram Date: Mon, 8 Aug 2022 12:54:42 +0530 Subject: [PATCH 093/113] Azure Quantum backend (#439) * Add Azure Quantum backend template files * Add implementation of Azure Quantum * Add support for Honeywell and improve exception handling * Fixes and reformatting * Fixes and Improvements * Fixes and Improvements * Fixes and Improvements * Fixes and Improvements * Fix QASM transulation * Fixes and Improvements * Documentation related changes * Fix typos * Add docstrings to util methods * Fix typos * Add Unittests * Add Unittests * Add Unittests & rename honeywell to quantinuum * Fixes for QSAM convector * Add Unittests * Add Unittests * Add Unittests * Add Unittests * Fix Quantinuum Backend for Azure Quantum * Add Unittests * Add Unittests * Add Unittests * Add Unittests * Fix Vdag gate for IonQ backend * Minor updates * Update Docs * Add Azure Quantum example * Update README.rst * Update Docs * Update Docs * Update Unittests * Azure Quantum: CI Fixes (#4) * Azure Quantum: CI Fixes * Fix optional dependency problem * Update pre-commit reformatting * Fix pylint issues * Fix pylint issues * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Resolve comments (#5) * Resolve comments * Remove rearrange_result method from _utils.py * Update with pytest.approx for assert floating numbers * Pytest skipif testcases for Azure Quantum (#6) * Pytest skipif testcases for Azure Quantum * Fix projectq.backends._azure._exceptions import * Add common utils module (#7) * Create common utils module * Optimize _rearrange_result method * Make sure pip, setuptools and wheel packages are up to date on CI * Fix formatting issues from latest changes in develop branch * Improve coveralls (#8) * Improve coveralls * Improve coveralls * Rename util.py to utils.py * Minor fixes * Update testcases * Update testcases * Improve coveralls (#9) * Improve coveralls * Improve coveralls * Precommit reformatting * Improve coveralls (#10) * Improve coveralls * Precommit formatting * Fix CI failures * Avoid a few more #pragma: no cover * Fix flake8 warning * Remove one more # pragma: no cover * Reformat file Co-authored-by: Nguyen Damien --- .github/workflows/ci.yml | 20 +- .travis.yml | 1 + CHANGELOG.md | 4 + README.rst | 31 +- docs/tutorials.rst | 8 + examples/azure-quantum.ipynb | 264 ++++++ projectq/backends/__init__.py | 2 + projectq/backends/_aqt/_aqt.py | 7 +- projectq/backends/_azure/__init__.py | 27 + projectq/backends/_azure/_azure_quantum.py | 387 ++++++++ .../backends/_azure/_azure_quantum_client.py | 96 ++ .../_azure/_azure_quantum_client_test.py | 420 +++++++++ .../backends/_azure/_azure_quantum_test.py | 862 ++++++++++++++++++ projectq/backends/_azure/_exceptions.py | 19 + projectq/backends/_azure/_utils.py | 307 +++++++ projectq/backends/_azure/_utils_test.py | 642 +++++++++++++ projectq/backends/_ionq/_ionq.py | 15 +- projectq/backends/_utils.py | 28 + projectq/backends/_utils_test.py | 35 + pyproject.toml | 4 + 20 files changed, 3148 insertions(+), 31 deletions(-) create mode 100644 examples/azure-quantum.ipynb create mode 100644 projectq/backends/_azure/__init__.py create mode 100644 projectq/backends/_azure/_azure_quantum.py create mode 100644 projectq/backends/_azure/_azure_quantum_client.py create mode 100644 projectq/backends/_azure/_azure_quantum_client_test.py create mode 100644 projectq/backends/_azure/_azure_quantum_test.py create mode 100644 projectq/backends/_azure/_exceptions.py create mode 100644 projectq/backends/_azure/_utils.py create mode 100644 projectq/backends/_azure/_utils_test.py create mode 100644 projectq/backends/_utils.py create mode 100644 projectq/backends/_utils_test.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a209f9c9f..ffda8c62b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,12 +54,12 @@ jobs: - name: Generate requirement file (Unix) if: runner.os != 'Windows' run: | - python setup.py gen_reqfile --include-extras=test,braket,revkit + python setup.py gen_reqfile --include-extras=test,azure-quantum,braket,revkit - name: Generate requirement file (Windows) if: runner.os == 'Windows' run: | - python setup.py gen_reqfile --include-extras=test,braket + python setup.py gen_reqfile --include-extras=test,azure-quantum,braket - name: Prepare env run: | @@ -74,11 +74,11 @@ jobs: - name: Build and install package (Unix) if: runner.os != 'Windows' - run: python -m pip install -ve .[braket,revkit,test] + run: python -m pip install -ve .[azure-quantum,braket,revkit,test] - name: Build and install package (Windows) if: runner.os == 'Windows' - run: python -m pip install -ve .[braket,test] + run: python -m pip install -ve .[azure-quantum,braket,test] - name: Pytest run: | @@ -143,8 +143,8 @@ jobs: - name: Prepare Python env run: | - python3 setup.py gen_reqfile --include-extras=test,braket python3 -m pip install -U pip setuptools wheel + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket cat requirements.txt python3 -m pip install -r requirements.txt --prefer-binary @@ -152,7 +152,7 @@ jobs: run: python3 -m pip install --upgrade pybind11 flaky --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[braket,test] + run: python3 -m pip install -ve .[azure-quantum,braket,test] - name: Pytest run: | @@ -190,8 +190,8 @@ jobs: - name: Prepare Python env run: | - python3 setup.py gen_reqfile --include-extras=test,braket python3 -m pip install -U pip setuptools wheel + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket cat requirements.txt python3 -m pip install -r requirements.txt --prefer-binary @@ -199,7 +199,7 @@ jobs: run: python3 -m pip install --upgrade pybind11 flaky --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[braket,test] + run: python3 -m pip install -ve .[azure-quantum,braket,test] - name: Pytest run: | @@ -274,13 +274,13 @@ jobs: - name: Install dependencies run: | - python3 setup.py gen_reqfile --include-extras=test,braket python3 -m pip install -U pip setuptools wheel + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket cat requirements.txt python3 -m pip install -r requirements.txt --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[braket,test] + run: python3 -m pip install -ve .[azure-quantum,braket,test] - name: Pytest run: | diff --git a/.travis.yml b/.travis.yml index 0dd408af0..b4ab7159f 100755 --- a/.travis.yml +++ b/.travis.yml @@ -60,6 +60,7 @@ install: - env - python3 -m pip install -U pip setuptools wheel - python3 -m pip install -U pybind11 dormouse revkit flaky pytest-cov coveralls boto3 + - python3 -m pip install -U azure-quantum - python3 -m pip install -r requirements.txt - python3 -m pip install -ve . diff --git a/CHANGELOG.md b/CHANGELOG.md index ec2209730..c7a5f625b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- New backend for the Azure Quantum platform + ### Changed - Support for Python 3.6 and earlier is now deprecated diff --git a/README.rst b/README.rst index 1f85c2cc2..a2c794bb0 100755 --- a/README.rst +++ b/README.rst @@ -27,7 +27,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, AWS Braket, or IonQ service provided devices +- run quantum programs on the IBM Quantum Experience chip, AQT devices, AWS Braket, Azure Quantum, 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 @@ -186,6 +186,35 @@ IonQ from IonQ and the state vector simulator SV1: python3 -m pip install -ve .[braket] +**Running a quantum program on a Azure Quantum provided device** + +To run a program on devices provided by the `Azure Quantum `_. + +Use `AzureQuantumBackend` to run ProjectQ circuits on hardware devices and simulator devices from providers `IonQ` and `Quantinuum`. + +.. code-block:: python + + from projectq.backends import AzureQuantumBackend + + azure_quantum_backend = AzureQuantumBackend( + use_hardware=False, target_name='ionq.simulator', resource_id='', location='', verbose=True + ) + +.. note:: + + In order to use the AzureQuantumBackend, you need to install ProjectQ with the 'azure-quantum' extra requirement: + + .. code-block:: bash + + python3 -m pip install projectq[azure-quantum] + + or + + .. code-block:: bash + + cd /path/to/projectq/source/code + python3 -m pip install -ve .[azure-quantum] + **Running a quantum program on IonQ devices** To run a program on the IonQ trapped ion hardware, use the `IonQBackend` and its corresponding setup. diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 669ec9eb0..bcc7e2d67 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -54,6 +54,14 @@ AWS Braket Backend requires the use of the official AWS SDK for Python, Boto3. T python -m pip install --user projectq[braket] +**Install Azure Quantum Backend requirement** + +Azure Quantum Backend requires the use of the official `Azure Quantum SDK `_ for Python. This is an extra requirement only needed if you plan to use the Azure Quantum 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[azure-quantum] + Detailed instructions and OS-specific hints ------------------------------------------- diff --git a/examples/azure-quantum.ipynb b/examples/azure-quantum.ipynb new file mode 100644 index 000000000..bd126815d --- /dev/null +++ b/examples/azure-quantum.ipynb @@ -0,0 +1,264 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2f4b2787-f008-4331-83ee-e743c64017aa", + "metadata": {}, + "source": [ + "# Azure Quantum Backend" + ] + }, + { + "cell_type": "markdown", + "id": "2bc9b3a4-23d8-4638-b875-386295010a3d", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "- An Azure account with active subcription.\n", + "- An Azure Quantum workspace. ([How to create this?](https://docs.microsoft.com/en-us/azure/quantum/how-to-create-workspace?tabs=tabid-quick))\n", + "- Resource ID and location of Azure Quantum workspace.\n", + "- Install Azure Quantum dependencies for ProjectQ.\n", + "```\n", + "python -m pip install --user projectq[azure-quantum]\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "e8e87193-fc98-4718-a62d-cbbde662b82f", + "metadata": {}, + "source": [ + "## Load Imports\n", + "\n", + "Run following cell to load requried imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a239f2e4-dcc1-4c9f-b8db-0a775a9e2863", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from projectq import MainEngine\n", + "from projectq.ops import H, CX, All, Measure\n", + "from projectq.cengines import BasicMapperEngine\n", + "from projectq.backends import AzureQuantumBackend\n", + "from projectq.libs.hist import histogram" + ] + }, + { + "cell_type": "markdown", + "id": "e98cedd2-3d13-4b81-8776-59ef74a0ca2c", + "metadata": {}, + "source": [ + "## Initialize Azure Quantum backend\n", + "\n", + "Update `resource_id` and `location` of your Azure Quantum workspace in below cell and run to initialize Azure Quantum workspace.\n", + "\n", + "Following are valid `target_names`:\n", + "- ionq.simulator\n", + "- ionq.qpu\n", + "- quantinuum.hqs-lt-s1-apival\n", + "- quantinuum.hqs-lt-s1-sim\n", + "- quantinuum.hqs-lt-s1\n", + "\n", + "Flag `use_hardware` represents wheather or not use real hardware or just a simulator. If False regardless of the value of `target_name`, simulator will be used." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e160b42-039e-4d5b-8333-7169bc77c57f", + "metadata": {}, + "outputs": [], + "source": [ + "azure_quantum_backend = AzureQuantumBackend(\n", + " use_hardware=False,\n", + " target_name='ionq.simulator',\n", + " resource_id=\"\", # resource id of workspace\n", + " location=\"\", # location of workspace\n", + " verbose=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "321876bd-dbbf-446d-9732-3627037d49f0", + "metadata": {}, + "source": [ + "## Create ProjectQ Engine\n", + "\n", + "Initialize ProjectQ `MainEngine` using Azure Quantum as backend." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6fbc7650-fa8a-4047-8739-ee79aa2e6c2a", + "metadata": {}, + "outputs": [], + "source": [ + "mapper = BasicMapperEngine()\n", + "max_qubits = 3\n", + "\n", + "mapping = {}\n", + "for i in range(max_qubits):\n", + " mapping[i] = i\n", + "\n", + "mapper.current_mapping = mapping\n", + "\n", + "main_engine = MainEngine(\n", + " backend=azure_quantum_backend,\n", + " engine_list=[mapper]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d2665b04-0903-4176-b62d-b8fed5d5e041", + "metadata": {}, + "source": [ + "## Create circuit using ProjectQ lean syntax!\n", + "\n", + "Allocate qubits, build circuit and measure qubits." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a8e84c3-cf1c-44db-80f1-e0a229a38eb9", + "metadata": {}, + "outputs": [], + "source": [ + "circuit = main_engine.allocate_qureg(3)\n", + "q0, q1, q2 = circuit\n", + "\n", + "H | q0\n", + "CX | (q0, q1)\n", + "CX | (q1, q2)\n", + "All(Measure) | circuit" + ] + }, + { + "cell_type": "markdown", + "id": "4a9901e8-ed74-4859-a004-8f1b9b72dd45", + "metadata": {}, + "source": [ + "## Run circuit and get result\n", + "\n", + "Flush down circuit to Azure Quantum backend and wait for results. It prints `job-id` for the reference (in case this operation timed-out). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9af5ea10-1edc-4fe9-9d8d-c021c196779d", + "metadata": {}, + "outputs": [], + "source": [ + "main_engine.flush()\n", + "\n", + "print(azure_quantum_backend.get_probabilities(circuit))" + ] + }, + { + "cell_type": "markdown", + "id": "b7dfc230-d4fc-4460-8acb-c42ebccc1659", + "metadata": {}, + "source": [ + "## Timed out! Re-run the circuit with retrieve_execution argument\n", + "\n", + "If job execution timed-out, use `retrieve_execution` argument retrive result instead of re-running the circuit. Use `job-id` from previous cell." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05001b5-25ca-4dfd-a165-123ac7e83b38", + "metadata": {}, + "outputs": [], + "source": [ + "azure_quantum_backend = AzureQuantumBackend(\n", + " use_hardware=False,\n", + " target_name='ionq.simulator',\n", + " resource_id=\"\", # resource id of workspace\n", + " location=\"\", # location of workspace\n", + " retrieve_execution=\"\", # job-id of Azure Quantum job\n", + " verbose=True\n", + ")\n", + "\n", + "mapper = BasicMapperEngine()\n", + "max_qubits = 10\n", + "\n", + "mapping = {}\n", + "for i in range(max_qubits):\n", + " mapping[i] = i\n", + "\n", + "mapper.current_mapping = mapping\n", + "\n", + "main_engine = MainEngine(\n", + " backend=azure_quantum_backend,\n", + " engine_list=[mapper]\n", + ")\n", + "\n", + "circuit = main_engine.allocate_qureg(3)\n", + "q0, q1, q2 = circuit\n", + "\n", + "H | q0\n", + "CX | (q0, q1)\n", + "CX | (q1, q2)\n", + "All(Measure) | circuit\n", + "\n", + "main_engine.flush()\n", + "\n", + "print(azure_quantum_backend.get_probabilities(circuit))" + ] + }, + { + "cell_type": "markdown", + "id": "b658cb79-15c8-4e25-acbd-77788af08f9e", + "metadata": {}, + "source": [ + "# Plot Histogram\n", + "\n", + "Now, let's plot histogram with above result." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68b3fa8d-a06d-4049-8350-d179304bf045", + "metadata": {}, + "outputs": [], + "source": [ + "histogram(main_engine.backend, circuit)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:projectq] *", + "language": "python", + "name": "conda-env-projectq-py" + }, + "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.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/projectq/backends/__init__.py b/projectq/backends/__init__.py index 0b72a8b71..e531ed9f2 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -25,10 +25,12 @@ * 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 Azure Quantum service devices (and simulators) * an interface to the IonQ trapped ionq hardware (and simulator). """ from ._aqt import AQTBackend from ._awsbraket import AWSBraketBackend +from ._azure import AzureQuantumBackend from ._circuits import CircuitDrawer, CircuitDrawerMatplotlib from ._exceptions import DeviceNotHandledError, DeviceOfflineError, DeviceTooSmall from ._ibm import IBMBackend diff --git a/projectq/backends/_aqt/_aqt.py b/projectq/backends/_aqt/_aqt.py index 48942b475..cb1993099 100644 --- a/projectq/backends/_aqt/_aqt.py +++ b/projectq/backends/_aqt/_aqt.py @@ -23,15 +23,10 @@ from projectq.types import WeakQubitRef from .._exceptions import InvalidCommandError +from .._utils import _rearrange_result from ._aqt_http_client import retrieve, send -# _rearrange_result & _format_counts imported and modified from qiskit -def _rearrange_result(input_result, length): - bin_input = list(bin(input_result)[2:].rjust(length, '0')) - return ''.join(bin_input)[::-1] - - def _format_counts(samples, length): counts = {} for result in samples: diff --git a/projectq/backends/_azure/__init__.py b/projectq/backends/_azure/__init__.py new file mode 100644 index 000000000..8c06e2392 --- /dev/null +++ b/projectq/backends/_azure/__init__.py @@ -0,0 +1,27 @@ +# Copyright 2022 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. + +"""ProjectQ module for supporting the Azure Quantum platform.""" + +try: + from ._azure_quantum import AzureQuantumBackend +except ImportError: # pragma: no cover + + class AzureQuantumBackend: + """Dummy class""" + + def __init__(self, *args, **kwargs): + raise ImportError( + "Missing optional 'azure-quantum' dependencies. To install run: pip install projectq[azure-quantum]" + ) diff --git a/projectq/backends/_azure/_azure_quantum.py b/projectq/backends/_azure/_azure_quantum.py new file mode 100644 index 000000000..e8187c87c --- /dev/null +++ b/projectq/backends/_azure/_azure_quantum.py @@ -0,0 +1,387 @@ +# Copyright 2022 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 Azure Quantum.""" + +from collections import Counter + +import numpy as np + +from projectq.cengines import BasicEngine +from projectq.meta import LogicalQubitIDTag +from projectq.ops import AllocateQubitGate, DeallocateQubitGate, FlushGate, MeasureGate +from projectq.types import WeakQubitRef + +from .._utils import _rearrange_result +from ._azure_quantum_client import retrieve, send +from ._exceptions import AzureQuantumTargetNotFoundError +from ._utils import ( + IONQ_PROVIDER_ID, + QUANTINUUM_PROVIDER_ID, + is_available_ionq, + is_available_quantinuum, + to_json, + to_qasm, +) + +try: + from azure.quantum import Workspace + from azure.quantum.target import IonQ, Quantinuum, Target + from azure.quantum.target.target_factory import TargetFactory +except ImportError: # pragma: no cover + raise ImportError( # pylint: disable=raise-missing-from + "Missing optional 'azure-quantum' dependencies. To install run: pip install projectq[azure-quantum]" + ) + + +class AzureQuantumBackend(BasicEngine): # pylint: disable=too-many-instance-attributes + """Backend for building circuits and submitting them to the Azure Quantum.""" + + DEFAULT_TARGETS = {IONQ_PROVIDER_ID: IonQ, QUANTINUUM_PROVIDER_ID: Quantinuum} + + def __init__( + self, + use_hardware=False, + num_runs=100, + verbose=False, + workspace=None, + target_name='ionq.simulator', + num_retries=100, + interval=1, + retrieve_execution=None, + **kwargs, + ): # pylint: disable=too-many-arguments + """ + Initialize an Azure Quantum Backend object. + + Args: + use_hardware (bool, optional): Whether or not to use real hardware or just a simulator. If False, + regardless of the value of ```target_name```, ```ionq.simulator``` used for IonQ provider and + ```quantinuum.hqs-lt-s1-apival``` used for Quantinuum provider. 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. + workspace (Workspace, optional): Azure Quantum workspace. If workspace is None, kwargs will be used to + create Workspace object. + target_name (str, optional): Target to run jobs on. Defaults to ```ionq.simulator```. + num_retries (int, optional): Number of times to retry fetching a job after it has been submitted. Defaults + to 100. + interval (int, optional): Number of seconds to wait in between result fetch retries. Defaults to 1. + retrieve_execution (str, optional): An Azure Quantum Job ID. If provided, a job result with this ID will be + fetched. Defaults to None. + """ + super().__init__() + + if target_name in IonQ.target_names: + self._provider_id = IONQ_PROVIDER_ID + elif target_name in Quantinuum.target_names: + self._provider_id = QUANTINUUM_PROVIDER_ID + else: + raise AzureQuantumTargetNotFoundError(f'Target {target_name} does not exit.') + + if use_hardware: + self._target_name = target_name + else: + if self._provider_id == IONQ_PROVIDER_ID: + self._target_name = 'ionq.simulator' + elif self._provider_id == QUANTINUUM_PROVIDER_ID: + if target_name == 'quantinuum.hqs-lt-s1': + self._target_name = 'quantinuum.hqs-lt-s1-apival' + else: + self._target_name = target_name + else: # pragma: no cover + raise RuntimeError("Invalid Azure Quantum target.") + + if workspace is None: + workspace = Workspace(**kwargs) + + workspace.append_user_agent('projectq') + self._workspace = workspace + + self._num_runs = num_runs + self._verbose = verbose + self._num_retries = num_retries + self._interval = interval + self._retrieve_execution = retrieve_execution + self._circuit = None + self._measured_ids = [] + self._probabilities = {} + self._clear = True + + 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 = None + self._clear = True + + 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._cmds). + + Args: + cmd (Command): Command to store + """ + if self._clear: + self._probabilities = {} + self._clear = False + self._circuit = None + + gate = cmd.gate + + # No-op/Meta gates + if isinstance(gate, (AllocateQubitGate, DeallocateQubitGate)): + return + + # Measurement + if isinstance(gate, MeasureGate): + logical_id = None + for tag in cmd.tags: + if isinstance(tag, LogicalQubitIDTag): + logical_id = tag.logical_qubit_id + break + + if logical_id is None: + raise RuntimeError('No LogicalQubitIDTag found in command!') + + self._measured_ids.append(logical_id) + return + + if self._provider_id == IONQ_PROVIDER_ID: + if not self._circuit: + self._circuit = [] + + json_cmd = to_json(cmd) + if json_cmd: + self._circuit.append(json_cmd) + + elif self._provider_id == QUANTINUUM_PROVIDER_ID: + if not self._circuit: + self._circuit = '' + + qasm_cmd = to_qasm(cmd) + if qasm_cmd: + self._circuit += f'\n{qasm_cmd}' + + else: + raise RuntimeError("Invalid Azure Quantum target.") + + 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. + """ + if self._provider_id == IONQ_PROVIDER_ID: + return is_available_ionq(cmd) + + if self._provider_id == QUANTINUUM_PROVIDER_ID: + return is_available_quantinuum(cmd) + + return False + + @staticmethod + def _target_factory(workspace, target_name, provider_id): # pragma: no cover + target_factory = TargetFactory( + base_cls=Target, workspace=workspace, default_targets=AzureQuantumBackend.DEFAULT_TARGETS + ) + + return target_factory.get_targets(name=target_name, provider_id=provider_id) + + @property + def _target(self): + target = self._target_factory( + workspace=self._workspace, target_name=self._target_name, provider_id=self._provider_id + ) + + if isinstance(target, list) and len(target) == 0: # pragma: no cover + raise AzureQuantumTargetNotFoundError( + f'Target {self._target_name} is not available on workspace {self._workspace.name}.' + ) + + return target + + @property + def current_availability(self): + """Get current availability for given target.""" + return self._target.current_availability + + @property + def average_queue_time(self): + """Get average queue time for given target.""" + return self._target.average_queue_time + + 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.get(state, 0.0) + + 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 + + @property + def _input_data(self): + qubit_mapping = self.main_engine.mapper.current_mapping + qubits = len(qubit_mapping.keys()) + + if self._provider_id == IONQ_PROVIDER_ID: + return {"qubits": qubits, "circuit": self._circuit} + + if self._provider_id == QUANTINUUM_PROVIDER_ID: + measurement_gates = "" + + for measured_id in self._measured_ids: + qb_loc = self.main_engine.mapper.current_mapping[measured_id] + measurement_gates += "measure q[{0}] -> c[{0}];\n".format(qb_loc) + + return ( + f"OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[{qubits}];\ncreg c[{qubits}];" + f"{self._circuit}\n{measurement_gates}" + ) + + raise RuntimeError("Invalid Azure Quantum target.") + + @property + def _metadata(self): + qubit_mapping = self.main_engine.mapper.current_mapping + num_qubits = len(qubit_mapping.keys()) + meas_map = [qubit_mapping[qubit_id] for qubit_id in self._measured_ids] + + return {"num_qubits": num_qubits, "meas_map": meas_map} + + def estimate_cost(self, **kwargs): + """Estimate cost for the circuit this object has built during engine execution.""" + return self._target.estimate_cost(circuit=self._input_data, num_shots=self._num_runs, **kwargs) + + 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 not self._circuit: + return + + if self._retrieve_execution is None: + res = send( + input_data=self._input_data, + metadata=self._metadata, + num_shots=self._num_runs, + target=self._target, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) + + if res is None: # pragma: no cover + raise RuntimeError('Failed to submit job to the Azure Quantum!') + else: + res = retrieve( + job_id=self._retrieve_execution, + target=self._target, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) + + if res is None: + raise RuntimeError( + f"Failed to retrieve job from Azure Quantum with job id: '{self._retrieve_execution}'!" + ) + + if self._provider_id == IONQ_PROVIDER_ID: + self._probabilities = { + _rearrange_result(int(k), len(self._measured_ids)): v for k, v in res["histogram"].items() + } + elif self._provider_id == QUANTINUUM_PROVIDER_ID: + histogram = Counter(res["c"]) + self._probabilities = {k: v / self._num_runs for k, v in histogram.items()} + else: # pragma: no cover + raise RuntimeError("Invalid Azure Quantum target.") + + # Set a single measurement result + bitstring = np.random.choice(list(self._probabilities.keys()), p=list(self._probabilities.values())) + for qid in self._measured_ids: + qubit_ref = WeakQubitRef(self.main_engine, qid) + self.main_engine.set_measurement_result(qubit_ref, bitstring[qid]) + + 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 Azure Quantum 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__ = ['AzureQuantumBackend'] diff --git a/projectq/backends/_azure/_azure_quantum_client.py b/projectq/backends/_azure/_azure_quantum_client.py new file mode 100644 index 000000000..86c3f1891 --- /dev/null +++ b/projectq/backends/_azure/_azure_quantum_client.py @@ -0,0 +1,96 @@ +# Copyright 2022 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. + +"""Client methods to run quantum programs using Azure Quantum.""" + +from .._exceptions import DeviceOfflineError, RequestTimeoutError + + +def _get_results(job, num_retries=100, interval=1, verbose=False): + if verbose: # pragma: no cover + print(f"- Waiting for results. [Job ID: {job.id}]") + + try: + return job.get_results(timeout_secs=num_retries * interval) + except TimeoutError: + raise RequestTimeoutError( # pylint: disable=raise-missing-from + f"Timeout. The ID of your submitted job is {job.id}." + ) + + +def send( + input_data, num_shots, target, num_retries=100, interval=1, verbose=False, **kwargs +): # pylint: disable=too-many-arguments + """ + Submit a job to the Azure Quantum. + + Args: + input_data (any): Input data for Quantum job. + num_shots (int): Number of runs. + target (Target), The target to run this on. + 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. + + Returns: + dict: An intermediate dict representation of an Azure Quantum job result. + """ + if target.current_availability != 'Available': + raise DeviceOfflineError('Device is offline.') + + if verbose: + print(f"- Running code: {input_data}") + + job = target.submit(circuit=input_data, num_shots=num_shots, **kwargs) + + res = _get_results(job=job, num_retries=num_retries, interval=interval, verbose=verbose) + + if verbose: + print("- Done.") + + return res + + +def retrieve(job_id, target, num_retries=100, interval=1, verbose=False): + """ + Retrieve a job from Azure Quantum. + + Args: + job_id (str), Azure Quantum job id. + target (Target), The target job runs on. + 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. + + Returns: + dict: An intermediate dict representation of an Azure Quantum job result. + """ + job = target.workspace.get_job(job_id=job_id) + + res = _get_results(job=job, num_retries=num_retries, interval=interval, verbose=verbose) + + if verbose: + print("- Done.") + + return res diff --git a/projectq/backends/_azure/_azure_quantum_client_test.py b/projectq/backends/_azure/_azure_quantum_client_test.py new file mode 100644 index 000000000..5ce1a6d8f --- /dev/null +++ b/projectq/backends/_azure/_azure_quantum_client_test.py @@ -0,0 +1,420 @@ +# Copyright 2022 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._azure._azure_quantum_client.py.""" + +from unittest import mock + +import pytest + +from .._exceptions import DeviceOfflineError, RequestTimeoutError + +_has_azure_quantum = True +try: + import azure.quantum # noqa: F401 + + from projectq.backends._azure._azure_quantum_client import retrieve, send +except ImportError: + _has_azure_quantum = False + +has_azure_quantum = pytest.mark.skipif(not _has_azure_quantum, reason="azure quantum package is not installed") + +ZERO_GUID = '00000000-0000-0000-0000-000000000000' + + +@has_azure_quantum +def test_is_online(): + def get_mock_target(): + mock_target = mock.MagicMock() + mock_target.current_availability = 'Offline' + + return mock_target + + with pytest.raises(DeviceOfflineError): + send( + input_data={}, + metadata={}, + num_shots=100, + target=get_mock_target(), + num_retries=1000, + interval=1, + verbose=True, + ) + + +@has_azure_quantum +@pytest.mark.parametrize('verbose', (False, True)) +def test_send_ionq(verbose): + expected_res = {'0': 0.125, '1': 0.125, '2': 0.125, '3': 0.125, '4': 0.125, '5': 0.125, '6': 0.125, '7': 0.125} + + def get_mock_target(): + mock_job = mock.MagicMock() + mock_job.id = ZERO_GUID + mock_job.get_results = mock.MagicMock(return_value=expected_res) + + mock_target = mock.MagicMock() + mock_target.current_availability = 'Available' + mock_target.submit = mock.MagicMock(return_value=mock_job) + + return mock_target + + input_data = { + 'qubits': 3, + 'circuit': [{'gate': 'h', 'targets': [0]}, {'gate': 'h', 'targets': [1]}, {'gate': 'h', 'targets': [2]}], + } + metadata = {'num_qubits': 3, 'meas_map': [0, 1, 2]} + + actual_res = send( + input_data=input_data, + metadata=metadata, + num_shots=100, + target=get_mock_target(), + num_retries=1000, + interval=1, + verbose=verbose, + ) + + assert actual_res == expected_res + + +@has_azure_quantum +@pytest.mark.parametrize('verbose', (False, True)) +def test_send_quantinuum(verbose): + expected_res = { + 'c': [ + '010', + '100', + '110', + '000', + '101', + '111', + '000', + '100', + '000', + '110', + '111', + '100', + '100', + '000', + '101', + '110', + '111', + '011', + '101', + '100', + '001', + '110', + '001', + '001', + '100', + '011', + '110', + '000', + '101', + '101', + '010', + '100', + '110', + '111', + '010', + '000', + '010', + '110', + '000', + '110', + '001', + '100', + '110', + '011', + '010', + '111', + '100', + '110', + '100', + '100', + '011', + '000', + '001', + '101', + '000', + '011', + '111', + '101', + '101', + '001', + '011', + '110', + '001', + '010', + '001', + '110', + '101', + '000', + '010', + '001', + '011', + '100', + '110', + '100', + '110', + '101', + '110', + '111', + '110', + '001', + '011', + '101', + '111', + '011', + '100', + '111', + '100', + '001', + '111', + '111', + '100', + '100', + '110', + '101', + '100', + '110', + '100', + '000', + '011', + '000', + ] + } + + def get_mock_target(): + mock_job = mock.MagicMock() + mock_job.id = ZERO_GUID + mock_job.get_results = mock.MagicMock(return_value=expected_res) + + mock_target = mock.MagicMock() + mock_target.current_availability = 'Available' + mock_target.submit = mock.MagicMock(return_value=mock_job) + + return mock_target + + input_data = ''''OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + creg c[3]; + h q[0]; + h q[1]; + h q[2]; + measure q[0] -> c[0]; + measure q[1] -> c[1]; + measure q[2] -> c[2]; +''' + + metadata = {'num_qubits': 3, 'meas_map': [0, 1, 2]} + + actual_res = send( + input_data=input_data, + metadata=metadata, + num_shots=100, + target=get_mock_target(), + num_retries=1000, + interval=1, + verbose=verbose, + ) + + assert actual_res == expected_res + + +@has_azure_quantum +@pytest.mark.parametrize('verbose', (False, True)) +def test_retrieve_ionq(verbose): + expected_res = {'0': 0.125, '1': 0.125, '2': 0.125, '3': 0.125, '4': 0.125, '5': 0.125, '6': 0.125, '7': 0.125} + + def get_mock_target(): + mock_job = mock.MagicMock() + mock_job.id = ZERO_GUID + mock_job.get_results = mock.MagicMock(return_value=expected_res) + + mock_workspace = mock.MagicMock() + mock_workspace.get_job = mock.MagicMock(return_value=mock_job) + + mock_target = mock.MagicMock() + mock_target.current_availability = 'Available' + mock_target.workspace = mock_workspace + mock_target.submit = mock.MagicMock(return_value=mock_job) + + return mock_target + + actual_res = retrieve(job_id=ZERO_GUID, target=get_mock_target(), num_retries=1000, interval=1, verbose=verbose) + + assert actual_res == expected_res + + +@has_azure_quantum +@pytest.mark.parametrize('verbose', (False, True)) +def test_retrieve_quantinuum(verbose): + expected_res = { + 'c': [ + '010', + '100', + '110', + '000', + '101', + '111', + '000', + '100', + '000', + '110', + '111', + '100', + '100', + '000', + '101', + '110', + '111', + '011', + '101', + '100', + '001', + '110', + '001', + '001', + '100', + '011', + '110', + '000', + '101', + '101', + '010', + '100', + '110', + '111', + '010', + '000', + '010', + '110', + '000', + '110', + '001', + '100', + '110', + '011', + '010', + '111', + '100', + '110', + '100', + '100', + '011', + '000', + '001', + '101', + '000', + '011', + '111', + '101', + '101', + '001', + '011', + '110', + '001', + '010', + '001', + '110', + '101', + '000', + '010', + '001', + '011', + '100', + '110', + '100', + '110', + '101', + '110', + '111', + '110', + '001', + '011', + '101', + '111', + '011', + '100', + '111', + '100', + '001', + '111', + '111', + '100', + '100', + '110', + '101', + '100', + '110', + '100', + '000', + '011', + '000', + ] + } + + def get_mock_target(): + mock_job = mock.MagicMock() + mock_job.id = ZERO_GUID + mock_job.get_results = mock.MagicMock(return_value=expected_res) + + mock_workspace = mock.MagicMock() + mock_workspace.get_job = mock.MagicMock(return_value=mock_job) + + mock_target = mock.MagicMock() + mock_target.current_availability = 'Available' + mock_target.workspace = mock_workspace + mock_target.submit = mock.MagicMock(return_value=mock_job) + + return mock_target + + actual_res = retrieve(job_id=ZERO_GUID, target=get_mock_target(), num_retries=1000, interval=1, verbose=verbose) + + assert actual_res == expected_res + + +@has_azure_quantum +@pytest.mark.parametrize('verbose', (False, True)) +def test_send_timeout_error(verbose): + def get_mock_target(): + mock_job = mock.MagicMock() + mock_job.id = ZERO_GUID + mock_job.get_results = mock.MagicMock() + mock_job.get_results.side_effect = TimeoutError() + + mock_target = mock.MagicMock() + mock_target.current_availability = 'Available' + mock_target.submit = mock.MagicMock(return_value=mock_job) + + return mock_target + + input_data = { + 'qubits': 3, + 'circuit': [{'gate': 'h', 'targets': [0]}, {'gate': 'h', 'targets': [1]}, {'gate': 'h', 'targets': [2]}], + } + metadata = {'num_qubits': 3, 'meas_map': [0, 1, 2]} + + with pytest.raises(RequestTimeoutError): + _ = send( + input_data=input_data, + metadata=metadata, + num_shots=100, + target=get_mock_target(), + num_retries=1000, + interval=1, + verbose=verbose, + ) diff --git a/projectq/backends/_azure/_azure_quantum_test.py b/projectq/backends/_azure/_azure_quantum_test.py new file mode 100644 index 000000000..3cdc51bb2 --- /dev/null +++ b/projectq/backends/_azure/_azure_quantum_test.py @@ -0,0 +1,862 @@ +# Copyright 2022 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._azure._azure_quantum.py.""" + +from unittest import mock + +import pytest + +from projectq.cengines import BasicMapperEngine, MainEngine +from projectq.ops import CX, All, Command, H, Measure +from projectq.types import WeakQubitRef + +_has_azure_quantum = True +try: + from azure.quantum import Workspace + + import projectq.backends._azure._azure_quantum + from projectq.backends import AzureQuantumBackend + from projectq.backends._azure._exceptions import AzureQuantumTargetNotFoundError +except ImportError: + _has_azure_quantum = False + +has_azure_quantum = pytest.mark.skipif(not _has_azure_quantum, reason="azure quantum package is not installed") + +ZERO_GUID = '00000000-0000-0000-0000-000000000000' + + +def mock_target(target_id, current_availability, average_queue_time): + target = mock.MagicMock() + + estimate_cost = mock.Mock() + estimate_cost.estimated_total = 10 + + target.id = target_id + target.current_availability = current_availability + target.average_queue_time = average_queue_time + target.estimate_cost = mock.MagicMock(return_value=estimate_cost) + + return target + + +def mock_providers(): + ionq_provider = mock.MagicMock() + ionq_provider.id = 'ionq' + ionq_provider.targets = [ + mock_target(target_id='ionq.simulator', current_availability='Available', average_queue_time=1000), + mock_target(target_id='ionq.qpu', current_availability='Available', average_queue_time=2000), + ] + + quantinuum_provider = mock.MagicMock() + quantinuum_provider.id = 'quantinuum' + quantinuum_provider.targets = [ + mock_target(target_id='quantinuum.hqs-lt-s1-apival', current_availability='Available', average_queue_time=3000), + mock_target(target_id='quantinuum.hqs-lt-s1-sim', current_availability='Available', average_queue_time=4000), + mock_target(target_id='quantinuum.hqs-lt-s1', current_availability='Degraded', average_queue_time=5000), + ] + + return [ionq_provider, quantinuum_provider] + + +def mock_target_factory(target_name): + for provider in mock_providers(): + for target in provider.targets: + if target.id == target_name: + return target + + return [] + + +def _get_azure_backend(use_hardware, target_name, retrieve_execution=None): + AzureQuantumBackend._target_factory = mock.MagicMock(return_value=mock_target_factory(target_name)) + + workspace = Workspace( + subscription_id=ZERO_GUID, resource_group='testResourceGroup', name='testWorkspace', location='East US' + ) + + backend = AzureQuantumBackend( + use_hardware=use_hardware, + target_name=target_name, + workspace=workspace, + retrieve_execution=retrieve_execution, + verbose=True, + ) + + return backend + + +def _get_main_engine(backend, max_qubits=3): + mapper = BasicMapperEngine() + + mapping = {} + for i in range(max_qubits): + mapping[i] = i + mapper.current_mapping = mapping + + main_engine = MainEngine(backend=backend, engine_list=[mapper], verbose=True) + + return main_engine + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id, expected_target_name", + [ + (False, 'ionq.simulator', 'ionq', 'ionq.simulator'), + (True, 'ionq.qpu', 'ionq', 'ionq.qpu'), + (False, 'ionq.qpu', 'ionq', 'ionq.simulator'), + ], +) +def test_azure_quantum_ionq_target(use_hardware, target_name, provider_id, expected_target_name): + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + + assert backend._target_name == expected_target_name + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id, expected_target_name", + [ + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum', 'quantinuum.hqs-lt-s1-apival'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum', 'quantinuum.hqs-lt-s1-sim'), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum', 'quantinuum.hqs-lt-s1'), + (False, 'quantinuum.hqs-lt-s1', 'quantinuum', 'quantinuum.hqs-lt-s1-apival'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum', 'quantinuum.hqs-lt-s1-sim'), + ], +) +def test_azure_quantum_quantinuum_target(use_hardware, target_name, provider_id, expected_target_name): + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + + assert backend._target_name == expected_target_name + + +@has_azure_quantum +def test_initialize_azure_backend_using_kwargs(): + backend = AzureQuantumBackend( + use_hardware=False, + target_name='ionq.simulator', + subscription_id=ZERO_GUID, + resource_group='testResourceGroup', + name='testWorkspace', + location='East US', + ) + + assert backend._target_name == 'ionq.simulator' + + +@has_azure_quantum +def test_azure_quantum_invalid_target(): + with pytest.raises(AzureQuantumTargetNotFoundError): + _get_azure_backend(use_hardware=False, target_name='invalid-target') + + +@has_azure_quantum +def test_is_available_ionq(): + with mock.patch('projectq.backends._azure._azure_quantum.is_available_ionq') as is_available_ionq_patch: + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend) + + q0 = main_engine.allocate_qubit() + + cmd = Command(main_engine, H, (q0,)) + main_engine.is_available(cmd) + + is_available_ionq_patch.assert_called() + + +@has_azure_quantum +def test_is_available_quantinuum(): + with mock.patch('projectq.backends._azure._azure_quantum.is_available_quantinuum') as is_available_quantinuum_patch: + backend = _get_azure_backend(use_hardware=False, target_name='quantinuum.hqs-lt-s1-sim') + main_engine = _get_main_engine(backend=backend) + + q0 = main_engine.allocate_qubit() + + cmd = Command(main_engine, H, (q0,)) + main_engine.is_available(cmd) + + is_available_quantinuum_patch.assert_called() + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id, current_availability", + [ + (False, 'ionq.simulator', 'ionq', 'Available'), + (True, 'ionq.qpu', 'ionq', 'Available'), + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum', 'Available'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum', 'Available'), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum', 'Degraded'), + ], +) +def test_current_availability(use_hardware, target_name, provider_id, current_availability): + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + + assert backend.current_availability == current_availability + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id, average_queue_time", + [ + (False, 'ionq.simulator', 'ionq', 1000), + (True, 'ionq.qpu', 'ionq', 2000), + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum', 3000), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum', 4000), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum', 5000), + ], +) +def test_average_queue_time(use_hardware, target_name, provider_id, average_queue_time): + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + + assert backend.average_queue_time == average_queue_time + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [(False, 'ionq.simulator', 'ionq'), (True, 'ionq.qpu', 'ionq')], +) +def test_run_ionq_get_probabilities(use_hardware, target_name, provider_id): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}} + ) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + result = backend.get_probabilities(circuit) + + assert len(result) == 8 + assert result['000'] == pytest.approx(0.5) + assert result['001'] == 0.0 + assert result['010'] == 0.0 + assert result['011'] == 0.0 + assert result['100'] == 0.0 + assert result['101'] == 0.0 + assert result['110'] == 0.0 + assert result['111'] == pytest.approx(0.5) + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [ + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum'), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum'), + ], +) +def test_run_quantinuum_get_probabilities(use_hardware, target_name, provider_id): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={ + 'c': [ + '000', + '000', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '000', + '000', + '000', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '000', + '000', + '000', + '000', + '111', + '111', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + ] + } + ) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + result = backend.get_probabilities(circuit) + + assert len(result) == 2 + assert result['000'] == pytest.approx(0.41) + assert result['111'] == pytest.approx(0.59) + + +@has_azure_quantum +def test_run_get_probabilities_unused_qubit(): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}} + ) + + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend, max_qubits=4) + + circuit = main_engine.allocate_qureg(3) + unused_qubit = main_engine.allocate_qubit() + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + result = backend.get_probabilities(unused_qubit) + + assert len(result) == 1 + assert result['0'] == pytest.approx(1) + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [(False, 'ionq.simulator', 'ionq'), (True, 'ionq.qpu', 'ionq')], +) +def test_run_ionq_get_probability(use_hardware, target_name, provider_id): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}} + ) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + assert backend.get_probability('000', circuit) == pytest.approx(0.5) + assert backend.get_probability('001', circuit) == 0.0 + assert backend.get_probability('010', circuit) == 0.0 + assert backend.get_probability('011', circuit) == 0.0 + assert backend.get_probability('100', circuit) == 0.0 + assert backend.get_probability('101', circuit) == 0.0 + assert backend.get_probability('110', circuit) == 0.0 + assert backend.get_probability('111', circuit) == pytest.approx(0.5) + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [ + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum'), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum'), + ], +) +def test_run_quantinuum_get_probability(use_hardware, target_name, provider_id): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={ + 'c': [ + '000', + '000', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '000', + '000', + '000', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '000', + '000', + '000', + '000', + '111', + '111', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + ] + } + ) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + assert backend.get_probability('000', circuit) == pytest.approx(0.41) + assert backend.get_probability('001', circuit) == 0.0 + assert backend.get_probability('010', circuit) == 0.0 + assert backend.get_probability('011', circuit) == 0.0 + assert backend.get_probability('100', circuit) == 0.0 + assert backend.get_probability('101', circuit) == 0.0 + assert backend.get_probability('110', circuit) == 0.0 + assert backend.get_probability('111', circuit) == pytest.approx(0.59) + + +@has_azure_quantum +def test_estimate_cost(): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}} + ) + + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + estimate_cost = backend.estimate_cost() + + assert estimate_cost.estimated_total == 10 + + +@has_azure_quantum +def test_run_get_probability_invalid_state(): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}} + ) + + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + with pytest.raises(ValueError): + _ = backend.get_probability('0000', circuit) + + +@has_azure_quantum +def test_run_no_circuit(): + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + + main_engine.flush() + + with pytest.raises(RuntimeError): + _ = backend.get_probabilities(circuit) + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [(False, 'ionq.simulator', 'ionq'), (True, 'ionq.qpu', 'ionq')], +) +@pytest.mark.parametrize( + 'retrieve_retval', + (None, {'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}}), + ids=('retrieve-FAIL', 'retrieve-SUCESS'), +) +def test_run_ionq_retrieve_execution(use_hardware, target_name, provider_id, retrieve_retval): + projectq.backends._azure._azure_quantum.retrieve = mock.MagicMock(return_value=retrieve_retval) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name, retrieve_execution=ZERO_GUID) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + if retrieve_retval is None: + with pytest.raises(RuntimeError): + main_engine.flush() + else: + main_engine.flush() + result = backend.get_probabilities(circuit) + + assert len(result) == 8 + assert result['000'] == pytest.approx(0.5) + assert result['001'] == 0.0 + assert result['010'] == 0.0 + assert result['011'] == 0.0 + assert result['100'] == 0.0 + assert result['101'] == 0.0 + assert result['110'] == 0.0 + assert result['111'] == pytest.approx(0.5) + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [ + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum'), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum'), + ], +) +def test_run_quantinuum_retrieve_execution(use_hardware, target_name, provider_id): + projectq.backends._azure._azure_quantum.retrieve = mock.MagicMock( + return_value={ + 'c': [ + '000', + '000', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '000', + '000', + '000', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '000', + '000', + '000', + '000', + '111', + '111', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + ] + } + ) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name, retrieve_execution=ZERO_GUID) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + result = backend.get_probabilities(circuit) + + assert len(result) == 2 + assert result['000'] == pytest.approx(0.41) + assert result['111'] == pytest.approx(0.59) + + +@has_azure_quantum +def test_error_no_logical_id_tag(): + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend) + + q0 = WeakQubitRef(engine=None, idx=0) + + with pytest.raises(RuntimeError): + main_engine.backend._store(Command(engine=main_engine, gate=Measure, qubits=([q0],))) + + +@has_azure_quantum +def test_error_invalid_provider(): + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + backend._provider_id = 'INVALID' # NB: this is forcing it... should actually never happen in practice + main_engine = _get_main_engine(backend=backend) + + q0 = WeakQubitRef(engine=None, idx=0) + + cmd = Command(engine=main_engine, gate=H, qubits=([q0],)) + with pytest.raises(RuntimeError): + main_engine.backend._store(cmd) + + with pytest.raises(RuntimeError): + main_engine.backend._input_data + + assert not main_engine.backend.is_available(cmd) diff --git a/projectq/backends/_azure/_exceptions.py b/projectq/backends/_azure/_exceptions.py new file mode 100644 index 000000000..20f8e65c5 --- /dev/null +++ b/projectq/backends/_azure/_exceptions.py @@ -0,0 +1,19 @@ +# Copyright 2022 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. + +"""Exception classes for projectq.backends._azure.""" + + +class AzureQuantumTargetNotFoundError(Exception): + """Raised when a Azure Quantum target doesn't exist with given target name.""" diff --git a/projectq/backends/_azure/_utils.py b/projectq/backends/_azure/_utils.py new file mode 100644 index 000000000..0031f8438 --- /dev/null +++ b/projectq/backends/_azure/_utils.py @@ -0,0 +1,307 @@ +# Copyright 2022 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. + +"""Utility functions for Azure Quantum.""" + +from projectq.meta import get_control_count, has_negative_control +from projectq.ops import ( + AllocateQubitGate, + BarrierGate, + ControlledGate, + DaggeredGate, + DeallocateQubitGate, + HGate, + MeasureGate, + R, + Rx, + Rxx, + Ry, + Ryy, + Rz, + Rzz, + Sdag, + SGate, + SqrtXGate, + SwapGate, + Tdag, + TGate, + XGate, + YGate, + ZGate, + get_inverse, +) + +from .._exceptions import InvalidCommandError + +IONQ_PROVIDER_ID = 'ionq' +QUANTINUUM_PROVIDER_ID = 'quantinuum' + +# https://docs.ionq.com/#section/Supported-Gates +IONQ_GATE_MAP = { + HGate: 'h', + SGate: 's', + SqrtXGate: 'v', + SwapGate: 'swap', + TGate: 't', + Rx: 'rx', + Rxx: 'xx', + Ry: 'ry', + Ryy: 'yy', + Rz: 'rz', + Rzz: 'zz', + XGate: 'x', + YGate: 'y', + ZGate: 'z', +} # excluding controlled, conjugate-transpose and meta gates + +IONQ_SUPPORTED_GATES = tuple(IONQ_GATE_MAP.keys()) + +QUANTINUUM_GATE_MAP = { + BarrierGate: 'barrier', + HGate: 'h', + Rx: 'rx', + Rxx: 'rxx', + Ry: 'ry', + Ryy: 'ryy', + Rz: 'rz', + Rzz: 'rzz', + SGate: 's', + TGate: 't', + XGate: 'x', + YGate: 'y', + ZGate: 'z', +} # excluding controlled, conjugate-transpose and meta gates + +QUANTINUUM_SUPPORTED_GATES = tuple(QUANTINUUM_GATE_MAP.keys()) + +V = SqrtXGate() +Vdag = get_inverse(V) + + +def is_available_ionq(cmd): + """ + Test if IonQ 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 + + if has_negative_control(cmd): + return False + + if isinstance(gate, ControlledGate): + num_ctrl_qubits = gate._n # pylint: disable=protected-access + else: + num_ctrl_qubits = get_control_count(cmd) + + # Get base gate wrapped in ControlledGate class + if isinstance(gate, ControlledGate): + gate = gate._gate # pylint: disable=protected-access + + # NOTE: IonQ supports up to 7 control qubits + if 0 < num_ctrl_qubits <= 7: + return isinstance(gate, (XGate,)) + + # Gates without control bits + if num_ctrl_qubits == 0: + supported = isinstance(gate, IONQ_SUPPORTED_GATES) + supported_meta = isinstance(gate, (MeasureGate, AllocateQubitGate, DeallocateQubitGate)) + supported_transpose = gate in (Sdag, Tdag, Vdag) + + return supported or supported_meta or supported_transpose + + return False + + +def is_available_quantinuum(cmd): + """ + Test if Quantinuum 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 + + if has_negative_control(cmd): + return False + + if isinstance(gate, ControlledGate): + num_ctrl_qubits = gate._n # pylint: disable=protected-access + else: + num_ctrl_qubits = get_control_count(cmd) + + # Get base gate wrapped in ControlledGate class + if isinstance(gate, ControlledGate): + gate = gate._gate # pylint: disable=protected-access + + # TODO: NEEDED CONFIRMATION- Does Quantinuum support more than 2 control gates? + if 0 < num_ctrl_qubits <= 2: + return isinstance(gate, (XGate, ZGate)) + + # Gates without control bits. + if num_ctrl_qubits == 0: + supported = isinstance(gate, QUANTINUUM_SUPPORTED_GATES) + supported_meta = isinstance(gate, (MeasureGate, AllocateQubitGate, DeallocateQubitGate, BarrierGate)) + supported_transpose = gate in (Sdag, Tdag) + return supported or supported_meta or supported_transpose + + return False + + +def to_json(cmd): + """ + Convert ProjectQ command to JSON format. + + Args: + cmd (Command): A command to process. + + Returns: + dict: JSON format of given command. + """ + # Invalid command, raise exception + if not is_available_ionq(cmd): + raise InvalidCommandError('Invalid command:', str(cmd)) + + gate = cmd.gate + + if isinstance(gate, ControlledGate): + inner_gate = gate._gate # pylint: disable=protected-access + gate_type = type(inner_gate) + elif isinstance(gate, DaggeredGate): + gate_type = type(gate.get_inverse()) + else: + gate_type = type(gate) + + gate_name = IONQ_GATE_MAP.get(gate_type) + + # Daggered gates get special treatment + if isinstance(gate, DaggeredGate): + gate_name = gate_name + 'i' + + # Controlled gates get special treatment too + if isinstance(gate, ControlledGate): + all_qubits = [qb.id for qureg in cmd.qubits for qb in qureg] + controls = all_qubits[: gate._n] # pylint: disable=protected-access + targets = all_qubits[gate._n :] # noqa: E203 # pylint: disable=protected-access + else: + controls = [qb.id for qb in cmd.control_qubits] + targets = [qb.id for qureg in cmd.qubits for qb in qureg] + + # 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 + + return gate_dict + + +def to_qasm(cmd): # pylint: disable=too-many-return-statements,too-many-branches + """ + Convert ProjectQ command to QASM format. + + Args: + cmd (Command): A command to process. + + Returns: + dict: QASM format of given command. + """ + # Invalid command, raise exception + if not is_available_quantinuum(cmd): + raise InvalidCommandError('Invalid command:', str(cmd)) + + gate = cmd.gate + + if isinstance(gate, ControlledGate): + inner_gate = gate._gate # pylint: disable=protected-access + gate_type = type(inner_gate) + elif isinstance(gate, DaggeredGate): + gate_type = type(gate.get_inverse()) + else: + gate_type = type(gate) + + gate_name = QUANTINUUM_GATE_MAP.get(gate_type) + + # Daggered gates get special treatment + if isinstance(gate, DaggeredGate): + gate_name = gate_name + 'dg' + + # Controlled gates get special treatment too + if isinstance(gate, ControlledGate): + all_qubits = [qb.id for qureg in cmd.qubits for qb in qureg] + controls = all_qubits[: gate._n] # pylint: disable=protected-access + targets = all_qubits[gate._n :] # noqa: E203 # pylint: disable=protected-access + else: + controls = [qb.id for qb in cmd.control_qubits] + targets = [qb.id for qureg in cmd.qubits for qb in qureg] + + # Barrier gate + if isinstance(gate, BarrierGate): + qb_str = "" + for pos in targets: + qb_str += f"q[{pos}], " + return f"{gate_name} {qb_str[:-2]};" + + # Daggered gates + if gate in (Sdag, Tdag): + return f"{gate_name} q[{targets[0]}];" + + # Controlled gates + if len(controls) > 0: + # 1-Controlled gates + if len(controls) == 1: + gate_name = 'c' + gate_name + return f"{gate_name} q[{controls[0]}], q[{targets[0]}];" + + # 2-Controlled gates + if len(controls) == 2: + gate_name = 'cc' + gate_name + return f"{gate_name} q[{controls[0]}], q[{controls[1]}], q[{targets[0]}];" + + raise InvalidCommandError('Invalid command:', str(cmd)) # pragma: no cover + + # Single qubit gates + if len(targets) == 1: + # Standard gates + if isinstance(gate, (HGate, XGate, YGate, ZGate, SGate, TGate)): + return f"{gate_name} q[{targets[0]}];" + + # Rotational gates + if isinstance(gate, (Rx, Ry, Rz)): + return f"{gate_name}({gate.angle}) q[{targets[0]}];" + + raise InvalidCommandError('Invalid command:', str(cmd)) # pragma: no cover + + # Two qubit gates + if len(targets) == 2: + # Rotational gates + if isinstance(gate, (Rxx, Ryy, Rzz)): + return f"{gate_name}({gate.angle}) q[{targets[0]}], q[{targets[1]}];" + + raise InvalidCommandError('Invalid command:', str(cmd)) + + # Invalid command + raise InvalidCommandError('Invalid command:', str(cmd)) # pragma: no cover diff --git a/projectq/backends/_azure/_utils_test.py b/projectq/backends/_azure/_utils_test.py new file mode 100644 index 000000000..8fdf70b48 --- /dev/null +++ b/projectq/backends/_azure/_utils_test.py @@ -0,0 +1,642 @@ +# Copyright 2022 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._azure._utils.py.""" + +import math + +import pytest + +from projectq.cengines import DummyEngine, MainEngine +from projectq.ops import ( + CNOT, + CX, + NOT, + Allocate, + Barrier, + C, + Command, + Deallocate, + H, + Measure, + Rx, + Rxx, + Ry, + Ryy, + Rz, + Rzz, + S, + Sdag, + Sdagger, + SqrtX, + SqrtXGate, + Swap, + T, + Tdag, + Tdagger, + X, + Y, + Z, + get_inverse, +) +from projectq.types import WeakQubitRef + +from .._exceptions import InvalidCommandError + +_has_azure_quantum = True +try: + import azure.quantum # noqa: F401 + + from projectq.backends._azure._utils import ( + is_available_ionq, + is_available_quantinuum, + to_json, + to_qasm, + ) +except ImportError: + _has_azure_quantum = False + +has_azure_quantum = pytest.mark.skipif(not _has_azure_quantum, reason="azure quantum package is not installed") + +V = SqrtXGate() +Vdag = get_inverse(V) + + +@has_azure_quantum +@pytest.mark.parametrize( + "single_qubit_gate, expected_result", + [ + (NOT, True), + (X, True), + (Y, True), + (Z, True), + (H, True), + (S, True), + (T, True), + (SqrtX, True), + (Rx(math.pi / 2), True), + (Ry(math.pi / 2), True), + (Rz(math.pi / 2), True), + (Sdag, True), + (Sdagger, True), + (Tdag, True), + (Tdagger, True), + (Vdag, True), + (Measure, True), + (Allocate, True), + (Deallocate, True), + (Barrier, False), + ], +) +def test_ionq_is_available_single_qubit_gates(single_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + + cmd = Command(eng, single_qubit_gate, (qb0,)) + assert is_available_ionq(cmd) == expected_result, f'Failing on {single_qubit_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "two_qubit_gate, expected_result", + [ + (Swap, True), + (CNOT, True), + (CX, True), + (Rxx(math.pi / 2), True), + (Ryy(math.pi / 2), True), + (Rzz(math.pi / 2), True), + ], +) +def test_ionq_is_available_two_qubit_gates(two_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + + cmd = Command(eng, two_qubit_gate, (qb0, qb1)) + assert is_available_ionq(cmd) == expected_result, f'Failing on {two_qubit_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, True), + (X, 1, True), + (X, 2, True), + (X, 3, True), + (X, 4, True), + (X, 5, True), + (X, 6, True), + (X, 7, True), + (X, 8, False), + (Y, 1, False), + ], +) +def test_ionq_is_available_n_controlled_qubits_type_1(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + # pass controls as parameter + cmd = Command(eng, base_gate, (qb0,), controls=qureg) + assert is_available_ionq(cmd) == expected_result, 'Failing on {}-controlled {} gate'.format( + num_ctrl_qubits, base_gate + ) + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, True), + (X, 1, True), + (X, 2, True), + (X, 3, True), + (X, 4, True), + (X, 5, True), + (X, 6, True), + (X, 7, True), + (X, 8, False), + (Y, 1, False), + ], +) +def test_ionq_is_available_n_controlled_qubits_type_2(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + n_controlled_gate = base_gate + for index in range(num_ctrl_qubits): + n_controlled_gate = C(n_controlled_gate) + + # pass controls as targets + cmd = Command( + eng, + n_controlled_gate, + ( + qureg, + qb0, + ), + ) + assert is_available_ionq(cmd) == expected_result, 'Failing on {}-controlled {} gate'.format( + num_ctrl_qubits, base_gate + ) + + +@has_azure_quantum +def test_ionq_is_available_negative_control(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg) + assert is_available_ionq(cmd), "Failing on negative controlled gate" + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg, control_state='1') + assert is_available_ionq(cmd), "Failing on negative controlled gate" + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg, control_state='0') + assert not is_available_ionq(cmd), "Failing on negative controlled gate" + + +@has_azure_quantum +@pytest.mark.parametrize( + "single_qubit_gate, expected_result", + [ + (NOT, True), + (X, True), + (Y, True), + (Z, True), + (H, True), + (S, True), + (T, True), + (Rx(math.pi / 2), True), + (Ry(math.pi / 2), True), + (Rz(math.pi / 2), True), + (Sdag, True), + (Sdagger, True), + (Tdag, True), + (Tdagger, True), + (Measure, True), + (Allocate, True), + (Deallocate, True), + (Barrier, True), + (SqrtX, False), + (Vdag, False), + ], +) +def test_quantinuum_is_available_single_qubit_gates(single_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + + cmd = Command(eng, single_qubit_gate, (qb0,)) + assert is_available_quantinuum(cmd) == expected_result, f'Failing on {single_qubit_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "two_qubit_gate, expected_result", + [ + (CNOT, True), + (CX, True), + (Rxx(math.pi / 2), True), + (Ryy(math.pi / 2), True), + (Rzz(math.pi / 2), True), + (Swap, False), + ], +) +def test_quantinuum_is_available_two_qubit_gates(two_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + + cmd = Command(eng, two_qubit_gate, (qb0, qb1)) + assert is_available_quantinuum(cmd) == expected_result, f'Failing on {two_qubit_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, True), + (X, 1, True), + (X, 2, True), + (X, 3, False), + (Z, 0, True), + (Z, 1, True), + (Z, 2, True), + (Z, 3, False), + (Y, 1, False), + ], +) +def test_quantinuum_is_available_n_controlled_qubits_type_1(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + cmd = Command(eng, base_gate, (qb0,), controls=qureg) + assert is_available_quantinuum(cmd) == expected_result, 'Failing on {}-controlled {} gate'.format( + num_ctrl_qubits, base_gate + ) + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, True), + (X, 1, True), + (X, 2, True), + (X, 3, False), + (Z, 0, True), + (Z, 1, True), + (Z, 2, True), + (Z, 3, False), + (Y, 1, False), + ], +) +def test_quantinuum_is_available_n_controlled_qubits_type_2(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + n_controlled_gate = base_gate + for index in range(num_ctrl_qubits): + n_controlled_gate = C(n_controlled_gate) + + # pass controls as targets + cmd = Command( + eng, + n_controlled_gate, + ( + qureg, + qb0, + ), + ) + assert is_available_quantinuum(cmd) == expected_result, 'Failing on {}-controlled {} gate'.format( + num_ctrl_qubits, base_gate + ) + + +@has_azure_quantum +def test_quantinuum_is_available_negative_control(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg) + assert is_available_quantinuum(cmd), "Failing on negative controlled gate" + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg, control_state='1') + assert is_available_quantinuum(cmd), "Failing on negative controlled gate" + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg, control_state='0') + assert not is_available_quantinuum(cmd), "Failing on negative controlled gate" + + +@has_azure_quantum +@pytest.mark.parametrize( + "single_qubit_gate, expected_result", + [ + (NOT, {'gate': 'x', 'targets': [0]}), + (X, {'gate': 'x', 'targets': [0]}), + (Y, {'gate': 'y', 'targets': [0]}), + (Z, {'gate': 'z', 'targets': [0]}), + (H, {'gate': 'h', 'targets': [0]}), + (S, {'gate': 's', 'targets': [0]}), + (T, {'gate': 't', 'targets': [0]}), + (Rx(0), {'gate': 'rx', 'rotation': 0.0, 'targets': [0]}), + (Ry(0), {'gate': 'ry', 'rotation': 0.0, 'targets': [0]}), + (Rz(0), {'gate': 'rz', 'rotation': 0.0, 'targets': [0]}), + (Rx(math.pi / 4), {'gate': 'rx', 'rotation': 0.785398163397, 'targets': [0]}), + (Ry(math.pi / 4), {'gate': 'ry', 'rotation': 0.785398163397, 'targets': [0]}), + (Rz(math.pi / 4), {'gate': 'rz', 'rotation': 0.785398163397, 'targets': [0]}), + (Rx(math.pi / 2), {'gate': 'rx', 'rotation': 1.570796326795, 'targets': [0]}), + (Ry(math.pi / 2), {'gate': 'ry', 'rotation': 1.570796326795, 'targets': [0]}), + (Rz(math.pi / 2), {'gate': 'rz', 'rotation': 1.570796326795, 'targets': [0]}), + (Rx(math.pi), {'gate': 'rx', 'rotation': 3.14159265359, 'targets': [0]}), + (Ry(math.pi), {'gate': 'ry', 'rotation': 3.14159265359, 'targets': [0]}), + (Rz(math.pi), {'gate': 'rz', 'rotation': 3.14159265359, 'targets': [0]}), + (Sdag, {'gate': 'si', 'targets': [0]}), + (Sdagger, {'gate': 'si', 'targets': [0]}), + (Tdag, {'gate': 'ti', 'targets': [0]}), + (Tdagger, {'gate': 'ti', 'targets': [0]}), + (SqrtX, {'gate': 'v', 'targets': [0]}), + (Vdag, {'gate': 'vi', 'targets': [0]}), + ], +) +def test_to_json_single_qubit_gates(single_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = WeakQubitRef(engine=eng, idx=0) + + actual_result = to_json(Command(eng, single_qubit_gate, ([qb0],))) + + assert len(actual_result) == len(expected_result) + assert actual_result['gate'] == expected_result['gate'] + assert actual_result['targets'] == expected_result['targets'] + if 'rotation' in expected_result: + assert actual_result['rotation'] == pytest.approx(expected_result['rotation']) + + +@has_azure_quantum +@pytest.mark.parametrize( + "two_qubit_gate, expected_result", + [ + (Swap, {'gate': 'swap', 'targets': [0, 1]}), + (CNOT, {'gate': 'x', 'targets': [1], 'controls': [0]}), + (CX, {'gate': 'x', 'targets': [1], 'controls': [0]}), + (Rxx(0), {'gate': 'xx', 'rotation': 0.0, 'targets': [0, 1]}), + (Ryy(0), {'gate': 'yy', 'rotation': 0.0, 'targets': [0, 1]}), + (Rzz(0), {'gate': 'zz', 'rotation': 0.0, 'targets': [0, 1]}), + (Rxx(math.pi / 4), {'gate': 'xx', 'rotation': 0.785398163397, 'targets': [0, 1]}), + (Ryy(math.pi / 4), {'gate': 'yy', 'rotation': 0.785398163397, 'targets': [0, 1]}), + (Rzz(math.pi / 4), {'gate': 'zz', 'rotation': 0.785398163397, 'targets': [0, 1]}), + (Rxx(math.pi / 2), {'gate': 'xx', 'rotation': 1.570796326795, 'targets': [0, 1]}), + (Ryy(math.pi / 2), {'gate': 'yy', 'rotation': 1.570796326795, 'targets': [0, 1]}), + (Rzz(math.pi / 2), {'gate': 'zz', 'rotation': 1.570796326795, 'targets': [0, 1]}), + (Rxx(math.pi), {'gate': 'xx', 'rotation': 3.14159265359, 'targets': [0, 1]}), + (Ryy(math.pi), {'gate': 'yy', 'rotation': 3.14159265359, 'targets': [0, 1]}), + (Rzz(math.pi), {'gate': 'zz', 'rotation': 3.14159265359, 'targets': [0, 1]}), + ], +) +def test_to_json_two_qubit_gates(two_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = WeakQubitRef(engine=eng, idx=0) + qb1 = WeakQubitRef(engine=eng, idx=1) + + actual_result = to_json(Command(eng, two_qubit_gate, ([qb0], [qb1]))) + + assert len(actual_result) == len(expected_result) + assert actual_result['gate'] == expected_result['gate'] + assert actual_result['targets'] == expected_result['targets'] + if 'rotation' in expected_result: + assert actual_result['rotation'] == pytest.approx(expected_result['rotation']) + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, {'gate': 'x', 'targets': [0]}), + (X, 1, {'gate': 'x', 'targets': [0], 'controls': [1]}), + (X, 2, {'gate': 'x', 'targets': [0], 'controls': [1, 2]}), + (X, 3, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3]}), + (X, 4, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4]}), + (X, 5, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5]}), + (X, 6, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5, 6]}), + (X, 7, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5, 6, 7]}), + ], +) +def test_to_json_n_controlled_qubits_type_1(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + cmd = Command(eng, base_gate, (qb0,), controls=qureg) + assert to_json(cmd) == expected_result, f'Failing on {num_ctrl_qubits}-controlled {base_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, {'gate': 'x', 'targets': [0]}), + (X, 1, {'gate': 'x', 'targets': [0], 'controls': [1]}), + (X, 2, {'gate': 'x', 'targets': [0], 'controls': [1, 2]}), + (X, 3, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3]}), + (X, 4, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4]}), + (X, 5, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5]}), + (X, 6, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5, 6]}), + (X, 7, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5, 6, 7]}), + ], +) +def test_to_json_n_controlled_qubits_type_2(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + n_controlled_gate = base_gate + for index in range(num_ctrl_qubits): + n_controlled_gate = C(n_controlled_gate) + + # pass controls as targets + cmd = Command( + eng, + n_controlled_gate, + ( + qureg, + qb0, + ), + ) + assert to_json(cmd) == expected_result, f'Failing on {num_ctrl_qubits}-controlled {base_gate} gate' + + +@has_azure_quantum +def test_to_json_invalid_command_gate_not_available(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + + cmd = Command(eng, Barrier, (qb0,)) + with pytest.raises(InvalidCommandError): + to_json(cmd) + + +@has_azure_quantum +@pytest.mark.parametrize( + "single_qubit_gate, expected_result", + [ + (NOT, 'x q[0];'), + (X, 'x q[0];'), + (Y, 'y q[0];'), + (Z, 'z q[0];'), + (H, 'h q[0];'), + (S, 's q[0];'), + (T, 't q[0];'), + (Rx(0), 'rx(0.0) q[0];'), + (Ry(0), 'ry(0.0) q[0];'), + (Rz(0), 'rz(0.0) q[0];'), + (Rx(math.pi / 4), 'rx(0.785398163397) q[0];'), + (Ry(math.pi / 4), 'ry(0.785398163397) q[0];'), + (Rz(math.pi / 4), 'rz(0.785398163397) q[0];'), + (Rx(math.pi / 2), 'rx(1.570796326795) q[0];'), + (Ry(math.pi / 2), 'ry(1.570796326795) q[0];'), + (Rz(math.pi / 2), 'rz(1.570796326795) q[0];'), + (Rx(math.pi), 'rx(3.14159265359) q[0];'), + (Ry(math.pi), 'ry(3.14159265359) q[0];'), + (Rz(math.pi), 'rz(3.14159265359) q[0];'), + (Sdag, 'sdg q[0];'), + (Sdagger, 'sdg q[0];'), + (Tdag, 'tdg q[0];'), + (Tdagger, 'tdg q[0];'), + ], +) +def test_to_qasm_single_qubit_gates(single_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = WeakQubitRef(engine=eng, idx=0) + + assert to_qasm(Command(eng, single_qubit_gate, ([qb0],))) == expected_result + + +@has_azure_quantum +@pytest.mark.parametrize( + "two_qubit_gate, expected_result", + [ + (CNOT, 'cx q[0], q[1];'), + (CX, 'cx q[0], q[1];'), + (Rxx(0), 'rxx(0.0) q[0], q[1];'), + (Ryy(0), 'ryy(0.0) q[0], q[1];'), + (Rzz(0), 'rzz(0.0) q[0], q[1];'), + (Rxx(math.pi / 4), 'rxx(0.785398163397) q[0], q[1];'), + (Ryy(math.pi / 4), 'ryy(0.785398163397) q[0], q[1];'), + (Rzz(math.pi / 4), 'rzz(0.785398163397) q[0], q[1];'), + (Rxx(math.pi / 2), 'rxx(1.570796326795) q[0], q[1];'), + (Ryy(math.pi / 2), 'ryy(1.570796326795) q[0], q[1];'), + (Rzz(math.pi / 2), 'rzz(1.570796326795) q[0], q[1];'), + (Rxx(math.pi), 'rxx(3.14159265359) q[0], q[1];'), + (Ryy(math.pi), 'ryy(3.14159265359) q[0], q[1];'), + (Rzz(math.pi), 'rzz(3.14159265359) q[0], q[1];'), + ], +) +def test_to_qasm_two_qubit_gates(two_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = WeakQubitRef(engine=eng, idx=0) + qb1 = WeakQubitRef(engine=eng, idx=1) + + assert to_qasm(Command(eng, two_qubit_gate, ([qb0], [qb1]))) == expected_result + + +@has_azure_quantum +@pytest.mark.parametrize( + "n_qubit_gate, n, expected_result", + [ + (Barrier, 2, 'barrier q[0], q[1];'), + (Barrier, 3, 'barrier q[0], q[1], q[2];'), + (Barrier, 4, 'barrier q[0], q[1], q[2], q[3];'), + ], +) +def test_to_qasm_n_qubit_gates(n_qubit_gate, n, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qureg = eng.allocate_qureg(n) + + assert to_qasm(Command(eng, n_qubit_gate, (qureg,))) == expected_result + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, 'x q[0];'), + (X, 1, 'cx q[1], q[0];'), + (X, 2, 'ccx q[1], q[2], q[0];'), + (Z, 0, 'z q[0];'), + (Z, 1, 'cz q[1], q[0];'), + (Z, 2, 'ccz q[1], q[2], q[0];'), + ], +) +def test_to_qasm_n_controlled_qubits_type_1(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + cmd = Command(eng, base_gate, (qb0,), controls=qureg) + assert to_qasm(cmd) == expected_result, f'Failing on {num_ctrl_qubits}-controlled {base_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, 'x q[0];'), + (X, 1, 'cx q[1], q[0];'), + (X, 2, 'ccx q[1], q[2], q[0];'), + (Z, 0, 'z q[0];'), + (Z, 1, 'cz q[1], q[0];'), + (Z, 2, 'ccz q[1], q[2], q[0];'), + ], +) +def test_to_qasm_n_controlled_qubits_type_2(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + n_controlled_gate = base_gate + for index in range(num_ctrl_qubits): + n_controlled_gate = C(n_controlled_gate) + + # pass controls as targets + cmd = Command( + eng, + n_controlled_gate, + ( + qureg, + qb0, + ), + ) + assert to_qasm(cmd) == expected_result, f'Failing on {num_ctrl_qubits}-controlled {base_gate} gate' + + +@has_azure_quantum +def test_to_qasm_invalid_command_gate_not_available(): + qb0 = WeakQubitRef(None, idx=0) + qb1 = WeakQubitRef(None, idx=1) + + cmd = Command(None, SqrtX, qubits=((qb0,),)) + with pytest.raises(InvalidCommandError): + to_qasm(cmd) + + # NB: unsupported gate for 2 qubits + cmd = Command(None, X, qubits=((qb0, qb1),)) + with pytest.raises(InvalidCommandError): + to_qasm(cmd) diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py index 089b26ce4..392864461 100644 --- a/projectq/backends/_ionq/_ionq.py +++ b/projectq/backends/_ionq/_ionq.py @@ -46,6 +46,7 @@ from projectq.types import WeakQubitRef from .._exceptions import InvalidCommandError, MidCircuitMeasurementError +from .._utils import _rearrange_result from . import _ionq_http_client as http_client GATE_MAP = { @@ -67,20 +68,6 @@ 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): # pylint: disable=too-many-instance-attributes """Backend for building circuits and submitting them to the IonQ API.""" diff --git a/projectq/backends/_utils.py b/projectq/backends/_utils.py new file mode 100644 index 000000000..2c33068ae --- /dev/null +++ b/projectq/backends/_utils.py @@ -0,0 +1,28 @@ +# Copyright 2022 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 utility functions.""" + + +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``. + """ + return f'{input_result:0{length}b}'[::-1] diff --git a/projectq/backends/_utils_test.py b/projectq/backends/_utils_test.py new file mode 100644 index 000000000..f2dddcd9b --- /dev/null +++ b/projectq/backends/_utils_test.py @@ -0,0 +1,35 @@ +# Copyright 2022 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._utils.py.""" + +import pytest + +from ._utils import _rearrange_result + + +@pytest.mark.parametrize( + "input_result, length, expected_result", + [ + (5, 3, '101'), + (5, 4, '1010'), + (5, 5, '10100'), + (16, 5, '00001'), + (16, 6, '000010'), + (63, 6, '111111'), + (63, 7, '1111110'), + ], +) +def test_rearrange_result(input_result, length, expected_result): + assert expected_result == _rearrange_result(input_result, length) diff --git a/pyproject.toml b/pyproject.toml index 82f728b54..5e0559d69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,10 @@ dependencies = [ [project.optional-dependencies] +azure-quantum = [ + 'azure-quantum' +] + braket = [ 'boto3' ] From e73397f78df8610b72cca7bc263a1f52499a53d4 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Fri, 14 Oct 2022 07:55:18 +0200 Subject: [PATCH 094/113] Fix installation on Apple Silicon and older Python versions (#444) --- CHANGELOG.md | 4 ++++ setup.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7a5f625b..bbce61d83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for Python 3.6 and earlier is now deprecated - Moved package metadata into pyproject.toml +### Fixed + +- Fixed installation on Apple Silicon with older Python versions (< 3.9) + ### Repository - Update `docker/setup-qemu-action` GitHub action to v2 diff --git a/setup.py b/setup.py index 9d23f1b60..32426d594 100755 --- a/setup.py +++ b/setup.py @@ -523,6 +523,17 @@ def _configure_intrinsics(self): '/arch:AVX', ] + if int(os.environ.get('PROJECTQ_NOINTRIN', '0')) or ( + sys.platform == 'darwin' + and platform.machine() == 'arm64' + and (sys.version_info.major == 3 and sys.version_info.minor < 9) + ): + important_msgs( + 'Either requested no-intrinsics or detected potentially unsupported Python version on ' + 'Apple Silicon: disabling intrinsics' + ) + self.compiler.define_macro('NOINTRIN') + return if os.environ.get('PROJECTQ_DISABLE_ARCH_NATIVE'): flags = flags[1:] From 009c880f26fe5f616cfadc1b0d1d4e49b8fc8375 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:43:53 +0200 Subject: [PATCH 095/113] Release version v0.8.0 (#446) * Merge master into develop branch (#436) * Preparing release vv0.7.2 * Fix missed bug in optimize.py * Fix typo in _optimize.py * Fix linters/formatters warnings * Update missed hooks * Hotfix: fix GitHub action workflows * Release version v0.7.3 (#435) * Merge master into develop branch (#431) * Preparing release vv0.7.2 * Fix missed bug in optimize.py * Fix typo in _optimize.py * Fix linters/formatters warnings * Update missed hooks * Hotfix: fix GitHub action workflows Co-authored-by: GitHub actions Co-authored-by: Nguyen Damien * IonQ API: Move to v0.2, and fixup backends path (#433) * Move to v0.2, and fixup backends path The previous path was mistakenly incorrect, could we release this as a patch release of project-Q? Happy to help how I can. * changelog * fix * One more fixup * remove urljoin * fmt * Preparing release v0.7.3 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub actions Co-authored-by: Nguyen Damien Co-authored-by: Jon Donovan Co-authored-by: GitHub actions Co-authored-by: Nguyen Damien Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Jon Donovan * Bump docker/setup-qemu-action from 1 to 2 (#437) * Bump docker/setup-qemu-action from 1 to 2 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 1 to 2. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v1...v2) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update CHANGELOG * Fix CentOS 7 build Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nguyen Damien * Fix setuptools-scm version for Python 3.6 (#440) * Add `blacken-docs` and `pyupgrade` to pre-commit configuration (#441) * Add `blacken-docs` and `pyupgrade` to pre-commit configuration * Apply new pre-commit hooks and fix related issues. * Update CHANGELOG * Fix unit tests failures * Fix pre-commit failure * Generalise the use of f-strings * Fix IonQ tests * Run `pyupgrade` before `isort` and `black` * Deprecate support for Python 3.6 (#442) * Azure Quantum backend (#439) * Add Azure Quantum backend template files * Add implementation of Azure Quantum * Add support for Honeywell and improve exception handling * Fixes and reformatting * Fixes and Improvements * Fixes and Improvements * Fixes and Improvements * Fixes and Improvements * Fix QASM transulation * Fixes and Improvements * Documentation related changes * Fix typos * Add docstrings to util methods * Fix typos * Add Unittests * Add Unittests * Add Unittests & rename honeywell to quantinuum * Fixes for QSAM convector * Add Unittests * Add Unittests * Add Unittests * Add Unittests * Fix Quantinuum Backend for Azure Quantum * Add Unittests * Add Unittests * Add Unittests * Add Unittests * Fix Vdag gate for IonQ backend * Minor updates * Update Docs * Add Azure Quantum example * Update README.rst * Update Docs * Update Docs * Update Unittests * Azure Quantum: CI Fixes (#4) * Azure Quantum: CI Fixes * Fix optional dependency problem * Update pre-commit reformatting * Fix pylint issues * Fix pylint issues * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Resolve comments (#5) * Resolve comments * Remove rearrange_result method from _utils.py * Update with pytest.approx for assert floating numbers * Pytest skipif testcases for Azure Quantum (#6) * Pytest skipif testcases for Azure Quantum * Fix projectq.backends._azure._exceptions import * Add common utils module (#7) * Create common utils module * Optimize _rearrange_result method * Make sure pip, setuptools and wheel packages are up to date on CI * Fix formatting issues from latest changes in develop branch * Improve coveralls (#8) * Improve coveralls * Improve coveralls * Rename util.py to utils.py * Minor fixes * Update testcases * Update testcases * Improve coveralls (#9) * Improve coveralls * Improve coveralls * Precommit reformatting * Improve coveralls (#10) * Improve coveralls * Precommit formatting * Fix CI failures * Avoid a few more #pragma: no cover * Fix flake8 warning * Remove one more # pragma: no cover * Reformat file Co-authored-by: Nguyen Damien * Fix installation on Apple Silicon and older Python versions (#444) * Preparing release v0.8.0 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub actions Co-authored-by: Nguyen Damien Co-authored-by: Jon Donovan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sai Seshu Chadaram --- .github/workflows/ci.yml | 33 +- .github/workflows/publish_release.yml | 2 +- .pre-commit-config.yaml | 14 +- .travis.yml | 1 + CHANGELOG.md | 25 +- README.rst | 86 +- docs/conf.py | 6 +- docs/package_description.py | 25 +- docs/tutorials.rst | 15 +- examples/aqt.py | 3 +- examples/azure-quantum.ipynb | 264 ++++++ examples/bellpair_circuit.py | 1 - examples/control_tester.py | 1 - examples/gate_zoo.py | 7 +- examples/grover.py | 1 - examples/hws4.py | 3 +- examples/hws6.py | 3 +- examples/ibm.py | 3 +- examples/ibmq_tutorial.ipynb | 2 +- examples/ionq.py | 3 +- examples/ionq_bv.py | 3 +- examples/ionq_half_adder.py | 1 - examples/mapper_tutorial.ipynb | 20 +- examples/quantum_random_numbers.py | 3 +- examples/quantum_random_numbers_ibm.py | 3 +- examples/shor.py | 15 +- examples/simulator_tutorial.ipynb | 32 +- examples/spectral_measurement.ipynb | 2 +- examples/teleport.py | 3 +- examples/teleport_circuit.py | 1 - examples/unitary_simulator.py | 1 - projectq/__init__.py | 1 - projectq/backends/__init__.py | 3 +- projectq/backends/_aqt/__init__.py | 1 - projectq/backends/_aqt/_aqt.py | 22 +- projectq/backends/_aqt/_aqt_http_client.py | 20 +- .../backends/_aqt/_aqt_http_client_test.py | 1 - projectq/backends/_aqt/_aqt_test.py | 1 - projectq/backends/_awsbraket/__init__.py | 1 - projectq/backends/_awsbraket/_awsbraket.py | 17 +- .../_awsbraket/_awsbraket_boto3_client.py | 34 +- .../_awsbraket_boto3_client_test.py | 5 +- .../_awsbraket_boto3_client_test_fixtures.py | 1 - .../backends/_awsbraket/_awsbraket_test.py | 4 +- .../_awsbraket/_awsbraket_test_fixtures.py | 1 - projectq/backends/_azure/__init__.py | 27 + projectq/backends/_azure/_azure_quantum.py | 387 ++++++++ .../backends/_azure/_azure_quantum_client.py | 96 ++ .../_azure/_azure_quantum_client_test.py | 420 +++++++++ .../backends/_azure/_azure_quantum_test.py | 862 ++++++++++++++++++ projectq/backends/_azure/_exceptions.py | 19 + projectq/backends/_azure/_utils.py | 307 +++++++ projectq/backends/_azure/_utils_test.py | 642 +++++++++++++ projectq/backends/_circuits/__init__.py | 1 - projectq/backends/_circuits/_drawer.py | 77 +- .../backends/_circuits/_drawer_matplotlib.py | 10 +- .../_circuits/_drawer_matplotlib_test.py | 34 +- projectq/backends/_circuits/_drawer_test.py | 28 +- projectq/backends/_circuits/_plot.py | 10 +- projectq/backends/_circuits/_plot_test.py | 13 +- projectq/backends/_circuits/_to_latex.py | 172 ++-- projectq/backends/_circuits/_to_latex_test.py | 9 +- projectq/backends/_exceptions.py | 1 - projectq/backends/_ibm/__init__.py | 1 - projectq/backends/_ibm/_ibm.py | 22 +- projectq/backends/_ibm/_ibm_http_client.py | 20 +- .../backends/_ibm/_ibm_http_client_test.py | 45 +- projectq/backends/_ibm/_ibm_test.py | 1 - projectq/backends/_ionq/__init__.py | 1 - projectq/backends/_ionq/_ionq.py | 26 +- projectq/backends/_ionq/_ionq_http_client.py | 37 +- .../backends/_ionq/_ionq_http_client_test.py | 1 - projectq/backends/_ionq/_ionq_mapper.py | 5 +- projectq/backends/_ionq/_ionq_mapper_test.py | 1 - projectq/backends/_ionq/_ionq_test.py | 1 - projectq/backends/_printer.py | 6 +- projectq/backends/_printer_test.py | 11 +- projectq/backends/_resource.py | 1 - projectq/backends/_resource_test.py | 3 +- projectq/backends/_sim/__init__.py | 1 - .../backends/_sim/_classical_simulator.py | 1 - .../_sim/_classical_simulator_test.py | 1 - projectq/backends/_sim/_pysim.py | 5 +- projectq/backends/_sim/_simulator.py | 10 +- projectq/backends/_sim/_simulator_test.py | 5 +- .../backends/_sim/_simulator_test_fixtures.py | 1 - projectq/backends/_unitary.py | 3 +- projectq/backends/_unitary_test.py | 3 +- projectq/backends/_utils.py | 28 + projectq/backends/_utils_test.py | 35 + projectq/cengines/__init__.py | 1 - projectq/cengines/_basicmapper.py | 1 - projectq/cengines/_basicmapper_test.py | 1 - projectq/cengines/_basics.py | 7 +- projectq/cengines/_basics_test.py | 1 - projectq/cengines/_cmdmodifier.py | 3 +- projectq/cengines/_cmdmodifier_test.py | 1 - projectq/cengines/_ibm5qubitmapper.py | 1 - projectq/cengines/_ibm5qubitmapper_test.py | 1 - projectq/cengines/_linearmapper.py | 3 +- projectq/cengines/_linearmapper_test.py | 1 - projectq/cengines/_main.py | 21 +- projectq/cengines/_main_test.py | 1 - projectq/cengines/_manualmapper.py | 1 - projectq/cengines/_manualmapper_test.py | 1 - projectq/cengines/_optimize.py | 1 - projectq/cengines/_optimize_test.py | 1 - projectq/cengines/_replacer/__init__.py | 1 - .../cengines/_replacer/_decomposition_rule.py | 1 - .../_replacer/_decomposition_rule_set.py | 4 +- .../_replacer/_decomposition_rule_test.py | 1 - projectq/cengines/_replacer/_replacer.py | 5 +- projectq/cengines/_replacer/_replacer_test.py | 1 - projectq/cengines/_swapandcnotflipper.py | 7 +- projectq/cengines/_swapandcnotflipper_test.py | 1 - projectq/cengines/_tagremover.py | 3 +- projectq/cengines/_tagremover_test.py | 3 +- projectq/cengines/_testengine.py | 7 +- projectq/cengines/_testengine_test.py | 6 +- projectq/cengines/_twodmapper.py | 1 - projectq/cengines/_twodmapper_test.py | 1 - projectq/libs/__init__.py | 1 - projectq/libs/hist/__init__.py | 1 - projectq/libs/hist/_histogram.py | 3 +- projectq/libs/hist/_histogram_test.py | 1 - projectq/libs/math/__init__.py | 1 - projectq/libs/math/_constantmath.py | 1 - projectq/libs/math/_constantmath_test.py | 1 - projectq/libs/math/_default_rules.py | 1 - projectq/libs/math/_gates.py | 93 +- projectq/libs/math/_gates_math_test.py | 3 +- projectq/libs/math/_gates_test.py | 1 - projectq/libs/math/_quantummath.py | 1 - projectq/libs/math/_quantummath_test.py | 3 +- projectq/libs/revkit/__init__.py | 1 - projectq/libs/revkit/_control_function.py | 5 +- .../libs/revkit/_control_function_test.py | 1 - projectq/libs/revkit/_permutation.py | 1 - projectq/libs/revkit/_permutation_test.py | 1 - projectq/libs/revkit/_phase.py | 5 +- projectq/libs/revkit/_phase_test.py | 1 - projectq/libs/revkit/_utils.py | 1 - projectq/meta/__init__.py | 1 - projectq/meta/_compute.py | 7 +- projectq/meta/_compute_test.py | 5 +- projectq/meta/_control.py | 14 +- projectq/meta/_control_test.py | 3 +- projectq/meta/_dagger.py | 8 +- projectq/meta/_dagger_test.py | 1 - projectq/meta/_dirtyqubit.py | 1 - projectq/meta/_dirtyqubit_test.py | 1 - projectq/meta/_exceptions.py | 1 - projectq/meta/_logicalqubit.py | 1 - projectq/meta/_logicalqubit_test.py | 1 - projectq/meta/_loop.py | 12 +- projectq/meta/_loop_test.py | 1 - projectq/meta/_util.py | 1 - projectq/meta/_util_test.py | 1 - projectq/ops/__init__.py | 1 - projectq/ops/_basics.py | 37 +- projectq/ops/_basics_test.py | 3 +- projectq/ops/_command.py | 7 +- projectq/ops/_command_test.py | 7 +- projectq/ops/_gates.py | 10 +- projectq/ops/_gates_test.py | 1 - projectq/ops/_metagates.py | 29 +- projectq/ops/_metagates_test.py | 11 +- projectq/ops/_qaagate.py | 15 +- projectq/ops/_qaagate_test.py | 1 - projectq/ops/_qftgate.py | 1 - projectq/ops/_qftgate_test.py | 1 - projectq/ops/_qpegate.py | 3 +- projectq/ops/_qpegate_test.py | 1 - projectq/ops/_qubit_operator.py | 26 +- projectq/ops/_qubit_operator_test.py | 1 - projectq/ops/_shortcuts.py | 1 - projectq/ops/_shortcuts_test.py | 1 - projectq/ops/_state_prep.py | 3 +- projectq/ops/_state_prep_test.py | 1 - projectq/ops/_time_evolution.py | 3 +- projectq/ops/_time_evolution_test.py | 1 - .../ops/_uniformly_controlled_rotation.py | 17 +- .../_uniformly_controlled_rotation_test.py | 1 - projectq/setups/__init__.py | 1 - projectq/setups/_utils.py | 1 - projectq/setups/aqt.py | 1 - projectq/setups/aqt_test.py | 1 - projectq/setups/awsbraket.py | 3 +- projectq/setups/awsbraket_test.py | 1 - projectq/setups/decompositions/__init__.py | 1 - projectq/setups/decompositions/_gates_test.py | 1 - .../decompositions/amplitudeamplification.py | 15 +- .../amplitudeamplification_test.py | 11 +- .../decompositions/arb1qubit2rzandry.py | 41 +- .../decompositions/arb1qubit2rzandry_test.py | 1 - projectq/setups/decompositions/barrier.py | 1 - .../setups/decompositions/barrier_test.py | 1 - .../decompositions/carb1qubit2cnotrzandry.py | 13 +- .../carb1qubit2cnotrzandry_test.py | 6 +- projectq/setups/decompositions/cnot2cz.py | 1 - .../setups/decompositions/cnot2cz_test.py | 1 - projectq/setups/decompositions/cnot2rxx.py | 1 - .../setups/decompositions/cnot2rxx_test.py | 1 - .../setups/decompositions/cnu2toffoliandcu.py | 1 - .../decompositions/cnu2toffoliandcu_test.py | 1 - .../setups/decompositions/controlstate.py | 1 - .../decompositions/controlstate_test.py | 1 - projectq/setups/decompositions/crz2cxandrz.py | 1 - projectq/setups/decompositions/entangle.py | 1 - projectq/setups/decompositions/globalphase.py | 1 - projectq/setups/decompositions/h2rx.py | 1 - projectq/setups/decompositions/h2rx_test.py | 1 - projectq/setups/decompositions/ph2r.py | 1 - .../setups/decompositions/phaseestimation.py | 25 +- .../decompositions/phaseestimation_test.py | 21 +- .../decompositions/qft2crandhadamard.py | 1 - .../setups/decompositions/qubitop2onequbit.py | 1 - .../decompositions/qubitop2onequbit_test.py | 3 +- projectq/setups/decompositions/r2rzandph.py | 1 - projectq/setups/decompositions/rx2rz.py | 1 - projectq/setups/decompositions/rx2rz_test.py | 1 - projectq/setups/decompositions/ry2rz.py | 1 - projectq/setups/decompositions/ry2rz_test.py | 1 - projectq/setups/decompositions/rz2rx.py | 1 - projectq/setups/decompositions/rz2rx_test.py | 1 - .../setups/decompositions/sqrtswap2cnot.py | 1 - .../decompositions/sqrtswap2cnot_test.py | 1 - .../setups/decompositions/stateprep2cnot.py | 1 - .../decompositions/stateprep2cnot_test.py | 1 - projectq/setups/decompositions/swap2cnot.py | 1 - .../setups/decompositions/time_evolution.py | 1 - .../decompositions/time_evolution_test.py | 1 - .../decompositions/toffoli2cnotandtgate.py | 1 - .../uniformlycontrolledr2cnot.py | 1 - .../uniformlycontrolledr2cnot_test.py | 3 +- projectq/setups/default.py | 1 - projectq/setups/grid.py | 1 - projectq/setups/grid_test.py | 1 - projectq/setups/ibm.py | 1 - projectq/setups/ibm_test.py | 1 - projectq/setups/ionq.py | 3 +- projectq/setups/ionq_test.py | 3 +- projectq/setups/linear.py | 1 - projectq/setups/linear_test.py | 1 - projectq/setups/restrictedgateset.py | 1 - projectq/setups/restrictedgateset_test.py | 1 - projectq/setups/trapped_ion_decomposer.py | 1 - .../setups/trapped_ion_decomposer_test.py | 1 - projectq/tests/__init__.py | 1 - projectq/tests/_factoring_test.py | 1 - projectq/types/__init__.py | 1 - projectq/types/_qubit.py | 6 +- projectq/types/_qubit_test.py | 3 +- pyproject.toml | 77 +- setup.cfg | 58 +- setup.py | 161 +++- 256 files changed, 4013 insertions(+), 1037 deletions(-) create mode 100644 examples/azure-quantum.ipynb create mode 100644 projectq/backends/_azure/__init__.py create mode 100644 projectq/backends/_azure/_azure_quantum.py create mode 100644 projectq/backends/_azure/_azure_quantum_client.py create mode 100644 projectq/backends/_azure/_azure_quantum_client_test.py create mode 100644 projectq/backends/_azure/_azure_quantum_test.py create mode 100644 projectq/backends/_azure/_exceptions.py create mode 100644 projectq/backends/_azure/_utils.py create mode 100644 projectq/backends/_azure/_utils_test.py create mode 100644 projectq/backends/_utils.py create mode 100644 projectq/backends/_utils_test.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a5939e11..ffda8c62b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,6 @@ jobs: matrix: runs-on: [ubuntu-latest, windows-latest, macos-latest] python: - - 3.6 - 3.7 - 3.8 - 3.9 @@ -55,15 +54,17 @@ jobs: - name: Generate requirement file (Unix) if: runner.os != 'Windows' run: | - python setup.py gen_reqfile --include-extras=test,braket,revkit + python setup.py gen_reqfile --include-extras=test,azure-quantum,braket,revkit - name: Generate requirement file (Windows) if: runner.os == 'Windows' run: | - python setup.py gen_reqfile --include-extras=test,braket + python setup.py gen_reqfile --include-extras=test,azure-quantum,braket - name: Prepare env run: | + python -m pip install -U pip setuptools wheel + cat requirements.txt python -m pip install -r requirements.txt --prefer-binary python -m pip install coveralls @@ -73,11 +74,11 @@ jobs: - name: Build and install package (Unix) if: runner.os != 'Windows' - run: python -m pip install -ve .[braket,revkit,test] + run: python -m pip install -ve .[azure-quantum,braket,revkit,test] - name: Build and install package (Windows) if: runner.os == 'Windows' - run: python -m pip install -ve .[braket,test] + run: python -m pip install -ve .[azure-quantum,braket,test] - name: Pytest run: | @@ -142,14 +143,16 @@ jobs: - name: Prepare Python env run: | - python3 setup.py gen_reqfile --include-extras=test,braket + python3 -m pip install -U pip setuptools wheel + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + cat requirements.txt python3 -m pip install -r requirements.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,test] + run: python3 -m pip install -ve .[azure-quantum,braket,test] - name: Pytest run: | @@ -187,14 +190,16 @@ jobs: - name: Prepare Python env run: | - python3 setup.py gen_reqfile --include-extras=test,braket + python3 -m pip install -U pip setuptools wheel + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + cat requirements.txt python3 -m pip install -r requirements.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,test] + run: python3 -m pip install -ve .[azure-quantum,braket,test] - name: Pytest run: | @@ -242,7 +247,9 @@ jobs: run: yum -y install https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm - name: Install Git > 2.18 - run: yum install -y git + run: | + yum install -y git + git config --global --add safe.directory /__w/ProjectQ/ProjectQ - uses: actions/checkout@v2 @@ -267,11 +274,13 @@ jobs: - name: Install dependencies run: | - python3 setup.py gen_reqfile --include-extras=test,braket + python3 -m pip install -U pip setuptools wheel + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + cat requirements.txt python3 -m pip install -r requirements.txt --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[braket,test] + run: python3 -m pip install -ve .[azure-quantum,braket,test] - name: Pytest run: | diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 0849ee1fa..ff4bcd221 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Set up QEMU if: matrix.cibw_archs == 'aarch64' - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 with: platforms: arm64 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d74edd3f..7b1ba27a8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,7 +35,6 @@ repos: - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace - - id: fix-encoding-pragma # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks @@ -43,6 +42,12 @@ repos: hooks: - id: remove-tabs + - repo: https://github.com/asottile/pyupgrade + rev: v2.34.0 + hooks: + - id: pyupgrade + args: [--py37-plus, --keep-mock] + - repo: https://github.com/PyCQA/isort rev: 5.10.1 hooks: @@ -57,6 +62,13 @@ repos: # This is a slow hook, so only run this if --hook-stage manual is passed stages: [manual] + - repo: https://github.com/asottile/blacken-docs + rev: v1.12.1 + hooks: + - id: blacken-docs + args: [-S, -l, '120'] + additional_dependencies: [black==22.3.0] + - repo: https://gitlab.com/PyCQA/flake8 rev: 3.9.2 hooks: diff --git a/.travis.yml b/.travis.yml index 0dd408af0..b4ab7159f 100755 --- a/.travis.yml +++ b/.travis.yml @@ -60,6 +60,7 @@ install: - env - python3 -m pip install -U pip setuptools wheel - python3 -m pip install -U pybind11 dormouse revkit flaky pytest-cov coveralls boto3 + - python3 -m pip install -U azure-quantum - python3 -m pip install -r requirements.txt - python3 -m pip install -ve . diff --git a/CHANGELOG.md b/CHANGELOG.md index 31e3d2685..5e8a3f9d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.8.0] - 2022-10-18 + +### Added + +- New backend for the Azure Quantum platform + +### Changed + +- Support for Python 3.6 and earlier is now deprecated +- Moved package metadata into pyproject.toml + +### Fixed + +- Fixed installation on Apple Silicon with older Python versions (< 3.9) + +### Repository + +- Update `docker/setup-qemu-action` GitHub action to v2 +- Fixed CentOS 7 configuration issue +- Added two new pre-commit hooks: `blacken-docs` and `pyupgrade` + ## [v0.7.3] - 2022-04-27 ### Fixed @@ -190,7 +211,9 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi 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/v0.7.3...HEAD +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.8.0...HEAD + +[v0.8.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.3...v0.8.0 [v0.7.3]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.2...v0.7.3 diff --git a/README.rst b/README.rst index 3e24d7332..a2c794bb0 100755 --- a/README.rst +++ b/README.rst @@ -27,7 +27,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, AWS Braket, or IonQ service provided devices +- run quantum programs on the IBM Quantum Experience chip, AQT devices, AWS Braket, Azure Quantum, 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 @@ -43,7 +43,10 @@ Examples .. 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) + 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 a quantum register with 1 qubit @@ -52,7 +55,7 @@ Examples Measure | qubit # measure the qubit eng.flush() # flush all gates (and execute measurements) - print("Measured {}".format(int(qubit))) # converting a qubit to int or bool gives access to the measurement result + print(f"Measured {int(qubit)}") # converting a qubit to int or bool gives access to the measurement result ProjectQ features a lean syntax which is close to the mathematical notation used in quantum physics. For example, a rotation of a qubit around the x-axis is usually specified as: @@ -80,9 +83,7 @@ Instead of simulating a quantum program, one can use our resource counter (as a from projectq.ops import QFT from projectq.setups import linear - compiler_engines = linear.get_engine_list(num_qubits=16, - one_qubit_gates='any', - two_qubit_gates=(CNOT, Swap)) + compiler_engines = linear.get_engine_list(num_qubits=16, one_qubit_gates='any', two_qubit_gates=(CNOT, Swap)) resource_counter = ResourceCounter() eng = MainEngine(backend=resource_counter, engine_list=compiler_engines) qureg = eng.allocate_qureg(16) @@ -112,12 +113,13 @@ 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) - eng = MainEngine(IBMBackend(token=token, use_hardware=True, num_runs=1024, - verbose=False, device=device), - engine_list=compiler_engines) + token = 'MY_TOKEN' + device = 'ibmq_16_melbourne' + compiler_engines = projectq.setups.ibm.get_engine_list(token=token, device=device) + eng = MainEngine( + IBMBackend(token=token, use_hardware=True, num_runs=1024, verbose=False, device=device), + engine_list=compiler_engines, + ) **Running a quantum program on AQT devices** @@ -129,12 +131,13 @@ 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) - eng = MainEngine(AQTBackend(token=token,use_hardware=True, num_runs=1024, - verbose=False, device=device), - engine_list=compiler_engines) + token = 'MY_TOKEN' + device = 'aqt_device' + compiler_engines = projectq.setups.aqt.get_engine_list(token=token, device=device) + eng = MainEngine( + AQTBackend(token=token, use_hardware=True, num_runs=1024, verbose=False, device=device), + engine_list=compiler_engines, + ) **Running a quantum program on a AWS Braket provided device** @@ -150,13 +153,21 @@ IonQ from IonQ and the state vector simulator SV1: 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=[]) + device = 'IonQ' + eng = MainEngine( + AWSBraketBackend( + use_hardware=True, + credentials=creds, + s3_folder=s3_folder, + num_runs=1024, + verbose=False, + device=device, + ), + engine_list=[], + ) .. note:: @@ -175,6 +186,35 @@ IonQ from IonQ and the state vector simulator SV1: python3 -m pip install -ve .[braket] +**Running a quantum program on a Azure Quantum provided device** + +To run a program on devices provided by the `Azure Quantum `_. + +Use `AzureQuantumBackend` to run ProjectQ circuits on hardware devices and simulator devices from providers `IonQ` and `Quantinuum`. + +.. code-block:: python + + from projectq.backends import AzureQuantumBackend + + azure_quantum_backend = AzureQuantumBackend( + use_hardware=False, target_name='ionq.simulator', resource_id='', location='', verbose=True + ) + +.. note:: + + In order to use the AzureQuantumBackend, you need to install ProjectQ with the 'azure-quantum' extra requirement: + + .. code-block:: bash + + python3 -m pip install projectq[azure-quantum] + + or + + .. code-block:: bash + + cd /path/to/projectq/source/code + python3 -m pip install -ve .[azure-quantum] + **Running a quantum program on IonQ devices** To run a program on the IonQ trapped ion hardware, use the `IonQBackend` and its corresponding setup. diff --git a/docs/conf.py b/docs/conf.py index 4b2b99fa2..a9d5efc09 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # projectq documentation build configuration file, created by # sphinx-quickstart on Tue Nov 29 11:51:46 2016. @@ -533,6 +532,7 @@ def linkcode_resolve(domain, info): import projectq.setups.ibm as ibm_setup from projectq import MainEngine + eng = MainEngine(engine_list=ibm_setup.get_engine_list()) # eng uses the default Simulator backend @@ -560,10 +560,10 @@ def linkcode_resolve(domain, info): os.mkdir(docgen_path) for desc in descriptions: - fname = os.path.join(docgen_path, 'projectq.{}.rst'.format(desc.name)) + fname = os.path.join(docgen_path, f'projectq.{desc.name}.rst') lines = None if os.path.exists(fname): - with open(fname, 'r') as fd: + with open(fname) as fd: lines = [line[:-1] for line in fd.readlines()] new_lines = desc.get_ReST() diff --git a/docs/package_description.py b/docs/package_description.py index fcfbb5b21..d04ba67c3 100644 --- a/docs/package_description.py +++ b/docs/package_description.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,7 +51,7 @@ def __init__( # pylint: disable=too-many-arguments if pkg_name not in PackageDescription.package_list: PackageDescription.package_list.append(pkg_name) - self.module = importlib.import_module('projectq.{}'.format(self.name)) + self.module = importlib.import_module(f'projectq.{self.name}') self.module_special_members = module_special_members self.submodule_special_members = submodule_special_members @@ -72,7 +71,7 @@ def __init__( # pylint: disable=too-many-arguments self.subpackages = [] self.submodules = [] for name, obj in sub: - if '{}.{}'.format(self.name, name) in PackageDescription.package_list: + if f'{self.name}.{name}' in PackageDescription.package_list: self.subpackages.append((name, obj)) else: self.submodules.append((name, obj)) @@ -115,7 +114,7 @@ def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-s new_lines.append(' :maxdepth: 1') new_lines.append('') for name, _ in self.subpackages: - new_lines.append(' projectq.{}.{}'.format(self.name, name)) + new_lines.append(f' projectq.{self.name}.{name}') new_lines.append('') else: submodule_has_index = True @@ -123,11 +122,11 @@ def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-s new_lines.append('') if self.submodules: for name, _ in self.submodules: - new_lines.append('\tprojectq.{}.{}'.format(self.name, name)) + new_lines.append(f'\tprojectq.{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(f'\tprojectq.{self.name}.{name}') new_lines.append('') if self.submodules: @@ -142,27 +141,27 @@ def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-s new_lines.append('.. autosummary::') new_lines.append('') for name, _ in self.submodules: - new_lines.append(' projectq.{}.{}'.format(self.name, name)) + new_lines.append(f' projectq.{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(f'.. automodule:: projectq.{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(f' :special-members: {self.submodule_special_members}') new_lines.append(' :undoc-members:') new_lines.append('') new_lines.append('Module contents') new_lines.append('-' * len(new_lines[-1])) new_lines.append('') - new_lines.append('.. automodule:: projectq.{}'.format(self.name)) + new_lines.append(f'.. automodule:: projectq.{self.name}') new_lines.append(' :members:') new_lines.append(' :undoc-members:') - new_lines.append(' :special-members: {}'.format(self.module_special_members)) + new_lines.append(f' :special-members: {self.module_special_members}') new_lines.append(' :imported-members:') new_lines.append('') @@ -174,9 +173,9 @@ def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-s new_lines.append(title) new_lines.append('^' * len(title)) new_lines.append('') - new_lines.append('.. automodule:: projectq.{}.{}'.format(self.name, name)) + new_lines.append(f'.. automodule:: projectq.{self.name}.{name}') for param in params: - new_lines.append(' {}'.format(param)) + new_lines.append(f' {param}') new_lines.append('') return new_lines[:-1] diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 58d8de1c9..bcc7e2d67 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -54,6 +54,14 @@ AWS Braket Backend requires the use of the official AWS SDK for Python, Boto3. T python -m pip install --user projectq[braket] +**Install Azure Quantum Backend requirement** + +Azure Quantum Backend requires the use of the official `Azure Quantum SDK `_ for Python. This is an extra requirement only needed if you plan to use the Azure Quantum 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[azure-quantum] + Detailed instructions and OS-specific hints ------------------------------------------- @@ -239,7 +247,10 @@ 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) + 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 @@ -248,6 +259,6 @@ To check out the ProjectQ syntax in action and to see whether the installation w Measure | qubit # measure the qubit eng.flush() # flush all gates (and execute measurements) - print("Measured {}".format(int(qubit))) # output measurement result + print(f"Measured {int(qubit)}") # output measurement result Which creates random bits (0 or 1). diff --git a/examples/aqt.py b/examples/aqt.py index 68da13606..1be765189 100644 --- a/examples/aqt.py +++ b/examples/aqt.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of running a quantum circuit using the AQT APIs.""" @@ -40,7 +39,7 @@ def run_entangle(eng, num_qubits=3): # 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])) + # print(f"Measured {state} with p = {results[state]}.") # or plot them directly: histogram(eng.backend, qureg) plt.show() diff --git a/examples/azure-quantum.ipynb b/examples/azure-quantum.ipynb new file mode 100644 index 000000000..bd126815d --- /dev/null +++ b/examples/azure-quantum.ipynb @@ -0,0 +1,264 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2f4b2787-f008-4331-83ee-e743c64017aa", + "metadata": {}, + "source": [ + "# Azure Quantum Backend" + ] + }, + { + "cell_type": "markdown", + "id": "2bc9b3a4-23d8-4638-b875-386295010a3d", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "- An Azure account with active subcription.\n", + "- An Azure Quantum workspace. ([How to create this?](https://docs.microsoft.com/en-us/azure/quantum/how-to-create-workspace?tabs=tabid-quick))\n", + "- Resource ID and location of Azure Quantum workspace.\n", + "- Install Azure Quantum dependencies for ProjectQ.\n", + "```\n", + "python -m pip install --user projectq[azure-quantum]\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "e8e87193-fc98-4718-a62d-cbbde662b82f", + "metadata": {}, + "source": [ + "## Load Imports\n", + "\n", + "Run following cell to load requried imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a239f2e4-dcc1-4c9f-b8db-0a775a9e2863", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from projectq import MainEngine\n", + "from projectq.ops import H, CX, All, Measure\n", + "from projectq.cengines import BasicMapperEngine\n", + "from projectq.backends import AzureQuantumBackend\n", + "from projectq.libs.hist import histogram" + ] + }, + { + "cell_type": "markdown", + "id": "e98cedd2-3d13-4b81-8776-59ef74a0ca2c", + "metadata": {}, + "source": [ + "## Initialize Azure Quantum backend\n", + "\n", + "Update `resource_id` and `location` of your Azure Quantum workspace in below cell and run to initialize Azure Quantum workspace.\n", + "\n", + "Following are valid `target_names`:\n", + "- ionq.simulator\n", + "- ionq.qpu\n", + "- quantinuum.hqs-lt-s1-apival\n", + "- quantinuum.hqs-lt-s1-sim\n", + "- quantinuum.hqs-lt-s1\n", + "\n", + "Flag `use_hardware` represents wheather or not use real hardware or just a simulator. If False regardless of the value of `target_name`, simulator will be used." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e160b42-039e-4d5b-8333-7169bc77c57f", + "metadata": {}, + "outputs": [], + "source": [ + "azure_quantum_backend = AzureQuantumBackend(\n", + " use_hardware=False,\n", + " target_name='ionq.simulator',\n", + " resource_id=\"\", # resource id of workspace\n", + " location=\"\", # location of workspace\n", + " verbose=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "321876bd-dbbf-446d-9732-3627037d49f0", + "metadata": {}, + "source": [ + "## Create ProjectQ Engine\n", + "\n", + "Initialize ProjectQ `MainEngine` using Azure Quantum as backend." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6fbc7650-fa8a-4047-8739-ee79aa2e6c2a", + "metadata": {}, + "outputs": [], + "source": [ + "mapper = BasicMapperEngine()\n", + "max_qubits = 3\n", + "\n", + "mapping = {}\n", + "for i in range(max_qubits):\n", + " mapping[i] = i\n", + "\n", + "mapper.current_mapping = mapping\n", + "\n", + "main_engine = MainEngine(\n", + " backend=azure_quantum_backend,\n", + " engine_list=[mapper]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d2665b04-0903-4176-b62d-b8fed5d5e041", + "metadata": {}, + "source": [ + "## Create circuit using ProjectQ lean syntax!\n", + "\n", + "Allocate qubits, build circuit and measure qubits." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a8e84c3-cf1c-44db-80f1-e0a229a38eb9", + "metadata": {}, + "outputs": [], + "source": [ + "circuit = main_engine.allocate_qureg(3)\n", + "q0, q1, q2 = circuit\n", + "\n", + "H | q0\n", + "CX | (q0, q1)\n", + "CX | (q1, q2)\n", + "All(Measure) | circuit" + ] + }, + { + "cell_type": "markdown", + "id": "4a9901e8-ed74-4859-a004-8f1b9b72dd45", + "metadata": {}, + "source": [ + "## Run circuit and get result\n", + "\n", + "Flush down circuit to Azure Quantum backend and wait for results. It prints `job-id` for the reference (in case this operation timed-out). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9af5ea10-1edc-4fe9-9d8d-c021c196779d", + "metadata": {}, + "outputs": [], + "source": [ + "main_engine.flush()\n", + "\n", + "print(azure_quantum_backend.get_probabilities(circuit))" + ] + }, + { + "cell_type": "markdown", + "id": "b7dfc230-d4fc-4460-8acb-c42ebccc1659", + "metadata": {}, + "source": [ + "## Timed out! Re-run the circuit with retrieve_execution argument\n", + "\n", + "If job execution timed-out, use `retrieve_execution` argument retrive result instead of re-running the circuit. Use `job-id` from previous cell." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b05001b5-25ca-4dfd-a165-123ac7e83b38", + "metadata": {}, + "outputs": [], + "source": [ + "azure_quantum_backend = AzureQuantumBackend(\n", + " use_hardware=False,\n", + " target_name='ionq.simulator',\n", + " resource_id=\"\", # resource id of workspace\n", + " location=\"\", # location of workspace\n", + " retrieve_execution=\"\", # job-id of Azure Quantum job\n", + " verbose=True\n", + ")\n", + "\n", + "mapper = BasicMapperEngine()\n", + "max_qubits = 10\n", + "\n", + "mapping = {}\n", + "for i in range(max_qubits):\n", + " mapping[i] = i\n", + "\n", + "mapper.current_mapping = mapping\n", + "\n", + "main_engine = MainEngine(\n", + " backend=azure_quantum_backend,\n", + " engine_list=[mapper]\n", + ")\n", + "\n", + "circuit = main_engine.allocate_qureg(3)\n", + "q0, q1, q2 = circuit\n", + "\n", + "H | q0\n", + "CX | (q0, q1)\n", + "CX | (q1, q2)\n", + "All(Measure) | circuit\n", + "\n", + "main_engine.flush()\n", + "\n", + "print(azure_quantum_backend.get_probabilities(circuit))" + ] + }, + { + "cell_type": "markdown", + "id": "b658cb79-15c8-4e25-acbd-77788af08f9e", + "metadata": {}, + "source": [ + "# Plot Histogram\n", + "\n", + "Now, let's plot histogram with above result." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68b3fa8d-a06d-4049-8350-d179304bf045", + "metadata": {}, + "outputs": [], + "source": [ + "histogram(main_engine.backend, circuit)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:projectq] *", + "language": "python", + "name": "conda-env-projectq-py" + }, + "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.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/bellpair_circuit.py b/examples/bellpair_circuit.py index c4343f801..611e1ffe5 100755 --- a/examples/bellpair_circuit.py +++ b/examples/bellpair_circuit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example implementation of a quantum circuit generating a Bell pair state.""" diff --git a/examples/control_tester.py b/examples/control_tester.py index 8f9463735..3175404a7 100755 --- a/examples/control_tester.py +++ b/examples/control_tester.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/examples/gate_zoo.py b/examples/gate_zoo.py index c8e97ed97..684addfa4 100644 --- a/examples/gate_zoo.py +++ b/examples/gate_zoo.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Showcase most of the quantum gates available in ProjectQ.""" @@ -95,12 +94,12 @@ def add(x, y): # generate latex code to draw the circuit s = drawing_engine.get_latex() prefix = 'zoo' - with open('{}.tex'.format(prefix), 'w') as f: + with open(f'{prefix}.tex', 'w') as f: f.write(s) # compile latex source code and open pdf file - os.system('pdflatex {}.tex'.format(prefix)) - openfile('{}.pdf'.format(prefix)) + os.system(f'pdflatex {prefix}.tex') + openfile(f'{prefix}.pdf') def openfile(filename): diff --git a/examples/grover.py b/examples/grover.py index 539fc525a..1d1511db1 100755 --- a/examples/grover.py +++ b/examples/grover.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example implementation of Grover's algorithm.""" diff --git a/examples/hws4.py b/examples/hws4.py index 8ba5b6174..4225480b2 100644 --- a/examples/hws4.py +++ b/examples/hws4.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of a 4-qubit phase function.""" @@ -30,4 +29,4 @@ def f(a, b, c, d): eng.flush() -print("Shift is {}".format(8 * int(x4) + 4 * int(x3) + 2 * int(x2) + int(x1))) +print(f"Shift is {8 * int(x4) + 4 * int(x3) + 2 * int(x2) + int(x1)}") diff --git a/examples/hws6.py b/examples/hws6.py index bf5540d07..4aa9a30e4 100644 --- a/examples/hws6.py +++ b/examples/hws6.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of a 6-qubit phase function.""" @@ -44,4 +43,4 @@ def f(a, b, c, d, e, f): All(Measure) | qubits # measurement result -print("Shift is {}".format(sum(int(q) << i for i, q in enumerate(qubits)))) +print(f"Shift is {sum(int(q) << i for i, q in enumerate(qubits))}") diff --git a/examples/ibm.py b/examples/ibm.py index 24bd0c097..ce19e5491 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of running a quantum circuit using the IBM QE APIs.""" @@ -40,7 +39,7 @@ def run_entangle(eng, num_qubits=3): # 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])) + # print(f"Measured {state} with p = {results[state]}.") # or plot them directly: histogram(eng.backend, qureg) plt.show() diff --git a/examples/ibmq_tutorial.ipynb b/examples/ibmq_tutorial.ipynb index 358bdcf9e..9721b71fb 100644 --- a/examples/ibmq_tutorial.ipynb +++ b/examples/ibmq_tutorial.ipynb @@ -133,7 +133,7 @@ " # access the probabilities via the back-end:\n", " # results = eng.backend.get_probabilities(qureg)\n", " # for state in results:\n", - " # print(\"Measured {} with p = {}.\".format(state, results[state]))\n", + " # print(f\"Measured {state} with p = {results[state]}.\")\n", " # or plot them directly:\n", " histogram(eng.backend, qureg)\n", " plt.show()\n", diff --git a/examples/ionq.py b/examples/ionq.py index 71f0c6831..472f45281 100644 --- a/examples/ionq.py +++ b/examples/ionq.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,7 +52,7 @@ def run_entangle(eng, num_qubits=3): # 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])) + # print(f"Measured {state} with p = {results[state]}.") # or plot them directly: histogram(eng.backend, qureg) plt.show() diff --git a/examples/ionq_bv.py b/examples/ionq_bv.py index afaf48c78..5cbe839fa 100644 --- a/examples/ionq_bv.py +++ b/examples/ionq_bv.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +36,7 @@ def oracle(qureg, input_size, s): def run_bv_circuit(eng, input_size, s_int): """Run the quantum circuit.""" - s = ('{0:0' + str(input_size) + 'b}').format(s_int) + s = f"{s_int:0{input_size}b}" print("Secret string: ", s) print("Number of qubits: ", str(input_size + 1)) circuit = eng.allocate_qureg(input_size + 1) diff --git a/examples/ionq_half_adder.py b/examples/ionq_half_adder.py index 905fa38e9..2efbe7a45 100644 --- a/examples/ionq_half_adder.py +++ b/examples/ionq_half_adder.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/examples/mapper_tutorial.ipynb b/examples/mapper_tutorial.ipynb index 5027819b2..b5c50881d 100644 --- a/examples/mapper_tutorial.ipynb +++ b/examples/mapper_tutorial.ipynb @@ -361,9 +361,9 @@ "\n", "# Remember that allocate_qubit returns a quantum register (Qureg) of size 1,\n", "# so accessing the qubit requires qubit[0]\n", - "print(\"This logical qubit0 has the unique ID: {}\".format(qubit0[0].id))\n", - "print(\"This logical qubit1 has the unique ID: {}\".format(qubit1[0].id))\n", - "print(\"This logical qubit2 has the unique ID: {}\".format(qubit2[0].id)) \n", + "print(f\"This logical qubit0 has the unique ID: {qubit0[0].id}\")\n", + "print(f\"This logical qubit1 has the unique ID: {qubit1[0].id}\")\n", + "print(f\"This logical qubit2 has the unique ID: {qubit2[0].id}\") \n", "\n", "eng4.flush()" ] @@ -400,9 +400,9 @@ "# current_mapping is a dictionary with keys being the\n", "# logical qubit ids and the values being the physical ids on\n", "# on the linear chain\n", - "print(\"Physical location of qubit0: {}\".format(current_mapping[qubit0[0].id]))\n", - "print(\"Physical location of qubit1: {}\".format(current_mapping[qubit1[0].id]))\n", - "print(\"Physical location of qubit2: {}\".format(current_mapping[qubit2[0].id]))" + "print(f\"Physical location of qubit0: {current_mapping[qubit0[0].id]}\")\n", + "print(f\"Physical location of qubit1: {current_mapping[qubit1[0].id]}\")\n", + "print(f\"Physical location of qubit2: {current_mapping[qubit2[0].id]}\")" ] }, { @@ -435,9 +435,9 @@ "eng4.flush()\n", "# Get current mapping:\n", "current_mapping = eng4.mapper.current_mapping\n", - "print(\"\\nPhysical location of qubit0: {}\".format(current_mapping[qubit0[0].id]))\n", - "print(\"Physical location of qubit1: {}\".format(current_mapping[qubit1[0].id]))\n", - "print(\"Physical location of qubit2: {}\".format(current_mapping[qubit2[0].id]))" + "print(f\"\\nPhysical location of qubit0: {current_mapping[qubit0[0].id]}\")\n", + "print(f\"Physical location of qubit1: {current_mapping[qubit1[0].id]}\")\n", + "print(f\"Physical location of qubit2: {current_mapping[qubit2[0].id]}\")" ] }, { @@ -491,7 +491,7 @@ "Measure | qubit0\n", "eng5.flush()\n", "\n", - "print(\"qubit0 was measured in state: {}\".format(int(qubit0)))" + "print(f\"qubit0 was measured in state: {int(qubit0)}\")" ] }, { diff --git a/examples/quantum_random_numbers.py b/examples/quantum_random_numbers.py index afa09dc4f..34dc61acc 100755 --- a/examples/quantum_random_numbers.py +++ b/examples/quantum_random_numbers.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of a simple quantum random number generator.""" @@ -20,4 +19,4 @@ eng.flush() # print the result: -print("Measured: {}".format(int(q1))) +print(f"Measured: {int(q1)}") diff --git a/examples/quantum_random_numbers_ibm.py b/examples/quantum_random_numbers_ibm.py index adfcb7101..2cbf35b93 100755 --- a/examples/quantum_random_numbers_ibm.py +++ b/examples/quantum_random_numbers_ibm.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of a simple quantum random number generator using IBM's API.""" @@ -22,4 +21,4 @@ eng.flush() # print the result: -print("Measured: {}".format(int(q1))) +print(f"Measured: {int(q1)}") diff --git a/examples/shor.py b/examples/shor.py index 39dce16dc..cb5d41bd8 100755 --- a/examples/shor.py +++ b/examples/shor.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example implementation of Shor's algorithm.""" @@ -13,8 +12,6 @@ except ImportError: from fractions import gcd -from builtins import input - import projectq.libs.math import projectq.setups.decompositions from projectq.backends import ResourceCounter, Simulator @@ -75,12 +72,12 @@ def run_shor(eng, N, a, verbose=False): X | ctrl_qubit if verbose: - print("\033[95m{}\033[0m".format(measurements[k]), end="") + print(f"\033[95m{measurements[k]}\033[0m", end="") sys.stdout.flush() All(Measure) | x # turn the measured values into a number in [0,1) - y = sum([(measurements[2 * n - 1 - i] * 1.0 / (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 @@ -130,13 +127,13 @@ def high_level_gates(eng, cmd): end="", ) N = int(input('\n\tNumber to factor: ')) - print("\n\tFactoring N = {}: \033[0m".format(N), end="") + print(f"\n\tFactoring N = {N}: \033[0m", end="") # choose a base at random: 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("\tFactor: {}\033[0m".format(gcd(a, N))) + print(f"\tFactor: {gcd(a, N)}\033[0m") else: # run the quantum subroutine r = run_shor(eng, N, a, True) @@ -150,8 +147,8 @@ def high_level_gates(eng, cmd): 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(f"\n\n\t\033[92mFactors found :-) : {f1} * {f2} = {N}\033[0m") else: - print("\n\n\t\033[91mBad luck: Found {} and {}\033[0m".format(f1, f2)) + print(f"\n\n\t\033[91mBad luck: Found {f1} and {f2}\033[0m") print(resource_counter) # print resource usage diff --git a/examples/simulator_tutorial.ipynb b/examples/simulator_tutorial.ipynb index f95ac662b..437011ad8 100644 --- a/examples/simulator_tutorial.ipynb +++ b/examples/simulator_tutorial.ipynb @@ -301,14 +301,14 @@ "\n", "# Amplitude will be 1 as Hadamard gate is not yet executed on the simulator backend\n", "# We forgot the eng.flush()!\n", - "print(\"Amplitude saved in amp_before: {}\".format(amp_before))\n", + "print(f\"Amplitude saved in amp_before: {amp_before}\")\n", "\n", "eng.flush() # Makes sure that all the gates are sent to the backend and executed\n", "\n", "amp_after = eng.backend.get_amplitude('00', qubit + qubit2)\n", "\n", "# Amplitude will be 1/sqrt(2) as Hadamard gate was executed on the simulator backend\n", - "print(\"Amplitude saved in amp_after: {}\".format(amp_after))\n", + "print(f\"Amplitude saved in amp_after: {amp_after}\")\n", "\n", "# To avoid triggering the warning of deallocating qubits which are in a superposition\n", "Measure | qubit\n", @@ -363,11 +363,11 @@ "prob00 = eng.backend.get_probability('00', qureg)\n", "prob_second_0 = eng.backend.get_probability('0', [qureg[1]])\n", "\n", - "print(\"Probability to measure 11: {}\".format(prob11))\n", - "print(\"Probability to measure 00: {}\".format(prob00))\n", - "print(\"Probability to measure 01: {}\".format(prob01))\n", - "print(\"Probability to measure 10: {}\".format(prob10))\n", - "print(\"Probability that second qubit is in state 0: {}\".format(prob_second_0))\n", + "print(f\"Probability to measure 11: {prob11}\")\n", + "print(f\"Probability to measure 00: {prob00}\")\n", + "print(f\"Probability to measure 01: {prob01}\")\n", + "print(f\"Probability to measure 10: {prob10}\")\n", + "print(f\"Probability that second qubit is in state 0: {prob_second_0}\")\n", "\n", "All(Measure) | qureg" ] @@ -406,11 +406,11 @@ "eng.flush()\n", "op0 = QubitOperator('Z0') # Z applied to qureg[0] tensor identity on qureg[1], qureg[2]\n", "expectation = eng.backend.get_expectation_value(op0, qureg)\n", - "print(\"Expectation value = = {}\".format(expectation))\n", + "print(f\"Expectation value = = {expectation}\")\n", "\n", "op_sum = QubitOperator('Z0 X1') - 0.5 * QubitOperator('X1')\n", "expectation2 = eng.backend.get_expectation_value(op_sum, qureg)\n", - "print(\"Expectation value = = {}\".format(expectation2))\n", + "print(f\"Expectation value = = {expectation2}\")\n", "\n", "All(Measure) | qureg # To avoid error message of deallocating qubits in a superposition" ] @@ -452,7 +452,7 @@ "Measure | qureg[1]\n", "eng.flush() # required such that all above gates are executed before accessing the measurement result\n", "\n", - "print(\"First qubit measured in state: {} and second qubit in state: {}\".format(int(qureg[0]), int(qureg[1])))" + "print(f\"First qubit measured in state: {int(qureg[0])} and second qubit in state: {int(qureg[1])}\")" ] }, { @@ -496,7 +496,7 @@ "prob_0 = eng.backend.get_probability('0', [qureg[1]])\n", "\n", "print(\"After forcing a measurement outcome of the first qubit to be 0, \\n\"\n", - " \"the second qubit is in state 0 with probability: {}\".format(prob_0))" + " f\"the second qubit is in state 0 with probability: {prob_0}\")" ] }, { @@ -573,18 +573,18 @@ "# also if the Python simulator is used, one should make a deepcopy:\n", "mapping, wavefunction = copy.deepcopy(eng.backend.cheat())\n", "\n", - "print(\"The full wavefunction is: {}\".format(wavefunction))\n", + "print(f\"The full wavefunction is: {wavefunction}\")\n", "# Note: qubit1 is a qureg of length 1, i.e. a list containing one qubit objects, therefore the\n", "# unique qubit id can be accessed via qubit1[0].id\n", - "print(\"qubit1 has bit-location {}\".format(mapping[qubit1[0].id]))\n", - "print(\"qubit2 has bit-location {}\".format(mapping[qubit2[0].id]))\n", + "print(f\"qubit1 has bit-location {mapping[qubit1[0].id]}\")\n", + "print(f\"qubit2 has bit-location {mapping[qubit2[0].id]}\")\n", "\n", "# Suppose we want to know the amplitude of the qubit1 in state 0 and qubit2 in state 1:\n", "state = 0 + (1 << mapping[qubit2[0].id])\n", - "print(\"Amplitude of state qubit1 in state 0 and qubit2 in state 1: {}\".format(wavefunction[state]))\n", + "print(f\"Amplitude of state qubit1 in state 0 and qubit2 in state 1: {wavefunction[state]}\")\n", "# If one only wants to access one (or a few) amplitudes, get_amplitude provides an easier interface:\n", "amplitude = eng.backend.get_amplitude('01', qubit1 + qubit2)\n", - "print(\"Accessing same amplitude but using get_amplitude instead: {}\".format(amplitude))\n", + "print(f\"Accessing same amplitude but using get_amplitude instead: {amplitude}\")\n", "\n", "All(Measure) | qubit1 + qubit2 # In order to not deallocate a qubit in a superposition state" ] diff --git a/examples/spectral_measurement.ipynb b/examples/spectral_measurement.ipynb index 0c791ecc0..825d2c7a5 100644 --- a/examples/spectral_measurement.ipynb +++ b/examples/spectral_measurement.ipynb @@ -334,7 +334,7 @@ } ], "source": [ - "print(\"We measured {} corresponding to energy {}\".format(est_phase, math.cos(2*math.pi*est_phase)))" + "print(f\"We measured {est_phase} corresponding to energy {math.cos(2*math.pi*est_phase)}\")" ] }, { diff --git a/examples/teleport.py b/examples/teleport.py index 4d2e684aa..e662aef49 100755 --- a/examples/teleport.py +++ b/examples/teleport.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example of a quantum teleportation circuit.""" @@ -65,7 +64,7 @@ def run_teleport(eng, state_creation_function, verbose=False): Measure | b1 msg_to_bob = [int(psi), int(b1)] if verbose: - print("Alice is sending the message {} to Bob.".format(msg_to_bob)) + print(f"Alice is sending the message {msg_to_bob} to Bob.") # Bob may have to apply up to two operation depending on the message sent # by Alice: diff --git a/examples/teleport_circuit.py b/examples/teleport_circuit.py index 1dfd3a485..f626f595e 100755 --- a/examples/teleport_circuit.py +++ b/examples/teleport_circuit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # pylint: skip-file """Example if drawing of a quantum teleportation circuit.""" diff --git a/examples/unitary_simulator.py b/examples/unitary_simulator.py index d2fd4ecdf..d91b0ede9 100644 --- a/examples/unitary_simulator.py +++ b/examples/unitary_simulator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/__init__.py b/projectq/__init__.py index d09243a4a..2da4bc1f8 100755 --- a/projectq/__init__.py +++ b/projectq/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/__init__.py b/projectq/backends/__init__.py index 8a8a50646..e531ed9f2 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,10 +25,12 @@ * 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 Azure Quantum service devices (and simulators) * an interface to the IonQ trapped ionq hardware (and simulator). """ from ._aqt import AQTBackend from ._awsbraket import AWSBraketBackend +from ._azure import AzureQuantumBackend from ._circuits import CircuitDrawer, CircuitDrawerMatplotlib from ._exceptions import DeviceNotHandledError, DeviceOfflineError, DeviceTooSmall from ._ibm import IBMBackend diff --git a/projectq/backends/_aqt/__init__.py b/projectq/backends/_aqt/__init__.py index 7c5dcb45e..d971c1b35 100644 --- a/projectq/backends/_aqt/__init__.py +++ b/projectq/backends/_aqt/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 709a97bb8..cb1993099 100644 --- a/projectq/backends/_aqt/_aqt.py +++ b/projectq/backends/_aqt/_aqt.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,15 +23,10 @@ from projectq.types import WeakQubitRef from .._exceptions import InvalidCommandError +from .._utils import _rearrange_result from ._aqt_http_client import retrieve, send -# _rearrange_result & _format_counts imported and modified from qiskit -def _rearrange_result(input_result, length): - bin_input = list(bin(input_result)[2:].rjust(length, '0')) - return ''.join(bin_input)[::-1] - - def _format_counts(samples, length): counts = {} for result in samples: @@ -167,7 +161,7 @@ def _store(self, cmd): return if gate == Barrier: return - raise InvalidCommandError('Invalid command: ' + str(cmd)) + raise InvalidCommandError(f"Invalid command: {str(cmd)}") def _logical_to_physical(self, qb_id): """ @@ -182,17 +176,15 @@ 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 {}. Please make sure " - "eng.flush() was called and that the qubit " - "was eliminated during optimization.".format(qb_id) + f"Unknown qubit id {qb_id}. " + "Please make sure eng.flush() was called and that the qubit was eliminated during optimization." ) return mapping[qb_id] 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) + f"Unknown qubit id {qb_id}. Please make sure eng.flush() was called and that the qubit was " + "eliminated during optimization." ) from err return qb_id @@ -288,7 +280,7 @@ def _run(self): star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: - print(str(state) + " with p = " + str(probability) + star) + print(f"{str(state)} with p = {probability}{star}") # register measurement result from AQT for qubit_id in self._measured_ids: diff --git a/projectq/backends/_aqt/_aqt_http_client.py b/projectq/backends/_aqt/_aqt_http_client.py index 7f869d254..ac1a5094c 100644 --- a/projectq/backends/_aqt/_aqt_http_client.py +++ b/projectq/backends/_aqt/_aqt_http_client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -126,12 +125,12 @@ def get_result( # pylint: disable=too-many-arguments ): """Get the result of an execution.""" if verbose: - print("Waiting for results. [Job ID: {}]".format(execution_id)) + print(f"Waiting for results. [Job ID: {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)) + raise Exception(f"Interrupted. The ID of your submitted job is {execution_id}.") try: signal.signal(signal.SIGINT, _handle_sigint_during_get_result) @@ -145,7 +144,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover 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(f"Error while running the code: {r_json['status']}.") time.sleep(interval) if self.is_online(device) and retries % 60 == 0: self.update_devices_list() @@ -154,14 +153,14 @@ 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) + f"Device went offline. The ID of your submitted job is {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)) + raise RequestTimeoutError(f"Timeout. The ID of your submitted job is {execution_id}.") def show_devices(verbose=False): @@ -226,7 +225,7 @@ def send( if verbose: print("- Authenticating...") if token is not None: - print('user API token: ' + token) + print(f"user API token: {token}") aqt_session.authenticate(token) # check if the device is online @@ -241,13 +240,12 @@ def send( runnable, qmax, qneeded = aqt_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) + f"The device is too small ({qmax} qubits available) for the code requested({qneeded} qubits needed).", + "Try to look for another device with more qubits", ) raise DeviceTooSmall("Device is too small.") if verbose: - print("- Running code: {}".format(info)) + print(f"- Running code: {info}") execution_id = aqt_session.run(info, device) if verbose: print("- Waiting for results...") diff --git a/projectq/backends/_aqt/_aqt_http_client_test.py b/projectq/backends/_aqt/_aqt_http_client_test.py index 64e50fee2..1159bc02b 100644 --- a/projectq/backends/_aqt/_aqt_http_client_test.py +++ b/projectq/backends/_aqt/_aqt_http_client_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_aqt/_aqt_test.py b/projectq/backends/_aqt/_aqt_test.py index 397f6ffd9..d35bcaf6a 100644 --- a/projectq/backends/_aqt/_aqt_test.py +++ b/projectq/backends/_aqt/_aqt_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_awsbraket/__init__.py b/projectq/backends/_awsbraket/__init__.py index 2d01597e0..7e2ed0f91 100644 --- a/projectq/backends/_awsbraket/__init__.py +++ b/projectq/backends/_awsbraket/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_awsbraket/_awsbraket.py b/projectq/backends/_awsbraket/_awsbraket.py index de7c82ecc..56f1a4be6 100755 --- a/projectq/backends/_awsbraket/_awsbraket.py +++ b/projectq/backends/_awsbraket/_awsbraket.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -299,13 +298,13 @@ def _store(self, cmd): # pylint: disable=too-many-branches json_cmd['angle'] = gate.angle if isinstance(gate, DaggeredGate): - json_cmd['type'] = 'c' * num_controls + self._gationary[gate_type] + 'i' + json_cmd['type'] = f"{'c' * num_controls + self._gationary[gate_type]}i" elif isinstance(gate, (XGate)) and num_controls > 0: - json_cmd['type'] = 'c' * (num_controls - 1) + 'cnot' + json_cmd['type'] = f"{'c' * (num_controls - 1)}cnot" else: json_cmd['type'] = 'c' * num_controls + self._gationary[gate_type] - self._circuit += json.dumps(json_cmd) + ", " + self._circuit += f"{json.dumps(json_cmd)}, " # TODO: Add unitary for the SV1 simulator as MatrixGate @@ -320,10 +319,8 @@ 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) + f"Unknown qubit id {qb_id} in current mapping. " + "Please make sure eng.flush() was called and that the qubit was eliminated during optimization." ) return mapping[qb_id] return qb_id @@ -367,7 +364,7 @@ def get_probabilities(self, qureg): mapped_state = ['0'] * len(qureg) 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)) + raise IndexError(f'Physical ID {qubit.id} > length of internal probabilities array') mapped_state[i] = state[self._logical_to_physical(qubit.id)] mapped_state = "".join(mapped_state) if mapped_state not in probability_dict: @@ -437,7 +434,7 @@ def _run(self): star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: - print(state + " with p = " + str(probability) + star) + print(f"{state} with p = {probability}{star}") # register measurement result for qubit_id in self._measured_ids: diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py index 39ad3b265..bc7d531a4 100755 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -218,12 +217,12 @@ def run(self, info, device): 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)) + print(f"Waiting for results. [Job Arn: {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)) + raise Exception(f"Interrupted. The Arn of your submitted job is {execution_id}.") def _calculate_measurement_probs(measurements): """ @@ -269,7 +268,7 @@ def _calculate_measurement_probs(measurements): status = quantum_task['status'] bucket = quantum_task['outputS3Bucket'] directory = quantum_task['outputS3Directory'] - resultsojectname = directory + '/results.json' + resultsojectname = f"{directory}/results.json" if status == 'COMPLETED': # Get the device type to obtian the correct measurement # structure @@ -282,7 +281,7 @@ def _calculate_measurement_probs(measurements): ) s3result = client_s3.get_object(Bucket=bucket, Key=resultsojectname) if verbose: - print("Results obtained. [Status: {}]".format(status)) + print(f"Results obtained. [Status: {status}]") result_content = json.loads(s3result['Body'].read()) if devicetype_used == 'QPU': @@ -291,11 +290,11 @@ def _calculate_measurement_probs(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']) + f'Error while running the code: {status}. ' + f'The failure reason was: {quantum_task["failureReason"]}.' ) if status == 'CANCELLING': - raise Exception("The job received a CANCEL operation: {}.".format(status)) + raise Exception(f"The job received a CANCEL operation: {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 @@ -311,9 +310,7 @@ def _calculate_measurement_probs(measurements): signal.signal(signal.SIGINT, original_sigint_handler) raise RequestTimeoutError( - "Timeout. " - "The Arn of your submitted job is {} and the status " - "of the job is {}.".format(execution_id, status) + f"Timeout. The Arn of your submitted job is {execution_id} and the status of the job is {status}." ) @@ -352,7 +349,7 @@ def retrieve(credentials, task_arn, 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(f"AWS credentials: {credentials['AWS_ACCESS_KEY_ID']}, {credentials['AWS_SECRET_KEY']}") awsbraket_session.authenticate(credentials=credentials) res = awsbraket_session.get_result(task_arn, num_retries=num_retries, interval=interval, verbose=verbose) return res @@ -386,7 +383,7 @@ def send( # pylint: disable=too-many-branches,too-many-arguments,too-many-local if verbose: print("- Authenticating...") if credentials is not None: - print("AWS credentials: " + credentials['AWS_ACCESS_KEY_ID'] + ", " + credentials['AWS_SECRET_KEY']) + print(f"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) @@ -403,17 +400,14 @@ def send( # pylint: disable=too-many-branches,too-many-arguments,too-many-local 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) + f"The device is too small ({qmax} qubits available) for the code", + f"requested({qneeded} qubits needed). Try to look for another device with more qubits", ) raise DeviceTooSmall("Device is too small.") if verbose: - print("- Running code: {}".format(info)) + print(f"- Running code: {info}") task_arn = awsbraket_session.run(info, device) - print("Your task Arn is: {}. Make note of that for future reference".format(task_arn)) + print(f"Your task Arn is: {task_arn}. Make note of that for future reference") if verbose: print("- Waiting for results...") diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py index 795d9e308..93cc994de 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -262,7 +261,7 @@ def test_send_that_errors_are_caught(mocker, var_error, send_that_error_setup): 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}}, + {"Error": {"Code": var_error, "Message": f"Msg error for {var_error}"}}, "create_quantum_task", ) mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) @@ -289,7 +288,7 @@ def test_retrieve_error_arn_not_exist(mocker, var_error, arntask, creds): 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}}, + {"Error": {"Code": var_error, "Message": f"Msg error for {var_error}"}}, "get_quantum_task", ) mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py index 0fa5cb2b9..ba38906b3 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_awsbraket/_awsbraket_test.py b/projectq/backends/_awsbraket/_awsbraket_test.py index 22205a2f1..1a5288c88 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -319,6 +318,7 @@ def test_awsbraket_backend_is_available_control_singlequbit_sv1(ctrl_singlequbit assert aws_backend.is_available(cmd) == is_available_sv1 +@has_boto3 def test_awsbraket_backend_is_available_negative_control(): backend = _awsbraket.AWSBraketBackend() @@ -426,7 +426,7 @@ def test_awsbraket_sent_error(mocker, sent_error_setup): 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}}, + {"Error": {"Code": var_error, "Message": f"Msg error for {var_error}"}}, "create_quantum_task", ) mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) diff --git a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py index 71968697d..7ee1dd7b9 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py +++ b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_azure/__init__.py b/projectq/backends/_azure/__init__.py new file mode 100644 index 000000000..8c06e2392 --- /dev/null +++ b/projectq/backends/_azure/__init__.py @@ -0,0 +1,27 @@ +# Copyright 2022 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. + +"""ProjectQ module for supporting the Azure Quantum platform.""" + +try: + from ._azure_quantum import AzureQuantumBackend +except ImportError: # pragma: no cover + + class AzureQuantumBackend: + """Dummy class""" + + def __init__(self, *args, **kwargs): + raise ImportError( + "Missing optional 'azure-quantum' dependencies. To install run: pip install projectq[azure-quantum]" + ) diff --git a/projectq/backends/_azure/_azure_quantum.py b/projectq/backends/_azure/_azure_quantum.py new file mode 100644 index 000000000..e8187c87c --- /dev/null +++ b/projectq/backends/_azure/_azure_quantum.py @@ -0,0 +1,387 @@ +# Copyright 2022 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 Azure Quantum.""" + +from collections import Counter + +import numpy as np + +from projectq.cengines import BasicEngine +from projectq.meta import LogicalQubitIDTag +from projectq.ops import AllocateQubitGate, DeallocateQubitGate, FlushGate, MeasureGate +from projectq.types import WeakQubitRef + +from .._utils import _rearrange_result +from ._azure_quantum_client import retrieve, send +from ._exceptions import AzureQuantumTargetNotFoundError +from ._utils import ( + IONQ_PROVIDER_ID, + QUANTINUUM_PROVIDER_ID, + is_available_ionq, + is_available_quantinuum, + to_json, + to_qasm, +) + +try: + from azure.quantum import Workspace + from azure.quantum.target import IonQ, Quantinuum, Target + from azure.quantum.target.target_factory import TargetFactory +except ImportError: # pragma: no cover + raise ImportError( # pylint: disable=raise-missing-from + "Missing optional 'azure-quantum' dependencies. To install run: pip install projectq[azure-quantum]" + ) + + +class AzureQuantumBackend(BasicEngine): # pylint: disable=too-many-instance-attributes + """Backend for building circuits and submitting them to the Azure Quantum.""" + + DEFAULT_TARGETS = {IONQ_PROVIDER_ID: IonQ, QUANTINUUM_PROVIDER_ID: Quantinuum} + + def __init__( + self, + use_hardware=False, + num_runs=100, + verbose=False, + workspace=None, + target_name='ionq.simulator', + num_retries=100, + interval=1, + retrieve_execution=None, + **kwargs, + ): # pylint: disable=too-many-arguments + """ + Initialize an Azure Quantum Backend object. + + Args: + use_hardware (bool, optional): Whether or not to use real hardware or just a simulator. If False, + regardless of the value of ```target_name```, ```ionq.simulator``` used for IonQ provider and + ```quantinuum.hqs-lt-s1-apival``` used for Quantinuum provider. 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. + workspace (Workspace, optional): Azure Quantum workspace. If workspace is None, kwargs will be used to + create Workspace object. + target_name (str, optional): Target to run jobs on. Defaults to ```ionq.simulator```. + num_retries (int, optional): Number of times to retry fetching a job after it has been submitted. Defaults + to 100. + interval (int, optional): Number of seconds to wait in between result fetch retries. Defaults to 1. + retrieve_execution (str, optional): An Azure Quantum Job ID. If provided, a job result with this ID will be + fetched. Defaults to None. + """ + super().__init__() + + if target_name in IonQ.target_names: + self._provider_id = IONQ_PROVIDER_ID + elif target_name in Quantinuum.target_names: + self._provider_id = QUANTINUUM_PROVIDER_ID + else: + raise AzureQuantumTargetNotFoundError(f'Target {target_name} does not exit.') + + if use_hardware: + self._target_name = target_name + else: + if self._provider_id == IONQ_PROVIDER_ID: + self._target_name = 'ionq.simulator' + elif self._provider_id == QUANTINUUM_PROVIDER_ID: + if target_name == 'quantinuum.hqs-lt-s1': + self._target_name = 'quantinuum.hqs-lt-s1-apival' + else: + self._target_name = target_name + else: # pragma: no cover + raise RuntimeError("Invalid Azure Quantum target.") + + if workspace is None: + workspace = Workspace(**kwargs) + + workspace.append_user_agent('projectq') + self._workspace = workspace + + self._num_runs = num_runs + self._verbose = verbose + self._num_retries = num_retries + self._interval = interval + self._retrieve_execution = retrieve_execution + self._circuit = None + self._measured_ids = [] + self._probabilities = {} + self._clear = True + + 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 = None + self._clear = True + + 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._cmds). + + Args: + cmd (Command): Command to store + """ + if self._clear: + self._probabilities = {} + self._clear = False + self._circuit = None + + gate = cmd.gate + + # No-op/Meta gates + if isinstance(gate, (AllocateQubitGate, DeallocateQubitGate)): + return + + # Measurement + if isinstance(gate, MeasureGate): + logical_id = None + for tag in cmd.tags: + if isinstance(tag, LogicalQubitIDTag): + logical_id = tag.logical_qubit_id + break + + if logical_id is None: + raise RuntimeError('No LogicalQubitIDTag found in command!') + + self._measured_ids.append(logical_id) + return + + if self._provider_id == IONQ_PROVIDER_ID: + if not self._circuit: + self._circuit = [] + + json_cmd = to_json(cmd) + if json_cmd: + self._circuit.append(json_cmd) + + elif self._provider_id == QUANTINUUM_PROVIDER_ID: + if not self._circuit: + self._circuit = '' + + qasm_cmd = to_qasm(cmd) + if qasm_cmd: + self._circuit += f'\n{qasm_cmd}' + + else: + raise RuntimeError("Invalid Azure Quantum target.") + + 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. + """ + if self._provider_id == IONQ_PROVIDER_ID: + return is_available_ionq(cmd) + + if self._provider_id == QUANTINUUM_PROVIDER_ID: + return is_available_quantinuum(cmd) + + return False + + @staticmethod + def _target_factory(workspace, target_name, provider_id): # pragma: no cover + target_factory = TargetFactory( + base_cls=Target, workspace=workspace, default_targets=AzureQuantumBackend.DEFAULT_TARGETS + ) + + return target_factory.get_targets(name=target_name, provider_id=provider_id) + + @property + def _target(self): + target = self._target_factory( + workspace=self._workspace, target_name=self._target_name, provider_id=self._provider_id + ) + + if isinstance(target, list) and len(target) == 0: # pragma: no cover + raise AzureQuantumTargetNotFoundError( + f'Target {self._target_name} is not available on workspace {self._workspace.name}.' + ) + + return target + + @property + def current_availability(self): + """Get current availability for given target.""" + return self._target.current_availability + + @property + def average_queue_time(self): + """Get average queue time for given target.""" + return self._target.average_queue_time + + 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.get(state, 0.0) + + 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 + + @property + def _input_data(self): + qubit_mapping = self.main_engine.mapper.current_mapping + qubits = len(qubit_mapping.keys()) + + if self._provider_id == IONQ_PROVIDER_ID: + return {"qubits": qubits, "circuit": self._circuit} + + if self._provider_id == QUANTINUUM_PROVIDER_ID: + measurement_gates = "" + + for measured_id in self._measured_ids: + qb_loc = self.main_engine.mapper.current_mapping[measured_id] + measurement_gates += "measure q[{0}] -> c[{0}];\n".format(qb_loc) + + return ( + f"OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[{qubits}];\ncreg c[{qubits}];" + f"{self._circuit}\n{measurement_gates}" + ) + + raise RuntimeError("Invalid Azure Quantum target.") + + @property + def _metadata(self): + qubit_mapping = self.main_engine.mapper.current_mapping + num_qubits = len(qubit_mapping.keys()) + meas_map = [qubit_mapping[qubit_id] for qubit_id in self._measured_ids] + + return {"num_qubits": num_qubits, "meas_map": meas_map} + + def estimate_cost(self, **kwargs): + """Estimate cost for the circuit this object has built during engine execution.""" + return self._target.estimate_cost(circuit=self._input_data, num_shots=self._num_runs, **kwargs) + + 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 not self._circuit: + return + + if self._retrieve_execution is None: + res = send( + input_data=self._input_data, + metadata=self._metadata, + num_shots=self._num_runs, + target=self._target, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) + + if res is None: # pragma: no cover + raise RuntimeError('Failed to submit job to the Azure Quantum!') + else: + res = retrieve( + job_id=self._retrieve_execution, + target=self._target, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) + + if res is None: + raise RuntimeError( + f"Failed to retrieve job from Azure Quantum with job id: '{self._retrieve_execution}'!" + ) + + if self._provider_id == IONQ_PROVIDER_ID: + self._probabilities = { + _rearrange_result(int(k), len(self._measured_ids)): v for k, v in res["histogram"].items() + } + elif self._provider_id == QUANTINUUM_PROVIDER_ID: + histogram = Counter(res["c"]) + self._probabilities = {k: v / self._num_runs for k, v in histogram.items()} + else: # pragma: no cover + raise RuntimeError("Invalid Azure Quantum target.") + + # Set a single measurement result + bitstring = np.random.choice(list(self._probabilities.keys()), p=list(self._probabilities.values())) + for qid in self._measured_ids: + qubit_ref = WeakQubitRef(self.main_engine, qid) + self.main_engine.set_measurement_result(qubit_ref, bitstring[qid]) + + 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 Azure Quantum 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__ = ['AzureQuantumBackend'] diff --git a/projectq/backends/_azure/_azure_quantum_client.py b/projectq/backends/_azure/_azure_quantum_client.py new file mode 100644 index 000000000..86c3f1891 --- /dev/null +++ b/projectq/backends/_azure/_azure_quantum_client.py @@ -0,0 +1,96 @@ +# Copyright 2022 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. + +"""Client methods to run quantum programs using Azure Quantum.""" + +from .._exceptions import DeviceOfflineError, RequestTimeoutError + + +def _get_results(job, num_retries=100, interval=1, verbose=False): + if verbose: # pragma: no cover + print(f"- Waiting for results. [Job ID: {job.id}]") + + try: + return job.get_results(timeout_secs=num_retries * interval) + except TimeoutError: + raise RequestTimeoutError( # pylint: disable=raise-missing-from + f"Timeout. The ID of your submitted job is {job.id}." + ) + + +def send( + input_data, num_shots, target, num_retries=100, interval=1, verbose=False, **kwargs +): # pylint: disable=too-many-arguments + """ + Submit a job to the Azure Quantum. + + Args: + input_data (any): Input data for Quantum job. + num_shots (int): Number of runs. + target (Target), The target to run this on. + 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. + + Returns: + dict: An intermediate dict representation of an Azure Quantum job result. + """ + if target.current_availability != 'Available': + raise DeviceOfflineError('Device is offline.') + + if verbose: + print(f"- Running code: {input_data}") + + job = target.submit(circuit=input_data, num_shots=num_shots, **kwargs) + + res = _get_results(job=job, num_retries=num_retries, interval=interval, verbose=verbose) + + if verbose: + print("- Done.") + + return res + + +def retrieve(job_id, target, num_retries=100, interval=1, verbose=False): + """ + Retrieve a job from Azure Quantum. + + Args: + job_id (str), Azure Quantum job id. + target (Target), The target job runs on. + 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. + + Returns: + dict: An intermediate dict representation of an Azure Quantum job result. + """ + job = target.workspace.get_job(job_id=job_id) + + res = _get_results(job=job, num_retries=num_retries, interval=interval, verbose=verbose) + + if verbose: + print("- Done.") + + return res diff --git a/projectq/backends/_azure/_azure_quantum_client_test.py b/projectq/backends/_azure/_azure_quantum_client_test.py new file mode 100644 index 000000000..5ce1a6d8f --- /dev/null +++ b/projectq/backends/_azure/_azure_quantum_client_test.py @@ -0,0 +1,420 @@ +# Copyright 2022 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._azure._azure_quantum_client.py.""" + +from unittest import mock + +import pytest + +from .._exceptions import DeviceOfflineError, RequestTimeoutError + +_has_azure_quantum = True +try: + import azure.quantum # noqa: F401 + + from projectq.backends._azure._azure_quantum_client import retrieve, send +except ImportError: + _has_azure_quantum = False + +has_azure_quantum = pytest.mark.skipif(not _has_azure_quantum, reason="azure quantum package is not installed") + +ZERO_GUID = '00000000-0000-0000-0000-000000000000' + + +@has_azure_quantum +def test_is_online(): + def get_mock_target(): + mock_target = mock.MagicMock() + mock_target.current_availability = 'Offline' + + return mock_target + + with pytest.raises(DeviceOfflineError): + send( + input_data={}, + metadata={}, + num_shots=100, + target=get_mock_target(), + num_retries=1000, + interval=1, + verbose=True, + ) + + +@has_azure_quantum +@pytest.mark.parametrize('verbose', (False, True)) +def test_send_ionq(verbose): + expected_res = {'0': 0.125, '1': 0.125, '2': 0.125, '3': 0.125, '4': 0.125, '5': 0.125, '6': 0.125, '7': 0.125} + + def get_mock_target(): + mock_job = mock.MagicMock() + mock_job.id = ZERO_GUID + mock_job.get_results = mock.MagicMock(return_value=expected_res) + + mock_target = mock.MagicMock() + mock_target.current_availability = 'Available' + mock_target.submit = mock.MagicMock(return_value=mock_job) + + return mock_target + + input_data = { + 'qubits': 3, + 'circuit': [{'gate': 'h', 'targets': [0]}, {'gate': 'h', 'targets': [1]}, {'gate': 'h', 'targets': [2]}], + } + metadata = {'num_qubits': 3, 'meas_map': [0, 1, 2]} + + actual_res = send( + input_data=input_data, + metadata=metadata, + num_shots=100, + target=get_mock_target(), + num_retries=1000, + interval=1, + verbose=verbose, + ) + + assert actual_res == expected_res + + +@has_azure_quantum +@pytest.mark.parametrize('verbose', (False, True)) +def test_send_quantinuum(verbose): + expected_res = { + 'c': [ + '010', + '100', + '110', + '000', + '101', + '111', + '000', + '100', + '000', + '110', + '111', + '100', + '100', + '000', + '101', + '110', + '111', + '011', + '101', + '100', + '001', + '110', + '001', + '001', + '100', + '011', + '110', + '000', + '101', + '101', + '010', + '100', + '110', + '111', + '010', + '000', + '010', + '110', + '000', + '110', + '001', + '100', + '110', + '011', + '010', + '111', + '100', + '110', + '100', + '100', + '011', + '000', + '001', + '101', + '000', + '011', + '111', + '101', + '101', + '001', + '011', + '110', + '001', + '010', + '001', + '110', + '101', + '000', + '010', + '001', + '011', + '100', + '110', + '100', + '110', + '101', + '110', + '111', + '110', + '001', + '011', + '101', + '111', + '011', + '100', + '111', + '100', + '001', + '111', + '111', + '100', + '100', + '110', + '101', + '100', + '110', + '100', + '000', + '011', + '000', + ] + } + + def get_mock_target(): + mock_job = mock.MagicMock() + mock_job.id = ZERO_GUID + mock_job.get_results = mock.MagicMock(return_value=expected_res) + + mock_target = mock.MagicMock() + mock_target.current_availability = 'Available' + mock_target.submit = mock.MagicMock(return_value=mock_job) + + return mock_target + + input_data = ''''OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + creg c[3]; + h q[0]; + h q[1]; + h q[2]; + measure q[0] -> c[0]; + measure q[1] -> c[1]; + measure q[2] -> c[2]; +''' + + metadata = {'num_qubits': 3, 'meas_map': [0, 1, 2]} + + actual_res = send( + input_data=input_data, + metadata=metadata, + num_shots=100, + target=get_mock_target(), + num_retries=1000, + interval=1, + verbose=verbose, + ) + + assert actual_res == expected_res + + +@has_azure_quantum +@pytest.mark.parametrize('verbose', (False, True)) +def test_retrieve_ionq(verbose): + expected_res = {'0': 0.125, '1': 0.125, '2': 0.125, '3': 0.125, '4': 0.125, '5': 0.125, '6': 0.125, '7': 0.125} + + def get_mock_target(): + mock_job = mock.MagicMock() + mock_job.id = ZERO_GUID + mock_job.get_results = mock.MagicMock(return_value=expected_res) + + mock_workspace = mock.MagicMock() + mock_workspace.get_job = mock.MagicMock(return_value=mock_job) + + mock_target = mock.MagicMock() + mock_target.current_availability = 'Available' + mock_target.workspace = mock_workspace + mock_target.submit = mock.MagicMock(return_value=mock_job) + + return mock_target + + actual_res = retrieve(job_id=ZERO_GUID, target=get_mock_target(), num_retries=1000, interval=1, verbose=verbose) + + assert actual_res == expected_res + + +@has_azure_quantum +@pytest.mark.parametrize('verbose', (False, True)) +def test_retrieve_quantinuum(verbose): + expected_res = { + 'c': [ + '010', + '100', + '110', + '000', + '101', + '111', + '000', + '100', + '000', + '110', + '111', + '100', + '100', + '000', + '101', + '110', + '111', + '011', + '101', + '100', + '001', + '110', + '001', + '001', + '100', + '011', + '110', + '000', + '101', + '101', + '010', + '100', + '110', + '111', + '010', + '000', + '010', + '110', + '000', + '110', + '001', + '100', + '110', + '011', + '010', + '111', + '100', + '110', + '100', + '100', + '011', + '000', + '001', + '101', + '000', + '011', + '111', + '101', + '101', + '001', + '011', + '110', + '001', + '010', + '001', + '110', + '101', + '000', + '010', + '001', + '011', + '100', + '110', + '100', + '110', + '101', + '110', + '111', + '110', + '001', + '011', + '101', + '111', + '011', + '100', + '111', + '100', + '001', + '111', + '111', + '100', + '100', + '110', + '101', + '100', + '110', + '100', + '000', + '011', + '000', + ] + } + + def get_mock_target(): + mock_job = mock.MagicMock() + mock_job.id = ZERO_GUID + mock_job.get_results = mock.MagicMock(return_value=expected_res) + + mock_workspace = mock.MagicMock() + mock_workspace.get_job = mock.MagicMock(return_value=mock_job) + + mock_target = mock.MagicMock() + mock_target.current_availability = 'Available' + mock_target.workspace = mock_workspace + mock_target.submit = mock.MagicMock(return_value=mock_job) + + return mock_target + + actual_res = retrieve(job_id=ZERO_GUID, target=get_mock_target(), num_retries=1000, interval=1, verbose=verbose) + + assert actual_res == expected_res + + +@has_azure_quantum +@pytest.mark.parametrize('verbose', (False, True)) +def test_send_timeout_error(verbose): + def get_mock_target(): + mock_job = mock.MagicMock() + mock_job.id = ZERO_GUID + mock_job.get_results = mock.MagicMock() + mock_job.get_results.side_effect = TimeoutError() + + mock_target = mock.MagicMock() + mock_target.current_availability = 'Available' + mock_target.submit = mock.MagicMock(return_value=mock_job) + + return mock_target + + input_data = { + 'qubits': 3, + 'circuit': [{'gate': 'h', 'targets': [0]}, {'gate': 'h', 'targets': [1]}, {'gate': 'h', 'targets': [2]}], + } + metadata = {'num_qubits': 3, 'meas_map': [0, 1, 2]} + + with pytest.raises(RequestTimeoutError): + _ = send( + input_data=input_data, + metadata=metadata, + num_shots=100, + target=get_mock_target(), + num_retries=1000, + interval=1, + verbose=verbose, + ) diff --git a/projectq/backends/_azure/_azure_quantum_test.py b/projectq/backends/_azure/_azure_quantum_test.py new file mode 100644 index 000000000..3cdc51bb2 --- /dev/null +++ b/projectq/backends/_azure/_azure_quantum_test.py @@ -0,0 +1,862 @@ +# Copyright 2022 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._azure._azure_quantum.py.""" + +from unittest import mock + +import pytest + +from projectq.cengines import BasicMapperEngine, MainEngine +from projectq.ops import CX, All, Command, H, Measure +from projectq.types import WeakQubitRef + +_has_azure_quantum = True +try: + from azure.quantum import Workspace + + import projectq.backends._azure._azure_quantum + from projectq.backends import AzureQuantumBackend + from projectq.backends._azure._exceptions import AzureQuantumTargetNotFoundError +except ImportError: + _has_azure_quantum = False + +has_azure_quantum = pytest.mark.skipif(not _has_azure_quantum, reason="azure quantum package is not installed") + +ZERO_GUID = '00000000-0000-0000-0000-000000000000' + + +def mock_target(target_id, current_availability, average_queue_time): + target = mock.MagicMock() + + estimate_cost = mock.Mock() + estimate_cost.estimated_total = 10 + + target.id = target_id + target.current_availability = current_availability + target.average_queue_time = average_queue_time + target.estimate_cost = mock.MagicMock(return_value=estimate_cost) + + return target + + +def mock_providers(): + ionq_provider = mock.MagicMock() + ionq_provider.id = 'ionq' + ionq_provider.targets = [ + mock_target(target_id='ionq.simulator', current_availability='Available', average_queue_time=1000), + mock_target(target_id='ionq.qpu', current_availability='Available', average_queue_time=2000), + ] + + quantinuum_provider = mock.MagicMock() + quantinuum_provider.id = 'quantinuum' + quantinuum_provider.targets = [ + mock_target(target_id='quantinuum.hqs-lt-s1-apival', current_availability='Available', average_queue_time=3000), + mock_target(target_id='quantinuum.hqs-lt-s1-sim', current_availability='Available', average_queue_time=4000), + mock_target(target_id='quantinuum.hqs-lt-s1', current_availability='Degraded', average_queue_time=5000), + ] + + return [ionq_provider, quantinuum_provider] + + +def mock_target_factory(target_name): + for provider in mock_providers(): + for target in provider.targets: + if target.id == target_name: + return target + + return [] + + +def _get_azure_backend(use_hardware, target_name, retrieve_execution=None): + AzureQuantumBackend._target_factory = mock.MagicMock(return_value=mock_target_factory(target_name)) + + workspace = Workspace( + subscription_id=ZERO_GUID, resource_group='testResourceGroup', name='testWorkspace', location='East US' + ) + + backend = AzureQuantumBackend( + use_hardware=use_hardware, + target_name=target_name, + workspace=workspace, + retrieve_execution=retrieve_execution, + verbose=True, + ) + + return backend + + +def _get_main_engine(backend, max_qubits=3): + mapper = BasicMapperEngine() + + mapping = {} + for i in range(max_qubits): + mapping[i] = i + mapper.current_mapping = mapping + + main_engine = MainEngine(backend=backend, engine_list=[mapper], verbose=True) + + return main_engine + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id, expected_target_name", + [ + (False, 'ionq.simulator', 'ionq', 'ionq.simulator'), + (True, 'ionq.qpu', 'ionq', 'ionq.qpu'), + (False, 'ionq.qpu', 'ionq', 'ionq.simulator'), + ], +) +def test_azure_quantum_ionq_target(use_hardware, target_name, provider_id, expected_target_name): + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + + assert backend._target_name == expected_target_name + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id, expected_target_name", + [ + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum', 'quantinuum.hqs-lt-s1-apival'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum', 'quantinuum.hqs-lt-s1-sim'), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum', 'quantinuum.hqs-lt-s1'), + (False, 'quantinuum.hqs-lt-s1', 'quantinuum', 'quantinuum.hqs-lt-s1-apival'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum', 'quantinuum.hqs-lt-s1-sim'), + ], +) +def test_azure_quantum_quantinuum_target(use_hardware, target_name, provider_id, expected_target_name): + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + + assert backend._target_name == expected_target_name + + +@has_azure_quantum +def test_initialize_azure_backend_using_kwargs(): + backend = AzureQuantumBackend( + use_hardware=False, + target_name='ionq.simulator', + subscription_id=ZERO_GUID, + resource_group='testResourceGroup', + name='testWorkspace', + location='East US', + ) + + assert backend._target_name == 'ionq.simulator' + + +@has_azure_quantum +def test_azure_quantum_invalid_target(): + with pytest.raises(AzureQuantumTargetNotFoundError): + _get_azure_backend(use_hardware=False, target_name='invalid-target') + + +@has_azure_quantum +def test_is_available_ionq(): + with mock.patch('projectq.backends._azure._azure_quantum.is_available_ionq') as is_available_ionq_patch: + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend) + + q0 = main_engine.allocate_qubit() + + cmd = Command(main_engine, H, (q0,)) + main_engine.is_available(cmd) + + is_available_ionq_patch.assert_called() + + +@has_azure_quantum +def test_is_available_quantinuum(): + with mock.patch('projectq.backends._azure._azure_quantum.is_available_quantinuum') as is_available_quantinuum_patch: + backend = _get_azure_backend(use_hardware=False, target_name='quantinuum.hqs-lt-s1-sim') + main_engine = _get_main_engine(backend=backend) + + q0 = main_engine.allocate_qubit() + + cmd = Command(main_engine, H, (q0,)) + main_engine.is_available(cmd) + + is_available_quantinuum_patch.assert_called() + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id, current_availability", + [ + (False, 'ionq.simulator', 'ionq', 'Available'), + (True, 'ionq.qpu', 'ionq', 'Available'), + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum', 'Available'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum', 'Available'), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum', 'Degraded'), + ], +) +def test_current_availability(use_hardware, target_name, provider_id, current_availability): + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + + assert backend.current_availability == current_availability + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id, average_queue_time", + [ + (False, 'ionq.simulator', 'ionq', 1000), + (True, 'ionq.qpu', 'ionq', 2000), + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum', 3000), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum', 4000), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum', 5000), + ], +) +def test_average_queue_time(use_hardware, target_name, provider_id, average_queue_time): + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + + assert backend.average_queue_time == average_queue_time + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [(False, 'ionq.simulator', 'ionq'), (True, 'ionq.qpu', 'ionq')], +) +def test_run_ionq_get_probabilities(use_hardware, target_name, provider_id): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}} + ) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + result = backend.get_probabilities(circuit) + + assert len(result) == 8 + assert result['000'] == pytest.approx(0.5) + assert result['001'] == 0.0 + assert result['010'] == 0.0 + assert result['011'] == 0.0 + assert result['100'] == 0.0 + assert result['101'] == 0.0 + assert result['110'] == 0.0 + assert result['111'] == pytest.approx(0.5) + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [ + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum'), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum'), + ], +) +def test_run_quantinuum_get_probabilities(use_hardware, target_name, provider_id): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={ + 'c': [ + '000', + '000', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '000', + '000', + '000', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '000', + '000', + '000', + '000', + '111', + '111', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + ] + } + ) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + result = backend.get_probabilities(circuit) + + assert len(result) == 2 + assert result['000'] == pytest.approx(0.41) + assert result['111'] == pytest.approx(0.59) + + +@has_azure_quantum +def test_run_get_probabilities_unused_qubit(): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}} + ) + + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend, max_qubits=4) + + circuit = main_engine.allocate_qureg(3) + unused_qubit = main_engine.allocate_qubit() + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + result = backend.get_probabilities(unused_qubit) + + assert len(result) == 1 + assert result['0'] == pytest.approx(1) + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [(False, 'ionq.simulator', 'ionq'), (True, 'ionq.qpu', 'ionq')], +) +def test_run_ionq_get_probability(use_hardware, target_name, provider_id): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}} + ) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + assert backend.get_probability('000', circuit) == pytest.approx(0.5) + assert backend.get_probability('001', circuit) == 0.0 + assert backend.get_probability('010', circuit) == 0.0 + assert backend.get_probability('011', circuit) == 0.0 + assert backend.get_probability('100', circuit) == 0.0 + assert backend.get_probability('101', circuit) == 0.0 + assert backend.get_probability('110', circuit) == 0.0 + assert backend.get_probability('111', circuit) == pytest.approx(0.5) + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [ + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum'), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum'), + ], +) +def test_run_quantinuum_get_probability(use_hardware, target_name, provider_id): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={ + 'c': [ + '000', + '000', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '000', + '000', + '000', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '000', + '000', + '000', + '000', + '111', + '111', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + ] + } + ) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + assert backend.get_probability('000', circuit) == pytest.approx(0.41) + assert backend.get_probability('001', circuit) == 0.0 + assert backend.get_probability('010', circuit) == 0.0 + assert backend.get_probability('011', circuit) == 0.0 + assert backend.get_probability('100', circuit) == 0.0 + assert backend.get_probability('101', circuit) == 0.0 + assert backend.get_probability('110', circuit) == 0.0 + assert backend.get_probability('111', circuit) == pytest.approx(0.59) + + +@has_azure_quantum +def test_estimate_cost(): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}} + ) + + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + estimate_cost = backend.estimate_cost() + + assert estimate_cost.estimated_total == 10 + + +@has_azure_quantum +def test_run_get_probability_invalid_state(): + projectq.backends._azure._azure_quantum.send = mock.MagicMock( + return_value={'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}} + ) + + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + with pytest.raises(ValueError): + _ = backend.get_probability('0000', circuit) + + +@has_azure_quantum +def test_run_no_circuit(): + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + + main_engine.flush() + + with pytest.raises(RuntimeError): + _ = backend.get_probabilities(circuit) + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [(False, 'ionq.simulator', 'ionq'), (True, 'ionq.qpu', 'ionq')], +) +@pytest.mark.parametrize( + 'retrieve_retval', + (None, {'histogram': {'0': 0.5, '1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.5}}), + ids=('retrieve-FAIL', 'retrieve-SUCESS'), +) +def test_run_ionq_retrieve_execution(use_hardware, target_name, provider_id, retrieve_retval): + projectq.backends._azure._azure_quantum.retrieve = mock.MagicMock(return_value=retrieve_retval) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name, retrieve_execution=ZERO_GUID) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + if retrieve_retval is None: + with pytest.raises(RuntimeError): + main_engine.flush() + else: + main_engine.flush() + result = backend.get_probabilities(circuit) + + assert len(result) == 8 + assert result['000'] == pytest.approx(0.5) + assert result['001'] == 0.0 + assert result['010'] == 0.0 + assert result['011'] == 0.0 + assert result['100'] == 0.0 + assert result['101'] == 0.0 + assert result['110'] == 0.0 + assert result['111'] == pytest.approx(0.5) + + +@has_azure_quantum +@pytest.mark.parametrize( + "use_hardware, target_name, provider_id", + [ + (False, 'quantinuum.hqs-lt-s1-apival', 'quantinuum'), + (False, 'quantinuum.hqs-lt-s1-sim', 'quantinuum'), + (True, 'quantinuum.hqs-lt-s1', 'quantinuum'), + ], +) +def test_run_quantinuum_retrieve_execution(use_hardware, target_name, provider_id): + projectq.backends._azure._azure_quantum.retrieve = mock.MagicMock( + return_value={ + 'c': [ + '000', + '000', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '111', + '000', + '000', + '000', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '111', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '111', + '000', + '000', + '000', + '000', + '111', + '111', + '000', + '111', + '111', + '000', + '000', + '111', + '000', + '111', + '000', + '111', + '000', + '111', + '111', + '000', + '111', + '000', + '111', + '111', + '111', + '000', + '111', + '111', + '000', + ] + } + ) + + backend = _get_azure_backend(use_hardware=use_hardware, target_name=target_name, retrieve_execution=ZERO_GUID) + main_engine = _get_main_engine(backend=backend) + + circuit = main_engine.allocate_qureg(3) + q0, q1, q2 = circuit + + H | q0 + CX | (q0, q1) + CX | (q1, q2) + All(Measure) | circuit + + main_engine.flush() + + result = backend.get_probabilities(circuit) + + assert len(result) == 2 + assert result['000'] == pytest.approx(0.41) + assert result['111'] == pytest.approx(0.59) + + +@has_azure_quantum +def test_error_no_logical_id_tag(): + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + main_engine = _get_main_engine(backend=backend) + + q0 = WeakQubitRef(engine=None, idx=0) + + with pytest.raises(RuntimeError): + main_engine.backend._store(Command(engine=main_engine, gate=Measure, qubits=([q0],))) + + +@has_azure_quantum +def test_error_invalid_provider(): + backend = _get_azure_backend(use_hardware=False, target_name='ionq.simulator') + backend._provider_id = 'INVALID' # NB: this is forcing it... should actually never happen in practice + main_engine = _get_main_engine(backend=backend) + + q0 = WeakQubitRef(engine=None, idx=0) + + cmd = Command(engine=main_engine, gate=H, qubits=([q0],)) + with pytest.raises(RuntimeError): + main_engine.backend._store(cmd) + + with pytest.raises(RuntimeError): + main_engine.backend._input_data + + assert not main_engine.backend.is_available(cmd) diff --git a/projectq/backends/_azure/_exceptions.py b/projectq/backends/_azure/_exceptions.py new file mode 100644 index 000000000..20f8e65c5 --- /dev/null +++ b/projectq/backends/_azure/_exceptions.py @@ -0,0 +1,19 @@ +# Copyright 2022 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. + +"""Exception classes for projectq.backends._azure.""" + + +class AzureQuantumTargetNotFoundError(Exception): + """Raised when a Azure Quantum target doesn't exist with given target name.""" diff --git a/projectq/backends/_azure/_utils.py b/projectq/backends/_azure/_utils.py new file mode 100644 index 000000000..0031f8438 --- /dev/null +++ b/projectq/backends/_azure/_utils.py @@ -0,0 +1,307 @@ +# Copyright 2022 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. + +"""Utility functions for Azure Quantum.""" + +from projectq.meta import get_control_count, has_negative_control +from projectq.ops import ( + AllocateQubitGate, + BarrierGate, + ControlledGate, + DaggeredGate, + DeallocateQubitGate, + HGate, + MeasureGate, + R, + Rx, + Rxx, + Ry, + Ryy, + Rz, + Rzz, + Sdag, + SGate, + SqrtXGate, + SwapGate, + Tdag, + TGate, + XGate, + YGate, + ZGate, + get_inverse, +) + +from .._exceptions import InvalidCommandError + +IONQ_PROVIDER_ID = 'ionq' +QUANTINUUM_PROVIDER_ID = 'quantinuum' + +# https://docs.ionq.com/#section/Supported-Gates +IONQ_GATE_MAP = { + HGate: 'h', + SGate: 's', + SqrtXGate: 'v', + SwapGate: 'swap', + TGate: 't', + Rx: 'rx', + Rxx: 'xx', + Ry: 'ry', + Ryy: 'yy', + Rz: 'rz', + Rzz: 'zz', + XGate: 'x', + YGate: 'y', + ZGate: 'z', +} # excluding controlled, conjugate-transpose and meta gates + +IONQ_SUPPORTED_GATES = tuple(IONQ_GATE_MAP.keys()) + +QUANTINUUM_GATE_MAP = { + BarrierGate: 'barrier', + HGate: 'h', + Rx: 'rx', + Rxx: 'rxx', + Ry: 'ry', + Ryy: 'ryy', + Rz: 'rz', + Rzz: 'rzz', + SGate: 's', + TGate: 't', + XGate: 'x', + YGate: 'y', + ZGate: 'z', +} # excluding controlled, conjugate-transpose and meta gates + +QUANTINUUM_SUPPORTED_GATES = tuple(QUANTINUUM_GATE_MAP.keys()) + +V = SqrtXGate() +Vdag = get_inverse(V) + + +def is_available_ionq(cmd): + """ + Test if IonQ 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 + + if has_negative_control(cmd): + return False + + if isinstance(gate, ControlledGate): + num_ctrl_qubits = gate._n # pylint: disable=protected-access + else: + num_ctrl_qubits = get_control_count(cmd) + + # Get base gate wrapped in ControlledGate class + if isinstance(gate, ControlledGate): + gate = gate._gate # pylint: disable=protected-access + + # NOTE: IonQ supports up to 7 control qubits + if 0 < num_ctrl_qubits <= 7: + return isinstance(gate, (XGate,)) + + # Gates without control bits + if num_ctrl_qubits == 0: + supported = isinstance(gate, IONQ_SUPPORTED_GATES) + supported_meta = isinstance(gate, (MeasureGate, AllocateQubitGate, DeallocateQubitGate)) + supported_transpose = gate in (Sdag, Tdag, Vdag) + + return supported or supported_meta or supported_transpose + + return False + + +def is_available_quantinuum(cmd): + """ + Test if Quantinuum 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 + + if has_negative_control(cmd): + return False + + if isinstance(gate, ControlledGate): + num_ctrl_qubits = gate._n # pylint: disable=protected-access + else: + num_ctrl_qubits = get_control_count(cmd) + + # Get base gate wrapped in ControlledGate class + if isinstance(gate, ControlledGate): + gate = gate._gate # pylint: disable=protected-access + + # TODO: NEEDED CONFIRMATION- Does Quantinuum support more than 2 control gates? + if 0 < num_ctrl_qubits <= 2: + return isinstance(gate, (XGate, ZGate)) + + # Gates without control bits. + if num_ctrl_qubits == 0: + supported = isinstance(gate, QUANTINUUM_SUPPORTED_GATES) + supported_meta = isinstance(gate, (MeasureGate, AllocateQubitGate, DeallocateQubitGate, BarrierGate)) + supported_transpose = gate in (Sdag, Tdag) + return supported or supported_meta or supported_transpose + + return False + + +def to_json(cmd): + """ + Convert ProjectQ command to JSON format. + + Args: + cmd (Command): A command to process. + + Returns: + dict: JSON format of given command. + """ + # Invalid command, raise exception + if not is_available_ionq(cmd): + raise InvalidCommandError('Invalid command:', str(cmd)) + + gate = cmd.gate + + if isinstance(gate, ControlledGate): + inner_gate = gate._gate # pylint: disable=protected-access + gate_type = type(inner_gate) + elif isinstance(gate, DaggeredGate): + gate_type = type(gate.get_inverse()) + else: + gate_type = type(gate) + + gate_name = IONQ_GATE_MAP.get(gate_type) + + # Daggered gates get special treatment + if isinstance(gate, DaggeredGate): + gate_name = gate_name + 'i' + + # Controlled gates get special treatment too + if isinstance(gate, ControlledGate): + all_qubits = [qb.id for qureg in cmd.qubits for qb in qureg] + controls = all_qubits[: gate._n] # pylint: disable=protected-access + targets = all_qubits[gate._n :] # noqa: E203 # pylint: disable=protected-access + else: + controls = [qb.id for qb in cmd.control_qubits] + targets = [qb.id for qureg in cmd.qubits for qb in qureg] + + # 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 + + return gate_dict + + +def to_qasm(cmd): # pylint: disable=too-many-return-statements,too-many-branches + """ + Convert ProjectQ command to QASM format. + + Args: + cmd (Command): A command to process. + + Returns: + dict: QASM format of given command. + """ + # Invalid command, raise exception + if not is_available_quantinuum(cmd): + raise InvalidCommandError('Invalid command:', str(cmd)) + + gate = cmd.gate + + if isinstance(gate, ControlledGate): + inner_gate = gate._gate # pylint: disable=protected-access + gate_type = type(inner_gate) + elif isinstance(gate, DaggeredGate): + gate_type = type(gate.get_inverse()) + else: + gate_type = type(gate) + + gate_name = QUANTINUUM_GATE_MAP.get(gate_type) + + # Daggered gates get special treatment + if isinstance(gate, DaggeredGate): + gate_name = gate_name + 'dg' + + # Controlled gates get special treatment too + if isinstance(gate, ControlledGate): + all_qubits = [qb.id for qureg in cmd.qubits for qb in qureg] + controls = all_qubits[: gate._n] # pylint: disable=protected-access + targets = all_qubits[gate._n :] # noqa: E203 # pylint: disable=protected-access + else: + controls = [qb.id for qb in cmd.control_qubits] + targets = [qb.id for qureg in cmd.qubits for qb in qureg] + + # Barrier gate + if isinstance(gate, BarrierGate): + qb_str = "" + for pos in targets: + qb_str += f"q[{pos}], " + return f"{gate_name} {qb_str[:-2]};" + + # Daggered gates + if gate in (Sdag, Tdag): + return f"{gate_name} q[{targets[0]}];" + + # Controlled gates + if len(controls) > 0: + # 1-Controlled gates + if len(controls) == 1: + gate_name = 'c' + gate_name + return f"{gate_name} q[{controls[0]}], q[{targets[0]}];" + + # 2-Controlled gates + if len(controls) == 2: + gate_name = 'cc' + gate_name + return f"{gate_name} q[{controls[0]}], q[{controls[1]}], q[{targets[0]}];" + + raise InvalidCommandError('Invalid command:', str(cmd)) # pragma: no cover + + # Single qubit gates + if len(targets) == 1: + # Standard gates + if isinstance(gate, (HGate, XGate, YGate, ZGate, SGate, TGate)): + return f"{gate_name} q[{targets[0]}];" + + # Rotational gates + if isinstance(gate, (Rx, Ry, Rz)): + return f"{gate_name}({gate.angle}) q[{targets[0]}];" + + raise InvalidCommandError('Invalid command:', str(cmd)) # pragma: no cover + + # Two qubit gates + if len(targets) == 2: + # Rotational gates + if isinstance(gate, (Rxx, Ryy, Rzz)): + return f"{gate_name}({gate.angle}) q[{targets[0]}], q[{targets[1]}];" + + raise InvalidCommandError('Invalid command:', str(cmd)) + + # Invalid command + raise InvalidCommandError('Invalid command:', str(cmd)) # pragma: no cover diff --git a/projectq/backends/_azure/_utils_test.py b/projectq/backends/_azure/_utils_test.py new file mode 100644 index 000000000..8fdf70b48 --- /dev/null +++ b/projectq/backends/_azure/_utils_test.py @@ -0,0 +1,642 @@ +# Copyright 2022 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._azure._utils.py.""" + +import math + +import pytest + +from projectq.cengines import DummyEngine, MainEngine +from projectq.ops import ( + CNOT, + CX, + NOT, + Allocate, + Barrier, + C, + Command, + Deallocate, + H, + Measure, + Rx, + Rxx, + Ry, + Ryy, + Rz, + Rzz, + S, + Sdag, + Sdagger, + SqrtX, + SqrtXGate, + Swap, + T, + Tdag, + Tdagger, + X, + Y, + Z, + get_inverse, +) +from projectq.types import WeakQubitRef + +from .._exceptions import InvalidCommandError + +_has_azure_quantum = True +try: + import azure.quantum # noqa: F401 + + from projectq.backends._azure._utils import ( + is_available_ionq, + is_available_quantinuum, + to_json, + to_qasm, + ) +except ImportError: + _has_azure_quantum = False + +has_azure_quantum = pytest.mark.skipif(not _has_azure_quantum, reason="azure quantum package is not installed") + +V = SqrtXGate() +Vdag = get_inverse(V) + + +@has_azure_quantum +@pytest.mark.parametrize( + "single_qubit_gate, expected_result", + [ + (NOT, True), + (X, True), + (Y, True), + (Z, True), + (H, True), + (S, True), + (T, True), + (SqrtX, True), + (Rx(math.pi / 2), True), + (Ry(math.pi / 2), True), + (Rz(math.pi / 2), True), + (Sdag, True), + (Sdagger, True), + (Tdag, True), + (Tdagger, True), + (Vdag, True), + (Measure, True), + (Allocate, True), + (Deallocate, True), + (Barrier, False), + ], +) +def test_ionq_is_available_single_qubit_gates(single_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + + cmd = Command(eng, single_qubit_gate, (qb0,)) + assert is_available_ionq(cmd) == expected_result, f'Failing on {single_qubit_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "two_qubit_gate, expected_result", + [ + (Swap, True), + (CNOT, True), + (CX, True), + (Rxx(math.pi / 2), True), + (Ryy(math.pi / 2), True), + (Rzz(math.pi / 2), True), + ], +) +def test_ionq_is_available_two_qubit_gates(two_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + + cmd = Command(eng, two_qubit_gate, (qb0, qb1)) + assert is_available_ionq(cmd) == expected_result, f'Failing on {two_qubit_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, True), + (X, 1, True), + (X, 2, True), + (X, 3, True), + (X, 4, True), + (X, 5, True), + (X, 6, True), + (X, 7, True), + (X, 8, False), + (Y, 1, False), + ], +) +def test_ionq_is_available_n_controlled_qubits_type_1(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + # pass controls as parameter + cmd = Command(eng, base_gate, (qb0,), controls=qureg) + assert is_available_ionq(cmd) == expected_result, 'Failing on {}-controlled {} gate'.format( + num_ctrl_qubits, base_gate + ) + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, True), + (X, 1, True), + (X, 2, True), + (X, 3, True), + (X, 4, True), + (X, 5, True), + (X, 6, True), + (X, 7, True), + (X, 8, False), + (Y, 1, False), + ], +) +def test_ionq_is_available_n_controlled_qubits_type_2(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + n_controlled_gate = base_gate + for index in range(num_ctrl_qubits): + n_controlled_gate = C(n_controlled_gate) + + # pass controls as targets + cmd = Command( + eng, + n_controlled_gate, + ( + qureg, + qb0, + ), + ) + assert is_available_ionq(cmd) == expected_result, 'Failing on {}-controlled {} gate'.format( + num_ctrl_qubits, base_gate + ) + + +@has_azure_quantum +def test_ionq_is_available_negative_control(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg) + assert is_available_ionq(cmd), "Failing on negative controlled gate" + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg, control_state='1') + assert is_available_ionq(cmd), "Failing on negative controlled gate" + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg, control_state='0') + assert not is_available_ionq(cmd), "Failing on negative controlled gate" + + +@has_azure_quantum +@pytest.mark.parametrize( + "single_qubit_gate, expected_result", + [ + (NOT, True), + (X, True), + (Y, True), + (Z, True), + (H, True), + (S, True), + (T, True), + (Rx(math.pi / 2), True), + (Ry(math.pi / 2), True), + (Rz(math.pi / 2), True), + (Sdag, True), + (Sdagger, True), + (Tdag, True), + (Tdagger, True), + (Measure, True), + (Allocate, True), + (Deallocate, True), + (Barrier, True), + (SqrtX, False), + (Vdag, False), + ], +) +def test_quantinuum_is_available_single_qubit_gates(single_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + + cmd = Command(eng, single_qubit_gate, (qb0,)) + assert is_available_quantinuum(cmd) == expected_result, f'Failing on {single_qubit_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "two_qubit_gate, expected_result", + [ + (CNOT, True), + (CX, True), + (Rxx(math.pi / 2), True), + (Ryy(math.pi / 2), True), + (Rzz(math.pi / 2), True), + (Swap, False), + ], +) +def test_quantinuum_is_available_two_qubit_gates(two_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + + cmd = Command(eng, two_qubit_gate, (qb0, qb1)) + assert is_available_quantinuum(cmd) == expected_result, f'Failing on {two_qubit_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, True), + (X, 1, True), + (X, 2, True), + (X, 3, False), + (Z, 0, True), + (Z, 1, True), + (Z, 2, True), + (Z, 3, False), + (Y, 1, False), + ], +) +def test_quantinuum_is_available_n_controlled_qubits_type_1(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + cmd = Command(eng, base_gate, (qb0,), controls=qureg) + assert is_available_quantinuum(cmd) == expected_result, 'Failing on {}-controlled {} gate'.format( + num_ctrl_qubits, base_gate + ) + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, True), + (X, 1, True), + (X, 2, True), + (X, 3, False), + (Z, 0, True), + (Z, 1, True), + (Z, 2, True), + (Z, 3, False), + (Y, 1, False), + ], +) +def test_quantinuum_is_available_n_controlled_qubits_type_2(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + n_controlled_gate = base_gate + for index in range(num_ctrl_qubits): + n_controlled_gate = C(n_controlled_gate) + + # pass controls as targets + cmd = Command( + eng, + n_controlled_gate, + ( + qureg, + qb0, + ), + ) + assert is_available_quantinuum(cmd) == expected_result, 'Failing on {}-controlled {} gate'.format( + num_ctrl_qubits, base_gate + ) + + +@has_azure_quantum +def test_quantinuum_is_available_negative_control(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg) + assert is_available_quantinuum(cmd), "Failing on negative controlled gate" + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg, control_state='1') + assert is_available_quantinuum(cmd), "Failing on negative controlled gate" + + cmd = Command(eng, X, qubits=(qb0,), controls=qureg, control_state='0') + assert not is_available_quantinuum(cmd), "Failing on negative controlled gate" + + +@has_azure_quantum +@pytest.mark.parametrize( + "single_qubit_gate, expected_result", + [ + (NOT, {'gate': 'x', 'targets': [0]}), + (X, {'gate': 'x', 'targets': [0]}), + (Y, {'gate': 'y', 'targets': [0]}), + (Z, {'gate': 'z', 'targets': [0]}), + (H, {'gate': 'h', 'targets': [0]}), + (S, {'gate': 's', 'targets': [0]}), + (T, {'gate': 't', 'targets': [0]}), + (Rx(0), {'gate': 'rx', 'rotation': 0.0, 'targets': [0]}), + (Ry(0), {'gate': 'ry', 'rotation': 0.0, 'targets': [0]}), + (Rz(0), {'gate': 'rz', 'rotation': 0.0, 'targets': [0]}), + (Rx(math.pi / 4), {'gate': 'rx', 'rotation': 0.785398163397, 'targets': [0]}), + (Ry(math.pi / 4), {'gate': 'ry', 'rotation': 0.785398163397, 'targets': [0]}), + (Rz(math.pi / 4), {'gate': 'rz', 'rotation': 0.785398163397, 'targets': [0]}), + (Rx(math.pi / 2), {'gate': 'rx', 'rotation': 1.570796326795, 'targets': [0]}), + (Ry(math.pi / 2), {'gate': 'ry', 'rotation': 1.570796326795, 'targets': [0]}), + (Rz(math.pi / 2), {'gate': 'rz', 'rotation': 1.570796326795, 'targets': [0]}), + (Rx(math.pi), {'gate': 'rx', 'rotation': 3.14159265359, 'targets': [0]}), + (Ry(math.pi), {'gate': 'ry', 'rotation': 3.14159265359, 'targets': [0]}), + (Rz(math.pi), {'gate': 'rz', 'rotation': 3.14159265359, 'targets': [0]}), + (Sdag, {'gate': 'si', 'targets': [0]}), + (Sdagger, {'gate': 'si', 'targets': [0]}), + (Tdag, {'gate': 'ti', 'targets': [0]}), + (Tdagger, {'gate': 'ti', 'targets': [0]}), + (SqrtX, {'gate': 'v', 'targets': [0]}), + (Vdag, {'gate': 'vi', 'targets': [0]}), + ], +) +def test_to_json_single_qubit_gates(single_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = WeakQubitRef(engine=eng, idx=0) + + actual_result = to_json(Command(eng, single_qubit_gate, ([qb0],))) + + assert len(actual_result) == len(expected_result) + assert actual_result['gate'] == expected_result['gate'] + assert actual_result['targets'] == expected_result['targets'] + if 'rotation' in expected_result: + assert actual_result['rotation'] == pytest.approx(expected_result['rotation']) + + +@has_azure_quantum +@pytest.mark.parametrize( + "two_qubit_gate, expected_result", + [ + (Swap, {'gate': 'swap', 'targets': [0, 1]}), + (CNOT, {'gate': 'x', 'targets': [1], 'controls': [0]}), + (CX, {'gate': 'x', 'targets': [1], 'controls': [0]}), + (Rxx(0), {'gate': 'xx', 'rotation': 0.0, 'targets': [0, 1]}), + (Ryy(0), {'gate': 'yy', 'rotation': 0.0, 'targets': [0, 1]}), + (Rzz(0), {'gate': 'zz', 'rotation': 0.0, 'targets': [0, 1]}), + (Rxx(math.pi / 4), {'gate': 'xx', 'rotation': 0.785398163397, 'targets': [0, 1]}), + (Ryy(math.pi / 4), {'gate': 'yy', 'rotation': 0.785398163397, 'targets': [0, 1]}), + (Rzz(math.pi / 4), {'gate': 'zz', 'rotation': 0.785398163397, 'targets': [0, 1]}), + (Rxx(math.pi / 2), {'gate': 'xx', 'rotation': 1.570796326795, 'targets': [0, 1]}), + (Ryy(math.pi / 2), {'gate': 'yy', 'rotation': 1.570796326795, 'targets': [0, 1]}), + (Rzz(math.pi / 2), {'gate': 'zz', 'rotation': 1.570796326795, 'targets': [0, 1]}), + (Rxx(math.pi), {'gate': 'xx', 'rotation': 3.14159265359, 'targets': [0, 1]}), + (Ryy(math.pi), {'gate': 'yy', 'rotation': 3.14159265359, 'targets': [0, 1]}), + (Rzz(math.pi), {'gate': 'zz', 'rotation': 3.14159265359, 'targets': [0, 1]}), + ], +) +def test_to_json_two_qubit_gates(two_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = WeakQubitRef(engine=eng, idx=0) + qb1 = WeakQubitRef(engine=eng, idx=1) + + actual_result = to_json(Command(eng, two_qubit_gate, ([qb0], [qb1]))) + + assert len(actual_result) == len(expected_result) + assert actual_result['gate'] == expected_result['gate'] + assert actual_result['targets'] == expected_result['targets'] + if 'rotation' in expected_result: + assert actual_result['rotation'] == pytest.approx(expected_result['rotation']) + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, {'gate': 'x', 'targets': [0]}), + (X, 1, {'gate': 'x', 'targets': [0], 'controls': [1]}), + (X, 2, {'gate': 'x', 'targets': [0], 'controls': [1, 2]}), + (X, 3, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3]}), + (X, 4, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4]}), + (X, 5, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5]}), + (X, 6, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5, 6]}), + (X, 7, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5, 6, 7]}), + ], +) +def test_to_json_n_controlled_qubits_type_1(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + cmd = Command(eng, base_gate, (qb0,), controls=qureg) + assert to_json(cmd) == expected_result, f'Failing on {num_ctrl_qubits}-controlled {base_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, {'gate': 'x', 'targets': [0]}), + (X, 1, {'gate': 'x', 'targets': [0], 'controls': [1]}), + (X, 2, {'gate': 'x', 'targets': [0], 'controls': [1, 2]}), + (X, 3, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3]}), + (X, 4, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4]}), + (X, 5, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5]}), + (X, 6, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5, 6]}), + (X, 7, {'gate': 'x', 'targets': [0], 'controls': [1, 2, 3, 4, 5, 6, 7]}), + ], +) +def test_to_json_n_controlled_qubits_type_2(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + n_controlled_gate = base_gate + for index in range(num_ctrl_qubits): + n_controlled_gate = C(n_controlled_gate) + + # pass controls as targets + cmd = Command( + eng, + n_controlled_gate, + ( + qureg, + qb0, + ), + ) + assert to_json(cmd) == expected_result, f'Failing on {num_ctrl_qubits}-controlled {base_gate} gate' + + +@has_azure_quantum +def test_to_json_invalid_command_gate_not_available(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + + cmd = Command(eng, Barrier, (qb0,)) + with pytest.raises(InvalidCommandError): + to_json(cmd) + + +@has_azure_quantum +@pytest.mark.parametrize( + "single_qubit_gate, expected_result", + [ + (NOT, 'x q[0];'), + (X, 'x q[0];'), + (Y, 'y q[0];'), + (Z, 'z q[0];'), + (H, 'h q[0];'), + (S, 's q[0];'), + (T, 't q[0];'), + (Rx(0), 'rx(0.0) q[0];'), + (Ry(0), 'ry(0.0) q[0];'), + (Rz(0), 'rz(0.0) q[0];'), + (Rx(math.pi / 4), 'rx(0.785398163397) q[0];'), + (Ry(math.pi / 4), 'ry(0.785398163397) q[0];'), + (Rz(math.pi / 4), 'rz(0.785398163397) q[0];'), + (Rx(math.pi / 2), 'rx(1.570796326795) q[0];'), + (Ry(math.pi / 2), 'ry(1.570796326795) q[0];'), + (Rz(math.pi / 2), 'rz(1.570796326795) q[0];'), + (Rx(math.pi), 'rx(3.14159265359) q[0];'), + (Ry(math.pi), 'ry(3.14159265359) q[0];'), + (Rz(math.pi), 'rz(3.14159265359) q[0];'), + (Sdag, 'sdg q[0];'), + (Sdagger, 'sdg q[0];'), + (Tdag, 'tdg q[0];'), + (Tdagger, 'tdg q[0];'), + ], +) +def test_to_qasm_single_qubit_gates(single_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = WeakQubitRef(engine=eng, idx=0) + + assert to_qasm(Command(eng, single_qubit_gate, ([qb0],))) == expected_result + + +@has_azure_quantum +@pytest.mark.parametrize( + "two_qubit_gate, expected_result", + [ + (CNOT, 'cx q[0], q[1];'), + (CX, 'cx q[0], q[1];'), + (Rxx(0), 'rxx(0.0) q[0], q[1];'), + (Ryy(0), 'ryy(0.0) q[0], q[1];'), + (Rzz(0), 'rzz(0.0) q[0], q[1];'), + (Rxx(math.pi / 4), 'rxx(0.785398163397) q[0], q[1];'), + (Ryy(math.pi / 4), 'ryy(0.785398163397) q[0], q[1];'), + (Rzz(math.pi / 4), 'rzz(0.785398163397) q[0], q[1];'), + (Rxx(math.pi / 2), 'rxx(1.570796326795) q[0], q[1];'), + (Ryy(math.pi / 2), 'ryy(1.570796326795) q[0], q[1];'), + (Rzz(math.pi / 2), 'rzz(1.570796326795) q[0], q[1];'), + (Rxx(math.pi), 'rxx(3.14159265359) q[0], q[1];'), + (Ryy(math.pi), 'ryy(3.14159265359) q[0], q[1];'), + (Rzz(math.pi), 'rzz(3.14159265359) q[0], q[1];'), + ], +) +def test_to_qasm_two_qubit_gates(two_qubit_gate, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = WeakQubitRef(engine=eng, idx=0) + qb1 = WeakQubitRef(engine=eng, idx=1) + + assert to_qasm(Command(eng, two_qubit_gate, ([qb0], [qb1]))) == expected_result + + +@has_azure_quantum +@pytest.mark.parametrize( + "n_qubit_gate, n, expected_result", + [ + (Barrier, 2, 'barrier q[0], q[1];'), + (Barrier, 3, 'barrier q[0], q[1], q[2];'), + (Barrier, 4, 'barrier q[0], q[1], q[2], q[3];'), + ], +) +def test_to_qasm_n_qubit_gates(n_qubit_gate, n, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qureg = eng.allocate_qureg(n) + + assert to_qasm(Command(eng, n_qubit_gate, (qureg,))) == expected_result + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, 'x q[0];'), + (X, 1, 'cx q[1], q[0];'), + (X, 2, 'ccx q[1], q[2], q[0];'), + (Z, 0, 'z q[0];'), + (Z, 1, 'cz q[1], q[0];'), + (Z, 2, 'ccz q[1], q[2], q[0];'), + ], +) +def test_to_qasm_n_controlled_qubits_type_1(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + cmd = Command(eng, base_gate, (qb0,), controls=qureg) + assert to_qasm(cmd) == expected_result, f'Failing on {num_ctrl_qubits}-controlled {base_gate} gate' + + +@has_azure_quantum +@pytest.mark.parametrize( + "base_gate, num_ctrl_qubits, expected_result", + [ + (X, 0, 'x q[0];'), + (X, 1, 'cx q[1], q[0];'), + (X, 2, 'ccx q[1], q[2], q[0];'), + (Z, 0, 'z q[0];'), + (Z, 1, 'cz q[1], q[0];'), + (Z, 2, 'ccz q[1], q[2], q[0];'), + ], +) +def test_to_qasm_n_controlled_qubits_type_2(base_gate, num_ctrl_qubits, expected_result): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qb0 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits) + + n_controlled_gate = base_gate + for index in range(num_ctrl_qubits): + n_controlled_gate = C(n_controlled_gate) + + # pass controls as targets + cmd = Command( + eng, + n_controlled_gate, + ( + qureg, + qb0, + ), + ) + assert to_qasm(cmd) == expected_result, f'Failing on {num_ctrl_qubits}-controlled {base_gate} gate' + + +@has_azure_quantum +def test_to_qasm_invalid_command_gate_not_available(): + qb0 = WeakQubitRef(None, idx=0) + qb1 = WeakQubitRef(None, idx=1) + + cmd = Command(None, SqrtX, qubits=((qb0,),)) + with pytest.raises(InvalidCommandError): + to_qasm(cmd) + + # NB: unsupported gate for 2 qubits + cmd = Command(None, X, qubits=((qb0, qb1),)) + with pytest.raises(InvalidCommandError): + to_qasm(cmd) diff --git a/projectq/backends/_circuits/__init__.py b/projectq/backends/_circuits/__init__.py index 638ddfbbc..eaf0abb56 100755 --- a/projectq/backends/_circuits/__init__.py +++ b/projectq/backends/_circuits/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/_circuits/_drawer.py b/projectq/backends/_circuits/_drawer.py index 7d67f6190..071ad2cea 100755 --- a/projectq/backends/_circuits/_drawer.py +++ b/projectq/backends/_circuits/_drawer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +14,6 @@ """Contain a compiler engine which generates TikZ Latex code describing the circuit.""" -from builtins import input from projectq.cengines import BasicEngine, LastEngineException from projectq.meta import get_control_count @@ -64,65 +62,66 @@ class CircuitDrawer(BasicEngine): .. code-block:: python circuit_backend = CircuitDrawer() - circuit_backend.set_qubit_locations({0: 1, 1: 0}) # swap lines 0 and 1 + circuit_backend.set_qubit_locations({0: 1, 1: 0}) # swap lines 0 and 1 eng = MainEngine(circuit_backend) - ... # run quantum algorithm on this main engine + ... # run quantum algorithm on this main engine - print(circuit_backend.get_latex()) # prints LaTeX code + print(circuit_backend.get_latex()) # prints LaTeX code To see the qubit IDs in the generated circuit, simply set the `draw_id` option in the settings.json file under "gates":"AllocateQubitGate" to True: .. code-block:: python - "gates": { - "AllocateQubitGate": { - "draw_id": True, - "height": 0.15, - "width": 0.2, - "pre_offset": 0.1, - "offset": 0.1 - }, - ... + { + "gates": { + "AllocateQubitGate": { + "draw_id": True, + "height": 0.15, + "width": 0.2, + "pre_offset": 0.1, + "offset": 0.1, + }, + # ... + } + } The settings.json file has the following structure: .. code-block:: python { - "control": { # settings for control "circle" - "shadow": false, - "size": 0.1 - }, - "gate_shadow": true, # enable/disable shadows for all gates + "control": {"shadow": false, "size": 0.1}, # settings for control "circle" + "gate_shadow": true, # enable/disable shadows for all gates "gates": { - "GateClassString": { - GATE_PROPERTIES - } - "GateClassString2": { - ... + "GateClassString": {GATE_PROPERTIES}, + "GateClassString2": { + # ... + }, + }, + "lines": { # settings for qubit lines + "double_classical": true, # draw double-lines for + # classical bits + "double_lines_sep": 0.04, # gap between the two lines + # for double lines + "init_quantum": true, # start out with quantum bits + "style": "very thin", # line style }, - "lines": { # settings for qubit lines - "double_classical": true, # draw double-lines for - # classical bits - "double_lines_sep": 0.04, # gap between the two lines - # for double lines - "init_quantum": true, # start out with quantum bits - "style": "very thin" # line style - } } All gates (except for the ones requiring special treatment) support the following properties: .. code-block:: python - "GateClassString": { - "height": GATE_HEIGHT, - "width": GATE_WIDTH - "pre_offset": OFFSET_BEFORE_PLACEMENT, - "offset": OFFSET_AFTER_PLACEMENT, - }, + { + "GateClassString": { + "height": GATE_HEIGHT, + "width": GATE_WIDTH, + "pre_offset": OFFSET_BEFORE_PLACEMENT, + "offset": OFFSET_AFTER_PLACEMENT, + } + } """ @@ -228,7 +227,7 @@ def _print_cmd(self, cmd): if self._accept_input: meas = None while meas not in ('0', '1', 1, 0): - prompt = "Input measurement result (0 or 1) for qubit " + str(qubit) + ": " + prompt = f"Input measurement result (0 or 1) for qubit {str(qubit)}: " meas = input(prompt) else: meas = self._default_measure diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py index 6098ebfde..7c6b58555 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib.py +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +16,6 @@ import itertools import re -from builtins import input from projectq.cengines import BasicEngine, LastEngineException from projectq.meta import get_control_count @@ -37,14 +35,14 @@ def _format_gate_str(cmd): params_str_list = [] for param in params: try: - params_str_list.append('{0:.2f}'.format(float(param))) + params_str_list.append(f'{float(param):.2f}') except ValueError: if len(param) < 8: params_str_list.append(param) else: - params_str_list.append(param[:5] + '...') + params_str_list.append(f"{param[:5]}...") - gate_name += '(' + ','.join(params_str_list) + ')' + gate_name += f"({','.join(params_str_list)})" return gate_name @@ -120,7 +118,7 @@ def _process(self, cmd): # pylint: disable=too-many-branches if self._accept_input: measurement = None while measurement not in ('0', '1', 1, 0): - prompt = "Input measurement result (0 or 1) for qubit {}: ".format(qubit) + prompt = f"Input measurement result (0 or 1) for qubit {qubit}: " measurement = input(prompt) else: measurement = self._default_measure diff --git a/projectq/backends/_circuits/_drawer_matplotlib_test.py b/projectq/backends/_circuits/_drawer_matplotlib_test.py index 885d835fa..9744599a4 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib_test.py +++ b/projectq/backends/_circuits/_drawer_matplotlib_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,6 +26,22 @@ from ._drawer_matplotlib import CircuitDrawerMatplotlib +class MockInputFunction: + def __init__(self, return_value=None): + self.return_value = return_value + self._orig_input_fn = __builtins__['input'] + + def _mock_input_fn(self, prompt): + print(prompt + str(self.return_value)) + return self.return_value + + def __enter__(self): + __builtins__['input'] = self._mock_input_fn + + def __exit__(self, type, value, traceback): + __builtins__['input'] = self._orig_input_fn + + def test_drawer_measurement(): drawer = CircuitDrawerMatplotlib(default_measure=0) eng = MainEngine(drawer, []) @@ -44,12 +59,9 @@ def test_drawer_measurement(): eng = MainEngine(drawer, []) qubit = eng.allocate_qubit() - old_input = _drawer.input - - _drawer.input = lambda x: '1' - Measure | qubit - assert int(qubit) == 1 - _drawer.input = old_input + with MockInputFunction(return_value='1'): + Measure | qubit + assert int(qubit) == 1 qb1 = WeakQubitRef(engine=eng, idx=1) qb2 = WeakQubitRef(engine=eng, idx=2) @@ -57,7 +69,7 @@ def test_drawer_measurement(): eng.backend._process(Command(engine=eng, gate=Measure, qubits=([qb1],), controls=[qb2])) -class MockEngine(object): +class MockEngine: def is_available(self, cmd): self.cmd = cmd self.called = True @@ -112,10 +124,10 @@ def __init__(self, *args): self.params = args def __str__(self): - param_str = '{}'.format(self.params[0]) + param_str = f'{self.params[0]}' for param in self.params[1:]: - param_str += ',{}'.format(param) - return str(self.__class__.__name__) + "(" + param_str + ")" + param_str += f',{param}' + return f"{str(self.__class__.__name__)}({param_str})" def test_drawer_draw(): diff --git a/projectq/backends/_circuits/_drawer_test.py b/projectq/backends/_circuits/_drawer_test.py index d9b94b42c..cec9488a0 100755 --- a/projectq/backends/_circuits/_drawer_test.py +++ b/projectq/backends/_circuits/_drawer_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +24,22 @@ from projectq.types import WeakQubitRef +class MockInputFunction: + def __init__(self, return_value=None): + self.return_value = return_value + self._orig_input_fn = __builtins__['input'] + + def _mock_input_fn(self, prompt): + print(prompt + str(self.return_value)) + return self.return_value + + def __enter__(self): + __builtins__['input'] = self._mock_input_fn + + def __exit__(self, type, value, traceback): + __builtins__['input'] = self._orig_input_fn + + @pytest.mark.parametrize("ordered", [False, True]) def test_drawer_getlatex(ordered): old_latex = _drawer.to_latex @@ -73,12 +88,9 @@ def test_drawer_measurement(): eng = MainEngine(drawer, []) qubit = eng.allocate_qubit() - old_input = _drawer.input - - _drawer.input = lambda x: '1' - Measure | qubit - assert int(qubit) == 1 - _drawer.input = old_input + with MockInputFunction(return_value='1'): + Measure | qubit + assert int(qubit) == 1 qb1 = WeakQubitRef(engine=eng, idx=1) qb2 = WeakQubitRef(engine=eng, idx=2) @@ -108,7 +120,7 @@ def test_drawer_qubitmapping(): drawer.set_qubit_locations({0: 1, 1: 0}) -class MockEngine(object): +class MockEngine: def is_available(self, cmd): self.cmd = cmd self.called = True diff --git a/projectq/backends/_circuits/_plot.py b/projectq/backends/_circuits/_plot.py index 7be85711d..b450309f4 100644 --- a/projectq/backends/_circuits/_plot.py +++ b/projectq/backends/_circuits/_plot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -109,11 +108,9 @@ def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): drawing_order = {qubit_id: n_qubits - qubit_id - 1 for qubit_id in list(qubit_lines)} else: if set(drawing_order) != set(qubit_lines): - raise RuntimeError('Qubit IDs in drawing_order do not match ' + 'qubit IDs in qubit_lines!') + raise RuntimeError("Qubit IDs in drawing_order do not match qubit IDs in qubit_lines!") if set(drawing_order.values()) != set(range(len(drawing_order))): - raise RuntimeError( - 'Indices of qubit wires in drawing_order must be between 0 and {}!'.format(len(drawing_order)) - ) + raise RuntimeError(f'Indices of qubit wires in drawing_order must be between 0 and {len(drawing_order)}!') plot_params = deepcopy(_DEFAULT_PLOT_PARAMS) plot_params.update(kwargs) @@ -357,8 +354,7 @@ def draw_gate( else: 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) + f"Multi-qubit gate with non-neighbouring qubits!\nGate: {gate_str} on wires {targets_order}" ) multi_qubit_gate( diff --git a/projectq/backends/_circuits/_plot_test.py b/projectq/backends/_circuits/_plot_test.py index d5f3f4f64..3e81d23d1 100644 --- a/projectq/backends/_circuits/_plot_test.py +++ b/projectq/backends/_circuits/_plot_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +28,7 @@ # ============================================================================== -class PseudoCanvas(object): +class PseudoCanvas: def __init__(self): pass @@ -40,19 +39,19 @@ def get_renderer(self): return -class PseudoFigure(object): +class PseudoFigure: def __init__(self): self.canvas = PseudoCanvas() self.dpi = 1 -class PseudoBBox(object): +class PseudoBBox: def __init__(self, width, height): self.width = width self.height = height -class PseudoText(object): +class PseudoText: def __init__(self, text): self.text = text self.figure = PseudoFigure() @@ -64,7 +63,7 @@ def remove(self): pass -class PseudoTransform(object): +class PseudoTransform: def __init__(self): pass @@ -75,7 +74,7 @@ def transform_bbox(self, bbox): return bbox -class PseudoAxes(object): +class PseudoAxes: def __init__(self): self.figure = PseudoFigure() self.transData = PseudoTransform() diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index ae3f05cd5..45499526a 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,7 +61,7 @@ def to_latex(circuit, drawing_order=None, draw_gates_in_parallel=True): Example: .. code-block:: python - settings['gates']['HGate'] = {'width': .5, 'offset': .15} + settings['gates']['HGate'] = {'width': 0.5, 'offset': 0.15} The default settings can be acquired using the get_default_settings() function, and written using write_settings(). @@ -182,27 +181,21 @@ def _header(settings): 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" + "\\tikzstyle{{operator}}=[basic,minimum size=1.5em]\n" + f"\\tikzstyle{{phase}}=[fill=black,shape=circle,minimum size={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=" + "]\n\\tikzstyle{{none}}=[inner sep=0pt,outer sep=-.5pt,minimum height=0.5cm+1pt]\n" + "\\tikzstyle{{measure}}=[operator,inner sep=0pt," + f"minimum height={settings['gates']['MeasureGate']['height']}cm," + f"minimum width={settings['gates']['MeasureGate']['width']}cm]\n" + "\\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 += f"{x_gate_radius}cm,minimum width={x_gate_radius}cm,inner sep=-1pt,{settings['lines']['style']}]\n" if settings['gate_shadow']: gate_style += ( "\\tikzset{\nshadowed/.style={preaction={transform " @@ -211,7 +204,7 @@ def _header(settings): ) 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 = f"\\tikzstyle{{edgestyle}}=[{settings['lines']['style']}]\n" return packages + init + gate_style + edge_style @@ -327,7 +320,7 @@ def to_tikz( # pylint: disable=too-many-branches,too-many-locals,too-many-state 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)]) + pos = max(self.pos[ll] for ll in range(min(all_lines), max(all_lines) + 1)) for _line in range(min(all_lines), max(all_lines) + 1): self.pos[_line] = pos + self._gate_pre_offset(gate) @@ -340,7 +333,7 @@ def to_tikz( # pylint: disable=too-many-branches,too-many-locals,too-many-state add_str = self._x_gate(lines, ctrl_lines) # and make the target qubit quantum if one of the controls is if not self.is_quantum[lines[0]]: - if sum([self.is_quantum[i] for i in ctrl_lines]) > 0: + if sum(self.is_quantum[i] for i in ctrl_lines) > 0: self.is_quantum[lines[0]] = True elif gate == Z and len(ctrl_lines) > 0: add_str = self._cz_gate(lines + ctrl_lines) @@ -360,32 +353,24 @@ def to_tikz( # pylint: disable=too-many-branches,too-many-locals,too-many-state 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[_line], - line=_line, - shift0=shift0, - shift1=shift1, - shift2=shift2, + f"\n\\node[measure,edgestyle] ({op}) at ({self.pos[_line]}" + f",-{_line}) {{}};\n\\draw[edgestyle] ([yshift=" + f"-{shift1}cm,xshift={shift2}cm]{op}.west) to " + f"[out=60,in=180] ([yshift={shift0}cm]{op}." + f"center) to [out=0, in=120] ([yshift=-{shift1}" + f"cm,xshift=-{shift2}cm]{op}.east);\n" + f"\\draw[edgestyle] ([yshift=-{shift1}cm]{op}." + f"center) to ([yshift=-{shift2}cm,xshift=-" + f"{shift1}cm]{op}.north east);" ) 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}}{}$}};" id_str = "" if self.settings['gates']['AllocateQubitGate']['draw_id']: - id_str = "^{{\\textcolor{{red}}{{{}}}}}".format(cmds[i].id) + id_str = f"^{{\\textcolor{{red}}{{{cmds[i].id}}}}}" xpos = self.pos[line] try: if self.settings['gates']['AllocateQubitGate']['allocate_at_zero']: @@ -397,18 +382,15 @@ def to_tikz( # pylint: disable=too-many-branches,too-many-locals,too-many-state xpos + self._gate_offset(gate) + self._gate_width(gate), self.pos[line], ) - add_str = add_str.format(self._op(line), xpos, line, id_str) + add_str = f"\n\\node[none] ({self._op(line)}) at ({xpos},-{line}) {{$\\Ket{{0}}{id_str}$}};" self.op_count[line] += 1 self.is_quantum[line] = self.settings['lines']['init_quantum'] elif gate == Deallocate: # draw 'end of line' op = self._op(line) - add_str = "\n\\node[none] ({}) at ({},-{}) {{}};" - 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) + add_str = f"\n\\node[none] ({op}) at ({self.pos[line]},-{line}) {{}};" + yshift = f"{str(self._gate_height(gate))}cm]" + add_str += f"\n\\draw ([yshift={yshift}{op}.center) edge [edgestyle] ([yshift=-{yshift}{op}.center);" self.op_count[line] += 1 self.pos[line] += self._gate_width(gate) + self._gate_offset(gate) else: @@ -449,46 +431,29 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): # pylint: disable=too-ma gate_str = "" for line in lines: op = self._op(line) - 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) + width = f"{0.5 * gate_width}cm" + blc = f"[xshift=-{width},yshift=-{width}]{op}.center" + trc = f"[xshift={width},yshift={width}]{op}.center" + tlc = f"[xshift=-{width},yshift={width}]{op}.center" + brc = f"[xshift={width},yshift=-{width}]{op}.center" 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=blc, - s2=trc, - s3=tlc, - s4=brc, - line=line, - pos=self.pos[line], - swap_style=swap_style, + f"\n\\node[swapstyle] ({op}) at ({self.pos[line]},-{line}) {{}};" + f"\n\\draw[{swap_style}] ({blc})--({trc});\n" + f"\\draw[{swap_style}] ({tlc})--({brc});" ) - # add a circled 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=op_mid, - line=midpoint, - pos=pos, - dagger='^{{\\dagger}}' if daggered else '', - ) + op_mid = f"line{'{}-{}'.format(*lines)}_gate{self.op_count[lines[0]]}" + dagger = '^{{\\dagger}}' if daggered else '' + gate_str += f"\n\\node[xstyle] ({op}) at ({pos},-{midpoint}){{\\scriptsize $\\frac{{1}}{{2}}{dagger}$}};" # 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 += f"\n\\draw ({self._op(lines[0])}) edge[edgestyle] ({op_mid});" + gate_str += f"\n\\draw ({op_mid}) edge[edgestyle] ({self._op(lines[1])});" if len(ctrl_lines) > 0: for ctrl in ctrl_lines: @@ -526,27 +491,18 @@ def _swap_gate(self, lines, ctrl_lines): # pylint: disable=too-many-locals gate_str = "" for line in lines: op = self._op(line) - 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) + width = f"{0.5 * gate_width}cm" + blc = f"[xshift=-{width},yshift=-{width}]{op}.center" + trc = f"[xshift={width},yshift={width}]{op}.center" + tlc = f"[xshift=-{width},yshift={width}]{op}.center" + brc = f"[xshift={width},yshift=-{width}]{op}.center" 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=blc, - s2=trc, - s3=tlc, - s4=brc, - line=line, - pos=self.pos[line], - swap_style=swap_style, + f"\n\\node[swapstyle] ({op}) at ({self.pos[line]},-{line}) {{}};" + f"\n\\draw[{swap_style}] ({blc})--({trc});\n" + f"\\draw[{swap_style}] ({tlc})--({brc});" ) gate_str += self._line(lines[0], lines[1]) @@ -584,10 +540,10 @@ def _x_gate(self, lines, ctrl_lines): 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]) + f"\n\\node[xstyle] ({op}) at ({self.pos[line]},-{line}) {{}};\n\\draw" + f"[edgestyle] ({op}.north)--({op}.south);\n\\draw" + f"[edgestyle] ({op}.west)--({op}.east);" + ) if len(ctrl_lines) > 0: for ctrl in ctrl_lines: @@ -705,8 +661,7 @@ def _phase(self, line, pos): tex_str (string): Latex string representing a control circle at the given position. """ - phase_str = "\n\\node[phase] ({}) at ({},-{}) {{}};" - return phase_str.format(self._op(line), pos, line) + return f"\n\\node[phase] ({self._op(line)}) at ({pos},-{line}) {{}};" def _op(self, line, op=None, offset=0): """ @@ -722,7 +677,7 @@ def _op(self, line, op=None, offset=0): """ if op is None: op = self.op_count[line] - return "line{}_gate{}".format(line, op + offset) + return f"line{line}_gate{op + offset}" def _line(self, point1, point2, double=False, line=None): # pylint: disable=too-many-locals,unused-argument """ @@ -755,7 +710,7 @@ def _line(self, point1, point2, double=False, line=None): # pylint: disable=too shift = "yshift={}cm" if quantum: - return "\n\\draw ({}) edge[edgestyle] ({});".format(op1, op2) + return f"\n\\draw ({op1}) edge[edgestyle] ({op2});" if point2 > point1: loc1, loc2 = loc2, loc1 @@ -799,8 +754,9 @@ def _regular_gate(self, gate, lines, ctrl_lines): # pylint: disable=too-many-lo node_str = "\n\\node[none] ({}) at ({},-{}) {{}};" 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 + node2 = ( + "\n\\node[none,minimum height={gate_height}cm,outer sep=0] ({self._op(line, offset=1)}) " + f"at ({pos + gate_width / 2.0},-{line}) {{}};" ) node3 = node_str.format(self._op(line, offset=2), pos + gate_width, line) tex_str += node1 + node2 + node3 @@ -808,15 +764,9 @@ def _regular_gate(self, gate, lines, ctrl_lines): # pylint: disable=too-many-lo 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, + f"\n\\draw[operator,edgestyle,outer sep={gate_width}cm] ([" + f"yshift={0.5 * gate_height}cm]{self._op(imin)}) rectangle ([yshift=-" + f"{0.5 * gate_height}cm]{self._op(imax, offset=2)}) node[pos=.5] {{{name}}};" ) for line in lines: diff --git a/projectq/backends/_circuits/_to_latex_test.py b/projectq/backends/_circuits/_to_latex_test.py index 0ebdc1054..3369e681a 100755 --- a/projectq/backends/_circuits/_to_latex_test.py +++ b/projectq/backends/_circuits/_to_latex_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -175,12 +174,12 @@ def test_body(): assert code.count("swapstyle") == 36 # CZ is two phases plus 2 from CNOTs + 2 from cswap + 2 from csqrtswap assert code.count("phase") == 8 - assert code.count("{{{}}}".format(str(H))) == 2 # 2 hadamard gates + assert code.count(f"{{{str(H)}}}") == 2 # 2 hadamard gates assert code.count("{$\\Ket{0}") == 3 # 3 qubits allocated # 1 cnot, 1 not gate, 3 SqrtSwap, 1 inv(SqrtSwap) assert code.count("xstyle") == 7 assert code.count("measure") == 1 # 1 measurement - assert code.count("{{{}}}".format(str(Z))) == 1 # 1 Z gate + assert code.count(f"{{{str(Z)}}}") == 1 # 1 Z gate assert code.count("{red}") == 3 @@ -378,7 +377,7 @@ def test_qubit_lines_classicalvsquantum2(): H | action code = drawer.get_latex() - assert code.count("{{{}}}".format(str(H))) == 1 # 1 Hadamard + assert code.count(f"{{{str(H)}}}") == 1 # 1 Hadamard assert code.count("{$") == 4 # four allocate gates assert code.count("node[phase]") == 3 # 3 controls @@ -397,7 +396,7 @@ def test_qubit_lines_classicalvsquantum3(): H | (action1, action2) code = drawer.get_latex() - assert code.count("{{{}}}".format(str(H))) == 1 # 1 Hadamard + assert code.count(f"{{{str(H)}}}") == 1 # 1 Hadamard assert code.count("{$") == 7 # 8 allocate gates assert code.count("node[phase]") == 3 # 1 control # (other controls are within the gate -> are not drawn) diff --git a/projectq/backends/_exceptions.py b/projectq/backends/_exceptions.py index 8626df65c..9ebd6390a 100644 --- a/projectq/backends/_exceptions.py +++ b/projectq/backends/_exceptions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_ibm/__init__.py b/projectq/backends/_ibm/__init__.py index 21c3b1789..db7bbddb4 100755 --- a/projectq/backends/_ibm/__init__.py +++ b/projectq/backends/_ibm/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 238f11bae..2a11aa5a7 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -169,15 +168,15 @@ def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements elif gate == NOT and get_control_count(cmd) == 1: 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.qasm += f"\ncx q[{ctrl_pos}], q[{qb_pos}];" self._json.append({'qubits': [ctrl_pos, qb_pos], 'name': 'cx'}) elif gate == Barrier: qb_pos = [qb.id for qr in cmd.qubits for qb in qr] self.qasm += "\nbarrier " qb_str = "" for pos in qb_pos: - qb_str += "q[{}], ".format(pos) - self.qasm += qb_str[:-2] + ";" + qb_str += f"q[{pos}], " + self.qasm += f"{qb_str[:-2]};" self._json.append({'qubits': qb_pos, 'name': 'barrier'}) elif isinstance(gate, (Rx, Ry, Rz)): qb_pos = cmd.qubits[0][0].id @@ -191,11 +190,11 @@ def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements gate_qasm = u_strs[str(gate)[0:2]].format(gate.angle) gate_name = u_name[str(gate)[0:2]] params = u_angle[str(gate)[0:2]] - self.qasm += "\n{} q[{}];".format(gate_qasm, qb_pos) + self.qasm += f"\n{gate_qasm} q[{qb_pos}];" self._json.append({'qubits': [qb_pos], 'name': gate_name, 'params': params}) elif gate == H: qb_pos = cmd.qubits[0][0].id - self.qasm += "\nu2(0,pi/2) q[{}];".format(qb_pos) + self.qasm += f"\nu2(0,pi/2) q[{qb_pos}];" self._json.append({'qubits': [qb_pos], 'name': 'u2', 'params': [0, 3.141592653589793]}) else: raise InvalidCommandError( @@ -213,9 +212,8 @@ 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 {}. Please make sure " - "eng.flush() was called and that the qubit " - "was eliminated during optimization.".format(qb_id) + f"Unknown qubit id {qb_id}. " + "Please make sure eng.flush() was called and that the qubit was eliminated during optimization." ) return mapping[qb_id] @@ -268,7 +266,7 @@ def _run(self): # pylint: disable=too-many-locals # 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[{0}] -> c[{0}];".format(qb_loc) + self.qasm += f"\nmeasure q[{qb_loc}] -> c[{qb_loc}];" self._json.append({'qubits': [qb_loc], 'name': 'measure', 'memory': [qb_loc]}) # return if no operations / measurements have been performed. if self.qasm == "": @@ -307,7 +305,7 @@ def _run(self): # pylint: disable=too-many-locals measured = "" for state in counts: probability = counts[state] * 1.0 / self._num_runs - state = "{0:b}".format(int(state, 0)) + state = f"{int(state, 0):b}" state = state.zfill(max_qubit_id) # states in ibmq are right-ordered, so need to reverse state string state = state[::-1] @@ -318,7 +316,7 @@ def _run(self): # pylint: disable=too-many-locals star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: - print(str(state) + " with p = " + str(probability) + star) + print(f"{str(state)} with p = {probability}{star}") # register measurement result from IBM for qubit_id in self._measured_ids: diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index a30233b44..400e235ee 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -219,12 +218,12 @@ def get_result( job_status_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/' + execution_id if verbose: - print("Waiting for results. [Job ID: {}]".format(execution_id)) + print(f"Waiting for results. [Job ID: {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)) + raise Exception(f"Interrupted. The ID of your submitted job is {execution_id}.") try: signal.signal(signal.SIGINT, _handle_sigint_during_get_result) @@ -271,20 +270,20 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover # 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(f"Error while running the code. Last status: {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) + f"Device went offline. The ID of your submitted job is {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(f"Timeout. The ID of your submitted job is {execution_id}.") def show_devices(token=None, verbose=False): @@ -368,15 +367,12 @@ def send( 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) + f"The device is too small ({qmax} qubits available) for the code " + f"requested({qneeded} qubits needed) Try to look for another device with more qubits" ) raise DeviceTooSmall("Device is too small.") if verbose: - print("- Running code: {}".format(info)) + print(f"- Running code: {info}") execution_id = ibmq_session.run(info, device) if verbose: print("- Waiting for results...") diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 69b067723..655a698e3 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -96,7 +95,7 @@ def raise_for_status(self): 200, ) # STEP2 - elif args[1] == "/" + execution_id + "/jobUploadUrl" and request_num[0] == 3: + elif args[1] == f"/{execution_id}/jobUploadUrl" and request_num[0] == 3: request_num[0] += 1 return MockResponse({"url": "s3_url"}, 200) # STEP5 @@ -104,7 +103,7 @@ def raise_for_status(self): args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}", ) and not result_ready[0] and request_num[0] == 5 @@ -116,7 +115,7 @@ def raise_for_status(self): args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}", ) and result_ready[0] and request_num[0] == 7 @@ -128,9 +127,7 @@ def raise_for_status(self): args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl".format( - execution_id=execution_id - ), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl", ) and request_num[0] == 8 ): @@ -170,7 +167,7 @@ def raise_for_status(self): answer1 = { 'objectStorageInfo': { 'downloadQObjectUrlEndpoint': 'url_dld_endpoint', - 'uploadQobjectUrlEndpoint': '/' + execution_id + '/jobUploadUrl', + 'uploadQobjectUrlEndpoint': f"/{execution_id}/jobUploadUrl", 'uploadUrl': 'url_upld', }, 'id': execution_id, @@ -178,7 +175,7 @@ def raise_for_status(self): 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, f"{jobs_url}/{execution_id}/jobDataUploaded") and request_num[0] == 4: request_num[0] += 1 return MockPostResponse({}, 200) @@ -187,9 +184,7 @@ def raise_for_status(self): args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded".format( - execution_id=execution_id - ), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded", ) and request_num[0] == 10 ): @@ -552,18 +547,18 @@ def raise_for_status(self): ], 200, ) - job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format(execution_id) + job_url = f"Network/ibm-q/Groups/open/Projects/main/Jobs/{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": + elif args[1] == f"/{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), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}", ): return MockResponse({"status": "RUNNING"}, 200) @@ -593,7 +588,7 @@ def raise_for_status(self): answer1 = { 'objectStorageInfo': { 'downloadQObjectUrlEndpoint': 'url_dld_endpoint', - 'uploadQobjectUrlEndpoint': '/' + execution_id + '/jobUploadUrl', + 'uploadQobjectUrlEndpoint': f"/{execution_id}/jobUploadUrl", 'uploadUrl': 'url_upld', }, 'id': execution_id, @@ -601,7 +596,7 @@ def raise_for_status(self): return MockPostResponse(answer1, 200) # STEP4 - elif args[1] == urljoin(_API_URL, jobs_url + "/" + execution_id + "/jobDataUploaded"): + elif args[1] == urljoin(_API_URL, f"{jobs_url}/{execution_id}/jobDataUploaded"): return MockPostResponse({}, 200) def mocked_requests_put(*args, **kwargs): @@ -685,8 +680,8 @@ def raise_for_status(self): ], 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") + job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/123e" + err_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/123ee" if args[1] == urljoin(_API_URL, job_url): request_num[0] += 1 return MockResponse({"status": "RUNNING", 'iteration': request_num[0]}, 200) @@ -760,7 +755,7 @@ def raise_for_status(self): args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}", ) and request_num[0] < 1 ): @@ -768,15 +763,13 @@ def raise_for_status(self): 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), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{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 - ), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl", ): return MockResponse({"url": "result_download_url"}, 200) # STEP7 @@ -806,9 +799,7 @@ def raise_for_status(self): # STEP8 elif args[1] == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded".format( - execution_id=execution_id - ), + f"Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded", ): return MockPostResponse({}, 200) diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index 8d744222f..dc9e2c2c7 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -1,4 +1,3 @@ -# -*- 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/_ionq/__init__.py b/projectq/backends/_ionq/__init__.py index dfc37dc08..9f1b6b9ea 100644 --- a/projectq/backends/_ionq/__init__.py +++ b/projectq/backends/_ionq/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py index 7645f3531..392864461 100644 --- a/projectq/backends/_ionq/_ionq.py +++ b/projectq/backends/_ionq/_ionq.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,6 +46,7 @@ from projectq.types import WeakQubitRef from .._exceptions import InvalidCommandError, MidCircuitMeasurementError +from .._utils import _rearrange_result from . import _ionq_http_client as http_client GATE_MAP = { @@ -68,20 +68,6 @@ 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): # pylint: disable=too-many-instance-attributes """Backend for building circuits and submitting them to the IonQ API.""" @@ -208,11 +194,11 @@ 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' # pylint: disable=protected-access + gate_name = f"{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: - raise InvalidCommandError('Invalid command: ' + str(cmd)) + raise InvalidCommandError(f"Invalid command: {str(cmd)}") # Now make sure there are no existing measurements on qubits involved # in this operation. @@ -230,7 +216,7 @@ def _store(self, cmd): if len(already_measured) > 0: err = ( 'Mid-circuit measurement is not supported. ' - 'The following qubits have already been measured: {}.'.format(list(already_measured)) + f'The following qubits have already been measured: {list(already_measured)}.' ) raise MidCircuitMeasurementError(err) @@ -331,7 +317,7 @@ def _run(self): # pylint: disable=too-many-locals verbose=self._verbose, ) if res is None: - raise RuntimeError("Failed to retrieve job with id: '{}'!".format(self._retrieve_execution)) + raise RuntimeError(f"Failed to retrieve job with id: '{self._retrieve_execution}'!") self._measured_ids = measured_ids = res['meas_qubit_ids'] # Determine random outcome from probable states. @@ -352,7 +338,7 @@ def _run(self): # pylint: disable=too-many-locals star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: # pragma: no cover - print(state + " with p = " + str(probability) + star) + print(f"{state} with p = {probability}{star}") # Register measurement results for idx, qubit_id in enumerate(measured_ids): diff --git a/projectq/backends/_ionq/_ionq_http_client.py b/projectq/backends/_ionq/_ionq_http_client.py index 4a92ecc57..b7f90cd82 100644 --- a/projectq/backends/_ionq/_ionq_http_client.py +++ b/projectq/backends/_ionq/_ionq_http_client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -114,7 +113,7 @@ def authenticate(self, token=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.headers.update({'Authorization': f'apiKey {token}'}) self.token = token def run(self, info, device): @@ -165,13 +164,7 @@ def run(self, info, device): 'code': 'UnknownError', 'error': 'An unknown error occurred!', } - raise JobSubmissionError( - "{}: {} (status={})".format( - failure['code'], - failure['error'], - status, - ) - ) + raise JobSubmissionError(f"{failure['code']}: {failure['error']} (status={status})") def get_result(self, device, execution_id, num_retries=3000, interval=1): """ @@ -201,12 +194,12 @@ def get_result(self, device, execution_id, num_retries=3000, interval=1): 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)) + print(f"Waiting for results. [Job ID: {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)) + raise Exception(f"Interrupted. The ID of your submitted job is {execution_id}.") signal.signal(signal.SIGINT, _handle_sigint_during_get_result) @@ -232,7 +225,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover # 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)) + raise Exception(f"Error while running the code: {status}.") # Sleep, then check availability before trying again. time.sleep(interval) @@ -240,13 +233,13 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover 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) + f"Device went offline. The ID of your submitted job is {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)) + raise RequestTimeoutError(f"Timeout. The ID of your submitted job is {execution_id}.") def show_devices(self): """Show the currently available device list for the IonQ provider. @@ -337,7 +330,7 @@ def send( if verbose: # pragma: no cover print("- Authenticating...") if verbose and token is not None: # pragma: no cover - print('user API token: ' + token) + print(f"user API token: {token}") ionq_session.authenticate(token) # check if the device is online @@ -353,13 +346,12 @@ def send( 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) + f'The device is too small ({qmax} qubits available) for the code requested({qneeded} qubits needed).', + 'Try to look for another device with more qubits', ) raise DeviceTooSmall("Device is too small.") if verbose: # pragma: no cover - print("- Running code: {}".format(info)) + print(f"- Running code: {info}") execution_id = ionq_session.run(info, device) if verbose: # pragma: no cover print("- Waiting for results...") @@ -382,12 +374,7 @@ def send( # Try to parse client errors if status_code == 400: err_json = err.response.json() - raise JobSubmissionError( - '{}: {}'.format( - err_json['error'], - err_json['message'], - ) - ) from err + raise JobSubmissionError(f"{err_json['error']}: {err_json['message']}") from err # Else, just print: print("- There was an error running your code:") diff --git a/projectq/backends/_ionq/_ionq_http_client_test.py b/projectq/backends/_ionq/_ionq_http_client_test.py index d92fb88f4..d1df8fb68 100644 --- a/projectq/backends/_ionq/_ionq_http_client_test.py +++ b/projectq/backends/_ionq/_ionq_http_client_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the 'License'); diff --git a/projectq/backends/_ionq/_ionq_mapper.py b/projectq/backends/_ionq/_ionq_mapper.py index 3a5eb5a57..e77b92c17 100644 --- a/projectq/backends/_ionq/_ionq_mapper.py +++ b/projectq/backends/_ionq/_ionq_mapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,10 +41,10 @@ def _process_cmd(self, cmd): 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)) + raise RuntimeError(f"Qubit with id {qubit_id} has already been allocated!") if self._qubit_idx >= self.max_qubits: - raise RuntimeError("Cannot allocate more than {} qubits!".format(self.max_qubits)) + raise RuntimeError(f"Cannot allocate more than {self.max_qubits} qubits!") new_id = self._qubit_idx self._qubit_idx += 1 diff --git a/projectq/backends/_ionq/_ionq_mapper_test.py b/projectq/backends/_ionq/_ionq_mapper_test.py index f86fb10d9..634f7d14a 100644 --- a/projectq/backends/_ionq/_ionq_mapper_test.py +++ b/projectq/backends/_ionq/_ionq_mapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_ionq/_ionq_test.py b/projectq/backends/_ionq/_ionq_test.py index b23450353..5a846b033 100644 --- a/projectq/backends/_ionq/_ionq_test.py +++ b/projectq/backends/_ionq/_ionq_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_printer.py b/projectq/backends/_printer.py index 9016b4753..50c20eb5c 100755 --- a/projectq/backends/_printer.py +++ b/projectq/backends/_printer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +15,6 @@ """Contains a compiler engine which prints commands to stdout prior to sending them on to the next engines.""" import sys -from builtins import input from projectq.cengines import BasicEngine, LastEngineException from projectq.meta import LogicalQubitIDTag, get_control_count @@ -85,7 +83,7 @@ def _print_cmd(self, cmd): if self._accept_input: meas = None while meas not in ('0', '1', 1, 0): - prompt = "Input measurement result (0 or 1) for qubit " + str(qubit) + ": " + prompt = f"Input measurement result (0 or 1) for qubit {str(qubit)}: " meas = input(prompt) else: meas = self._default_measure @@ -100,7 +98,7 @@ def _print_cmd(self, cmd): 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") + sys.stdout.write(f'\x00\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 8d81ffc1b..658fb78c7 100755 --- a/projectq/backends/_printer_test.py +++ b/projectq/backends/_printer_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +15,8 @@ Tests for projectq.backends._printer.py. """ +import io + import pytest from projectq import MainEngine @@ -47,11 +48,15 @@ def available_cmd(self, cmd): def test_command_printer_accept_input(monkeypatch): cmd_printer = _printer.CommandPrinter() eng = MainEngine(backend=cmd_printer, engine_list=[DummyEngine()]) - monkeypatch.setattr(_printer, "input", lambda x: 1) + + number_input = io.StringIO('1\n') + monkeypatch.setattr('sys.stdin', number_input) qubit = eng.allocate_qubit() Measure | qubit assert int(qubit) == 1 - monkeypatch.setattr(_printer, "input", lambda x: 0) + + number_input = io.StringIO('0\n') + monkeypatch.setattr('sys.stdin', number_input) qubit = eng.allocate_qubit() NOT | qubit Measure | qubit diff --git a/projectq/backends/_resource.py b/projectq/backends/_resource.py index e279d579f..ec741746c 100755 --- a/projectq/backends/_resource.py +++ b/projectq/backends/_resource.py @@ -1,4 +1,3 @@ -# -*- 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/_resource_test.py b/projectq/backends/_resource_test.py index 89ebeef0f..426d2916e 100755 --- a/projectq/backends/_resource_test.py +++ b/projectq/backends/_resource_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +24,7 @@ from projectq.types import WeakQubitRef -class MockEngine(object): +class MockEngine: def is_available(self, cmd): return False diff --git a/projectq/backends/_sim/__init__.py b/projectq/backends/_sim/__init__.py index 1557d03a1..3cdb3731b 100755 --- a/projectq/backends/_sim/__init__.py +++ b/projectq/backends/_sim/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 eb270e219..f873d1096 100755 --- a/projectq/backends/_sim/_classical_simulator.py +++ b/projectq/backends/_sim/_classical_simulator.py @@ -1,4 +1,3 @@ -# -*- 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_test.py b/projectq/backends/_sim/_classical_simulator_test.py index f8e9121bb..67db700ec 100755 --- a/projectq/backends/_sim/_classical_simulator_test.py +++ b/projectq/backends/_sim/_classical_simulator_test.py @@ -1,4 +1,3 @@ -# -*- 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/_pysim.py b/projectq/backends/_sim/_pysim.py index a98bad4ca..05b7477c0 100755 --- a/projectq/backends/_sim/_pysim.py +++ b/projectq/backends/_sim/_pysim.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -331,9 +330,9 @@ def emulate_time_evolution(self, terms_dict, time, ids, ctrlids): # pylint: dis ctrlids (list): A list of control qubit IDs. """ # Determine the (normalized) trace, which is nonzero only for identity terms: - trace = sum([c for (t, c) in terms_dict if len(t) == 0]) + 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]) + op_nrm = abs(time) * sum(abs(c) for (_, c) in terms_dict) # rescale the operator by s: scale = int(op_nrm + 1.0) correction = _np.exp(-1j * time * trace / float(scale)) diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index ff5c4f4fc..9752ea669 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -407,10 +406,8 @@ def _handle(self, cmd): # pylint: disable=too-many-branches,too-many-locals,too 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: " - "{}-qubit gate applied to {} qubits.".format( - str(cmd.gate), int(math.log(len(cmd.gate.matrix), 2)), len(ids) - ) + f"Simulator: Error applying {str(cmd.gate)} gate: {int(math.log(len(cmd.gate.matrix), 2))}-qubit" + f" gate applied to {len(ids)} qubits." ) self._simulator.apply_controlled_gate(matrix.tolist(), ids, [qb.id for qb in cmd.control_qubits]) @@ -418,8 +415,7 @@ def _handle(self, cmd): # pylint: disable=too-many-branches,too-many-locals,too self._simulator.run() else: raise Exception( - "This simulator only supports controlled k-qubit" - " gates with k < 6!\nPlease add an auto-replacer" + "This simulator only supports controlled k-qubit gates with k < 6!\nPlease add an auto-replacer" " engine to your list of compiler engines." ) diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 67f14eb9a..1b4b6b7bf 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -228,7 +227,7 @@ def test_simulator_functional_measurement(sim): All(Measure) | qubits - bit_value_sum = sum([int(qubit) for qubit in qubits]) + 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) @@ -644,7 +643,7 @@ def test_simulator_no_uncompute_exception(sim): assert qubit[0].id == -1 -class MockSimulatorBackend(object): +class MockSimulatorBackend: def __init__(self): self.run_cnt = 0 diff --git a/projectq/backends/_sim/_simulator_test_fixtures.py b/projectq/backends/_sim/_simulator_test_fixtures.py index 8d256fbc2..e04f9084d 100644 --- a/projectq/backends/_sim/_simulator_test_fixtures.py +++ b/projectq/backends/_sim/_simulator_test_fixtures.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_unitary.py b/projectq/backends/_unitary.py index 74fc47f17..a8941b1e3 100644 --- a/projectq/backends/_unitary.py +++ b/projectq/backends/_unitary.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -146,7 +145,7 @@ def is_available(self, cmd): try: gate_mat = cmd.gate.matrix if len(gate_mat) > 2**6: - warnings.warn("Potentially large matrix gate encountered! ({} qubits)".format(math.log2(len(gate_mat)))) + warnings.warn(f"Potentially large matrix gate encountered! ({math.log2(len(gate_mat))} qubits)") return True except AttributeError: return False diff --git a/projectq/backends/_unitary_test.py b/projectq/backends/_unitary_test.py index bd150697a..4053bd413 100644 --- a/projectq/backends/_unitary_test.py +++ b/projectq/backends/_unitary_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -258,7 +257,7 @@ def test_unitary_functional_measurement(): eng.flush() All(Measure) | qubits - bit_value_sum = sum([int(qubit) for qubit in qubits]) + 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) diff --git a/projectq/backends/_utils.py b/projectq/backends/_utils.py new file mode 100644 index 000000000..2c33068ae --- /dev/null +++ b/projectq/backends/_utils.py @@ -0,0 +1,28 @@ +# Copyright 2022 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 utility functions.""" + + +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``. + """ + return f'{input_result:0{length}b}'[::-1] diff --git a/projectq/backends/_utils_test.py b/projectq/backends/_utils_test.py new file mode 100644 index 000000000..f2dddcd9b --- /dev/null +++ b/projectq/backends/_utils_test.py @@ -0,0 +1,35 @@ +# Copyright 2022 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._utils.py.""" + +import pytest + +from ._utils import _rearrange_result + + +@pytest.mark.parametrize( + "input_result, length, expected_result", + [ + (5, 3, '101'), + (5, 4, '1010'), + (5, 5, '10100'), + (16, 5, '00001'), + (16, 6, '000010'), + (63, 6, '111111'), + (63, 7, '1111110'), + ], +) +def test_rearrange_result(input_result, length, expected_result): + assert expected_result == _rearrange_result(input_result, length) diff --git a/projectq/cengines/__init__.py b/projectq/cengines/__init__.py index e9ca6e132..aa36092f2 100755 --- a/projectq/cengines/__init__.py +++ b/projectq/cengines/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_basicmapper.py b/projectq/cengines/_basicmapper.py index e81a96c19..45f477d30 100644 --- a/projectq/cengines/_basicmapper.py +++ b/projectq/cengines/_basicmapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_basicmapper_test.py b/projectq/cengines/_basicmapper_test.py index 42c6e3cbf..2d319702d 100644 --- a/projectq/cengines/_basicmapper_test.py +++ b/projectq/cengines/_basicmapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_basics.py b/projectq/cengines/_basics.py index a8f393a81..8737d36d9 100755 --- a/projectq/cengines/_basics.py +++ b/projectq/cengines/_basics.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,9 +30,9 @@ def __init__(self, engine): """Initialize the exception.""" super().__init__( ( - "\nERROR: Sending to next engine failed. {} as last engine?\nIf this is legal, please override" - "'isAvailable' to adapt its behavior." - ).format(engine.__class__.__name__), + f"\nERROR: Sending to next engine failed. {engine.__class__.__name__} as last engine?" + "\nIf this is legal, please override 'isAvailable' to adapt its behavior." + ), ) diff --git a/projectq/cengines/_basics_test.py b/projectq/cengines/_basics_test.py index cb4b2e0b2..b521abf28 100755 --- a/projectq/cengines/_basics_test.py +++ b/projectq/cengines/_basics_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_cmdmodifier.py b/projectq/cengines/_cmdmodifier.py index f5deae94a..1f8769304 100755 --- a/projectq/cengines/_cmdmodifier.py +++ b/projectq/cengines/_cmdmodifier.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +41,8 @@ def __init__(self, cmd_mod_fun): def cmd_mod_fun(cmd): cmd.tags += [MyOwnTag()] + + compiler_engine = CommandModifier(cmd_mod_fun) ... """ diff --git a/projectq/cengines/_cmdmodifier_test.py b/projectq/cengines/_cmdmodifier_test.py index 5b7ed9fb6..06fd5ae82 100755 --- a/projectq/cengines/_cmdmodifier_test.py +++ b/projectq/cengines/_cmdmodifier_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index 8c121ac5d..bb7549ea6 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_ibm5qubitmapper_test.py b/projectq/cengines/_ibm5qubitmapper_test.py index d56e9e7ed..f0b3285b4 100755 --- a/projectq/cengines/_ibm5qubitmapper_test.py +++ b/projectq/cengines/_ibm5qubitmapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_linearmapper.py b/projectq/cengines/_linearmapper.py index 098d464a1..ca4b45320 100644 --- a/projectq/cengines/_linearmapper.py +++ b/projectq/cengines/_linearmapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -162,7 +161,7 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_comma 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(f"Invalid command (number of qubits): {str(cmd)}") if isinstance(cmd.gate, AllocateQubitGate): qubit_id = cmd.qubits[0][0].id diff --git a/projectq/cengines/_linearmapper_test.py b/projectq/cengines/_linearmapper_test.py index 0b414fd12..5a5657adf 100644 --- a/projectq/cengines/_linearmapper_test.py +++ b/projectq/cengines/_linearmapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_main.py b/projectq/cengines/_main.py index 6f13e25e0..dfbf5b7e4 100755 --- a/projectq/cengines/_main.py +++ b/projectq/cengines/_main.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -90,7 +89,8 @@ def __init__( # pylint: disable=too-many-statements,too-many-branches .. code-block:: python from projectq import MainEngine - eng = MainEngine() # uses default engine_list and the Simulator + + eng = MainEngine() # uses default engine_list and the Simulator Instead of the default `engine_list` one can use, e.g., one of the IBM setups which defines a custom `engine_list` useful for one of the IBM @@ -101,6 +101,7 @@ def __init__( # pylint: disable=too-many-statements,too-many-branches import projectq.setups.ibm as ibm_setup from projectq import MainEngine + eng = MainEngine(engine_list=ibm_setup.get_engine_list()) # eng uses the default Simulator backend @@ -109,14 +110,17 @@ def __init__( # pylint: disable=too-many-statements,too-many-branches Example: .. code-block:: python - from projectq.cengines import (TagRemover, AutoReplacer, - LocalOptimizer, - DecompositionRuleSet) + from projectq.cengines import ( + TagRemover, + AutoReplacer, + LocalOptimizer, + DecompositionRuleSet, + ) from projectq.backends import Simulator from projectq import MainEngine + rule_set = DecompositionRuleSet() - engines = [AutoReplacer(rule_set), TagRemover(), - LocalOptimizer(3)] + engines = [AutoReplacer(rule_set), TagRemover(), LocalOptimizer(3)] eng = MainEngine(Simulator(), engines) """ super().__init__() @@ -250,8 +254,9 @@ def get_measurement_result(self, qubit): from projectq.ops import H, Measure from projectq import MainEngine + eng = MainEngine() - qubit = eng.allocate_qubit() # quantum register of size 1 + qubit = eng.allocate_qubit() # quantum register of size 1 H | qubit Measure | qubit eng.get_measurement_result(qubit[0]) == int(qubit) diff --git a/projectq/cengines/_main_test.py b/projectq/cengines/_main_test.py index 6600fd865..392fff73a 100755 --- a/projectq/cengines/_main_test.py +++ b/projectq/cengines/_main_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_manualmapper.py b/projectq/cengines/_manualmapper.py index 8699feb88..4e7701021 100755 --- a/projectq/cengines/_manualmapper.py +++ b/projectq/cengines/_manualmapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_manualmapper_test.py b/projectq/cengines/_manualmapper_test.py index c84301742..f627bea2f 100755 --- a/projectq/cengines/_manualmapper_test.py +++ b/projectq/cengines/_manualmapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 957ec63f8..8d6b83678 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index 104dfcef3..72aa4656e 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_replacer/__init__.py b/projectq/cengines/_replacer/__init__.py index bae476ff6..312e4b22e 100755 --- a/projectq/cengines/_replacer/__init__.py +++ b/projectq/cengines/_replacer/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_replacer/_decomposition_rule.py b/projectq/cengines/_replacer/_decomposition_rule.py index 812ca1e81..24392fe24 100755 --- a/projectq/cengines/_replacer/_decomposition_rule.py +++ b/projectq/cengines/_replacer/_decomposition_rule.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_replacer/_decomposition_rule_set.py b/projectq/cengines/_replacer/_decomposition_rule_set.py index 738ea0420..9cdb5d9da 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_set.py +++ b/projectq/cengines/_replacer/_decomposition_rule_set.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -103,8 +102,7 @@ def recogn_toffoli(cmd): .. code-block:: python - register_decomposition(X.__class__, decompose_toffoli, - recogn_toffoli) + register_decomposition(X.__class__, decompose_toffoli, recogn_toffoli) Note: See projectq.setups.decompositions for more example codes. diff --git a/projectq/cengines/_replacer/_decomposition_rule_test.py b/projectq/cengines/_replacer/_decomposition_rule_test.py index a67759dc7..1989275d6 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_test.py +++ b/projectq/cengines/_replacer/_decomposition_rule_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_replacer/_replacer.py b/projectq/cengines/_replacer/_replacer.py index a0a942196..69cedf7ac 100755 --- a/projectq/cengines/_replacer/_replacer.py +++ b/projectq/cengines/_replacer/_replacer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -113,6 +112,8 @@ def __init__( def decomposition_chooser(cmd, decomp_list): return decomp_list[0] + + repl = AutoReplacer(decomposition_chooser) """ super().__init__() @@ -186,7 +187,7 @@ def _process_command(self, cmd): # pylint: disable=too-many-locals,too-many-bra break if len(decomp_list) == 0: - raise NoGateDecompositionError("\nNo replacement found for " + str(cmd) + "!") + raise NoGateDecompositionError(f"\nNo replacement found for {str(cmd)}!") # use decomposition chooser to determine the best decomposition chosen_decomp = self._decomp_chooser(cmd, decomp_list) diff --git a/projectq/cengines/_replacer/_replacer_test.py b/projectq/cengines/_replacer/_replacer_test.py index dbf68ea2e..b6dabffa2 100755 --- a/projectq/cengines/_replacer/_replacer_test.py +++ b/projectq/cengines/_replacer/_replacer_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_swapandcnotflipper.py b/projectq/cengines/_swapandcnotflipper.py index 2d7943abb..d25ac7222 100755 --- a/projectq/cengines/_swapandcnotflipper.py +++ b/projectq/cengines/_swapandcnotflipper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -89,7 +88,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(f"The provided connectivity does not allow to execute the CNOT gate {str(cmd)}.") return not is_possible def _send_cnot(self, cmd, control, target, flip=False): @@ -139,9 +138,7 @@ 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(f"The provided connectivity does not allow to execute the Swap gate {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 dc9611a48..c00fe0805 100755 --- a/projectq/cengines/_swapandcnotflipper_test.py +++ b/projectq/cengines/_swapandcnotflipper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_tagremover.py b/projectq/cengines/_tagremover.py index 2c6497ac5..a939366ef 100755 --- a/projectq/cengines/_tagremover.py +++ b/projectq/cengines/_tagremover.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -48,7 +47,7 @@ def __init__(self, tags=None): elif isinstance(tags, list): self._tags = tags else: - raise TypeError('tags should be a list! Got: {}'.format(tags)) + raise TypeError(f'tags should be a list! Got: {tags}') def receive(self, command_list): """ diff --git a/projectq/cengines/_tagremover_test.py b/projectq/cengines/_tagremover_test.py index c9679dcd0..6d61dad8a 100755 --- a/projectq/cengines/_tagremover_test.py +++ b/projectq/cengines/_tagremover_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,7 +33,7 @@ def test_tagremover_invalid(): def test_tagremover(): backend = DummyEngine(save_commands=True) - tag_remover = _tagremover.TagRemover([type("")]) + tag_remover = _tagremover.TagRemover([str]) eng = MainEngine(backend=backend, engine_list=[tag_remover]) # Create a command_list and check if "NewTag" is removed qubit = eng.allocate_qubit() diff --git a/projectq/cengines/_testengine.py b/projectq/cengines/_testengine.py index c89b42095..2933bde82 100755 --- a/projectq/cengines/_testengine.py +++ b/projectq/cengines/_testengine.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -95,10 +94,10 @@ def __str__(self): """Return a string representation of the object.""" string = "" for qubit_id, _l in enumerate(self._l): - string += "Qubit {0} : ".format(qubit_id) + string += f"Qubit {qubit_id} : " for command in self._l[qubit_id]: - string += str(command) + ", " - string = string[:-2] + "\n" + string += f"{str(command)}, " + string = f"{string[:-2]}\n" return string diff --git a/projectq/cengines/_testengine_test.py b/projectq/cengines/_testengine_test.py index fee0866e5..ff0786ff0 100755 --- a/projectq/cengines/_testengine_test.py +++ b/projectq/cengines/_testengine_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,9 +27,8 @@ def test_compare_engine_str(): 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" + "Qubit 0 : Allocate | Qureg[0], H | Qureg[0], CX | ( Qureg[0], Qureg[1] )\n" + "Qubit 1 : Allocate | Qureg[1], CX | ( Qureg[0], Qureg[1] )\n" ) assert str(compare_engine) == expected diff --git a/projectq/cengines/_twodmapper.py b/projectq/cengines/_twodmapper.py index 5aa1dc26f..7a54a0291 100644 --- a/projectq/cengines/_twodmapper.py +++ b/projectq/cengines/_twodmapper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/cengines/_twodmapper_test.py b/projectq/cengines/_twodmapper_test.py index f86c4ee02..5722b21c9 100644 --- a/projectq/cengines/_twodmapper_test.py +++ b/projectq/cengines/_twodmapper_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/__init__.py b/projectq/libs/__init__.py index 0690f2b10..a60e7ce84 100755 --- a/projectq/libs/__init__.py +++ b/projectq/libs/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 b4e9085db..07911534a 100644 --- a/projectq/libs/hist/__init__.py +++ b/projectq/libs/hist/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/hist/_histogram.py b/projectq/libs/hist/_histogram.py index 18a674e23..d2bf255aa 100644 --- a/projectq/libs/hist/_histogram.py +++ b/projectq/libs/hist/_histogram.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,7 +46,7 @@ def histogram(backend, qureg): qubit_list.append(qb) if len(qubit_list) > 5: - print('Warning: For {0} qubits there are 2^{0} different outcomes'.format(len(qubit_list))) + print(f'Warning: For {len(qubit_list)} qubits there are 2^{len(qubit_list)} different outcomes') print("The resulting histogram may look bad and/or take too long.") print("Consider calling histogram() with a sublist of the qubits.") diff --git a/projectq/libs/hist/_histogram_test.py b/projectq/libs/hist/_histogram_test.py index 6181a0161..55d6fb470 100644 --- a/projectq/libs/hist/_histogram_test.py +++ b/projectq/libs/hist/_histogram_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/__init__.py b/projectq/libs/math/__init__.py index 06dc384c7..c2433cf34 100755 --- a/projectq/libs/math/__init__.py +++ b/projectq/libs/math/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/math/_constantmath.py b/projectq/libs/math/_constantmath.py index 1b502aabc..14c87e7b4 100755 --- a/projectq/libs/math/_constantmath.py +++ b/projectq/libs/math/_constantmath.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/_constantmath_test.py b/projectq/libs/math/_constantmath_test.py index aafcff127..d4ff4cf4d 100755 --- a/projectq/libs/math/_constantmath_test.py +++ b/projectq/libs/math/_constantmath_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/_default_rules.py b/projectq/libs/math/_default_rules.py index ca4ad4a4f..7243feae4 100755 --- a/projectq/libs/math/_default_rules.py +++ b/projectq/libs/math/_default_rules.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/_gates.py b/projectq/libs/math/_gates.py index e541a34ea..ed6d9d26c 100755 --- a/projectq/libs/math/_gates.py +++ b/projectq/libs/math/_gates.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,9 +24,9 @@ class AddConstant(BasicMathGate): Example: .. code-block:: python - 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 + 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. @@ -52,7 +51,7 @@ def get_inverse(self): def __str__(self): """Return a string representation of the object.""" - return "AddConstant({})".format(self.a) + return f"AddConstant({self.a})" def __eq__(self, other): """Equal operator.""" @@ -73,9 +72,9 @@ def SubConstant(a): # pylint: disable=invalid-name Example: .. code-block:: python - qunum = eng.allocate_qureg(5) # 5-qubit number - X | qunum[2] # qunum is now equal to 4 - SubConstant(3) | qunum # qunum is now equal to 1 + qunum = eng.allocate_qureg(5) # 5-qubit number + X | qunum[2] # qunum is now equal to 4 + SubConstant(3) | qunum # qunum is now equal to 1 """ return AddConstant(-a) @@ -89,9 +88,9 @@ class AddConstantModN(BasicMathGate): Example: .. code-block:: python - qunum = eng.allocate_qureg(5) # 5-qubit number - X | qunum[1] # qunum is now equal to 2 - AddConstantModN(3, 4) | qunum # qunum is now equal to 1 + qunum = eng.allocate_qureg(5) # 5-qubit number + X | qunum[1] # qunum is now equal to 2 + AddConstantModN(3, 4) | qunum # qunum is now equal to 1 .. note:: @@ -119,7 +118,7 @@ def __init__(self, a, N): def __str__(self): """Return a string representation of the object.""" - return "AddConstantModN({}, {})".format(self.a, self.N) + return f"AddConstantModN({self.a}, {self.N})" def get_inverse(self): """Return the inverse gate (subtraction of the same number a modulo the same number N).""" @@ -147,9 +146,9 @@ def SubConstantModN(a, N): # pylint: disable=invalid-name Example: .. code-block:: python - qunum = eng.allocate_qureg(3) # 3-qubit number - X | qunum[1] # qunum is now equal to 2 - SubConstantModN(4,5) | qunum # qunum is now -2 = 6 = 1 (mod 5) + qunum = eng.allocate_qureg(3) # 3-qubit number + X | qunum[1] # qunum is now equal to 2 + SubConstantModN(4, 5) | qunum # qunum is now -2 = 6 = 1 (mod 5) .. note:: @@ -171,9 +170,9 @@ class MultiplyByConstantModN(BasicMathGate): Example: .. code-block:: python - qunum = eng.allocate_qureg(5) # 5-qubit number - X | qunum[2] # qunum is now equal to 4 - MultiplyByConstantModN(3,5) | qunum # qunum is now 2. + qunum = eng.allocate_qureg(5) # 5-qubit number + X | qunum[2] # qunum is now equal to 4 + MultiplyByConstantModN(3, 5) | qunum # qunum is now 2. .. note:: @@ -203,7 +202,7 @@ def __init__(self, a, N): # pylint: disable=invalid-name def __str__(self): """Return a string representation of the object.""" - return "MultiplyByConstantModN({}, {})".format(self.a, self.N) + return f"MultiplyByConstantModN({self.a}, {self.N})" def __eq__(self, other): """Equal operator.""" @@ -223,12 +222,12 @@ class AddQuantumGate(BasicMathGate): Example: .. code-block:: python - qunum_a = eng.allocate_qureg(5) # 5-qubit number - qunum_b = eng.allocate_qureg(5) # 5-qubit number + 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 + 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 """ @@ -303,10 +302,10 @@ class SubtractQuantumGate(BasicMathGate): 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 + 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 @@ -353,15 +352,13 @@ class ComparatorQuantumGate(BasicMathGate): Example: .. code-block:: python - qunum_a = eng.allocate_qureg(5) # 5-qubit number - qunum_b = eng.allocate_qureg(5) # 5-qubit number + 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 + 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 - + # qunum_a and qunum_b remain 16 and 8, qunum_b is now 12 and compare bit is now 1 """ def __init__(self): @@ -416,18 +413,18 @@ class DivideQuantumGate(BasicMathGate): 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 + 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 + 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) + 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> + # |dividend>|remainder>|divisor> -> |remainder>|quotient>|divisor> """ def __init__(self): @@ -499,13 +496,13 @@ 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) - 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 + 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): diff --git a/projectq/libs/math/_gates_math_test.py b/projectq/libs/math/_gates_math_test.py index 8463fa75b..6bf28f77c 100644 --- a/projectq/libs/math/_gates_math_test.py +++ b/projectq/libs/math/_gates_math_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,7 +42,7 @@ 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 = [int(x) for x in list((f'{i:0b}').zfill(y))] qubit_list = qubit_list[::-1] prob = eng.backend.get_probability(qubit_list, qureg) if prob != 0.0: diff --git a/projectq/libs/math/_gates_test.py b/projectq/libs/math/_gates_test.py index aa78ebf8e..6eb9613e9 100755 --- a/projectq/libs/math/_gates_test.py +++ b/projectq/libs/math/_gates_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/_quantummath.py b/projectq/libs/math/_quantummath.py index 8b66fbb69..fb3dcbdc6 100644 --- a/projectq/libs/math/_quantummath.py +++ b/projectq/libs/math/_quantummath.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/math/_quantummath_test.py b/projectq/libs/math/_quantummath_test.py index c71ae0ef7..d32715ca6 100644 --- a/projectq/libs/math/_quantummath_test.py +++ b/projectq/libs/math/_quantummath_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +34,7 @@ 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 = [int(x) for x in list((f'{i:0b}').zfill(y))] qubit_list = qubit_list[::-1] prob = eng.backend.get_probability(qubit_list, qureg) if prob != 0.0: diff --git a/projectq/libs/revkit/__init__.py b/projectq/libs/revkit/__init__.py index a411ab55d..e527091be 100644 --- a/projectq/libs/revkit/__init__.py +++ b/projectq/libs/revkit/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 341cb5f9f..c20db35f5 100644 --- a/projectq/libs/revkit/_control_function.py +++ b/projectq/libs/revkit/_control_function.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +37,7 @@ class ControlFunctionOracle: # pylint: disable=too-few-public-methods .. code-block:: python - ControlFunctionOracle(0x8e) | ([a, b, c], d) + ControlFunctionOracle(0x8E) | ([a, b, c], d) """ def __init__(self, function, **kwargs): @@ -103,7 +102,7 @@ def __or__(self, qubits): # create truth table from function integer hex_length = max(2 ** (len(qs) - 1) // 4, 1) - revkit.tt(table="{0:#0{1}x}".format(self.function, hex_length)) + revkit.tt(table=f"{self.function:#0{hex_length}x}") # create reversible circuit from truth table self.kwargs.get("synth", revkit.esopbs)() diff --git a/projectq/libs/revkit/_control_function_test.py b/projectq/libs/revkit/_control_function_test.py index 3ea58c58a..ff4d24fe5 100644 --- a/projectq/libs/revkit/_control_function_test.py +++ b/projectq/libs/revkit/_control_function_test.py @@ -1,4 +1,3 @@ -# -*- 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/_permutation.py b/projectq/libs/revkit/_permutation.py index b5bc522b1..87f6e09f1 100644 --- a/projectq/libs/revkit/_permutation.py +++ b/projectq/libs/revkit/_permutation.py @@ -1,4 +1,3 @@ -# -*- 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/_permutation_test.py b/projectq/libs/revkit/_permutation_test.py index 06c9e0d33..19d7be0ac 100644 --- a/projectq/libs/revkit/_permutation_test.py +++ b/projectq/libs/revkit/_permutation_test.py @@ -1,4 +1,3 @@ -# -*- 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/_phase.py b/projectq/libs/revkit/_phase.py index edfc0afef..399697449 100644 --- a/projectq/libs/revkit/_phase.py +++ b/projectq/libs/revkit/_phase.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +37,7 @@ class PhaseOracle: # pylint: disable=too-few-public-methods .. code-block:: python - PhaseOracle(0x8e) | (a, b, c) + PhaseOracle(0x8E) | (a, b, c) """ def __init__(self, function, **kwargs): @@ -99,7 +98,7 @@ def __or__(self, qubits): # create truth table from function integer hex_length = max(2 ** (len(qs) - 1) // 4, 1) - revkit.tt(table="{0:#0{1}x}".format(self.function, hex_length)) + revkit.tt(table=f"{self.function:#0{hex_length}x}") # create phase circuit from truth table self.kwargs.get("synth", revkit.esopps)() diff --git a/projectq/libs/revkit/_phase_test.py b/projectq/libs/revkit/_phase_test.py index 5c06a9162..37152e027 100644 --- a/projectq/libs/revkit/_phase_test.py +++ b/projectq/libs/revkit/_phase_test.py @@ -1,4 +1,3 @@ -# -*- 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/_utils.py b/projectq/libs/revkit/_utils.py index 7fd44ca9a..e1f47c025 100644 --- a/projectq/libs/revkit/_utils.py +++ b/projectq/libs/revkit/_utils.py @@ -1,4 +1,3 @@ -# -*- 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/__init__.py b/projectq/meta/__init__.py index 634b046c7..764081800 100755 --- a/projectq/meta/__init__.py +++ b/projectq/meta/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/_compute.py b/projectq/meta/_compute.py index dcf7932ab..295c4b7a4 100755 --- a/projectq/meta/_compute.py +++ b/projectq/meta/_compute.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -259,7 +258,7 @@ class Compute: with Compute(eng): do_something(qubits) action(qubits) - Uncompute(eng) # runs inverse of the compute section + 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 @@ -279,7 +278,7 @@ class Compute: do_something_else(qubits) Uncompute(eng) # will allocate a new ancilla (with a different id) - # and then deallocate it again + # and then deallocate it again .. code-block:: python @@ -400,7 +399,7 @@ def Uncompute(engine): # pylint: disable=invalid-name with Compute(eng): do_something(qubits) action(qubits) - Uncompute(eng) # runs inverse of the compute section + Uncompute(eng) # runs inverse of the compute section """ compute_eng = engine.next_engine if not isinstance(compute_eng, ComputeEngine): diff --git a/projectq/meta/_compute_test.py b/projectq/meta/_compute_test.py index 1a87d413f..b32cd1f2f 100755 --- a/projectq/meta/_compute_test.py +++ b/projectq/meta/_compute_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +28,7 @@ def test_compute_tag(): tag0 = _compute.ComputeTag() tag1 = _compute.ComputeTag() - class MyTag(object): + class MyTag: pass assert not tag0 == MyTag() @@ -41,7 +40,7 @@ def test_uncompute_tag(): tag0 = _compute.UncomputeTag() tag1 = _compute.UncomputeTag() - class MyTag(object): + class MyTag: pass assert not tag0 == MyTag() diff --git a/projectq/meta/_control.py b/projectq/meta/_control.py index 90c97e503..543ebfe80 100755 --- a/projectq/meta/_control.py +++ b/projectq/meta/_control.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,12 +61,11 @@ def canonical_ctrl_state(ctrl_state, 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] + converted_str = f'{ctrl_state:b}'.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 - ) + f'Control state specified as {ctrl_state} ({converted_str}) is higher than maximum for {num_qubits} ' + f'qubits: {2 ** num_qubits - 1}' ) return converted_str @@ -75,12 +73,10 @@ def canonical_ctrl_state(ctrl_state, num_qubits): # 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 - ) + f'Control state {ctrl_state} has different length than the number of control qubits {num_qubits}' ) if not set(ctrl_state).issubset({'0', '1'}): - raise ValueError('Control state {} has string other than 1 and 0'.format(ctrl_state)) + raise ValueError(f'Control state {ctrl_state} has string other than 1 and 0') return ctrl_state raise TypeError('Input must be a string, an integer or an enum value of class State') diff --git a/projectq/meta/_control_test.py b/projectq/meta/_control_test.py index e30bda335..73810b95e 100755 --- a/projectq/meta/_control_test.py +++ b/projectq/meta/_control_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +35,7 @@ def test_canonical_representation(): num_qubits = 4 for i in range(2**num_qubits): - state = '{0:0b}'.format(i).zfill(num_qubits) + state = f'{i:0b}'.zfill(num_qubits) assert _control.canonical_ctrl_state(i, num_qubits) == state[::-1] assert _control.canonical_ctrl_state(state, num_qubits) == state diff --git a/projectq/meta/_dagger.py b/projectq/meta/_dagger.py index 36ead9f48..8987b1b2a 100755 --- a/projectq/meta/_dagger.py +++ b/projectq/meta/_dagger.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -81,7 +80,8 @@ class Dagger: .. code-block:: python with Dagger(eng): - [code to invert] + # [code to invert] + pass Warning: If the code to invert contains allocation of qubits, those qubits have @@ -93,7 +93,7 @@ class Dagger: with Dagger(eng): qb = eng.allocate_qubit() - H | qb # qb is still available!!! + H | qb # qb is still available!!! The **correct way** of handling qubit (de-)allocation is as follows: @@ -102,7 +102,7 @@ class Dagger: with Dagger(eng): qb = eng.allocate_qubit() ... - del qb # sends deallocate gate (which becomes an allocate) + del qb # sends deallocate gate (which becomes an allocate) """ def __init__(self, engine): diff --git a/projectq/meta/_dagger_test.py b/projectq/meta/_dagger_test.py index a95beeda1..330e0d106 100755 --- a/projectq/meta/_dagger_test.py +++ b/projectq/meta/_dagger_test.py @@ -1,4 +1,3 @@ -# -*- 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/_dirtyqubit.py b/projectq/meta/_dirtyqubit.py index 3e9ba9833..c13ed2afb 100755 --- a/projectq/meta/_dirtyqubit.py +++ b/projectq/meta/_dirtyqubit.py @@ -1,4 +1,3 @@ -# -*- 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/_dirtyqubit_test.py b/projectq/meta/_dirtyqubit_test.py index 74e4d94f3..09e942c1e 100755 --- a/projectq/meta/_dirtyqubit_test.py +++ b/projectq/meta/_dirtyqubit_test.py @@ -1,4 +1,3 @@ -# -*- 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/_exceptions.py b/projectq/meta/_exceptions.py index 2f12ff361..b9c5858b1 100644 --- a/projectq/meta/_exceptions.py +++ b/projectq/meta/_exceptions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/meta/_logicalqubit.py b/projectq/meta/_logicalqubit.py index 2c6295235..0aaf4ddd3 100644 --- a/projectq/meta/_logicalqubit.py +++ b/projectq/meta/_logicalqubit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/meta/_logicalqubit_test.py b/projectq/meta/_logicalqubit_test.py index c64a79837..43ed2943a 100644 --- a/projectq/meta/_logicalqubit_test.py +++ b/projectq/meta/_logicalqubit_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/meta/_loop.py b/projectq/meta/_loop.py index cc64034ca..47681b764 100755 --- a/projectq/meta/_loop.py +++ b/projectq/meta/_loop.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +19,7 @@ with Loop(eng, 4): H | qb - Rz(M_PI/3.) | qb + Rz(M_PI / 3.0) | qb """ from copy import deepcopy @@ -191,6 +190,7 @@ class Loop: with Loop(eng, 4): # [quantum gates to be executed 4 times] + pass Warning: If the code in the loop contains allocation of qubits, those qubits have to be deleted prior to exiting the @@ -202,7 +202,7 @@ class Loop: with Loop(eng, 4): qb = eng.allocate_qubit() - H | qb # qb is still available!!! + H | qb # qb is still available!!! The **correct way** of handling qubit (de-)allocation is as follows: @@ -210,8 +210,8 @@ class Loop: with Loop(eng, 4): qb = eng.allocate_qubit() - ... - del qb # sends deallocate gate + # ... + del qb # sends deallocate gate """ def __init__(self, engine, num): @@ -227,7 +227,7 @@ def __init__(self, engine, num): with Loop(eng, 4): H | qb - Rz(M_PI/3.) | qb + Rz(M_PI / 3.0) | qb Raises: TypeError: If number of iterations (num) is not an integer ValueError: If number of iterations (num) is not >= 0 diff --git a/projectq/meta/_loop_test.py b/projectq/meta/_loop_test.py index 618c5f16a..01ef872ce 100755 --- a/projectq/meta/_loop_test.py +++ b/projectq/meta/_loop_test.py @@ -1,4 +1,3 @@ -# -*- 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.py b/projectq/meta/_util.py index 414930f50..79f4dc43c 100755 --- a/projectq/meta/_util.py +++ b/projectq/meta/_util.py @@ -1,4 +1,3 @@ -# -*- 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 90b781b53..a60d4ac51 100755 --- a/projectq/meta/_util_test.py +++ b/projectq/meta/_util_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 67b570263..13d23ebaf 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 17485e921..c07563fca 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -79,26 +78,26 @@ def __init__(self): .. code-block:: python - ExampleGate | (a,b,c,d,e) + ExampleGate | (a, b, c, d, e) where a and b are interchangeable. Then, call this function as follows: .. code-block:: python - self.set_interchangeable_qubit_indices([[0,1]]) + self.set_interchangeable_qubit_indices([[0, 1]]) As another example, consider .. code-block:: python - ExampleGate2 | (a,b,c,d,e) + 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 .. code-block:: python - self.set_interchangeable_qubit_indices([[0,1],[2,3,4]]) + self.set_interchangeable_qubit_indices([[0, 1], [2, 3, 4]]) """ self.interchangeable_qubit_indices = [] @@ -290,7 +289,7 @@ def __eq__(self, other): def __str__(self): """Return a string representation of the object.""" - return "MatrixGate(" + str(self.matrix.tolist()) + ")" + return f"MatrixGate({str(self.matrix.tolist())})" def __hash__(self): """Compute the hash of the object.""" @@ -362,9 +361,9 @@ def to_string(self, symbols=False): angle written in radian otherwise. """ if symbols: - angle = "(" + str(round(self.angle / math.pi, 3)) + unicodedata.lookup("GREEK SMALL LETTER PI") + ")" + angle = f"({str(round(self.angle / math.pi, 3))}{unicodedata.lookup('GREEK SMALL LETTER PI')})" else: - angle = "(" + str(self.angle) + ")" + angle = f"({str(self.angle)})" return str(self.__class__.__name__) + angle def tex_str(self): @@ -377,7 +376,7 @@ def tex_str(self): [CLASSNAME]$_[ANGLE]$ """ - return str(self.__class__.__name__) + "$_{" + str(round(self.angle / math.pi, 3)) + "\\pi}$" + return f"{str(self.__class__.__name__)}$_{{{str(round(self.angle / math.pi, 3))}\\pi}}$" def get_inverse(self): """Return the inverse of this rotation gate (negate the angle, return new object).""" @@ -451,7 +450,7 @@ def __str__(self): [CLASSNAME]([ANGLE]) """ - return str(self.__class__.__name__) + "(" + str(self.angle) + ")" + return f"{str(self.__class__.__name__)}({str(self.angle)})" def tex_str(self): """ @@ -463,7 +462,7 @@ def tex_str(self): [CLASSNAME]$_[ANGLE]$ """ - return str(self.__class__.__name__) + "$_{" + str(self.angle) + "}$" + return f"{str(self.__class__.__name__)}$_{{{str(self.angle)}}}$" def get_inverse(self): """Return the inverse of this rotation gate (negate the angle, return new object).""" @@ -545,7 +544,7 @@ class BasicMathGate(BasicGate): .. code-block:: python def add(x): - return (x+a,) + 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, @@ -554,8 +553,8 @@ def add(x): .. code-block:: python - def multiply(a,b,c) - return (a,b,c+a*b) + def multiply(a, b, c): + return (a, b, c + a * b) """ def __init__(self, math_fun): @@ -569,8 +568,10 @@ def __init__(self, math_fun): Example: .. code-block:: python - def add(a,b): - return (a,a+b) + def add(a, b): + return (a, a + b) + + super().__init__(add) If the gate acts on, e.g., fixed point numbers, the number of bits per register is also required in order to @@ -587,9 +588,11 @@ def add(a,b): def get_math_function(self, qubits): n = len(qubits[0]) - scal = 2.**n + scal = 2.0**n + def math_fun(a): return (int(scal * (math.sin(math.pi * a / scal))),) + return math_fun """ diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index af3ab34b2..35b19986d 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -170,7 +169,7 @@ def test_basic_rotation_gate_init(input_angle, modulo_angle): def test_basic_rotation_gate_str(): gate = _basics.BasicRotationGate(math.pi) assert str(gate) == "BasicRotationGate(3.14159265359)" - assert gate.to_string(symbols=True) == u"BasicRotationGate(1.0π)" + assert gate.to_string(symbols=True) == "BasicRotationGate(1.0π)" assert gate.to_string(symbols=False) == "BasicRotationGate(3.14159265359)" diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index c67de0683..481efcc16 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -284,7 +283,7 @@ def add_control_qubits(self, qubits, state=CtrlAll.One): qubits, states = list(zip(*data)) if len(set(states)) != 1: raise IncompatibleControlState( - 'Control qubits {} cannot have conflicting control states: {}'.format(list(qubits), states) + f'Control qubits {list(qubits)} cannot have conflicting control states: {states}' ) @property @@ -356,6 +355,6 @@ def to_string(self, symbols=False): for qreg in qubits: qstring += str(Qureg(qreg)) qstring += ", " - qstring = qstring[:-2] + " )" + qstring = f"{qstring[:-2]} )" cstring = "C" * len(ctrlqubits) - return cstring + self.gate.to_string(symbols) + " | " + qstring + return f"{cstring + self.gate.to_string(symbols)} | {qstring}" diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index 27377a558..9fcbf544e 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -219,7 +218,7 @@ def test_commmand_add_control_qubits_two(main_engine, state): 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_state == '1' + canonical_ctrl_state(state, 2) + assert cmd.control_state == f"1{canonical_ctrl_state(state, 2)}" def test_command_all_qubits(main_engine): @@ -304,8 +303,8 @@ def test_command_to_string(main_engine): cmd.add_control_qubits(ctrl_qubit) cmd2 = _command.Command(main_engine, Rx(0.5 * math.pi), (qubit,)) - assert cmd.to_string(symbols=True) == u"CRx(0.5π) | ( Qureg[1], Qureg[0] )" - assert cmd2.to_string(symbols=True) == u"Rx(0.5π) | Qureg[0]" + assert cmd.to_string(symbols=True) == "CRx(0.5π) | ( Qureg[1], Qureg[0] )" + assert cmd2.to_string(symbols=True) == "Rx(0.5π) | Qureg[0]" if sys.version_info.major == 3: assert cmd.to_string(symbols=False) == "CRx(1.570796326795) | ( Qureg[1], Qureg[0] )" assert cmd2.to_string(symbols=False) == "Rx(1.570796326795) | Qureg[0]" diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 97acd12bc..88024b7cf 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -515,16 +514,15 @@ def __init__(self, bits_to_flip): def __str__(self): """Return a string representation of the object.""" - return "FlipBits(" + str(self.bits_to_flip) + ")" + return f"FlipBits({self.bits_to_flip})" def __or__(self, qubits): """Operator| overload which enables the syntax Gate | 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' + f"{str(self)} 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): @@ -539,4 +537,4 @@ def __eq__(self, other): def __hash__(self): """Compute the hash of the object.""" - return hash(self.__str__()) + return hash(str(self)) diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index 12b49f7a9..ab431b923 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index f67e4927f..0c1f89f3f 100644 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +21,7 @@ Example: .. code-block:: python - Tensor(H) | (qubit1, qubit2) # apply H to qubit #1 and #2 + 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) @@ -75,13 +74,13 @@ def __init__(self, gate): def __str__(self): r"""Return string representation (str(gate) + \"^\dagger\").""" - return str(self._gate) + r"^\dagger" + return f"{str(self._gate)}^\\dagger" def tex_str(self): """Return the Latex string representation of a Daggered gate.""" if hasattr(self._gate, 'tex_str'): - return self._gate.tex_str() + r"${}^\dagger$" - return str(self._gate) + r"${}^\dagger$" + return f"{self._gate.tex_str()}${{}}^\\dagger$" + return f"{str(self._gate)}${{}}^\\dagger$" def get_inverse(self): """Return the inverse gate (the inverse of the inverse of a gate is the gate itself).""" @@ -108,7 +107,7 @@ def get_inverse(gate): Example: .. code-block:: python - get_inverse(H) # returns a Hadamard gate (HGate object) + get_inverse(H) # returns a Hadamard gate (HGate object) """ try: return gate.get_inverse() @@ -128,8 +127,8 @@ def is_identity(gate): Example: .. code-block:: python - get_inverse(Rx(2*math.pi)) # returns True - get_inverse(Rx(math.pi)) # returns False + get_inverse(Rx(2 * math.pi)) # returns True + get_inverse(Rx(math.pi)) # returns False """ return gate.is_identity() @@ -147,9 +146,9 @@ class ControlledGate(BasicGate): Example: .. code-block:: python - ControlledGate(gate, 2) | (qb0, qb2, qb3) # qb0 & qb2 are controls - C(gate, 2) | (qb0, qb2, qb3) # This is much nicer. - C(gate, 2) | ([qb0,qb2], qb3) # Is equivalent + ControlledGate(gate, 2) | (qb0, qb2, qb3) # qb0 & qb2 are controls + C(gate, 2) | (qb0, qb2, qb3) # This is much nicer. + C(gate, 2) | ([qb0, qb2], qb3) # Is equivalent Note: Use :func:`C` rather than ControlledGate, i.e., @@ -235,7 +234,7 @@ def C(gate, n_qubits=1): Example: .. code-block:: python - C(NOT) | (c, q) # equivalent to CNOT | (c, q) + C(NOT) | (c, q) # equivalent to CNOT | (c, q) """ return ControlledGate(gate, n_qubits) @@ -249,8 +248,8 @@ class Tensor(BasicGate): Example: .. code-block:: python - Tensor(H) | x # applies H to every qubit in the list of qubits x - Tensor(H) | (x,) # alternative to be consistent with other syntax + Tensor(H) | x # applies H to every qubit in the list of qubits x + Tensor(H) | (x,) # alternative to be consistent with other syntax """ def __init__(self, gate): @@ -260,7 +259,7 @@ def __init__(self, gate): def __str__(self): """Return a string representation of the object.""" - return "Tensor(" + str(self._gate) + ")" + return f"Tensor({str(self._gate)})" def get_inverse(self): """Return the inverse of this tensored gate (which is the tensored inverse of the gate).""" diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index e00d4b617..cacd1fb8c 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -89,7 +88,7 @@ def test_daggered_gate_init(): def test_daggered_gate_str(): daggered_gate = _metagates.DaggeredGate(Y) - assert str(daggered_gate) == str(Y) + r"^\dagger" + assert str(daggered_gate) == f"{str(Y)}^\\dagger" def test_daggered_gate_hashable(): @@ -104,13 +103,13 @@ def test_daggered_gate_hashable(): def test_daggered_gate_tex_str(): daggered_gate = _metagates.DaggeredGate(Y) str_Y = Y.tex_str() if hasattr(Y, 'tex_str') else str(Y) - assert daggered_gate.tex_str() == str_Y + r"${}^\dagger$" + assert daggered_gate.tex_str() == f"{str_Y}${{}}^\\dagger$" # test for a gate with tex_str method rx = Rx(0.5) daggered_rx = _metagates.DaggeredGate(rx) str_rx = rx.tex_str() if hasattr(rx, 'tex_str') else str(rx) - assert daggered_rx.tex_str() == str_rx + r"${}^\dagger$" + assert daggered_rx.tex_str() == f"{str_rx}${{}}^\\dagger$" def test_daggered_gate_get_inverse(): @@ -164,7 +163,7 @@ def test_controlled_gate_init(): def test_controlled_gate_str(): one_control = _metagates.ControlledGate(Y, 2) - assert str(one_control) == "CC" + str(Y) + assert str(one_control) == f"CC{str(Y)}" def test_controlled_gate_get_inverse(): @@ -234,7 +233,7 @@ def test_tensor_init(): def test_tensor_str(): gate = _metagates.Tensor(Y) - assert str(gate) == "Tensor(" + str(Y) + ")" + assert str(gate) == f"Tensor({str(Y)})" def test_tensor_get_inverse(): diff --git a/projectq/ops/_qaagate.py b/projectq/ops/_qaagate.py index 565d61498..5e2d889c4 100755 --- a/projectq/ops/_qaagate.py +++ b/projectq/ops/_qaagate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,17 +31,19 @@ class QAA(BasicGate): Example: .. code-block:: python - def func_algorithm(eng,system_qubits): + def func_algorithm(eng, system_qubits): All(H) | system_qubits - def func_oracle(eng,system_qubits,qaa_ancilla): + + def func_oracle(eng, system_qubits, qaa_ancilla): # This oracle selects the state |010> as the one marked with Compute(eng): - All(X) | system_qubits[0::2] + All(X) | system_qubits[0::2] with Control(eng, system_qubits): - X | qaa_ancilla + X | qaa_ancilla Uncompute(eng) + system_qubits = eng.allocate_qureg(3) # Prepare the qaa_ancilla qubit in the |-> state qaa_ancilla = eng.allocate_qubit() @@ -52,7 +53,7 @@ def func_oracle(eng,system_qubits,qaa_ancilla): # Creates the initial state form the Algorithm func_algorithm(eng, system_qubits) # Apply Quantum Amplitude Amplification the correct number of times - num_it = int(math.pi/4.*math.sqrt(1 << 3)) + num_it = int(math.pi / 4.0 * math.sqrt(1 << 3)) with Loop(eng, num_it): QAA(func_algorithm, func_oracle) | (system_qubits, qaa_ancilla) @@ -78,4 +79,4 @@ def __init__(self, algorithm, oracle): def __str__(self): """Return a string representation of the object.""" - return 'QAA(Algorithm = {0}, Oracle = {1})'.format(str(self.algorithm.__name__), str(self.oracle.__name__)) + return f'QAA(Algorithm = {str(self.algorithm.__name__)}, Oracle = {str(self.oracle.__name__)})' diff --git a/projectq/ops/_qaagate_test.py b/projectq/ops/_qaagate_test.py index 707fb2fd5..e6d68688b 100755 --- a/projectq/ops/_qaagate_test.py +++ b/projectq/ops/_qaagate_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_qftgate.py b/projectq/ops/_qftgate.py index 0ea56c032..3a14de37a 100755 --- a/projectq/ops/_qftgate.py +++ b/projectq/ops/_qftgate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_qftgate_test.py b/projectq/ops/_qftgate_test.py index 8fa43058f..a790506f0 100755 --- a/projectq/ops/_qftgate_test.py +++ b/projectq/ops/_qftgate_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_qpegate.py b/projectq/ops/_qpegate.py index 96ed00a5d..3eab6e84c 100755 --- a/projectq/ops/_qpegate.py +++ b/projectq/ops/_qpegate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,4 +31,4 @@ def __init__(self, unitary): def __str__(self): """Return a string representation of the object.""" - return 'QPE({})'.format(str(self.unitary)) + return f'QPE({str(self.unitary)})' diff --git a/projectq/ops/_qpegate_test.py b/projectq/ops/_qpegate_test.py index 557b980bd..d404127ff 100755 --- a/projectq/ops/_qpegate_test.py +++ b/projectq/ops/_qpegate_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 6e2abda8c..f15a4ed40 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -82,7 +81,7 @@ class QubitOperator(BasicGate): 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.0j) | qureg # Applies X to qubit 0 and 5 with an additional global phase of 1.j Attributes: @@ -105,8 +104,7 @@ def __init__(self, term=None, coefficient=1.0): # pylint: disable=too-many-bran Example: .. code-block:: python - ham = ((QubitOperator('X0 Y3', 0.5) - + 0.6 * QubitOperator('X0 Y3'))) + ham = QubitOperator('X0 Y3', 0.5) + 0.6 * QubitOperator('X0 Y3') # Equivalently ham2 = QubitOperator('X0 Y3', 0.5) ham2 += 0.6 * QubitOperator('X0 Y3') @@ -247,9 +245,9 @@ def __or__(self, qubits): # pylint: disable=too-many-locals 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.0j) | qureg # Applies X to qubit 0 and 5 + # with an additional global + # phase of 1.j While in the above example the QubitOperator gate is applied to 6 qubits, it only acts non-trivially on the two qubits qureg[0] and @@ -259,7 +257,7 @@ def __or__(self, qubits): # pylint: disable=too-many-locals .. code-block:: python - QubitOperator('X0 X1', 1.j) | [qureg[0], qureg[5]] + QubitOperator('X0 X1', 1.0j) | [qureg[0], qureg[5]] which is only a two qubit gate. @@ -423,7 +421,7 @@ def __imul__(self, multiplier): # pylint: disable=too-many-locals,too-many-bran result_terms[tmp_key] = new_coefficient self.terms = result_terms return self - 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): """ @@ -564,19 +562,19 @@ def __str__(self): return '0' string_rep = '' for term, coeff in self.terms.items(): - tmp_string = '{}'.format(coeff) + tmp_string = f'{coeff}' if term == (): tmp_string += ' I' for operator in term: if operator[1] == 'X': - tmp_string += ' X{}'.format(operator[0]) + tmp_string += f' X{operator[0]}' elif operator[1] == 'Y': - tmp_string += ' Y{}'.format(operator[0]) + tmp_string += f' Y{operator[0]}' elif operator[1] == 'Z': - tmp_string += ' Z{}'.format(operator[0]) + tmp_string += f' Z{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) + string_rep += f'{tmp_string} +\n' return string_rep[:-3] def __repr__(self): diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index 3bbd09e2b..3b5e30508 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_shortcuts.py b/projectq/ops/_shortcuts.py index afcc8bdf4..bc80c6fcc 100755 --- a/projectq/ops/_shortcuts.py +++ b/projectq/ops/_shortcuts.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_shortcuts_test.py b/projectq/ops/_shortcuts_test.py index 345f8396c..8c55d6625 100755 --- a/projectq/ops/_shortcuts_test.py +++ b/projectq/ops/_shortcuts_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_state_prep.py b/projectq/ops/_state_prep.py index 74c26d97a..a2f3564df 100644 --- a/projectq/ops/_state_prep.py +++ b/projectq/ops/_state_prep.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -54,4 +53,4 @@ def __eq__(self, other): def __hash__(self): """Compute the hash of the object.""" - return hash("StatePreparation(" + str(self.final_state) + ")") + return hash(f"StatePreparation({str(self.final_state)})") diff --git a/projectq/ops/_state_prep_test.py b/projectq/ops/_state_prep_test.py index 198ace500..f6cac5358 100644 --- a/projectq/ops/_state_prep_test.py +++ b/projectq/ops/_state_prep_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_time_evolution.py b/projectq/ops/_time_evolution.py index aa10944a9..957a1d21d 100644 --- a/projectq/ops/_time_evolution.py +++ b/projectq/ops/_time_evolution.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -202,4 +201,4 @@ def __eq__(self, other): def __str__(self): """Return a string representation of the object.""" - return "exp({0} * ({1}))".format(-1j * self.time, self.hamiltonian) + return f"exp({-1j * self.time} * ({self.hamiltonian}))" diff --git a/projectq/ops/_time_evolution_test.py b/projectq/ops/_time_evolution_test.py index 078c96f39..af7914fa1 100644 --- a/projectq/ops/_time_evolution_test.py +++ b/projectq/ops/_time_evolution_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/ops/_uniformly_controlled_rotation.py b/projectq/ops/_uniformly_controlled_rotation.py index 393467406..46cf023f7 100644 --- a/projectq/ops/_uniformly_controlled_rotation.py +++ b/projectq/ops/_uniformly_controlled_rotation.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,9 +30,9 @@ class UniformlyControlledRy(BasicGate): Example: .. code-block:: python - controls = eng.allocate_qureg(2) - target = eng.allocate_qubit() - UniformlyControlledRy(angles=[0.1, 0.2, 0.3, 0.4]) | (controls, target) + controls = eng.allocate_qureg(2) + target = eng.allocate_qubit() + 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 @@ -69,7 +68,7 @@ def get_merged(self, other): def __str__(self): """Return a string representation of the object.""" - return "UniformlyControlledRy(" + str(self.angles) + ")" + return f"UniformlyControlledRy({str(self.angles)})" def __eq__(self, other): """Return True if same class, same rotation angles.""" @@ -93,9 +92,9 @@ class UniformlyControlledRz(BasicGate): Example: .. code-block:: python - controls = eng.allocate_qureg(2) - target = eng.allocate_qubit() - UniformlyControlledRz(angles=[0.1, 0.2, 0.3, 0.4]) | (controls, target) + controls = eng.allocate_qureg(2) + target = eng.allocate_qubit() + 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 @@ -131,7 +130,7 @@ def get_merged(self, other): def __str__(self): """Return a string representation of the object.""" - return "UniformlyControlledRz(" + str(self.angles) + ")" + return f"UniformlyControlledRz({str(self.angles)})" def __eq__(self, other): """Return True if same class, same rotation angles.""" diff --git a/projectq/ops/_uniformly_controlled_rotation_test.py b/projectq/ops/_uniformly_controlled_rotation_test.py index 362932483..49bef25ed 100644 --- a/projectq/ops/_uniformly_controlled_rotation_test.py +++ b/projectq/ops/_uniformly_controlled_rotation_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/__init__.py b/projectq/setups/__init__.py index f279b3d1d..dc799ab87 100755 --- a/projectq/setups/__init__.py +++ b/projectq/setups/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/_utils.py b/projectq/setups/_utils.py index 6f933aac6..434eb5424 100644 --- a/projectq/setups/_utils.py +++ b/projectq/setups/_utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 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 02ac24b19..5b668aaf5 100644 --- a/projectq/setups/aqt.py +++ b/projectq/setups/aqt.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/aqt_test.py b/projectq/setups/aqt_test.py index e36146e0b..415b5a4a8 100644 --- a/projectq/setups/aqt_test.py +++ b/projectq/setups/aqt_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/awsbraket.py b/projectq/setups/awsbraket.py index 0268b834f..46247ca9d 100644 --- a/projectq/setups/awsbraket.py +++ b/projectq/setups/awsbraket.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -86,4 +85,4 @@ def get_engine_list(credentials=None, device=None): other_gates=(Barrier,), ) return setup - raise DeviceNotHandledError('Unsupported device type: {}!'.format(device)) # pragma: no cover + raise DeviceNotHandledError(f'Unsupported device type: {device}!') # pragma: no cover diff --git a/projectq/setups/awsbraket_test.py b/projectq/setups/awsbraket_test.py index 6f8ad41b7..a0455258f 100644 --- a/projectq/setups/awsbraket_test.py +++ b/projectq/setups/awsbraket_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index 908b04cc9..ec5fcb277 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/_gates_test.py b/projectq/setups/decompositions/_gates_test.py index 5ea730ff0..12c05cdc8 100755 --- a/projectq/setups/decompositions/_gates_test.py +++ b/projectq/setups/decompositions/_gates_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/amplitudeamplification.py b/projectq/setups/decompositions/amplitudeamplification.py index cbf2b0af7..bd38f8086 100644 --- a/projectq/setups/decompositions/amplitudeamplification.py +++ b/projectq/setups/decompositions/amplitudeamplification.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,17 +27,19 @@ Example: .. code-block:: python - def func_algorithm(eng,system_qubits): + def func_algorithm(eng, system_qubits): All(H) | system_qubits - def func_oracle(eng,system_qubits,qaa_ancilla): + + def func_oracle(eng, system_qubits, qaa_ancilla): # This oracle selects the state |010> as the one marked with Compute(eng): - All(X) | system_qubits[0::2] + All(X) | system_qubits[0::2] with Control(eng, system_qubits): - X | qaa_ancilla + X | qaa_ancilla Uncompute(eng) + system_qubits = eng.allocate_qureg(3) # Prepare the qaa_ancilla qubit in the |-> state qaa_ancilla = eng.allocate_qubit() @@ -48,9 +49,9 @@ def func_oracle(eng,system_qubits,qaa_ancilla): # Creates the initial state form the Algorithm func_algorithm(eng, system_qubits) # Apply Quantum Amplitude Amplification the correct number of times - num_it = int(math.pi/4.*math.sqrt(1 << 3)) + num_it = int(math.pi / 4.0 * math.sqrt(1 << 3)) with Loop(eng, num_it): - QAA(func_algorithm, func_oracle) | (system_qubits, qaa_ancilla) + QAA(func_algorithm, func_oracle) | (system_qubits, qaa_ancilla) All(Measure) | system_qubits diff --git a/projectq/setups/decompositions/amplitudeamplification_test.py b/projectq/setups/decompositions/amplitudeamplification_test.py index adab7cd92..34b490de9 100644 --- a/projectq/setups/decompositions/amplitudeamplification_test.py +++ b/projectq/setups/decompositions/amplitudeamplification_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -90,10 +89,7 @@ def test_simple_grover(): 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, - ) + ), f"The obtained probability is less than expected {total_prob_after:f} vs. {theoretical_prob:f}" def complex_algorithm(eng, qreg): @@ -172,10 +168,7 @@ def test_complex_aa(): 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, - ) + ), f"The obtained probability is less than expected {total_prob_after:f} vs. {theoretical_prob:f}" def test_string_functions(): diff --git a/projectq/setups/decompositions/arb1qubit2rzandry.py b/projectq/setups/decompositions/arb1qubit2rzandry.py index 699b1fcfd..33da891ea 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -129,7 +128,7 @@ def _find_parameters(matrix): # pylint: disable=too-many-branches,too-many-stat raise Exception( "Couldn't find parameters for matrix ", matrix, - "This shouldn't happen. Maybe the matrix is " + "not unitary?", + "This shouldn't happen. Maybe the matrix is not unitary?", ) # Case 2: cos(c/2) == 0: elif abs(matrix[0][0]) < TOLERANCE: @@ -158,7 +157,7 @@ def _find_parameters(matrix): # pylint: disable=too-many-branches,too-many-stat raise Exception( "Couldn't find parameters for matrix ", matrix, - "This shouldn't happen. Maybe the matrix is " + "not unitary?", + "This shouldn't happen. Maybe the matrix is not unitary?", ) # Case 3: sin(c/2) != 0 and cos(c/2) !=0: else: @@ -171,22 +170,26 @@ def _find_parameters(matrix): # pylint: disable=too-many-branches,too-many-stat else: 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), - (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]) - 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), - (two_b/4.+3./2.*math.pi) % (2*math.pi)] + possible_d_half = [ + two_d / 4.0 % (2 * math.pi), + (two_d / 4.0 + math.pi / 2.0) % (2 * math.pi), + (two_d / 4.0 + math.pi) % (2 * math.pi), + (two_d / 4.0 + 3.0 / 2.0 * math.pi) % (2 * math.pi), + ] + two_b = 2.0 * cmath.phase(matrix[1][0]) - 2.0 * cmath.phase(matrix[0][0]) + possible_b_half = [ + two_b / 4.0 % (2 * math.pi), + (two_b / 4.0 + math.pi / 2.0) % (2 * math.pi), + (two_b / 4.0 + math.pi) % (2 * math.pi), + (two_b / 4.0 + 3.0 / 2.0 * math.pi) % (2 * math.pi), + ] tmp = math.acos(abs(matrix[1][1])) - 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 + possible_c_half = [ + tmp % (2 * math.pi), + (tmp + math.pi) % (2 * math.pi), + (-1.0 * tmp) % (2 * math.pi), + (-1.0 * tmp + math.pi) % (2 * math.pi), + ] found = False 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): @@ -196,7 +199,7 @@ def _find_parameters(matrix): # pylint: disable=too-many-branches,too-many-stat raise Exception( "Couldn't find parameters for matrix ", matrix, - "This shouldn't happen. Maybe the matrix is " + "not unitary?", + "This shouldn't happen. Maybe the matrix is not unitary?", ) return (a, b_half, c_half, d_half) diff --git a/projectq/setups/decompositions/arb1qubit2rzandry_test.py b/projectq/setups/decompositions/arb1qubit2rzandry_test.py index 90a72057a..452e4d9c9 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry_test.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/barrier.py b/projectq/setups/decompositions/barrier.py index f3e94f408..6711e838b 100755 --- a/projectq/setups/decompositions/barrier.py +++ b/projectq/setups/decompositions/barrier.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/barrier_test.py b/projectq/setups/decompositions/barrier_test.py index c7b3ca158..a09bb2f70 100755 --- a/projectq/setups/decompositions/barrier_test.py +++ b/projectq/setups/decompositions/barrier_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # -*- codingf53: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py index b2f8b701a..48490a814 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017, 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -139,12 +138,12 @@ def _recognize_v(matrix): # pylint: disable=too-many-branches (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 + possible_c_half = [ + tmp % (2 * math.pi), + (tmp + math.pi) % (2 * math.pi), + (-1.0 * tmp) % (2 * math.pi), + (-1.0 * tmp + math.pi) % (2 * math.pi), + ] 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) diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py index 280df747b..f402f7027 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -99,12 +98,9 @@ def _decomp_gates(eng, cmd): return False -# yapf: disable -@pytest.mark.parametrize("gate_matrix", [[[1, 0], [0, -1]], - [[0, -1j], [1j, 0]]]) +@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()) diff --git a/projectq/setups/decompositions/cnot2cz.py b/projectq/setups/decompositions/cnot2cz.py index ea60ce2f3..950b12b68 100644 --- a/projectq/setups/decompositions/cnot2cz.py +++ b/projectq/setups/decompositions/cnot2cz.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/cnot2cz_test.py b/projectq/setups/decompositions/cnot2cz_test.py index ca9b3af2f..6738f0483 100644 --- a/projectq/setups/decompositions/cnot2cz_test.py +++ b/projectq/setups/decompositions/cnot2cz_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index 7565ef062..f60ee89ed 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/cnot2rxx_test.py b/projectq/setups/decompositions/cnot2rxx_test.py index 9cb72fc3e..e131ab928 100644 --- a/projectq/setups/decompositions/cnot2rxx_test.py +++ b/projectq/setups/decompositions/cnot2rxx_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/cnu2toffoliandcu.py b/projectq/setups/decompositions/cnu2toffoliandcu.py index cd4b79901..a463de0c2 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/cnu2toffoliandcu_test.py b/projectq/setups/decompositions/cnu2toffoliandcu_test.py index 178813beb..b64203bfd 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu_test.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/controlstate.py b/projectq/setups/decompositions/controlstate.py index f10a8add3..7aa0b5924 100755 --- a/projectq/setups/decompositions/controlstate.py +++ b/projectq/setups/decompositions/controlstate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/controlstate_test.py b/projectq/setups/decompositions/controlstate_test.py index f50a9b01a..02512e5e7 100755 --- a/projectq/setups/decompositions/controlstate_test.py +++ b/projectq/setups/decompositions/controlstate_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/crz2cxandrz.py b/projectq/setups/decompositions/crz2cxandrz.py index b36f356ba..b56603cc9 100755 --- a/projectq/setups/decompositions/crz2cxandrz.py +++ b/projectq/setups/decompositions/crz2cxandrz.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/entangle.py b/projectq/setups/decompositions/entangle.py index 30d921a9d..0f692f669 100755 --- a/projectq/setups/decompositions/entangle.py +++ b/projectq/setups/decompositions/entangle.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/globalphase.py b/projectq/setups/decompositions/globalphase.py index 69e38f081..679c08a51 100755 --- a/projectq/setups/decompositions/globalphase.py +++ b/projectq/setups/decompositions/globalphase.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py index 9a0eb4239..608629b10 100644 --- a/projectq/setups/decompositions/h2rx.py +++ b/projectq/setups/decompositions/h2rx.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/h2rx_test.py b/projectq/setups/decompositions/h2rx_test.py index 551517e42..fb3d2045b 100644 --- a/projectq/setups/decompositions/h2rx_test.py +++ b/projectq/setups/decompositions/h2rx_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/ph2r.py b/projectq/setups/decompositions/ph2r.py index c72f459ee..2fefe0dcd 100755 --- a/projectq/setups/decompositions/ph2r.py +++ b/projectq/setups/decompositions/ph2r.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/phaseestimation.py b/projectq/setups/decompositions/phaseestimation.py index efc788bdb..50d02253f 100644 --- a/projectq/setups/decompositions/phaseestimation.py +++ b/projectq/setups/decompositions/phaseestimation.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,29 +34,31 @@ n_qpe_ancillas = 3 qpe_ancillas = eng.allocate_qureg(n_qpe_ancillas) system_qubits = eng.allocate_qureg(1) - angle = cmath.pi*2.*0.125 - U = Ph(angle) # unitary_specfic_to_the_problem() + angle = cmath.pi * 2.0 * 0.125 + U = Ph(angle) # unitary_specfic_to_the_problem() # Apply Quantum Phase Estimation QPE(U) | (qpe_ancillas, system_qubits) All(Measure) | qpe_ancillas # Compute the phase from the ancilla measurement - #(https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) + # (https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) phasebinlist = [int(q) for q in qpe_ancillas] phase_in_bin = ''.join(str(j) for j in phasebinlist) - phase_int = int(phase_in_bin,2) - phase = phase_int / (2 ** n_qpe_ancillas) - print (phase) + phase_int = int(phase_in_bin, 2) + phase = phase_int / (2**n_qpe_ancillas) + print(phase) # Example using a function (two_qubit_gate). # Instead of applying QPE on a gate U one could provide a function + 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]) + n_qpe_ancillas = 3 qpe_ancillas = eng.allocate_qureg(n_qpe_ancillas) system_qubits = eng.allocate_qureg(2) @@ -68,12 +69,12 @@ def two_qubit_gate(system_q, time): All(Measure) | qpe_ancillas # Compute the phase from the ancilla measurement - #(https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) + # (https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) phasebinlist = [int(q) for q in qpe_ancillas] phase_in_bin = ''.join(str(j) for j in phasebinlist) - phase_int = int(phase_in_bin,2) - phase = phase_int / (2 ** n_qpe_ancillas) - print (phase) + phase_int = int(phase_in_bin, 2) + phase = phase_int / (2**n_qpe_ancillas) + print(phase) Attributes: unitary (BasicGate): Unitary Operation either a ProjectQ gate or a function f. diff --git a/projectq/setups/decompositions/phaseestimation_test.py b/projectq/setups/decompositions/phaseestimation_test.py index 151b8643a..08e33e587 100644 --- a/projectq/setups/decompositions/phaseestimation_test.py +++ b/projectq/setups/decompositions/phaseestimation_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,10 +57,7 @@ def test_simple_test_X_eigenvectors(): eng.flush() num_phase = (results == 0.5).sum() - assert num_phase / N >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / N, - 0.35, - ) + assert num_phase / N >= 0.35, f"Statistics phase calculation are not correct ({num_phase / N:f} vs. {0.35:f})" @flaky(max_runs=5, min_passes=2) @@ -91,10 +87,7 @@ def test_Ph_eigenvectors(): eng.flush() num_phase = (results == 0.125).sum() - assert num_phase / N >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / N, - 0.35, - ) + assert num_phase / N >= 0.35, f"Statistics phase calculation are not correct ({num_phase / N:f} vs. {0.35:f})" def two_qubit_gate(system_q, time): @@ -129,10 +122,7 @@ def test_2qubitsPh_andfunction_eigenvectors(): eng.flush() num_phase = (results == 0.125).sum() - assert num_phase / N >= 0.34, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / N, - 0.34, - ) + assert num_phase / N >= 0.34, f"Statistics phase calculation are not correct ({num_phase / N:f} vs. {0.34:f})" def test_X_no_eigenvectors(): @@ -179,10 +169,7 @@ def test_X_no_eigenvectors(): assert total == pytest.approx(N, abs=5) 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, - ) + ), f"Statistics on |+> probability are not correct ({plus_probability:f} vs. {1.0 / 4.0:f})" def test_string(): diff --git a/projectq/setups/decompositions/qft2crandhadamard.py b/projectq/setups/decompositions/qft2crandhadamard.py index 4d8f9ebb1..de7346bbf 100755 --- a/projectq/setups/decompositions/qft2crandhadamard.py +++ b/projectq/setups/decompositions/qft2crandhadamard.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/qubitop2onequbit.py b/projectq/setups/decompositions/qubitop2onequbit.py index 4109759ed..5fd68e345 100644 --- a/projectq/setups/decompositions/qubitop2onequbit.py +++ b/projectq/setups/decompositions/qubitop2onequbit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/qubitop2onequbit_test.py b/projectq/setups/decompositions/qubitop2onequbit_test.py index 44567fc9b..ed124c963 100644 --- a/projectq/setups/decompositions/qubitop2onequbit_test.py +++ b/projectq/setups/decompositions/qubitop2onequbit_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -96,7 +95,7 @@ def test_qubitop2singlequbit(): correct_eng.flush() for fstate in range(2 ** (num_qubits + 1)): - binary_state = format(fstate, '0' + str(num_qubits + 1) + 'b') + binary_state = format(fstate, f"0{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) diff --git a/projectq/setups/decompositions/r2rzandph.py b/projectq/setups/decompositions/r2rzandph.py index 918038e59..9fca9ed52 100755 --- a/projectq/setups/decompositions/r2rzandph.py +++ b/projectq/setups/decompositions/r2rzandph.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/rx2rz.py b/projectq/setups/decompositions/rx2rz.py index e7839461e..6fe1f22be 100644 --- a/projectq/setups/decompositions/rx2rz.py +++ b/projectq/setups/decompositions/rx2rz.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/rx2rz_test.py b/projectq/setups/decompositions/rx2rz_test.py index 35896f29f..767c805a0 100644 --- a/projectq/setups/decompositions/rx2rz_test.py +++ b/projectq/setups/decompositions/rx2rz_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/ry2rz.py b/projectq/setups/decompositions/ry2rz.py index 040907b3f..089d9c018 100644 --- a/projectq/setups/decompositions/ry2rz.py +++ b/projectq/setups/decompositions/ry2rz.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/ry2rz_test.py b/projectq/setups/decompositions/ry2rz_test.py index e5e0c909f..61f9e1cfd 100644 --- a/projectq/setups/decompositions/ry2rz_test.py +++ b/projectq/setups/decompositions/ry2rz_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index f61c10a11..d3116ed23 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py index 63dbc1f60..7418c5a1d 100644 --- a/projectq/setups/decompositions/rz2rx_test.py +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/sqrtswap2cnot.py b/projectq/setups/decompositions/sqrtswap2cnot.py index 8e5392a51..e2b645eaa 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot.py +++ b/projectq/setups/decompositions/sqrtswap2cnot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/sqrtswap2cnot_test.py b/projectq/setups/decompositions/sqrtswap2cnot_test.py index 0ec4706cd..2e920a70f 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot_test.py +++ b/projectq/setups/decompositions/sqrtswap2cnot_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py index 6b95bc30d..80e399acd 100644 --- a/projectq/setups/decompositions/stateprep2cnot.py +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index 72372e77b..dd1c1aced 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/swap2cnot.py b/projectq/setups/decompositions/swap2cnot.py index df17b4e4c..23ffa8c69 100755 --- a/projectq/setups/decompositions/swap2cnot.py +++ b/projectq/setups/decompositions/swap2cnot.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/time_evolution.py b/projectq/setups/decompositions/time_evolution.py index 9453f767e..9e029391e 100644 --- a/projectq/setups/decompositions/time_evolution.py +++ b/projectq/setups/decompositions/time_evolution.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/time_evolution_test.py b/projectq/setups/decompositions/time_evolution_test.py index e8eecaa1d..8d0197a56 100644 --- a/projectq/setups/decompositions/time_evolution_test.py +++ b/projectq/setups/decompositions/time_evolution_test.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/toffoli2cnotandtgate.py b/projectq/setups/decompositions/toffoli2cnotandtgate.py index cdaaf5bd6..b46c92ba8 100755 --- a/projectq/setups/decompositions/toffoli2cnotandtgate.py +++ b/projectq/setups/decompositions/toffoli2cnotandtgate.py @@ -1,4 +1,3 @@ -# -*- 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/decompositions/uniformlycontrolledr2cnot.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py index 62571e82e..e865e939d 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py index dbc5e75f9..08564ecd9 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -142,7 +141,7 @@ def test_uniformly_controlled_ry(n, gate_classes): correct_eng.flush() for fstate in range(2 ** (n + 1)): - binary_state = format(fstate, '0' + str(n + 1) + 'b') + binary_state = format(fstate, f"0{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) diff --git a/projectq/setups/default.py b/projectq/setups/default.py index 8ac280417..109f9c10b 100755 --- a/projectq/setups/default.py +++ b/projectq/setups/default.py @@ -1,4 +1,3 @@ -# -*- 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/grid.py b/projectq/setups/grid.py index 1261a72d4..65021b930 100644 --- a/projectq/setups/grid.py +++ b/projectq/setups/grid.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/grid_test.py b/projectq/setups/grid_test.py index 20ed6a7f2..7760d487f 100644 --- a/projectq/setups/grid_test.py +++ b/projectq/setups/grid_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index bab8ee824..122febaa6 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -1,4 +1,3 @@ -# -*- 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/ibm_test.py b/projectq/setups/ibm_test.py index d7ca80710..1cdef3425 100644 --- a/projectq/setups/ibm_test.py +++ b/projectq/setups/ibm_test.py @@ -1,4 +1,3 @@ -# -*- 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/ionq.py b/projectq/setups/ionq.py index e0c678a02..3c451048c 100644 --- a/projectq/setups/ionq.py +++ b/projectq/setups/ionq.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,7 +51,7 @@ def get_engine_list(token=None, device=None): service.authenticate(token=token) devices = service.show_devices() if not device or device not in devices: - raise DeviceOfflineError("Error checking engine list: no '{}' devices available".format(device)) + raise DeviceOfflineError(f"Error checking engine list: no '{device}' devices available") # # Qubit mapper diff --git a/projectq/setups/ionq_test.py b/projectq/setups/ionq_test.py index 6a3cde827..2b632bef7 100644 --- a/projectq/setups/ionq_test.py +++ b/projectq/setups/ionq_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,4 +52,4 @@ def mock_show_devices(*args, **kwargs): ) with pytest.raises(DeviceOfflineError): - projectq.setups.ionq.get_engine_list(device='simulator') + projectq.setups.ionq.get_engine_list(token='TOKEN', device='simulator') diff --git a/projectq/setups/linear.py b/projectq/setups/linear.py index 223ac2d26..299a64f80 100644 --- a/projectq/setups/linear.py +++ b/projectq/setups/linear.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/linear_test.py b/projectq/setups/linear_test.py index 7e838f460..a5e1ed773 100644 --- a/projectq/setups/linear_test.py +++ b/projectq/setups/linear_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index 86811b682..9e8049d4e 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/restrictedgateset_test.py b/projectq/setups/restrictedgateset_test.py index 163386902..92144bde7 100644 --- a/projectq/setups/restrictedgateset_test.py +++ b/projectq/setups/restrictedgateset_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index 0eda14f37..3841b638a 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/trapped_ion_decomposer_test.py b/projectq/setups/trapped_ion_decomposer_test.py index ba5d1518d..2638aac83 100644 --- a/projectq/setups/trapped_ion_decomposer_test.py +++ b/projectq/setups/trapped_ion_decomposer_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/tests/__init__.py b/projectq/tests/__init__.py index 16fc4afdf..ee1451dcd 100755 --- a/projectq/tests/__init__.py +++ b/projectq/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 5cb409d4c..541c1492c 100755 --- a/projectq/tests/_factoring_test.py +++ b/projectq/tests/_factoring_test.py @@ -1,4 +1,3 @@ -# -*- 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/__init__.py b/projectq/types/__init__.py index d81996907..ad912d2ba 100755 --- a/projectq/types/__init__.py +++ b/projectq/types/__init__.py @@ -1,4 +1,3 @@ -# -*- 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 74daedcac..41021fe1c 100755 --- a/projectq/types/_qubit.py +++ b/projectq/types/_qubit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +22,7 @@ .. code-block:: python from projectq import MainEngine + eng = MainEngine() qubit = eng.allocate_qubit() @@ -189,11 +189,11 @@ 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(f'{start_id}-{start_id + count - 1}' if count > 1 else f'{start_id}') start_id = qubit_id count = 1 - return "Qureg[{}]".format(', '.join(out_list)) + return f"Qureg[{', '.join(out_list)}]" @property def engine(self): diff --git a/projectq/types/_qubit_test.py b/projectq/types/_qubit_test.py index 35fc961e6..fb5fb394b 100755 --- a/projectq/types/_qubit_test.py +++ b/projectq/types/_qubit_test.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -85,7 +84,7 @@ def test_basic_qubit_hash(): @pytest.fixture def mock_main_engine(): - class MockMainEngine(object): + class MockMainEngine: def __init__(self): self.num_calls = 0 self.active_qubits = set() diff --git a/pyproject.toml b/pyproject.toml index 7752fa4bf..5e0559d69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,18 +1,85 @@ [build-system] -requires = ["setuptools>=42", "wheel", "pybind11>=2", "setuptools_scm[toml]>=3.4"] +requires = [ + 'setuptools>=61;python_version>="3.7"', + 'setuptools>=59;python_version<"3.7"', + 'wheel', + 'pybind11>=2', + 'setuptools_scm[toml]>6;python_version>="3.7"' +] build-backend = "setuptools.build_meta" +[project] +name = 'projectq' +authors = [ + {name = 'ProjectQ', email = 'info@projectq.ch'} +] +description = 'ProjectQ - An open source software framework for quantum computing' +requires-python = '>= 3.7' +license = {text= 'Apache License Version 2.0'} +readme = 'README.rst' +classifiers = [ + 'License :: OSI Approved :: Apache Software License', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10' +] +dynamic = ["version"] + +dependencies = [ + 'matplotlib >= 2.2.3', + 'networkx >= 2', + 'numpy', + 'requests', + 'scipy' +] + +[project.urls] +'Homepage' = 'http://www.projectq.ch' +'Documentation' = 'https://projectq.readthedocs.io/en/latest/' +'Issue Tracker' = 'https://github.com/ProjectQ-Framework/ProjectQ/' + +[project.optional-dependencies] + +azure-quantum = [ + 'azure-quantum' +] + +braket = [ + 'boto3' +] + +revkit = [ + 'revkit == 3.0a2.dev2', + 'dormouse' +] + +test = [ + 'flaky', + 'mock', + 'pytest >= 6.0', + 'pytest-cov', + 'pytest-mock' +] + +docs = [ + 'sphinx', + 'sphinx_rtd_theme' +] + # ============================================================================== [tool.black] line-length = 120 - target-version = ['py36','py37','py38'] + target-version = ['py37','py38','py39','py310'] skip-string-normalization = true [tool.check-manifest] - ignore = [ +ignore = [ 'PKG-INFO', '*.egg-info', '*.egg-info/*', @@ -110,10 +177,6 @@ write_to = 'VERSION.txt' write_to_template = '{version}' local_scheme = 'no-local-version' -[tool.yapf] - -column_limit = 120 - [tool.cibuildwheel] archs = ['auto64'] diff --git a/setup.cfg b/setup.cfg index 0f5a22bfd..c5248b57f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,69 +1,14 @@ -[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 - Programming Language :: Python :: 3.10 - [options] zip_safe = False packages = find: -python_requires = >= 3 -setup_requires = - setuptools_scm[toml] - pybind11 >= 2 -install_requires = - matplotlib >= 2.2.3 - networkx >= 2 - numpy - requests - scipy - -[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 - # ============================================================================== [flake8] max-line-length = 120 +ignore = E203,W503,E800 exclude = .git, __pycache__, @@ -71,6 +16,5 @@ exclude = dist, __init__.py docstring-quotes = """ -eradicate-whitelist = # yapf: disable# yapf: enable # ============================================================================== diff --git a/setup.py b/setup.py index 46c394d1b..32426d594 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Some of the setup.py code is inspired or copied from SQLAlchemy # SQLAlchemy was created by Michael Bayer. @@ -53,11 +52,101 @@ LinkError, ) from distutils.spawn import find_executable, spawn +from operator import itemgetter +from pathlib import Path from setuptools import Distribution as _Distribution from setuptools import Extension, setup from setuptools.command.build_ext import build_ext +try: + import setuptools_scm # noqa: F401 # pylint: disable=unused-import + + _HAS_SETUPTOOLS_SCM = True +except ImportError: + _HAS_SETUPTOOLS_SCM = False + +try: + import tomllib + + def parse_toml(filename): + """Parse a TOML file.""" + with open(str(filename), 'rb') as toml_file: + return tomllib.load(toml_file) + +except ImportError: + try: + import toml + + def parse_toml(filename): + """Parse a TOML file.""" + return toml.load(filename) + + except ImportError: + + def _find_toml_section_end(lines, start): + """Find the index of the start of the next section.""" + return ( + next(filter(itemgetter(1), enumerate(line.startswith('[') for line in lines[start + 1 :])))[0] + + start + + 1 + ) + + def _parse_list(lines): + """Parse a TOML list into a Python list.""" + # NB: This function expects the TOML list to be formatted like so (ignoring leading and trailing spaces): + # name = [ + # '...', + # ] + # Any other format is not supported. + name = None + elements = [] + + for idx, line in enumerate(lines): + if name is None and not line.startswith("'"): + name = line.split('=')[0].strip() + continue + if line.startswith("]"): + return (name, elements, idx + 1) + elements.append(line.rstrip(',').strip("'").strip('"')) + + raise RuntimeError(f'Failed to locate closing "]" for {name}') + + def parse_toml(filename): + """Very simple parser routine for pyproject.toml.""" + result = {'project': {'optional-dependencies': {}}} + with open(filename) as toml_file: + lines = [line.strip() for line in toml_file.readlines()] + lines = [line for line in lines if line and not line.startswith('#')] + + start = lines.index('[project]') + project_data = lines[start : _find_toml_section_end(lines, start)] + + start = lines.index('[project.optional-dependencies]') + optional_dependencies = lines[start + 1 : _find_toml_section_end(lines, start)] + + idx = 0 + N = len(project_data) + while idx < N: + line = project_data[idx] + shift = 1 + if line.startswith('name'): + result['project']['name'] = line.split('=')[1].strip().strip("'") + elif line.startswith('dependencies'): + (name, pkgs, shift) = _parse_list(project_data[idx:]) + result['project'][name] = pkgs + idx += shift + + idx = 0 + N = len(optional_dependencies) + while idx < N: + (opt_name, opt_pkgs, shift) = _parse_list(optional_dependencies[idx:]) + result['project']['optional-dependencies'][opt_name] = opt_pkgs + idx += shift + + return result + + # ============================================================================== # Helper functions and classes @@ -110,7 +199,7 @@ def compiler_test( """Return a boolean indicating whether a flag name is supported on the specified compiler.""" 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)) + temp.write(f'{include}\nint main (int argc, char **argv) {{ {body} return 0; }}') fname = temp.name if compile_postargs is None: @@ -139,7 +228,7 @@ def compiler_test( if compiler.compiler_type == 'msvc': err.close() os.dup2(olderr, sys.stderr.fileno()) - with open('err.txt', 'r') as err_file: + with open('err.txt') as err_file: if err_file.readlines(): raise RuntimeError('') except (CompileError, LinkError, RuntimeError): @@ -171,7 +260,7 @@ def _fix_macosx_header_paths(*args): compiler_args[idx] = item.replace(_MACOSX_DEVTOOLS_REF_PATH, _MACOSX_XCODE_REF_PATH) -# ------------------------------------------------------------------------------ +# ============================================================================== class BuildFailed(Exception): @@ -362,7 +451,7 @@ def _configure_compiler(self): # Other compiler tests status_msgs('Other compiler tests') - self.compiler.define_macro('VERSION_INFO', '"{}"'.format(self.distribution.get_version())) + self.compiler.define_macro('VERSION_INFO', f'"{self.distribution.get_version()}"') if compiler_type == 'unix' and compiler_test(self.compiler, '-fvisibility=hidden'): self.opts.append('-fvisibility=hidden') @@ -394,7 +483,7 @@ def _configure_openmp(self): # 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) + l_arg = f'-L{llvm_root}/lib' if compiler_test(self.compiler, flag, link_postargs=[l_arg, flag], **kwargs): self.opts.append(flag) self.link_opts.extend((l_arg, flag)) @@ -411,8 +500,8 @@ def _configure_openmp(self): # Only add the flag if the compiler we are using is the one # from MacPorts if macports_root in compiler_root: - inc_dir = '{}/include/libomp'.format(macports_root) - lib_dir = '{}/lib/libomp'.format(macports_root) + inc_dir = f'{macports_root}/include/libomp' + lib_dir = f'{macports_root}/lib/libomp' c_arg = '-I' + inc_dir l_arg = '-L' + lib_dir @@ -434,6 +523,17 @@ def _configure_intrinsics(self): '/arch:AVX', ] + if int(os.environ.get('PROJECTQ_NOINTRIN', '0')) or ( + sys.platform == 'darwin' + and platform.machine() == 'arm64' + and (sys.version_info.major == 3 and sys.version_info.minor < 9) + ): + important_msgs( + 'Either requested no-intrinsics or detected potentially unsupported Python version on ' + 'Apple Silicon: disabling intrinsics' + ) + self.compiler.define_macro('NOINTRIN') + return if os.environ.get('PROJECTQ_DISABLE_ARCH_NATIVE'): flags = flags[1:] @@ -458,9 +558,6 @@ def _configure_cxx_standard(self): return cxx_standards = [17, 14, 11] - if sys.version_info[0] < 3: - cxx_standards = [year for year in cxx_standards if year < 17] - if sys.platform == 'darwin': major_version = int(platform.mac_ver()[0].split('.')[0]) minor_version = int(platform.mac_ver()[0].split('.')[1]) @@ -468,11 +565,11 @@ def _configure_cxx_standard(self): cxx_standards = [year for year in cxx_standards if year < 17] for year in cxx_standards: - flag = '-std=c++{}'.format(year) + flag = f'-std=c++{year}' if compiler_test(self.compiler, flag): self.opts.append(flag) return - flag = '/Qstd=c++{}'.format(year) + flag = f'/Qstd=c++{year}' if compiler_test(self.compiler, flag): self.opts.append(flag) return @@ -504,7 +601,7 @@ def _cleanup_compiler_flags(self): if compiler_test(compiler, flag, link_shared_lib=True, compile_postargs=['-fPIC']): flags.append(flag) else: - important_msgs('WARNING: ignoring unsupported compiler flag: {}'.format(flag)) + important_msgs(f'WARNING: ignoring unsupported compiler flag: {flag}') self.compiler.compiler = [compiler_exe] + list(compiler_flags) self.compiler.compiler_so = [compiler_exe_so] + list(compiler_so_flags) @@ -518,7 +615,7 @@ def _cleanup_compiler_flags(self): if compiler_test(self.compiler, flag): flags.append(flag) else: - important_msgs('WARNING: ignoring unsupported compiler flag: {}'.format(flag)) + important_msgs(f'WARNING: ignoring unsupported compiler flag: {flag}') self.compiler.compiler.extend(flags) self.compiler.compiler_so.extend(flags) @@ -588,33 +685,30 @@ def initialize_options(self): self.include_extras = None self.include_all_extras = None self.extra_pkgs = [] + self.dependencies = [] def finalize_options(self): """Finalize this command's options.""" include_extras = self.include_extras.split(',') if self.include_extras else [] + pyproject_toml = parse_toml(Path(__file__).parent / 'pyproject.toml') - 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) + for name, pkgs in pyproject_toml['project']['optional-dependencies'].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) + # pylint: disable=attribute-defined-outside-init + self.dependencies = self.distribution.install_requires + if not self.dependencies: + self.dependencies = pyproject_toml['project']['dependencies'] def run(self): """Execute this command.""" with open('requirements.txt', 'w') as req_file: - try: - for pkg in self.distribution.install_requires: - req_file.write('{}\n'.format(pkg)) - except TypeError: # Mostly for old setuptools (< 30.x) - for pkg in self.distribution.command_options['options']['install_requires']: - req_file.write('{}\n'.format(pkg)) + for pkg in self.dependencies: + req_file.write(f'{pkg}\n') req_file.write('\n') for pkg in self.extra_pkgs: - req_file.write('{}\n'.format(pkg)) + req_file.write(f'{pkg}\n') # ------------------------------------------------------------------------------ @@ -645,9 +739,12 @@ def run_setup(with_cext): else: kwargs['ext_modules'] = [] + # NB: Workaround for people calling setup.py without a proper environment containing setuptools-scm + # This can typically be the case when calling the `gen_reqfile` or `clang_tidy`commands + if not _HAS_SETUPTOOLS_SCM: + kwargs['version'] = '0.0.0' + setup( - use_scm_version={'local_scheme': 'no-local-version'}, - setup_requires=['setuptools_scm'], cmdclass={ 'build_ext': BuildExt, 'clang_tidy': ClangTidy, From b4b839e32907972458aa54f2539b59a4e5b7f2d2 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 27 Oct 2022 10:01:08 +0200 Subject: [PATCH 096/113] Context Manager: with flushing(MainEngine()) as eng: (#449) * Context Manager: with flushing(MainEngine()) as eng: Simplify the creation, use, and flushing of any Engine using a Python context manager. When exiting the `with` block, the engine is _automatically_ flushed. Modeled after https://docs.python.org/3/library/contextlib.html#contextlib.closing ```python from projectq import MainEngine, flushing # import the main compiler engine from projectq.ops import ( H, Measure, ) # import the operations we want to perform (Hadamard and measurement) with flushing(MainEngine()) as eng: # create a default compiler (the back-end is a simulator) qubit = eng.allocate_qubit() # allocate a quantum register with 1 qubit H | qubit # apply a Hadamard gate Measure | qubit # measure the qubit # This line is no longer required... eng.flush() print(f"Measured {int(qubit)}") # converting a qubit to int or bool gives access to the measurement result * Add tests * Run linters/formatters on code * Fix isort comment pragmas * Really fix isort issues in cengines/__init__.py Co-authored-by: Damien Nguyen --- CHANGELOG.md | 6 ++++ projectq/cengines/__init__.py | 23 +++++++++++++++ projectq/cengines/_withflushing_test.py | 38 +++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 projectq/cengines/_withflushing_test.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8839518a3..ec8c85b9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +- Python context manager `with flusing(MainEngine()) as eng:` + ## [v0.8.0] - 2022-10-18 ### Added diff --git a/projectq/cengines/__init__.py b/projectq/cengines/__init__.py index aa36092f2..bb487c748 100755 --- a/projectq/cengines/__init__.py +++ b/projectq/cengines/__init__.py @@ -14,10 +14,14 @@ """ProjectQ module containing all compiler engines.""" +from contextlib import contextmanager + from ._basics import BasicEngine, ForwarderEngine, LastEngineException # isort:skip from ._cmdmodifier import CommandModifier # isort:skip from ._basicmapper import BasicMapperEngine # isort:skip +# isort: split + from ._ibm5qubitmapper import IBM5QubitMapper from ._linearmapper import LinearMapper, return_swap_depth from ._main import MainEngine, NotYetMeasuredError, UnsupportedEngineError @@ -33,3 +37,22 @@ from ._tagremover import TagRemover from ._testengine import CompareEngine, DummyEngine from ._twodmapper import GridMapper + + +@contextmanager +def flushing(engine): + """ + Context manager to flush the given engine at the end of the 'with' context block. + + Example: + with flushing(MainEngine()) as eng: + qubit = eng.allocate_qubit() + ... + + Calling 'eng.flush()' is no longer needed because the engine will be flushed at the + end of the 'with' block even if an exception has been raised within that block. + """ + try: + yield engine + finally: + engine.flush() diff --git a/projectq/cengines/_withflushing_test.py b/projectq/cengines/_withflushing_test.py new file mode 100644 index 000000000..b0e844bf5 --- /dev/null +++ b/projectq/cengines/_withflushing_test.py @@ -0,0 +1,38 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for projectq.cengines.__init__.py.""" + +from unittest.mock import MagicMock + +from projectq.cengines import DummyEngine, flushing + + +def test_with_flushing(): + """Test with flushing() as eng:""" + with flushing(DummyEngine()) as engine: + engine.flush = MagicMock() + assert engine.flush.call_count == 0 + assert engine.flush.call_count == 1 + + +def test_with_flushing_with_exception(): + """Test with flushing() as eng: with an exception raised in the 'with' block.""" + try: + with flushing(DummyEngine()) as engine: + engine.flush = MagicMock() + assert engine.flush.call_count == 0 + raise ValueError("An exception is raised in the 'with' block") + except ValueError: + pass + assert engine.flush.call_count == 1 From e73d5822cf411fa0f3dafb2066642c04e495c09a Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 27 Oct 2022 10:33:01 +0200 Subject: [PATCH 097/113] Upgrade GitHub Actions (#450) * Upgrade GitHub Actions https://github.com/actions/cache/releases https://github.com/actions/checkout/releases https://github.com/actions/setup-python/releases * Update CHANGELOG * Properly update CHANGELOG Co-authored-by: Damien Nguyen --- .github/workflows/ci.yml | 22 +++++++++++----------- CHANGELOG.md | 4 ++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffda8c62b..cbbfb6183 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: runs-on: ${{ matrix.runs-on }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} @@ -34,7 +34,7 @@ jobs: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Setup Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} architecture: 'x64' @@ -45,7 +45,7 @@ jobs: echo "::set-output name=dir::$(python -m pip cache dir)" - name: Cache wheels - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache.outputs.dir }} key: ${{ runner.os }}-${{ matrix.python }}-pip-${{ hashFiles('**/setup.cfg') }} @@ -125,7 +125,7 @@ jobs: container: "silkeh/clang:${{ matrix.clang }}" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} @@ -173,7 +173,7 @@ jobs: container: "gcc:${{ matrix.gcc }}" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} @@ -226,7 +226,7 @@ jobs: run: echo 'keepcache=1' >> /etc/yum.conf - name: Setup yum cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | /var/cache/yum/ @@ -251,7 +251,7 @@ jobs: yum install -y git git config --global --add safe.directory /__w/ProjectQ/ProjectQ - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} @@ -263,7 +263,7 @@ jobs: run: mkdir -p ~/.cache/pip - name: Cache wheels - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-centos${{ matrix.centos }}-pip-${{ hashFiles('**/setup.cfg') }} @@ -293,13 +293,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Create pip cache dir run: mkdir -p ~/.cache/pip - name: Cache wheels - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-doc-pip-${{ hashFiles('**/setup.cfg') }} @@ -311,7 +311,7 @@ jobs: git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 - name: Install docs & setup requirements run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index ec8c85b9e..cb610947c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Python context manager `with flusing(MainEngine()) as eng:` +### Repository + +- Update GitHub workflow action versions + ## [v0.8.0] - 2022-10-18 ### Added From 71a3af0c3ca265a72023968d96473df99f29048a Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Sun, 30 Oct 2022 20:13:11 +0100 Subject: [PATCH 098/113] Feature/upgrade GitHub workflows (#452) * Update GitHub Action versions * Also update actions/checkout * Update CHANGELOG --- .github/workflows/ci.yml | 19 +++++++------------ .github/workflows/draft_release.yml | 2 +- .github/workflows/format.yml | 11 +++++++---- .github/workflows/publish_release.yml | 26 +++++++++++++++----------- .github/workflows/pull_request.yml | 2 +- CHANGELOG.md | 2 +- 6 files changed, 32 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbbfb6183..9f89399a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,18 +38,10 @@ jobs: 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@v3 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.python }}-pip-${{ hashFiles('**/setup.cfg') }} - restore-keys: ${{ runner.os }}-${{ matrix.python }}-pip- + cache: 'pip' + cache-dependency-path: | + setup.cfg + pyproject.toml - name: Generate requirement file (Unix) if: runner.os != 'Windows' @@ -312,6 +304,9 @@ jobs: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - uses: actions/setup-python@v4 + with: + python-version: '3.x' + architecture: 'x64' - name: Install docs & setup requirements run: | diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index 6594b2253..c4854cd2c 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -14,7 +14,7 @@ jobs: - name: Install git-flow run: sudo apt update && sudo apt install -y git-flow - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Configure git-flow run: | diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 752d64fc9..1a3b4cb96 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -14,19 +14,22 @@ jobs: name: Format and static analysis runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - uses: actions/setup-python@v2 + + - uses: actions/setup-python@v4 + with: + python-version: '3.x' - name: Install pre-commit run: python3 -m pip install --upgrade pre-commit 'virtualenv!=20.11' - name: Cache pre-commit hooks - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pre-commit key: pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} @@ -46,7 +49,7 @@ jobs: CXX: clang++ steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Prepare env run: > diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index ff4bcd221..aa88b802a 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -30,10 +30,10 @@ jobs: with: platforms: arm64 - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 if: github.event_name != 'workflow_dispatch' - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 if: github.event_name == 'workflow_dispatch' with: ref: 'master' @@ -82,7 +82,9 @@ jobs: # ======================================================================== - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 + with: + python-version: '3.x' - name: Install Python packages run: python -m pip install -U --prefer-binary pip setuptools build wheel twine 'cibuildwheel<3,>=2' @@ -113,21 +115,21 @@ jobs: CIBW_TEST_SKIP: '*' - name: Files for Pypi upload - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: steps.src-dist.outcome == 'success' with: name: pypy_wheels path: ./dist - name: Binary wheels - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: steps.binary-dist.outcome == 'success' with: name: wheels path: ./binary_dist - name: Binary wheels that failed tests - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: steps.failed-dist.outcome == 'success' with: name: failed_wheels @@ -175,17 +177,17 @@ jobs: # ------------------------------------------------------------------------ # Checkout repository to get CHANGELOG - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 if: github.event_name != 'workflow_dispatch' - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 if: github.event_name == 'workflow_dispatch' with: ref: 'master' # ------------------------------------------------------------------------ - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 # Code below inspired from this action: # - uses: taiki-e/create-gh-release-action@v1 @@ -220,9 +222,11 @@ jobs: runs-on: ubuntu-latest needs: release steps: - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 + with: + python-version: '3.x' - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 - name: Publish standard package uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index acabd22ce..35eae58cc 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest if: github.ref != 'refs/heads/master' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - id: changelog-enforcer uses: dangoslen/changelog-enforcer@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index cb610947c..f3d65056a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository -- Update GitHub workflow action versions +- Update all GitHub workflow action versions ## [v0.8.0] - 2022-10-18 From ff757e75f205dc401fd9377de1f5806155c44b5c Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Sun, 30 Oct 2022 21:57:35 +0100 Subject: [PATCH 099/113] Prepare the introduction of pre-commit CI + repository configuration updates (#453) * Remove pre-commit from GitHub workflows in favour of pre-commit CI * Remove manual tag for pre-commit hooks * Update CHANGELOG * Update pre-commit configuration file + add .yamllint - Added new pre-commit hooks + codespell + pydocstyle + doc8 + yamllint - Update pre-commit hook versions - Add .yamllint configuration file - Add .codespell.allow file for codespell * Delete .travis.yml file since not used anymore * Update YAML files to satisfy yamllint * Update Markdown files to fix linter/formatter warnings * Update ReST files to fix linter/formatter warnings * Update Python files to fix linter/formatter warnings * Rename CI jobs * Install pybind11 earlier in CI build * Update CHANGELOG * Address pre-commit CI virtualenv size limitations --- .codespell.allow | 5 + .github/workflows/ci.yml | 445 +++++++++--------- .github/workflows/draft_release.yml | 87 ++-- .github/workflows/format.yml | 58 +-- .github/workflows/publish_release.yml | 62 ++- .github/workflows/pull_request.yml | 16 +- .pre-commit-config.yaml | 50 +- .readthedocs.yaml | 6 +- .travis.yml | 76 --- .yamllint | 8 + CHANGELOG.md | 10 +- README.rst | 60 ++- docs/README.rst | 7 +- docs/conf.py | 2 +- docs/examples.rst | 67 ++- docs/index.rst | 12 +- docs/projectq.rst | 3 +- docs/tutorials.rst | 74 ++- examples/README.rst | 36 +- projectq/__init__.py | 2 +- projectq/backends/_aqt/__init__.py | 2 +- projectq/backends/_aqt/_aqt.py | 2 +- projectq/backends/_aqt/_aqt_http_client.py | 4 +- projectq/backends/_awsbraket/__init__.py | 5 +- projectq/backends/_awsbraket/_awsbraket.py | 4 +- .../_awsbraket/_awsbraket_boto3_client.py | 16 +- projectq/backends/_azure/__init__.py | 3 +- projectq/backends/_azure/_azure_quantum.py | 2 +- projectq/backends/_circuits/__init__.py | 2 +- projectq/backends/_circuits/_to_latex.py | 1 + projectq/backends/_ibm/__init__.py | 2 +- projectq/backends/_ibm/_ibm_http_client.py | 2 +- projectq/backends/_ionq/__init__.py | 2 +- projectq/backends/_ionq/_ionq.py | 4 +- projectq/backends/_ionq/_ionq_http_client.py | 2 +- projectq/backends/_resource.py | 2 +- projectq/backends/_sim/__init__.py | 2 +- projectq/backends/_sim/_simulator.py | 2 +- projectq/cengines/_linearmapper.py | 6 +- projectq/cengines/_main.py | 4 +- projectq/cengines/_optimize.py | 2 +- projectq/cengines/_swapandcnotflipper.py | 4 +- projectq/cengines/_testengine.py | 2 +- projectq/libs/__init__.py | 2 +- projectq/libs/hist/__init__.py | 5 +- projectq/libs/math/__init__.py | 2 + projectq/libs/math/_gates.py | 4 +- projectq/libs/math/_quantummath.py | 2 +- projectq/libs/revkit/__init__.py | 2 +- projectq/libs/revkit/_control_function.py | 2 +- projectq/libs/revkit/_phase.py | 2 +- projectq/libs/revkit/_utils.py | 4 +- projectq/meta/__init__.py | 5 +- projectq/meta/_compute.py | 4 +- projectq/meta/_compute_test.py | 2 +- projectq/meta/_control.py | 2 +- projectq/meta/_dagger.py | 2 +- projectq/meta/_loop.py | 2 +- projectq/ops/__init__.py | 2 +- projectq/ops/_basics.py | 14 +- projectq/ops/_command_test.py | 4 +- projectq/ops/_gates.py | 2 +- projectq/ops/_qaagate.py | 6 +- projectq/ops/_qubit_operator.py | 4 +- projectq/ops/_time_evolution.py | 2 +- projectq/setups/__init__.py | 2 +- projectq/setups/_utils.py | 2 +- projectq/setups/aqt.py | 2 +- projectq/setups/decompositions/__init__.py | 2 + .../decompositions/amplitudeamplification.py | 6 +- .../amplitudeamplification_test.py | 8 +- .../decompositions/arb1qubit2rzandry.py | 2 +- .../decompositions/carb1qubit2cnotrzandry.py | 6 +- .../setups/decompositions/controlstate.py | 2 +- .../setups/decompositions/phaseestimation.py | 2 +- projectq/setups/grid.py | 2 +- projectq/setups/linear.py | 2 +- projectq/setups/restrictedgateset.py | 4 +- projectq/setups/trapped_ion_decomposer.py | 6 +- projectq/tests/__init__.py | 2 + projectq/types/__init__.py | 2 +- projectq/types/_qubit.py | 2 +- pyproject.toml | 12 +- setup.py | 4 +- 84 files changed, 706 insertions(+), 603 deletions(-) create mode 100644 .codespell.allow delete mode 100755 .travis.yml create mode 100644 .yamllint diff --git a/.codespell.allow b/.codespell.allow new file mode 100644 index 000000000..1edf5ded2 --- /dev/null +++ b/.codespell.allow @@ -0,0 +1,5 @@ +Braket +braket +te +Ket +ket diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f89399a4..a9a252762 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,5 @@ +--- + name: CI on: @@ -16,86 +18,85 @@ jobs: matrix: runs-on: [ubuntu-latest, windows-latest, macos-latest] python: - - 3.7 - - 3.8 - - 3.9 - - '3.10' + - 3.7 + - 3.8 + - 3.9 + - '3.10' - name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" + name: "Python ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" runs-on: ${{ matrix.runs-on }} steps: - - uses: actions/checkout@v3 - - - name: Get history and tags for SCM versioning to work - if: ${{ !env.ACT }} - run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - - name: Setup Python ${{ matrix.python }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python }} - architecture: 'x64' - cache: 'pip' - cache-dependency-path: | - setup.cfg - pyproject.toml - - - name: Generate requirement file (Unix) - if: runner.os != 'Windows' - run: | - python setup.py gen_reqfile --include-extras=test,azure-quantum,braket,revkit - - - name: Generate requirement file (Windows) - if: runner.os == 'Windows' - run: | - python setup.py gen_reqfile --include-extras=test,azure-quantum,braket - - - name: Prepare env - run: | - python -m pip install -U pip setuptools wheel - cat requirements.txt - python -m pip install -r requirements.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 (Unix) - if: runner.os != 'Windows' - run: python -m pip install -ve .[azure-quantum,braket,revkit,test] - - - name: Build and install package (Windows) - if: runner.os == 'Windows' - run: python -m pip install -ve .[azure-quantum,braket,test] - - - 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 - + - uses: actions/checkout@v3 + + - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + architecture: 'x64' + cache: 'pip' + cache-dependency-path: | + setup.cfg + pyproject.toml + + - name: Generate requirement file (Unix) + if: runner.os != 'Windows' + run: | + python setup.py gen_reqfile --include-extras=test,azure-quantum,braket,revkit + + - name: Generate requirement file (Windows) + if: runner.os == 'Windows' + run: | + python setup.py gen_reqfile --include-extras=test,azure-quantum,braket + + - name: Prepare env + run: | + python -m pip install -U pip setuptools wheel pybind11 + cat requirements.txt + python -m pip install -r requirements.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 (Unix) + if: runner.os != 'Windows' + run: python -m pip install -ve .[azure-quantum,braket,revkit,test] + + - name: Build and install package (Windows) + if: runner.os == 'Windows' + run: python -m pip install -ve .[azure-quantum,braket,test] + + - 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 }} + - name: Coveralls Finished + run: | + pip3 install --upgrade coveralls + coveralls --finish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} clang: @@ -113,43 +114,43 @@ jobs: CXX: clang++ PROJECTQ_CLEANUP_COMPILER_FLAGS: ${{ (matrix.clang < 10) && 1 || 0 }} - name: "🐍 3 • Clang ${{ matrix.clang }} • x64" + name: "Python 3 • Clang ${{ matrix.clang }} • x64" container: "silkeh/clang:${{ matrix.clang }}" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - name: Get history and tags for SCM versioning to work - if: ${{ !env.ACT }} - run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} + 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 - libomp-dev - --no-install-recommends + - 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 + libomp-dev + --no-install-recommends - - name: Prepare Python env - run: | - python3 -m pip install -U pip setuptools wheel - python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket - cat requirements.txt - python3 -m pip install -r requirements.txt --prefer-binary + - name: Prepare Python env + run: | + python3 -m pip install -U pip setuptools wheel + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + cat requirements.txt + python3 -m pip install -r requirements.txt --prefer-binary - - name: Upgrade pybind11 and flaky - run: python3 -m pip install --upgrade pybind11 flaky --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 .[azure-quantum,braket,test] + - name: Build and install package + run: python3 -m pip install -ve .[azure-quantum,braket,test] - - name: Pytest - run: | - echo 'backend: Agg' > matplotlibrc - python3 -m pytest -p no:warnings + - name: Pytest + run: | + echo 'backend: Agg' > matplotlibrc + python3 -m pytest -p no:warnings gcc: @@ -161,42 +162,42 @@ jobs: - 7 # C++17 earliest version - latest - name: "🐍 3 • GCC ${{ matrix.gcc }} • x64" + name: "Python 3 • GCC ${{ matrix.gcc }} • x64" container: "gcc:${{ matrix.gcc }}" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - name: Get history and tags for SCM versioning to work - if: ${{ !env.ACT }} - run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} + 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 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 -m pip install -U pip setuptools wheel - python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket - cat requirements.txt - python3 -m pip install -r requirements.txt --prefer-binary + - name: Prepare Python env + run: | + python3 -m pip install -U pip setuptools wheel + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + cat requirements.txt + python3 -m pip install -r requirements.txt --prefer-binary - - name: Upgrade pybind11 and flaky - run: python3 -m pip install --upgrade pybind11 flaky --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 .[azure-quantum,braket,test] + - name: Build and install package + run: python3 -m pip install -ve .[azure-quantum,braket,test] - - name: Pytest - run: | - echo 'backend: Agg' > matplotlibrc - python3 -m pytest -p no:warnings + - 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 @@ -210,74 +211,74 @@ jobs: - 7 # GCC 4.8 - 8 - name: "🐍 3 • CentOS ${{ matrix.centos }} • x64" + name: "Python 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@v3 - with: - path: | - /var/cache/yum/ - /var/cache/dnf/ - key: ${{ runner.os }}-centos${{ matrix.centos }}-yum-${{ secrets.yum_cache }} - - - name: Fix repository URLs (CentOS 8 only) - if: matrix.centos == 8 - run: | - sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* - sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* - - - 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.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm - - - name: Install Git > 2.18 - run: | - yum install -y git - git config --global --add safe.directory /__w/ProjectQ/ProjectQ - - - uses: actions/checkout@v3 - - - name: Get history and tags for SCM versioning to work - if: ${{ !env.ACT }} - 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@v3 - 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 -m pip install -U pip setuptools wheel - python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket - cat requirements.txt - python3 -m pip install -r requirements.txt --prefer-binary - - - name: Build and install package - run: python3 -m pip install -ve .[azure-quantum,braket,test] - - - name: Pytest - run: | - echo 'backend: Agg' > matplotlibrc - python3 -m pytest -p no:warnings + - name: Enable cache for yum + run: echo 'keepcache=1' >> /etc/yum.conf + + - name: Setup yum cache + uses: actions/cache@v3 + with: + path: | + /var/cache/yum/ + /var/cache/dnf/ + key: ${{ runner.os }}-centos${{ matrix.centos }}-yum-${{ secrets.yum_cache }} + + - name: Fix repository URLs (CentOS 8 only) + if: matrix.centos == 8 + run: | + sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* + sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* + + - 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.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm + + - name: Install Git > 2.18 + run: | + yum install -y git + git config --global --add safe.directory /__w/ProjectQ/ProjectQ + + - uses: actions/checkout@v3 + + - name: Get history and tags for SCM versioning to work + if: ${{ !env.ACT }} + 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@v3 + 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 -m pip install -U pip setuptools wheel + python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + cat requirements.txt + python3 -m pip install -r requirements.txt --prefer-binary + + - name: Build and install package + run: python3 -m pip install -ve .[azure-quantum,braket,test] + + - name: Pytest + run: | + echo 'backend: Agg' > matplotlibrc + python3 -m pytest -p no:warnings documentation: @@ -285,35 +286,35 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - - name: Create pip cache dir - run: mkdir -p ~/.cache/pip - - - name: Cache wheels - uses: actions/cache@v3 - 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 - if: ${{ !env.ACT }} - run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - - uses: actions/setup-python@v4 - with: - python-version: '3.x' - architecture: 'x64' - - - name: Install docs & setup requirements - run: | - python3 -m pip install .[docs] - - - name: Build docs - run: python3 -m sphinx -b html docs docs/.build - - - name: Make SDist - run: python3 setup.py sdist + - uses: actions/checkout@v3 + + - name: Create pip cache dir + run: mkdir -p ~/.cache/pip + + - name: Cache wheels + uses: actions/cache@v3 + 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 + if: ${{ !env.ACT }} + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + architecture: 'x64' + + - name: Install docs & setup requirements + run: | + python3 -m pip install .[docs] + + - name: Build docs + run: python3 -m sphinx -b html docs docs/.build + + - name: Make SDist + run: python3 setup.py sdist diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index c4854cd2c..2c54ffd07 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -1,3 +1,5 @@ +--- + name: "Draft new release" on: @@ -11,56 +13,57 @@ jobs: name: "Draft a new release" runs-on: ubuntu-latest steps: - - name: Install git-flow - run: sudo apt update && sudo apt install -y git-flow + - name: Install git-flow + run: sudo apt update && sudo apt install -y git-flow - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - name: Configure git-flow - run: | - git fetch --tags --depth=1 origin master develop - git flow init --default --tag v + - 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.tag }} + - name: Create release branch + run: git flow release start ${{ github.event.inputs.tag }} - - name: Update changelog - uses: thomaseizinger/keep-a-changelog-new-release@1.3.0 - with: - version: ${{ github.event.inputs.tag }} + - name: Update changelog + uses: thomaseizinger/keep-a-changelog-new-release@1.3.0 + with: + version: ${{ github.event.inputs.tag }} - - name: Initialize mandatory git config - run: | - git config user.name "GitHub actions" - git config user.email noreply@github.com + - 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 ${{ github.event.inputs.tag }}" + - name: Commit changelog and manifest files + id: make-commit + run: | + git add CHANGELOG.md + git commit --message "Preparing release ${{ github.event.inputs.tag }}" - echo "::set-output name=commit::$(git rev-parse HEAD)" + echo "::set-output name=commit::$(git rev-parse HEAD)" - - name: Push new branch - run: git flow release publish ${{ github.event.inputs.tag }} + - name: Push new branch + run: git flow release publish ${{ github.event.inputs.tag }} - - name: Create pull request - uses: thomaseizinger/create-pull-request@1.2.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - head: release/${{ github.event.inputs.tag }} - base: master - title: Release version ${{ github.event.inputs.tag }} - 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 }}! + # yamllint disable rule:line-length + - name: Create pull request + uses: thomaseizinger/create-pull-request@1.2.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + head: release/${{ github.event.inputs.tag }} + base: master + title: Release version ${{ github.event.inputs.tag }} + 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 }}. + 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. + 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 index 1a3b4cb96..c2ce1e4bf 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -1,3 +1,5 @@ +--- + name: Format on: @@ -5,59 +7,29 @@ on: pull_request: push: branches: - - master - - stable - - "v*" + - master + - stable + - "v*" jobs: - pre-commit: - name: Format and static analysis - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Get history and tags for SCM versioning to work - if: ${{ !env.ACT }} - run: | - git fetch --prune --unshallow - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - - name: Install pre-commit - run: python3 -m pip install --upgrade pre-commit 'virtualenv!=20.11' - - - name: Cache pre-commit hooks - uses: actions/cache@v3 - with: - path: ~/.cache/pre-commit - key: pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} - restore-keys: pre-commit- - - - name: Run pre-commit - run: | - # Slow hooks are marked with manual - slow is okay here, run them too - pre-commit run --hook-stage manual --all-files - clang-tidy: name: Clang-Tidy runs-on: ubuntu-latest - container: silkeh/clang:10 + container: silkeh/clang:14 env: CC: clang CXX: clang++ steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - name: Prepare env - run: > - apt-get update && apt-get install -y python3-dev python3-pip python3-setuptools python3-wheel - --no-install-recommends + - 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: Upgrade pybind11 + run: python3 -m pip install --upgrade pybind11 --prefer-binary - - name: Run Clang-Tidy - run: python3 setup.py clang_tidy --warning-as-errors + - 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 index aa88b802a..f6a25fd07 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -1,3 +1,5 @@ +--- + name: "Publish new release" on: @@ -15,7 +17,12 @@ 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) || github.event_name == 'workflow_dispatch' + if: > + startsWith(github.ref, 'refs/tags/') + || (github.event_name == 'pull_request' + && github.event.pull_request.merged == true) + || github.event_name == 'workflow_dispatch' + strategy: matrix: cibw_archs: ["auto64"] @@ -47,7 +54,10 @@ jobs: # ======================================================================== - name: Extract version from branch name (for release branches) (Unix) - if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') && runner.os != 'Windows' + if: > + github.event_name == 'pull_request' + && startsWith(github.event.pull_request.head.ref, 'release/') + && runner.os != 'Windows' run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#release/} @@ -55,7 +65,10 @@ jobs: git tag ${VERSION} master - name: Extract version from branch name (for hotfix branches) (Unix) - if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os != 'Windows' + if: > + github.event_name == 'pull_request' + && startsWith(github.event.pull_request.head.ref, 'hotfix/') + && runner.os != 'Windows' run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#hotfix/} @@ -65,7 +78,10 @@ jobs: # ------------------------------------------------------------------------ - name: Extract version from branch name (for release branches) (Windows) - if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') && runner.os == 'Windows' + if: > + github.event_name == 'pull_request' + && startsWith(github.event.pull_request.head.ref, 'release/') + && runner.os == 'Windows' run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "release/","" @@ -73,7 +89,10 @@ jobs: git tag ${VERSION} master - name: Extract version from branch name (for hotfix branches) (Windows) - if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os == 'Windows' + if: > + github.event_name == 'pull_request' + && startsWith(github.event.pull_request.head.ref, 'hotfix/') + && runner.os == 'Windows' run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "hotfix/","" @@ -159,7 +178,7 @@ jobs: 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/') + 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/v} @@ -200,12 +219,13 @@ jobs: - name: Create release env: target: x86_64-unknown-linux-musl - parse_changelog_tag: v0.3.0 + source_url: https://github.com/taiki-e/parse-changelog/releases/download + parse_changelog_tag: v0.5.1 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 - + curl -LsSf "${source_url}/${parse_changelog_tag}/parse-changelog-${target}.tar.gz" | tar xzf - notes=$(./parse-changelog "${changelog}" "${RELEASE_VERSION}") rm -f ./parse-changelog @@ -214,7 +234,10 @@ jobs: fi mkdir -p wheels pypy_wheels - gh release create "v${RELEASE_VERSION}" ${prerelease:-} --title "ProjectQ v${RELEASE_VERSION}" --notes "${notes:-}" pypy_wheels/* wheels/* + gh release create "v${RELEASE_VERSION}" ${prerelease:-} \ + --title "ProjectQ v${RELEASE_VERSION}" \ + --notes "${notes:-}" \ + pypy_wheels/* wheels/* upload_to_pypi: @@ -222,18 +245,18 @@ jobs: runs-on: ubuntu-latest needs: release steps: - - uses: actions/setup-python@v4 - with: - python-version: '3.x' + - uses: actions/setup-python@v4 + with: + python-version: '3.x' - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v3 - - name: Publish standard package - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.pypi_password }} - packages_dir: pypy_wheels/ + - name: Publish standard package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.pypi_password }} + packages_dir: pypy_wheels/ master_to_develop_pr: name: Merge master back into develop @@ -250,6 +273,7 @@ jobs: head: master base: develop title: Merge master into develop branch + # yamllint disable rule:line-length 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 index 35eae58cc..9325a3ab6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,7 +1,9 @@ +--- + name: PR on: pull_request: - types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] jobs: # Enforces the update of a changelog file on every pull request @@ -9,10 +11,10 @@ jobs: runs-on: ubuntu-latest if: github.ref != 'refs/heads/master' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - id: changelog-enforcer - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: 'CHANGELOG.md' - skipLabels: 'Skip-Changelog' + - id: changelog-enforcer + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: 'CHANGELOG.md' + skipLabels: 'Skip-Changelog' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7b1ba27a8..729fded8d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,14 +23,16 @@ repos: - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: check-added-large-files + - id: check-ast + - id: check-builtin-literals - id: check-case-conflict - id: check-merge-conflict - id: check-symlinks - - id: check-yaml - id: check-toml + - id: check-yaml - id: debug-statements - id: end-of-file-fixer - id: mixed-line-ending @@ -38,12 +40,40 @@ repos: # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.13 + rev: v1.3.1 hooks: - id: remove-tabs + - repo: https://github.com/pycqa/pydocstyle + rev: 6.1.1 + hooks: + - id: pydocstyle + exclude: (_test.*\.py)$ + additional_dependencies: [toml] + + - repo: https://github.com/PyCQA/doc8/ + rev: v1.0.0 + hooks: + - id: doc8 + require_serial: false + additional_dependencies: [tomli] + + - repo: https://github.com/codespell-project/codespell + rev: v2.2.2 + hooks: + - id: codespell + require_serial: false + files: .*\.(py|txt|cmake|md|rst|sh|ps1|hpp|tpp|cpp|cc)$ + args: [-S, '.git,third_party', -I, .codespell.allow] + + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.28.0 + hooks: + - id: yamllint + require_serial: false + - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v3.1.0 hooks: - id: pyupgrade args: [--py37-plus, --keep-mock] @@ -55,19 +85,17 @@ repos: name: isort (python) - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.10.0 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/asottile/blacken-docs rev: v1.12.1 hooks: - id: blacken-docs args: [-S, -l, '120'] - additional_dependencies: [black==22.3.0] + additional_dependencies: [black==22.10.0] - repo: https://gitlab.com/PyCQA/flake8 rev: 3.9.2 @@ -85,13 +113,11 @@ repos: files: ^(.*_test\.py)$ - repo: https://github.com/pre-commit/mirrors-pylint - rev: 'v3.0.0a4' + rev: 'v3.0.0a5' hooks: - id: pylint 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, sympy] + additional_dependencies: [pybind11>=2.6, numpy, requests, matplotlib, networkx] - repo: https://github.com/mgedmin/check-manifest rev: '0.48' diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 9199f30a8..e43492a1b 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,3 +1,5 @@ +--- + # .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details @@ -11,8 +13,8 @@ sphinx: formats: all python: - version: 3.8 - install: + version: 3.8 + install: - method: pip path: . extra_requirements: diff --git a/.travis.yml b/.travis.yml deleted file mode 100755 index b4ab7159f..000000000 --- a/.travis.yml +++ /dev/null @@ -1,76 +0,0 @@ -# ============================================================================== - -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 - -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: - - env - - python3 -m pip install -U pip setuptools wheel - - python3 -m pip install -U pybind11 dormouse revkit flaky pytest-cov coveralls boto3 - - python3 -m pip install -U azure-quantum - - python3 -m pip install -r requirements.txt - - python3 -m pip install -ve . - -before_script: - - "echo 'backend: Agg' > matplotlibrc" - -script: - - python3 -m pytest projectq --cov projectq -p no:warnings - -after_success: - - coveralls - -# ============================================================================== diff --git a/.yamllint b/.yamllint new file mode 100644 index 000000000..9770e81ca --- /dev/null +++ b/.yamllint @@ -0,0 +1,8 @@ +--- + +extends: default + +rules: + line-length: + max: 120 + level: error diff --git a/CHANGELOG.md b/CHANGELOG.md index f3d65056a..b1fae5e28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Python context manager `with flusing(MainEngine()) as eng:` +- Python context manager `with flushing(MainEngine()) as eng:` ### Repository -- Update all GitHub workflow action versions +- Update GitHub workflow action versions: `actions/cache@v3`, `actions/checkout@v3`, `actions/setup-python@v4` +- Introduce pre-commit CI +- Update to clang-tidy 14 in GitHub workflow +- Update pre-commit hook versions +- Added new pre-commit hooks: `codespell`, `doc8`, `pydocstyle` and `yamllint` ## [v0.8.0] - 2022-10-18 @@ -170,7 +174,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added CHANGELOG.md - Added support for GitHub Actions - - Build and testing on various plaforms and compilers + - Build and testing on various platforms and compilers - Automatic draft of new release - Automatic publication of new release once ready - Automatic upload of releases artifacts to PyPi and GitHub diff --git a/README.rst b/README.rst index a2c794bb0..e8451dfae 100755 --- a/README.rst +++ b/README.rst @@ -27,7 +27,8 @@ 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, AWS Braket, Azure Quantum, or IonQ service provided devices +- run quantum programs on the IBM Quantum Experience chip, AQT devices, AWS Braket, Azure Quantum, 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 @@ -58,7 +59,8 @@ Examples print(f"Measured {int(qubit)}") # converting a qubit to int or bool gives access to the measurement result -ProjectQ features a lean syntax which is close to the mathematical notation used in quantum physics. For example, a rotation of a qubit around the x-axis is usually specified as: +ProjectQ features a lean syntax which is close to the mathematical notation used in quantum physics. For example, a +rotation of a qubit around the x-axis is usually specified as: .. image:: docs/images/braket_notation.svg :alt: Rx(theta)|qubit> @@ -70,11 +72,14 @@ The same statement in ProjectQ's syntax is: Rx(theta) | qubit -The **|**-operator separates the specification of the gate operation (left-hand side) from the quantum bits to which the operation is applied (right-hand side). +The **|**-operator separates the specification of the gate operation (left-hand side) from the quantum bits to which the +operation is applied (right-hand side). **Changing the compiler and using a resource counter as a back-end** -Instead of simulating a quantum program, one can use our resource counter (as a back-end) to determine how many operations it would take on a future quantum computer with a given architecture. Suppose the qubits are arranged on a linear chain and the architecture supports any single-qubit gate as well as the two-qubit CNOT and Swap operations: +Instead of simulating a quantum program, one can use our resource counter (as a back-end) to determine how many +operations it would take on a future quantum computer with a given architecture. Suppose the qubits are arranged on a +linear chain and the architecture supports any single-qubit gate as well as the two-qubit CNOT and Swap operations: .. code-block:: python @@ -106,7 +111,8 @@ Instead of simulating a quantum program, one can use our resource counter (as a **Running a quantum program on IBM's QE chips** -To run a program on the IBM Quantum Experience chips, all one has to do is choose the `IBMBackend` and the corresponding setup: +To run a program on the IBM Quantum Experience chips, all one has to do is choose the `IBMBackend` and the corresponding +setup: .. code-block:: python @@ -142,9 +148,8 @@ To run a program on the AQT trapped ion quantum computer, choose the `AQTBackend **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: +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 @@ -190,7 +195,8 @@ IonQ from IonQ and the state vector simulator SV1: To run a program on devices provided by the `Azure Quantum `_. -Use `AzureQuantumBackend` to run ProjectQ circuits on hardware devices and simulator devices from providers `IonQ` and `Quantinuum`. +Use `AzureQuantumBackend` to run ProjectQ circuits on hardware devices and simulator devices from providers `IonQ` and +`Quantinuum`. .. code-block:: python @@ -248,10 +254,17 @@ Currently available devices are: **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 `__. +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 +`__. -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: +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 @@ -264,7 +277,7 @@ The advanced features of the simulator are also particularly useful to investiga hamiltonian = QubitOperator("X0 X1") + 0.5 * QubitOperator("Y0 Y1") # Apply exp(-i * Hamiltonian * time) (without Trotter error) TimeEvolution(time=1, hamiltonian=hamiltonian) | wavefunction - # Measure the expection value using the simulator shortcut: + # Measure the expectation value using the simulator shortcut: eng.flush() value = eng.backend.get_expectation_value(hamiltonian, wavefunction) @@ -277,17 +290,20 @@ The advanced features of the simulator are also particularly useful to investiga Getting started --------------- -To start using ProjectQ, simply follow the installation instructions in the `tutorials `__. There, you will also find OS-specific hints, a small introduction to the ProjectQ syntax, and a few `code examples `__. More example codes and tutorials can be found in the examples folder `here `__ on GitHub. +To start using ProjectQ, simply follow the installation instructions in the `tutorials +`__. There, you will also find OS-specific hints, a small +introduction to the ProjectQ syntax, and a few `code examples +`__. More example codes and tutorials can be found in the +examples folder `here `__ on GitHub. -Also, make sure to check out the `ProjectQ -website `__ and the detailed `code documentation `__. +Also, make sure to check out the `ProjectQ website `__ and the detailed `code documentation +`__. How to contribute ----------------- -For information on how to contribute, please visit the `ProjectQ -website `__ or send an e-mail to -info@projectq.ch. +For information on how to contribute, please visit the `ProjectQ website `__ or send an e-mail +to info@projectq.ch. Please cite ----------- @@ -299,8 +315,9 @@ When using ProjectQ for research projects, please cite `Quantum 2, 49 (2018) `__ (published on `arXiv `__ on 23 Dec 2016) - 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) + "A Software Methodology for Compiling Quantum Programs" `Quantum Sci. Technol. 3 (2018) 020501 + `__ (published on `arXiv `__ on 5 + Apr 2016) Authors ------- @@ -313,7 +330,8 @@ in the group of `Prof. Dr. Matthias Troyer `__ at ETH Zurich. -ProjectQ is constantly growing and `many other people `__ have already contributed to it in the meantime. +ProjectQ is constantly growing and `many other people +`__ have already contributed to it in the meantime. License ------- diff --git a/docs/README.rst b/docs/README.rst index 96dacc34e..2521e47ff 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -6,12 +6,15 @@ Documentation :alt: Documentation Status -Our detailed code documentation can be found online at `Read the Docs `__ and gets updated automatically. Besides the latest code documentation, there are also previous and offline versions available for download. +Our detailed code documentation can be found online at `Read the Docs `__ and +gets updated automatically. Besides the latest code documentation, there are also previous and offline versions +available for download. Building the docs locally ------------------------- -Before submitting new code, please make sure that the new or changed docstrings render nicely by building the docs manually. To this end, one has to install sphinx and the Read the Docs theme: +Before submitting new code, please make sure that the new or changed docstrings render nicely by building the docs +manually. To this end, one has to install sphinx and the Read the Docs theme: .. code-block:: bash diff --git a/docs/conf.py b/docs/conf.py index a9d5efc09..382a41366 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -457,7 +457,7 @@ def linkcode_resolve(domain, info): PackageDescription( 'libs.math', desc=''' -A tiny math library which will be extended thoughout the next weeks. Right now, it only contains the math functions +A tiny math library which will be extended throughout the next weeks. Right now, it only contains the math functions necessary to run Beauregard's implementation of Shor's algorithm. ''', ), diff --git a/docs/examples.rst b/docs/examples.rst index 609b8bf90..667f4e7f0 100755 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -11,7 +11,8 @@ All of these example codes **and more** can be found on `GitHub `_ 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. + 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: @@ -159,7 +184,9 @@ As a third example, consider Shor's algorithm for factoring, which for a given ( 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 + 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 @@ -168,7 +195,8 @@ As a third example, consider Shor's algorithm for factoring, which for a given ( :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! + 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 @@ -179,4 +207,7 @@ As a third example, consider Shor's algorithm for factoring, which for a given ( :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. + 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 0c3f5ec4d..6a71e8dc9 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,7 +3,9 @@ ProjectQ ======== -ProjectQ is an open-source software framework for quantum computing. It aims at providing tools which facilitate **inventing**, **implementing**, **testing**, **debugging**, and **running** quantum algorithms using either classical hardware or actual quantum devices. +ProjectQ is an open-source software framework for quantum computing. It aims at providing tools which facilitate +**inventing**, **implementing**, **testing**, **debugging**, and **running** quantum algorithms using either classical +hardware or actual quantum devices. The **four core principles** of this open-source effort are @@ -14,8 +16,12 @@ 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 diff --git a/docs/projectq.rst b/docs/projectq.rst index 35a6f7285..ffcb10c26 100755 --- a/docs/projectq.rst +++ b/docs/projectq.rst @@ -3,7 +3,8 @@ Code Documentation ================== -Welcome to the package documentation of ProjectQ. You may now browse through the entire documentation and discover the capabilities of the ProjectQ framework. +Welcome to the package documentation of ProjectQ. You may now browse through the entire documentation and discover the +capabilities of the ProjectQ framework. For a detailed documentation of a subpackage or module, click on its name below: diff --git a/docs/tutorials.rst b/docs/tutorials.rst index bcc7e2d67..3fba8c9da 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -15,21 +15,30 @@ To start using ProjectQ, simply run python -m pip install --user projectq -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. +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 +Alternatively, you can also `clone/download `_ this repository (e.g., to +your /home directory) and run .. code-block:: bash 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. +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 ``PROJECTQ_DISABLE_CEXT`` environment variable to avoid any compilation steps. + If you want to skip the installation of the C++-Simulator altogether, you can define the ``PROJECTQ_DISABLE_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: @@ -41,14 +50,20 @@ ProjectQ comes with a high-performance quantum simulator written in C++. Please 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 `_. + 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** -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 +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 including this requirement you can include it in the +installation instruction as .. code-block:: bash @@ -56,7 +71,9 @@ AWS Braket Backend requires the use of the official AWS SDK for Python, Boto3. T **Install Azure Quantum Backend requirement** -Azure Quantum Backend requires the use of the official `Azure Quantum SDK `_ for Python. This is an extra requirement only needed if you plan to use the Azure Quantum Backend. To install ProjectQ inluding this requirement you can include it in the installation instruction as +Azure Quantum Backend requires the use of the official `Azure Quantum SDK `_ +for Python. This is an extra requirement only needed if you plan to use the Azure Quantum Backend. To install ProjectQ +including this requirement you can include it in the installation instruction as .. code-block:: bash @@ -114,16 +131,24 @@ Detailed instructions and OS-specific hints **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: + 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 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**: @@ -135,7 +160,8 @@ Detailed instructions and OS-specific hints 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: + 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 @@ -152,7 +178,9 @@ Detailed instructions and OS-specific hints 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: + 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 @@ -189,7 +217,8 @@ Detailed instructions and OS-specific hints **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 @@ -209,7 +238,9 @@ Detailed instructions and OS-specific hints 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 @@ -225,7 +256,8 @@ Detailed instructions and OS-specific hints The ProjectQ syntax ------------------- -Our goal is to have an intuitive syntax in order to enable an easy learning curve. Therefore, ProjectQ features a lean syntax which is close to the mathematical notation used in physics. +Our goal is to have an intuitive syntax in order to enable an easy learning curve. Therefore, ProjectQ features a lean +syntax which is close to the mathematical notation used in physics. For example, consider applying an x-rotation by an angle `theta` to a qubit. In ProjectQ, this looks as follows: @@ -237,12 +269,16 @@ whereas the corresponding notation in physics would be :math:`R_x(\theta) \; |\text{qubit}\rangle` -Moreover, the `|`-operator separates the classical arguments (on the left) from the quantum arguments (on the right). Next, you will see a basic quantum program using this syntax. Further examples can be found in the docs (`Examples` in the panel on the left) and in the ProjectQ examples folder on `GitHub `_. +Moreover, the `|`-operator separates the classical arguments (on the left) from the quantum arguments (on the +right). Next, you will see a basic quantum program using this syntax. Further examples can be found in the docs +(`Examples` in the panel on the left) and in the ProjectQ examples folder on `GitHub +`_. Basic quantum program --------------------- -To check out the ProjectQ syntax in action and to see whether the installation worked, try to run the following basic example +To check out the ProjectQ syntax in action and to see whether the installation worked, try to run the following basic +example .. code-block:: python diff --git a/examples/README.rst b/examples/README.rst index 14ff5190e..821663817 100644 --- a/examples/README.rst +++ b/examples/README.rst @@ -1,32 +1,48 @@ Examples and Tutorials ====================== -This folder contains a collection of **examples** and **tutorials** for how to use ProjectQ. They offer a great way to get started. While this collection is growing, it will never be possible to cover everything. Therefore, we refer the readers to also have a look at: +This folder contains a collection of **examples** and **tutorials** for how to use ProjectQ. They offer a great way to +get started. While this collection is growing, it will never be possible to cover everything. Therefore, we refer the +readers to also have a look at: -* Our complete **code documentation** which can be found online `here `__. Besides the newest version of the documentation it also provides older versions. Moreover, these docs can be downloaded for offline usage. +* Our complete **code documentation** which can be found online `here + `__. Besides the newest version of the documentation it also provides older + versions. Moreover, these docs can be downloaded for offline usage. -* Our **unit tests**. More than 99% of all lines of code are covered with various unit tests since the first release. Tests are really important to us. Therefore, if you are wondering how a specific feature can be used, have a look at the **unit tests**, where you can find plenty of examples. Finding the unit tests is very easy: E.g., the tests of the simulator implemented in *ProjectQ/projectq/backends/_sim/_simulator.py* can all be found in the same folder in the file *ProjectQ/projectq/backends/_sim/_simulator_test.py*. +* Our **unit tests**. More than 99% of all lines of code are covered with various unit tests since the first + release. Tests are really important to us. Therefore, if you are wondering how a specific feature can be used, have a + look at the **unit tests**, where you can find plenty of examples. Finding the unit tests is very easy: E.g., the + tests of the simulator implemented in *ProjectQ/projectq/backends/_sim/_simulator.py* can all be found in the same + folder in the file *ProjectQ/projectq/backends/_sim/_simulator_test.py*. Getting started / background information ---------------------------------------- -It might be a good starting point to have a look at our paper which explains the goals of the ProjectQ framework and also gives a good overview: +It might be a good starting point to have a look at our paper which explains the goals of the ProjectQ framework and +also gives a good overview: -* 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) +* 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) Our second paper looks at a few aspects of ProjectQ in more details: -* Damian S. Steiger, Thomas Häner, and Matthias Troyer "Advantages of a modular high-level quantum programming framework" `[arxiv:1806.01861] `__ +* Damian S. Steiger, Thomas Häner, and Matthias Troyer "Advantages of a modular high-level quantum programming + framework" `[arxiv:1806.01861] `__ Examples and tutorials in this folder ------------------------------------- -1. Some of the files in this folder are explained in the `documentation `__. +1. Some of the files in this folder are explained in the `documentation + `__. -2. Take a look at the *simulator_tutorial.ipynb* for a detailed introduction to most of the features of our high performance quantum simulator. +2. Take a look at the *simulator_tutorial.ipynb* for a detailed introduction to most of the features of our high + performance quantum simulator. 3. Running on the IBM QE chip is explained in more details in *ibm_entangle.ipynb*. -4. A small tutorial on the compiler is available in *compiler_tutorial.ipynb* which explains how to compile to a specific gate set. +4. A small tutorial on the compiler is available in *compiler_tutorial.ipynb* which explains how to compile to a + specific gate set. -5. A small tutorial on the mappers is available in *mapper_tutorial.ipynb* which explains how to map a quantum circuit to a linear chain or grid of physical qubits. +5. A small tutorial on the mappers is available in *mapper_tutorial.ipynb* which explains how to map a quantum circuit + to a linear chain or grid of physical qubits. diff --git a/projectq/__init__.py b/projectq/__init__.py index 2da4bc1f8..430859a3a 100755 --- a/projectq/__init__.py +++ b/projectq/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -ProjectQ - An open source software framework for quantum computing +ProjectQ - An open source software framework for quantum computing. Get started: Simply import the main compiler engine (from projectq import MainEngine) diff --git a/projectq/backends/_aqt/__init__.py b/projectq/backends/_aqt/__init__.py index d971c1b35..37d42fcd9 100644 --- a/projectq/backends/_aqt/__init__.py +++ b/projectq/backends/_aqt/__init__.py @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module for supporting the AQT platform""" +"""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 cb1993099..9d5e5e2fe 100644 --- a/projectq/backends/_aqt/_aqt.py +++ b/projectq/backends/_aqt/_aqt.py @@ -233,7 +233,7 @@ def _run(self): # NOTE AQT DOESN'T SEEM TO HAVE MEASUREMENT INSTRUCTIONS (no # intermediate measurements are allowed, so implicit at the end) # return if no operations. - if self._circuit == []: + if not self._circuit: return n_qubit = max(self._allocated_qubits) + 1 diff --git a/projectq/backends/_aqt/_aqt_http_client.py b/projectq/backends/_aqt/_aqt_http_client.py index ac1a5094c..a545ab2d0 100644 --- a/projectq/backends/_aqt/_aqt_http_client.py +++ b/projectq/backends/_aqt/_aqt_http_client.py @@ -206,7 +206,7 @@ def send( verbose=False, ): # pylint: disable=too-many-arguments """ - Send cicruit through the AQT API and runs the quantum circuit. + Send circuit through the AQT API and runs the quantum circuit. Args: info(dict): Contains representation of the circuit to run. @@ -233,7 +233,7 @@ def send( 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 diff --git a/projectq/backends/_awsbraket/__init__.py b/projectq/backends/_awsbraket/__init__.py index 7e2ed0f91..985fc1b10 100644 --- a/projectq/backends/_awsbraket/__init__.py +++ b/projectq/backends/_awsbraket/__init__.py @@ -12,16 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module for supporting the AWS Braket platform""" +"""ProjectQ module for supporting the AWS Braket platform.""" try: from ._awsbraket import AWSBraketBackend except ImportError: # pragma: no cover class AWSBraketBackend: # pylint: disable=too-few-public-methods - """Dummy class""" + """Dummy class.""" def __init__(self, *args, **kwargs): + """Initialize dummy class.""" raise RuntimeError( "Failed to import one of the dependencies required to use " "the Amazon Braket Backend.\n" diff --git a/projectq/backends/_awsbraket/_awsbraket.py b/projectq/backends/_awsbraket/_awsbraket.py index 56f1a4be6..78fe5bd01 100755 --- a/projectq/backends/_awsbraket/_awsbraket.py +++ b/projectq/backends/_awsbraket/_awsbraket.py @@ -137,7 +137,7 @@ def is_available(self, cmd): # pylint: disable=too-many-return-statements,too-m Depending on the device chosen, the operations available differ. - The operations avialable for the Aspen-8 Rigetti device are: + The operations available 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, @@ -349,7 +349,7 @@ def get_probabilities(self, qureg): 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 + executed circuit will store the results in the S3 bucket and it can be retrieved 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. diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py index bc7d531a4..7924a40b8 100755 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py @@ -73,7 +73,7 @@ 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 + verbose (bool): print the returned dictionary if True Returns: (dict) backends dictionary by deviceName, containing the qubit size 'nq', the coupling map 'coupling_map' @@ -112,8 +112,7 @@ def get_list_devices(self, verbose=False): 'GateModelParameters' ]['properties']['braketSchemaHeader']['const'], } - # Unfortunatelly the Capabilities schemas are not homogeneus - # for real devices and simulators + # Unfortunately the Capabilities schemas are not homogeneus for real devices and simulators elif result['deviceType'] == 'SIMULATOR': device_capabilities = json.loads( client.get_device(deviceArn=result['deviceArn'])['deviceCapabilities'] @@ -301,7 +300,7 @@ def _calculate_measurement_probs(measurements): # 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 + # In addition the results and state is written in the # results.json file in the S3 Bucket and does not depend on the # status of the device @@ -339,7 +338,8 @@ def retrieve(credentials, task_arn, num_retries=30, interval=1, verbose=False): Args: credentials (dict): Dictionary storing the AWS credentials with keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. - task_arn (str): The Arn of the task to retreive + task_arn (str): The Arn of the task to retrieve + Returns: (dict) measurement probabilities from the result stored in the S3 folder @@ -365,7 +365,7 @@ def send( # pylint: disable=too-many-branches,too-many-arguments,too-many-local info, device, credentials, s3_folder, num_retries=30, interval=1, verbose=False ): """ - Send cicruit through the Boto3 SDK and runs the quantum circuit. + Send circuit through the Boto3 SDK and runs the quantum circuit. Args: info(dict): Contains representation of the circuit to run. @@ -391,7 +391,7 @@ def send( # pylint: disable=too-many-branches,too-many-arguments,too-many-local 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, please 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.") @@ -423,7 +423,7 @@ def send( # pylint: disable=too-many-branches,too-many-arguments,too-many-local 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") + print("- There was an internal Bracket service error") if error_code == 'ServiceQuotaExceededException': print("- There was an error: the quota on Braket was exceed") if error_code == 'ValidationException': diff --git a/projectq/backends/_azure/__init__.py b/projectq/backends/_azure/__init__.py index 8c06e2392..23d2cb3c8 100644 --- a/projectq/backends/_azure/__init__.py +++ b/projectq/backends/_azure/__init__.py @@ -19,9 +19,10 @@ except ImportError: # pragma: no cover class AzureQuantumBackend: - """Dummy class""" + """Dummy class.""" def __init__(self, *args, **kwargs): + """Initialize dummy class.""" raise ImportError( "Missing optional 'azure-quantum' dependencies. To install run: pip install projectq[azure-quantum]" ) diff --git a/projectq/backends/_azure/_azure_quantum.py b/projectq/backends/_azure/_azure_quantum.py index e8187c87c..682caa69d 100644 --- a/projectq/backends/_azure/_azure_quantum.py +++ b/projectq/backends/_azure/_azure_quantum.py @@ -292,7 +292,7 @@ def _input_data(self): for measured_id in self._measured_ids: qb_loc = self.main_engine.mapper.current_mapping[measured_id] - measurement_gates += "measure q[{0}] -> c[{0}];\n".format(qb_loc) + measurement_gates += f"measure q[{qb_loc}] -> c[{qb_loc}];\n" return ( f"OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[{qubits}];\ncreg c[{qubits}];" diff --git a/projectq/backends/_circuits/__init__.py b/projectq/backends/_circuits/__init__.py index eaf0abb56..62dd8861a 100755 --- a/projectq/backends/_circuits/__init__.py +++ b/projectq/backends/_circuits/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module for exporting/printing quantum circuits""" +"""ProjectQ module for exporting/printing quantum circuits.""" from ._drawer import CircuitDrawer from ._drawer_matplotlib import CircuitDrawerMatplotlib diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index 45499526a..c2f870b7d 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -447,6 +447,7 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): # pylint: disable=too-ma # add a circled 1/2 midpoint = (lines[0] + lines[1]) / 2.0 pos = self.pos[lines[0]] + # pylint: disable=consider-using-f-string op_mid = f"line{'{}-{}'.format(*lines)}_gate{self.op_count[lines[0]]}" dagger = '^{{\\dagger}}' if daggered else '' gate_str += f"\n\\node[xstyle] ({op}) at ({pos},-{midpoint}){{\\scriptsize $\\frac{{1}}{{2}}{dagger}$}};" diff --git a/projectq/backends/_ibm/__init__.py b/projectq/backends/_ibm/__init__.py index db7bbddb4..254142fbc 100755 --- a/projectq/backends/_ibm/__init__.py +++ b/projectq/backends/_ibm/__init__.py @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module for supporting the IBM QE platform""" +"""ProjectQ module for supporting the IBM QE platform.""" from ._ibm import IBMBackend diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 400e235ee..815a04626 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -51,7 +51,7 @@ def get_list_devices(self, verbose=False): Get the list of available IBM backends with their properties. Args: - verbose (bool): print the returned dictionnary if True + verbose (bool): print the returned dictionary if True Returns: (dict) backends dictionary by name device, containing the qubit size 'nq', the coupling map 'coupling_map' diff --git a/projectq/backends/_ionq/__init__.py b/projectq/backends/_ionq/__init__.py index 9f1b6b9ea..8d132eaf8 100644 --- a/projectq/backends/_ionq/__init__.py +++ b/projectq/backends/_ionq/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module for supporting the IonQ platform""" +"""ProjectQ module for supporting the IonQ platform.""" from ._ionq import IonQBackend diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py index 392864461..cd70b75a0 100644 --- a/projectq/backends/_ionq/_ionq.py +++ b/projectq/backends/_ionq/_ionq.py @@ -96,7 +96,7 @@ def __init__( ``'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. + interval (int, optional): Number of seconds to wait in between 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. """ @@ -138,7 +138,7 @@ def is_available(self, cmd): if 0 < num_ctrl_qubits <= 7: return isinstance(gate, (XGate,)) - # Gates witout control bits. + # Gates without control bits. if num_ctrl_qubits == 0: supported = isinstance(gate, SUPPORTED_GATES) supported_transpose = gate in (Sdag, Tdag) diff --git a/projectq/backends/_ionq/_ionq_http_client.py b/projectq/backends/_ionq/_ionq_http_client.py index b7f90cd82..72e16555f 100644 --- a/projectq/backends/_ionq/_ionq_http_client.py +++ b/projectq/backends/_ionq/_ionq_http_client.py @@ -339,7 +339,7 @@ def send( # 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 diff --git a/projectq/backends/_resource.py b/projectq/backends/_resource.py index ec741746c..fa73d74e6 100755 --- a/projectq/backends/_resource.py +++ b/projectq/backends/_resource.py @@ -14,7 +14,7 @@ """ Contain a compiler engine to calculate resource count used by a quantum circuit. -A resrouce counter compiler engine counts the number of calls for each type of gate used in a circuit, in addition to +A resource counter compiler engine counts the number of calls for each type of gate used in a circuit, in addition to the max. number of active qubits. """ diff --git a/projectq/backends/_sim/__init__.py b/projectq/backends/_sim/__init__.py index 3cdb3731b..8898be48f 100755 --- a/projectq/backends/_sim/__init__.py +++ b/projectq/backends/_sim/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module dedicated to simulation""" +"""ProjectQ module dedicated to simulation.""" from ._classical_simulator import ClassicalSimulator from ._simulator import Simulator diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index 9752ea669..f3365fbcc 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -74,7 +74,7 @@ def __init__(self, gate_fusion=False, rnd_seed=None): implementation of the kernels. While this is much slower, it is still good enough to run basic quantum algorithms. - If you need to run large simulations, check out the tutorial in the docs which gives futher hints on how + If you need to run large simulations, check out the tutorial in the docs which gives further hints on how to build the C++ extension. """ if rnd_seed is None: diff --git a/projectq/cengines/_linearmapper.py b/projectq/cengines/_linearmapper.py index ca4b45320..c544fa9b6 100644 --- a/projectq/cengines/_linearmapper.py +++ b/projectq/cengines/_linearmapper.py @@ -143,7 +143,7 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_comma allocated_qubits = deepcopy(currently_allocated_ids) active_qubits = deepcopy(currently_allocated_ids) # Segments contains a list of segments. A segment is a list of - # neighouring qubit ids + # neighbouring qubit ids segments = [] # neighbour_ids only used to speedup the lookup process if qubits # are already connected. key: qubit_id, value: set of neighbour ids @@ -207,7 +207,7 @@ def _process_two_qubit_gate( # pylint: disable=too-many-arguments,too-many-bran """ Process a two qubit gate. - It either removes the two qubits from active_qubits if the gate is not possible or updates the segements such + It either removes the two qubits from active_qubits if the gate is not possible or updates the segments such that the gate is possible. Args: @@ -232,7 +232,7 @@ def _process_two_qubit_gate( # pylint: disable=too-many-arguments,too-many-bran active_qubits.discard(qubit0) active_qubits.discard(qubit1) # qubits are both active and either not yet in a segment or at - # the end of segement: + # the end of segment: else: segment_index_qb0 = None qb0_is_left_end = None diff --git a/projectq/cengines/_main.py b/projectq/cengines/_main.py index dfbf5b7e4..87ec1b3d3 100755 --- a/projectq/cengines/_main.py +++ b/projectq/cengines/_main.py @@ -321,6 +321,6 @@ def flush(self, deallocate_qubits=False): """ if deallocate_qubits: while [qb for qb in self.active_qubits if qb is not None]: - qb = self.active_qubits.pop() - qb.__del__() + qb = self.active_qubits.pop() # noqa: F841 + qb.__del__() # pylint: disable=unnecessary-dunder-call self.receive([Command(self, FlushGate(), ([WeakQubitRef(self, -1)],))]) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 8d6b83678..1cce7317a 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -248,7 +248,7 @@ def receive(self, command_list): if len(_l) > 0: # pragma: no cover new_dict[idx] = _l self._l = new_dict - if self._l != {}: # pragma: no cover + if self._l: # pragma: no cover raise RuntimeError('Internal compiler error: qubits remaining in LocalOptimizer after a flush!') self.send([cmd]) else: diff --git a/projectq/cengines/_swapandcnotflipper.py b/projectq/cengines/_swapandcnotflipper.py index d25ac7222..997274a0a 100755 --- a/projectq/cengines/_swapandcnotflipper.py +++ b/projectq/cengines/_swapandcnotflipper.py @@ -56,7 +56,7 @@ def is_available(self, cmd): """ return self._is_swap(cmd) or self.next_engine.is_available(cmd) - def _is_cnot(self, cmd): # pylint: disable=no-self-use + def _is_cnot(self, cmd): """ Check if the command corresponds to a CNOT (controlled NOT gate). @@ -65,7 +65,7 @@ def _is_cnot(self, cmd): # pylint: disable=no-self-use """ return isinstance(cmd.gate, NOT.__class__) and get_control_count(cmd) == 1 - def _is_swap(self, cmd): # pylint: disable=no-self-use + def _is_swap(self, cmd): """ Check if the command corresponds to a Swap gate. diff --git a/projectq/cengines/_testengine.py b/projectq/cengines/_testengine.py index 2933bde82..3e9b0379b 100755 --- a/projectq/cengines/_testengine.py +++ b/projectq/cengines/_testengine.py @@ -32,7 +32,7 @@ class CompareEngine(BasicEngine): Command list comparison compiler engine for testing purposes. 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. + backends can be compared and return True if they contain the same commands. """ def __init__(self): diff --git a/projectq/libs/__init__.py b/projectq/libs/__init__.py index a60e7ce84..ff6f1e495 100755 --- a/projectq/libs/__init__.py +++ b/projectq/libs/__init__.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module containing libraries expanding the basic functionalities of ProjectQ""" +"""ProjectQ module containing libraries expanding the basic functionalities of ProjectQ.""" diff --git a/projectq/libs/hist/__init__.py b/projectq/libs/hist/__init__.py index 07911534a..d55514dda 100644 --- a/projectq/libs/hist/__init__.py +++ b/projectq/libs/hist/__init__.py @@ -12,8 +12,9 @@ # 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 +Measurement histogram plot helper functions. + +Contains a function to plot measurement outcome probabilities as a histogram for the simulator """ from ._histogram import histogram diff --git a/projectq/libs/math/__init__.py b/projectq/libs/math/__init__.py index c2433cf34..87e1eee3e 100755 --- a/projectq/libs/math/__init__.py +++ b/projectq/libs/math/__init__.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Math gate definitions.""" + from ._default_rules import all_defined_decomposition_rules from ._gates import ( AddConstant, diff --git a/projectq/libs/math/_gates.py b/projectq/libs/math/_gates.py index ed6d9d26c..ad466d2d7 100755 --- a/projectq/libs/math/_gates.py +++ b/projectq/libs/math/_gates.py @@ -346,7 +346,7 @@ def get_inverse(self): class ComparatorQuantumGate(BasicMathGate): """ - Flip a compare qubit if the binary value of first imput is higher than the second input. + Flip a compare qubit if the binary value of first input is higher than the second input. The numbers are stored from low- to high-bit, i.e., qunum[0] is the LSB. Example: @@ -363,7 +363,7 @@ class ComparatorQuantumGate(BasicMathGate): def __init__(self): """ - Initilize a ComparatorQuantumGate object. + Initialize a ComparatorQuantumGate object. Initialize the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. diff --git a/projectq/libs/math/_quantummath.py b/projectq/libs/math/_quantummath.py index fb3dcbdc6..59aa7cf9f 100644 --- a/projectq/libs/math/_quantummath.py +++ b/projectq/libs/math/_quantummath.py @@ -315,7 +315,7 @@ def quantum_division(eng, dividend, remainder, divisor): Quantum Restoring Integer Division from: https://arxiv.org/pdf/1609.01241.pdf. """ - # The circuit consits of three parts + # The circuit consists of three parts # i) leftshift # ii) subtraction # iii) conditional add operation. diff --git a/projectq/libs/revkit/__init__.py b/projectq/libs/revkit/__init__.py index e527091be..f8d80db0e 100644 --- a/projectq/libs/revkit/__init__.py +++ b/projectq/libs/revkit/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing code to interface with RevKit""" +"""Module containing code to interface with RevKit.""" from ._control_function import ControlFunctionOracle from ._permutation import PermutationOracle diff --git a/projectq/libs/revkit/_control_function.py b/projectq/libs/revkit/_control_function.py index c20db35f5..40a07fe9f 100644 --- a/projectq/libs/revkit/_control_function.py +++ b/projectq/libs/revkit/_control_function.py @@ -119,4 +119,4 @@ def _check_function(self): # function must be positive. We check in __or__ whether function is # too large if self.function < 0: - raise AttributeError("Function must be a postive integer") + raise AttributeError("Function must be a positive integer") diff --git a/projectq/libs/revkit/_phase.py b/projectq/libs/revkit/_phase.py index 399697449..8d0eb2931 100644 --- a/projectq/libs/revkit/_phase.py +++ b/projectq/libs/revkit/_phase.py @@ -115,4 +115,4 @@ def _check_function(self): # function must be positive. We check in __or__ whether function is # too large if self.function < 0: - raise AttributeError("Function must be a postive integer") + raise AttributeError("Function must be a positive integer") diff --git a/projectq/libs/revkit/_utils.py b/projectq/libs/revkit/_utils.py index e1f47c025..1dfb2287b 100644 --- a/projectq/libs/revkit/_utils.py +++ b/projectq/libs/revkit/_utils.py @@ -13,7 +13,7 @@ # limitations under the License. -"""Module containing some utility functions""" +"""Module containing some utility functions.""" # flake8: noqa # pylint: skip-file @@ -21,7 +21,7 @@ def _exec(code, qs): """ - Executes the Python code in 'filename'. + Execute the Python code in 'filename'. Args: code (string): ProjectQ code. diff --git a/projectq/meta/__init__.py b/projectq/meta/__init__.py index 764081800..382148d7f 100755 --- a/projectq/meta/__init__.py +++ b/projectq/meta/__init__.py @@ -12,8 +12,9 @@ # 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., +Provides 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 295c4b7a4..84cde8a7a 100755 --- a/projectq/meta/_compute.py +++ b/projectq/meta/_compute.py @@ -102,7 +102,7 @@ def run_uncompute(self): # pylint: disable=too-many-branches,too-many-statement for active_qubit in self.main_engine.active_qubits: if active_qubit.id == qubit_id: active_qubit.id = -1 - active_qubit.__del__() + del active_qubit qubit_found = True break if not qubit_found: @@ -158,7 +158,7 @@ def add_uncompute(command, old_tags=deepcopy(cmd.tags)): for active_qubit in self.main_engine.active_qubits: if active_qubit.id == qubit_id: active_qubit.id = -1 - active_qubit.__del__() + del active_qubit qubit_found = True break if not qubit_found: diff --git a/projectq/meta/_compute_test.py b/projectq/meta/_compute_test.py index b32cd1f2f..e5caa360b 100755 --- a/projectq/meta/_compute_test.py +++ b/projectq/meta/_compute_test.py @@ -307,7 +307,7 @@ def allow_dirty_qubits(self, meta_tag): assert backend.received_commands[18].qubits[0][0].id == ancilla_uncompt_id assert backend.received_commands[19].qubits[0][0].id == qubit_id assert backend.received_commands[20].qubits[0][0].id == qubit_id - # Test that ancilla qubits should have seperate ids + # Test that ancilla qubits should have separate ids assert ancilla_uncompt_id != ancilla_compt_id # Do the same thing with CustomUncompute and compare using the diff --git a/projectq/meta/_control.py b/projectq/meta/_control.py index 543ebfe80..549e0e9e5 100755 --- a/projectq/meta/_control.py +++ b/projectq/meta/_control.py @@ -45,7 +45,7 @@ def canonical_ctrl_state(ctrl_state, num_qubits): 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: + This means in particular that the following are equivalent: .. code-block:: python diff --git a/projectq/meta/_dagger.py b/projectq/meta/_dagger.py index 8987b1b2a..f2a7dc243 100755 --- a/projectq/meta/_dagger.py +++ b/projectq/meta/_dagger.py @@ -43,7 +43,7 @@ def run(self): 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 " + + "Dagger(eng)' context,\n which have not explicitly " + "been deallocated.\n" + "Correct usage:\n" + "with Dagger(eng):\n" diff --git a/projectq/meta/_loop.py b/projectq/meta/_loop.py index 47681b764..5fd94157f 100755 --- a/projectq/meta/_loop.py +++ b/projectq/meta/_loop.py @@ -88,7 +88,7 @@ def run(self): 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" + "explicitly been deallocated in the Loop context.\n" "Correct usage:\nwith Loop(eng, 5):\n" " qubit = eng.allocate_qubit()\n" " ...\n" diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 13d23ebaf..2a931b617 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module containing all basic gates (operations)""" +"""ProjectQ module containing all basic gates (operations).""" from ._basics import ( BasicGate, diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index c07563fca..854696586 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -27,7 +27,7 @@ Gate | qubit Gate | (qubit,) -This means that for more than one quantum argument (right side of | ), a tuple needs to be made explicitely, while for +This means that for more than one quantum argument (right side of | ), a tuple needs to be made explicitly, while for one argument it is optional. """ @@ -101,7 +101,7 @@ def __init__(self): """ self.interchangeable_qubit_indices = [] - def get_inverse(self): # pylint: disable=no-self-use + def get_inverse(self): """ Return the inverse gate. @@ -112,7 +112,7 @@ def get_inverse(self): # pylint: disable=no-self-use """ raise NotInvertible("BasicGate: No get_inverse() implemented.") - def get_merged(self, other): # pylint: disable=no-self-use + def get_merged(self, other): """ Return this gate merged with another gate. @@ -158,7 +158,7 @@ def make_tuple_of_qureg(qubits): qubits = list(qubits) for i, qubit in enumerate(qubits): - if isinstance(qubits[i], BasicQubit): + if isinstance(qubit, BasicQubit): qubits[i] = [qubit] return tuple(qubits) @@ -230,7 +230,7 @@ def __hash__(self): """Compute the hash of the object.""" return hash(str(self)) - def is_identity(self): # pylint: disable=no-self-use + def is_identity(self): """Return True if the gate is an identity gate. In this base class, always returns False.""" return False @@ -274,7 +274,7 @@ def __eq__(self, other): """ Equal operator. - Return True only if both gates have a matrix respresentation and the matrices are (approximately) + Return True only if both gates have a matrix representation and the matrices are (approximately) equal. Otherwise return False. """ if not hasattr(other, 'matrix'): @@ -415,7 +415,7 @@ def __hash__(self): def is_identity(self): """Return True if the gate is equivalent to an Identity gate.""" - return self.angle == 0.0 or self.angle == 4 * math.pi + return self.angle in (0.0, 4 * math.pi) class BasicPhaseGate(BasicGate): diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index 9fcbf544e..0a315d64d 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -182,7 +182,7 @@ def test_command_interchangeable_qubit_indices(main_engine): [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): +def test_command_add_control_qubits_one(main_engine, state): qubit0 = Qureg([Qubit(main_engine, 0)]) qubit1 = Qureg([Qubit(main_engine, 1)]) cmd = _command.Command(main_engine, Rx(0.5), (qubit0,)) @@ -210,7 +210,7 @@ def test_commmand_add_control_qubits_one(main_engine, state): 'CtrlAll.Zero', ], ) -def test_commmand_add_control_qubits_two(main_engine, state): +def test_command_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)]) diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 88024b7cf..10527a868 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -173,7 +173,7 @@ 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): # pylint: disable=no-self-use + def tex_str(self): """Return the Latex string representation of a SqrtXGate.""" return r'$\sqrt{X}$' diff --git a/projectq/ops/_qaagate.py b/projectq/ops/_qaagate.py index 5e2d889c4..c5561eaa4 100755 --- a/projectq/ops/_qaagate.py +++ b/projectq/ops/_qaagate.py @@ -19,14 +19,14 @@ class QAA(BasicGate): """ - Quantum Aplitude Amplification gate. + Quantum Amplitude 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) 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. + marked state(s) with high probability. The starting state on which the QAA algorithm is executed is the one + resulting of applying the Algorithm on the |0> state. Example: .. code-block:: python diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index f15a4ed40..80c901dd3 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -53,7 +53,7 @@ class QubitOperator(BasicGate): A term is an operator acting on n qubits and can be represented as: - coefficent * local_operator[0] x ... x local_operator[n-1] + coefficient * 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 @@ -117,7 +117,7 @@ def __init__(self, term=None, coefficient=1.0): # pylint: disable=too-many-bran Args: 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): + term (optional, empty 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). diff --git a/projectq/ops/_time_evolution.py b/projectq/ops/_time_evolution.py index 957a1d21d..c28de10c2 100644 --- a/projectq/ops/_time_evolution.py +++ b/projectq/ops/_time_evolution.py @@ -33,7 +33,7 @@ class TimeEvolution(BasicGate): This gate is the unitary time evolution propagator: exp(-i * H * t), where H is the Hamiltonian of the system and t is the time. Note that -i - factor is stored implicitely. + factor is stored implicitly. Example: .. code-block:: python diff --git a/projectq/setups/__init__.py b/projectq/setups/__init__.py index dc799ab87..32d24fd1e 100755 --- a/projectq/setups/__init__.py +++ b/projectq/setups/__init__.py @@ -12,4 +12,4 @@ # 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""" +"""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 index 434eb5424..20c531abe 100644 --- a/projectq/setups/_utils.py +++ b/projectq/setups/_utils.py @@ -69,7 +69,7 @@ def get_engine_list_linear_grid_base(mapper, one_qubit_gates="any", two_qubit_ga 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 + decomposition rules. This implementation 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: diff --git a/projectq/setups/aqt.py b/projectq/setups/aqt.py index 5b668aaf5..96465842b 100644 --- a/projectq/setups/aqt.py +++ b/projectq/setups/aqt.py @@ -33,7 +33,7 @@ def get_engine_list(token=None, device=None): - """Return the default list of compiler engine for the AQT plaftorm.""" + """Return the default list of compiler engine for the AQT platform.""" # Access to the hardware properties via show_devices # Can also be extended to take into account gate fidelities, new available # gate, etc.. diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index ec5fcb277..f377093ab 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ's decomposition rules.""" + from . import ( amplitudeamplification, arb1qubit2rzandry, diff --git a/projectq/setups/decompositions/amplitudeamplification.py b/projectq/setups/decompositions/amplitudeamplification.py index bd38f8086..5a8528367 100644 --- a/projectq/setups/decompositions/amplitudeamplification.py +++ b/projectq/setups/decompositions/amplitudeamplification.py @@ -21,8 +21,8 @@ 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. +probability. The starting state on which the QAA algorithm is executed +is the one resulting of applying the algorithm on the |0> state. Example: .. code-block:: python @@ -92,7 +92,7 @@ def _decompose_QAA(cmd): # pylint: disable=invalid-name oracle(eng, system_qubits, qaa_ancilla) # Apply the inversion of the Algorithm, - # the inversion of the aplitude of |0> and the Algorithm + # the inversion of the amplitude of |0> and the Algorithm with Compute(eng): with Dagger(eng): diff --git a/projectq/setups/decompositions/amplitudeamplification_test.py b/projectq/setups/decompositions/amplitudeamplification_test.py index 34b490de9..8e68aad1b 100644 --- a/projectq/setups/decompositions/amplitudeamplification_test.py +++ b/projectq/setups/decompositions/amplitudeamplification_test.py @@ -75,8 +75,8 @@ def test_simple_grover(): with Loop(eng, num_it): QAA(hache_algorithm, simple_oracle) | (system_qubits, control) - # Get the probabilty of getting the marked state after the AA - # to compare with the theoretical probability after teh AA + # Get the probability of getting the marked state after the AA + # to compare with the theoretical probability after the AA eng.flush() prob1010101 = eng.backend.get_probability('1010101', system_qubits) total_prob_after = prob1010101 @@ -135,7 +135,7 @@ def test_complex_aa(): # Creates the initial state form the Algorithm complex_algorithm(eng, system_qubits) - # Get the probabilty of getting the marked state before the AA + # Get the probability of getting the marked state before the AA # to calculate the number of iterations eng.flush() prob000000 = eng.backend.get_probability('000000', system_qubits) @@ -153,7 +153,7 @@ def test_complex_aa(): with Loop(eng, num_it): QAA(complex_algorithm, complex_oracle) | (system_qubits, control) - # Get the probabilty of getting the marked state after the AA + # Get the probability of getting the marked state after the AA # to compare with the theoretical probability after the AA eng.flush() prob000000 = eng.backend.get_probability('000000', system_qubits) diff --git a/projectq/setups/decompositions/arb1qubit2rzandry.py b/projectq/setups/decompositions/arb1qubit2rzandry.py index 33da891ea..fc9d1a4c0 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry.py @@ -208,7 +208,7 @@ def _decompose_arb1qubit(cmd): """ Use Z-Y decomposition of Nielsen and Chuang (Theorem 4.1). - An arbitrary one qubit gate matrix can be writen as + An arbitrary one qubit gate matrix can be written as U = [[exp(j*(a-b/2-d/2))*cos(c/2), -exp(j*(a-b/2+d/2))*sin(c/2)], [exp(j*(a+b/2-d/2))*sin(c/2), exp(j*(a+b/2+d/2))*cos(c/2)]] where a,b,c,d are real numbers. diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py index 48490a814..549db3af1 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Register the decomposition of an controlled arbitary single qubit gate. +Register the decomposition of an controlled arbitrary single qubit gate. See paper "Elementary gates for quantum computing" by Adriano Barenco et al., arXiv:quant-ph/9503016v1. (Note: They use different gate definitions!) or @@ -156,7 +156,7 @@ def _decompose_carb1qubit(cmd): # pylint: disable=too-many-branches See Nielsen and Chuang chapter 4.3. - An arbitrary one qubit gate matrix can be writen as + An arbitrary one qubit gate matrix can be written as U = [[exp(j*(a-b/2-d/2))*cos(c/2), -exp(j*(a-b/2+d/2))*sin(c/2)], [exp(j*(a+b/2-d/2))*sin(c/2), exp(j*(a+b/2+d/2))*cos(c/2)]] where a,b,c,d are real numbers. @@ -170,7 +170,7 @@ def _decompose_carb1qubit(cmd): # pylint: disable=too-many-branches the controlled phase C(exp(ia)) can be implemented with single qubit gates. - If the one qubit gate matrix can be writen as + If the one qubit gate matrix can be written as V = [[-sin(c/2) * exp(j*a), exp(j*(a-b)) * cos(c/2)], [exp(j*(a+b)) * cos(c/2), exp(j*a) * sin(c/2)]] Then C(V) = C(exp(ja))* E * CNOT * D with diff --git a/projectq/setups/decompositions/controlstate.py b/projectq/setups/decompositions/controlstate.py index 7aa0b5924..b8d180ecc 100755 --- a/projectq/setups/decompositions/controlstate.py +++ b/projectq/setups/decompositions/controlstate.py @@ -15,7 +15,7 @@ """ Register a decomposition to replace turn negatively controlled qubits into positively controlled qubits. -This achived by applying X gates to selected qubits. +This achieved by applying X gates to selected qubits. """ from copy import deepcopy diff --git a/projectq/setups/decompositions/phaseestimation.py b/projectq/setups/decompositions/phaseestimation.py index 50d02253f..0913064e5 100644 --- a/projectq/setups/decompositions/phaseestimation.py +++ b/projectq/setups/decompositions/phaseestimation.py @@ -21,7 +21,7 @@ phase should be executed outside of the QPE. The decomposition uses as ancillas (qpe_ancillas) the first qubit/qureg in -the Command and as system qubits teh second qubit/qureg in the Command. +the Command and as system qubits the second qubit/qureg in the Command. The unitary operator for which the phase estimation is estimated (unitary) is the gate in Command diff --git a/projectq/setups/grid.py b/projectq/setups/grid.py index 65021b930..6d58e7924 100644 --- a/projectq/setups/grid.py +++ b/projectq/setups/grid.py @@ -35,7 +35,7 @@ def get_engine_list(num_rows, num_columns, one_qubit_gates="any", two_qubit_gate 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 + decomposition rules. This implementation 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: diff --git a/projectq/setups/linear.py b/projectq/setups/linear.py index 299a64f80..5ee1e0193 100644 --- a/projectq/setups/linear.py +++ b/projectq/setups/linear.py @@ -34,7 +34,7 @@ def get_engine_list(num_qubits, cyclic=False, one_qubit_gates="any", two_qubit_g 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 + decomposition rules. This implementation 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: diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index 9e8049d4e..202bb990d 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -15,7 +15,7 @@ Defines a setup to compile to a restricted gate set. It provides the `engine_list` for the `MainEngine`. This engine list contains an AutoReplacer with most of the gate -decompositions of ProjectQ, which are used to decompose a circuit into a restricted gate set (with some limitions on +decompositions of ProjectQ, which are used to decompose a circuit into a restricted gate set (with some limitations on the choice of gates). """ @@ -54,7 +54,7 @@ def get_engine_list( # pylint: disable=too-many-branches,too-many-statements 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 + decomposition rules. This implementation 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: diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index 3841b638a..320c8a6ad 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -34,8 +34,7 @@ from projectq.setups import restrictedgateset # ------------------chooser_Ry_reducer-------------------# -# If the qubit is not in the prev_Ry_sign dictionary, then no decomposition -# occured +# If the qubit is not in the prev_Ry_sign dictionary, then no decomposition occurred # If the value is: # -1 then the last gate applied (during a decomposition!) was Ry(-math.pi/2) # 1 then the last gate applied (during a decomposition!) was Ry(+math.pi/2) @@ -117,8 +116,7 @@ def chooser_Ry_reducer(cmd, decomposition_list): # pylint: disable=invalid-name local_prev_Ry_sign[qubit_id] = 1 return decomp_rule['P'] - # No decomposition chosen, so use the first decompostion in the list - # like the default function + # No decomposition chosen, so use the first decomposition in the list like the default function return decomposition_list[0] diff --git a/projectq/tests/__init__.py b/projectq/tests/__init__.py index ee1451dcd..7d98bf34e 100755 --- a/projectq/tests/__init__.py +++ b/projectq/tests/__init__.py @@ -11,3 +11,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 testing module.""" diff --git a/projectq/types/__init__.py b/projectq/types/__init__.py index ad912d2ba..dd1bc28b5 100755 --- a/projectq/types/__init__.py +++ b/projectq/types/__init__.py @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module containing all basic types""" +"""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 41021fe1c..39631410b 100755 --- a/projectq/types/_qubit.py +++ b/projectq/types/_qubit.py @@ -16,7 +16,7 @@ A Qureg represents a list of Qubit or WeakQubit objects. 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. +automatically deallocated if they go out of scope and intended to be used within Qureg objects in user code. Example: .. code-block:: python diff --git a/pyproject.toml b/pyproject.toml index 5e0559d69..8d3b23afd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -155,9 +155,14 @@ ignore = [ disable = [ 'expression-not-assigned', 'pointless-statement', - 'fixme' + 'fixme', + 'unspecified-encoding', + 'R0801', ] + [tool.pylint.typecheck] + ignored-modules = ['boto3', 'botocore', 'sympy'] + [tool.pytest.ini_options] @@ -167,6 +172,11 @@ testpaths = ['projectq'] ignore-glob = ['*flycheck*.py'] mock_use_standalone_module = true +[tool.doc8] + +verbose = 0 +max_line_length = 120 + [tool.isort] profile = "black" diff --git a/setup.py b/setup.py index 32426d594..84e6d57af 100755 --- a/setup.py +++ b/setup.py @@ -37,6 +37,8 @@ """Setup.py file.""" +# pylint: disable=deprecated-module + import distutils.log import os import platform @@ -717,7 +719,7 @@ def run(self): class Distribution(_Distribution): """Distribution class.""" - def has_ext_modules(self): # pylint: disable=no-self-use + def has_ext_modules(self): """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 From b2fff97d1669c91c5f732133c9ebde7b47ddde2b Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Sun, 30 Oct 2022 22:49:05 +0100 Subject: [PATCH 100/113] Fix formatting in CHANGELOG and cleanup CI builds workflows --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++------------------ CHANGELOG.md | 8 -------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9a252762..81e77c60d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -210,6 +210,13 @@ jobs: centos: - 7 # GCC 4.8 - 8 + include: + - centos: 7 + python_pkg: rh-python38-python-pip rh-python38-python-devel + enable_repo: --enablerepo=centos-sclo-rh + - centos: 8 + python_pkg: python38-devel + name: "Python 3 • CentOS ${{ matrix.centos }} • x64" container: "centos:${{ matrix.centos }}" @@ -232,12 +239,18 @@ jobs: sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* - - name: Add Python 3 and other dependencies - run: yum update -y && yum install -y python3-devel gcc-c++ make + - name: Update YUM/DNF + run: yum update -y - - name: Setup Endpoint repository (CentOS 7 only) + - name: Enable extra repositories && modify PATH (CentOS 7) if: matrix.centos == 7 - run: yum -y install https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm + run: | + yum install -y centos-release-scl-rh + yum -y install https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm + echo '/opt/rh/rh-python38/root/usr/bin' >> $GITHUB_PATH + + - name: Add Python 3 and other dependencies + run: yum install -y ${{ matrix.enable_repo }} ${{ matrix.python_pkg }} gcc-c++ make - name: Install Git > 2.18 run: | @@ -259,12 +272,9 @@ jobs: uses: actions/cache@v3 with: path: ~/.cache/pip - key: ${{ runner.os }}-centos${{ matrix.centos }}-pip-${{ hashFiles('**/setup.cfg') }} + key: ${{ runner.os }}-centos${{ matrix.centos }}-pip-${{ hashFiles('**/setup.cfg', '**/pyproject.toml') }} restore-keys: ${{ runner.os }}-centos-pip- - - name: Update pip - run: python3 -m pip install --upgrade pip - - name: Install dependencies run: | python3 -m pip install -U pip setuptools wheel @@ -288,16 +298,6 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Create pip cache dir - run: mkdir -p ~/.cache/pip - - - name: Cache wheels - uses: actions/cache@v3 - 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 if: ${{ !env.ACT }} run: | @@ -308,6 +308,10 @@ jobs: with: python-version: '3.x' architecture: 'x64' + cache: 'pip' + cache-dependency-path: | + setup.cfg + pyproject.toml - name: Install docs & setup requirements run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index b1fae5e28..52e8ba08a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -223,19 +223,11 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi 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) -<<<<<<< HEAD -[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.3...HEAD - -[v0.7.3]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.2...v0.7.3 -||||||| 41e608d -[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.2...HEAD -======= [Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.8.0...HEAD [v0.8.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.3...v0.8.0 [v0.7.3]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.2...v0.7.3 ->>>>>>> master [v0.7.2]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.1...v0.7.2 From 7cac539230fe5025833fe1804e758e2c44fe0ec7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 22:44:06 +0100 Subject: [PATCH 101/113] [pre-commit.ci] pre-commit autoupdate (#454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/asottile/pyupgrade: v3.1.0 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.2.0) - https://gitlab.com/PyCQA/flake8 → https://github.com/PyCQA/flake8 - [github.com/PyCQA/flake8: 3.9.2 → 5.0.4](https://github.com/PyCQA/flake8/compare/3.9.2...5.0.4) * Update CHANGELOG Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Nguyen Damien --- .pre-commit-config.yaml | 6 +++--- CHANGELOG.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 729fded8d..770f638c3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -73,7 +73,7 @@ repos: require_serial: false - repo: https://github.com/asottile/pyupgrade - rev: v3.1.0 + rev: v3.2.0 hooks: - id: pyupgrade args: [--py37-plus, --keep-mock] @@ -97,8 +97,8 @@ repos: args: [-S, -l, '120'] additional_dependencies: [black==22.10.0] - - repo: https://gitlab.com/PyCQA/flake8 - rev: 3.9.2 + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 hooks: - id: flake8 name: flake8-strict diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e8ba08a..a95a98fd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update GitHub workflow action versions: `actions/cache@v3`, `actions/checkout@v3`, `actions/setup-python@v4` - Introduce pre-commit CI - Update to clang-tidy 14 in GitHub workflow -- Update pre-commit hook versions - Added new pre-commit hooks: `codespell`, `doc8`, `pydocstyle` and `yamllint` +- Update pre-commit hook versions ## [v0.8.0] - 2022-10-18 From 4b278330d20dbe5f0000a61d17bafaf1e1c5ce08 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 23:32:07 +0100 Subject: [PATCH 102/113] [pre-commit.ci] pre-commit autoupdate (#455) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0) - [github.com/Lucas-C/pre-commit-hooks: v1.3.1 → v1.4.2](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.3.1...v1.4.2) - [github.com/pycqa/pydocstyle: 6.1.1 → 6.3.0](https://github.com/pycqa/pydocstyle/compare/6.1.1...6.3.0) - https://github.com/PyCQA/doc8/: v1.0.0 → v1.1.1 - [github.com/adrienverge/yamllint.git: v1.28.0 → v1.29.0](https://github.com/adrienverge/yamllint.git/compare/v1.28.0...v1.29.0) - [github.com/asottile/pyupgrade: v3.2.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.2.0...v3.3.1) - [github.com/PyCQA/isort: 5.10.1 → 5.12.0](https://github.com/PyCQA/isort/compare/5.10.1...5.12.0) - [github.com/psf/black: 22.10.0 → 22.12.0](https://github.com/psf/black/compare/22.10.0...22.12.0) - [github.com/asottile/blacken-docs: v1.12.1 → 1.13.0](https://github.com/asottile/blacken-docs/compare/v1.12.1...1.13.0) - [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0) - [github.com/mgedmin/check-manifest: 0.48 → 0.49](https://github.com/mgedmin/check-manifest/compare/0.48...0.49) * Update CHANGELOG and keep Flake8 5.X * Disable `no-member` Pylint warning on pre-commit CI * Fix failing GitHub workflows * Proper fix for Clang-10 workflow * New attempt at fixing build for Clang-10 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add Git workaround on all container workflows * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix Clang-10 build again --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Nguyen Damien --- .github/workflows/ci.yml | 19 ++++++++++++++++++- .pre-commit-config.yaml | 22 +++++++++++----------- CHANGELOG.md | 3 +++ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81e77c60d..0086ff23b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,12 +112,21 @@ jobs: env: CC: clang CXX: clang++ - PROJECTQ_CLEANUP_COMPILER_FLAGS: ${{ (matrix.clang < 10) && 1 || 0 }} + PROJECTQ_CLEANUP_COMPILER_FLAGS: ${{ (matrix.clang <= 10) && 1 || 0 }} name: "Python 3 • Clang ${{ matrix.clang }} • x64" container: "silkeh/clang:${{ matrix.clang }}" steps: + - name: Install Git + if: matrix.clang == 10 + run: | + apt-get update && apt-get install -y git --no-install-recommends + + # Work-around for https://github.com/actions/runner-images/issues/6775 + - name: Change Owner of Container Working Directory + run: chown root:root . + - uses: actions/checkout@v3 - name: Get history and tags for SCM versioning to work @@ -168,6 +177,10 @@ jobs: steps: - uses: actions/checkout@v3 + # Work-around for https://github.com/actions/runner-images/issues/6775 + - name: Change Owner of Container Working Directory + run: chown root:root . + - name: Get history and tags for SCM versioning to work if: ${{ !env.ACT }} run: | @@ -257,6 +270,10 @@ jobs: yum install -y git git config --global --add safe.directory /__w/ProjectQ/ProjectQ + # Work-around for https://github.com/actions/runner-images/issues/6775 + - name: Change Owner of Container Working Directory + run: chown root:root . + - uses: actions/checkout@v3 - name: Get history and tags for SCM versioning to work diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 770f638c3..424f0f1d7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-added-large-files - id: check-ast @@ -40,19 +40,19 @@ repos: # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.3.1 + rev: v1.4.2 hooks: - id: remove-tabs - repo: https://github.com/pycqa/pydocstyle - rev: 6.1.1 + rev: 6.3.0 hooks: - id: pydocstyle exclude: (_test.*\.py)$ additional_dependencies: [toml] - repo: https://github.com/PyCQA/doc8/ - rev: v1.0.0 + rev: v1.1.1 hooks: - id: doc8 require_serial: false @@ -67,31 +67,31 @@ repos: args: [-S, '.git,third_party', -I, .codespell.allow] - repo: https://github.com/adrienverge/yamllint.git - rev: v1.28.0 + rev: v1.29.0 hooks: - id: yamllint require_serial: false - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.3.1 hooks: - id: pyupgrade args: [--py37-plus, --keep-mock] - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort name: isort (python) - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 22.12.0 hooks: - id: black language_version: python3 - repo: https://github.com/asottile/blacken-docs - rev: v1.12.1 + rev: 1.13.0 hooks: - id: blacken-docs args: [-S, -l, '120'] @@ -116,11 +116,11 @@ repos: rev: 'v3.0.0a5' hooks: - id: pylint - args: ['--score=n'] + args: ['--score=n', '--disable=no-member'] additional_dependencies: [pybind11>=2.6, numpy, requests, matplotlib, networkx] - repo: https://github.com/mgedmin/check-manifest - rev: '0.48' + rev: '0.49' hooks: - id: check-manifest additional_dependencies: ['setuptools-scm', 'pybind11>=2.6'] diff --git a/CHANGELOG.md b/CHANGELOG.md index a95a98fd3..0aae8b1f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository - Update GitHub workflow action versions: `actions/cache@v3`, `actions/checkout@v3`, `actions/setup-python@v4` +- Fix failing GitHub workflows (Clang-10) - Introduce pre-commit CI - Update to clang-tidy 14 in GitHub workflow - Added new pre-commit hooks: `codespell`, `doc8`, `pydocstyle` and `yamllint` - Update pre-commit hook versions +- Keep `flake8` hook to version 5.0.4 until plugin support Flake8 6.X +- Disable `no-member` warning for Pylint on pre-commit CI ## [v0.8.0] - 2022-10-18 From 9f2198221e3f2b92b736fe23bd0c6723ccdfc87c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 00:03:48 +0100 Subject: [PATCH 103/113] Bump thomaseizinger/create-pull-request from 1.2.2 to 1.3.0 (#456) * Bump thomaseizinger/create-pull-request from 1.2.2 to 1.3.0 Bumps [thomaseizinger/create-pull-request](https://github.com/thomaseizinger/create-pull-request) from 1.2.2 to 1.3.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.2.2...1.3.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 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nguyen Damien --- .github/workflows/draft_release.yml | 2 +- .github/workflows/publish_release.yml | 2 +- CHANGELOG.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index 2c54ffd07..514bf0346 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -49,7 +49,7 @@ jobs: # yamllint disable rule:line-length - name: Create pull request - uses: thomaseizinger/create-pull-request@1.2.2 + uses: thomaseizinger/create-pull-request@1.3.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index f6a25fd07..d2c988358 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -266,7 +266,7 @@ jobs: - upload_to_pypi steps: - name: Merge master into develop branch - uses: thomaseizinger/create-pull-request@1.2.2 + uses: thomaseizinger/create-pull-request@1.3.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aae8b1f5..7d0af631a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository -- Update GitHub workflow action versions: `actions/cache@v3`, `actions/checkout@v3`, `actions/setup-python@v4` +- Update GitHub workflow action versions: `actions/cache@v3`, `actions/checkout@v3`, `actions/setup-python@v4`, `thomaseizinger/create-pull-request` - Fix failing GitHub workflows (Clang-10) - Introduce pre-commit CI - Update to clang-tidy 14 in GitHub workflow From 439ffd5110a0fe982306743a95910ba816403a1d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 23:14:15 +0100 Subject: [PATCH 104/113] [pre-commit.ci] pre-commit autoupdate (#458) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/psf/black: 22.12.0 → 23.1.0](https://github.com/psf/black/compare/22.12.0...23.1.0) - [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0) * Update CHANGELOG and keep flake8 5.0.4 for pre-commit hooks * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Damien Nguyen --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 2 +- projectq/backends/_aqt/_aqt_http_client.py | 1 - .../backends/_awsbraket/_awsbraket_boto3_client_test.py | 2 -- projectq/backends/_ibm/_ibm_http_client.py | 1 - projectq/backends/_ionq/_ionq.py | 1 - projectq/backends/_sim/_pysim.py | 4 ++-- projectq/setups/awsbraket_test.py | 2 -- projectq/setups/trapped_ion_decomposer_test.py | 1 - setup.py | 8 +++++++- 10 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 424f0f1d7..5bbc0be35 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -85,7 +85,7 @@ repos: name: isort (python) - repo: https://github.com/psf/black - rev: 22.12.0 + rev: 23.1.0 hooks: - id: black language_version: python3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d0af631a..726187b7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update to clang-tidy 14 in GitHub workflow - Added new pre-commit hooks: `codespell`, `doc8`, `pydocstyle` and `yamllint` - Update pre-commit hook versions -- Keep `flake8` hook to version 5.0.4 until plugin support Flake8 6.X +- Keep `flake8` hook to version 5.0.4 until plugins support Flake8 6.X - Disable `no-member` warning for Pylint on pre-commit CI ## [v0.8.0] - 2022-10-18 diff --git a/projectq/backends/_aqt/_aqt_http_client.py b/projectq/backends/_aqt/_aqt_http_client.py index a545ab2d0..b2c8d9048 100644 --- a/projectq/backends/_aqt/_aqt_http_client.py +++ b/projectq/backends/_aqt/_aqt_http_client.py @@ -136,7 +136,6 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover signal.signal(signal.SIGINT, _handle_sigint_during_get_result) for retries in range(num_retries): - argument = {'id': execution_id, 'access_token': self.token} req = super().put(urljoin(_API_URL, self.backends[device]['url']), data=argument) req.raise_for_status() diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py index 93cc994de..2b6e33e05 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py @@ -180,7 +180,6 @@ def test_send_too_many_qubits(mocker, send_too_many_setup): ], ) def test_send_real_device_online_verbose(mocker, var_status, var_result, real_device_online_setup): - ( qtarntask, creds, @@ -285,7 +284,6 @@ def test_send_that_errors_are_caught(mocker, var_error, send_that_error_setup): @has_boto3 @pytest.mark.parametrize("var_error", [('ResourceNotFoundException')]) def test_retrieve_error_arn_not_exist(mocker, var_error, arntask, creds): - 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": f"Msg error for {var_error}"}}, diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 815a04626..27602984b 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -228,7 +228,6 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover 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().get(urljoin(_API_URL, job_status_url), **json_step5) diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py index cd70b75a0..4c59369c7 100644 --- a/projectq/backends/_ionq/_ionq.py +++ b/projectq/backends/_ionq/_ionq.py @@ -205,7 +205,6 @@ def _store(self, cmd): 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) diff --git a/projectq/backends/_sim/_pysim.py b/projectq/backends/_sim/_pysim.py index 05b7477c0..51a4eb165 100755 --- a/projectq/backends/_sim/_pysim.py +++ b/projectq/backends/_sim/_pysim.py @@ -236,7 +236,7 @@ def get_expectation_value(self, terms_dict, ids): """ expectation = 0.0 current_state = _np.copy(self._state) - for (term, coefficient) in terms_dict: + for term, coefficient in terms_dict: self._apply_term(term, ids) delta = coefficient * _np.vdot(current_state, self._state).real expectation += delta @@ -253,7 +253,7 @@ def apply_qubit_operator(self, terms_dict, ids): """ new_state = _np.zeros_like(self._state) current_state = _np.copy(self._state) - for (term, coefficient) in terms_dict: + for term, coefficient in terms_dict: self._apply_term(term, ids) self._state *= coefficient new_state += self._state diff --git a/projectq/setups/awsbraket_test.py b/projectq/setups/awsbraket_test.py index a0455258f..a4a95d469 100644 --- a/projectq/setups/awsbraket_test.py +++ b/projectq/setups/awsbraket_test.py @@ -132,7 +132,6 @@ @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 @@ -144,7 +143,6 @@ def test_awsbraket_get_engine_list(mock_boto3_client, var_device): @has_boto3 @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 diff --git a/projectq/setups/trapped_ion_decomposer_test.py b/projectq/setups/trapped_ion_decomposer_test.py index 2638aac83..8670ef1e9 100644 --- a/projectq/setups/trapped_ion_decomposer_test.py +++ b/projectq/setups/trapped_ion_decomposer_test.py @@ -138,7 +138,6 @@ def test_chooser_Ry_reducer(): ), (get_engine_list(), 11), ]: - backend = DummyEngine(save_commands=True) eng = projectq.MainEngine(backend, engine_list, verbose=True) qubit1 = eng.allocate_qubit() diff --git a/setup.py b/setup.py index 84e6d57af..5605d3e0c 100755 --- a/setup.py +++ b/setup.py @@ -380,7 +380,13 @@ def build_extensions(self): def _get_compilation_commands(self, ext): # pylint: disable=protected-access - (_, objects, extra_postargs, pp_opts, build,) = self.compiler._setup_compile( + ( + _, + objects, + extra_postargs, + pp_opts, + build, + ) = self.compiler._setup_compile( outdir=self.build_temp, sources=ext.sources, macros=ext.define_macros, From 92594453a89c8cb1746237a57aaf953b643347dd Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Thu, 9 Mar 2023 17:01:56 +0100 Subject: [PATCH 105/113] Update GitHub workflows to work with merge queues (#461) --- .github/workflows/ci.yml | 1 + .github/workflows/format.yml | 1 + .github/workflows/pull_request.yml | 1 + CHANGELOG.md | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0086ff23b..84a63d78a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ name: CI on: workflow_dispatch: + merge_group: pull_request: push: branches: diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index c2ce1e4bf..6c83dd693 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -4,6 +4,7 @@ name: Format on: workflow_dispatch: + merge_group: pull_request: push: branches: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 9325a3ab6..b7a90cdde 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -2,6 +2,7 @@ name: PR on: + merge_group: pull_request: types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] diff --git a/CHANGELOG.md b/CHANGELOG.md index 726187b7e..3e50339fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository +- Update GitHub workflows to work with merge queues - Update GitHub workflow action versions: `actions/cache@v3`, `actions/checkout@v3`, `actions/setup-python@v4`, `thomaseizinger/create-pull-request` - Fix failing GitHub workflows (Clang-10) - Introduce pre-commit CI From 67c660ca18725d23ab0b261a45e34873b6a58d03 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Fri, 10 Mar 2023 01:03:57 +0900 Subject: [PATCH 106/113] Fix typo in compiler_tutorial.ipynb (#460) * Fix typo in compiler_tutorial.ipynb documention -> documentation * Update CHANGELOG --------- Co-authored-by: Nguyen Damien --- CHANGELOG.md | 4 ++++ examples/compiler_tutorial.ipynb | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e50339fb..08fef0a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Python context manager `with flushing(MainEngine()) as eng:` +### Fixed + +- Fixed some typos (thanks to @eltociear) + ### Repository - Update GitHub workflows to work with merge queues diff --git a/examples/compiler_tutorial.ipynb b/examples/compiler_tutorial.ipynb index 92cab21d7..68419fc4e 100644 --- a/examples/compiler_tutorial.ipynb +++ b/examples/compiler_tutorial.ipynb @@ -231,7 +231,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Please have a look at the documention of the [restrictedgateset](http://projectq.readthedocs.io/en/latest/projectq.setups.html#module-projectq.setups.restrictedgateset) for details. The above compiler compiles the circuit to gates consisting of any single qubit gate, the `CNOT` and `Toffoli` gate. The gate specifications can either be a gate class, e.g., `Rz` or a specific instance `Rz(math.pi)`. A smaller but still universal gate set would be for example `CNOT` and `Rz, Ry`:" + "Please have a look at the documentation of the [restrictedgateset](http://projectq.readthedocs.io/en/latest/projectq.setups.html#module-projectq.setups.restrictedgateset) for details. The above compiler compiles the circuit to gates consisting of any single qubit gate, the `CNOT` and `Toffoli` gate. The gate specifications can either be a gate class, e.g., `Rz` or a specific instance `Rz(math.pi)`. A smaller but still universal gate set would be for example `CNOT` and `Rz, Ry`:" ] }, { @@ -292,7 +292,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As mentioned in the documention of [this setup](http://projectq.readthedocs.io/en/latest/projectq.setups.html#module-projectq.setups.restrictedgateset), one cannot (yet) choose an arbitrary gate set but there is a limited choice. If it doesn't work for a specified gate set, the compiler will either raises a `NoGateDecompositionError` or a `RuntimeError: maximum recursion depth exceeded...` which means that for this particular choice of gate set, one would be required to write more [decomposition rules](https://github.com/ProjectQ-Framework/ProjectQ/tree/develop/projectq/setups/decompositions) to make it work. Also for some choice of gate set there might be compiler engines producing more optimal code." + "As mentioned in the documentation of [this setup](http://projectq.readthedocs.io/en/latest/projectq.setups.html#module-projectq.setups.restrictedgateset), one cannot (yet) choose an arbitrary gate set but there is a limited choice. If it doesn't work for a specified gate set, the compiler will either raises a `NoGateDecompositionError` or a `RuntimeError: maximum recursion depth exceeded...` which means that for this particular choice of gate set, one would be required to write more [decomposition rules](https://github.com/ProjectQ-Framework/ProjectQ/tree/develop/projectq/setups/decompositions) to make it work. Also for some choice of gate set there might be compiler engines producing more optimal code." ] }, { From 8cc782bdd205c6baa268aa567cc1885aa2f48506 Mon Sep 17 00:00:00 2001 From: Valentin Stauber Date: Sun, 7 Jan 2024 16:15:58 +0100 Subject: [PATCH 107/113] Update README.rst (#467) * Update README.rst fix imports in README example * Update CHANGELOG.md --- CHANGELOG.md | 2 +- README.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08fef0a62..3a099c501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Fixed some typos (thanks to @eltociear) +- Fixed some typos (thanks to @eltociear, @Darkdragon84) ### Repository diff --git a/README.rst b/README.rst index e8451dfae..3397c4070 100755 --- a/README.rst +++ b/README.rst @@ -85,7 +85,7 @@ linear chain and the architecture supports any single-qubit gate as well as the from projectq import MainEngine from projectq.backends import ResourceCounter - from projectq.ops import QFT + from projectq.ops import QFT, CNOT, Swap from projectq.setups import linear compiler_engines = linear.get_engine_list(num_qubits=16, one_qubit_gates='any', two_qubit_gates=(CNOT, Swap)) From cdb4d044eccc948eeea5f56d6a2bbd4a01099498 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Jan 2024 16:16:25 +0100 Subject: [PATCH 108/113] Bump docker/setup-qemu-action from 2 to 3 (#466) Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nguyen Damien --- .github/workflows/publish_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index d2c988358..d203057c5 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Set up QEMU if: matrix.cibw_archs == 'aarch64' - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: platforms: arm64 From c30dfec479c48092f261a93d34b60ce8740b26a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Jan 2024 16:16:55 +0100 Subject: [PATCH 109/113] Bump thomaseizinger/create-pull-request from 1.3.0 to 1.3.1 (#464) * Bump thomaseizinger/create-pull-request from 1.3.0 to 1.3.1 Bumps [thomaseizinger/create-pull-request](https://github.com/thomaseizinger/create-pull-request) from 1.3.0 to 1.3.1. - [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.3.0...1.3.1) --- updated-dependencies: - dependency-name: thomaseizinger/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update CHANGELOG --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nguyen Damien --- .github/workflows/draft_release.yml | 2 +- .github/workflows/publish_release.yml | 2 +- CHANGELOG.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index 514bf0346..7518c4a38 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -49,7 +49,7 @@ jobs: # yamllint disable rule:line-length - name: Create pull request - uses: thomaseizinger/create-pull-request@1.3.0 + uses: thomaseizinger/create-pull-request@1.3.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index d203057c5..b431279fb 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -266,7 +266,7 @@ jobs: - upload_to_pypi steps: - name: Merge master into develop branch - uses: thomaseizinger/create-pull-request@1.3.0 + uses: thomaseizinger/create-pull-request@1.3.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a099c501..62d3ab86a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Keep `flake8` hook to version 5.0.4 until plugins support Flake8 6.X - Disable `no-member` warning for Pylint on pre-commit CI + ## [v0.8.0] - 2022-10-18 ### Added From dad48c59e0a7f4c114b5885a38439e7774698b39 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 7 Jan 2024 17:05:07 +0100 Subject: [PATCH 110/113] [pre-commit.ci] pre-commit autoupdate (#459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/Lucas-C/pre-commit-hooks: v1.4.2 → v1.5.4](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.4.2...v1.5.4) - [github.com/codespell-project/codespell: v2.2.2 → v2.2.6](https://github.com/codespell-project/codespell/compare/v2.2.2...v2.2.6) - [github.com/adrienverge/yamllint.git: v1.29.0 → v1.33.0](https://github.com/adrienverge/yamllint.git/compare/v1.29.0...v1.33.0) - [github.com/asottile/pyupgrade: v3.3.1 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.3.1...v3.15.0) - [github.com/PyCQA/isort: 5.12.0 → 5.13.2](https://github.com/PyCQA/isort/compare/5.12.0...5.13.2) - [github.com/psf/black: 23.1.0 → 23.12.1](https://github.com/psf/black/compare/23.1.0...23.12.1) - [github.com/asottile/blacken-docs: 1.13.0 → 1.16.0](https://github.com/asottile/blacken-docs/compare/1.13.0...1.16.0) - [github.com/PyCQA/flake8: 5.0.4 → 6.1.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.1.0) * Temporary disable Azure Quantum backend * Fix CI issues * Fix CI issues with GCC build * Fix typos * Use virtualenv for GCC and Clang builds * Update CHANGELOG * Fix failing CI tests * Fix failing CI tests * Fix pre-commit issues + remove distutils.log * Temporary disable step in failing CI job --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Nguyen Damien --- .github/workflows/ci.yml | 37 ++++++++++--------- .pre-commit-config.yaml | 18 ++++----- CHANGELOG.md | 2 +- docs/tutorials.rst | 2 +- .../backends/_azure/_azure_quantum_test.py | 3 +- projectq/cengines/_linearmapper.py | 2 +- projectq/cengines/_main_test.py | 2 +- projectq/meta/_control.py | 2 +- projectq/ops/_command_test.py | 2 +- setup.py | 5 +-- 10 files changed, 38 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84a63d78a..ebe2d38d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,7 +120,6 @@ jobs: steps: - name: Install Git - if: matrix.clang == 10 run: | apt-get update && apt-get install -y git --no-install-recommends @@ -140,27 +139,27 @@ jobs: 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 - libomp-dev + python3-pytest python3-pytest-cov python3-flaky python3-venv --no-install-recommends - name: Prepare Python env run: | - python3 -m pip install -U pip setuptools wheel - python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + python3 -m venv venv + ./venv/bin/python3 -m pip install -U pip setuptools wheel + ./venv/bin/python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket cat requirements.txt - python3 -m pip install -r requirements.txt --prefer-binary + ./venv/bin/python3 -m pip install -r requirements.txt --prefer-binary - name: Upgrade pybind11 and flaky - run: python3 -m pip install --upgrade pybind11 flaky --prefer-binary + run: ./venv/bin/python3 -m pip install --upgrade pybind11 flaky --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[azure-quantum,braket,test] + run: ./venv/bin/python3 -m pip install -ve .[azure-quantum,braket,test] - name: Pytest run: | echo 'backend: Agg' > matplotlibrc - python3 -m pytest -p no:warnings + ./venv/bin/python3 -m pytest -p no:warnings gcc: @@ -192,26 +191,27 @@ jobs: 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 + python3-pytest python3-pytest-cov python3-flaky python3-venv --no-install-recommends - name: Prepare Python env run: | - python3 -m pip install -U pip setuptools wheel - python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket + python3 -m venv venv + ./venv/bin/python3 -m pip install -U pip setuptools wheel + ./venv/bin/python3 setup.py gen_reqfile --include-extras=test,azure-quantum,braket cat requirements.txt - python3 -m pip install -r requirements.txt --prefer-binary + ./venv/bin/python3 -m pip install -r requirements.txt --prefer-binary - name: Upgrade pybind11 and flaky - run: python3 -m pip install --upgrade pybind11 flaky --prefer-binary + run: ./venv/bin/python3 -m pip install --upgrade pybind11 flaky --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[azure-quantum,braket,test] + run: ./venv/bin/python3 -m pip install -ve .[azure-quantum,braket,test] - name: Pytest run: | echo 'backend: Agg' > matplotlibrc - python3 -m pytest -p no:warnings + ./venv/bin/python3 -m pytest -p no:warnings # Testing on CentOS (manylinux uses a centos base, and this is an easy way @@ -338,5 +338,6 @@ jobs: - name: Build docs run: python3 -m sphinx -b html docs docs/.build - - name: Make SDist - run: python3 setup.py sdist + # NB: disabling until setup.py is updated to remove any mention of distutils + # - name: Make SDist + # run: python3 setup.py sdist diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5bbc0be35..27cfb9c49 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-ast @@ -40,7 +40,7 @@ repos: # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.4.2 + rev: v1.5.4 hooks: - id: remove-tabs @@ -59,7 +59,7 @@ repos: additional_dependencies: [tomli] - repo: https://github.com/codespell-project/codespell - rev: v2.2.2 + rev: v2.2.6 hooks: - id: codespell require_serial: false @@ -67,38 +67,38 @@ repos: args: [-S, '.git,third_party', -I, .codespell.allow] - repo: https://github.com/adrienverge/yamllint.git - rev: v1.29.0 + rev: v1.33.0 hooks: - id: yamllint require_serial: false - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.15.0 hooks: - id: pyupgrade args: [--py37-plus, --keep-mock] - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort name: isort (python) - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.12.1 hooks: - id: black language_version: python3 - repo: https://github.com/asottile/blacken-docs - rev: 1.13.0 + rev: 1.16.0 hooks: - id: blacken-docs args: [-S, -l, '120'] additional_dependencies: [black==22.10.0] - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.1.0 hooks: - id: flake8 name: flake8-strict diff --git a/CHANGELOG.md b/CHANGELOG.md index 62d3ab86a..d7fe748f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,9 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Introduce pre-commit CI - Update to clang-tidy 14 in GitHub workflow - Added new pre-commit hooks: `codespell`, `doc8`, `pydocstyle` and `yamllint` -- Update pre-commit hook versions - Keep `flake8` hook to version 5.0.4 until plugins support Flake8 6.X - Disable `no-member` warning for Pylint on pre-commit CI +- Update pre-commit hook versions ## [v0.8.0] - 2022-10-18 diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 3fba8c9da..dd264073d 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -239,7 +239,7 @@ Detailed instructions and OS-specific hints 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 + suitable compiler with support for **C++11**, OpenMP, and intrinsics. The best option is to install clang 9.0 also using macports (note: gcc installed via macports does not work). .. code-block:: bash diff --git a/projectq/backends/_azure/_azure_quantum_test.py b/projectq/backends/_azure/_azure_quantum_test.py index 3cdc51bb2..f6d2193d2 100644 --- a/projectq/backends/_azure/_azure_quantum_test.py +++ b/projectq/backends/_azure/_azure_quantum_test.py @@ -22,7 +22,8 @@ from projectq.ops import CX, All, Command, H, Measure from projectq.types import WeakQubitRef -_has_azure_quantum = True +# NB: temporary workaround until Azure Quantum backend is fixed +_has_azure_quantum = False try: from azure.quantum import Workspace diff --git a/projectq/cengines/_linearmapper.py b/projectq/cengines/_linearmapper.py index c544fa9b6..0e39bbf56 100644 --- a/projectq/cengines/_linearmapper.py +++ b/projectq/cengines/_linearmapper.py @@ -121,7 +121,7 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_comma Build 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. + basis. More complicated 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 diff --git a/projectq/cengines/_main_test.py b/projectq/cengines/_main_test.py index 392fff73a..665e8815f 100755 --- a/projectq/cengines/_main_test.py +++ b/projectq/cengines/_main_test.py @@ -67,7 +67,7 @@ def test_main_engine_init_defaults(): default_engines = projectq.setups.default.get_engine_list() for engine, expected in zip(eng_list, default_engines): - assert type(engine) == type(expected) + assert type(engine) is type(expected) def test_main_engine_too_many_compiler_engines(): diff --git a/projectq/meta/_control.py b/projectq/meta/_control.py index 549e0e9e5..176892709 100755 --- a/projectq/meta/_control.py +++ b/projectq/meta/_control.py @@ -43,7 +43,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. + register, e.g. if ctrl_state == 2, its binary representation if '10' with the least significant bit being 0. This means in particular that the following are equivalent: diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index 0a315d64d..19db3644c 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -45,7 +45,7 @@ def test_command_init(main_engine): for cmd_qureg, expected_qureg in zip(cmd.qubits, expected_tuple): assert cmd_qureg[0].id == expected_qureg[0].id # Testing that Qubits are now WeakQubitRef objects - assert type(cmd_qureg[0]) == WeakQubitRef + assert type(cmd_qureg[0]) is WeakQubitRef assert cmd._engine == main_engine # Test that quregs are ordered if gate has interchangeable qubits: symmetric_gate = BasicGate() diff --git a/setup.py b/setup.py index 5605d3e0c..934c31ccf 100755 --- a/setup.py +++ b/setup.py @@ -39,7 +39,6 @@ # pylint: disable=deprecated-module -import distutils.log import os import platform import subprocess @@ -655,7 +654,7 @@ def run(self): # 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) + # 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 @@ -663,7 +662,7 @@ def run(self): cmd_obj.run() self.distribution.have_run[command] = 1 except BuildFailed as err: - distutils.log.error('build_ext --dry-run --gen-compiledb command failed!') + # distutils.log.error('build_ext --dry-run --gen-compiledb command failed!') raise RuntimeError('build_ext --dry-run --gen-compiledb command failed!') from err command = ['clang-tidy'] From 3897613f1f9dba2ba1845140d4b641ebcf53c9ba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 19:18:41 +0000 Subject: [PATCH 111/113] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/Lucas-C/pre-commit-hooks: v1.5.4 → v1.5.5](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.5.4...v1.5.5) - [github.com/adrienverge/yamllint.git: v1.33.0 → v1.35.1](https://github.com/adrienverge/yamllint.git/compare/v1.33.0...v1.35.1) - [github.com/asottile/pyupgrade: v3.15.0 → v3.15.2](https://github.com/asottile/pyupgrade/compare/v3.15.0...v3.15.2) - [github.com/psf/black: 23.12.1 → 24.3.0](https://github.com/psf/black/compare/23.12.1...24.3.0) - [github.com/PyCQA/flake8: 6.1.0 → 7.0.0](https://github.com/PyCQA/flake8/compare/6.1.0...7.0.0) --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27cfb9c49..cf59ca9c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.4 + rev: v1.5.5 hooks: - id: remove-tabs @@ -67,13 +67,13 @@ repos: args: [-S, '.git,third_party', -I, .codespell.allow] - repo: https://github.com/adrienverge/yamllint.git - rev: v1.33.0 + rev: v1.35.1 hooks: - id: yamllint require_serial: false - repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 + rev: v3.15.2 hooks: - id: pyupgrade args: [--py37-plus, --keep-mock] @@ -85,7 +85,7 @@ repos: name: isort (python) - repo: https://github.com/psf/black - rev: 23.12.1 + rev: 24.3.0 hooks: - id: black language_version: python3 @@ -98,7 +98,7 @@ repos: additional_dependencies: [black==22.10.0] - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 + rev: 7.0.0 hooks: - id: flake8 name: flake8-strict From 088c97380c88e3600e4d66fd2b411d9e833c5525 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Tue, 2 Apr 2024 22:20:32 +0200 Subject: [PATCH 112/113] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7fe748f0..6ac525fdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,13 +19,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update GitHub workflows to work with merge queues - Update GitHub workflow action versions: `actions/cache@v3`, `actions/checkout@v3`, `actions/setup-python@v4`, `thomaseizinger/create-pull-request` +- Update pre-commit hook versions - Fix failing GitHub workflows (Clang-10) - Introduce pre-commit CI - Update to clang-tidy 14 in GitHub workflow - Added new pre-commit hooks: `codespell`, `doc8`, `pydocstyle` and `yamllint` - Keep `flake8` hook to version 5.0.4 until plugins support Flake8 6.X - Disable `no-member` warning for Pylint on pre-commit CI -- Update pre-commit hook versions ## [v0.8.0] - 2022-10-18 From f2a2c2117a668869b3864f8da2b14260276527de Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Tue, 23 Apr 2024 21:20:47 +0200 Subject: [PATCH 113/113] Remove Python 3.7 + add Python 3.11 + 3.12 (#471) * Remove Python 3.7 + add Python 3.11 + 3.12 * Update cibuildwheel config * Update black config * Update minimum GCC and Clang version on CI * Fix refcheck=False when in CI * Update minimum package versions for 3rd-party dependencies * Update CHANGELOG * Fix compatibility with Python 3.12 * Fix linter warnings * Update GitHub release workflows * Fix setup.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix setup.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Automatically cancel redundant GitHub workflows * Fix clang-tidy workflow * Fix setuptools installation for CI runs * Fix setup.py * Test with naked except clause * Fix exception handling when testing for compiler flags * Fix linter warnings * Fix typo --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 17 +++++++---- .github/workflows/draft_release.yml | 20 +++++++++---- .github/workflows/format.yml | 9 ++++-- .github/workflows/publish_release.yml | 20 +++++-------- CHANGELOG.md | 5 ++++ docs/tutorials.rst | 12 ++++---- projectq/backends/_sim/_pysim.py | 2 +- pyproject.toml | 34 ++++++++------------- setup.py | 43 ++++++++++++++++----------- 9 files changed, 92 insertions(+), 70 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebe2d38d0..bb242ba80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,10 @@ on: - develop - v* +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: standard: strategy: @@ -19,10 +23,11 @@ jobs: matrix: runs-on: [ubuntu-latest, windows-latest, macos-latest] python: - - 3.7 - 3.8 - 3.9 - '3.10' + - '3.11' + - '3.12' name: "Python ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" runs-on: ${{ matrix.runs-on }} @@ -46,6 +51,10 @@ jobs: setup.cfg pyproject.toml + - name: Prepare env + run: | + python -m pip install -U "setuptools>=61.0.0" + - name: Generate requirement file (Unix) if: runner.os != 'Windows' run: | @@ -106,9 +115,7 @@ jobs: 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) + - 11 - latest env: CC: clang @@ -168,7 +175,7 @@ jobs: fail-fast: false matrix: gcc: - - 7 # C++17 earliest version + - 9 - latest name: "Python 3 • GCC ${{ matrix.gcc }} • x64" diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index 7518c4a38..63092b6dc 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -2,7 +2,7 @@ name: "Draft new release" -on: +on: # yamllint disable-line rule:truthy workflow_dispatch: inputs: tag: @@ -18,6 +18,13 @@ jobs: - uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v3 + with: + python-version: '3.11' + cache: 'pip' + cache-dependency-path: '**/setup.cfg' + - name: Configure git-flow run: | git fetch --tags --depth=1 origin master develop @@ -26,16 +33,17 @@ jobs: - name: Create release branch run: git flow release start ${{ github.event.inputs.tag }} - - name: Update changelog - uses: thomaseizinger/keep-a-changelog-new-release@1.3.0 - with: - version: ${{ github.event.inputs.tag }} - - name: Initialize mandatory git config run: | git config user.name "GitHub actions" git config user.email noreply@github.com + - name: Update CHANGELOG + run: | + python3 -m pip install mdformat-gfm 'git+https://github.com/Takishima/keepachangelog@v1.0.1' + python3 -m keepachangelog release "${{ github.event.inputs.tag }}" + python3 -m mdformat CHANGELOG.md + - name: Commit changelog and manifest files id: make-commit run: | diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 6c83dd693..18a5a10c3 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -12,6 +12,11 @@ on: - stable - "v*" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + jobs: clang-tidy: name: Clang-Tidy @@ -29,8 +34,8 @@ jobs: 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: Upgrade pybind11 and setuptools + run: python3 -m pip install --upgrade pybind11 "setuptools>=61.0.0" --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 index b431279fb..1695a745d 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -59,10 +59,9 @@ jobs: && startsWith(github.event.pull_request.head.ref, 'release/') && runner.os != 'Windows' run: | - BRANCH_NAME="${{ github.event.pull_request.head.ref }}" - VERSION=${BRANCH_NAME#release/} - echo "VERSION = ${VERSION}" - git tag ${VERSION} master + TAG_NAME="${GITHUB_REF/refs\/tags\//}" + VERSION=${TAG_NAME#v} + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV - name: Extract version from branch name (for hotfix branches) (Unix) if: > @@ -71,9 +70,8 @@ jobs: && runner.os != 'Windows' run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" - VERSION=${BRANCH_NAME#hotfix/} - echo "VERSION = ${VERSION}" - git tag ${VERSION} master + VERSION=${BRANCH_NAME#release/v} + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV # ------------------------------------------------------------------------ @@ -84,9 +82,8 @@ jobs: && runner.os == 'Windows' run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" - $VERSION = $BRANCH_NAME -replace "release/","" - Write-Output "VERSION = ${VERSION}" - git tag ${VERSION} master + $VERSION = $BRANCH_NAME -replace "release/v","" + Write-Output "VERSION = ${VERSION}" >> $Env:GITHUB_ENV - name: Extract version from branch name (for hotfix branches) (Windows) if: > @@ -96,8 +93,7 @@ jobs: run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "hotfix/","" - Write-Output "VERSION = ${VERSION}" - git tag ${VERSION} master + Write-Output "VERSION = ${VERSION}" >> $Env:GITHUB_ENV # ======================================================================== diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac525fdf..e58e12027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed some typos (thanks to @eltociear, @Darkdragon84) +- Fixed support for Python 3.12 + +### Removed + +- Support for Python 3.7 ### Repository diff --git a/docs/tutorials.rst b/docs/tutorials.rst index dd264073d..fd6b8c08a 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -132,7 +132,7 @@ Detailed instructions and OS-specific hints **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 + do so is using, e.g., the Python 3.8 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 @@ -220,23 +220,23 @@ Detailed instructions and OS-specific hints 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.8 by entering the following command .. code-block:: bash - sudo port install python37 + sudo port install python38 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 - sudo port install py37-gnureadline + sudo port install py38-gnureadline Install pip by .. code-block:: bash - sudo port install py37-pip + sudo port install py38-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 intrinsics. The best option is to install clang 9.0 also @@ -250,7 +250,7 @@ Detailed instructions and OS-specific hints .. 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.8 -m pip install --user projectq The ProjectQ syntax diff --git a/projectq/backends/_sim/_pysim.py b/projectq/backends/_sim/_pysim.py index 51a4eb165..f6fb58fd5 100755 --- a/projectq/backends/_sim/_pysim.py +++ b/projectq/backends/_sim/_pysim.py @@ -24,7 +24,7 @@ import numpy as _np _USE_REFCHECK = True -if 'TRAVIS' in os.environ: # pragma: no cover +if 'CI' in os.environ: # pragma: no cover _USE_REFCHECK = False diff --git a/pyproject.toml b/pyproject.toml index 8d3b23afd..6e4686e23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,9 @@ [build-system] requires = [ - 'setuptools>=61;python_version>="3.7"', - 'setuptools>=59;python_version<"3.7"', + 'setuptools>=61', 'wheel', 'pybind11>=2', - 'setuptools_scm[toml]>6;python_version>="3.7"' + 'setuptools_scm[toml]>6' ] build-backend = "setuptools.build_meta" @@ -14,26 +13,27 @@ authors = [ {name = 'ProjectQ', email = 'info@projectq.ch'} ] description = 'ProjectQ - An open source software framework for quantum computing' -requires-python = '>= 3.7' +requires-python = '>= 3.8' license = {text= 'Apache License Version 2.0'} readme = 'README.rst' classifiers = [ 'License :: OSI Approved :: Apache Software License', 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10' + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12' ] dynamic = ["version"] dependencies = [ 'matplotlib >= 2.2.3', - 'networkx >= 2', - 'numpy', - 'requests', - 'scipy' + 'networkx >= 2.4', + 'numpy>=1.21.5', + 'requests>=2.25.1', + 'scipy>=1.8.0' ] [project.urls] @@ -57,7 +57,7 @@ revkit = [ ] test = [ - 'flaky', + 'flaky>=3.7.0', 'mock', 'pytest >= 6.0', 'pytest-cov', @@ -65,7 +65,7 @@ test = [ ] docs = [ - 'sphinx', + 'sphinx>=4.3.2', 'sphinx_rtd_theme' ] @@ -74,7 +74,7 @@ docs = [ [tool.black] line-length = 120 - target-version = ['py37','py38','py39','py310'] + target-version = ['py38','py39','py310','py311','py312'] skip-string-normalization = true @@ -205,11 +205,3 @@ test-command = 'python {package}/examples/grover.py' # Normal options, etc. manylinux-x86_64-image = 'manylinux2014' - -[[tool.cibuildwheel.overrides]] -select = 'cp36-*' -manylinux-x86_64-image = 'manylinux1' - -[[tool.cibuildwheel.overrides]] -select = 'cp3{7,8,9}-*' -manylinux-x86_64-image = 'manylinux2010' diff --git a/setup.py b/setup.py index 934c31ccf..24a647bd4 100755 --- a/setup.py +++ b/setup.py @@ -37,29 +37,36 @@ """Setup.py file.""" -# pylint: disable=deprecated-module +# pylint: disable=deprecated-module,attribute-defined-outside-init import os import platform import subprocess import sys import tempfile -from distutils.cmd import Command -from distutils.errors import ( - CCompilerError, - CompileError, - DistutilsExecError, - DistutilsPlatformError, - LinkError, -) -from distutils.spawn import find_executable, spawn from operator import itemgetter from pathlib import Path +from setuptools import Command from setuptools import Distribution as _Distribution from setuptools import Extension, setup from setuptools.command.build_ext import build_ext +try: + from setuptools._distutils.errors import ( + CCompilerError, + CompileError, + DistutilsError, + ) + from setuptools._distutils.spawn import DistutilsExecError, find_executable, spawn + from setuptools.errors import PlatformError + + _SETUPTOOL_IMPORT_ERROR = None + +except ImportError as setuptools_import_error: + _SETUPTOOL_IMPORT_ERROR = setuptools_import_error + + try: import setuptools_scm # noqa: F401 # pylint: disable=unused-import @@ -232,7 +239,7 @@ def compiler_test( with open('err.txt') as err_file: if err_file.readlines(): raise RuntimeError('') - except (CompileError, LinkError, RuntimeError): + except Exception: # pylint: disable=broad-except return False else: return True @@ -277,7 +284,9 @@ def __init__(self): # Python build related variable cpython = platform.python_implementation() == 'CPython' -ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError) +ext_errors = () +if _SETUPTOOL_IMPORT_ERROR is None: + ext_errors = (CCompilerError, DistutilsError, CompileError, DistutilsExecError) if sys.platform == 'win32': # 2.6's distutils.msvc9compiler can raise an IOError when failing to # find the compiler @@ -329,13 +338,16 @@ def finalize_options(self): """Finalize this command's options.""" build_ext.finalize_options(self) if self.gen_compiledb: - self.dry_run = True # pylint: disable=attribute-defined-outside-init + self.dry_run = True def run(self): """Execute this command.""" + if _SETUPTOOL_IMPORT_ERROR is not None: + raise _SETUPTOOL_IMPORT_ERROR + try: build_ext.run(self) - except DistutilsPlatformError as err: + except PlatformError as err: raise BuildFailed() from err def build_extensions(self): @@ -416,8 +428,6 @@ 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 @@ -703,7 +713,6 @@ def finalize_options(self): if self.include_all_extras or name in include_extras: self.extra_pkgs.extend(pkgs) - # pylint: disable=attribute-defined-outside-init self.dependencies = self.distribution.install_requires if not self.dependencies: self.dependencies = pyproject_toml['project']['dependencies']