{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Clifford Randomized Benchmarking" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tutorial demonstrates how to generate Clifford randomized benchmarking (RB) circuits using `pygsti`. We will focus on (holistic) Clifford RB for an arbitrary number of qubits. The tutorial uses an in-built Clifford compilation algorithm that works for an arbitrary number of qubits, but does not permit the user to specify their own Clifford compilation. We follow the current standard RB protocol first defined by Magesan et al. in [\"Scalable and Robust Benchmarking of Quantum Processes\"](http://journals.aps.org/prl/abstract/10.1103/PhysRevLett.106.180504). Other RB variants exist, however, and there are often good reasons to use them, particularly for multi-qubit benchmarking. One of these alternatives, [\"Direct RB\"](https://arxiv.org/abs/1807.07975), is detailed in its own tutorial (17 Direct Randomized Benchmarking).\n", "\n", "Please note that this tutorial does *not* demonstrate how to analyze Clifford RB data within `pyGSTi`. Because the same RB analysis proceedure is applicable to many RB protocols, it appears in a separate tutorial (18 Randomized Benchmarking Data Analysis). " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function #python 2 & 3 compatibility\n", "\n", "import pygsti\n", "from pygsti.extras import rb" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Specifying the device to be benchmarked\n", "\n", "To generate Clifford RB circuits, we first specify the two-qubit gate connectivity. The compiled circuits will respect this connectivity, and contain only gates in the \"native\" gate-set of the device. We do this using a `ProcessorSpec` object: see the previous tutorial (15 Multi-Qubit Devices and Quantum Circuits) on how to create these. Here we'll demonstrate creating Clifford RB circuits for a device with:\n", "- Five qubits on a ring, labelled 'Q0', ..., 'Q4'\n", "- 1-qubit gates consisting of $\\sigma_x$ and $\\sigma_y$ rotations by $\\pm \\pi/2$, and an idle gate\n", "- Controlled-Z gates connecting adjacent qubits on the ring\n", "\n", "Below, we generate the `ProcessorSpec` for this device:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "nQubits = 5 \n", "qubit_labels = ['Q0','Q1','Q2','Q3','Q4'] \n", "gate_names = ['Gi', 'Gxpi2', 'Gxmpi2', 'Gypi2', 'Gympi2', 'Gcphase'] \n", "availability = {'Gcphase':[('Q0','Q1'), ('Q1','Q2'), ('Q2','Q3'), \n", " ('Q3','Q4'),('Q4','Q0')]}\n", "pspec = pygsti.obj.ProcessorSpec(nQubits, gate_names, availability=availability, \n", " qubit_labels=qubit_labels)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generating a Clifford RB experiment\n", "\n", "We can generate a set of Clifford RB circuits using the `rb.sample.clifford_rb_experiment()` function.\n", "\n", "#### Essential parameters\n", "To sample a Clifford RB experiment, it is necessary to specify:\n", "- The RB sequence lengths\n", "- The number of circuits to sample at each length\n", "\n", "A \"Clifford RB length\" ($m$) is, within an additive constant, the length of the *uncompiled* Clifford circuit (the number of random $n$-qubit Clifford gates in the circuit). There are two possible conventions for the additive constant. \n", "1. The literature convention is that $m$ corresponds to the number of *independent, uniformly random* Clifford gates in the uncompiled circuit. So this corresponds to the total number of Clifford gates in the uncompiled circuit *minus 1* (the inversion Clifford gate is uniquely defined by the preceeding sequence, and so is not independent). Under this convention, $m$ can be an integer $\\geq 1$. \n", "2. The convention used by `pyGSTi` is that $m$ corresponds to the number of Clifford gates in the uncompiled circuit *minus 2*. This choice is motivated by the original Clifford RB protocol, which demands that the circuits being used have at least two gates (a circuit consisting only of state prep and measurement is not normally permitted in Clifford RB). Under this convention, $m$ can be any integer $\\geq 0$. \n", "\n", "We use this second convention consistently across *all* RB circuit generating methods within `pyGSTi`. Data *analysis*, however, is largely indepenent of which convention is used for the circuit generation. Recall that RB data is fit to the function $P_m = A + Bp^m$. Rescaling the length, $m$, by an additive constant will only change the value of $B$ in the optimal fit (scaling it by factors of $p$). Importantly, the optimal fit of $p$, which fixes the error rate, remains unchanged.\n", "\n", "In our example, we'll fix the Clifford RB lengths to $m \\in \\{0, 1, 2, 4, 8, 16\\}$ and take the number of circuits at each length to be $k = 10$." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "lengths = [0,1,2,4,8,16]\n", "k = 10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Optional parameters\n", "\n", "The RB samplers in `pyGSTi` allow the user to restrict the benchmarking sequences to address a subset of the qubits by specifying a __`subsetQs`__ list. This then means that a `ProcessorSpec` can be specified for an entire device even if you only wish to benchmark some subset of it. If this list is not specified then the RB circuits returned will cover all qubits, providing a set of experiments capable of holistically benchmarking the entire device.\n", "\n", "Obviously, the set of qubits specified must be connected by the two-qubit gates. If this is not the case, it is not possible to implement $n$-qubit Clifford gates over these $n$ qubits. \n", "\n", "Let's demonstrate generating circuits to benchmark 3 of the qubits:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "subsetQs = ['Q0','Q1','Q2']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another important optional parameter is __`randomizeout`__. This specifies whether the perfect output of the circuits should be the input state (assumed here to be the all-zero state) or a random computational basis state. The standard Clifford RB procedure is a perfect identity sequence, corresponding to the default of `randomizeout` being `False`. While there are many good reasons to instead set this to True (such as identifying biased measurement noise or leakage), here we'll stick to the standard procedure and set it to `False`." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "randomizeout = False" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Generating the circuits\n", "\n", "We are now ready to generate the RB experiment:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "- Sampling 10 circuits at CRB length 0 (1 of 6 lengths)\n", " - Number of circuits sampled = 1,2,3,4,5,6,7,8,9,10,\n", "- Sampling 10 circuits at CRB length 1 (2 of 6 lengths)\n", " - Number of circuits sampled = 1,2,3,4,5,6,7,8,9,10,\n", "- Sampling 10 circuits at CRB length 2 (3 of 6 lengths)\n", " - Number of circuits sampled = 1,2,3,4,5,6,7,8,9,10,\n", "- Sampling 10 circuits at CRB length 4 (4 of 6 lengths)\n", " - Number of circuits sampled = 1,2,3,4,5,6,7,8,9,10,\n", "- Sampling 10 circuits at CRB length 8 (5 of 6 lengths)\n", " - Number of circuits sampled = 1,2,3,4,5,6,7,8,9,10,\n", "- Sampling 10 circuits at CRB length 16 (6 of 6 lengths)\n", " - Number of circuits sampled = 1,2,3,4,5,6,7,8,9,10,\n" ] } ], "source": [ "exp_dict = rb.sample.clifford_rb_experiment(pspec, lengths, k, subsetQs=subsetQs, randomizeout=randomizeout)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And that's it!\n", "\n", "## What's in the output?\n", "\n", "The returned dictionary contains a full specification for the RB circuits to implement on the device defined by `pspec`. This dictionary contains 4 keys:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "dict_keys(['spec', 'qubitordering', 'circuits', 'idealout'])\n" ] } ], "source": [ "print(exp_dict.keys())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The sampled circuits\n", "\n", "Of particular note here is the `'circuits'` key, which indexes another dictionary for which the keys are tuples ($m$,$i$) where $m$ is the RB length and $i = 0, 1, \\dots, k$ corresponds to the $i^{\\rm th}$ circuit at length $m$. " ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "dict_keys([(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9), (4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7), (4, 8), (4, 9), (8, 0), (8, 1), (8, 2), (8, 3), (8, 4), (8, 5), (8, 6), (8, 7), (8, 8), (8, 9), (16, 0), (16, 1), (16, 2), (16, 3), (16, 4), (16, 5), (16, 6), (16, 7), (16, 8), (16, 9)])\n" ] } ], "source": [ "print(exp_dict['circuits'].keys())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's look at one of the circuits, which is a `pyGSTi Circuit` object. We'll look at the first circuit sampled at Clifford RB length 0, which consists of two $3$-qubit Clifford gates (one uniformly random, the other it's inverse) compiled into the native gates:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Here we show first circuit sampled at Clifford RB length $m=0$ (consisting of two uncompiled Clifford gates):\n", "\n", "Qubit Q0 ---|Gympi2|-| Gi |-| Gi |-| ●Q1 |-|Gympi2|-|Gympi2|-|Gympi2|-|Gymp\n", "Qubit Q1 ---|Gypi2 |-|Gxmpi2|-|Gympi2|-| ●Q0 |-|Gympi2|-|Gxpi2 |-|Gympi2|-|Gymp\n", "Qubit Q2 ---|Gxmpi2|-|Gxmpi2|-|Gxmpi2|-|Gympi2|-| Gi |-| Gi |-| Gi |-| Gi\n", "\n", "i2|-|Gi |-| Gi |-| Gi |-| Gi |-| Gi |-|●Q1|-|Gympi2|-|Gympi2|-|Gxmpi2|-| \n", "i2|-|●Q2|-|Gympi2|-|Gympi2|-|Gxpi2|-|Gympi2|-|●Q0|-|Gympi2|-|Gxpi2 |-|Gympi2|-|G\n", " |-|●Q1|-|Gympi2|-|Gxpi2 |-|Gxpi2|-|Gympi2|-|Gi |-| Gi |-| Gi |-| Gi |-| \n", "\n", " Gi |-|Gi |-| Gi |-| Gi |-| Gi |-| Gi |-|Gi |-| Gi |-| Gi |-| Gi \n", "ympi2|-|●Q2|-|Gympi2|-|Gympi2|-|Gxpi2 |-|Gympi2|-|●Q2|-|Gympi2|-|Gxpi2 |-|Gypi2 \n", " Gi |-|●Q1|-|Gympi2|-|Gxmpi2|-|Gympi2|-|Gympi2|-|●Q1|-|Gympi2|-|Gympi2|-|Gxmpi2\n", "\n", "|-| Gi |-| Gi |-| Gi |-|Gxmpi2|-|Gxmpi2|-|Gxmpi2|-|Gxmpi2|-|Gxmpi2|-|Gympi2|\n", "|-|Gypi2 |-| Gi |-| Gi |-|Gympi2|-| Gi |-| Gi |-| ●Q2 |-|Gympi2|-|Gympi2|\n", "|-|Gxmpi2|-|Gypi2|-|Gypi2|-|Gypi2 |-|Gxmpi2|-|Gympi2|-| ●Q1 |-|Gympi2|-|Gxpi2 |\n", "\n", "-|Gympi2|-| Gi |-| Gi |-| Gi |-|●Q1|-|Gympi2|-|Gympi2|-|Gympi2|-|Gympi2|-|\n", "-|Gxmpi2|-|Gxmpi2|-|Gxmpi2|-|Gympi2|-|●Q0|-|Gympi2|-|Gxpi2 |-|Gympi2|-|Gympi2|-|\n", "-|Gypi2 |-|Gxmpi2|-|Gympi2|-| Gi |-|Gi |-| Gi |-| Gi |-| Gi |-| Gi |-|\n", "\n", "Gi |-| Gi |-| Gi |-| Gi |-| Gi |-|●Q1|-|Gympi2|-|Gxmpi2|-|Gxmpi2|-| Gi \n", "●Q2|-|Gympi2|-|Gympi2|-|Gxpi2|-|Gympi2|-|●Q0|-|Gympi2|-|Gxpi2 |-|Gympi2|-|Gympi2\n", "●Q1|-|Gympi2|-|Gxpi2 |-|Gxpi2|-|Gympi2|-|Gi |-| Gi |-| Gi |-| Gi |-| Gi \n", "\n", "|-|Gi |-| Gi |-| Gi |-| Gi |-| Gi |-| Gi |-| Gi |---\n", "|-|●Q2|-|Gympi2|-|Gympi2|-|Gxmpi2|-|Gxmpi2|-|Gxmpi2|-|Gxmpi2|---\n", "|-|●Q1|-|Gympi2|-|Gxmpi2|-| Gi |-| Gi |-| Gi |-| Gi |---\n", "\n", "The circuit size is: 186\n", "The circuit depth is: 62\n", "The circuit multi-qubit-gate count is: 10\n" ] } ], "source": [ "print(\"Here we show first circuit sampled at Clifford RB length $m=0$ (consisting of two uncompiled Clifford gates):\")\n", "print(\"\")\n", "gate_string = exp_dict['circuits'][0,0].__str__()\n", "gate_string = gate_string.split('\\n')\n", "for bar in range(int(len(gate_string[0])/80)+1):\n", " for ind in range(len(gate_string)):\n", " print(gate_string[ind][80*bar:80*(bar+1)])\n", "print(\"The circuit size is: \", exp_dict['circuits'][0,0].size())\n", "print(\"The circuit depth is: \", exp_dict['circuits'][0,0].depth())\n", "print(\"The circuit multi-qubit-gate count is: \", exp_dict['circuits'][0,0].multiQgate_count())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Each of these circuits can be converted to OpenQasm or Quil using the methods shown in the tutorial introducing the `Circuit` object. The set of circuits can also be saved to file, using the `GateString` methods. To do this, we first turn the `circuits` dictionary into a `list`. \n", "\n", "Note that when analyzing the data from an RB experiment, it is important to be able to assign to each gate sequence a Clifford RB length, $m$. It is not strictly possible to extract this given only the compiled circuit, so we are careful to order the saved sequences so that we can easily deduce their associated the RB lengths. Below, we order the resulting list so that the first `k` sequences have length $m=0$, the second `k` sequences have length $m=1$, etc. " ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "circuitlist = [exp_dict['circuits'][m,i] for m in lengths for i in range(k)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can then use the `GateString` export function:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "pygsti.io.write_gatestring_list(\"tutorial_files/CliffordRBCircuits.txt\",circuitlist,\n", " \"Clifford RB circuits\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The other outputs\n", "The output dictionary contains some other things, which can be very important in some situations.\n", "\n", "#### The RB specification\n", "\n", "One of these is the specification used to generate the circuits:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'lengths': [0, 1, 2, 4, 8, 16],\n", " 'circuits_per_length': 10,\n", " 'subsetQs': ['Q0', 'Q1', 'Q2'],\n", " 'randomizeout': False,\n", " 'citerations': 20,\n", " 'compilerargs': [],\n", " 'descriptor': 'A Clifford RB experiment'}" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "exp_dict['spec']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This stores all the information necessary to sample new circuits in the same way (and to know how the circuits were generated), when combined with the `ProcessorSpec` used (which is not stored, as this can be a fairly large object).\n", "\n", "However there is one warning here: the **`compilerargs`** specifies what Clifford compiler to use, and when left as an empty list it will use the default option. This will be set to whatever we consider to be the best general-purpose Clifford compiler in `pyGSTi` (there are multiple algorithms), and so this may change in future version of `pyGSTi`. This is important as the Clifford RB error rate is very strongly dependent on the compilation used (as it is defined as an error rate *per Clifford* gate). Note that we can avoid compilation-dependence complications by instead implementing [\"Direct RB\"](https://arxiv.org/abs/1807.07975) - see the next tutorial.\n", "\n", "This RB-specification dictionary highlights one other aspects of the Clifford sampling function which may sometimes be important: __`citerations`__ is the number of iterations used in our randomized Clifford compilers. Increasing this will often reduce the size and two-qubit-gate count in each compiled Clifford, at the cost of slowing down the circuit sampling. Sometimes it may be useful to increase this value, particularly if you want to implement Clifford RB on a number of qubits that is at the edge of feasability on your device (due to the native gate error rates) as in this case reducing the \"cost\" of each Clifford gate will be critical.\n", "\n", "#### The error-free circuit outputs\n", "\n", "Another component of the output dictionary is the ideal outputs of the circuits, which is a dictionary with the same keys as with 'circuits':" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "dict_keys([(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9), (4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7), (4, 8), (4, 9), (8, 0), (8, 1), (8, 2), (8, 3), (8, 4), (8, 5), (8, 6), (8, 7), (8, 8), (8, 9), (16, 0), (16, 1), (16, 2), (16, 3), (16, 4), (16, 5), (16, 6), (16, 7), (16, 8), (16, 9)])" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "exp_dict['idealout'].keys()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The values are the error-free outcomes of the circuits. Here, because we have left `randomizeout` as False, this is always the bit-string of 3 zeros:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(0, 0, 0)" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "exp_dict['idealout'][0,0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But if `randomizeout` is True, these will be random bit-strings. It will not be possible to analyze the results of the RB experiments without these bit-strings: because \"success\" corresponds to the circuit output being the particular bit-string stored here. (These bit-strings can always be re-calculated from the circuits, but doing this with `pyGSTi` requires accessing functions that are currently not demonstrated in any tutorial).\n", "\n", "Note that the `'idealout'` bitstring assumes the input is ideally $0,0,0,\\dots$. So if you use an input state other than $0,0,0,\\dots$ it will be necessary to correct for this. E.g., if you start in the computational basis state $1,0,0,\\dots$ you would need to add this bit-string modulo 2 to the `'idealout'` bit-string.\n", "\n", "#### The qubit ordering\n", "\n", "The final element in the output dictionary is 'qubitordering':" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('Q0', 'Q1', 'Q2')" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "exp_dict['qubitordering']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is just a tuple that shows which qubit correpsonds to which bit in the `'idealout'` bit-tuples. Note that this can also be extracted from the circuit objects and the RB `'spec'`, but it's useful information to have easily available." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.7.0" } }, "nbformat": 4, "nbformat_minor": 1 }