{
"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": [
"Differentiating quantum error mitigation transforms\n===================================================\n\n::: {.meta}\n:property=\\\"og:description\\\": Differentiable error mitigation\n:property=\\\"og:image\\\":\n\n:::\n\n::: {.related}\ntutorial\\_error\\_mitigation Error mitigation with Mitiq and PennyLane\n:::\n\n*Author: Korbinian Kottmann --- Posted: 22 August 2022.*\n\nError mitigation is an important strategy for minimizing noise when\nusing noisy-intermediate scale quantum (NISQ) hardware, especially when\ndesigning and testing variational algorithms. In this demo, we will show\nhow error mitigation can be combined with variational workflows,\nallowing you to differentiate [through]{.title-ref} the error\nmitigation.\n\nDifferentiating quantum error mitigation transforms\n---------------------------------------------------\n\nMost variational quantum algorithms (VQAs) are concerned with optimizing\na [quantum function]{.title-ref},\n\n$$f(\\theta) = \\langle 0 | U^\\dagger(\\theta) H U(\\theta) | 0 \\rangle,$$\n\nfor some Ansatz unitary $U$ with variational parameters $\\theta$ and\nobservable $H$. These algorithms arose due to the constraints of noisy\nnear-term quantum hardware. This means that naturally in that scenario\nwe do not have direct access to $f$, but rather a noisy version $f^{\u26a1}$\nwhere the variational state\n$|\\psi(\\theta)\\rangle = U^\\dagger(\\theta)|0\\rangle$ is distorted via a\nnoise channel $\\Phi(|\\psi(\\theta)\\rangle \\langle \\psi(\\theta)|)$. Since\nnoisy channels generally yield mixed states (see e.g.\n`tutorial_noisy_circuits`{.interpreted-text role=\"doc\"}), we can\nformally write\n\n$$f^{\u26a1}(\\theta) := \\text{tr}\\left[H \\Phi(|\\psi(\\theta)\\rangle \\langle \\psi(\\theta)|) \\right].$$\n\nTo be able to get the most out of these devices, it is advisable to use\nquantum error mitigation \\-\\-- a method of altering and/or\npost-processing the quantum function $f^{\u26a1}(\\theta)$ to improve the\nresult and be closer to the ideal scenario of an error free execution,\n$f(\\theta)$.\n\nFormally, we can treat error mitigation as yet another transform that\nmaps the noisy quantum function $f^{\u26a1}$ to a new, mitigated, quantum\nfunction $\\tilde{f}$,\n\n$$\\text{mitigate}: f^{\u26a1} \\mapsto \\tilde{f}.$$\n\nIn order to run our VQA with our mitigated quantum function, we need to\nensure that $\\tilde{f}$ is differentiable \\-\\-- both formally and\npractically in our implementation. PennyLane now provides one such\ndifferentiable quantum error mitigation technique with [zero noise\nextrapolation]{.title-ref} (ZNE), which can be used and differentiated\nin simulation and on hardware. Thus, we can improve the estimates of\nobservables without breaking the differentiable workflow of our\nvariational algorithm. We will briefly introduce these functionalities\nand afterwards go more in depth to explore what happens under the hood.\n\nWe start by initializing a noisy device under the\n`~.pennylane.DepolarizingChannel`{.interpreted-text role=\"class\"}:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import pennylane as qml\nimport pennylane.numpy as np\nfrom pennylane.transforms import mitigate_with_zne\n\nfrom matplotlib import pyplot as plt\n\nn_wires = 4\nnp.random.seed(1234)\n\n# Describe noise\nnoise_gate = qml.DepolarizingChannel\nnoise_strength = 0.05\n\n# Load devices\ndev_ideal = qml.device(\"default.mixed\", wires=n_wires)\ndev_noisy = qml.transforms.insert(noise_gate, noise_strength, position=\"all\")(dev_ideal)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We are going to use the transverse field Ising model Hamiltonian\n$H = - \\sum_i X_i X_{i+1} + 0.5 \\sum_i Z_i$ as our observable:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"coeffs = [1.0] * (n_wires - 1) + [0.5] * n_wires\nobservables = [qml.PauliX(i) @ qml.PauliX(i + 1) for i in range(n_wires - 1)]\nobservables += [qml.PauliZ(i) for i in range(n_wires)]\n\nH = qml.Hamiltonian(coeffs, observables)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The quantum function, the expectation value of $H$, can then be executed\non the noisy or ideal device by creating respective QNodes for both. As\nour ansatz, we\\'ll use a\n`~.pennylane.SimplifiedTwoDesign`{.interpreted-text role=\"class\"} with\nall-constant parameters set to `1`:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"n_layers = 2\n\nw1 = np.ones((n_wires), requires_grad=True)\nw2 = np.ones((n_layers, n_wires - 1, 2), requires_grad=True)\n\ndef qfunc(w1, w2):\n qml.SimplifiedTwoDesign(w1, w2, wires=range(n_wires))\n return qml.expval(H)\n\nqnode_noisy = qml.QNode(qfunc, dev_noisy)\nqnode_ideal = qml.QNode(qfunc, dev_ideal)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can then simply transform the noisy QNode $f^{\u26a1}$ with\n`~.pennylane.transforms.mitigate_with_zne`{.interpreted-text\nrole=\"func\"} to generate $\\tilde{f}$. If everything goes as planned,\nexecuting the mitigated QNode is then closer to the ideal result:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"scale_factors = [1, 2, 3]\n\nqnode_mitigated = mitigate_with_zne(\n scale_factors=scale_factors,\n folding=qml.transforms.fold_global,\n extrapolate=qml.transforms.richardson_extrapolate,\n)(qnode_noisy)\n\nprint(\"Ideal QNode: \", qnode_ideal(w1, w2))\nprint(\"Mitigated QNode: \", qnode_mitigated(w1, w2))\nprint(\"Noisy QNode: \", qnode_noisy(w1, w2))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The transforms provided for the `folding` and `extrapolate` arguments\ncan be treated as default black boxes for the moment. We will explain\nthem in more detail in the following section.\n\nThe cool thing about this new mitigated QNode is that it is still\ndifferentiable! That is, we can compute its gradient as usual:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"grad = qml.grad(qnode_mitigated)(w1, w2)\nprint(grad[0])\nprint(grad[1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Under the hood of Zero Noise Extrapolation\n==========================================\n\nWhat is happening here under the hood? The basic idea of ZNE is to\nartificially increase the noise in a circuit, controlled by a parameter\n$\\lambda$ that is called the `scale_factor`, to then be able to\nextrapolate back to zero noise.\n\nConsider two circuits: $U$ and $U U^\\dagger U$. They are logically\nequivalent, but we can expect the latter to have more noise due its\nlarger gate count. This is the underlying concept of unitary folding,\nwhich is used to artificially increase the noise of a quantum function.\nGiven a unitary circuit $U = L_d .. L_1$, where $L_i$ can be either a\ngate or layer, we use\n`~.pennylane.transforms.fold_global`{.interpreted-text role=\"func\"} to\nconstruct\n\n$$\\texttt{fold_global}(U) = U (U^\\dagger U)^n (L^\\dagger_d L^\\dagger_{d-1} .. L^\\dagger_s) (L_s .. L_d),$$\n\nwhere $n = \\lfloor (\\lambda - 1)/2 \\rfloor$ and\n$s = \\lfloor \\left((\\lambda -1) \\mod 2 \\right) (d/2) \\rfloor$ are\ndetermined via the `scale_factor` $\\lambda$.\n\nThe version of ZNE that we are showcasing is simply executing the noisy\nquantum function $f^{\u26a1}$ for different scale factors, and then\nextrapolate to $\\lambda \\rightarrow 0$ (zero noise). This is done with a\npolynomial fit in $f^{\u26a1}$ as a function of $\\lambda$. Note that\n`scale_factor = 1` corresponds to the original circuit, i.e. the noisy\nexecution.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"scale_factors = [1, 2, 3]\nfolded_res = [\n qml.transforms.fold_global(qnode_noisy, lambda_)(w1, w2) for lambda_ in scale_factors\n]\n\nideal_res = qnode_ideal(w1, w2)\n\n# coefficients are ordered like\n# coeffs[0] * x**2 + coeffs[1] * x + coeffs[0]\n# i.e. fitted_func(0)=coeff[-1]\ncoeffs = np.polyfit(scale_factors, folded_res, 2)\nzne_res = coeffs[-1]\n\nx_fit = np.linspace(0, scale_factors[-1], 20)\ny_fit = np.poly1d(coeffs)(x_fit)\n\nplt.figure(figsize=(8, 5))\nplt.plot(scale_factors, folded_res, \"x--\", label=\"folded result\")\nplt.plot(0, ideal_res, \"X\", label=\"ideal result\")\nplt.plot(0, zne_res, \"X\", label=\"ZNE result\", color=\"tab:red\")\nplt.plot(x_fit, y_fit, label=\"fit\", color=\"tab:red\", alpha=0.5)\nplt.xlabel(\"$\\\\lambda$\")\nplt.ylabel(\"f\u26a1\")\nplt.legend()\nplt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We see that the mitigated result comes close to the ideal result,\nwhereas the noisy result is further off (see value at `scale_factor=1`).\n\nNote that this folding scheme is relatively simple and only really is\nsensible for integer values of `scale_factor`. At the same time,\n`scale_factor` is limited from above by the noise as the noisy quantum\nfunction quickly decoheres under this folding. I.e., for $\\lambda\\geq 4$\nthe results are typically already decohered. Therefore, one typically\nonly uses `scale_factors = [1, 2, 3]`. In principle, one can think of\nmore fine grained folding schemes and test them by providing custom\nfolding operations. How this can be done in PennyLane with the given API\nis described in\n`~.pennylane.transforms.mitigate_with_zne`{.interpreted-text\nrole=\"func\"}.\n\nNote that Richardson extrapolation, which we used to define the\n`mitigated_qnode`, is just a fancy way to describe a polynomial fit of\n`order = len(x) - 1`. Alternatively, you can use\n`~.pennylane.transforms.poly_extrapolate`{.interpreted-text role=\"func\"}\nand manually pass the order via a keyword argument\n`extrapolate_kwargs={'order': 2}`.\n\nDifferentiable mitigation in a variational quantum algorithm\n============================================================\n\nWe will now use mitigation while we optimize the parameters of our\nvariational circuit to obtain the ground state of the Hamiltonian \\-\\--\nthis is the variational quantum eigensolving (VQE), see\n`tutorial_vqe`{.interpreted-text role=\"doc\"}. Then, we will compare VQE\noptimization runs for the ideal, noisy, and mitigated QNodes and see\nthat the mitigated one comes close to the ideal (zero noise) results,\nwhereas the noisy execution is further off.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def VQE_run(cost_fn, max_iter, stepsize=0.1):\n \"\"\"VQE Optimization loop\"\"\"\n opt = qml.AdamOptimizer(stepsize=stepsize)\n\n # fixed initial guess\n w1 = np.ones((n_wires), requires_grad=True)\n w2 = np.ones((n_layers, n_wires - 1, 2), requires_grad=True)\n\n energy = []\n\n # Optimization loop\n for _ in range(max_iter):\n (w1, w2), prev_energy = opt.step_and_cost(cost_fn, w1, w2)\n\n energy.append(prev_energy)\n\n energy.append(cost_fn(w1, w2))\n\n return energy\n\n\nmax_iter = 70\n\nenergy_ideal = VQE_run(qnode_ideal, max_iter)\nenergy_noisy = VQE_run(qnode_noisy, max_iter)\nenergy_mitigated = VQE_run(qnode_mitigated, max_iter)\n\nenergy_exact = np.min(np.linalg.eigvalsh(qml.matrix(H)))\n\nplt.figure(figsize=(8, 5))\nplt.plot(energy_noisy, \".--\", label=\"VQE E_noisy\")\nplt.plot(energy_mitigated, \".--\", label=\"VQE E_mitigated\")\nplt.plot(energy_ideal, \".--\", label=\"VQE E_ideal\")\nplt.plot([1, max_iter + 1], [energy_exact] * 2, \"--\", label=\"E_exact\")\nplt.legend(fontsize=14)\nplt.xlabel(\"Iteration\", fontsize=18)\nplt.ylabel(\"Energy\", fontsize=18)\nplt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We see that during the optimization we are for the most part\nsignificantly closer to the ideal simulation and end up with a better\nenergy compared to executing the noisy device without ZNE.\n\nSo far we have been using PennyLane gradient methods that use `autograd`\nfor simulation and `parameter-shift` rules for real device executions.\nWe can also use the other interfaces that are supported by PennyLane,\n`jax`, `torch` and `tensorflow`, in the usual way as described in the\ninterfaces section of the documentation\n`introduction/interfaces`{.interpreted-text role=\"doc\"}.\n\nDifferentiating the mitigation transform itself\n===============================================\n\nIn the previous sections, we have been concerned with differentiating\n[through]{.title-ref} the mitigation transform. An interesting direction\nfor future work is differentiating the transform itself[^1]. In\nparticular, the authors in[^2] make the interesting observation that for\nsome error mitigation schemes, the cost function is smooth in some of\nthe mitigation parameters. Here, we show one of their examples, which is\na time-sensitive dynamical decoupling scheme:\n\n![Time-sensitive dynamical decoupling\nscheme.](/demonstrations/diffable-mitigation/Mitigate_real_vs_sim3.png){.align-center\nwidth=\"50.0%\"}\n\nIn this mitigation technique, the single qubit state is put into an\nequal superposition: $|+\\rangle = (|0\\rangle + |1\\rangle)/\\sqrt{2}$.\nDuring the first idle time $t_1$, the state is altered due to noise.\nApplying $X$ reverses the roles of each computational basis state. The\nidea is that the noise in the second idle time $T-t_1$ is cancelling out\nthe effect of the first time window. We see that the output fidelity is\na smooth function of $t_1$. This was executed on `ibm_perth`, and we\nnote that simple noise models, like the simulated IBM device, do not\nsuffice to reproduce the behavior of the real device.\n\nObtaining the gradient with respect to this parameter is difficult.\nFormally, writing down the derivative of this transform with respect to\nthe idle time in order to derive its parameter-shift rules would require\naccess to the noise model. This is very difficult for a realistic\nscenario. Further, most mitigation parameters are integers and would\nhave to be smoothed in a differentiable way. A simple but effective\nstrategy is using finite differences for the gradient with respect to\nmitigation parameters.\n\nOverall, this is a nice example of a mitigation scheme where varying the\nmitigation parameter has direct impact to the simulation result. It is\ntherefore desirable to be able to optimize this parameter at the same\ntime as we perform a variational quantum algorithm.\n\nConclusion\n==========\n\nWe demonstrated how zero-noise extrapolation can be seamlessly\nincorporated in a differentiable workflow in PennyLane to achieve better\nresults. Further, the possibility of differentiating error mitigation\ntransforms themselves has been discussed and we have seen that some\nmitigation schemes require execution on real devices or more advanced\nnoise simulations.\n\nReferences\n==========\n\nAbout the author\n================\n\n[^1]: Olivia Di Matteo, Josh Izaac, Tom Bromley, Anthony Hayes,\n Christina Lee, Maria Schuld, Antal Sz\u00e1va, Chase Roberts, Nathan\n Killoran. \\\"Quantum computing with differentiable quantum\n transforms.\\\" [arXiv:2202.13414](https://arxiv.org/abs/2202.13414),\n 2021.\n\n[^2]: Gokul Subramanian Ravi, Kaitlin N. Smith, Pranav Gokhale, Andrea\n Mari, Nathan Earnest, Ali Javadi-Abhari, Frederic T. Chong. \\\"VAQEM:\n A Variational Approach to Quantum Error Mitigation.\\\"\n [arXiv:2112.05821](https://arxiv.org/abs/2112.05821), 2021.\n"
]
}
],
"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.9.15"
}
},
"nbformat": 4,
"nbformat_minor": 0
}