{ "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# ProjectQ Compiler Tutorial\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The aim of this short tutorial is to give a first introduction to the ProjectQ compiler and show the use different preconfigured setups. In particular, we will show how to specify the gate set to which the compiler should translate a quantum program. A more extended tutorial will follow soon. Please check out our [ProjectQ paper](http://arxiv.org/abs/1612.08091) for an introduction to the basic concepts behind our compiler. If you are interested how to compile to a restricted hardware with, e.g., only nearest neighbour connectivity, please have a look at the `mapper_tutorial.ipynb` afterwards." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The default compiler" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To compile a quantum program, we begin by creating a compiler called `MainEngine` and specify the backend for which the compiler should translate the program. For the purpose of this tutorial, we will use a `CommandPrinter` as a backend to display the compiled algorithm. It works the same for all other backends such as, e.g., the simulator or an interface to real hardware.\n", "\n", "Let's write a small program:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Allocate | Qureg[1]\n", "Allocate | Qureg[0]\n", "Allocate | Qureg[2]\n", "Allocate | Qureg[3]\n", "Cexp(-0.1j * (0.5 X0 Y1 Z2)) | ( Qureg[0], Qureg[1-3] )\n", "QFT | Qureg[1-3]\n", "Rx(0.1) | Qureg[0]\n", "CX | ( Qureg[0], Qureg[1] )\n", "Measure | Qureg[1]\n", "Measure | Qureg[2]\n", "Measure | Qureg[3]\n", "Measure | Qureg[0]\n", "Deallocate | Qureg[0]\n", "Deallocate | Qureg[3]\n", "Deallocate | Qureg[2]\n", "Deallocate | Qureg[1]\n" ] } ], "source": [ "import projectq\n", "from projectq.backends import CommandPrinter\n", "from projectq.meta import Control\n", "from projectq.ops import All, CNOT, Measure, QFT, QubitOperator, Rx, TimeEvolution, X\n", "\n", "# create the compiler and specify the backend:\n", "eng = projectq.MainEngine(backend=CommandPrinter(accept_input=False))\n", "\n", "def my_quantum_program(eng):\n", " qubit = eng.allocate_qubit()\n", " qureg = eng.allocate_qureg(3)\n", " with Control(eng, qubit):\n", " hamiltonian = 0.5 * QubitOperator(\"X0 Y1 Z2\")\n", " TimeEvolution(0.1, hamiltonian) | qureg\n", " QFT | qureg\n", " Rx(0.1) | qubit\n", " CNOT | (qubit, qureg[0])\n", " All(Measure) | qureg\n", " Measure | qubit\n", " eng.flush()\n", "my_quantum_program(eng)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the above example, the compiler did nothing because the default compiler (when `MainEngine` is called without a specific `engine_list` parameter) translates the individual gates to the gate set supported by the backend. In our case, the backend is a `CommandPrinter` which supports any type of gate.\n", "\n", "We can check what happens when the backend is a `Simulator` by inserting a `CommandPrinter` as a last compiler engine before the backend so that every command is printed before it gets sent to the Simulator: " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Allocate | Qureg[1]\n", "Allocate | Qureg[0]\n", "Allocate | Qureg[2]\n", "Allocate | Qureg[3]\n", "Cexp(-0.1j * (0.5 X0 Y1 Z2)) | ( Qureg[0], Qureg[1-3] )\n", "H | Qureg[3]\n", "CR(1.5707963268) | ( Qureg[2], Qureg[3] )\n", "CR(0.785398163397) | ( Qureg[1], Qureg[3] )\n", "H | Qureg[2]\n", "CR(1.5707963268) | ( Qureg[1], Qureg[2] )\n", "H | Qureg[1]\n", "Rx(0.1) | Qureg[0]\n", "CX | ( Qureg[0], Qureg[1] )\n", "Measure | Qureg[1]\n", "Measure | Qureg[2]\n", "Measure | Qureg[3]\n", "Measure | Qureg[0]\n", "Deallocate | Qureg[0]\n", "Deallocate | Qureg[3]\n", "Deallocate | Qureg[2]\n", "Deallocate | Qureg[1]\n" ] } ], "source": [ "from projectq.backends import Simulator\n", "from projectq.setups.default import get_engine_list\n", "\n", "# Use the default compiler engines with a CommandPrinter in the end:\n", "engines2 = get_engine_list() + [CommandPrinter()]\n", "\n", "eng2 = projectq.MainEngine(backend=Simulator(), engine_list=engines2)\n", "my_quantum_program(eng2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As one can see, in this case the compiler had to do a little work because the Simulator does not support a QFT gate. Therefore, it automatically replaces the QFT gate by a sequence of lower-level gates." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using a provided setup and specifying a particular gate set" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ProjectQ's compiler is fully modular, so one can easily build a special purpose compiler. All one has to do is compose a list of [compiler engines](https://github.com/ProjectQ-Framework/ProjectQ/tree/develop/projectq/cengines) through which the individual operations will pass in a serial order and give this compiler list to the `MainEngine` as the `engine_list` parameter.\n", "For common compiler needs we try to provide predefined \"setups\" which contain a function `get_engine_list` which returns a suitable list of compiler engines for the `MainEngine`. All of our current setups can be found in [projectq.setups](https://github.com/ProjectQ-Framework/ProjectQ/tree/develop/projectq/setups). For example there is a setup called `restrictedgateset` which allows to compile to common restricted gate sets. This is useful, for example, to obtain resource estimates for running a given program on actual quantum hardware which does not support every quantum gate. Let's look at an example:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Allocate | Qureg[3]\n", "Allocate | Qureg[1]\n", "Allocate | Qureg[2]\n", "CCX | ( Qureg[1-2], Qureg[3] )\n", "H | Qureg[3]\n", "Rz(0.785398163398) | Qureg[3]\n", "R(0.785398163398) | Qureg[2]\n", "CX | ( Qureg[2], Qureg[3] )\n", "Rz(11.780972451) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "R(0.392699081698) | Qureg[1]\n", "Rz(0.392699081698) | Qureg[3]\n", "CX | ( Qureg[1], Qureg[3] )\n", "H | Qureg[2]\n", "Rz(12.1736715327) | Qureg[3]\n", "CX | ( Qureg[1], Qureg[3] )\n", "R(0.785398163398) | Qureg[1]\n", "Rz(0.785398163398) | Qureg[2]\n", "CX | ( Qureg[1], Qureg[2] )\n", "Rz(11.780972451) | Qureg[2]\n", "CX | ( Qureg[1], Qureg[2] )\n", "H | Qureg[1]\n", "Measure | Qureg[1]\n", "Measure | Qureg[2]\n", "Measure | Qureg[3]\n", "Allocate | Qureg[0]\n", "H | Qureg[0]\n", "Rx(0.3) | Qureg[0]\n", "Measure | Qureg[0]\n", "Deallocate | Qureg[0]\n", "Deallocate | Qureg[3]\n", "Deallocate | Qureg[2]\n", "Deallocate | Qureg[1]\n" ] } ], "source": [ "import projectq\n", "from projectq.setups import restrictedgateset\n", "from projectq.ops import All, H, Measure, Rx, Ry, Rz, Toffoli\n", "engine_list3 = restrictedgateset.get_engine_list(one_qubit_gates=\"any\",\n", " two_qubit_gates=(CNOT,),\n", " other_gates=(Toffoli,))\n", "eng3 = projectq.MainEngine(backend=CommandPrinter(accept_input=False),\n", " engine_list=engine_list3)\n", "\n", "def my_second_program(eng):\n", " qubit = eng3.allocate_qubit()\n", " qureg = eng3.allocate_qureg(3)\n", " H | qubit\n", " Rx(0.3) | qubit\n", " Toffoli | (qureg[:-1], qureg[2])\n", " QFT | qureg\n", " All(Measure) | qureg\n", " Measure | qubit\n", " eng.flush()\n", "my_second_program(eng3)" ] }, { "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`:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Allocate | Qureg[7]\n", "Allocate | Qureg[5]\n", "Allocate | Qureg[6]\n", "CCX | ( Qureg[5-6], Qureg[7] )\n", "H | Qureg[7]\n", "Rz(0.785398163398) | Qureg[7]\n", "R(0.785398163398) | Qureg[6]\n", "CX | ( Qureg[6], Qureg[7] )\n", "Rz(11.780972451) | Qureg[7]\n", "CX | ( Qureg[6], Qureg[7] )\n", "R(0.392699081698) | Qureg[5]\n", "Rz(0.392699081698) | Qureg[7]\n", "CX | ( Qureg[5], Qureg[7] )\n", "H | Qureg[6]\n", "Rz(12.1736715327) | Qureg[7]\n", "CX | ( Qureg[5], Qureg[7] )\n", "R(0.785398163398) | Qureg[5]\n", "Rz(0.785398163398) | Qureg[6]\n", "CX | ( Qureg[5], Qureg[6] )\n", "Rz(11.780972451) | Qureg[6]\n", "CX | ( Qureg[5], Qureg[6] )\n", "H | Qureg[5]\n", "Measure | Qureg[5]\n", "Measure | Qureg[6]\n", "Measure | Qureg[7]\n", "Allocate | Qureg[4]\n", "H | Qureg[4]\n", "Rx(0.3) | Qureg[4]\n", "Measure | Qureg[4]\n", "Deallocate | Qureg[4]\n", "Deallocate | Qureg[7]\n", "Deallocate | Qureg[6]\n", "Deallocate | Qureg[5]\n" ] } ], "source": [ "engine_list4 = restrictedgateset.get_engine_list(one_qubit_gates=(Rz, Ry),\n", " two_qubit_gates=(CNOT,),\n", " other_gates=())\n", "eng4 = projectq.MainEngine(backend=CommandPrinter(accept_input=False),\n", " engine_list=engine_list4)\n", "my_second_program(eng4)" ] }, { "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Error messages\n", "By default the `MainEngine` shortens error messages as most often this is enough information to find the error. To see the full error message one can to set `verbose=True`, i.e.:\n", "`MainEngine(verbose=True)`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## DIY: Build a compiler engine list for a specific gate set\n", "In this short example, we want to look at how to build an own compiler `engine_list` for compiling to a restricted gate set. Please have a look at the [predefined setups](https://github.com/ProjectQ-Framework/ProjectQ/tree/develop/projectq/setups) for guidance.\n", "\n", "One of the important compiler engines to change the gate set is the `AutoReplacer`. It queries the following engines to check if a particular gate is supported and if not, it will use decomposition rules to change this gate to supported ones. Most engines just forward this query to the next engine until the backend is reached. The engine after an `AutoReplacer` is usually a `TagRemover` which removes previous tags in commands such as, e.g., `ComputeTag` which allows a following `LocalOptimizer` to perform more optimizations (otherwise it would only optimize within a \"compute\" section and not over the boundaries).\n", "\n", "To specify different intermediate gate sets, one can insert an `InstructionFilter` into the `engine_list` after the `AutoReplacer` in order to return `True` or `False` for the queries of the `AutoReplacer` asking if a specific gate is supported. \n", "\n", "Here is a minimal example of a compiler which compiles to CNOT and single qubit gates but doesn't perform optimizations (which could be achieved using the `LocalOptimizer`). For the more optimal versions, have a look at the [`restrictricedgateset` setup](https://github.com/ProjectQ-Framework/ProjectQ/blob/develop/projectq/setups/restrictedgateset.py#L63):" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Allocate | Qureg[0]\n", "Allocate | Qureg[1]\n", "Allocate | Qureg[2]\n", "Allocate | Qureg[3]\n", "H | Qureg[3]\n", "CX | ( Qureg[1], Qureg[3] )\n", "T | Qureg[1]\n", "T^\\dagger | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[2], Qureg[1] )\n", "T^\\dagger | Qureg[1]\n", "T | Qureg[3]\n", "CX | ( Qureg[2], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[3] )\n", "T^\\dagger | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "T | Qureg[3]\n", "T | Qureg[2]\n", "H | Qureg[3]\n", "H | Qureg[3]\n", "R(0.785398163398) | Qureg[2]\n", "Rz(0.785398163398) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "Rz(11.780972451) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "R(0.392699081698) | Qureg[1]\n", "Rz(0.392699081698) | Qureg[3]\n", "CX | ( Qureg[1], Qureg[3] )\n", "Rz(12.1736715327) | Qureg[3]\n", "CX | ( Qureg[1], Qureg[3] )\n", "H | Qureg[2]\n", "R(0.785398163398) | Qureg[1]\n", "Rz(0.785398163398) | Qureg[2]\n", "CX | ( Qureg[1], Qureg[2] )\n", "Rz(11.780972451) | Qureg[2]\n", "CX | ( Qureg[1], Qureg[2] )\n", "H | Qureg[1]\n", "Measure | Qureg[1]\n", "Measure | Qureg[2]\n", "Measure | Qureg[3]\n", "Measure | Qureg[0]\n", "Deallocate | Qureg[0]\n", "Deallocate | Qureg[3]\n", "Deallocate | Qureg[2]\n", "Deallocate | Qureg[1]\n" ] } ], "source": [ "import projectq\n", "from projectq.backends import CommandPrinter\n", "from projectq.cengines import AutoReplacer, DecompositionRuleSet, InstructionFilter\n", "from projectq.ops import All, ClassicalInstructionGate, Measure, Toffoli, X\n", "import projectq.setups.decompositions\n", "\n", "# Write a function which, given a Command object, returns whether the command is supported:\n", "def is_supported(eng, cmd):\n", " if isinstance(cmd.gate, ClassicalInstructionGate):\n", " # This is required to allow Measure, Allocate, Deallocate, Flush\n", " return True\n", " elif isinstance(cmd.gate, X.__class__) and len(cmd.control_qubits) == 1:\n", " # Allows a CNOT gate which is an X gate with one control qubit\n", " return True\n", " elif (len(cmd.control_qubits) == 0 and \n", " len(cmd.qubits) == 1 and\n", " len(cmd.qubits[0]) == 1):\n", " # Gate which has no control qubits, applied to 1 qureg consisting of 1 qubit\n", " return True\n", " else:\n", " return False\n", "\n", "#is_supported(\"test\", \"eng\")\n", "\n", "rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions])\n", "engine_list5 = [AutoReplacer(rule_set), InstructionFilter(is_supported)]\n", "eng5 = projectq.MainEngine(backend=CommandPrinter(accept_input=False),\n", " engine_list=engine_list5)\n", "\n", "def my_third_program(eng):\n", " qubit = eng5.allocate_qubit()\n", " qureg = eng5.allocate_qureg(3)\n", " Toffoli | (qureg[:2], qureg[2])\n", " QFT | qureg\n", " All(Measure) | qureg\n", " Measure | qubit\n", " eng5.flush()\n", "my_third_program(eng5)" ] }, { "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 }