[404218]: / Code / All Qiskit, PennyLane QML Nov 23 / 23a Unitary Designs Fid. +.99 kkawchak.ipynb

Download this file

1019 lines (1018 with data), 42.1 kB

{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "id": "_mDT6fzqwQlp"
      },
      "outputs": [],
      "source": [
        "# This cell is added by sphinx-gallery\n",
        "# It can be customized to whatever you like\n",
        "%matplotlib inline\n",
        "# !pip install pennylane"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HmioP9IlwQlq"
      },
      "source": [
        "Unitary designs\n",
        "===============\n",
        "\n",
        "::: {.meta}\n",
        ":property=\\\"og:description\\\": Learn about designs and their uses in\n",
        "quantum computing.\n",
        "\n",
        ":property=\\\"og:image\\\": <https://pennylane.ai/qml/_images/fano.png>\n",
        ":::\n",
        "\n",
        "::: {.related}\n",
        "tutorial\\_haar\\_measure Understanding the Haar measure\n",
        ":::\n",
        "\n",
        "*Author: Olivia Di Matteo --- Posted: 07 September 2021. Last updated:\n",
        "07 September 2021.*\n",
        "\n",
        "::: {.note}\n",
        "::: {.title}\n",
        "Note\n",
        ":::\n",
        "\n",
        "This demo is intended to be a sequel to the\n",
        "`demo about the Haar measure </demos/tutorial_haar_measure>`{.interpreted-text\n",
        "role=\"doc\"}. If you are not familiar with the Haar measure, we recommend\n",
        "going through that demo first before exploring this one.\n",
        ":::\n",
        "\n",
        "Take a close look at the following mathematical object:\n",
        "\n",
        "![](/demonstrations/unitary_designs/fano_no_labels.svg){.align-center\n",
        "width=\"30.0%\"}\n",
        "\n",
        "|\n",
        "\n",
        "There are many things we can say about it: it consists of seven points\n",
        "and seven lines (the circle counts as a line); each line contains three\n",
        "points, and each point is contained in three lines. Furthermore, any\n",
        "pair of points occur together in exactly one line. This object, called\n",
        "the [Fano plane](https://en.wikipedia.org/wiki/Fano_plane), is an\n",
        "instance of a mathematical structure called a [projective\n",
        "plane](https://en.wikipedia.org/wiki/Projective_plane), which is just\n",
        "one example of a [combinatorial\n",
        "design](https://en.wikipedia.org/wiki/Combinatorial_design). Designs are\n",
        "sets of objects, and groups of those objects, that satisfy certain\n",
        "balance properties and symmetries. They have been studied for hundreds\n",
        "of years in a huge variety of contexts, from [error correcting\n",
        "codes](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.50.5465),\n",
        "to [card\n",
        "games](https://homepages.warwick.ac.uk/staff/D.Maclagan/papers/set.pdf),\n",
        "and even\n",
        "[agriculture](http://www-groups.mcs.st-and.ac.uk/~rab/histLShand.pdf).\n",
        "So, what about quantum computing?\n",
        "\n",
        "Designs are actually quite prevalent in quantum computing. You\\'ve\n",
        "almost certainly come across one before, though you may not have\n",
        "realized it. At the end of the Haar measure demo, we asked a very\n",
        "important question: \\\"do we always *need* to sample from the full Haar\n",
        "measure?\\\". The answer to this is \\\"no\\\", and the reasoning lies in the\n",
        "study of *unitary designs*.\n",
        "\n",
        "In this demo, you\\'ll learn the definition of $t$-designs, what it means\n",
        "to generalize them to unitary $t$-designs, and you\\'ll see some\n",
        "canonical examples of designs in quantum computing. You\\'ll also learn\n",
        "about their connection with the Haar measure, what it means to *twirl* a\n",
        "quantum channel, and explore how to leverage 2-designs in PennyLane to\n",
        "compute the average fidelity of a noisy channel. You will experience\n",
        "directly a situation where we can use a $t$-design as a shortcut over\n",
        "the full Haar measure to greatly improve the efficiency of a task 🎉.\n",
        "\n",
        "From spheres to unitary $t$-designs\n",
        "-----------------------------------\n",
        "\n",
        "### Spherical designs\n",
        "\n",
        "Before diving into unitary designs, let\\'s look at the sphere for some\n",
        "intuition. Suppose we have a polynomial in $d$ variables, and we would\n",
        "like to compute its average over the surface of a real, $d$-dimensional\n",
        "unit sphere, $S(R^d)$. We can do so by integrating that function over\n",
        "the sphere (using the proper measure), but that would be a lot of\n",
        "parameters to keep track of.\n",
        "\n",
        "One could alternatively approximate the average by sampling thousands of\n",
        "points uniformly at random on the sphere, evaluating the function at\n",
        "those points, and computing their average value. That will always work,\n",
        "and it will get us close, but it will not be exact.\n",
        "\n",
        "In fact, both of those approaches may be overkill in some special\n",
        "cases---if the terms in the polynomial have the same degree of at most\n",
        "$t$, you can compute the average **exactly** over the sphere using only\n",
        "a small set of points rather than integrating over the entire sphere.\n",
        "That set of points is called a spherical $t$-design. More formally,:\n",
        "\n",
        "::: {.admonition .defn}\n",
        "Definition\n",
        "\n",
        "Let $p_t: \\mathcal{S}(R^d)\\rightarrow R$ be a polynomial in $d$\n",
        "variables, with all terms homogeneous in degree at most $t$. A set\n",
        "$X = \\{x: x \\in \\mathcal{S}(R^d)\\}$ is a spherical $t$-design if\n",
        "\n",
        "$$\\frac{1}{|X|} \\sum_{x \\in X} p_t(x) = \\int_{\\mathcal{S}(R^d)} p_t (u) d\\mu(u)$$\n",
        "\n",
        "holds for all possible $p_t$, where $d\\mu$ is the uniform, normalized\n",
        "spherical measure. A spherical $t$-design is also a $k$-design for all\n",
        "$k < t$.\n",
        ":::\n",
        "\n",
        "Now this is a pretty abstract picture, so let\\'s consider the\n",
        "3-dimensional sphere. This definition tells us that if we want to take\n",
        "the average of a polynomial over a sphere where all terms have the same\n",
        "degree of at most 2, we can do so using a small, representative set of\n",
        "points called a 2-design, rather than the whole sphere. Similarly, if\n",
        "all terms of the polynomial have the same degree of at most 3, we could\n",
        "use a 3-design.\n",
        "\n",
        "But what are these representative sets of points? Since we are using\n",
        "these points as a stand-in for averaging over the whole sphere, we\\'d\n",
        "want the points in the set to be distributed in a way that provides\n",
        "sufficient \\\"coverage\\\". In the 3-dimensional case, the vertices of some\n",
        "familiar solids form $t$-designs ,:\n",
        "\n",
        "![](/demonstrations/unitary_designs/shapes.svg){.align-center\n",
        "width=\"80.0%\"}\n",
        "\n",
        "|\n",
        "\n",
        "We see from these illustrations that spherical designs are sets of\n",
        "*evenly-spaced points*. As $t$ increases, the configurations become\n",
        "increasingly sphere-like. Looking at this in a different way, the more\n",
        "complex a function becomes as its degree increases, the closer the\n",
        "$t$-design must be to a sphere; we need to evaluate the function at more\n",
        "points in order to gain sufficient information when a function is\n",
        "varying more quickly due to a higher degree. In 3 dimensions, we can\n",
        "compute the average of a polynomial with degree 2 by evaluating it only\n",
        "at the points of a tetrahedron, despite the fact that it doesn\\'t look\n",
        "spherical at all. More complex functions require more points and thus\n",
        "more intricate configurations for the design. Spherical designs exist\n",
        "for all $t$ and dimension $d$. They are not always unique, and may have\n",
        "varying numbers of points.\n",
        "\n",
        "To show that this really works, let\\'s look at an explicit example.\n",
        "Consider the following polynomial in 3 variables:\n",
        "\n",
        "$$f(x, y, z) = x^4 - 4 x^3 y + y^2 z^2$$\n",
        "\n",
        "We can compute the average value of $f$ by integrating over a unit\n",
        "sphere: the result is $4/15 \\approx 0.26667$. However, this integral is\n",
        "non-trivial to evaluate by hand; the most straightforward way is to\n",
        "convert to polar coordinates, and even then, it involves integrating\n",
        "functions with 4th and 5th powers of trigonometric functions.\n",
        "\n",
        "Instead, this is a case where we can leverage the fact that all terms in\n",
        "the polynomial have degree 4, and compute the average exactly using only\n",
        "a subset of points that form a 4-design. We choose a dodecahedron for\n",
        "convenience; while this is actually a 5 design, it also forms a\n",
        "4-design, and is a more familiar shape than the 4-design depicted above.\n",
        "\n",
        "First, we define the set of points that comprise a dodecahedron:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {
        "id": "g_cdAioPwQlu"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "\n",
        "# The golden ratio\n",
        "g = (1 + np.sqrt(5)) / 2\n",
        "\n",
        "# A dodecahedron has 20 points\n",
        "dodecahedron = np.array([\n",
        "    # 8 of them form a cube within the sphere\n",
        "    [1, 1, 1], [1, 1, -1], [1, -1, 1], [1, -1, -1],\n",
        "    [-1, 1, 1], [-1, 1, -1], [-1, -1, 1], [-1, -1, -1],\n",
        "\n",
        "    # 4 of them form a rectangle within the y-z plane\n",
        "    [0, g, 1/g], [0, g, -1/g], [0, -g, 1/g], [0, -g, -1/g],\n",
        "\n",
        "    # 4 of them form a rectangle within the x-z plane\n",
        "    [1/g, 0, g], [1/g, 0, -g], [-1/g, 0, g], [-1/g, 0, -g],\n",
        "\n",
        "    # 4 of them form a rectangle within the x-y plane\n",
        "    [g, 1/g, 0],[g, -1/g, 0], [-g, 1/g, 0], [-g, -1/g, 0],\n",
        "])\n",
        "\n",
        "# Normalize the points so they all fit in the unit sphere\n",
        "dodecahedron = np.array(\n",
        "   [point / np.linalg.norm(point) for point in dodecahedron]\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PBMbC8iewQlv"
      },
      "source": [
        "Now we define our function and compute the average over the\n",
        "dodecahedron:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "id": "ErTDyta4wQlv",
        "outputId": "671a8656-0194-45a8-b499-5ab17c83a7c5"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "0.2666666666666668\n"
          ]
        }
      ],
      "source": [
        "def f(x, y, z):\n",
        "    return (x ** 4) - 4 * (x ** 3) * y +  y ** 2 * z ** 2\n",
        "\n",
        "dodeca_average = np.mean([f(*point) for point in dodecahedron])\n",
        "print(dodeca_average)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "czP6zKXlwQlw"
      },
      "source": [
        "This is exactly the value we expect. What happens if we try to do this\n",
        "using only a 3-design, the cube?\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "id": "vlX-usmgwQlw",
        "outputId": "d68333f6-c85a-49e0-eada-02d3311b142c"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "0.22222222222222235\n"
          ]
        }
      ],
      "source": [
        "# The first 8 points of the dodecahedron are a cube\n",
        "cube = dodecahedron[:8]\n",
        "\n",
        "cube_average = np.mean([f(*point) for point in cube])\n",
        "print(cube_average)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lHNtu3-GwQlx"
      },
      "source": [
        "This clearly differs from the true value. We need a design with $t=4$ or\n",
        "better in order to compute this average, and when such a design is\n",
        "available, we may save significant computational time.\n",
        "\n",
        "Unitary designs\n",
        "===============\n",
        "\n",
        "We\\'ve learned now that spherical designs are sets of evenly-spaced\n",
        "points, and saw how they can be used as a shortcut to evaluate the\n",
        "average of a polynomial up to a given degree $t$. However, there was\n",
        "nothing quantum about this; there weren\\'t even any complex numbers\n",
        "involved. A *unitary design* extends this concept from\n",
        "evenly-distributed points to evenly-distributed unitaries. More\n",
        "formally, instead of averaging polynomials over spheres, we consider\n",
        "polynomials that are functions of the entries of unitary matrices,.\n",
        "\n",
        "::: {.admonition .defn}\n",
        "Definition\n",
        "\n",
        "Let $P_{t,t}(U)$ be a polynomial with homogeneous degree at most $t$ in\n",
        "$d$ variables in the entries of a unitary matrix $U$, and degree $t$ in\n",
        "the complex conjugates of those entries. A unitary $t$-design is a set\n",
        "of $K$ unitaries $\\{U_k\\}$ such that\n",
        "\n",
        "$$\\frac{1}{K} \\sum_{k=1}^{K} P_{t,t}(U_k) = \\int_{\\mathcal{U}(d)}\n",
        "P_{t,t} (U) d\\mu(U)$$\n",
        "\n",
        "holds for all possible $P_{t,t}$, and where $d\\mu$ is the uniform *Haar\n",
        "measure*.\n",
        ":::\n",
        "\n",
        "We stress again that this expression is **exact**. The unitaries in a\n",
        "unitary design are a representative set of points that are \\\"evenly\n",
        "spaced\\\" across the unitary group. With just a subset of the full group,\n",
        "we can evaluate complex expressions that would be otherwise intractable.\n",
        "\n",
        "A surprising result about unitary designs is that they exist for all\n",
        "possible combinations of $t$ and $d$. There are some known lower bounds\n",
        "for the number of unitaries required; for example, a 2-design in\n",
        "dimension $d$ has at least $d^4 - 2d^2 + 2$ elements, . However,\n",
        "actually finding the sets (and in particular, finding ones with minimal\n",
        "size), is a challenging problem, though very recently some constructions\n",
        "have been put forward.\n",
        "\n",
        "::: {.admonition}\n",
        "Fun fact\n",
        "\n",
        "Applying the elements of a unitary design to a fixed pure state produces\n",
        "a set of vectors that form a *complex projective design*. These are much\n",
        "like spherical designs, but they live in a complex vector space. If\n",
        "you\\'ve ever studied the characterization of quantum systems, you may\n",
        "have come across some special sets of measurements called mutually\n",
        "unbiased bases (MUBs), or symmetric, informationally complete positive\n",
        "operator valued measurements (SIC-POVMs). Both of these sets of vectors\n",
        "are complex projective 2-designs.\n",
        "\n",
        "![The vectors of the simplest SIC-POVM in dimension 2, plotted on a\n",
        "Bloch\n",
        "sphere.](/demonstrations/unitary_designs/sic-povm.svg){.align-center\n",
        "width=\"80.0%\"}\n",
        ":::\n",
        "\n",
        "Unitary $t$-designs in action\n",
        "-----------------------------\n",
        "\n",
        "Unitary designs come into play in applications that require\n",
        "randomization, or sampling of random unitaries---essentially, they can\n",
        "be used as a stand-in for the Haar measure. The way in which the\n",
        "unitaries are used in the application may place restrictions on the\n",
        "value of $t$ that is required; arguably the most common is the unitary\n",
        "2-design.\n",
        "\n",
        "While in general unitary designs are hard to construct, there are well\n",
        "known results for unitary 1-, 2-, and 3-designs based on familiar\n",
        "objects in quantum computing. Before we see what those are, let\\'s\n",
        "explore an important use case.\n",
        "\n",
        "Average fidelity\n",
        "================\n",
        "\n",
        "A key application of unitary 2-designs is benchmarking quantum\n",
        "operations. Suppose we have a noisy quantum channel $\\Lambda$ that\n",
        "should perform something close to the unitary operation $V$. What can we\n",
        "say about the performance of this channel?\n",
        "\n",
        "One metric of interest is the *fidelity*. Consider the state\n",
        "$|0\\rangle$. In an ideal case, we apply $V$ and obtain $V|0\\rangle$. But\n",
        "applying the channel $\\Lambda$ gives us something a little different.\n",
        "Since it\\'s noisy, we must consider the state as a density matrix. The\n",
        "action of $\\Lambda$ on our starting state is\n",
        "$\\Lambda(|0\\rangle \\langle 0|)$. If $\\Lambda$ was perfect, then\n",
        "$\\Lambda(|0\\rangle \\langle 0|) = V|0\\rangle \\langle\n",
        "0|V^\\dagger$, and the fidelity is\n",
        "\n",
        "$$F(\\Lambda, V) = \\langle 0 | V^\\dagger \\cdot \\Lambda(|0\\rangle \\langle 0|) \\cdot V|0\\rangle = 1.$$\n",
        "\n",
        "In reality, $\\Lambda$ is not going to implement $V$ perfectly, and\n",
        "$F < 1$. More importantly though, all we\\'ve computed so far is the\n",
        "fidelity when the initial state is $|0\\rangle$. What if the initial\n",
        "state is something different? What is the fidelity *on average*?\n",
        "\n",
        "To compute an average fidelity, we must do so with respect to the full\n",
        "set of Haar-random states. We usually generate random states by applying\n",
        "a Haar-random unitary $U$ to $|0\\rangle$. Thus to compute the average\n",
        "over all such $U$ we must evaluate\n",
        "\n",
        "$$\\bar{F}(\\Lambda, V) = \\int_{\\mathcal{U}} d\\mu(U) \\langle 0 | U^\\dagger V^\\dagger \\Lambda(U |0\\rangle \\langle 0| U^\\dagger) V U |0\\rangle.$$\n",
        "\n",
        "This is known as *twirling* the channel $\\Lambda$. Computing the average\n",
        "fidelity in this way would be a nightmare---we\\'d have to compute the\n",
        "fidelity with respect to an infinite number of states!\n",
        "\n",
        "However, consider the expression in the integral above. We have an inner\n",
        "product involving two instances of $U$, and two instances of\n",
        "$U^\\dagger$. This means that the expression is a polynomial of degree 2\n",
        "in both the elements of $U$ and its complex conjugates---this matches\n",
        "exactly the definition of a unitary 2-design. This means that if we can\n",
        "find a set of $K$ unitaries that form a 2-design, we can compute the\n",
        "average fidelity using only a finite set of initial states:\n",
        "\n",
        "$$\\frac{1}{K} \\sum_{j=1}^K \\langle 0 | U_j^\\dagger V^\\dagger \\Lambda(U_j |0\\rangle \\langle 0|\n",
        "U_j^\\dagger) V^\\dagger U_j |0\\rangle = \\int_{\\mathcal{U}} d\\mu(U) \\langle 0\n",
        "| U^\\dagger V^\\dagger \\Lambda(U |0\\rangle \\langle 0| U^\\dagger) V U |0\\rangle$$\n",
        "\n",
        "This is great, but a question remains: what is the representative set of\n",
        "unitaries?\n",
        "\n",
        "The Clifford group\n",
        "==================\n",
        "\n",
        "A beautiful result in quantum computing is that some special groups you\n",
        "may already be familiar with are unitary designs:\n",
        "\n",
        "-   the Pauli group forms a unitary 1-design, and\n",
        "-   the Clifford group forms a unitary 3-design.\n",
        "\n",
        "By the definition of designs, this means the Clifford group is also a\n",
        "1-and 2-design.\n",
        "\n",
        "The $n$-qubit Pauli group, $\\mathcal{P}(n)$, is the set of all tensor\n",
        "products of Pauli operations $X$, $Y$, $Z$, and $I$. The $n$-qubit\n",
        "Clifford group, $\\mathcal{C}(n)$, is the *normalizer* of the Pauli\n",
        "group. In simpler terms, the Clifford group is the set of operations\n",
        "that send Paulis to Paulis (up to a phase) under conjugation i.e.,\n",
        "\n",
        "$$C P C^\\dagger = \\pm P^\\prime, \\quad \\forall P, P^\\prime \\in \\mathcal{P}(n), \\quad C \\in \\mathcal{C}(n).$$\n",
        "\n",
        "The Clifford group has some profoundly interesting properties and\n",
        "countless uses across quantum computing, from circuit compilation to\n",
        "error correcting codes. For a single qubit, the group is built from just\n",
        "two operations. One is the Hadamard:\n",
        "\n",
        "$$H X H^\\dagger = Z, \\quad H Y H^\\dagger = -Y, \\quad H Z H^\\dagger = X.$$\n",
        "\n",
        "This clearly maps Paulis to Paulis (up to a phase). The other is the\n",
        "phase gate $S$:\n",
        "\n",
        "$$S X S^\\dagger = Y, \\quad S Y S^\\dagger = -X, \\quad S Z S^\\dagger = Z.$$\n",
        "\n",
        "If both $H$ and $S$ map Paulis to Paulis, then products of them do as\n",
        "well. In group theory terms, the single-qubit Clifford group is\n",
        "generated by $H$ and $S$. For example, consider the action of $HS$:\n",
        "\n",
        "$$(HS) X (HS)^\\dagger = -Y, \\quad (HS) Y (HS)^\\dagger = -Z, \\quad (HS) Z (HS)^\\dagger = X.$$\n",
        "\n",
        "Since $Y = iXZ$, it is enough to specify Clifford operations by how they\n",
        "act on $X$ and $Z$. For a particular Clifford, there are 6 possible ways\n",
        "it can transform $X$, namely $\\pm X, \\pm Y$, or $\\pm Z$. Once that is\n",
        "determined, there are four remaining options for the transformation of\n",
        "$Z$, leading to 24 elements total.\n",
        "\n",
        "It takes some work, but you can take combinations of $H$ and $S$ and\n",
        "evaluate their action on $X$ and $Z$ (or look at their matrix\n",
        "representations) until you find all 24 unique elements. The results of\n",
        "this endeavour are expressed below as strings:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "id": "htyWtrFVwQly"
      },
      "outputs": [],
      "source": [
        "single_qubit_cliffords = [\n",
        " '',\n",
        " 'H', 'S',\n",
        " 'HS', 'SH', 'SS',\n",
        " 'HSH', 'HSS', 'SHS', 'SSH', 'SSS',\n",
        " 'HSHS', 'HSSH', 'HSSS', 'SHSS', 'SSHS',\n",
        " 'HSHSS', 'HSSHS', 'SHSSH', 'SHSSS', 'SSHSS',\n",
        " 'HSHSSH', 'HSHSSS', 'HSSHSS'\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pGo_-fX8wQlz"
      },
      "source": [
        "To see for yourself how this set of unitaries is evenly distributed, try\n",
        "applying each of the Cliffords to the initial state $|0\\rangle$, and\n",
        "plot the resulting states on the Bloch sphere. You\\'ll find they are\n",
        "symmetric and evenly spaced; in fact, they are all eigenstates of $X$,\n",
        "$Y$, and $Z$. Furthermore, under the full group action, the result is\n",
        "balanced in the sense that each eigenstate is obtained the same number\n",
        "of times.\n",
        "\n",
        "The multi-qubit Clifford group can also be specified by only a small set\n",
        "of generators (in fact, only one more than is needed for the\n",
        "single-qubit case). Together, $H$, $S$, and CNOT (on every possible\n",
        "qubit or pair of qubits) generate the $n$-qubit group. Be careful\n",
        "though---the size of the group increases exponentially. The 2-qubit\n",
        "group alone has 11520 elements! The size can be worked out in a manner\n",
        "analogous to that we used above in the single qubit case: by looking at\n",
        "the combinatorics of the possible ways the gates can map Paulis with\n",
        "only $X$ and $Z$ to other Paulis.\n",
        "\n",
        "An experiment\n",
        "=============\n",
        "\n",
        "The whole idea of unitary designs may sound too good to be true. Can we\n",
        "*really* compute the exact average fidelity using just 24 operations? In\n",
        "this section, we put them to the test: we\\'ll compute the average\n",
        "fidelity of an operation first with experiments using a large but finite\n",
        "amount of Haar-random unitaries, and then again with only the Clifford\n",
        "group.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {
        "id": "0D3Bw8IXwQlz"
      },
      "outputs": [],
      "source": [
        "import pennylane as qml\n",
        "\n",
        "# Scipy allows us to sample Haar-random unitaries directly\n",
        "from scipy.stats import unitary_group\n",
        "\n",
        "# set the random seed\n",
        "np.random.seed(42)\n",
        "\n",
        "# Use the mixed state simulator\n",
        "dev = qml.device(\"default.mixed\", wires=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AooBqsfDwQl0"
      },
      "source": [
        "Let\\'s set up a noisy quantum channel. To keep things simple, assume it\n",
        "consists of applying `~.pennylane.SX`{.interpreted-text role=\"class\"},\n",
        "the square-root of $X$ gate, followed by a few different types of noise.\n",
        "First, write a quantum function for our ideal experiment:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {
        "id": "yZtqAXHkwQl0"
      },
      "outputs": [],
      "source": [
        "def ideal_experiment():\n",
        "    qml.SX(wires=0)\n",
        "    return qml.state()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XZX2g2SswQl1"
      },
      "source": [
        "Next, we apply some noise. We do so by making use of a relatively new\n",
        "feature in PennyLane called [quantum function\n",
        "transforms](https://pennylane.readthedocs.io/en/latest/code/qml_transforms.html).\n",
        "Such transforms work by modifying the underlying, low-level quantum\n",
        "tapes which queue the quantum operations. Suppose the noisy channel is\n",
        "composed of the following:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "id": "iWgu5nLXwQl1"
      },
      "outputs": [],
      "source": [
        "def noisy_operations(damp_factor, depo_factor, flip_prob):\n",
        "    qml.AmplitudeDamping(damp_factor, wires=0)\n",
        "    qml.DepolarizingChannel(depo_factor, wires=0)\n",
        "    qml.BitFlip(flip_prob, wires=0)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hXMC2ARewQl1"
      },
      "source": [
        "Let\\'s create a transform that applies this noise to any quantum\n",
        "function *after* the original operations, but before the measurements.\n",
        "We use the convenient\n",
        "`~.pennylane.transforms.qfunc_transform`{.interpreted-text role=\"func\"}\n",
        "decorator:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "id": "lhN6Ge4VwQl1"
      },
      "outputs": [],
      "source": [
        "@qml.qfunc_transform\n",
        "def apply_noise(tape, damp_factor, depo_factor, flip_prob):\n",
        "    # Apply the original operations\n",
        "    for op in tape.operations:\n",
        "        qml.apply(op)\n",
        "\n",
        "    # Apply the noisy sequence\n",
        "    noisy_operations(damp_factor, depo_factor, flip_prob)\n",
        "\n",
        "    # Apply the original measurements\n",
        "    for m in tape.measurements:\n",
        "        qml.apply(m)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nV2Mpa88wQl2"
      },
      "source": [
        "We can now apply this transform to create a noisy version of our ideal\n",
        "quantum function:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {
        "id": "N1eG4TFWwQl2"
      },
      "outputs": [],
      "source": [
        "# The strengths of various types of noise\n",
        "damp_factor = 0.02\n",
        "depo_factor = 0.02\n",
        "flip_prob = 0.0001\n",
        "\n",
        "noisy_experiment = apply_noise(damp_factor, depo_factor, flip_prob)(ideal_experiment)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EomIg-qmwQl2"
      },
      "source": [
        "The last part of the experiment involves applying a random unitary\n",
        "matrix before all the operations, and its inverse right before the\n",
        "measurements. We can write another transform here to streamline this\n",
        "process:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {
        "id": "kNLUeoQAwQl2"
      },
      "outputs": [],
      "source": [
        "@qml.qfunc_transform\n",
        "def conjugate_with_unitary(tape, matrix):\n",
        "    qml.QubitUnitary(matrix, wires=0)\n",
        "\n",
        "    for op in tape.operations:\n",
        "        qml.apply(op)\n",
        "\n",
        "    qml.QubitUnitary(matrix.conj().T, wires=0)\n",
        "\n",
        "    for m in tape.measurements:\n",
        "        qml.apply(m)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BbICvvouwQl3"
      },
      "source": [
        "Finally, in order to perform a comparison, we need a function to compute\n",
        "the [fidelity](https://en.wikipedia.org/wiki/Fidelity_of_quantum_states)\n",
        "compared to the ideal operation.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {
        "id": "aDrUBq_awQl3"
      },
      "outputs": [],
      "source": [
        "from scipy.linalg import sqrtm\n",
        "\n",
        "def fidelity(rho, sigma):\n",
        "    # Inputs rho and sigma are density matrices\n",
        "    sqrt_sigma = sqrtm(sigma)\n",
        "    fid = np.trace(sqrtm(sqrt_sigma @ rho @ sqrt_sigma))\n",
        "    return fid.real"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "77i6U6C4wQl3"
      },
      "source": [
        "Let\\'s now compute the average fidelity, averaging over 50000\n",
        "Haar-random unitaries:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "id": "-SWuQCvHwQl3",
        "outputId": "4b8b328b-8cb0-46e7-b34f-b4789fcebd01"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Mean fidelity = 0.9900781215223946\n"
          ]
        }
      ],
      "source": [
        "n_samples = 50000\n",
        "\n",
        "fidelities = []\n",
        "\n",
        "for _ in range(n_samples):\n",
        "    # Select a Haar-random unitary\n",
        "    U = unitary_group.rvs(2)\n",
        "\n",
        "    # Apply transform to construct the ideal and noisy quantum functions\n",
        "    conjugated_ideal_experiment = conjugate_with_unitary(U)(ideal_experiment)\n",
        "    conjugated_noisy_experiment = conjugate_with_unitary(U)(noisy_experiment)\n",
        "\n",
        "    # Use the functions to create QNodes\n",
        "    ideal_qnode = qml.QNode(conjugated_ideal_experiment, dev)\n",
        "    noisy_qnode = qml.QNode(conjugated_noisy_experiment, dev)\n",
        "\n",
        "    # Execute the QNodes\n",
        "    ideal_state = ideal_qnode()\n",
        "    noisy_state = noisy_qnode()\n",
        "\n",
        "    # Compute the fidelity\n",
        "    fidelities.append(fidelity(ideal_state, noisy_state))\n",
        "\n",
        "fid_mean = np.mean(fidelities)\n",
        "print(f\"Mean fidelity = {fid_mean}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hneAjRLbwQl3"
      },
      "source": [
        "Now let\\'s repeat the procedure using only Clifford group elements.\n",
        "First, we write a quantum function that performs a Clifford operation\n",
        "(or its inverse) based on its string representation.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "metadata": {
        "id": "YAaqy1W0wQl4"
      },
      "outputs": [],
      "source": [
        "def apply_single_clifford(clifford_string, inverse=False):\n",
        "    for gate in clifford_string:\n",
        "        if gate == 'H':\n",
        "            qml.Hadamard(wires=0)\n",
        "        else:\n",
        "            sign = -1 if inverse else 1\n",
        "            qml.PhaseShift(sign * np.pi/2, wires=0)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2adwduIowQl4"
      },
      "source": [
        "Next, we write a transform that applies a Clifford in the context of the\n",
        "full experiment, i.e., apply the Clifford, then the operations, followed\n",
        "by the inverse of the Clifford.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {
        "id": "pI-3y8HzwQl4"
      },
      "outputs": [],
      "source": [
        "@qml.qfunc_transform\n",
        "def conjugate_with_clifford(tape, clifford_string):\n",
        "    apply_single_clifford(clifford_string, inverse=False)\n",
        "\n",
        "    for op in tape.operations:\n",
        "        qml.apply(op)\n",
        "\n",
        "    apply_single_clifford(clifford_string, inverse=True)\n",
        "\n",
        "    for m in tape.measurements:\n",
        "        qml.apply(m)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0kaDBhE5wQl4"
      },
      "source": [
        "You may have noticed this transform has exactly the same form as\n",
        "`conjugate_with_unitary` from above. Only the input type has changed,\n",
        "since the application of Cliffords here is specified by their string\n",
        "representation.\n",
        "\n",
        "It\\'s now time to run the experiments:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "metadata": {
        "id": "85LSAbLpwQl4"
      },
      "outputs": [],
      "source": [
        "fidelities = []\n",
        "\n",
        "for C in single_qubit_cliffords:\n",
        "    conjugated_ideal_experiment = conjugate_with_clifford(C)(ideal_experiment)\n",
        "    conjugated_noisy_experiment = conjugate_with_clifford(C)(noisy_experiment)\n",
        "\n",
        "    ideal_qnode = qml.QNode(conjugated_ideal_experiment, dev)\n",
        "    noisy_qnode = qml.QNode(conjugated_noisy_experiment, dev)\n",
        "\n",
        "    ideal_state = ideal_qnode()\n",
        "    noisy_state = noisy_qnode()\n",
        "\n",
        "    fidelities.append(fidelity(ideal_state, noisy_state))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cx0-SuYBwQl4"
      },
      "source": [
        "Let\\'s see how our results compare to the earlier simulation:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "id": "P846UtObwQl5",
        "outputId": "b4a95c23-1d32-4831-e3ca-3ed1f89924bf"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Haar-random mean fidelity = 0.9900781215223946\n",
            "Clifford mean fidelity    = 0.9900574834455249\n"
          ]
        }
      ],
      "source": [
        "clifford_fid_mean = np.mean(fidelities)\n",
        "\n",
        "print(f\"Haar-random mean fidelity = {fid_mean}\")\n",
        "print(f\"Clifford mean fidelity    = {clifford_fid_mean}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kGKdKRmnwQl5"
      },
      "source": [
        "Incredible 🤯 🤯 🤯 We were able to compute the average fidelity using only\n",
        "24 experiments. Furthermore, the mean fidelity obtained from the\n",
        "Clifford experiments is **exact**; even with 50000 Haar-random\n",
        "experiments, we see deviations starting a few decimal places in.\n",
        "Consider the resources that would be saved if you were actually\n",
        "implementing this in a lab! It\\'s not hard to see why the Clifford group\n",
        "plays such an important role in characterization procedures.\n",
        "\n",
        "Conclusion\n",
        "==========\n",
        "\n",
        "In this demo, we\\'ve barely scratched the surface of designs and their\n",
        "applications in quantum computing. While benchmarking is a key\n",
        "application area, there are many others. The Pauli group as a unitary\n",
        "1-design has applications in the construction of private quantum\n",
        "channels[^1]. *Approximate* unitary $t$-designs (where the equality in\n",
        "the definition is replaced by approximately equal up to some finite\n",
        "precision) are also of interest, as there ways to construct them that\n",
        "are more efficient than those of exact designs[^2]. In particular, it\n",
        "has been shown that approximate complex projective 4-designs have\n",
        "applications to the state discrimination problem[^3].\n",
        "\n",
        "Furthermore, unitary designs are not the only designs that you\\'ll\n",
        "encounter in quantum computing. The familiar Hadamard gate is just a\n",
        "2-dimensional example of a broader family of *Hadamard designs*, on\n",
        "which there has been extensive research[^4]. Some sets of [mutually\n",
        "orthogonal Latin\n",
        "squares](https://en.wikipedia.org/wiki/Mutually_orthogonal_Latin_squares)\n",
        "have a direct correspondence with mutually unbiased bases, which are\n",
        "optimal quantum measurements[^5], as well as complex projective designs;\n",
        "and Latin squares themselves have direct correspondence with affine and\n",
        "projective planes, bringing us full circle back to the Fano plane from\n",
        "which we began.\n",
        "\n",
        "![An affine plane, Hadamard matrix, and a depiction of mutually\n",
        "orthogonal Latin\n",
        "squares.](/demonstrations/unitary_designs/affine-latin.svg){.align-center\n",
        "width=\"80.0%\"}\n",
        "\n",
        "References\n",
        "==========\n",
        "\n",
        "About the author\n",
        "================\n",
        "\n",
        "[^1]: A. Ambainis, M. Mosca, A. Tapp, and R. de Wolf (2000) *Private\n",
        "    Quantum Channels*. Proc. 41st FOCS, 547-553.\n",
        "    [(PDF)](https://homepages.cwi.nl/~rdewolf/publ/qc/AMTW00.pdf).\n",
        "\n",
        "[^2]: C. Dankert, R. Cleve, J. Emerson, and E. Levine (2009) *Exact and\n",
        "    Approximate Unitary 2-Designs: Constructions and Applications.*\n",
        "    Phys. Rev. A 80, 012304.\n",
        "    [(arXiv)](https://arxiv.org/abs/quant-ph/0606161).\n",
        "\n",
        "[^3]: A. Ambainis and J. Emerson (2007) *Quantum t-designs: t-wise\n",
        "    independence in the quantum world.* Twenty-Second Annual IEEE\n",
        "    Conference on Computational Complexity 129-140.\n",
        "    [(arXiv)](https://arxiv.org/abs/quant-ph/0701126).\n",
        "\n",
        "[^4]: J. Seberry and M. Yamada (1992) *Hadamard matrices, sequences, and\n",
        "    block designs.* Contemporary Design Theory -- A Collection of\n",
        "    Surveys (D. J. Stinson and J. Dinitz, Eds.), John Wiley and Sons,\n",
        "    431-560. [(PDF)](http://mathscinet.ru/files/YamadaSeberry1992.pdf).\n",
        "\n",
        "[^5]: M. Gaeta, O. Di Matteo, A. B. Klimov, and H. de Guise (2014)\n",
        "    *Discrete phase-space approach to mutually orthogonal Latin\n",
        "    squares*. J. Phys. A: Math. Theor. 47 (43) 435303.\n",
        "    [(arXiv)](https://arxiv.org/abs/1408.6742).\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "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.9.17"
    },
    "colab": {
      "provenance": []
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}