Skip to content

Instantly share code, notes, and snippets.

@natestemen
Created July 28, 2022 13:38
Show Gist options
  • Save natestemen/3cc8abd2509507dc18df82c1361d0b93 to your computer and use it in GitHub Desktop.
Save natestemen/3cc8abd2509507dc18df82c1361d0b93 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"id": "c9bad241",
"metadata": {},
"source": [
"# Hands-on lab on error mitigation with Mitiq.\n",
"\n",
"This notebook is a guided tutorial on error mitigation with Mitiq. The notebook is focused on the zero-noise extrapolation (ZNE) technique.\n",
"\n",
"<img src=https://mitiq.readthedocs.io/en/stable/_images/zne_workflow2_steps.png width=\"600\">\n",
"\n",
"\n",
"Useful links:\n",
"\n",
"* Mitiq repository: https://github.com/unitaryfund/mitiq\n",
"* Mitiq documentation: https://mitiq.readthedocs.io/en/stable/\n",
"* Mitiq docs on ZNE: https://mitiq.readthedocs.io/en/stable/guide/zne.html\n",
"* Mitiq white paper: https://arxiv.org/abs/2009.04417\n",
"* Unitary Fund: https://unitary.fund"
]
},
{
"cell_type": "markdown",
"id": "637af1e3",
"metadata": {},
"source": [
"## Checking Python packages are installed correctly"
]
},
{
"cell_type": "markdown",
"id": "7ad8e65a",
"metadata": {},
"source": [
"This notebook was tested with **Mitiq v0.17.0** and **qiskit v0.36.2**. It probably works with other versions too. Moreover, with minor changes, it can be adapted to quantum libraries that are different from Qiskit: Cirq, Braket, PyQuil, etc.."
]
},
{
"cell_type": "markdown",
"id": "322ca785",
"metadata": {},
"source": [
"If you need to install Mitiq and/or Qiskit, you can uncomment and run the following cells."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "63ba0527",
"metadata": {},
"outputs": [],
"source": [
"# !pip install mitiq==0.17.0"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "880c226b",
"metadata": {},
"outputs": [],
"source": [
"# !pip install qiskit==0.36.2"
]
},
{
"cell_type": "markdown",
"id": "5b0c6a6e",
"metadata": {},
"source": [
"If you encounter problems when installing Mitiq on your local machine,\n",
"you can try creating a new notebook in the online Binder einvironment at the following link:\n",
"https://mybinder.org/v2/gh/unitaryfund/mitiq/0da4965f3d80b9ee7ed9e93527c7e7c09d4b2f7e"
]
},
{
"cell_type": "markdown",
"id": "8f3a04ba",
"metadata": {},
"source": [
"You can check your locally installed version of Mitiq and of the associated frontend libraries by running the next cell."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2adb69fc",
"metadata": {},
"outputs": [],
"source": [
"from mitiq import about\n",
"\n",
"about()"
]
},
{
"cell_type": "markdown",
"id": "0bd6f170",
"metadata": {},
"source": [
"## Computing a quantum expectation value without error mitigation"
]
},
{
"cell_type": "markdown",
"id": "756cf831",
"metadata": {},
"source": [
"### Define the circuit of interest"
]
},
{
"cell_type": "markdown",
"id": "8f4eed86",
"metadata": {},
"source": [
"For example, we can define a circuit $U$ that prepares the GHZ state for $n$ qubits.\n",
"\n",
"$$ U |00...0\\rangle = \\frac{|00...0\\rangle + |11...1\\rangle}{\\sqrt{2}} $$"
]
},
{
"cell_type": "markdown",
"id": "6a6773b9",
"metadata": {},
"source": [
"This can be done by manually defining a Qiskit circuit or by calling the Mitiq function `mitiq.benchmarks.generate_ghz_circuit()`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4fb8ff0c",
"metadata": {},
"outputs": [],
"source": [
"from mitiq.benchmarks import generate_ghz_circuit\n",
"\n",
"\n",
"n_qubits = 7\n",
"\n",
"circuit = generate_ghz_circuit(n_qubits=n_qubits, return_type=\"qiskit\")\n",
"print(\"GHZ circuit:\")\n",
"print(circuit)"
]
},
{
"cell_type": "markdown",
"id": "115a55f4",
"metadata": {},
"source": [
"Let us define the Hermitian observable:\n",
"\n",
"$$ A = |00...0\\rangle\\langle 00...0| + |11...1\\rangle\\langle 11...1|.$$\n",
"\n",
"In the **absence of noise**, the expectation value of $A$ is equal to 1: \n",
"\n",
"$${\\rm tr}(\\rho_{\\rm} A)= \\langle 00...0| U^\\dagger A U |00...0\\rangle= \\frac{1}{2} + \\frac{1}{2}=1.$$\n",
"\n",
"In practice this means that, when measuring the state in the computational basis, we can only obtain either the bitstring $00\\dots 0$ or the biststring $11\\dots 1$.\n",
"\n",
"In the **presence of noise** instead, the expectation value of the same observable $A$ will be smaller.\n",
"Let's verify this fact, before applying any error mitigation."
]
},
{
"cell_type": "markdown",
"id": "3fa69631",
"metadata": {},
"source": [
"### Run the circuit with a noiseless backend and with a noisy backend."
]
},
{
"cell_type": "markdown",
"id": "a07e3fd6",
"metadata": {},
"source": [
"**Hint:** You can follow [this Qiskit example](https://qiskit.org/documentation/tutorials/simulators/2_device_noise_simulation.html) in which a (simulated) noiseless backend and a (simulated) noisy backend are compared."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "393ba432",
"metadata": {},
"outputs": [],
"source": [
"from qiskit import QuantumCircuit\n",
"from qiskit.providers.aer import AerSimulator\n",
"from qiskit.tools.visualization import plot_histogram\n",
"from qiskit import transpile\n",
"from qiskit.test import mock # Fake (simulated) QPUs\n",
"\n",
"# Number of measurements\n",
"shots = 10 ** 5"
]
},
{
"cell_type": "markdown",
"id": "d19c156d",
"metadata": {},
"source": [
"We first execute the circuit on an ideal noiseless simulator."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d3a72664",
"metadata": {},
"outputs": [],
"source": [
"ideal_backend = AerSimulator()\n",
"\n",
"# Append measurement gates\n",
"circuit_to_run = circuit.copy()\n",
"circuit_to_run.measure_all()\n",
"\n",
"# TODO: Run circuit_to_run on the ideal backend and get the ideal counts\n",
"\n",
"plot_histogram(ideal_counts, title='Counts for an ideal GHZ state')"
]
},
{
"cell_type": "markdown",
"id": "67b65d3b",
"metadata": {},
"source": [
"We now execute the same circuit on a noisy backend (a classical emulator of the real IBM Jakarta QPU)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c106e746",
"metadata": {},
"outputs": [],
"source": [
"noisy_backend = mock.FakeJakarta() # QPU emulator\n",
"\n",
"# Compile the circuit into the native gates of the backend\n",
"compiled_circuit = transpile(circuit_to_run, noisy_backend)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a2a4bc14",
"metadata": {},
"outputs": [],
"source": [
"# Run the simulation on the noisy backend\n",
"\n",
"# TODO: Run circuit_to_run on the noisy backend and get the noisy counts\n",
"\n",
"plot_histogram(noisy_counts, title='Counts for a noisy GHZ state', figsize=(15, 5))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8bff88dd",
"metadata": {},
"outputs": [],
"source": [
"ideal_expectation_value = # TODO: get <A> from ideal_counts\n",
"print(f\"The ideal expectation value is <A> = {ideal_expectation_value}\")\n",
"\n",
"noisy_expectation_value = # TODO: get <A> from noisy_counts\n",
"print(f\"The noisy expectation value is <A> = {noisy_expectation_value}\")"
]
},
{
"cell_type": "markdown",
"id": "b06bea77",
"metadata": {},
"source": [
"## Apply zero-noise extrapolation with Mitiq"
]
},
{
"cell_type": "markdown",
"id": "0615dead",
"metadata": {},
"source": [
"Before using Mitiq we need wrap the previous code into a function that takes as input a circuit and returns the noisy expectation value of the observable $A$. This function will be used by Mitiq as a black box during the error mitigation process."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f2919710",
"metadata": {},
"outputs": [],
"source": [
"def execute(compiled_circuit):\n",
" \"\"\"Executes the input circuits and returns the expectation value of A=|00..0><00..0| + |11..1><11..1|.\"\"\"\n",
" print(\"Executing a circuit of depth:\", compiled_circuit.depth())\n",
" \n",
" # TODO: cope and paste the instructions that we previously used to obtain noisy <A>.\n",
" return noisy_expectation_value"
]
},
{
"cell_type": "markdown",
"id": "475d3371",
"metadata": {},
"source": [
"Let us check if the function works as expeted."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2e12e168",
"metadata": {},
"outputs": [],
"source": [
"print(f\"The noisy expectation value is <A> = {execute(compiled_circuit)}\")"
]
},
{
"cell_type": "markdown",
"id": "632b6812",
"metadata": {},
"source": [
"We can now apply zero-noise extrapolation with Mitiq. Without advanced options, this requires a single line of code."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d982af4a",
"metadata": {},
"outputs": [],
"source": [
"from mitiq import zne\n",
"\n",
"zne_value = zne.execute_with_zne(\n",
" circuit= # TODO... docs: https://mitiq.readthedocs.io/en/stable/apidoc.html#module-mitiq.zne.zne\n",
" executor= # TODO...\n",
")\n",
" \n",
"print(f\"The error mitigated expectation value is <A> = {zne_value}\")"
]
},
{
"cell_type": "markdown",
"id": "d9dc8d1b",
"metadata": {},
"source": [
"**Note:** As you can see from the printed output, Mitiq calls the execute function multiple times (3 in this case) to evaluate circuits of different depths in order to extrapolate the ideal result."
]
},
{
"cell_type": "markdown",
"id": "776a4197",
"metadata": {},
"source": [
"Let us compare the absolute estimation error obtained with and without Mitiq."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06280b9c",
"metadata": {},
"outputs": [],
"source": [
"print(f\"Error without Mitiq: {abs(ideal_expectation_value - noisy_expectation_value)}\")\n",
"print(f\"Error with Mitiq: {abs(ideal_expectation_value - zne_value)}\")"
]
},
{
"cell_type": "markdown",
"id": "8a02cd1e",
"metadata": {},
"source": [
"## Explicitly selecting the noise-scaling method and the extrapolation method"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24f93d2d",
"metadata": {},
"outputs": [],
"source": [
"from mitiq import zne\n",
"\n",
"# Select a noise scaling method\n",
"folding_function = zne.scaling.fold_global\n",
"\n",
"# Select an inference method\n",
"factory = zne.inference.RichardsonFactory(scale_factors = [1.0, 2.0, 3.0])\n",
"\n",
"zne_value = zne.execute_with_zne(\n",
" compiled_circuit, \n",
" executor=execute,\n",
" # TODO: pass the \"folding_function\" and the \"factory\" as arguments. \n",
" # See docs: https://mitiq.readthedocs.io/en/stable/apidoc.html#module-mitiq.zne.zne\n",
")\n",
"factory.plot_fit()\n",
"print(f\"The error mitigated expectation value is <A> = {zne_value}\")"
]
},
{
"cell_type": "markdown",
"id": "0cfc3011",
"metadata": {},
"source": [
"## What happens behind the scenes? A low-level application of ZNE."
]
},
{
"cell_type": "markdown",
"id": "56a11454",
"metadata": {},
"source": [
"### STEP 1: Noise-scaled expectation values are evaluated via gate-level \"unitary folding\" transformations"
]
},
{
"cell_type": "markdown",
"id": "00c1bb25",
"metadata": {},
"source": [
"In Mitiq one can indirectly amplify noise by intentionally increasing the depth of the circuit in different ways."
]
},
{
"cell_type": "markdown",
"id": "1bf2c495",
"metadata": {},
"source": [
"For example, the function `zne.scaling.fold_gates_at_random()` applies transformation $G \\rightarrow G G^\\dagger G$ to each gate of the circuit (or to a random subset of gates)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bc53e4ed",
"metadata": {},
"outputs": [],
"source": [
"locally_folded_circuit = # apply fold_gates_at_random() to \"circuit\" with scale factor of 3.\n",
"# Link to docs: https://mitiq.readthedocs.io/en/stable/apidoc.html#mitiq.zne.scaling.folding.fold_gates_at_random\n",
"\n",
"print(\"Locally folded GHZ circuit:\")\n",
"print(locally_folded_circuit)"
]
},
{
"cell_type": "markdown",
"id": "af17346b",
"metadata": {},
"source": [
"**Note:** To get a simple visualization, we did't apply the preliminary circuit transpilation that we used in the previous section."
]
},
{
"cell_type": "markdown",
"id": "bba0800b",
"metadata": {},
"source": [
"Alternatively, the function `zne.scaling.fold_global()` applies the transformation $U \\rightarrow U U^\\dagger U$ to the full circuit."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef04943e",
"metadata": {},
"outputs": [],
"source": [
"globally_folded_circuit = # apply fold_global() to \"circuit\" with scale factor of 3.\n",
"# Link to docs: https://mitiq.readthedocs.io/en/stable/apidoc.html#mitiq.zne.scaling.folding.fold_global\n",
"\n",
"print(\"Globally folded GHZ circuit:\")\n",
"print(globally_folded_circuit)"
]
},
{
"cell_type": "markdown",
"id": "6af10f64",
"metadata": {},
"source": [
"In both cases, the results are longer circuits which are more sensitive to noise. Those circuits can be used to evaluate noise scaled expectation values."
]
},
{
"cell_type": "markdown",
"id": "a06b9309",
"metadata": {},
"source": [
"For example, let's use global folding to evaluate a list of noise scaled expectation values."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5882ec2e",
"metadata": {},
"outputs": [],
"source": [
"scale_factors = [1.0, 2.0, 3.0]\n",
"# It is usually better apply unitary folding to the compiled circuit\n",
"noise_scaled_circuits = [zne.scaling.fold_global(compiled_circuit, s) for s in scale_factors]\n",
"\n",
"# We run all the noise scaled circuits on the noisy backend\n",
"noise_scaled_vals = [execute(c) for c in noise_scaled_circuits]\n",
"\n",
"print(\"Noise-scaled expectation values:\", noise_scaled_vals)"
]
},
{
"cell_type": "markdown",
"id": "2737ae23",
"metadata": {},
"source": [
"### STEP 2: Inference of the ideal result via zero-noise extrapolation\n"
]
},
{
"cell_type": "markdown",
"id": "5209ef3f",
"metadata": {},
"source": [
"Given the list of noise scaled expectation values, one can extrapolate the zero-noise limit. This is the final classical post-processing step."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "816e0169",
"metadata": {},
"outputs": [],
"source": [
"# Initialize a Richardson extrapolation object\n",
"richardson_factory = zne.RichardsonFactory(scale_factors)\n",
"\n",
"# Load the previously measured data\n",
"for s, val in zip(scale_factors, noise_scaled_vals):\n",
" richardson_factory.push({\"scale_factor\": s}, val)\n",
"\n",
"print(\"The Richardson zero-noise extrapolation is:\", richardson_factory.reduce())\n",
"_ = richardson_factory.plot_fit()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "809c8cf7",
"metadata": {},
"outputs": [],
"source": [
"# Initialize a linear extrapolation object\n",
"linear_factory = # TODO... see docs: https://mitiq.readthedocs.io/en/stable/apidoc.html#mitiq.zne.inference.LinearFactory\n",
"\n",
"# Load the previously measured data\n",
"for s, val in zip(scale_factors, noise_scaled_vals):\n",
" linear_factory.push({\"scale_factor\": s}, val)\n",
"\n",
"print(\"The linear zero-noise extrapolation is\", linear_factory.reduce())\n",
"_ = linear_factory.plot_fit()"
]
},
{
"cell_type": "markdown",
"id": "d4061ff1",
"metadata": {},
"source": [
"**Note:** We evaluated two different extrapolations without measuring the system twice. This is possible since the final extrapolation step is simply a classical post-processing of the same measured data."
]
},
{
"cell_type": "markdown",
"id": "8a6c1e66",
"metadata": {},
"source": [
"## References\n",
"\n",
"1. _Mitiq: A software package for error mitigation on noisy quantum computers_, R. LaRose at al., [arXiv:2009.04417](https://arxiv.org/abs/2009.04417) (2020).\n",
"\n",
"2. _Efficient variational quantum simulator incorporating active error minimisation_, Y. Li, S. C. Benjamin, [arXiv:1611.09301](https://arxiv.org/abs/1611.09301) (2016).\n",
"\n",
"3. _Error mitigation for short-depth quantum circuits_, K. Temme, S. Bravyi, J. M. Gambetta, [arXiv:1612.02058](https://arxiv.org/abs/1612.02058) (2016).\n",
"\n",
"4. _Digital zero noise extrapolation for quantum error mitigation_, \n",
"T. Giurgica-Tiron, Y. Hindy, R. LaRose, A. Mari, W. J. Zeng,\n",
"[arXiv:2005.10921](https://arxiv.org/abs/2005.10921) (2020).\n",
"\n"
]
}
],
"metadata": {
"jupytext": {
"text_representation": {
"extension": ".myst",
"format_name": "myst",
"format_version": 0.13,
"jupytext_version": "1.11.1"
}
},
"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.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment