diff --git a/examples/README.rst b/examples/README.rst index d22e3c6ea..6ee67c41b 100644 --- a/examples/README.rst +++ b/examples/README.rst @@ -14,6 +14,10 @@ It might be a good starting point to have a look at our paper which explains the * Damian S. Steiger, Thomas Häner, and Matthias Troyer "ProjectQ: An Open Source Software Framework for Quantum Computing" `[arxiv:1612.08091] `__ +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] `__ + Examples and tutorials in this folder ------------------------------------- @@ -24,3 +28,5 @@ Examples and tutorials in this folder 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. + +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/examples/mapper_tutorial.ipynb b/examples/mapper_tutorial.ipynb new file mode 100644 index 000000000..5027819b2 --- /dev/null +++ b/examples/mapper_tutorial.ipynb @@ -0,0 +1,553 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ProjectQ Mapper Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The aim of this short tutorial is to give an introduction to the ProjectQ mappers.\n", + "\n", + "ProjectQ allows a user to write a quantum program in a high-level language. For example, one can apply quantum operations on n-qubits, e.g., `QFT`, and the compiler will decompose this operations into two-qubit and single-qubit gates. See the [compiler_tutorial](https://github.com/ProjectQ-Framework/ProjectQ/tree/develop/examples) for an introduction.\n", + "\n", + "After decomposing a quantum program into two-qubit and single-qubit gates which a quantum computer supports, we have to take the physical layout of these qubits into account. Two-qubit gates are only possible if the qubits are next to each other. For example the qubits could be arranged in a linear chain or a two-dimensional grid and only nearest neighbour qubits can perform a two-qubit gate. ProjectQ uses **mappers** which move the positions of the qubits close to each other using `Swap` operations in order that we can execute a two-qubit gate.\n", + "\n", + "The implementation and some results of ProjectQ's mappers are discussed in [our paper (section 3C)](https://arxiv.org/abs/1806.01861)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example: Mapping to a linear chain\n", + "\n", + "Let's look at an example of a quantum fourier transform (`QFT`) compiled into single-qubit gates and `CNOT`s. First, we look at the resources required if the qubits have an *all-to-all* connectivity, i.e., any pairs of qubits can execute a `CNOT`:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Gate class counts:\n", + " AllocateQubitGate : 15\n", + " CXGate : 210\n", + " HGate : 15\n", + " R : 105\n", + " Rz : 210\n", + "\n", + "Gate counts:\n", + " Allocate : 15\n", + " CX : 210\n", + " H : 15\n", + " R(0.000191747598) : 2\n", + " R(0.000383495197) : 3\n", + " R(0.000766990394) : 4\n", + " R(0.001533980788) : 5\n", + " R(0.003067961576) : 6\n", + " R(0.006135923151) : 7\n", + " R(0.012271846303) : 8\n", + " R(0.024543692606) : 9\n", + " R(0.049087385213) : 10\n", + " R(0.098174770424) : 11\n", + " R(0.196349540849) : 12\n", + " R(0.392699081698) : 13\n", + " R(0.785398163398) : 14\n", + " R(9.5873799e-05) : 1\n", + " Rz(0.000191747598) : 2\n", + " Rz(0.000383495197) : 3\n", + " Rz(0.000766990394) : 4\n", + " Rz(0.001533980788) : 5\n", + " Rz(0.003067961576) : 6\n", + " Rz(0.006135923151) : 7\n", + " Rz(0.012271846303) : 8\n", + " Rz(0.024543692606) : 9\n", + " Rz(0.049087385213) : 10\n", + " Rz(0.098174770424) : 11\n", + " Rz(0.196349540849) : 12\n", + " Rz(0.392699081698) : 13\n", + " Rz(0.785398163398) : 14\n", + " Rz(11.780972451) : 14\n", + " Rz(12.1736715327) : 13\n", + " Rz(12.3700210735) : 12\n", + " Rz(12.4681958439) : 11\n", + " Rz(12.5172832291) : 10\n", + " Rz(12.5418269218) : 9\n", + " Rz(12.5540987681) : 8\n", + " Rz(12.5602346912) : 7\n", + " Rz(12.5633026528) : 6\n", + " Rz(12.5648366336) : 5\n", + " Rz(12.565603624) : 4\n", + " Rz(12.5659871192) : 3\n", + " Rz(12.5661788668) : 2\n", + " Rz(12.5662747406) : 1\n", + " Rz(9.5873799e-05) : 1\n", + "\n", + "Max. width (number of qubits) : 15.\n" + ] + } + ], + "source": [ + "import projectq\n", + "from projectq.backends import ResourceCounter\n", + "from projectq.ops import CNOT, QFT\n", + "from projectq.setups import restrictedgateset\n", + "\n", + "engine_list = restrictedgateset.get_engine_list(one_qubit_gates=\"any\",\n", + " two_qubit_gates=(CNOT,))\n", + "resource_counter = ResourceCounter()\n", + "eng = projectq.MainEngine(backend=resource_counter, engine_list=engine_list)\n", + "\n", + "qureg = eng.allocate_qureg(15)\n", + "QFT | qureg\n", + "eng.flush()\n", + "\n", + "print(resource_counter)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's assume our qubits are arrange on a linear chain, we can use an already [predefined compiler setup](http://projectq.readthedocs.io/en/latest/projectq.setups.html#module-projectq.setups.linear) to compile to this architecture:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Gate class counts:\n", + " AllocateQubitGate : 15\n", + " CXGate : 888\n", + " HGate : 15\n", + " R : 105\n", + " Rz : 210\n", + "\n", + "Gate counts:\n", + " Allocate : 15\n", + " CX : 888\n", + " H : 15\n", + " R(0.000191747598) : 2\n", + " R(0.000383495197) : 3\n", + " R(0.000766990394) : 4\n", + " R(0.001533980788) : 5\n", + " R(0.003067961576) : 6\n", + " R(0.006135923151) : 7\n", + " R(0.012271846303) : 8\n", + " R(0.024543692606) : 9\n", + " R(0.049087385213) : 10\n", + " R(0.098174770424) : 11\n", + " R(0.196349540849) : 12\n", + " R(0.392699081698) : 13\n", + " R(0.785398163398) : 14\n", + " R(9.5873799e-05) : 1\n", + " Rz(0.000191747598) : 2\n", + " Rz(0.000383495197) : 3\n", + " Rz(0.000766990394) : 4\n", + " Rz(0.001533980788) : 5\n", + " Rz(0.003067961576) : 6\n", + " Rz(0.006135923151) : 7\n", + " Rz(0.012271846303) : 8\n", + " Rz(0.024543692606) : 9\n", + " Rz(0.049087385213) : 10\n", + " Rz(0.098174770424) : 11\n", + " Rz(0.196349540849) : 12\n", + " Rz(0.392699081698) : 13\n", + " Rz(0.785398163398) : 14\n", + " Rz(11.780972451) : 14\n", + " Rz(12.1736715327) : 13\n", + " Rz(12.3700210735) : 12\n", + " Rz(12.4681958439) : 11\n", + " Rz(12.5172832291) : 10\n", + " Rz(12.5418269218) : 9\n", + " Rz(12.5540987681) : 8\n", + " Rz(12.5602346912) : 7\n", + " Rz(12.5633026528) : 6\n", + " Rz(12.5648366336) : 5\n", + " Rz(12.565603624) : 4\n", + " Rz(12.5659871192) : 3\n", + " Rz(12.5661788668) : 2\n", + " Rz(12.5662747406) : 1\n", + " Rz(9.5873799e-05) : 1\n", + "\n", + "Max. width (number of qubits) : 15.\n" + ] + } + ], + "source": [ + "from projectq.setups import linear\n", + "\n", + "engine_list2 = linear.get_engine_list(num_qubits=15, cyclic=False,\n", + " one_qubit_gates=\"any\",\n", + " two_qubit_gates=(CNOT,))\n", + "resource_counter2 = ResourceCounter()\n", + "eng2 = projectq.MainEngine(backend=resource_counter2, engine_list=engine_list2)\n", + "\n", + "qureg2 = eng2.allocate_qureg(15)\n", + "QFT | qureg2\n", + "eng2.flush()\n", + "\n", + "print(resource_counter2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One can see that once we restricted the hardware to a linear chain, the same program requires a lot more `CNOT` (also called `CX`) gates. This is due to additionals `Swap` operations to move the qubits around (a `Swap` gate can be constructed out of three `CX` gates)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example: Mapping to a two-dimensional grid\n", + "\n", + "ProjectQ also has a [predefined setup](http://projectq.readthedocs.io/en/latest/projectq.setups.html#module-projectq.setups.grid) to map to a two-dimensional grid." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Gate class counts:\n", + " AllocateQubitGate : 15\n", + " CXGate : 741\n", + " HGate : 15\n", + " R : 105\n", + " Rz : 210\n", + "\n", + "Gate counts:\n", + " Allocate : 15\n", + " CX : 741\n", + " H : 15\n", + " R(0.000191747598) : 2\n", + " R(0.000383495197) : 3\n", + " R(0.000766990394) : 4\n", + " R(0.001533980788) : 5\n", + " R(0.003067961576) : 6\n", + " R(0.006135923151) : 7\n", + " R(0.012271846303) : 8\n", + " R(0.024543692606) : 9\n", + " R(0.049087385213) : 10\n", + " R(0.098174770424) : 11\n", + " R(0.196349540849) : 12\n", + " R(0.392699081698) : 13\n", + " R(0.785398163398) : 14\n", + " R(9.5873799e-05) : 1\n", + " Rz(0.000191747598) : 2\n", + " Rz(0.000383495197) : 3\n", + " Rz(0.000766990394) : 4\n", + " Rz(0.001533980788) : 5\n", + " Rz(0.003067961576) : 6\n", + " Rz(0.006135923151) : 7\n", + " Rz(0.012271846303) : 8\n", + " Rz(0.024543692606) : 9\n", + " Rz(0.049087385213) : 10\n", + " Rz(0.098174770424) : 11\n", + " Rz(0.196349540849) : 12\n", + " Rz(0.392699081698) : 13\n", + " Rz(0.785398163398) : 14\n", + " Rz(11.780972451) : 14\n", + " Rz(12.1736715327) : 13\n", + " Rz(12.3700210735) : 12\n", + " Rz(12.4681958439) : 11\n", + " Rz(12.5172832291) : 10\n", + " Rz(12.5418269218) : 9\n", + " Rz(12.5540987681) : 8\n", + " Rz(12.5602346912) : 7\n", + " Rz(12.5633026528) : 6\n", + " Rz(12.5648366336) : 5\n", + " Rz(12.565603624) : 4\n", + " Rz(12.5659871192) : 3\n", + " Rz(12.5661788668) : 2\n", + " Rz(12.5662747406) : 1\n", + " Rz(9.5873799e-05) : 1\n", + "\n", + "Max. width (number of qubits) : 15.\n" + ] + } + ], + "source": [ + "from projectq.setups import grid\n", + "\n", + "engine_list3 = grid.get_engine_list(num_rows=3, num_columns=5,\n", + " one_qubit_gates=\"any\",\n", + " two_qubit_gates=(CNOT,))\n", + "resource_counter3 = ResourceCounter()\n", + "eng3 = projectq.MainEngine(backend=resource_counter3, engine_list=engine_list3)\n", + "\n", + "qureg3 = eng3.allocate_qureg(15)\n", + "QFT | qureg3\n", + "eng3.flush()\n", + "\n", + "print(resource_counter3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that mapping a `QFT` to a two-dimensional grid layout requires fewer `CX` gates than mapping to a linear chain as expected." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Inspecting the current mapping of logical qubits to physical qubits\n", + "\n", + "A qubit which you obtain by calling the `allocate_qubit()` function of the compiler (`MainEngine`) is just an abstract objects which has a unique ID." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This logical qubit0 has the unique ID: 10\n", + "This logical qubit1 has the unique ID: 11\n", + "This logical qubit2 has the unique ID: 12\n", + "Allocate | Qureg[0]\n", + "X | Qureg[0]\n", + "Allocate | Qureg[1]\n", + "Allocate | Qureg[2]\n" + ] + } + ], + "source": [ + "from projectq.backends import CommandPrinter\n", + "from projectq.ops import X, Swap\n", + "\n", + "engine_list4 = linear.get_engine_list(num_qubits=3, cyclic=False,\n", + " one_qubit_gates=\"any\",\n", + " two_qubit_gates=(CNOT, Swap))\n", + "\n", + "eng4 = projectq.MainEngine(backend=CommandPrinter(), engine_list=engine_list4)\n", + "\n", + "# For instructional purposes we change that the eng4 gives logical ids starting\n", + "# from 10. This could e.g. be the case if a previous part of the program\n", + "# already allocated 10 qubits\n", + "eng4._qubit_idx = 10\n", + "\n", + "qubit0 = eng4.allocate_qubit()\n", + "qubit1 = eng4.allocate_qubit()\n", + "qubit2 = eng4.allocate_qubit()\n", + "\n", + "X | qubit0\n", + "\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", + "\n", + "eng4.flush()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see `qubit0` has a logical ID equal to 10. The *LinearMapper* in this compiler setup then places these qubits on a linear chain with the following physical qubit ID ordering:\n", + "\n", + "0 -- 1 -- 2\n", + "\n", + "where -- indicates that these two qubits can perform a `CNOT` gate. If you are interested in knowing where a specific logical qubit is currently placed, you can access this information via the `current_mapping` property of the mapper:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Physical location of qubit0: 0\n", + "Physical location of qubit1: 1\n", + "Physical location of qubit2: 2\n" + ] + } + ], + "source": [ + "# eng.mapper gives back the mapper in the engine_list\n", + "current_mapping = eng4.mapper.current_mapping\n", + "# 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]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose we now perform a `CNOT` between `qubit0` and `qubit2`, then the mapper needs to swap these two qubits close to each other to perform the operation:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Swap | ( Qureg[1], Qureg[2] )\n", + "CX | ( Qureg[0], Qureg[1] )\n", + "\n", + "Physical location of qubit0: 0\n", + "Physical location of qubit1: 2\n", + "Physical location of qubit2: 1\n" + ] + } + ], + "source": [ + "CNOT | (qubit0, qubit2)\n", + "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]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that the compiler added a `Swap` gate to change the location of the logical qubits in this chain so that the CNOT can be performed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Measurements, probabilities, and amplitudes\n", + "\n", + "While the compiler automatically remaps logical qubits to different physical locations, how does this affect the high-level programmer?\n", + "\n", + "The short answer is not at all. \n", + "\n", + "If you want to measure a logical qubit, just apply a measurement gate as before and the compiler will automatically find the correct physical qubit to measure:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "qubit0 was measured in state: 1\n" + ] + } + ], + "source": [ + "from projectq.backends import Simulator\n", + "from projectq.ops import Measure\n", + "\n", + "engine_list5 = linear.get_engine_list(num_qubits=3, cyclic=False,\n", + " one_qubit_gates=\"any\",\n", + " two_qubit_gates=(CNOT, Swap))\n", + "\n", + "eng5 = projectq.MainEngine(backend=Simulator(), engine_list=engine_list5)\n", + "\n", + "qubit0 = eng5.allocate_qubit()\n", + "qubit1 = eng5.allocate_qubit()\n", + "qubit2 = eng5.allocate_qubit()\n", + "\n", + "X | qubit0\n", + "Measure | qubit0\n", + "eng5.flush()\n", + "\n", + "print(\"qubit0 was measured in state: {}\".format(int(qubit0)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All the simulator functionalities, e.g., `get_probability` or `get_amplitude` work as usual because they take logical qubits as arguments so the programmer does not need to worry about at which physical location `qubit0` is at the moment:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.0" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng5.backend.get_probability('1', qubit0)" + ] + }, + { + "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 +}