{
"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": [
"Error mitigation with Mitiq and PennyLane\n=========================================\n\n::: {.meta}\n:property=\\\"og:description\\\": Learn how to mitigate quantum circuits\nusing Mitiq and PennyLane.\n\n:property=\\\"og:image\\\": \n:::\n\n::: {.related}\ntutorial\\_chemical\\_reactions Modelling chemical reactions on a quantum\ncomputer tutorial\\_noisy\\_circuits Noisy circuits\n:::\n\n*Authors: Tom Bromley (PennyLane) and Andrea Mari (Mitiq) --- Posted: 29\nNovember 2021. Last updated: 29 November 2021.*\n\nHave you ever run a circuit on quantum hardware and not quite got the\nresult you were expecting? If so, welcome to the world of noisy\nintermediate-scale quantum (NISQ) devices! These devices must function\nin noisy environments and are unable to execute quantum circuits\nperfectly, resulting in outputs that can have a significant error. The\nlong-term plan of quantum computing is to develop a subsequent\ngeneration of error-corrected hardware. In the meantime, how can we best\nutilize our error-prone NISQ devices for practical tasks? One proposed\nsolution is to adopt an approach called error *mitigation*, which aims\nto minimize the effects of noise by executing a family of related\ncircuits and using the results to estimate an error-free value.\n\n![](../demonstrations/error_mitigation/laptop.png){.align-center}\n\nThis demo shows how error mitigation can be carried out by combining\nPennyLane with the [Mitiq](https://github.com/unitaryfund/mitiq)\npackage, a Python-based library providing a range of error mitigation\ntechniques. Integration with PennyLane is available from the `0.11`\nversion of Mitiq, which can be installed using\n\n``` {.bash}\npip install \"mitiq>=0.11\"\n```\n\nWe\\'ll begin the demo by jumping straight into the deep end and seeing\nhow to mitigate a simple noisy circuit in PennyLane with Mitiq as a\nbackend. After, we\\'ll take a step back and discuss the theory behind\nthe error mitigation approach we used, known as zero-noise\nextrapolation. Using this knowledge, we\\'ll give a more detailed\nexplanation of how error mitigation can be carried out in PennyLane. The\nfinal part of this demo showcases the application of mitigation to\nquantum chemistry, allowing us to more accurately calculate the\npotential energy surface of molecular hydrogen.\n\nMitigating noise in a simple circuit\n------------------------------------\n\nWe first need a noisy device to execute our circuit on. Let\\'s keep\nthings simple for now by loading the\n`default.mixed `{.interpreted-text\nrole=\"mod\"} simulator and artificially adding\n`PhaseDamping `{.interpreted-text role=\"class\"}\nnoise.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import pennylane as qml\n\nn_wires = 4\n\n# Describe noise\nnoise_gate = qml.PhaseDamping\nnoise_strength = 0.1\n\n# Load devices\ndev_ideal = qml.device(\"default.mixed\", wires=n_wires)\ndev_noisy = qml.transforms.insert(noise_gate, noise_strength)(dev_ideal)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the above, we load a noise-free device `dev_ideal` and a noisy device\n`dev_noisy`, which is constructed from the\n`qml.transforms.insert `{.interpreted-text\nrole=\"func\"} transform. This transform works by intercepting each\ncircuit executed on the device and adding the\n`PhaseDamping `{.interpreted-text role=\"class\"}\nnoise channel directly after every gate in the circuit. To get a better\nunderstanding of noise channels like\n`PhaseDamping `{.interpreted-text role=\"class\"},\ncheck out the `tutorial_noisy_circuits`{.interpreted-text role=\"doc\"}\ntutorial.\n\nThe next step is to define our circuit. Inspired by the mirror circuits\nconcept introduced by Proctor *et al.* let\\'s fix a circuit that applies\na unitary $U$ followed by its inverse $U^{\\dagger}$, with $U$ given by\nthe\n`SimplifiedTwoDesign `{.interpreted-text\nrole=\"class\"} template. We also fix a measurement of the\n`PauliZ `{.interpreted-text role=\"class\"} observable\non our first qubit. Importantly, such a circuit performs an identity\ntransformation $U^{\\dagger} U |\\psi\\rangle = |\\psi\\rangle$ to any input\nstate $|\\psi\\rangle$ and we can show that the expected value of an ideal\ncircuit execution with an input state $|0\\rangle$ is\n\n$$\\langle 0 | U U^{\\dagger} Z U^{\\dagger} U | 0 \\rangle = 1.$$\n\nAlthough this circuit seems trivial, it provides an ideal test case for\nbenchmarking noisy devices where we expect the output to be less than\none due to the detrimental effects of noise. Let\\'s check this out in\nPennyLane code:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from pennylane import numpy as np\n\nnp.random.seed(1967)\n\n# Select template to use within circuit and generate parameters\nn_layers = 1\ntemplate = qml.SimplifiedTwoDesign\nweights_shape = template.shape(n_layers, n_wires)\nw1, w2 = [2 * np.pi * np.random.random(s) for s in weights_shape]\n\n\ndef circuit(w1, w2):\n template(w1, w2, wires=range(n_wires))\n qml.adjoint(template)(w1, w2, wires=range(n_wires))\n return qml.expval(qml.PauliZ(0))\n\n\nideal_qnode = qml.QNode(circuit, dev_ideal)\nnoisy_qnode = qml.QNode(circuit, dev_noisy)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, we\\'ll visualize the circuit:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(qml.draw(ideal_qnode, expansion_strategy=\"device\")(w1, w2))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As expected, executing the circuit on an ideal noise-free device gives a\nresult of `1`.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"ideal_qnode(w1, w2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"On the other hand, we obtain a noisy result when running on `dev_noisy`:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"noisy_qnode(w1, w2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So, we have set ourselves up with a benchmark circuit and seen that\nexecuting on a noisy device gives imperfect results. Can the results be\nimproved? Time for error mitigation! We\\'ll first show how easy it is to\nadd error mitigation in PennyLane with Mitiq as a backend, before\nexplaining what is going on behind the scenes.\n\n::: {.note}\n::: {.title}\nNote\n:::\n\nTo run the code below you will need to have the Qiskit plugin installed.\nThis plugin can be installed using:\n\n``` {.bash}\npip install pennylane-qiskit\n```\n\nThe Qiskit plugin is required to convert our PennyLane circuits to\nOpenQASM 2.0, which is used as an intermediate representation when\nworking with Mitiq.\n:::\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from mitiq.zne.scaling import fold_global\nfrom mitiq.zne.inference import RichardsonFactory\nfrom pennylane.transforms import mitigate_with_zne\n\nextrapolate = RichardsonFactory.extrapolate\nscale_factors = [1, 2, 3]\n\nmitigated_qnode = mitigate_with_zne(scale_factors, fold_global, extrapolate)(\n noisy_qnode\n)\nmitigated_qnode(w1, w2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Amazing! Using PennyLane\\'s\n`mitigate_with_zne `{.interpreted-text\nrole=\"func\"} transform, we can create a new `mitigated_qnode` whose\nresult is closer to the ideal noise-free value of `1`. How does this\nwork?\n\nUnderstanding error mitigation\n==============================\n\nError mitigation can be realized through a number of techniques, and the\nMitiq [documentation](https://mitiq.readthedocs.io/en/stable/) is a\ngreat resource to start learning more. In this demo, we focus upon the\nzero-noise extrapolation (ZNE) method originally introduced by Temme et\nal. and Li et al..\n\nThe ZNE method works by assuming that the amount of noise present when a\ncircuit is run on a noisy device is enumerated by a parameter $\\gamma$.\nSuppose we have an input circuit that experiences an amount of noise\n$\\gamma = \\gamma_{0}$ when executed. Ideally, we would like to evaluate\nthe result of the circuit in the $\\gamma = 0$ noise-free setting.\n\nTo do this, we create a family of equivalent circuits whose ideal\nnoise-free value is the same as our input circuit. However, when run on\na noisy device, each circuit experiences an amount of noise\n$\\gamma = s \\gamma_{0}$ for some scale factor $s \\ge 1$. By evaluating\nthe noisy outputs of each circuit, we can extrapolate to $s=0$ to\nestimate the result of running a noise-free circuit.\n\nA key element of ZNE is the ability to run equivalent circuits for a\nrange of scale factors $s$. When the noise present in a circuit scales\nwith the number of gates, $s$ can be varied using unitary folding.\nUnitary folding works by noticing that any unitary $V$ is equivalent to\n$V V^{\\dagger} V$. This type of transform can be applied to individual\ngates in the circuit or to the whole circuit. Let\\'s see how folding\nworks in code using Mitiq\\'s\n[fold\\_global](https://mitiq.readthedocs.io/en/stable/apidoc.html#mitiq.zne.scaling.folding.fold_global)\nfunction, which folds globally by setting $V$ to be the whole circuit.\nWe begin by making a copy of our above circuit using a\n`QuantumTape `{.interpreted-text\nrole=\"class\"}, which provides a low-level approach for circuit\nconstruction in PennyLane.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"with qml.tape.QuantumTape() as circuit:\n template(w1, w2, wires=range(n_wires))\n qml.adjoint(template)(w1, w2, wires=range(n_wires))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Don\\'t worry, in most situations you will not need to work with a\nPennyLane `QuantumTape `{.interpreted-text\nrole=\"class\"}! We are just dropping down to this representation to gain\na greater understanding of the Mitiq integration. Let\\'s see how folding\nworks for some typical scale factors:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"scale_factors = [1, 2, 3]\nfolded_circuits = [fold_global(circuit, scale_factor=s) for s in scale_factors]\n\nfor s, c in zip(scale_factors, folded_circuits):\n print(f\"Globally-folded circuit with a scale factor of {s}:\")\n print(qml.drawer.tape_text(c, decimals=2, max_length=80))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Although these circuits are a bit deep, if you look carefully, you might\nbe able to convince yourself that they are all equivalent! In fact,\nsince we have fixed our original circuit to be of the form\n$U U^{\\dagger}$, we get:\n\n- When the scale factor is $s=1$, the resulting circuit is\n\n $$V = U^{\\dagger} U = \\mathbb{I}.$$\n\n Hence, the $s=1$ setting gives us the original unfolded circuit.\n\n- When $s=3$, the resulting circuit is\n\n $$V V^{\\dagger} V = U^{\\dagger} U U U^{\\dagger} U^{\\dagger} U = \\mathbb{I}.$$\n\n In other words, we fold the whole circuit once when $s=3$.\n Generally, whenever $s$ is an odd integer, we fold $(s - 1) / 2$\n times.\n\n- The $s=2$ setting is a bit more subtle. Now we apply folding only to\n the second half of the circuit, which is in our case given by\n $U^{\\dagger}$. The resulting partially-folded circuit is\n\n $$(U^{\\dagger} U U^{\\dagger}) U = \\mathbb{I}.$$\n\n Visit Ref. to gain a deeper understanding of unitary folding.\n\nIf you\\'re still not convinced, we can evaluate the folded circuits on\nour noise-free device `dev_ideal`. To do this, we\\'ll define an\n`executor` function that adds the\n`PauliZ `{.interpreted-text role=\"class\"} measurement\nonto the first qubit of each input circuit and then runs the circuits on\na target device.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def executor(circuits, dev=dev_noisy):\n # Support both a single circuit and multiple circuit execution\n circuits = [circuits] if isinstance(circuits, qml.tape.QuantumTape) else circuits\n\n circuits_with_meas = []\n\n # Loop through circuits and add on measurement\n for c in circuits:\n with qml.tape.QuantumTape() as circuit_with_meas:\n for o in c.operations:\n qml.apply(o)\n qml.expval(qml.PauliZ(0))\n circuits_with_meas.append(circuit_with_meas)\n\n return qml.execute(circuits_with_meas, dev, gradient_fn=None)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We need to do this step as part of the Mitiq integration with the\nlow-level PennyLane\n`QuantumTape `{.interpreted-text\nrole=\"class\"}. You will not have to worry about these details when using\nthe main\n`mitigate_with_zne `{.interpreted-text\nrole=\"func\"} function we encountered earlier.\n\nNow, let\\'s execute these circuits:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"executor(folded_circuits, dev=dev_ideal)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By construction, these circuits are equivalent to the original and have\nthe same output value of $1$. On the other hand, each circuit has a\ndifferent depth. If we expect each gate in a circuit to contribute an\namount of noise when running on NISQ hardware, we should see the result\nof the executed circuit degrade with increased depth. This can be\nconfirmed using the `dev_noisy` device\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"executor(folded_circuits, dev=dev_noisy)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Although this degradation may seem undesirable, it is part of the\nstandard recipe for ZNE error mitigation: we have a family of equivalent\ncircuits that experience a varying amount of noise when executed on\nhardware, and we are able to control the amount of noise by varying the\nfolding scale factor $s$ which determines the circuit depth. The final\nstep is to extrapolate our results back to $s=0$, providing us with an\nestimate of the noise-free result of the circuit.\n\nPerforming extrapolation is a well-studied numeric method in\nmathematics, and Mitiq provides access to some of the core approaches.\nHere we use the [Richardson\nextrapolation](https://en.wikipedia.org/wiki/Richardson_extrapolation)\nmethod with the objective of finding a curve $y = f(x)$ with some fixed\n$(x, y)$ values given by the scale factors and corresponding circuit\nexecution results, respectively. This can be performed using:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Evaluate noise-scaled expectation values\nnoisy_expectation_values = executor(folded_circuits, dev=dev_noisy)\n\n# Initialize extrapolation method\nfac = RichardsonFactory(scale_factors)\n\n# Load data into extrapolation factory\nfor x, y in zip(scale_factors, noisy_expectation_values):\n fac.push({\"scale_factor\": x}, y)\n\n# Run extrapolation\nzero_noise = fac.reduce()\n\nprint(f\"ZNE result: {zero_noise}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let\\'s make a plot of the data and fitted extrapolation function.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"_ = fac.plot_fit()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Since we are using the Richardson extrapolation method, the fitted\nfunction (dashed line) corresponds to a polynomial interpolation of the\nmeasured data (blue points).\n\nThe zero-noise limit corresponds to the value of the extrapolation\nfunction evaluated at [x=0]{.title-ref}.\n\nError mitigation in PennyLane\n=============================\n\nNow that we understand the ZNE method for error mitigation, we can\nprovide a few more details on how it can be performed using PennyLane.\nAs we have seen, the\n`mitigate_with_zne `{.interpreted-text\nrole=\"func\"} function provides the main entry point. This function is an\nexample of a\n`circuit transform, `{.interpreted-text\nrole=\"doc\"} and it can be applied to pre-constructed QNodes as well as\nbeing used as a decorator when constructing new QNodes. For example,\nsuppose we have a `qnode` already defined. A mitigated QNode can be\ncreated using:\n\n``` {.python}\nmitigated_qnode = mitigate_with_zne(scale_factors, folding, extrapolate)(qnode)\n```\n\nWhen using `mitigate_with_zne`, we must specify the target scale factors\nas well as provide functions for folding and extrapolation. Due to\nPennyLane\\'s integration with Mitiq, it is possible to use the folding\nfunctions provided in the\n[mitiq.zne.scaling.folding](https://mitiq.readthedocs.io/en/stable/apidoc.html#module-mitiq.zne.scaling.folding)\nmodule. For extrapolation, one can use the `extrapolate` method of the\nfactories in the\n[mitiq.zne.inference](https://mitiq.readthedocs.io/en/stable/apidoc.html#module-mitiq.zne.inference)\nmodule.\n\nWe now provide an example of how `mitigate_with_zne` can be used when\nconstructing a QNode:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from mitiq.zne.scaling import fold_gates_at_random as folding\n\nextrapolate = RichardsonFactory.extrapolate\n\n\n@mitigate_with_zne(scale_factors, folding, extrapolate, reps_per_factor=100)\n@qml.qnode(dev_noisy)\ndef mitigated_qnode(w1, w2):\n template(w1, w2, wires=range(n_wires))\n qml.adjoint(template)(w1, w2, wires=range(n_wires))\n return qml.expval(qml.PauliZ(0))\n\n\nmitigated_qnode(w1, w2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the above, we can easily add in error mitigation using the\n`@mitigate_with_zne` decorator. To keep things interesting, we\\'ve\nswapped out our folding function to instead perform folding on\nrandomly-selected gates. Whenever the folding function is stochastic,\nthere will not be a unique folded circuit corresponding to a given scale\nfactor. For example, the following three distinct circuits are all\nfolded with a scale factor of $s=1.1$:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"for _ in range(3):\n print(qml.drawer.tape_text(folding(circuit, scale_factor=1.1), decimals=2, max_length=80))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To accommodate for this randomness, we can perform multiple repetitions\nof random folding for a fixed $s$ and average over the execution results\nto generate the value $f(s)$ used for extrapolation. As shown above, the\nnumber of repetitions is controlled by setting the optional\n`reps_per_factor` argument.\n\nWe conclude this section by highlighting the possibility of working\ndirectly with the core functionality available in Mitiq. For example,\nthe\n[execute\\_with\\_zne](https://mitiq.readthedocs.io/en/stable/apidoc.html#mitiq.zne.zne.execute_with_zne)\nfunction is one of the central components of ZNE support in Mitiq and is\ncompatible with circuits constructed using a PennyLane\n`QuantumTape `{.interpreted-text\nrole=\"class\"}. Working directly with Mitiq can be preferable when more\nflexibility is required in specifying the error mitigation protocol. For\nexample, the code below shows how an adaptive approach can be used to\ndetermine a sequence of scale factors for extrapolation using Mitiq\\'s\n[AdaExpFactory](https://mitiq.readthedocs.io/en/stable/apidoc.html#mitiq.zne.inference.AdaExpFactory).\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from mitiq.zne import execute_with_zne\nfrom mitiq.zne.inference import AdaExpFactory\n\nfactory = AdaExpFactory(steps=20)\n\nexecute_with_zne(circuit, executor, factory=factory, scale_noise=fold_global)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Recall that `circuit` is a PennyLane\n`QuantumTape `{.interpreted-text\nrole=\"class\"} and that it should not include measurements. We also use\nthe `executor` function defined earlier that adds on the target\nmeasurement and executes on a noisy device.\n\nMitigating noisy circuits in quantum chemistry\n==============================================\n\nWe\\'re now ready to apply our knowledge to a more practical problem in\nquantum chemistry: calculating the potential energy surface of molecular\nhydrogen. This is achieved by finding the ground state energy of $H_{2}$\nas we increase the bond length between the hydrogen atoms. As shown in\n`this `{.interpreted-text role=\"doc\"}\ntutorial, one approach to finding the ground state energy is to\ncalculate the corresponding qubit Hamiltonian and to fix an ansatz\nvariational quantum circuit that returns its expectation value. We can\nthen vary the parameters of the circuit to minimize the energy.\n\nTo find the potential energy surface of $H_{2}$, we must choose a range\nof interatomic distances and calculate the qubit Hamiltonian\ncorresponding to each distance. We then optimize the variational circuit\nwith a new set of parameters for each Hamiltonian and plot the resulting\nenergies for each distance. In this demo, we compare the potential\nenergy surface reconstructed when the optimized variational circuits are\nrun on ideal, noisy, and noise-mitigated devices.\n\nInstead of modifying the\n`default.mixed `{.interpreted-text\nrole=\"mod\"} device to add simple noise as we do above, let\\'s choose a\nnoise model that is a little closer to physical hardware. Suppose we\nwant to simulate the `ibmq_lima` hardware device available on IBMQ. We\ncan load a noise model that represents this device using:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from qiskit.test.mock import FakeLima\nfrom qiskit.providers.aer.noise import NoiseModel\n\nbackend = FakeLima()\nnoise_model = NoiseModel.from_backend(backend)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can then set up our ideal device and the noisy simulator of\n`ibmq_lima`.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"n_wires = 4\n\ndev_ideal = qml.device(\"default.qubit\", wires=n_wires)\ndev_noisy = qml.device(\n \"qiskit.aer\",\n wires=n_wires,\n noise_model=noise_model,\n optimization_level=0,\n shots=10000,\n)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note the use of the `optimization_level=0` argument when loading the\nnoisy device. This prevents the `qiskit.aer` transpiler from performing\na pre-execution circuit optimization.\n\nTo simplify this demo, we will load pre-trained parameters for our\nvariational circuit.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"params = np.load(\"error_mitigation/params.npy\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"These parameters can be downloaded by clicking\n`here <../demonstrations/error_mitigation/params.npy>`{.interpreted-text\nrole=\"download\"}. We are now ready to set up the variational circuit and\nrun on the ideal and noisy devices.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from pennylane import qchem\n\n# Describe quantum chemistry problem\nsymbols = [\"H\", \"H\"]\ndistances = np.arange(0.5, 3.0, 0.25)\n\nideal_energies = []\nnoisy_energies = []\n\nfor r, phi in zip(distances, params):\n # Assume atoms lie on the Z axis\n coordinates = np.array([0.0, 0.0, 0.0, 0.0, 0.0, r])\n\n # Load qubit Hamiltonian\n H, _ = qchem.molecular_hamiltonian(symbols, coordinates)\n\n # Define ansatz circuit\n def qchem_circuit(phi):\n qml.PauliX(wires=0)\n qml.PauliX(wires=1)\n qml.DoubleExcitation(phi, wires=range(n_wires))\n return qml.expval(H)\n\n ideal_energy = qml.QNode(qchem_circuit, dev_ideal)\n noisy_energy = qml.QNode(qchem_circuit, dev_noisy)\n\n ideal_energies.append(ideal_energy(phi))\n noisy_energies.append(noisy_energy(phi))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"An error-mitigated version of the potential energy surface can also be\ncalculated using the following:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"mitig_energies = []\n\nfor r, phi in zip(distances, params):\n # Assume atoms lie on the Z axis\n coordinates = np.array([0.0, 0.0, 0.0, 0.0, 0.0, r])\n\n # Load qubit Hamiltonian\n H, _ = qchem.molecular_hamiltonian(symbols, coordinates)\n\n # Define ansatz circuit\n with qml.tape.QuantumTape() as circuit:\n qml.PauliX(wires=0)\n qml.PauliX(wires=1)\n qml.DoubleExcitation(phi, wires=range(n_wires))\n\n # Define custom executor that expands Hamiltonian measurement\n # into a linear combination of tensor products of Pauli\n # operators.\n def executor(circuit):\n\n # Add Hamiltonian measurement to circuit\n with qml.tape.QuantumTape() as circuit_with_meas:\n for o in circuit.operations:\n qml.apply(o)\n qml.expval(H)\n\n # Expand Hamiltonian measurement into tensor product of\n # of Pauli operators. We get a list of circuits to execute\n # and a postprocessing function to combine the results into\n # a single number.\n circuits, postproc = qml.transforms.hamiltonian_expand(\n circuit_with_meas, group=False\n )\n circuits_executed = qml.execute(circuits, dev_noisy, gradient_fn=None)\n return postproc(circuits_executed)\n\n mitig_energy = execute_with_zne(circuit, executor, scale_noise=fold_global)\n mitig_energies.append(mitig_energy)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we can plot the three surfaces and compare:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n\nplt.plot(ideal_energies, label=\"ideal\")\nplt.plot(noisy_energies, label=\"noisy\")\nplt.plot(mitig_energies, label=\"mitigated\")\nplt.xlabel(\"Bond length (Bohr)\")\nplt.ylabel(\"Total energy (Hartree)\")\nplt.legend()\nplt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Great, error mitigation has allowed us to more closely approximate the\nideal noise-free curve!\n\nWe have seen in this demo how easy error mitigation can be in PennyLane\nwhen using Mitiq as a backend. As well as understanding the basics of\nthe ZNE method, we have also seen how mitigation can be used to uplift\nthe performance of noisy devices for practical tasks like quantum\nchemistry. On the other hand, we\\'ve only just started to scratch the\nsurface of what can be done with error mitigation. We can explore\napplying the ZNE method to other use cases, or even try out other\nmitigation methods like [probabilistic error\ncancellation](https://mitiq.readthedocs.io/en/stable/examples/pec-tutorial.html).\nLet us know where your adventures take you, and the Mitiq and PennyLane\nteams will keep working to help make error mitigation as easy as\npossible!\n\nReferences\n==========\n\nAbout the authors\n=================\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.16"
}
},
"nbformat": 4,
"nbformat_minor": 0
}