--- a
+++ b/Code/All Qiskit, PennyLane QML Nov 23/23a Unitary Designs Fid. +.99 kkawchak.ipynb
@@ -0,0 +1,1018 @@
+{
+  "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
+}