{ "cells": [ { "cell_type": "markdown", "id": "33be72af", "metadata": {}, "source": [ "# Custom Expectation Value Program for the Qiskit Runtime\n", "\n", "```{post} 2021-10-13\n", ":tags: Custom\n", ":category: Runtime\n", "```\n", "\n", "
\n", "Paul Nation\n", "
\n", "\n", "IBM Quantum Partners Technical Enablement Team\n", "
\n", "\n", "Here we will show how to make a program that takes a circuit, or list of circuits, and computes the expectation values of one or more diagonal operators." ] }, { "cell_type": "markdown", "id": "118e72f0", "metadata": {}, "source": [ "## Prerequisites\n", "\n", "- You must have Qiskit 0.30+ installed.\n", "- You must have an IBM Quantum Experience account with the ability to upload a Runtime program. **Currently there is no way to know if you have Runtime upload ability outside of an email from IBM**." ] }, { "cell_type": "markdown", "id": "6368c90a", "metadata": {}, "source": [ "## Background\n", "\n", "The primary method by which information is obtained from quantum computers is via expectation values. Indeed, the samples that come from executing a quantum circuit multiple times, once converted to probabilities, can be viewed as just a finite sample approximation to the expectation value for the projection operators corresponding to each bitstring. More practically, many quantum algorithms require computing expectation values over Pauli operators, e.g. Variational Quantum Eigensolvers, and thus having a runtime program that computes these quantities is of fundamental importance. Here we look at one such example, where an user passes one or more circuits and expectation operators and gets back the computed expectation values, and possibly error bounds.\n", "\n", "### Expectation value of a diagonal operator\n", "\n", "Consider a generic observable given by the tensor product of diagonal operators over $N$ qubits $O = O_{N-1}\\dots O_{0}$ where the subscript indicates the qubit on which the operator acts. Then for a set of observed $M$ bitstrings $\\{b_{0}, \\dots b_{M-1}\\}$, where $M \\leq 2^N $, with corresponding approximate probabilites $p_{m}$ the expectation value is given by\n", "\n", "$$\n", "\\langle O\\rangle \\simeq \\sum_{m=0}^{M-1} p_{m}\\prod_{n=0}^{N-1}O_{n}[b_{m}[N-n-1], b_{m}[N-n-1]],\n", "$$\n", "\n", "where $O_{n}[b_{m}[N-n-1], b_{m}[N-n-1]]$ is the diagonal element of $O_{n}$ specified by the $N-n-1$th bit in bitstring $b_{m}$. The reason for the complicated indexing in `b_{m}` is because Qiskit uses least-sginificant bit indexing where the zeroth element of the bit-strings is given by the right-most bit.\n", "\n", "Here we will use built-in routines to compute these expectation values. However, it is not hard to do yourself, with plenty of examples to be found." ] }, { "cell_type": "markdown", "id": "42df9e62", "metadata": {}, "source": [ "## Main program\n", "\n", "Here we define our main function for the expectation value runtime program. As always, our program must start with the `backend`, and `user_messenger` arguements, followed by the actual inputs we pass to the program. Here our options are quite simple:\n", "\n", "- `circuits`: A single QuantumCircuit or list of QuantumCircuits to be executed on the target backend.\n", "\n", "\n", "- `expectation_operators`: The operators we want to evaluate. These can be strings of diagonal Pauli's, eg, `ZIZZ`, or custom operators defined by dictionarys. For example, the projection operator on the all ones state of 4 qubits is `{'1111': 1}`.\n", "\n", "\n", "- `shots`: Howe many times to sample each circuit.\n", "\n", "\n", "- `transpiler_config`: A dictionary that passes additional arguments on to the transpile function, eg. `optimization_level`.\n", "\n", "\n", "- `run_config`: A dictionary that passes additional arguments on to `backend.run()`.\n", "\n", "\n", "- `skip_transpilation`: A flag to skip transpilation altogether and just run the circuits. This is useful for situations where you need to transpile parameterized circuits once, but must bind parameters multiple times and evaluate. \n", "\n", "\n", "- `return_stddev`: Flag to return bound on standard deviation. If using measurement mitigation this adds some overhead to the computation.\n", "\n", "\n", "- `use_measurement_mitigation`: Use M3 measurement mitigation and compute expecation value and standard deviation bound from quasi-probabilities.\n", "\n", "At the top of the cell below you will see a commented out `%%writefile sample_expval.py`. We will use this to convert the cell to a Python module named `sample_expval.py` to upload." ] }, { "cell_type": "code", "execution_count": 1, "id": "06b48e43", "metadata": {}, "outputs": [], "source": [ "#%%writefile sample_expval.py\n", "import mthree\n", "from qiskit import transpile\n", "\n", "# The entrypoint for our Runtime Program\n", "def main(backend, user_messenger,\n", " circuits,\n", " expectation_operators='',\n", " shots = 8192,\n", " transpiler_config={},\n", " run_config={},\n", " skip_transpilation=False,\n", " return_stddev=False,\n", " use_measurement_mitigation=False,\n", " ):\n", " \n", " \"\"\"Compute expectation \n", " values for a list of operators after\n", " executing a list of circuits on the target backend.\n", " \n", " Parameters:\n", " backend (ProgramBackend): Qiskit backend instance.\n", " user_messenger (UserMessenger): Used to communicate with the program user.\n", " circuits: (QuantumCircuit or list): A single list of QuantumCircuits.\n", " expectation_operators (str or dict or list): Expectation values to evaluate.\n", " shots (int): Number of shots to take per circuit.\n", " transpiler_config (dict): A collection of kwargs passed to transpile().\n", " run_config (dict): A collection of kwargs passed to backend.run().\n", " skip_transpilation (bool): Skip transpiling of circuits, default=False.\n", " return_stddev (bool): Return upper bound on standard devitation,\n", " default=False. \n", " use_measurement_mitigation (bool): Improve resulting using measurement\n", " error mitigation, default=False.\n", " \n", " Returns:\n", " array_like: Returns array of expectation values or a list of (expval, stddev)\n", " tuples if return_stddev=True.\n", " \"\"\"\n", " \n", " # transpiling the circuits using given transpile options\n", " if not skip_transpilation:\n", " trans_circuits = transpile(circuits, backend=backend,\n", " **transpiler_config)\n", " # Make sure everything is a list\n", " if not isinstance(trans_circuits, list):\n", " trans_circuits = [trans_circuits]\n", " # If skipping set circuits -> trans_circuits\n", " else:\n", " if not isinstance(circuits, list):\n", " trans_circuits = [circuits]\n", " else:\n", " trans_circuits = circuits\n", "\n", " # If we are given a single circuit but requesting multiple expectation\n", " # values, then set flag to make multiple pointers to same result.\n", " duplicate_results = False\n", " if isinstance(expectation_operators, list):\n", " if len(expectation_operators) and len(trans_circuits) == 1:\n", " duplicate_results = True\n", " \n", " # If doing measurement mitigation we must build and calibrate a\n", " # mitigator object. Will also determine which qubits need to be\n", " # calibrated.\n", " if use_measurement_mitigation:\n", " # Get an the measurement mappings at end of circuits\n", " meas_maps = mthree.utils.final_measurement_mapping(trans_circuits)\n", " # Get an M3 mitigator\n", " mit = mthree.M3Mitigation(backend)\n", " # Calibrate over the set of qubits measured in the transpiled circuits.\n", " mit.cals_from_system(meas_maps)\n", "\n", " # Compute raw results\n", " result = backend.run(trans_circuits, shots=shots, **run_config).result()\n", " raw_counts = result.get_counts()\n", "\n", " # When using measurement mitigation we need to apply the correction and then\n", " # compute the expectation values from the computed quasi-probabilities.\n", " if use_measurement_mitigation:\n", " quasi_dists = mit.apply_correction(raw_counts, meas_maps,\n", " return_mitigation_overhead=return_stddev)\n", " \n", " if duplicate_results:\n", " quasi_dists = mthree.classes.QuasiCollection(\n", " [quasi_dists]*len(expectation_operators))\n", " # There are two different calls depending on what we want returned.\n", " if return_stddev:\n", " return quasi_dists.expval_and_stddev(expectation_operators)\n", " return quasi_dists.expval(expectation_operators)\n", " \n", " # If the program didn't return in the mitigation loop above it means\n", " # we are processing the raw_counts data. We do so here using the\n", " # mthree utilities\n", " if duplicate_results:\n", " raw_counts = [raw_counts]*len(expectation_operators)\n", " if return_stddev:\n", " return mthree.utils.expval_and_stddev(raw_counts, expectation_operators)\n", " return mthree.utils.expval(raw_counts, expectation_operators)" ] }, { "cell_type": "markdown", "id": "edadd3f8", "metadata": {}, "source": [ "## Local testing\n", "\n", "Here we test with a local \"Fake\" backend that mimics the noise properties of a real system and a 4-qubit GHZ state." ] }, { "cell_type": "code", "execution_count": 2, "id": "2a25b3ac", "metadata": {}, "outputs": [], "source": [ "from qiskit import QuantumCircuit\n", "from qiskit.test.mock import FakeSantiago\n", "from qiskit.providers.ibmq.runtime import UserMessenger\n", "msg = UserMessenger()\n", "backend = FakeSantiago()" ] }, { "cell_type": "code", "execution_count": 3, "id": "6339a144", "metadata": {}, "outputs": [], "source": [ "qc = QuantumCircuit(4)\n", "qc.h(2)\n", "qc.cx(2, 1)\n", "qc.cx(1, 0)\n", "qc.cx(2, 3)\n", "qc.measure_all()" ] }, { "cell_type": "code", "execution_count": 4, "id": "3966f447", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.974614 , 1. , 0.02428596])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "main(backend, msg,\n", " qc,\n", " expectation_operators=['ZZZZ', 'IIII', 'IZZZ'],\n", " transpiler_config={'optimization_level':3, 'layout_method': 'sabre',\n", " 'routing_method': 'sabre'},\n", " run_config={},\n", " skip_transpilation=False,\n", " return_stddev=False,\n", " use_measurement_mitigation=True\n", " )" ] }, { "cell_type": "markdown", "id": "fafe3295", "metadata": {}, "source": [ "If we have done our job correctly, the above should print out two expectation values close to one and a final expectation value close to zero." ] }, { "cell_type": "markdown", "id": "f1fa15d4", "metadata": {}, "source": [ "## Program metadata\n", "\n", "© Copyright IBM 2017, 2021.
This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.