{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Random Circuit Samplers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tutorial introduces the different random circuit layer samplers in-built into `pyGSTi`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function #python 2 & 3 compatibility\n", "import pygsti" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The circuit layer samplers in pyGSTi exist primarily for the purpose of running [Direct Randomized Benchmarking](RB-DirectRB.ipynb) and/or [Mirror Randomized Benchmarking](RB-MirroRB.ipynb). Here we'll demonstate them by just creating a more generic random circuit consisting of independently sampled random layers, but the same syntax is used to select these samplers in the `DirectRBDesign` and `MirrorRBDesign` functions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pygsti.algorithms.randomcircuit import create_random_circuit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The random circuit samplers generate circuits for a specific device, so we define that device via a ProcessorSpec." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_qubits = 4\n", "qubit_labels = ['Q0','Q1','Q2','Q3'] \n", "gate_names = ['Gxpi2', 'Gxmpi2', 'Gypi2', 'Gympi2', 'Gcphase'] \n", "availability = {'Gcphase':[('Q0','Q1'), ('Q1','Q2'), ('Q2','Q3'), ('Q3','Q0')]}\n", "pspec = pygsti.obj.ProcessorSpec(n_qubits, gate_names, availability=availability, qubit_labels=qubit_labels,\n", " construct_models=('clifford',))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What kind of circuit layer samplers do we need for Direct and Mirror RB?\n", "\n", "As these samplers are mostly useful for Direct and Mirror RB, it's first worthwhile to go over what properties we want of the circuit layer samplers in those protocols.\n", "\n", "By construction, the Direct and Mirror RB error rates depend on how the circuits are sampled. Essentially, these error rates quantify gate performance over circuits that are sampled according to the used sampling distribution. So we need to pick a sampling distribution that provides useful error rates.\n", "\n", "A second consideration is that Direct and Mirror RB are only reliable - i.e., the success probabilities decay exponentially and the error rates are easily interpreted - if the sampling distribution is sufficiently scrambling. To be \"sufficiently scrambling\" we need a high enough rate of two-qubit gates, in order to spread any errors that occur locally across the full set of qubits being benchmarked. We also need sufficient local randomization to quickly convert coherent to stochastic errors, to stop errors coherently adding across many layers." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "depth = 10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. The qubit elimination layer sampler\n", "\n", "**Inputs.** A probability $p$.\n", "\n", "**Algorithm.**\n", "1. Start with an empty layer $L$.\n", "2. Pick a qubit $q_1$ uniformly at random from all the qubits that do not yet have a gate assigned to them in $L$.\n", "2. Pick another qubit $q_2$, uniformly at random, that also does not have a gate assigned to it in $L$ *and* that is connected to $q_1$. If there is no such qubits, skip to (4).\n", "3. If such a qubit has been found, assign a two-qubit gate to this pair of qubits ($q_1$ and $q_2$) with probability $p$.\n", "4. If a two-qubit gate has not been assigned to $q_1$, pick a uniformly random 1-qubit gate to assign to $q_1$.\n", "5. Repeat 2-4 until all qubits have been assigned a gate." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sampler = 'Qelimination'\n", "samplerargs = [0.5] # Setting p = 0.5 (this is the default).\n", "circuit = create_random_circuit(pspec, depth, sampler=sampler, samplerargs=samplerargs)\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The qubit elimination sampler is easy to use: it works for any connectivity, and the same value of $p$ can be used for benchmarking any number of qubits. But it's perhaps not the best sampler, because the relationship between $p$ and the number of two-qubit gates per layer is opaque, except for devices with full connectivity. A sampler that is similar in spirit, but that we've found more useful, is the \"edge grab\" sampler. \n", "\n", "### 2. The edge-grab sampler\n", "\n", "**Inputs.** A float $\\gamma$, corresponding to the expected number of two-qubit gates in the layer.\n", "\n", "**Algorithm.** \n", "1. Select a set of candidate edges in the connective graph as follows:\n", " (1.1) Start with an empty candidate edge list $A$, and a list that initial contains all the edges $B$.\n", " (1.2) Select an edge from $B$ uniformly at random, and add it to the $A$. Delete all the edges in $B$ that contain a vertex (qubit) in common with the selected edge.\n", " (1.3) Repeat (1.2) until $B$ contains no edges.\n", "2. Indepedently consider each edge in $A$, and add a two-qubit gate on that edge to the layer with probability $\\gamma/|A|$ where $|A|$ is the number of edges in $A$.\n", "3. Independently and uniformly at random, select a one-qubit gate to apply to each qubit that does not have a two-qubit gate assigned to it in the layer.\n", "\n", "This algorithm is designed so that the expected number of two-qubit gates in the layer is $\\gamma$, and every possible circuit layer is sampled with a non-zero probability (unless $\\gamma=0$). But note that this algorithm will fail if $\\gamma/|A|$ can ever be greater then $1$. The maximum allowable $\\gamma$ depends on the connectivity of the graph. \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sampler = 'edgegrab'\n", "samplerargs = [1] # Setting gamma = 1\n", "circuit = create_random_circuit(pspec, depth, sampler=sampler, samplerargs=samplerargs)\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3. The compatible two-qubit gates sampler\n", "\n", "The compatible two-qubit gates sampler is more involved to use, but it provides a lot of control over the distribution of two-qubit gates in a layer, while keeping the 1-qubit gate distribution simple.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sampler = 'co2Qgates'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This sampler requires the user to specify sets of compatible 2-qubit gates, meaning 2-qubit gates that can applied in parallel. We specifying this as a list of lists of `Label` objects (see the [Ciruit tutorial](../objects/Circuit.ipynb) for more on `Label` objects), so let's import the `Label` object:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pygsti.objects import Label as L" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, we have 4 qubits in a ring. So we can easily write down all of the possible compatible 2-qubit gate lists over these 4 qubits. There are only 7 of them: a list containing no 2-qubit gates, and 4 lists containing only 1 2-qubit gate, and 2 lists containing 2 2-qubit gates." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "C2QGs1 = [] # A list containing no 2-qubit gates is an acceptable set of compatible 2-qubit gates.\n", "C2QGs2 = [L('Gcphase',('Q0','Q1')),]\n", "C2QGs3 = [L('Gcphase',('Q1','Q2')),] \n", "C2QGs4 = [L('Gcphase',('Q2','Q3')),] \n", "C2QGs5 = [L('Gcphase',('Q3','Q0')),] \n", "C2QGs6 = [L('Gcphase',('Q0','Q1')), L('Gcphase',('Q2','Q3')),] \n", "C2QGs7 = [L('Gcphase',('Q1','Q2')), L('Gcphase',('Q3','Q0')),] " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that we often wouldn't want to start by writting down all possible sets of compatible 2-qubit gates - there can be a lot of them - and we only need to specify the compatible sets that we want to use. Continuing the example, we put all of these compatible 2-qubit gate lists into a list **`co2Qgates`**, we also pick a probability distribution over this list **`co2Qgatesprob`**, and we pick a probability **`twoQprob`** between 0 and 1." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "co2Qgates = [C2QGs1, C2QGs2, C2QGs3, C2QGs4, C2QGs5, C2QGs6, C2QGs7]\n", "co2Qgatesprob = [0.5, 0.125, 0.125, 0.125, 0.125, 0, 0]\n", "twoQprob = 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The sampler then picks a layer as follows:\n", "1. Sample a list from `co2Qgates` according to the distribution `co2Qgatesprob`.\n", "2. Consider each gate in this list, and add it to the layer with probability `twoQprob`.\n", "3. For every qubit that doesn't yet have a gate assigned to it in the layer, independently and uniformly at random, sample a 1-qubit gate to assign to that qubit, sampled from the \"native\" 1-qubit gates in the device.\n", "\n", "So with the example above there is a 50% probability of no 2-qubit gates in a layer, a 50% chance that there is one 2-qubit gate in the layer, there is no probability of more than one 2-qubit gate in the layer, and each of the 4 possible 2-qubit gates is equally likely to appear in a layer.\n", "\n", "Note that there is more than one way to achieve the same sampling here. Instead, we could have set `co2Qgatesprob = [0,0.25,0.25,0.25,0.25,0,0]` and `twoQprob = 0.5`.\n", "\n", "To use these sampler parameters, we put them (in this order) into the samplerargs list:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "samplerargs = [co2Qgates, co2Qgatesprob, twoQprob]\n", "circuit = create_random_circuit(pspec, depth, sampler=sampler, samplerargs=samplerargs)\n", "print(circuit)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is similar to the sampling used in the Direct RB experiments of [*Direct randomized benchmarking for multi-qubit devices*](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.123.030503).\n", "\n", "There is also an option for \"nested\" sets of compatible two-qubit gates. That is, `co2Qgates` can be a list where some or all of the elements are not lists containing compatable two-qubit gates, but are instead lists of lists of compatible two-qubit gates. \n", "\n", "An element of `co2Qgates` is sampled according to the `co2Qgatesprob` distribution (which defaults to the uniform distribution if not specified). If the chosen element is just a list of `Labels` (i.e., a list of compatible 2-qubit gates), the algorithm proceeds as above. But if the chosen element is a list of lists of `Labels`, the sampler picks one of these sublists uniformly at random; this sublist should be a list of compatible 2-qubit gates.\n", "\n", "This may sound complicated, so below we show how to re-write the previous example in this format. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "co2Qgates = [C2QGs1,[C2QGs2,C2QGs3,C2QGs4, C2QGs5]]\n", "co2Qgatesprob = [0.5,0.5] # This doesn't need to be specified, as the uniform dist is the default.\n", "twoQprob = 1 # This also doesn't need to be specifed, as this value is the default.\n", "samplerargs = [co2Qgates,] # We leave the latter two values off this list, because they are the defaults." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "circuit = create_random_circuit(pspec, depth, sampler=sampler, samplerargs=samplerargs)\n", "print(circuit)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "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.8.6" } }, "nbformat": 4, "nbformat_minor": 1 }