{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "0d58aa59",
      "metadata": {},
      "source": [
        "---\n",
        "title: QESEM - A Qiskit Function by Qedma\n",
        "description: Run quantum circuits on noisy QPUs to obtain highly accurate error-free results with highly efficient QPU-time overheads, close to fundamental bounds.\n",
        "---\n",
        "\n",
        "{/* cspell:ignore DESY Multibase, Quasicrystal, Downfolding, Aharonov, Goldack, wavefunctions, Sakuma */}\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dde95705",
      "metadata": {},
      "source": [
        "# QESEM: A Qiskit Function by Qedma\n",
        "\n",
        "*See the [API reference](/docs/api/functions/qedma-qesem)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6256578e",
      "metadata": {
        "tags": [
          "version-info"
        ]
      },
      "source": [
        "{/*\n",
        "  DO NOT EDIT THIS CELL!!!\n",
        "  This cell's content is generated automatically by a script. Anything you add\n",
        "  here will be removed next time the notebook is run. To add new content, create\n",
        "  a new cell before or after this one.\n",
        "  */}\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Package versions\">\n",
        "    The code on this page was developed using the following requirements.\n",
        "    We recommend using these versions or newer.\n",
        "\n",
        "    ```\n",
        "    qiskit[all]~=2.5.0\n",
        "    qiskit-ibm-runtime~=0.47.0\n",
        "    ```\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "13528739",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"Note\">\n",
        "  Qiskit Functions are an experimental feature available only to IBM Quantum® Premium Plan, Flex Plan, and On-Prem (via IBM Quantum Platform API) Plan users. They are in preview release status and subject to change.\n",
        "</Admonition>\n",
        "\n",
        "## Overview\n",
        "\n",
        "While quantum processing units have vastly improved in recent years, errors due to noise and imperfections in existing hardware remain a central challenge for quantum algorithm developers. As the field approaches utility-scale quantum computations that cannot be verified classically, solutions for canceling noise with guaranteed accuracy are becoming increasingly important. To overcome this challenge, Qedma has developed Quantum Error Mitigation (QESEM), seamlessly integrated on IBM Quantum Platform as a [Qiskit Function](/docs/guides/functions).\n",
        "\n",
        "With QESEM, users can run their quantum circuits on noisy QPUs to obtain highly accurate error-free results with highly efficient QPU-time overheads, close to fundamental bounds. To achieve this, QESEM leverages a suite of proprietary methods developed by Qedma, for the characterization and reduction of errors. Error reduction techniques include gate optimization, noise-aware transpilation, error suppression (ES), and unbiased error mitigation (EM). With this combination of these characterization-based methods, users can achieve reliable, error-free results for generic large-volume quantum circuits, unlocking applications that cannot be accomplished otherwise.\n",
        "\n",
        "For a full description of the underlying components, as well as a utility-scale demonstration, refer to the paper [Reliable high-accuracy error mitigation for utility-scale quantum circuits](https://arxiv.org/abs/2508.10997).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5f761442",
      "metadata": {},
      "source": [
        "## Description\n",
        "\n",
        "You can use the QESEM function by Qedma to easily estimate and execute your circuits with error suppression and mitigation, achieving larger circuit volumes and higher accuracies. To use QESEM, you provide a quantum circuit, a set of observables to measure, a target statistical accuracy for each observable, and a chosen QPU. Before you run the circuit to the target accuracy, you can estimate the required QPU time based on an analytical calculation that does not require circuit execution. Once you are satisfied with the QPU time estimation, you can execute the circuit with QESEM.\n",
        "\n",
        "When you execute a circuit, QESEM runs a device characterization protocol tailored to your circuit, yielding a reliable noise model for the errors occurring in the circuit. Based on the characterization, QESEM first implements noise-aware transpilation to map the input circuit onto a set of physical qubits and gates, which minimizes the noise affecting the target observable. These include the natively available gates (CX/CZ on IBM® devices), as well as additional gates optimized by QESEM, forming QESEM's extended gate set. QESEM then runs a set of characterization-based ES and EM circuits on the QPU and collects their measurement outcomes. These are then classically post-processed to provide an unbiased expectation value and an error bar for each observable, corresponding to the requested accuracy.\n",
        "\n",
        "![Qedma QESEM overview](https://quantum.cloud.ibm.com/docs/images/guides/qedma-qesem/overview.svg)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "74823696",
      "metadata": {},
      "source": [
        "QESEM has been demonstrated to provide high-accuracy results for a variety of quantum applications and on the largest circuit volumes achievable today. QESEM offers the following user-facing features, demonstrated in the benchmarks section below:\n",
        "\n",
        "* **Guaranteed accuracy:** QESEM outputs unbiased estimations for expectation values of observables. Its EM method is equipped with theoretical guarantees, which - together with Qedma's cutting-edge characterization - ensure the mitigation converges to the noiseless circuit output up to the user-specified accuracy. In contrast to many heuristic EM methods that are prone to systematic errors or biases, QESEM's guaranteed accuracy is essential for ensuring reliable results in generic quantum circuits and observables.\n",
        "* **Scalability to large QPUs:** QESEM's QPU time depends on circuit volumes, but is otherwise independent of the number of qubits. Qedma has demonstrated QESEM on the largest quantum devices available today, including the IBM Quantum 127-qubit Eagle and 133-qubit Heron devices.\n",
        "* **Application-agnostic:** QESEM has been demonstrated on a variety of applications, including Hamiltonian simulation, VQE, QAOA, and amplitude estimation. Users can input any quantum circuit and observable to be measured, and obtain accurate error-free results. The only limitations are dictated by the hardware specifications and allocated QPU time, which determine the accessible circuit volumes and output accuracies. In contrast, many error reduction solutions are application-specific or involve uncontrolled heuristics, rendering them inapplicable for generic quantum circuits and applications.\n",
        "* **Extended gate set:** QESEM supports fractional-angle gates, and provides Qedma-optimized fractional-angle $Rzz(\\theta)$ gates on IBM Quantum Heron and Eagle devices. This extended gate set enables more efficient compilation and unlocks circuit volumes larger by a factor of up to 2 compared to default CX/CZ compilation.\n",
        "* **Multibase observables:** QESEM supports input observables composed of many non-commuting Pauli strings, such as generic Hamiltonians. The choice of measurement bases and the optimization of QPU resource allocation (shots and circuits) is then performed automatically by QESEM to minimize the required QPU time for the requested accuracy. This optimization, which takes into account hardware fidelities and execution rates, enables you to run deeper circuits and obtain higher accuracies.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "57b41ab0",
      "metadata": {},
      "source": [
        "## Benchmarks\n",
        "\n",
        "QESEM has been tested on a wide variety of use cases and applications. The following examples can assist you with assessing which types of workloads you can run with QESEM.\n",
        "\n",
        "A key figure of merit for quantifying the hardness of both error mitigation and classical simulation for a given circuit and observable is **active volume**: the number of CNOT gates affecting the observable in the circuit. The active volume depends on the circuit depth and width, on the observable weight, and on the circuit structure, which determines the light cone of the observable. For further details, see the talk from the [2024 IBM Quantum Summit](https://www.youtube.com/watch?v=Hd-IGvuARfE\\&t=1730s). QESEM provides particularly large value in the high-volume regime, giving reliable results for generic circuits and observables.\n",
        "\n",
        "![Active volume](https://quantum.cloud.ibm.com/docs/images/guides/qedma-qesem/active_volume.svg)\n",
        "\n",
        "| Application                        | Number of qubits | Device     | Circuit description                                      | Accuracy | Total time | Runtime usage |\n",
        "| ---------------------------------- | ---------------- | ---------- | -------------------------------------------------------- | -------- | ---------- | ------------- |\n",
        "| VQE circuit                        | 8                | Eagle (r3) | 21 total layers, 9 measurement bases, 1D chain           | 98%      | 35 min     | 14 min        |\n",
        "| Kicked Ising                       | 28               | Eagle (r3) | 3 unique layers x 3 steps, 2D heavy-hex topology         | 97%      | 22 min     | 4 min         |\n",
        "| Kicked Ising                       | 28               | Eagle (r3) | 3 unique layers x 8 steps, 2D heavy-hex topology         | 97%      | 116 min    | 23 min        |\n",
        "| Trotterized Hamiltonian simulation | 40               | Eagle (r3) | 2 unique layers x 10 Trotter steps, 1D chain             | 97%      | 3 hours    | 25 min        |\n",
        "| Trotterized Hamiltonian simulation | 119              | Eagle (r3) | 3 unique layers x 9 Trotter steps, 2D heavy-hex topology | 95%      | 6.5 hours  | 45 min        |\n",
        "| Kicked Ising                       | 136              | Heron (r2) | 3 unique layers x 15 steps, 2D heavy-hex topology        | 99%      | 52 min     | 9 min         |\n",
        "\n",
        "Accuracy is measured here relative to the ideal value of the observable: $\\frac{\\langle O \\rangle_{ideal} - \\epsilon}{\\langle O \\rangle_{ideal}}$, where '$\\epsilon$' is the absolute precision of the mitigation (set by the user input), and $\\langle O \\rangle_{ideal}$ is the observable at the noiseless circuit.\n",
        "'Runtime usage' measures the usage of the benchmark in batch mode (sum over usage of individual jobs), whereas 'total time' measures usage in session mode (experiment wall time), which includes additional classical and communication times. QESEM is available for execution in both modes, so that users can make the best use of their available resources.\n",
        "\n",
        "The 28-qubit Kicked Ising circuits simulate the Discrete Time Quasicrystal studied by Shinjo et al. (see [arXiv 2403.16718](https://arxiv.org/abs/2403.16718) and [Q2B24 Tokyo](https://www.youtube.com/watch?v=tQW6FdLc6zo)) on three connected loops of ibm\\_kawasaki. The circuit parameters taken here are $(\\theta_x, \\theta_z) = (0.9 \\pi, 0)$, with a ferromagnetic initial state $| \\psi_0 \\rangle = | 0 \\rangle ^{\\otimes n}$. The measured observable is the absolute value of the magnetization $M = |\\frac{1}{28} \\sum_{i=0}^{27} \\langle Z_i \\rangle|$. The utility-scale Kicked Ising experiment was run on the 136 best qubits of ibm\\_fez; this particular benchmark was run at the Clifford angle $(\\theta_x, \\theta_z) = (\\pi, 0)$, at which the active volume grows slowly with circuit depth, which - together with the high device fidelities - enables high accuracy at a short runtime.\n",
        "\n",
        "Trotterized Hamiltonian simulation circuits are for a Transverse-Field Ising model at fractional angles: $(\\theta_{zz}, \\theta_x) = (\\pi / 4, \\pi /8)$ and $(\\theta_{zz}, \\theta_x) = (\\pi / 6, \\pi / 8)$ correspondingly (see [Q2B24 Tokyo](https://www.youtube.com/watch?v=tQW6FdLc6zo)). The utility-scale circuit was run on the 119 best qubits of ibm\\_brisbane, whereas the 40-qubit experiment was run on the best available chain. The accuracy is reported for the magnetization; high-accuracy results were obtained for higher-weight observables as well.\n",
        "\n",
        "The VQE circuit was developed together with researchers from the Center for Quantum Technology and Applications at the Deutsches Elektronen-Synchrotron (DESY). The target observable here was a Hamiltonian consisting of a large number of non-commuting Pauli strings, emphasizing QESEM's optimized performance for multi-basis observables. Mitigation was applied to a classically-optimized ansatz; although these results are still unpublished, results of the same quality will be obtained for different circuits with similar structural properties.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d6fb65de",
      "metadata": {},
      "source": [
        "## Get started\n",
        "\n",
        "Authenticate using your [IBM Quantum Platform API key](http://quantum.cloud.ibm.com/), and select the QESEM Qiskit Function as follows. (This snippet assumes you've already [saved your account](/docs/guides/functions-get-started#install-qiskit-functions-catalog-client) to your local environment.)\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "95a715d2",
      "metadata": {},
      "outputs": [],
      "source": [
        "import qiskit\n",
        "from qiskit_ibm_catalog import QiskitFunctionsCatalog\n",
        "\n",
        "catalog = QiskitFunctionsCatalog(channel=\"ibm_quantum_platform\")\n",
        "\n",
        "# verify that you have access to the function\n",
        "catalog.list()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "2d78b033",
      "metadata": {},
      "outputs": [],
      "source": [
        "# load the function\n",
        "qesem_function = catalog.load(\"qedma/qesem\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5f0120d8",
      "metadata": {},
      "source": [
        "## Examples\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7d102971",
      "metadata": {},
      "source": [
        "### Time estimation job example\n",
        "\n",
        "The time estimation job is useful for estimating the QPU time required for a given `pub` and `backend_name`. `backend_name` can also be set to any simulator backend; for example, `fake_fez`.\n",
        "\n",
        "QESEM uses a quasi-probabilistic, characterization-based EM method. This method has a QPU time overhead that roughly scales as:\n",
        "\n",
        "$T_{QPU} = a \\frac{e^{\\alpha IF\\cdot V_a}}{\\epsilon^2} + b$\n",
        "\n",
        "Where $V_a$ is the active volume of the circuit, $\\epsilon$ is the target precision, and $IF$ is the infidelity of the native gates.\n",
        "\n",
        "Note that `\"estimate_time_only\": \"empirical\"` uses a few minutes of QPU time to estimate the time required for the job (if the backend is a real device; if it's a simulator, then no QPU time is used). This will usually take around 5 minutes, but no more than 10 minutes. If the infidelity changes drastically between the empirical time estimation job and the mitigation job, the QPU time will also change drastically.\n",
        "\n",
        "To get started, try this basic example of estimating the required QPU time to run QESEM for a given `pub`:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d56e1440",
      "metadata": {},
      "outputs": [],
      "source": [
        "backend_name = \"fake_fez\"\n",
        "\n",
        "circ = qiskit.QuantumCircuit(5)\n",
        "circ.cx(0, 1)\n",
        "circ.cx(2, 3)\n",
        "circ.cx(1, 2)\n",
        "circ.cx(3, 4)\n",
        "\n",
        "avg_magnetization = qiskit.quantum_info.SparsePauliOp.from_sparse_list(\n",
        "    [(\"Z\", [q], 1 / 5) for q in range(5)], num_qubits=5\n",
        ")\n",
        "other_observable = qiskit.quantum_info.SparsePauliOp.from_sparse_list(\n",
        "    [(\"ZZ\", [0, 1], 1.0), (\"XZ\", [1, 4], 0.5)], num_qubits=5\n",
        ")\n",
        "\n",
        "time_estimation_job = qesem_function.run(\n",
        "    pubs=[(circ, [avg_magnetization, other_observable])],\n",
        "    options={\n",
        "        \"estimate_time_only\": \"empirical\",\n",
        "    },\n",
        "    backend_name=backend_name,  # example: \"fake_fez\", \"ibm_fez\"\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "a6eb4bda04796ac5",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2026-05-03T12:04:42.901739Z",
          "start_time": "2026-05-03T12:02:20.288020Z"
        }
      },
      "outputs": [],
      "source": [
        "time_estimate_result = (\n",
        "    time_estimation_job.result()\n",
        ")  # a list of results per pub (circuit)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5ec8053b",
      "metadata": {},
      "source": [
        "The following code snippet describes how to retrieve different execution metrics from the time estimation job (`estimate_time_only` is set):\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 106,
      "id": "77a9dca8efb1a37b",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2026-05-12T11:22:06.848571Z",
          "start_time": "2026-05-12T11:22:06.838844Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The estimated QPU time for mitigation for this PUB is: 300\n",
            "The QPU time that this time estimation job took is (here it is 0 because we used fake_fez): 0\n",
            "Gates fidelity measured during the experiment: {'CZ': 0.9951354916722668, 'ID1Q': 0.9991246627329172}\n",
            "Total shots: 220000\n",
            "Resource usage breakdown: {'RUNNING: MAPPING': {'CPU_TIME': 33.6066133165732, 'GPU_TIME': 0.0, 'QPU_TIME': 0.0}, 'RUNNING: OPTIMIZING_FOR_HARDWARE': {'CPU_TIME': 184.53575124032795, 'GPU_TIME': 0.0, 'QPU_TIME': 0.0}, 'RUNNING: WAITING_FOR_QPU': {'CPU_TIME': 0.0, 'GPU_TIME': 0.0, 'QPU_TIME': 0.0}, 'RUNNING: EXECUTING_QPU': {'CPU_TIME': 0.0, 'GPU_TIME': 0.0, 'QPU_TIME': 0.0}, 'RUNNING: POST_PROCESSING': {'CPU_TIME': 0.0, 'GPU_TIME': 0.0, 'QPU_TIME': 0.0}}\n"
          ]
        }
      ],
      "source": [
        "pub_result = time_estimate_result[0]\n",
        "\n",
        "print(\n",
        "    f\"The estimated QPU time for mitigation for this PUB is: {pub_result.metadata['time_estimation_sec']}\"\n",
        ")\n",
        "print(\n",
        "    f\"The QPU time that this time estimation job took is (here it is 0 because we used fake_fez): {pub_result.metadata['total_qpu_time']}\"\n",
        ")\n",
        "print(\n",
        "    f\"Gates fidelity measured during the experiment: {pub_result.metadata['gate_fidelities']}\"\n",
        ")\n",
        "print(f\"Total shots: {pub_result.metadata['total_shots']}\")\n",
        "print(f\"Resource usage breakdown: {pub_result.metadata['resource_usage']}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5f68f983189f00a1",
      "metadata": {},
      "source": [
        "When empirical time estimation is enabled, QESEM runs a small number of circuits to provide a more accurate QPU time estimate. The mitigation results from these circuits are available in the `empirical_estimation_mitigation_results` field of the job.\n",
        "\n",
        "Since these results are derived from a limited sample of circuits, they are significantly less accurate than the final results obtained from the complete QESEM mitigation job. However, when the circuit is small or the target precision is low, the mitigation performed during empirical time estimation may be sufficient to reach the desired precision, and the full mitigation job may not be necessary.\n",
        "\n",
        "The `empirical_estimation_mitigation_results` field is a list of `PrimitiveResult` objects per input parameter. If the circuit is not parametrized, the list will be of length 1.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "7acca648911dacd5",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2026-05-05T13:41:38.131003Z",
          "start_time": "2026-05-05T13:41:38.127666Z"
        }
      },
      "outputs": [],
      "source": [
        "empirical_estimation_mitigation_results = time_estimate_result[0].metadata[\n",
        "    \"empirical_estimation_mitigation_results\"\n",
        "][0]  # a list per parameter"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "60b482803339ce27",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2026-05-05T15:23:58.817671Z",
          "start_time": "2026-05-05T15:23:58.811680Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Partial results for the observables:\n",
            "    Mitigated expectation values: [1.00347302 1.00693905]\n",
            "    Mitigated error bars: [0.00304061 0.00714276]\n",
            "    Number of shots used for mitigation: 180000\n",
            "    Qubit mapping: [[[0, 136], [1, 143], [2, 142], [3, 141], [4, 140]]]\n",
            "    Number of measurement bases: 2\n",
            "\n",
            "Results for each observable:\n",
            "Observable 1: ObservablesArray({'IIIIZ': 0.2, 'IIIZI': 0.2, 'IIZII': 0.2, 'IZIII': 0.2, 'ZIIII': 0.2}, shape=())\n",
            "    QESEM mitigated value: 1.003473015776871 ± 0.0030406128032204015\n",
            "Observable 2: ObservablesArray({'IIIZZ': 1.0, 'ZIIXI': 0.5}, shape=())\n",
            "    QESEM mitigated value: 1.0069390542613554 ± 0.0071427606736885925\n"
          ]
        }
      ],
      "source": [
        "print(\"Partial results for the observables:\")\n",
        "\n",
        "print(\n",
        "    f\"    Mitigated expectation values: {empirical_estimation_mitigation_results.data.evs}\"\n",
        ")\n",
        "print(\n",
        "    f\"    Mitigated error bars: {empirical_estimation_mitigation_results.data.stds}\"\n",
        ")\n",
        "print(\n",
        "    f\"    Number of shots used for mitigation: {empirical_estimation_mitigation_results.metadata['mitigation_shots']}\"\n",
        ")\n",
        "transpiled_circ = empirical_estimation_mitigation_results.metadata[\n",
        "    \"transpiled_circ\"\n",
        "]\n",
        "print(f\"    Qubit mapping: {transpiled_circ['qubit_maps']}\")\n",
        "print(\n",
        "    f\"    Number of measurement bases: {transpiled_circ['num_measurement_bases']}\\n\"\n",
        ")\n",
        "\n",
        "# results per obs\n",
        "emp_obs_results = empirical_estimation_mitigation_results.metadata[\"results\"][\n",
        "    0\n",
        "]\n",
        "# print(f\"Results for each observable: {results}\")\n",
        "print(\"Results for each observable:\")\n",
        "\n",
        "for i, (obs_array, result_dict) in enumerate(emp_obs_results):\n",
        "    # obs_array, result_dict = results\n",
        "    print(f\"Observable {i+1}: {obs_array}\")\n",
        "    print(\n",
        "        f\"    QESEM mitigated value: {result_dict['qesem']['value']} \\u00b1 {result_dict['qesem']['error_bar']}\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ae10a4c8",
      "metadata": {},
      "source": [
        "### QESEM mitigation job example\n",
        "\n",
        "The following example executes a QESEM job:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e7351d6b",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2026-05-05T13:42:11.391952Z",
          "start_time": "2026-05-05T13:42:01.266109Z"
        }
      },
      "outputs": [],
      "source": [
        "sample_job = qesem_function.run(\n",
        "    pubs=[(circ, [avg_magnetization, other_observable])],\n",
        "    backend_name=backend_name,  # example: \"ibm_fez\"\n",
        "    # options = {\n",
        "    #     \"estimate_time_only\": \"empirical\",\n",
        "    #     \"default_precision\": 0.2,  # Default precision is applied to all pubs that don't have a precision specified, see API reference for more details\n",
        "    #     \"max_execution_time\": 3600,  # You can specify a maximum QPU time in seconds, see API reference for more details\n",
        "    #     \"transpilation_level\": \"standard\",  # \"minimal_with_layout_opt\" for minimal transpilation, see API reference for more details\n",
        "    #     \"parallel_execution\": True,  # True for parallel execution, see API reference for more details\n",
        "    # },\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f7f7fe68",
      "metadata": {},
      "source": [
        "For a detailed description of each input field and option, see the [QESEM API reference](/docs/api/functions/qedma-qesem).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dfe99043",
      "metadata": {},
      "source": [
        "You can use the familiar Qiskit Serverless APIs to check your Qiskit Function workload's status or return results:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "856fe992",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "3ac6b2df-15b0-4dc0-8f48-cf14bd20a1c8\n",
            "DONE\n"
          ]
        }
      ],
      "source": [
        "# Print the ID so you can use it later, if necessary\n",
        "print(sample_job.job_id)\n",
        "print(sample_job.status())\n",
        "sample_result = sample_job.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a9e51542",
      "metadata": {},
      "source": [
        "The following code snippet demonstrates how to retrieve the mitigation results and execution metrics. These contain essential data that enables a deeper understanding of how different parameters impact the QESEM execution. It may also be relevant when writing a paper based on your research.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 50,
      "id": "117c2e4aa624ed51",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2026-05-12T11:23:45.739919Z",
          "start_time": "2026-05-12T11:23:45.737884Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "PUB 0:\n",
            "  The QPU time that this job took is (here it is 0 because we used fake_fez): 0.0\n",
            "  Gates fidelity measured during the experiment: {'CZ': 0.9953704216147041, 'ID1Q': 0.9991834123567518}\n",
            "  Total shots: 446000\n",
            "  Number of shots used for mitigation: 194000\n",
            "  Resource usage breakdown: {'RUNNING: MAPPING': {'CPU_TIME': 32.52745003718883, 'GPU_TIME': 0.0, 'QPU_TIME': 0.0}, 'RUNNING: OPTIMIZING_FOR_HARDWARE': {'CPU_TIME': 257.850521848537, 'GPU_TIME': 0.0, 'QPU_TIME': 0.0}, 'RUNNING: WAITING_FOR_QPU': {'CPU_TIME': 0.0, 'GPU_TIME': 0.0, 'QPU_TIME': 0.0}, 'RUNNING: EXECUTING_QPU': {'CPU_TIME': 0.0, 'GPU_TIME': 0.0, 'QPU_TIME': 0.0}, 'RUNNING: POST_PROCESSING': {'CPU_TIME': 0.0, 'GPU_TIME': 0.0, 'QPU_TIME': 0.0}}\n"
          ]
        }
      ],
      "source": [
        "for pub_idx, pub_result in enumerate(\n",
        "    sample_result\n",
        "):  # each element in the list is a result for a different pub, here we sent only one pub\n",
        "    print(f\"\\nPUB {pub_idx}:\")\n",
        "    print(\n",
        "        f\"  The QPU time that this job took is (here it is 0 because we used fake_fez): {pub_result.metadata['total_qpu_time']}\"\n",
        "    )\n",
        "    print(\n",
        "        f\"  Gates fidelity measured during the experiment: {pub_result.metadata['gate_fidelities']}\"\n",
        "    )\n",
        "    print(f\"  Total shots: {pub_result.metadata['total_shots']}\")\n",
        "    print(\n",
        "        f\"  Number of shots used for mitigation: {pub_result.metadata['mitigation_shots']}\"\n",
        "    )\n",
        "    print(\n",
        "        f\"  Resource usage breakdown: {pub_result.metadata['resource_usage']}\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3f71cc3a",
      "metadata": {},
      "source": [
        "In `metadata[\"results\"]`, results are grouped first by circuit instance and then by observable.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 99,
      "id": "a5899795",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Full QESEM mitigation results:\n",
            "\n",
            "PUB 0:\n",
            "  Mitigated expectation values: [1.00648343 1.00636289]\n",
            "  Mitigated error bars: [0.00253812 0.00693586]\n",
            "  Unmitigated expectation values: [0.98031429 0.96357143]\n",
            "  Unmitigated error bars: [0.00124128 0.00578812]\n",
            "  Transpiled circuits:\n",
            "    Circuit 0:\n",
            "      Qubit mapping: [[[0, 140], [1, 141], [2, 142], [3, 143], [4, 136]]]\n",
            "      Measurement bases: 2\n",
            "  Results for each observable:\n",
            "    Circuit 0:\n",
            "      Observable 0: ObservablesArray({'IIIIZ': 0.2, 'IIIZI': 0.2, 'IIZII': 0.2, 'IZIII': 0.2, 'ZIIII': 0.2}, shape=())\n",
            "        QESEM mitigated value: 1.0064834305181962 ± 0.002538119914534849\n",
            "        Unmitigated value: 0.9803142857142859 ± 0.0012412835813609938\n",
            "      Observable 1: ObservablesArray({'IIIZZ': 1.0, 'ZIIXI': 0.5}, shape=())\n",
            "        QESEM mitigated value: 1.0063628870614818 ± 0.006935859820870656\n",
            "        Unmitigated value: 0.9635714285714285 ± 0.005788121870526659\n"
          ]
        }
      ],
      "source": [
        "print(\"Full QESEM mitigation results:\")\n",
        "\n",
        "for pub_idx, pub_result in enumerate(sample_result):\n",
        "    print(f\"\\nPUB {pub_idx}:\")\n",
        "\n",
        "    print(f\"  Mitigated expectation values: {pub_result.data.evs}\")\n",
        "    print(f\"  Mitigated error bars: {pub_result.data.stds}\")\n",
        "    noisy_results = pub_result.metadata.get(\"noisy_results\")\n",
        "    print(f\"  Unmitigated expectation values: {noisy_results.evs}\")\n",
        "    print(f\"  Unmitigated error bars: {noisy_results.stds}\")\n",
        "\n",
        "    print(\"  Transpiled circuits:\")\n",
        "    for circ_idx, transpiled_circ in enumerate(\n",
        "        pub_result.metadata[\"transpiled_circs\"]\n",
        "    ):\n",
        "        print(f\"    Circuit {circ_idx}:\")\n",
        "        # print(f\"      Circuit: \\n {transpiled_circ['circuit']}\") # not printing it because it's long but you can see the transpiled circuit itself\n",
        "        print(f\"      Qubit mapping: {transpiled_circ['qubit_maps']}\")\n",
        "        print(\n",
        "            f\"      Measurement bases: {transpiled_circ['num_measurement_bases']}\"\n",
        "        )\n",
        "\n",
        "    print(\"  Results for each observable:\")\n",
        "    for circ_idx, circ_results in enumerate(pub_result.metadata[\"results\"]):\n",
        "        print(f\"    Circuit {circ_idx}:\")\n",
        "        for obs_idx, (obs_array, result_dict) in enumerate(circ_results):\n",
        "            print(f\"      Observable {obs_idx}: {obs_array}\")\n",
        "            print(\n",
        "                f\"        QESEM mitigated value: {result_dict['qesem']['value']} ± {result_dict['qesem']['error_bar']}\"\n",
        "            )\n",
        "            print(\n",
        "                f\"        Unmitigated value: {result_dict['unmitigated']['value']} ± {result_dict['unmitigated']['error_bar']}\"\n",
        "            )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "345524a3",
      "metadata": {},
      "source": [
        "**Main results breakdown:**\n",
        "\n",
        "* `mitigated`: the fully mitigated QESEM expectation value.\n",
        "* `unmitigated`: the raw physical-noise result without error mitigation.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "65a2e503",
      "metadata": {},
      "source": [
        "#### QESEM heuristic extrapolation results\n",
        "\n",
        "In a standard QESEM run with a single `precision` float, the results also include automatically available noise-scaling points that are used for the QESEM heuristic. These points are computed without additional QPU resources.\n",
        "\n",
        "Scale `1.0` represents the physical device noise level with readout mitigation (REM), while scale `2.0` is the complementary noise-amplified point, also with REM. These points are used to produce the `qesem_heuristic` result.\n",
        "\n",
        "* `qesem_heuristic`: a ZNE-style estimate computed from the available noise-scaled data. Currently, this uses exponential extrapolation.\n",
        "* `noise_scaling.results_with_REM`: expectation values at different noise scales, all with readout mitigation (REM).\n",
        "\n",
        "A subtle but important detail is that the scale `1.0` result is not the same as the `unmitigated` result. Both correspond to the physical device noise level, but the scale `1.0` point includes readout mitigation, while `unmitigated` does not.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 101,
      "id": "21afd3d2",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "QESEM heuristic results:\n",
            "\n",
            "PUB 0:\n",
            "  Circuit 0:\n",
            "    Observable 0: ObservablesArray({'IIIIZ': 0.2, 'IIIZI': 0.2, 'IIZII': 0.2, 'IZIII': 0.2, 'ZIIII': 0.2}, shape=())\n",
            "      QESEM heuristic:\n",
            "        Value: 1.0008161638888535 ± 0.0038859458884403964\n",
            "        Extrapolation: exponential\n",
            "        Scale factors: [1.0, 2.0]\n",
            "      Noise scaling results:\n",
            "        Scaling method: QESEM\n",
            "        Results with Readout mitigation (REM):\n",
            "          Scale factor 1.0: 0.9918395459270772 ± 0.0012565417579355634\n",
            "          Scale factor 2.0: 0.982943441922748 ± 0.0028919278067695018\n",
            "    Observable 1: ObservablesArray({'IIIZZ': 1.0, 'ZIIXI': 0.5}, shape=())\n",
            "      QESEM heuristic:\n",
            "        Value: 0.9960853148925298 ± 0.013811635038961175\n",
            "        Extrapolation: exponential\n",
            "        Scale factors: [1.0, 2.0]\n",
            "      Noise scaling results:\n",
            "        Scaling method: QESEM\n",
            "        Results with Readout mitigation (REM):\n",
            "          Scale factor 1.0: 0.9902860583785115 ± 0.005921236914723409\n",
            "          Scale factor 2.0: 0.984520565414274 ± 0.006909522262347639\n"
          ]
        }
      ],
      "source": [
        "print(\"QESEM heuristic results:\")\n",
        "\n",
        "for pub_idx, pub_result in enumerate(sample_result):\n",
        "    print(f\"\\nPUB {pub_idx}:\")\n",
        "    for circ_idx, circ_results in enumerate(pub_result.metadata[\"results\"]):\n",
        "        print(f\"  Circuit {circ_idx}:\")\n",
        "        for obs_idx, (obs_array, result_dict) in enumerate(circ_results):\n",
        "            print(f\"    Observable {obs_idx}: {obs_array}\")\n",
        "            qesem_heuristic = result_dict[\"qesem_heuristic\"][0]\n",
        "            print(\"      QESEM heuristic:\")\n",
        "            print(\n",
        "                f\"        Value: {qesem_heuristic['value']} ± {qesem_heuristic['error_bar']}\"\n",
        "            )\n",
        "            print(\n",
        "                f\"        Extrapolation: {qesem_heuristic['extrapolation']}\"\n",
        "            )\n",
        "            print(\n",
        "                f\"        Scale factors: {qesem_heuristic['scale_factors']}\"\n",
        "            )\n",
        "            noise_scaling = result_dict[\"noise_scaling\"]\n",
        "            print(\"      Noise scaling results:\")\n",
        "            print(\n",
        "                f\"        Scaling method: {noise_scaling['scaling_method']}\"\n",
        "            )\n",
        "            print(\"        Results with Readout mitigation (REM):\")\n",
        "            for rem_result in sorted(\n",
        "                (\n",
        "                    item\n",
        "                    for item in noise_scaling[\"results_with_REM\"]\n",
        "                    if item[\"scale\"] != 0.0\n",
        "                ),\n",
        "                key=lambda item: item[\"scale\"],\n",
        "            ):\n",
        "                print(\n",
        "                    f\"          Scale factor {rem_result['scale']}: {rem_result['value']} ± {rem_result['error_bar']}\"\n",
        "                )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "899d229a",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\">\n",
        "  The following examples focus on feature-specific inputs and results, so they do not print the full execution metrics each time. The top-level metadata shown earlier, such as <code>total\\_qpu\\_time</code>, <code>gate\\_fidelities</code>, <code>total\\_shots</code>, <code>mitigation\\_shots</code>, and <code>resource\\_usage</code>, is available for these jobs as well.\n",
        "\n",
        "  Some variables from the previous examples, including the backend, observables, and base circuit parameters, are reused below for conciseness.\n",
        "</Admonition>\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  All of the following examples can also be run with empirical time estimation. To enable it, pass <code>\"estimate\\_time\\_only\": \"empirical\"</code> in the function options.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "079cb5be",
      "metadata": {},
      "source": [
        "### Parameterized circuit example\n",
        "\n",
        "Many algorithms evaluate the same circuit at several parameter values. Sending the parameterized circuit as one QESEM job lets QESEM share characterization and calibration across the circuit instances, which can reduce QPU-time overhead compared with running separate jobs.\n",
        "\n",
        "Submitting a parameterized circuit requires using the `\"minimal_with_layout_opt\"` transpilation level.\n",
        "Circuits submitted at this level should already be expressed using the backend's basis gates, depending on the backend. At this level, QESEM keeps the submitted structure as close as possible to the input circuit, respects barriers during layerification (grouping operations into layers of parallel two-qubit gates), and still handles hardware mapping to high-fidelity qubits and device connectivity automatically.\n",
        "\n",
        "In practice, this means you should transpile circuits to the target backend’s basis gates before submission. A simple basis-gate transpilation example is shown below.\n",
        "\n",
        "QESEM currently supports only one observable per parameter set. The two parameter rows below are zipped with the two observables: the first row is measured with `avg_magnetization`, and the second row is measured with `other_observable`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "704f8aa5",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Transpile to the backend basis gates only. With minimal_with_layout_opt, QESEM handles hardware mapping/connectivity and observable layout internally.\n",
        "from qiskit_ibm_runtime.fake_provider import FakeFez\n",
        "\n",
        "backend = FakeFez()\n",
        "basis = backend.operation_names\n",
        "print(basis)\n",
        "\n",
        "param0 = qiskit.circuit.Parameter(\"param0\")\n",
        "param1 = qiskit.circuit.Parameter(\"param1\")\n",
        "parametrized_circ = qiskit.QuantumCircuit(5)\n",
        "parametrized_circ.rx(param0, 0)\n",
        "parametrized_circ.rx(param1, 1)\n",
        "parametrized_circ.cx(0, 1)\n",
        "parametrized_circ.cx(2, 3)\n",
        "parametrized_circ.cx(1, 2)\n",
        "parametrized_circ.cx(3, 4)\n",
        "\n",
        "parametrized_circ = qiskit.transpile(\n",
        "    parametrized_circ, basis_gates=basis, optimization_level=1\n",
        ")\n",
        "parametrized_parameter_values = [[0.5, 0.1], [0.0, 0.6]]\n",
        "parametrized_observables = [avg_magnetization, other_observable]\n",
        "\n",
        "parametrized_job = qesem_function.run(\n",
        "    pubs=[\n",
        "        (\n",
        "            parametrized_circ,\n",
        "            parametrized_observables,\n",
        "            parametrized_parameter_values,\n",
        "            0.1,\n",
        "        )\n",
        "    ],\n",
        "    backend_name=backend_name,\n",
        "    options={\n",
        "        \"max_execution_time\": 300,\n",
        "        \"transpilation_level\": \"minimal_with_layout_opt\",\n",
        "    },\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 72,
      "id": "85a452fc",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "d1b0e29b-196c-4896-aec6-44a268ebd874\n",
            "DONE\n"
          ]
        }
      ],
      "source": [
        "print(parametrized_job.job_id)\n",
        "print(parametrized_job.status())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 73,
      "id": "244cd3b2",
      "metadata": {},
      "outputs": [],
      "source": [
        "parametrized_result = parametrized_job.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 77,
      "id": "03399db5",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Parameterized circuit QESEM results:\n",
            "\n",
            "PUB 0:\n",
            "  Mitigated expectation values: [0.92392021 0.82517653]\n",
            "  Mitigated error bars: [0.00565281 0.00616016]\n",
            "  Unmitigated expectation values: [0.9028     0.78771429]\n",
            "  Unmitigated error bars: [0.00335142 0.00925413]\n",
            "  Results for each parameter value:\n",
            "    Parameter set 0: [0.5, 0.1]\n",
            "      Observable 0: ObservablesArray({'IIIIZ': 0.2, 'IIIZI': 0.2, 'IIZII': 0.2, 'IZIII': 0.2, 'ZIIII': 0.2}, shape=())\n",
            "        QESEM mitigated value: 0.923920210615709 ± 0.005652811570890183\n",
            "        Unmitigated value: 0.9028 ± 0.0033514176105045447\n",
            "    Parameter set 1: [0.0, 0.6]\n",
            "      Observable 0: ObservablesArray({'IIIZZ': 1.0, 'ZIIXI': 0.5}, shape=())\n",
            "        QESEM mitigated value: 0.8251765289285893 ± 0.006160161743353999\n",
            "        Unmitigated value: 0.7877142857142858 ± 0.009254130564027902\n"
          ]
        }
      ],
      "source": [
        "print(\"Parameterized circuit QESEM results:\")\n",
        "\n",
        "for pub_idx, pub_result in enumerate(parametrized_result):\n",
        "    print(f\"\\nPUB {pub_idx}:\")\n",
        "    print(f\"  Mitigated expectation values: {pub_result.data.evs}\")\n",
        "    print(f\"  Mitigated error bars: {pub_result.data.stds}\")\n",
        "    noisy_results = pub_result.metadata[\"noisy_results\"]\n",
        "    print(f\"  Unmitigated expectation values: {noisy_results.evs}\")\n",
        "    print(f\"  Unmitigated error bars: {noisy_results.stds}\")\n",
        "    print(\"  Results for each parameter value:\")\n",
        "    for param_idx, param_results in enumerate(pub_result.metadata[\"results\"]):\n",
        "        print(\n",
        "            f\"    Parameter set {param_idx}: {parametrized_parameter_values[param_idx]}\"\n",
        "        )\n",
        "        for obs_idx, (obs_array, result_dict) in enumerate(param_results):\n",
        "            print(f\"      Observable {obs_idx}: {obs_array}\")\n",
        "            print(\n",
        "                f\"        QESEM mitigated value: {result_dict['qesem']['value']} ± {result_dict['qesem']['error_bar']}\"\n",
        "            )\n",
        "            print(\n",
        "                f\"        Unmitigated value: {result_dict['unmitigated']['value']} ± {result_dict['unmitigated']['error_bar']}\"\n",
        "            )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d6d1b6c2",
      "metadata": {},
      "source": [
        "### Multi-pub example\n",
        "\n",
        "Multi-pub execution is useful when you want to run several related circuits in one QESEM job. Like parameterized execution, this can reduce overhead because QESEM can share characterization and calibration across circuit instances instead of repeating it in separate jobs.\n",
        "\n",
        "This is especially useful for circuits with a shared layer structure, such as **Trotter-type** workloads, where different circuits reuse the same unique layers. In that case, running them together can reduce characterization cost compared with independent QESEM jobs.\n",
        "\n",
        "Multi-pub jobs require `\"transpilation_level\": \"minimal_with_layout_opt\"`. As in the parameterized example, the circuits should be transpiled to the target backend’s basis gates before submission. QESEM then handles device connectivity, layout, and mapping to high-fidelity qubits internally.\n",
        "\n",
        "Each PUB below contains one circuit and the same two observables used earlier in the notebook, so the returned `PrimitiveResult` contains one `PubResult` per input circuit.\n",
        "\n",
        "The example below uses two simple Trotter circuits with the same layer pattern: `circ_a` has one Trotter layer, and `circ_b` repeats the same layer pattern twice. This makes the shared structure explicit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8cc663fc",
      "metadata": {},
      "outputs": [],
      "source": [
        "def make_trotter_circuit(num_qubits, num_layers, zz_angle=0.2, x_angle=0.1):\n",
        "    trotter_circ = qiskit.QuantumCircuit(num_qubits)\n",
        "    for _ in range(num_layers):\n",
        "        for q in range(num_qubits):\n",
        "            trotter_circ.rx(x_angle, q)\n",
        "        trotter_circ.barrier()\n",
        "        for q in range(0, num_qubits - 1, 2):\n",
        "            trotter_circ.rzz(zz_angle, q, q + 1)\n",
        "        trotter_circ.barrier()\n",
        "        for q in range(1, num_qubits - 1, 2):\n",
        "            trotter_circ.rzz(zz_angle, q, q + 1)\n",
        "        trotter_circ.barrier()\n",
        "    return trotter_circ\n",
        "\n",
        "\n",
        "circ_a = make_trotter_circuit(num_qubits=5, num_layers=1)\n",
        "circ_b = make_trotter_circuit(num_qubits=5, num_layers=2)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "371c4893",
      "metadata": {},
      "outputs": [],
      "source": [
        "multi_pubs = [\n",
        "    (\n",
        "        qiskit.transpile(qci, basis_gates=basis, optimization_level=1),\n",
        "        [avg_magnetization, other_observable],\n",
        "    )\n",
        "    for qci in [circ_a, circ_b]\n",
        "]\n",
        "\n",
        "multi_circ_job = qesem_function.run(\n",
        "    pubs=multi_pubs,\n",
        "    backend_name=backend_name,\n",
        "    options={\n",
        "        \"max_execution_time\": 300,\n",
        "        \"transpilation_level\": \"minimal_with_layout_opt\",\n",
        "        \"default_precision\": 0.1,\n",
        "    },\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 91,
      "id": "92e45737",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "e34565b8-7262-4133-a120-de42ce624a99\n",
            "DONE\n"
          ]
        }
      ],
      "source": [
        "print(multi_circ_job.job_id)\n",
        "print(multi_circ_job.status())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 92,
      "id": "23c61008",
      "metadata": {},
      "outputs": [],
      "source": [
        "multi_circ_result = multi_circ_job.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 105,
      "id": "1ae6f8c3",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Multi-pub QESEM results:\n",
            "\n",
            "PUB 0:\n",
            "  Mitigated expectation values: [0.99502406 1.02209332]\n",
            "  Mitigated error bars: [0.00483819 0.00707488]\n",
            "  Unmitigated expectation values: [0.96934286 0.97271429]\n",
            "  Unmitigated error bars: [0.00124855 0.00617294]\n",
            "  Results for each observable:\n",
            "    Circuit 0:\n",
            "      Observable 0: ObservablesArray({'IIIIZ': 0.2, 'IIIZI': 0.2, 'IIZII': 0.2, 'IZIII': 0.2, 'ZIIII': 0.2}, shape=())\n",
            "        QESEM mitigated value: 0.9950240647925642 ± 0.004838188086259301\n",
            "        Unmitigated value: 0.9693428571428573 ± 0.0012485470362492692\n",
            "      Observable 1: ObservablesArray({'IIIZZ': 1.0, 'ZIIXI': 0.5}, shape=())\n",
            "        QESEM mitigated value: 1.0220933230604674 ± 0.007074884384355636\n",
            "        Unmitigated value: 0.9727142857142859 ± 0.006172939375512439\n",
            "\n",
            "PUB 1:\n",
            "  Mitigated expectation values: [0.98850017 1.02555188]\n",
            "  Mitigated error bars: [0.0077912  0.01672652]\n",
            "  Unmitigated expectation values: [0.93682857 0.95371429]\n",
            "  Unmitigated error bars: [0.00156245 0.00665735]\n",
            "  Results for each observable:\n",
            "    Circuit 0:\n",
            "      Observable 0: ObservablesArray({'IIIIZ': 0.2, 'IIIZI': 0.2, 'IIZII': 0.2, 'IZIII': 0.2, 'ZIIII': 0.2}, shape=())\n",
            "        QESEM mitigated value: 0.988500171577252 ± 0.007791203181151346\n",
            "        Unmitigated value: 0.9368285714285716 ± 0.001562451883089579\n",
            "      Observable 1: ObservablesArray({'IIIZZ': 1.0, 'ZIIXI': 0.5}, shape=())\n",
            "        QESEM mitigated value: 1.02555188098689 ± 0.016726524388086233\n",
            "        Unmitigated value: 0.9537142857142858 ± 0.006657345655544263\n"
          ]
        }
      ],
      "source": [
        "print(\"Multi-pub QESEM results:\")\n",
        "\n",
        "for pub_idx, pub_result in enumerate(multi_circ_result):\n",
        "    print(f\"\\nPUB {pub_idx}:\")\n",
        "    print(f\"  Mitigated expectation values: {pub_result.data.evs}\")\n",
        "    print(f\"  Mitigated error bars: {pub_result.data.stds}\")\n",
        "    noisy_results = pub_result.metadata[\"noisy_results\"]\n",
        "    print(f\"  Unmitigated expectation values: {noisy_results.evs}\")\n",
        "    print(f\"  Unmitigated error bars: {noisy_results.stds}\")\n",
        "    print(\"  Results for each observable:\")\n",
        "    for circ_idx, circ_results in enumerate(pub_result.metadata[\"results\"]):\n",
        "        print(f\"    Circuit {circ_idx}:\")\n",
        "        for obs_idx, (obs_array, result_dict) in enumerate(circ_results):\n",
        "            print(f\"      Observable {obs_idx}: {obs_array}\")\n",
        "            print(\n",
        "                f\"        QESEM mitigated value: {result_dict['qesem']['value']} ± {result_dict['qesem']['error_bar']}\"\n",
        "            )\n",
        "            print(\n",
        "                f\"        Unmitigated value: {result_dict['unmitigated']['value']} ± {result_dict['unmitigated']['error_bar']}\"\n",
        "            )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f28d826",
      "metadata": {},
      "source": [
        "### Quasi-probabilistic Error Tuning (QET) example\n",
        "\n",
        "Quasi-probabilistic Error Tuning (QET) requests expectation values at selected noise scale factors. This is useful for custom noise-scaling studies and Zero-Noise Extrapolation workflows. Scale `1.0` is the physical noise level, values between `0.0` and `1.0` partially reduce noise, and values above `1.0` amplify noise.\n",
        "\n",
        "To use QET with the Qiskit Function, pass a dictionary as the PUB precision. The dictionary maps each requested noise scale to its target precision. Returned scale-factor results are stored in `noise_scaling.results_with_REM` and include readout mitigation. The scale `1.0` point is therefore not identical to the unmitigated value, because `1.0` includes readout mitigation while `unmitigated` does not.\n",
        "\n",
        "When a scale is requested, QESEM also returns the complementary scale around `1.0` without extra QPU usage. For example, requesting `0.5` can also return `1.5`, and requesting `1.3` can also return `0.7`. The precision on the complementary scale is not guaranteed.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 88,
      "id": "723cdf95",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_scale_precision = {0.5: 0.15, 1.3: 0.2}\n",
        "\n",
        "qet_job = qesem_function.run(\n",
        "    pubs=[\n",
        "        (\n",
        "            circ,\n",
        "            [avg_magnetization, other_observable],\n",
        "            None,\n",
        "            noise_scale_precision,\n",
        "        )\n",
        "    ],\n",
        "    backend_name=backend_name,\n",
        "    options={\"max_execution_time\": 300},\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 95,
      "id": "d68a015c",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "8195fa58-f037-4651-8715-36ce1cdc5521\n",
            "DONE\n"
          ]
        }
      ],
      "source": [
        "print(qet_job.job_id)\n",
        "print(qet_job.status())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 96,
      "id": "927d8543",
      "metadata": {},
      "outputs": [],
      "source": [
        "qet_result = qet_job.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 104,
      "id": "1c9ba079",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "QET noise-scaling results:\n",
            "\n",
            "PUB 0:\n",
            "  Unmitigated expectation values: [0.97822857 0.96171429]\n",
            "  Unmitigated error bars: [0.00123812 0.00672958]\n",
            "  Results for each observable:\n",
            "    Circuit 0:\n",
            "      Observable 0: ObservablesArray({'IIIIZ': 0.2, 'IIIZI': 0.2, 'IIZII': 0.2, 'IZIII': 0.2, 'ZIIII': 0.2}, shape=())\n",
            "        Scaling method: QESEM\n",
            "        Results with Readout mitigation (REM):\n",
            "          Scale factor 0.5: 0.9938730340199383 ± 0.0032116907357568275\n",
            "          Scale factor 0.7: 0.9963976191853445 ± 0.00036300258869586616\n",
            "          Scale factor 1.0: 0.9898115079506586 ± 0.0012525947426560995\n",
            "          Scale factor 1.3: 0.9864667065580341 ± 0.002633221613518526\n",
            "          Scale factor 1.5: 0.9838755527197551 ± 0.002948417797996015\n",
            "      Observable 1: ObservablesArray({'IIIZZ': 1.0, 'ZIIXI': 0.5}, shape=())\n",
            "        Scaling method: QESEM\n",
            "        Results with Readout mitigation (REM):\n",
            "          Scale factor 0.5: 1.0006538544450332 ± 0.002121742014777343\n",
            "          Scale factor 0.7: 1.0004159801523036 ± 0.0021671375357823794\n",
            "          Scale factor 1.0: 0.9898058846339917 ± 0.00690183710903159\n",
            "          Scale factor 1.3: 0.9948946719997267 ± 0.002146532859610311\n",
            "          Scale factor 1.5: 0.9927220368192772 ± 0.0020875057190323882\n"
          ]
        }
      ],
      "source": [
        "print(\"QET noise-scaling results:\")\n",
        "\n",
        "for pub_idx, pub_result in enumerate(qet_result):\n",
        "    print(f\"\\nPUB {pub_idx}:\")\n",
        "    noisy_results = pub_result.metadata[\"noisy_results\"]\n",
        "    print(f\"  Unmitigated expectation values: {noisy_results.evs}\")\n",
        "    print(f\"  Unmitigated error bars: {noisy_results.stds}\")\n",
        "    print(\"  Results for each observable:\")\n",
        "    for circ_idx, circ_results in enumerate(pub_result.metadata[\"results\"]):\n",
        "        print(f\"    Circuit {circ_idx}:\")\n",
        "        for obs_idx, (obs_array, result_dict) in enumerate(circ_results):\n",
        "            print(f\"      Observable {obs_idx}: {obs_array}\")\n",
        "            noise_scaling = result_dict[\"noise_scaling\"]\n",
        "            print(\n",
        "                f\"        Scaling method: {noise_scaling['scaling_method']}\"\n",
        "            )\n",
        "            print(\"        Results with Readout mitigation (REM):\")\n",
        "            for rem_result in sorted(\n",
        "                (\n",
        "                    item\n",
        "                    for item in noise_scaling[\"results_with_REM\"]\n",
        "                    if item[\"scale\"] != 0.0\n",
        "                ),\n",
        "                key=lambda item: item[\"scale\"],\n",
        "            ):\n",
        "                print(\n",
        "                    f\"          Scale factor {rem_result['scale']}: {rem_result['value']} ± {rem_result['error_bar']}\"\n",
        "                )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "35aed54a",
      "metadata": {},
      "source": [
        "## Fetch error messages\n",
        "\n",
        "If your workload status is ERROR, use `job.result()` to fetch the error message as follows:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "d95a3f30",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(2,), dtype=float64>), stds=np.ndarray(<shape=(2,), dtype=float64>), shape=(2,)), metadata={'gate_fidelities': {'CZ': 0.9979444718552628, 'ID1Q': 0.9991994239814883}, 'total_shots': 498600, 'mitigation_shots': 223400, 'transpiled_circs': [{'circuit': 'OPENQASM 3.0;\\ninclude \"stdgates.inc\";\\nbit[76] c0;\\nqubit[76] q0;\\nrx(0) q0[54];\\nrx(0) q0[59];\\nrx(0) q0[75];\\nrz(pi/2) q0[54];\\nrz(pi/2) q0[59];\\nrz(pi/2) q0[75];\\nrx(pi/2) q0[54];\\nr... (truncated 3771 characters)\n"
          ]
        }
      ],
      "source": [
        "# Get the result and truncate for readability\n",
        "result = sample_job.result()\n",
        "result_str = str(result)\n",
        "max_length = 500  # Adjust this value as necessary\n",
        "\n",
        "if len(result_str) > max_length:\n",
        "    truncated = (\n",
        "        result_str[:max_length]\n",
        "        + f\"... (truncated {len(result_str) - max_length} characters)\"\n",
        "    )\n",
        "else:\n",
        "    truncated = result_str\n",
        "\n",
        "print(truncated)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e9ec2e67",
      "metadata": {},
      "source": [
        "## Get support\n",
        "\n",
        "The Qedma support team is here to help! If you encounter any issues or have questions about using the QESEM Qiskit Function, please don't hesitate to reach out. Our knowledgeable and friendly support staff are ready to assist you with any technical concerns or inquiries you may have.\n",
        "\n",
        "You can email us at [support@qedma.com](mailto:support@qedma.com) for assistance. Please include as much detail as possible about the issue you're experiencing to help us provide a swift and accurate response. You can also contact your dedicated Qedma POC representative via email or phone.\n",
        "\n",
        "To help us assist you more efficiently, please provide the following information when you contact us:\n",
        "\n",
        "* A detailed description of the issue\n",
        "* The job ID\n",
        "* Any relevant error messages or codes\n",
        "\n",
        "We are committed to providing you with prompt and effective support to ensure you have the best possible experience with our Qiskit Function.\n",
        "\n",
        "We are always looking to improve our product and we welcome your suggestions! If you have ideas on how we can enhance our services or features you'd like to see, please send us your thoughts at [support@qedma.com](mailto:support@qedma.com).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5a6a25c8",
      "metadata": {},
      "source": [
        "## Next steps\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recommendations\">\n",
        "  * [Request access to Qedma QESEM](/functions?id=qedma-qesem).\n",
        "  * Visit the [API reference](/docs/api/functions/qedma-qesem) for this Qiskit Function.\n",
        "  * Try the [Simulate 2D tilted-field Ising with the QESEM function](/docs/tutorials/qedma-2d-ising-with-qesem) tutorial.\n",
        "  * Review [Aharonov, D., et al. (2025). Reliable high-accuracy error mitigation for utility-scale quantum circuits. arXiv preprint arXiv:2508.10997](https://arxiv.org/pdf/2508.10997).\n",
        "  * Review [Aharonov, D., et al. (2025). Syndrome aware mitigation of logical errors. arXiv preprint arXiv:2508.10997](https://arxiv.org/pdf/2508.10997).\n",
        "  * Review [Aharonov, D., et al. (2025). On the Importance of Error Mitigation for Quantum Computation. arXiv preprint arXiv:2512.23810](https://arxiv.org/abs/2512.23810).\n",
        "  * Review [Bauman, N. P., et al. (2025). Coupled Cluster Downfolding Theory in Simulations of Chemical Systems on Quantum Hardware. arXiv preprint arXiv:2507.01199](https://arxiv.org/pdf/2507.01199).\n",
        "  * Review [Goldack, M., et al. (2026). Computing Statistical Properties of Velocity Fields on Current Quantum Hardware. arXiv preprint arXiv:2601.10166](https://arxiv.org/pdf/2601.10166).\n",
        "  * Review [Sakuma, R., et al. (2026). Point-group symmetry analysis of many-electron wavefunctions on a quantum computer arXiv preprint arXiv:2605.24824](https://arxiv.org/abs/2605.24824).\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "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"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}