{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Quantum Processes and Computation (with PyZX!)\n", "\n", "_Aleks Kissinger | 2020_\n", "\n", "\n", "Welcome to the first in a series of labs (or \"practicals\" in UK parlance) which will build up from basics to using the ZX calculus to do some interesting and potentially useful stuff on a real quantum computer.\n", "\n", "These practicals are designed to go with the [Quantum Processes and Computation](https://www.cs.ox.ac.uk/teaching/courses/2020-2021/quantum/) course taught in Oxford, but if you came across these another way, or just happened up them, you are more than welcome to give them a go! The primary goals of these practicals are:\n", "1. construct quantum circuits and ZX-diagrams programmatically\n", "2. evaluate/simulate them numerically\n", "3. do basic diagrammatic reasoning with software\n", "4. apply 1-3 to a practical (but very small!) example of a quantum computation\n", "\n", "Along the way, we'll achieve some secondary goals:\n", "5. learn a bit about [NISQ](https://arxiv.org/abs/1801.00862) quantum hardware available today, and how to make use of it via cloud quantum computing\n", "6. learn about the [one-way model of measurement-based quantum computation](https://en.wikipedia.org/wiki/One-way_quantum_computer), and do some experiments with ZX/pyzx\n", "\n", "If you are following the QPC course at Oxford, you probably have (or will shortly have) a good idea of what _ZX-diagrams_ and the _ZX-calculus_ are. If not, these are covered in detail in Chapter 9 of the course textbook [Picturing Quantum Processes](https://cambridge.org/pqp). A short tutorial and pretty comprehensive list of references can also be found at [zxcalculus.com](http://www.zxcalculus.com). Everything you need to know about the PyZX library should be introduced as we go, but if you need more, the full code documentation is at [pyzx.readthedocs.io](https://pyzx.readthedocs.io/en/latest/).\n", "\n", "We will only be scratching the surface on quantum hardware and software in these practicals. To learn more about the big picture, a great place to get started is [fullstackquantumcomputation.tech](https://fullstackquantumcomputation.tech/).\n", "\n", "_These practicals, like PyZX itself are released under the [Apache 2](https://github.com/Quantomatic/pyzx/blob/master/LICENSE) open-source license. Feel free to use, copy, and modify them at will, e.g. to use your own course._" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Part 1\n", "\n", "Alright, lets get stuck in! This is a [Jupyter](https://jupyter.org/) notebook, which is a place for running bits of python code and seeing (and saving) pretty output. Some of the code is written for you, and some of it you will write yourself. As you go through, you will find code cells marked `In [ ]:`. Click inside of these cells and press `SHIFT+ENTER` to run the code. Some code defines variables used by other code, so make sure you do this in the right order. To answer the questions, you will need to insert new cells yourself, using the `Insert` menu.\n", "\n", "The first thing we'll do is install the PyZX library if we don't have it already. Jupyter notebooks have a \"magic\" command for installing libraries called `%pip`. So, run this first to install PyZX. You should only need to do it once. (You can delete the command later if you like.)\n", "\n", "If it claims you need to \"restart the kernel\" click the refresh-looking button in the Jupyter toolbar, or go to `Kernel > Restart`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install pyzx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we'll import some stuff from the Python standard library. In particular, we'll use the `Fraction` type for expressing phases later." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sys, os, math\n", "from fractions import Fraction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now we import `pyzx` itself. To save us some extra typing, we'll abbreviate the name to `zx`, import all the basic rules, and make some other useful abbreviations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sys.path.insert(0,os.path.expanduser('~/git/pyzx')) # git version\n", "import pyzx as zx\n", "from pyzx import print_matrix\n", "from pyzx.basicrules import *\n", "\n", "Z = zx.VertexType.Z\n", "X = zx.VertexType.X\n", "B = zx.VertexType.BOUNDARY\n", "SE = zx.EdgeType.SIMPLE\n", "HE = zx.EdgeType.HADAMARD" ] }, { "attachments": { "spiders.png": { "image/png": "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" } }, "cell_type": "markdown", "metadata": {}, "source": [ "ZX-diagrams are string diagrams (aka tensor networks) made up of 2 special generators: _Z-spiders_ and _X-spiders_.\n", "\n", "![spiders.png](attachment:spiders.png)\n", "\n", "A useful thing about these generators is they are invariant under changing the order of inputs/outputs, or even changing an input to an output. Hence, in PyZX, we represent ZX-diagrams as indirected graphs.\n", "\n", "In the previous code block, you saw there were 3 kinds of `VertexType`s we use in graphs: `Z`, `X`, and `B(OUNDARY)`. The first two correspond to Z and X (i.e. green and red) spiders, whereas the third one is a dummy type we use for representing inputs and outputs of the diagram, i.e. wires which are not connected to a spider at one end.\n", "\n", "To get started, lets make a Z-copy spider, i.e. a Z-spider with 1 input, 2 outputs, and no phase." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create a new, empty graph\n", "zcopy = zx.Graph()\n", "\n", "# add_vertex adds a vertex of a given type and returns an id that we\n", "# can refer to later\n", "in1 = zcopy.add_vertex(B, qubit=1, row=0)\n", "z1 = zcopy.add_vertex(Z, qubit=1, row=1)\n", "out1 = zcopy.add_vertex(B, qubit=0, row=2)\n", "out2 = zcopy.add_vertex(B, qubit=2, row=2)\n", "\n", "# add_edge takes a pair of vertex id's and creates an edge. Note the\n", "# double-parentheses!\n", "zcopy.add_edge((in1,z1))\n", "zcopy.add_edge((z1,out1))\n", "zcopy.add_edge((z1,out2))\n", "\n", "# tell pyzx which boundary vertices should count as 'inputs' and which\n", "# as 'outputs', and in what order. This only matters for sequential\n", "# composition.\n", "zcopy.inputs = [in1]\n", "zcopy.outputs = [out1,out2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Most of the time, we won't be building graphs by hand, but it's useful to get an idea of how the data structure works. One thing to note is `add_vertex` takes two parameters `row` and `qubit` which tell PyZX where that vertex should be drawn. These names make sense in the context of a ZX-diagram that came from a quantum circuit, as we'll see later. However, for generic ZX-diagrams, it suffices to think of `row` and `qubit` as X and Y coordinates for positioning vertices on the screen.\n", "\n", "Speaking of drawing, we do that with `zx.draw`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "zx.draw(zcopy)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note you can move vertices around by clicking and dragging. This comes in handy when the graphs get more complicated.\n", "\n", "Now, lets make graphs that correspond to a single wire (i.e. the identity process) and the swap. We use a convenience method called `auto_detect_io`, which tries to guess which boundaries should be inputs and outputs, and orders them from top to bottom." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "wire = zx.Graph()\n", "in1 = wire.add_vertex(B, qubit=0, row=0)\n", "out1 = wire.add_vertex(B, qubit=0, row=1)\n", "wire.add_edge((in1,out1))\n", "wire.auto_detect_io()\n", "\n", "print(\"wire :=\")\n", "zx.draw(wire)\n", "\n", "swap = zx.Graph()\n", "in1 = swap.add_vertex(B, qubit=0, row=0)\n", "in2 = swap.add_vertex(B, qubit=1, row=0)\n", "out1 = swap.add_vertex(B, qubit=0, row=1)\n", "out2 = swap.add_vertex(B, qubit=1, row=1)\n", "swap.add_edge((in1,out2))\n", "swap.add_edge((in2,out1))\n", "swap.auto_detect_io()\n", "\n", "print(\"swap :=\")\n", "zx.draw(swap)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rather than starting from scratch, we can make an X-merge by taking the adjoint of Z-copy, then changing the Z vertex to an X vertex with `set_type`. Note: we needed to know that the id of the Z vertex is equal to 1." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "xmerge = zcopy.adjoint()\n", "z1 = 1\n", "xmerge.set_type(z1, X)\n", "zx.draw(xmerge)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can set the phase of a spider with `set_phase`, or use the optional `phase=` paremeter to `add_vertex`. It is given as a rational multiple of pi, so we use Python's built-in `Fraction` type for this." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "xmerge_pi4 = xmerge.copy()\n", "xmerge_pi4.set_phase(z1, Fraction(1,4))\n", "zx.draw(xmerge_pi4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "