{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Programming with the ZX-calculus" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this lab session we will be working with PyZX, a library in Python that allows us to manage ZX-diagrams and use the ZX-calculus to optimise circuits. PyZX's documentation can be found at: https://pyzx.readthedocs.io/en/latest/index.html.\n", "\n", "We will also look at its interaction with the two other Python libraries that we have seen in lectures. Qiskit is a quantum software development kit that aims to cover the entire quantum programming pipeline. OpenQASM is a low-level intermediate representation that allows specification of quantum circuit operations.\n", "\n", "The aim of this lab is not to become fluent in any of the languages, but to give a taste of which things in class have already been automated and how some things are easier to express than others.\n", "\n", "If you find anything that you don't like, maybe *you* can be someone who makes it better!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First install the libraries and import them if you don't already have them." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install pyzx\n", "%pip install qiskit\n", "%pip install openqasm3\n", "%pip install matplotlib" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import pyzx as zx\n", "import qiskit\n", "import openqasm3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Overview\n", "\n", "We have been discussing the values of different quantum programming representations throughout the semester. The exercises here have been chosen to give you the chance to think about them for yourself. We will focus on interacting with ZX-based representations. \n", "\n", "There are two main exercises provided in this document - each provides a code example for you to implement, then asks you to use some of the features in the systems more creatively. Each then has a couple of more open-ended questions for us to discuss in class.\n", "\n", "### ! There is more work here than you are expected to do. Pick one or two questions to work through and discuss your thoughts with your peers. !\n", "\n", "### Overarching Questions\n", "\n", " 1. When you write a program, is it easy to tell what it does? Why? \n", " 2. What ways can you see a program being optimised? Are these easy to spot?\n", " 3. Can you write other programs that are similar to the ones we have written?\n", "\n", "## Introduction\n", "\n", "This section has all of the tools you need to do any of the other exercises.\n", "\n", "## Questions: What is ZX for?\n", "### 1. Synthesis\n", "- a. Synthesising a classical program from a matrix\n", "\n", "### 2. Optimisation\n", "- a. Circuit optimisation\n", "- b. Targeting real quantum hardware\n", "\n", "### 3. Simulation\n", "If you wish, you can also try simulating the results of your quantum programs with Qiskit. This is not an explicit question." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Introduction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's go through making and visualising circuits and zx-diagrams. All of the cells in this section can just be run for reference.\n", "\n", "[*This section is a modified version of a combination of the one available at https://github.com/Quantomatic/pyzx, created by Aleks Kissinger and John van de Wetering and changes made previously by Pablo Andrés-Martínez and Robert I. Booth.*]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To make and edit ZX-diagrams directly go to this page: https://pyzx.readthedocs.io/en/latest/graph.html#the-zx-diagram-editor" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## PyZX and Circuits\n", "\n", "We can use PyZX to create ZX diagrams, circuits, and ZX-diagrams that equate to circuits.\n", "\n", "One example, which we will see in lectures, are Clifford circuits. A Clifford circuit is a ZX- circuits whose phases are all multiples of $\\pi/2$. They are easy to manage because the axioms of the original ZX-calculus are enough to prove any equivalence of Clifford circuits. \n", "\n", "We will now use PyZX to create a random Clifford circuit and visualise it." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import random\n", "qubit_amount = 4\n", "depth = 15\n", "random.seed(1337)\n", "circ = zx.generate.cliffords(qubit_amount, depth)\n", "zx.draw_d3(circ,labels=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The green and red nodes represent Z- and X-phase gates respectively, the yellow boxes are Hadamard gates, and the vertical lines going between two different colored nodes are CNOT gates.\n", "\n", "We can get the circuit representation for this and look at which gates are in it.\n", "\n", "#### N.B. If you use zx.extract, the circuit *will* become empty, so you can't use it after this. You can use .copy() to get one to extract without editing your original circuit." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Circuit on 4 qubits with 61 gates.\n", " 0 is the T-count\n", " 61 Cliffords among which\n", " 7 2-qubit gates (0 CNOT, 7 other) and\n", " 34 Hadamard gates.\n" ] } ], "source": [ "c = circ.copy()\n", "c = zx.extract.extract_circuit(c)\n", "print(c.stats())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[*Aside*] If you look carefully, you might notice that converting all of the dots directly to gates would have given you 30 gates and two CNOT gates, but the circuit stats are not this. Why might this be?\n", "\n", "(You can look at the qasm circuit version below)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "OPENQASM 2.0;\n", "include \"qelib1.inc\";\n", "qreg q[4];\n", "h q[2];\n", "h q[3];\n", "rz(1.0*pi) q[2];\n", "rz(1.5*pi) q[3];\n", "h q[2];\n", "h q[3];\n", "rz(1.0*pi) q[2];\n", "rz(1.5*pi) q[3];\n", "h q[2];\n", "h q[3];\n", "cz q[2], q[3];\n", "rz(1.0*pi) q[2];\n", "h q[2];\n", "h q[3];\n", "cz q[2], q[3];\n", "rz(0.5*pi) q[2];\n", "h q[2];\n", "h q[0];\n", "h q[1];\n", "rz(1.0*pi) q[2];\n", "rz(1.5*pi) q[1];\n", "h q[2];\n", "h q[0];\n", "h q[1];\n", "cz q[0], q[1];\n", "rz(1.0*pi) q[2];\n", "h q[2];\n", "h q[0];\n", "h q[1];\n", "rz(0.5*pi) q[2];\n", "rz(0.5*pi) q[3];\n", "rz(1.0*pi) q[0];\n", "rz(0.5*pi) q[1];\n", "h q[2];\n", "h q[3];\n", "h q[0];\n", "h q[1];\n", "cz q[0], q[1];\n", "rz(1.5*pi) q[2];\n", "rz(1.5*pi) q[0];\n", "h q[2];\n", "h q[3];\n", "h q[0];\n", "cz q[0], q[2];\n", "rz(0.5*pi) q[2];\n", "rz(1.5*pi) q[3];\n", "h q[2];\n", "h q[3];\n", "cz q[2], q[3];\n", "h q[2];\n", "h q[2];\n", "h q[1];\n", "h q[0];\n", "cz q[0], q[2];\n", "rz(1.0*pi) q[3];\n", "rz(1.0*pi) q[1];\n", "rz(1.5*pi) q[0];\n", "h q[3];\n", "h q[2];\n", "h q[1];\n", "h q[0];\n", "\n" ] } ], "source": [ "print(c.to_qasm())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Internally the ZX diagram of the circuit is represented as a graph:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Graph(46 vertices, 49 edges)\n", "All edges: [(0, 4), (1, 5), (2, 6), (3, 7), (4, 13), (5, 11), (6, 8), (7, 9), (8, 10), (9, 12), (10, 14), (11, 18), (12, 15), (13, 19), (14, 15), (14, 17), (15, 16), (16, 17), (16, 24), (17, 20), (18, 19), (18, 21), (19, 23), (20, 22), (21, 29), (22, 25), (23, 28), (24, 27), (25, 26), (26, 31), (27, 33), (28, 29), (28, 30), (29, 35), (30, 31), (30, 37), (31, 32), (32, 33), (32, 34), (33, 41), (34, 36), (35, 39), (36, 37), (36, 40), (37, 38), (38, 42), (39, 43), (40, 44), (41, 45)]\n", "\n", "The neighbours of vertex 14: [15, 10, 17]\n" ] } ], "source": [ "print(circ)\n", "print(\"All edges: \", list(circ.edges()))\n", "print(\"\\nThe neighbours of vertex 14: \", list(circ.neighbors(14)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using this graph representation we can use the rules of the ZX-calculus to automatically simplify it:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "g = circ.copy()\n", "zx.clifford_simp(g)\n", "zx.draw_d3(g)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "g.normalize() # Reposition nodes horizontally to look nicer\n", "zx.draw_d3(g)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can get back circuits from the reduced diagram and see its stats:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Circuit on 4 qubits with 26 gates.\n", " 0 is the T-count\n", " 26 Cliffords among which\n", " 13 2-qubit gates (0 CNOT, 13 other) and\n", " 9 Hadamard gates.\n" ] } ], "source": [ "c = zx.extract.extract_circuit(g)\n", "print(c.stats())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## PyZX <-> Qiskit <-> QASM" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We already saw how to go from PyZX to QASM, from this we can also map to a Qiskit circuit:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "OPENQASM 3;\n", "include \"stdgates.inc\";\n", "qubit[4] q;\n", "h q[2];\n", "h q[3];\n", "rz(1.0*pi) q[2];\n", "rz(1.5*pi) q[3];\n", "h q[2];\n", "h q[3];\n", "rz(1.0*pi) q[2];\n", "rz(1.5*pi) q[3];\n", "h q[2];\n", "h q[3];\n", "cz q[2], q[3];\n", "rz(1.0*pi) q[2];\n", "h q[2];\n", "h q[3];\n", "cz q[2], q[3];\n", "rz(0.5*pi) q[2];\n", "h q[2];\n", "h q[0];\n", "h q[1];\n", "rz(1.0*pi) q[2];\n", "rz(1.5*pi) q[1];\n", "h q[2];\n", "h q[0];\n", "h q[1];\n", "cz q[0], q[1];\n", "rz(1.0*pi) q[2];\n", "h q[2];\n", "h q[0];\n", "h q[1];\n", "rz(0.5*pi) q[2];\n", "rz(0.5*pi) q[3];\n", "rz(1.0*pi) q[0];\n", "rz(0.5*pi) q[1];\n", "h q[2];\n", "h q[3];\n", "h q[0];\n", "h q[1];\n", "cz q[0], q[1];\n", "rz(1.5*pi) q[2];\n", "rz(1.5*pi) q[0];\n", "h q[2];\n", "h q[3];\n", "h q[0];\n", "cz q[0], q[2];\n", "rz(0.5*pi) q[2];\n", "rz(1.5*pi) q[3];\n", "h q[2];\n", "h q[3];\n", "cz q[2], q[3];\n", "h q[2];\n", "h q[2];\n", "h q[1];\n", "h q[0];\n", "cz q[0], q[2];\n", "rz(1.0*pi) q[3];\n", "rz(1.0*pi) q[1];\n", "rz(1.5*pi) q[0];\n", "h q[3];\n", "h q[2];\n", "h q[1];\n", "h q[0];\n", "\n" ] } ], "source": [ "import random\n", "qubit_amount = 4\n", "depth = 15\n", "random.seed(1337)\n", "circ = zx.generate.cliffords(qubit_amount, depth)\n", "zx.draw_d3(circ)\n", "c = zx.extract.extract_circuit(circ)\n", "qasm_circ = c.to_qasm(3)\n", "print(qasm_circ)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's the qiskit code you need:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install qiskit_qasm3_import\n", "%pip install pylatexenc" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2025-03-03T22:18:01.701959\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.1, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from qiskit import QuantumCircuit\n", "import qiskit.qasm3\n", "qc = qiskit.qasm3.loads(qasm_circ)\n", "qc.draw(\"mpl\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then you can go back from Qiskit to QASM:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'OPENQASM 3.0;\\ninclude \"stdgates.inc\";\\nqubit[4] q;\\nh q[2];\\nh q[3];\\nrz(pi) q[2];\\nrz(3*pi/2) q[3];\\nh q[2];\\nh q[3];\\nrz(pi) q[2];\\nrz(3*pi/2) q[3];\\nh q[2];\\nh q[3];\\ncz q[2], q[3];\\nrz(pi) q[2];\\nh q[2];\\nh q[3];\\ncz q[2], q[3];\\nrz(pi/2) q[2];\\nh q[2];\\nh q[0];\\nh q[1];\\nrz(pi) q[2];\\nrz(3*pi/2) q[1];\\nh q[2];\\nh q[0];\\nh q[1];\\ncz q[0], q[1];\\nrz(pi) q[2];\\nh q[2];\\nh q[0];\\nh q[1];\\nrz(pi/2) q[2];\\nrz(pi/2) q[3];\\nrz(pi) q[0];\\nrz(pi/2) q[1];\\nh q[2];\\nh q[3];\\nh q[0];\\nh q[1];\\ncz q[0], q[1];\\nrz(3*pi/2) q[2];\\nrz(3*pi/2) q[0];\\nh q[2];\\nh q[3];\\nh q[0];\\ncz q[0], q[2];\\nrz(pi/2) q[2];\\nrz(3*pi/2) q[3];\\nh q[2];\\nh q[3];\\ncz q[2], q[3];\\nh q[2];\\nh q[2];\\nh q[1];\\nh q[0];\\ncz q[0], q[2];\\nrz(pi) q[3];\\nrz(pi) q[1];\\nrz(3*pi/2) q[0];\\nh q[3];\\nh q[2];\\nh q[1];\\nh q[0];\\n'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from qiskit.qasm3 import dumps\n", " \n", "dumps(qc)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Synthesising programs to run on a quantum computer\n", "\n", "Synthesis is the process of taking a high-level description of a program and generating an implementation.\n", "\n", "a. Synthesising a classical program from a matrix\n", "\n", "extra note: Synthesising from arbitrary classical programs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### a. Synthesising a classical program (matrices)\n", "\n", "If we start with a description of a classical program, how can we get a quantum program that does the same thing?\n", "\n", "If I want to use the quantum program as a subroutine in a *unitary* quantum program, the implementation of the quantum program also has to be *unitary* and therefore *reversible*. There are algorithms for generating these include Bennet's trick. \n", "\n", "The method that Qiskit employs for this is from matrices, and PyZX can also synthesise ZX-diagrams from arbitrary matrices.\n", "\n", "As it is a bit of a pain to actually make a matrix for some quantum algorithm, we will just work with deriving one for a classical program for now." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will consider the 8-bit adder for this exercise. \n", "\n", "The 8-bit adder takes two registers of 8-bits, a and b, and returns a+b on 8 bits and c, the carry.\n", "\n", "Either implement this yourself by building the classical circuit (hint for implementation of a 2-bit full adder: https://www.quantum-inspire.com/kbase/full-adder/) or by using Qiskit's inbuilt adders and getting the matrix from them. (https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.FullAdderGate)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "def get_matrix_adder_8_bits():\n", " my_matrix = ...\n", " return my_matrix\n", "\n", "'''\n", " You might want to use the below code to get the unitary matrix from a circuit. You need to pip install qiskit-aer for this to work.\n", "'''\n", "\n", "from qiskit.circuit.quantumcircuit import QuantumCircuit\n", "import numpy as np\n", "from qiskit import QuantumCircuit, transpile\n", "from qiskit.circuit.library import QFT, SGate, SXGate, U1Gate, UnitaryGate, XGate, ZGate, u1 #Here's some example of qiskit gates you might want\n", "from qiskit.circuit.library.generalized_gates import unitary\n", "from qiskit_aer import AerSimulator\n", "import qiskit.quantum_info as qi\n", "\n", "\n", "def print_unitary(circ):\n", " simulator = AerSimulator(method = 'unitary')\n", " circ = transpile(circ, simulator)\n", " circ.save_unitary()\n", " #job execution and getting the result as an object\n", " result = simulator.run(circ).result()\n", " print(circ)\n", " unitary = result.get_unitary(circ, decimals=3)\n", " #get the unitary matrix from the result object\n", " print(\"Circuit unitary:\\n\", np.real_if_close(np.asarray(unitary).round(5)))\n", " return unitary" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install qiskit-aer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use qiskit synthesis (https://docs.quantum.ibm.com/api/qiskit/synthesis) and PyZX synthesis (https://pyzx.readthedocs.io/en/latest/api.html#pyzx.linalg.Mat2) to generate both a ZX-diagram and a quantum circuit from your matrix. \n", "\n", "Then, look at their circuit implementations. How do these compare to each other and the original circuit you started with?" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "def matrix_to_pyzx():\n", " # Your code here\n", " ...\n", "\n", "def matrix_to_qiskit():\n", " # Your code here\n", " ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Side note on turning classical programs into quantum programs\n", "\n", "There are some automatic tools for generating quantum programs from classical programs - that take code and produce quantum algorithms. Some examples are [ReverC](https://github.com/msr-quarc/ReVerC) and a tool in the language [Quipper](https://www.mathstat.dal.ca/~selinger/quipper/doc/Quipper-Internal-Classical.html) (which we will see later in the course)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Optimisation Strategies\n", "\n", "- a. Circuit optimisation\n", "\n", "- b. Targeting real quantum hardware" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### a. Circuit Optimisation\n", "\n", "Circuit optimisation is the process of producing an equivalent quantum circuit (i.e. one that implements the same unitary evolution) that typically satisfies at least one of the following:\n", "\n", "- has fewer gates of a relevant kind (some gates are more costly than others, particularly, T-gates or two-qubits gates like CNOTs),\n", "- has a shorter maximum sequence of consecutive gates (known as depth-reduction),\n", "- uses less qubits\n", "\n", "\n", "[*This section is a modified version of the one available at https://github.com/Quantomatic/pyzx, created by Aleks Kissinger and John van de Wetering and incorporates changes made by Pablo Andrés-Martínez and Robert I. Booth.*]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try optimising a Clifford+T circuit (meaning it may include $\\pi/4$ phases)." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Circuit on 6 qubits with 162 gates.\n", " 10 is the T-count\n", " 152 Cliffords among which\n", " 16 2-qubit gates (0 CNOT, 16 other) and\n", " 92 Hadamard gates.\n" ] } ], "source": [ "qubit_amount = 6\n", "depth = 70\n", "random.seed(1338)\n", "circ = zx.generate.cliffordT(qubit_amount, depth,p_t=0.2)\n", "zx.draw_d3(circ)\n", "c = zx.extract.extract_circuit(circ)\n", "print(c.stats())\n", "#N.B. zx.extract empties the circuit!" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2025-03-03T22:18:18.903317\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.1, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "circ = zx.generate.cliffordT(qubit_amount, depth,p_t=0.2)\n", "\n", "g = circ.copy()\n", "zx.clifford_simp(g, quiet=True)\n", "g.normalize()\n", "zx.draw_matplotlib(g)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once we have the reduced diagram, we ask PyZX to extract the circuit:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Circuit on 6 qubits with 72 gates.\n", " 10 is the T-count\n", " 62 Cliffords among which\n", " 26 2-qubit gates (0 CNOT, 26 other) and\n", " 26 Hadamard gates.\n" ] } ], "source": [ "g2 = g.copy()\n", "c = zx.extract.extract_circuit(g2)\n", "print(c.stats())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now convert it back to a PyZX graph:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2025-03-03T22:18:23.068848\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.1, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "zx.draw_matplotlib(c.to_graph())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And verify that it is still equal to the original graph:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "zx.compare_tensors(c.to_tensor(), circ.to_tensor(),preserve_scalar=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, lets look at this circuit in the QASM circuit description language:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "OPENQASM 2.0;\n", "include \"qelib1.inc\";\n", "qreg q[6];\n", "cx q[1], q[3];\n", "cx q[3], q[1];\n", "cx q[1], q[3];\n", "h q[5];\n", "h q[3];\n", "h q[2];\n", "h q[1];\n", "h q[0];\n", "h q[4];\n", "h q[3];\n", "h q[1];\n", "cz q[0], q[4];\n", "cz q[0], q[3];\n", "cz q[0], q[1];\n", "rz(0.5*pi) q[2];\n", "rz(1.5*pi) q[0];\n", "h q[2];\n", "h q[5];\n", "h q[0];\n", "cz q[4], q[5];\n", "cz q[2], q[5];\n", "cz q[0], q[2];\n", "rz(0.75*pi) q[4];\n", "rz(0.25*pi) q[2];\n", "h q[4];\n", "h q[2];\n", "cz q[3], q[4];\n", "cz q[2], q[3];\n", "cz q[1], q[3];\n", "rz(1.5*pi) q[3];\n", "h q[3];\n", "cz q[1], q[4];\n", "cz q[1], q[3];\n", "cz q[0], q[1];\n", "rz(1.0*pi) q[1];\n", "h q[1];\n", "cz q[4], q[5];\n", "cz q[2], q[5];\n", "cz q[1], q[5];\n", "cz q[1], q[4];\n", "cz q[1], q[3];\n", "rz(1.0*pi) q[5];\n", "rz(1.75*pi) q[0];\n", "rz(1.25*pi) q[4];\n", "rz(1.75*pi) q[1];\n", "h q[5];\n", "h q[0];\n", "h q[4];\n", "h q[1];\n", "cz q[2], q[4];\n", "cz q[0], q[1];\n", "rz(0.75*pi) q[5];\n", "rz(1.75*pi) q[3];\n", "rz(1.25*pi) q[2];\n", "rz(1.75*pi) q[0];\n", "h q[5];\n", "h q[3];\n", "h q[2];\n", "h q[0];\n", "cz q[3], q[5];\n", "cz q[1], q[4];\n", "cz q[1], q[2];\n", "cz q[0], q[4];\n", "cz q[0], q[2];\n", "cz q[0], q[1];\n", "rz(0.5*pi) q[5];\n", "h q[5];\n", "rz(1.0*pi) q[4];\n", "h q[4];\n", "rz(0.5*pi) q[3];\n", "rz(0.5*pi) q[2];\n", "h q[2];\n", "rz(1.25*pi) q[1];\n", "rz(1.5*pi) q[0];\n", "\n" ] } ], "source": [ "print(c.to_basic_gates().to_qasm())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Questions\n", "\n", "How does this new circuit compare to the original circuit? What is better? What is worse?\n", "\n", "What happened to the T-gates? What happened to the two-qubit gates?\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For some circuits, extract_circuit can result in quite large circuits involving many CNOT gates. If one is only interested in optimizing the T-count of a circuit, the extraction stage can be skipped by using the phase-teleportation method of [this paper](https://arxiv.org/abs/1903.10477). This applies full_reduce in such a way that only phases are moved around the circuit, and all other structure remains intact:\n", "\n", "What happens if you use this? " ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "g = c.to_graph()\n", "zx.teleport_reduce(g)\n", "c_opt = zx.Circuit.from_graph(g) # This function is able to reconstruct a Circuit from a Graph that looks sufficiently like a Circuit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Qiskit optimisation is now only supported alongside their transpiler - to do this next bit do part b) first.\n", "\n", "Try optimizing the corresponding Qiskit circuit. What do you see?\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Extensions:\n", "\n", "##### 1. Other programs\n", "\n", "Try optimising some of the other programs in this tutorial - what happens?\n", "\n", "##### 2. Individual ZX rewrites\n", "\n", "Try using some of the ZX [simplifications](https://pyzx.readthedocs.io/en/latest/api.html#list-of-simplifications) defined in PyZX.\n", "\n", "Below is some code that will help you apply some ZX rules you saw in class." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import widgets\n", "from IPython.display import display, Markdown\n", "\n", "import pyzx.rules as r\n", "\n", "class Steps:\n", " def __init__(self,g):\n", " self.current = g.copy()\n", " self.stepList = [zx.draw_matplotlib(self.current,h_edge_draw='box')]\n", " self.stepNames = ['start']\n", " \n", " def applyXTimes(self,name,match,rule,x):\n", " for i in range(x):\n", " self.applyOnce(name,match,rule)\n", " \n", " def applyOnce(self,name,match,rule):\n", " m = match(self.current)\n", " if len(m) > 0:\n", " zx.rules.apply_rule(self.current,rule,[m[0]])\n", " self.stepList.append(zx.draw_matplotlib(self.current,h_edge_draw='box'))\n", " self.stepNames.append(name)\n", " \n", " def to_gh(self):\n", " zx.to_gh(self.current)\n", " self.stepList.append(zx.draw_matplotlib(self.current,h_edge_draw='box'))\n", " self.stepNames.append('to_gh')\n", " \n", " def plotter(self,rewrite):\n", " display(self.stepList[rewrite])\n", " display(Markdown(\"**Rewrite step**: \" + self.stepNames[rewrite]))\n", " \n", " def drawSteps(self):\n", " self.current.normalize()\n", " self.stepList.append(zx.draw_matplotlib(self.current))\n", " self.stepNames.append('rearrange')\n", " w = widgets.interactive(self.plotter, rewrite=(0,len(self.stepList)-1))\n", " slider = w.children[0]\n", " slider.value = 0\n", " return w" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Try using the function `applyXTimes` to apply ZX rules on the diagram above. You can apply some combination of the rules listed below, possibly interleaving them:\n", "- removal of identities (phases with angle 0) `s.applyXTimes('id', r.match_ids, r.remove_ids, x)`,\n", "- spider contraction `s.applyXTimes('spider', r.match_spider, r.spider, x)`;\n", "- local complementation `s.applyXTimes('lcomp', r.match_lcomp, r.lcomp, x)`;\n", "- pivoting `s.applyXTimes('pivot', r.match_pivot, r.pivot, x)`.\n", "\n", "Argument `x` indicates how many consecutive times the same rule should be applied. Write your code in the snippet below. The last line on the snippet must be `display(s.drawSteps())`, which will display the derivation you've come up with. A sliding bar should allow you to scroll over the different steps of your derivation (this requires that you have succesfully enabled ipywidgets)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s = Steps(circ) #Create an instance of the Steps class we just defined\n", "\n", "\n", "### APPLY THE FIRST ROUND OF RULES HERE ###\n", "s.applyXTimes('spider', r.match_spider, r.spider, 2)\n", "\n", "\n", "\n", "\n", "s.to_gh() #Convert all X-phases (red nodes) into Z-phases surrounded by H-boxes. This is a necessary step.\n", "\n", "### APPLY THE SECOND ROUND OF RULES HERE ###\n", "s.applyXTimes('spider', r.match_spider, r.spider, 3)\n", "\n", "\n", "\n", "\n", "display(s.drawSteps())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### b. Targeting real quantum hardware\n", "\n", "To actually *run* a quantum program, the hardware it is going to be run on must be taken into account. The process of taking an existing program and translating it to be expressed in another set of primitives is called transpilation.\n", "\n", "Real quantum computers each have a 'native gate set', this is a set of one and two qubit operations that they can actually apply. In fact, most quantum computers can't implement CX gates in one step! To run any quantum circuit or ZX-diagram on a quantum computer, we must transpile it into a form that is amenable to the hardware we want to target.\n", "\n", "Furthermore, a real quantum computer can only apply two-qubit gates on qubits that are physically next to each other (or be can be moved next to each other). Typically, this is referred to as routing.\n", "\n", "In this exercise we will use the Deutsch-Josza algorithm that we saw in the lecture notes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First write a Qiskit or QASM implementation of the Deutsch-Josza algorithm for n-qubits." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "def Deutsch_Josza(num_qubits:int):\n", "\n", " return ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now target our program to a specific quantum computer. You can choose to try this for any of the following examples." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Qiskit\n", "\n", "Qiskit provides a class called [Target](https://quantum.cloud.ibm.com/docs/en/api/qiskit/qiskit.transpiler.Target) which is used to describe specific quantum computers. \n", "\n", "As Qiskit is built and maintained by IBM, you can use a [FakeProvider](https://docs.quantum.ibm.com/api/qiskit-ibm-runtime/fake-provider) that is similar to one of their backends - or even a real one (though this requires an IBMQuantum account).\n", "\n", "Have a go modifying and extending the code below to transpile your Deutsch-Josza implementation into a circuit that can be run on a `fake-provider`.\n", "\n", "What happens if you try it on a different backend? What if you change some of the transpilation settings? \n", "\n", "Try changing some of the additional arguments to `transpile`. Look [here](https://docs.quantum.ibm.com/api/qiskit/0.27/qiskit.compiler.transpile) for some options. You might want to try changing the `routing_method` or `approximation_degree`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install qiskit_ibm_runtime" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from qiskit import QuantumCircuit\n", "from qiskit import transpile\n", "from qiskit.visualization import plot_histogram\n", "from qiskit_ibm_runtime import SamplerV2\n", "from qiskit_ibm_runtime.fake_provider import FakeManilaV2\n", " \n", "# Get a fake backend from the fake provider\n", "backend = FakeManilaV2()\n", "\n", "circuit = Deutsch_Josza(5)\n", "# Remember to change this into a Qiskit Circuit if you used QASM for the previous step\n", "\n", "# Transpile the ideal circuit to a circuit that can be\n", "# directly executed by the backend\n", "transpiled_circuit = transpile(circuit, backend,) # Add options here\n", "transpiled_circuit.draw('mpl', style=\"iqp\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also use the above code to define your own target. Can you try it for the case where you are using 4 qubits, have the gate set {CZ, H, T} and your qubit connectivity graph is:\n", "\n", "\"drawing\"\n", "\n", "(Here dots are qubits, and lines are connections.)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## PyZX \n", "\n", "The `extract_circuit` function we saw earlier does not take into account the architecture that the resulting circuit will be run on. PyZX provides functionality to do so by allowing you to define an `Architecture`.\n", "\n", "This is called [architecture aware qubit routing](https://pyzx.readthedocs.io/en/latest/simplify.html#architecture-aware-circuit-routing).\n", "\n", "First, we define the architecture we want to target. The function `create_architecture` can be used to create a number of pre-defined `Architecture` objects.:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pyzx.routing import architecture\n", "\n", "# Create a 9-qubit square grid architecture\n", "grid_arch = architecture.create_architecture(architecture.SQUARE, **{\"n_qubits\":9})\n", "# Note these are implemented using **kwargs, so extra arguments need to be passed as a dictionary\n", "\n", "# Create a IBM qx5 architecture\n", "ibm_arch = architecture.create_architecture(architecture.IBM_QX5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Try defining the same architecture as the one you used above!\n", "\n", "(documentation: https://pyzx.readthedocs.io/en/latest/api.html#pyzx.routing.create_architecture)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "PyZX can apply routing to phase-polynomial circuits. These circuits are composed of CNOT, XCX, and ZPhase gates, which can be directly translated to phase gadgets in ZX. The module `generate` provides a function for creating such circuits to an architecture." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "c_pp = zx.generate.phase_poly(n_qubits=16, n_phase_layers=10, cnots_per_layer=10)\n", "routed_circuit = zx.routing.route_phase_poly(c_pp, ibm_arch)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Extension\n", "\n", "PyZX does not provide functionality to convert circuits into phase-polynomial circuits.\n", "\n", "Either try generating a random phase-polynomial circuit and compare its PyZX routing to its Qiskit routing, or use Qiskit to transpile your circuit into a phase-polynomial circuit and then use PyZX." ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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.13.1" } }, "nbformat": 4, "nbformat_minor": 2 }