{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# This cell is added by sphinx-gallery\n# It can be customized to whatever you like\n%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"------------------------------------------------------------------------\n\n::: {.meta}\n:property=\\\"og:description\\\": We dive into two algorithms for splitting\na large quantum circuit into smaller ones.\n:::\n\n::: {.related}\ntutorial\\_qaoa\\_intro Intro to QAOA tutorial\\_qaoa\\_maxcut QAOA for\nMaxCut tutorial\\_haar\\_measure Understanding the Haar measure\ntutorial\\_unitary\\_designs Unitary designs\n:::\n\n*Authors: Gideon Uchehara, Matija Medvidovi\u0107, Anuj Apte --- Posted: 02\nSeptember 2022. Last updated: 02 September 2022.*\n\nIntroduction\n============\n\nQuantum circuits with a large number of qubits are difficult to\nsimulate. They cannot be programmed on actual hardware due to size\nconstraints (insufficient qubits), and they are also error-prone. What\nif we \\\"cut\\\" a large circuit into smaller, more manageable pieces? This\nis the main idea behind the algorithm that allows you to simulate large\nquantum circuits on a small quantum computer called *quantum circuit\ncutting*.\n\nIn this demo, we will first introduce the theory behind quantum circuit\ncutting based on Pauli measurements and see how it is implemented in\nPennyLane. This method was first introduced in. Thereafter, we discuss\nthe theoretical basis on randomized circuit cutting with two-designs and\ndemonstrate the resulting improvement in performance compared to Pauli\nmeasurement based circuit cutting for an instance of Quantum Approximate\nOptimization Algorithm (QAOA).\n\nBackground: Understanding the Pauli cutting method\n==================================================\n\nConsider a two-level quantum system in an arbitrary state, described by\ndensity matrix $\\rho$. The quantum state $\\rho$ can be expressed as a\nlinear combination of the Pauli matrices:\n\n$$\\rho = \\frac{1}{2}\\sum_{i=1}^{8} c_i Tr(\\rho O_i)\\rho_i.$$\n\nHere, we have denoted Pauli matrices by $O_i$, their eigenprojectors by\n$\\rho_i$ and their corresponding eigenvalues by $c_i$. In the above\nequation,\n\n$$O_1 = O_2 = I,$$\n\n$$O_3 = O_4 = X,$$\n\n$$O_5 = O_6 = Y$$\n\nand\n\n$$O_7 = O_8 = X.$$\n\nAlso,\n\n$$\\rho_1 = \\rho_7=\\left | {0} \\right\\rangle \\left\\langle {0} \\right |,$$\n\n$$\\rho_2 = \\rho_8 = \\left | {1} \\right\\rangle \\left\\langle {1} \\right |,$$\n\n$$\\rho_3 = \\left | {+} \\right\\rangle \\left\\langle {+} \\right |,$$\n\n$$\\rho_4 = \\left | {-} \\right\\rangle \\left\\langle {-} \\right |,$$\n\n$$\\rho_5 = \\left | {+i} \\right\\rangle \\left\\langle {+i} \\right |,$$\n\n$$\\rho_6 = \\left | {-i} \\right\\rangle \\left\\langle {-i} \\right |$$\n\nand\n\n$$c_i = \\pm 1.$$\n\nThe above equation can be implemented as a quantum circuit on a quantum\ncomputer. To do this, each term $Tr(\\rho O_i)\\rho_i$ in the equation is\nbroken into two parts. The first part, $Tr(\\rho O_i)$ is the expectation\nof the observable $O_i$ when the system is in the state $\\rho$. Let\\'s\ncall this first circuit subcircuit-$u$. The second part, $\\rho_i$ is\ninitialization or preparation of the eigenstate, $\\rho_i$. Let\\'s call\nthis Second circuit subcircuit-$v$. The above equation shows how we can\nrecover a quantum state after a is cut made on one of its qubits as\nshown in figure 1. This forms the core of quantum circuit cutting.\n\nIt turns out that we only have to do three measurements\n$\\left (Tr(\\rho X), Tr(\\rho Y), Tr(\\rho Z) \\right)$ for subcircuit-$u$\nand initialize subcircuit-$v$ with only four states:\n$\\left | {0} \\right\\rangle$, $\\left | {1} \\right\\rangle$,\n$\\left | {+} \\right\\rangle$ and $\\left | {+i} \\right\\rangle$. The other\ntwo nontrivial expectation values for states $\\left | {-} \\right\\rangle$\nand $\\left | {- i} \\right\\rangle$ can be derived with classical\npost-processing.\n\nIn general, there is a resolution of the identity along a wire (qubit)\nthat we can interpret as circuit cutting. In the following section, we\nwill provide a more clever way of resolving the same identity that leads\nto fewer shots needed to estimate observables.\n\n![Figure 1. The Pauli circuit cutting method for 1-qubit circuit. The\nfirst half of the cut circuit on the left (subcircuit-u) is the part\nwith `MeasureNode`. The second half of the cut circuit on the right\n(subcircuit-v) is the part with\n`PrepareNode`](../demonstrations/quantum_circuit_cutting/1Qubit-Circuit-Cutting.png){.align-center\nwidth=\"80.0%\"}\n\nPennyLane implementation\n------------------------\n\nPennyLane\\'s built-in circuit cutting algorithm, `qml.cut_circuit`,\ntakes a large quantum circuit and decomposes it into smaller subcircuits\nthat are executed on a small quantum device. The results from executing\nthe smaller subcircuits are then recombined through some classical\npost-processing to obtain the original result of the large quantum\ncircuit.\n\nLet's simulate a \\\"real-world\\\" scenario with `qml.cut_circuit` using\nthe circuit below.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Import the relevant libraries\nimport pennylane as qml\nfrom pennylane import numpy as np\n\ndev = qml.device(\"default.qubit\", wires=3)\n\n\n@qml.qnode(dev)\ndef circuit(x):\n qml.RX(x, wires=0)\n qml.RY(0.9, wires=1)\n qml.RX(0.3, wires=2)\n\n qml.CZ(wires=[0, 1])\n qml.RY(-0.4, wires=0)\n\n qml.CZ(wires=[1, 2])\n\n return qml.expval(qml.grouping.string_to_pauli_word(\"ZZZ\"))\n\n\nx = np.array(0.531, requires_grad=True)\nfig, ax = qml.draw_mpl(circuit)(x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Given the above quantum circuit, our goal is to simulate a 3-qubit\nquantum circuit on a 2-qubit quantum computer. This means that we have\nto cut the circuit such that the resulting subcircuits have at most 2\nqubits.\n\nApart from ensuring that the number of qubits for each subcircuit does\nnot exceed the number of qubits on our quantum device, we also have to\nensure that the resulting subcircuits have the most efficient classical\npost-processing. This is not quite trivial to determine in most cases,\nbut for the above circuit, the best cut location turns out to be between\nthe two `CZ` gates on qubit 1 (more on this later). Hence, we place a\n`WireCut` operation at that location as shown below:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"dev = qml.device(\"default.qubit\", wires=3)\n\n# Quantum Circuit with QNode\n\n\n@qml.qnode(dev)\ndef circuit(x):\n qml.RX(x, wires=0)\n qml.RY(0.9, wires=1)\n qml.RX(0.3, wires=2)\n\n qml.CZ(wires=[0, 1])\n qml.RY(-0.4, wires=0)\n\n qml.WireCut(wires=1) # Cut location\n\n qml.CZ(wires=[1, 2])\n\n return qml.expval(qml.grouping.string_to_pauli_word(\"ZZZ\"))\n\n\nx = np.array(0.531, requires_grad=True) # Defining the parameter x\nfig, ax = qml.draw_mpl(circuit)(x) # Drawing circuit"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The double vertical lines between the two `CZ` gates on qubit 1 in the\nabove figure show where we have chosen to cut. This is where the\n`WireCut` operation is inserted. `WireCut` is used to manually mark\nlocations for wire cuts.\n\nNext, we apply `qml.cut_circuit` operation as a decorator to the\n`circuit` function to perform circuit cutting on the quantum circuit.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"dev = qml.device(\"default.qubit\", wires=3)\n\n# Quantum Circuit with QNode\n\n\n@qml.cut_circuit() # Applying qml.cut_circuit for circuit cut operation\n@qml.qnode(dev)\ndef circuit(x):\n qml.RX(x, wires=0)\n qml.RY(0.9, wires=1)\n qml.RX(0.3, wires=2)\n\n qml.CZ(wires=[0, 1])\n qml.RY(-0.4, wires=0)\n\n qml.WireCut(wires=1) # Cut location\n\n qml.CZ(wires=[1, 2])\n\n return qml.expval(qml.grouping.string_to_pauli_word(\"ZZZ\"))\n\n\nx = np.array(0.531, requires_grad=True)\ncircuit(x) # Executing the quantum circuit"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let\\'s explore what happens behind the scenes in `qml.cut_circuit`. When\nthe `circuit` qnode function is executed, the quantum circuit is\nconverted to a [quantum\ntape](https://pennylane.ai/blog/2021/08/how-to-write-quantum-function-transforms-in-pennylane/)\nand then to a graph. Any `WireCut` in the quantum circuit graph is\nreplaced with `MeasureNode` and `PrepareNode` pairs as shown in figure\n2. The `MeasureNode` is the point on the cut qubit that indicates where\nto measure the observable $O_i$ after cut. On the other hand, the\n`PrepareNode` is the point on the cut qubit that indicates Where to\ninitialize the state $\\rho$ after cut. Both `MeasureNode` and\n`PrepareNode` are placeholder operations that allow us to cut the\nquantum circuit graph and then iterate over measurements of Pauli\nobservables and preparations of their corresponding eigenstates\nconfigurations at cut locations.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![Figure 2. Replace WireCut with MeasureNode and\nPrepareNode](../demonstrations/quantum_circuit_cutting/MeasurePrepareNodes.png){.align-center\nwidth=\"90.0%\"}\n\nCutting at the said location gives two graph fragments with 2 qubits\neach. To separate these fragments into different subcircuit graphs, the\n`fragment_graph()` function is called to pull apart the quantum circuit\ngraph as shown in figure 3. The subcircuit graphs are reconverted back\nto quantum tapes and `qml.cut_circuit` runs multiple configurations of\nthe 2-qubit subcircuit tapes which are then post-processed to replicate\nthe result of the uncut circuit.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![Figure 3. Separate fragments into different\nsubcircuits](../demonstrations/quantum_circuit_cutting/separateMeasurePrepareNodes.png){.align-center\nwidth=\"90.0%\"}\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Automatic cut placement**\n===========================\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We manually found a good cut position, but what if we didn\\'t know where\nit was in general? Changing cut positions results in different outcomes\nin terms of simulation efficiency, so choosing the optimal cut reduces\npost-processing overhead and improves simulation efficiency.\n\nAutomatic cut placment is a PennyLane functionality that aids us in\nfinding the optimal cut that fragments a circuit such that the classical\npost-processing overhead is minimized. The main algorithm behind\nautomatic cut placement is [graph partitioning](https://kahypar.org/)\n\nIf `auto_cutter` is enabled in `qml.cut_circuit`, PennyLane makes\nattempts to find an optimal cut using graph partitioning. Whenever it is\ndifficult to manually determine the optimal cut location, this is the\nrecommended approach to circuit cutting. The following example shows\nthis capability on the same circuit as above but with the `WireCut`\nremoved.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"dev = qml.device(\"default.qubit\", wires=3)\n\n\n@qml.cut_circuit(auto_cutter=True) # auto_cutter enabled\n@qml.qnode(dev)\ndef circuit(x):\n qml.RX(x, wires=0)\n qml.RY(0.9, wires=1)\n qml.RX(0.3, wires=2)\n\n qml.CZ(wires=[0, 1])\n qml.RY(-0.4, wires=0)\n\n qml.CZ(wires=[1, 2])\n\n return qml.expval(qml.grouping.string_to_pauli_word(\"ZZZ\"))\n\n\nx = np.array(0.531, requires_grad=True)\ncircuit(x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Randomized Circuit Cutting\n==========================\n\nAfter reviewing the standard circuit cutting based on Pauli measurements\non single qubits, we are now ready to discuss an improved circuit\ncutting protocol that uses randomized measurements to speed up circuit\ncutting. Our description of this method will be based on the recently\npublished work.\n\nThe key idea behind this approach is to use measurements in an entagled\nbasis that is based on a unitary 2-design to get more information about\nthe state with fewer measurements compared to single qubit Pauli\nmeasurements.\n\nThe concept of 2-designs is simple --- a unitary 2-design is finite\ncollection of unitaries such that the average of any degree 2 polynomial\nfunction of a linear operator over the design is exactly the same as the\naverage over Haar random measure. For further explanation of this\nmeasure read the [Haar Measure\ndemo](https://pennylane.ai/qml/demos/tutorial_haar_measure.html).\n\nMore precisely, let $P(U)$ be a polynomial with homogeneous degree at\nmost two in the entries of a unitary matrix $U$, and degree two in the\ncomplex conjugates of those entries. A unitary 2-design is a set of $L$\nunitaries $\\{U_{L}\\}$ such that\n\n$$\\frac{1}{L} \\sum_{l=1}^{L} P(U_l) = \\int_{\\mathcal{U}(d)} P (U) d\\mu(U)~.$$\n\nThe elemements of the Clifford group over the qubits being cut are an\nexample of a 2-design. We don't have a lot of space here to go into too\nmany details. But fear not - there is an [entire\ndemo](https://pennylane.ai/qml/demos/tutorial_unitary_designs.html)\ndedicated to this wonderful topic!\n\n![Figure 4. Illustration of Randomized Circuit Cutting based on\nTwo-Designs. Taken\nfrom.](../demonstrations/quantum_circuit_cutting/flowchart.svg){.align-center\nwidth=\"90.0%\"}\n\nIf $k$ qubits are being cut, then the dimensionality of the Hilbert\nspace is $d=2^{k}$. The key idea of Randomized Circuit Cutting is to\nemploy two different quantum channels with probabilities such that\ntogether they comprise a resolution of Identity. In the randomized\nmeasurement circuit cutting procedure, we trace out the $k$ qubits and\nprepare a random basis state with probability $d/(2d+1)$. For a linear\noperator $X \\in \\mathbf{L}(\\mathbb{C}^{d})$ acting on the $k$ qubits,\nthis operation corresponds to the completely depolarizing channel\n\n$$\\Psi_{1}(X) = \\textrm{Tr}(X)\\frac{\\mathbf{1}}{d}~.$$\n\nOtherwise, we perform measure-and-prepare protocol based on a unitary\n2-design (e.g.\u00a0a random Clifford) with probability $(d+1)/(2d+1)$,\ncorresponding to the channel\n\n$$\\Psi_{0}(X) = \\frac{1}{d+1}\\left(\\textrm{Tr}(X)\\mathbf{1} + X\\right)~.$$\n\nThe sets of Kraus operators for the channels $\\Psi_{1} and \\Psi_{0}$ are\n\n$$\\Psi_{1}(X) \\xrightarrow{} \\left\\{ \\frac{|i\\rangle \\langle j|}{\\sqrt{d}} \\right\\} \\quad\n\\Psi_{0}(X) \\xrightarrow{} \\left\\{ \\frac{\\mathbf{1}}{\\sqrt{d+1}} ~,~ \\frac{|i\\rangle \\langle j|}{\\sqrt{d+1}} \\right\\}~,$$\n\nwhere indices $i,j$ run over the $d$ basis elements.\n\nTogether, these two channels can be used to obtain a resolution of the\nIdentity channel on the $k$-qubits as follows\n\n$$X = (d+1)\\Psi_0(X)-d\\Psi_1(X)~.$$\n\nBy employing this procedure, we can estimate the outcome of the original\ncircuit by using the cut circuits. For an error threshold of\n$\\varepsilon$, the associated overhead is\n$O(4^{k}(n+k^{2})/\\varepsilon^{2})$. When $k$ is a small constant and\nthe circuit is cut into roughly two equal halves, this procedure\neffectively doubles the number of qubits that can be simulated given a\nquantum device, since the overhead is $O(4^k)$ which is much lower than\nthe $O(16^k)$ overhead of cutting with single-qubit measurements. Note\nthat, although the overhead incurred is smaller, the average depth of\nthe circuit is greater since a random Clifford unitary over the $k$\nqubits has to be implemented when randomized measurement is performed.\n\nComparison\n==========\n\nWe have seen that looking at circuit cutting through the lens of\n2-designs can be a source of considerable speedups. A good test case\nwhere one may care about accurately estimating an observable is the\n[Quantum Approximate Optimization\nAlgorithm](https://pennylane.ai/qml/demos/tutorial_qaoa_intro.html)\n(QAOA). In its simplest form, QAOA concerns itself with finding a lowest\nenergy state of a *cost hamiltonian* $H_{\\mathcal{C}}$:\n\n$$H_\\mathcal{C} = \\frac{1}{|E|} \\sum _{(i, j) \\in E} Z_i Z_j$$\n\non a graph $G=(V,E)$, where $Z_i$ is a Pauli-$Z$ operator. The\nnormalization factor is just here so that expectation values do not lie\noutside the $[-1,1]$ interval.\n\nSetup\n-----\n\nSuppose that we have a specific class of graphs we care about and\nsomeone already provided us with optimal angles $\\gamma$ and $\\beta$ for\nQAOA of depth $p=1$. Here's how to map the input graph $G$ to the QAOA\ncircuit that solves our problem:\n\n![Figure 5. An example of mapping an input interaction graph to a QAOA\ncircuit. (Note: the \"stick\" gates represent the ZZ rotation gates, to\navoid overcrowding the\ndiagram.)](../demonstrations/quantum_circuit_cutting/graph_to_circuit.svg){.align-center\nwidth=\"90.0%\"}\n\nLet's generate a similar QAOA graph to the one in the figure this using\n[NetworkX](https://networkx.org/)!\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import networkx as nx\nfrom itertools import product, combinations\n\nnp.random.seed(1337)\n\nn_side_nodes = 2\nn_middle_nodes = 3\n\ntop_nodes = range(0, n_side_nodes)\nmiddle_nodes = range(n_side_nodes, n_side_nodes + n_middle_nodes)\nbottom_nodes = range(n_side_nodes + n_middle_nodes, n_middle_nodes + 2 * n_side_nodes)\n\ntop_edges = list(product(top_nodes, middle_nodes))\nbottom_edges = list(product(middle_nodes, bottom_nodes))\n\ngraph = nx.Graph()\ngraph.add_edges_from(combinations(top_nodes, 2), color=0)\ngraph.add_edges_from(top_edges, color=0)\ngraph.add_edges_from(bottom_edges, color=1)\ngraph.add_edges_from(combinations(bottom_nodes, 2), color=1)\n\nnx.draw_spring(graph, with_labels=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For this graph, optimal QAOA parameters read:\n\n$$\\gamma ^* \\approx -0.240 \\; ; \\qquad \\beta ^* \\approx 0.327 \\quad \\Rightarrow \\quad \\left\\langle H_\\mathcal{C} \\right\\rangle ^* \\approx -0.248$$\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"optimal_params = np.array([-0.240, 0.327])\noptimal_cost = -0.248"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We also define our cost operator $H_{\\mathcal{C}}$ as a function.\nBecause it is diagonal in the computational basis, we only need to\ndefine its action on computational basis bitstrings.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def qaoa_cost(bitstring):\n\n bitstring = np.atleast_2d(bitstring)\n # Make sure that we operate correctly on a batch of bitstrings\n\n z = (-1) ** bitstring[:, graph.edges()] # Filter out pairs of bits correspondimg to graph edges\n costs = z.prod(axis=-1).sum(axis=-1) # Do products and sums\n return np.squeeze(costs) / len(graph.edges) # Normalize"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's make a quick and simple QAOA circuit in PennyLane. Before we\nactually cut the circuit, we have to briefly think about the cut\nplacement. First, we want to apply all ZZ rotation gates corresponding\nto the `top_edges`, place the wire cut, and then the `bottom_edges`, to\nensure that the circuit actually splits in two after cutting.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def qaoa_template(params):\n\n gamma, beta = params\n\n for i in range(len(graph)): # Apply the Hadamard gates\n qml.Hadamard(wires=i)\n\n for i, j in top_edges:\n\n # Apply the ZZ rotation gates\n # corresponding to the\n # green edges in the figure\n\n qml.MultiRZ(2 * gamma, wires=[i, j])\n\n qml.WireCut(wires=middle_nodes) # Place the wire cut\n\n for i, j in bottom_edges:\n\n # Apply the ZZ rotation gates\n # corresponding to the\n # purple edges in the figure\n\n qml.MultiRZ(2 * gamma, wires=[i, j])\n\n for i in graph.nodes(): # Finally, apply the RX gates\n qml.RX(2 * beta, wires=i)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's construct the `QuantumTape` corresponding to this template and\ndraw the circuit:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from pennylane.tape import QuantumTape\n\nall_wires = list(range(len(graph)))\n\nwith QuantumTape() as tape:\n qaoa_template(optimal_params)\n qml.sample(wires=all_wires)\n\nfig, _ = qml.drawer.tape_mpl(tape, expansion_strategy=\"device\")\nfig.set_size_inches(12, 6)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The Pauli cutting method\n========================\n\nTo run fragment subcircuits and combine them into a finite-shot estimate\nof the optimal cost function using the Pauli cut method, we can use\nbuilt-in PennyLane functions. We simply use the `qml.cut_circuit_mc`\ntransform and everything is taken care of for us.\n\nNote that we have already introduced the `qml.cut_circuit` transform in\nthe previous section. The `_mc` appendix stands for Monte Carlo and is\nused to calculate finite-shot estimates of observables. The observable\nitself is passed to the `qml.cut_circuit_mc` transform as a function\nmapping a bitstring (circuit sample) to a single number.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"dev = qml.device(\"default.qubit\", wires=all_wires)\n\n\n@qml.cut_circuit_mc(classical_processing_fn=qaoa_cost)\n@qml.qnode(dev)\ndef qaoa(params):\n qaoa_template(params)\n return qml.sample(wires=all_wires)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can obtain the cost estimate by simply running `qaoa` like a \"normal\"\n`QNode`. Let's do just that for a grid of values so we can study\nconvergence.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"n_shots = 10000\n\nshot_counts = np.logspace(1, 4, num=20, dtype=int, requires_grad=False)\npauli_cost_values = np.zeros_like(shot_counts, dtype=float)\n\nfor i, shots in enumerate(shot_counts):\n pauli_cost_values[i] = qaoa(optimal_params, shots=shots)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We will save these results for later and plot them together with results\nof the randomized measurement method.\n\nThe randomized channel-based cutting method\n===========================================\n\nAs noted earlier, the easiest way to mathematically represent the\nrandomized channel-based method is to write down Kraus operators for the\nrelevant channels, $\\Psi _0$ and $\\Psi _1$. Once we have represented\nthem in explicit matrix form, we can simply use `qml.QubitChannel`.\n\nTo get our matrices, we represent the computational basis set along the\n$k$ cut wires as a unit vector\n\n$$\\left\\vert i \\right\\rangle \\mapsto (0, \\ldots, 1,\\ldots,0)$$\n\nwith the 1 positioned at index $i$. Therefore:\n\n$$\\begin{aligned}\n\\left\\vert i \\right\\rangle \\left\\langle j \\right\\vert \\mapsto \\begin{pmatrix}\n 0 & 0 & \\cdots & 0 & 0 \\\\\n 0 & \\ddots & \\cdots & 0 & 0 \\\\\n \\vdots & 0 & 1 & 0 & \\vdots \\\\\n 0 & 0 & \\cdots & \\ddots & 0 \\\\\n 0 & 0 & \\cdots & 0 & 0 \\\\\n\\end{pmatrix}\n\\end{aligned}$$\n\nwhere the 1 sits at column $i$ and row $j$.\n\nGiven this representation, a neat way to get all Kraus operators' matrix\nrepresentations is the following:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def make_kraus_ops(num_wires: int):\n\n d = 2**num_wires\n\n # High level idea: Take the identity operator on d^2 x d^2 and look at each row independently.\n # When reshaped into a matrix, it gives exactly the matrix representation of |i>