{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Quantum Software (with PyZX!)\n", "\n", "_Aleks Kissinger | 2023_\n", "\n", "\n", "This problem sheet is designed to go with the [Quantum Software](https://www.cs.ox.ac.uk/teaching/courses/2022-2023/qsoft/) course taught in Oxford, but if you came across it another way, you are more than welcome to give it a go! The primary goals of this and the next sheet are to:\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 example of a quantum computation\n", "\n", "Along the way, we'll achieve a secondary goal:\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", "\n", "If you are following the Quantum Software course at Oxford, you should have a good idea of what _ZX-diagrams_ and the _ZX-calculus_ are. If not, have a look at [ZX-calculus for the working quantum computer scientist](https://arxiv.org/abs/2012.13966). 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 problem sheets, 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 blocks marked `In [ ]:`. Click inside of these blocks 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.\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", "Note we are installing the git version of pyzx here. To install the latest release, use simply `pip install pyzx`.\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 @ git+https://github.com/Quantomatic/pyzx.git\"" ] }, { "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": [ "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", "\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.set_inputs((in1,))\n", "zcopy.set_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": true }, "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": [ "