{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "3e0865c2",
      "metadata": {},
      "source": [
        "---\n",
        "title: Use postselection in workloads\n",
        "description: Understand how to integrate postselection as part of your error mitigation strategy\n",
        "---\n",
        "\n",
        "# Use postselection in workloads\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "23b642e2",
      "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.4.0\n",
        "    qiskit-ibm-runtime~=0.46.1\n",
        "    qiskit-addon-utils~=0.3.1\n",
        "    ```\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b042e4d5",
      "metadata": {},
      "source": [
        "When optimizing a workload's error mitigation strategy, it is often useful to filter out measurements known to have been contaminated by non-Markovian (correlated) noise processes. One such method for doing so involves appending a circuit with a post-processing step that measures active and adjacent \"spectator\" qubits, applies a slow rotation to each qubit, and then measures them again. In instances where the two measurements do not confirm a flipped qubit as expected, the shot is discarded by applying a mask to the results.\n",
        "\n",
        "The [Qiskit addon utilities](https://qiskit.github.io/qiskit-addon-utils/) package provides a set of transpiler passes and a postselection function to apply the mask. This page provides guidance on how to incorporate postselection into your quantum workloads by using a four-qubit GHZ state as an example.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a645f8ff",
      "metadata": {},
      "source": [
        "## Create workload\n",
        "\n",
        "Start by preparing the circuit to execute and transpile against a backend that supports fractional gates.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "68fa9100",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/guides/post-selection/extracted-outputs/68fa9100-0.svg\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit.circuit import QuantumCircuit\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "\n",
        "circuit = QuantumCircuit(4)\n",
        "circuit.h(0)\n",
        "circuit.cx(0, 1)\n",
        "circuit.cx(1, 2)\n",
        "circuit.cx(2, 3)\n",
        "circuit.measure_all()\n",
        "\n",
        "\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(use_fractional_gates=True)\n",
        "pm = generate_preset_pass_manager(optimization_level=3, backend=backend)\n",
        "\n",
        "transpiled_circuit = pm.run(circuit)\n",
        "transpiled_circuit.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5adeb935",
      "metadata": {},
      "source": [
        "## Add postselection transpiler passes\n",
        "\n",
        "Next, create a preset pass manager that includes the [`AddPostSelectionMeasures`](https://qiskit.github.io/qiskit-addon-utils/stubs/qiskit_addon_utils.noise_management.post_selection.transpiler.passes.AddSpectatorMeasures.html) and [`AddSpectatorMeasures`](https://qiskit.github.io/qiskit-addon-utils/stubs/qiskit_addon_utils.noise_management.post_selection.transpiler.passes.AddPostSelectionMeasures.html) passes from the [`qiskit-addon-utils`](https://qiskit.github.io/qiskit-addon-utils/index.html) package. This will append the circuit with a sequence of small angle `RX` rotations (effectively producing a long `X` gate) along with a second set of measurements.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "faf50950",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/guides/post-selection/extracted-outputs/faf50950-0.svg\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.transpiler import PassManager\n",
        "from qiskit_addon_utils.noise_management.post_selection import PostSelector\n",
        "from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (\n",
        "    AddPostSelectionMeasures,\n",
        "    AddSpectatorMeasures,\n",
        ")\n",
        "\n",
        "\n",
        "post_selection_pm = PassManager(\n",
        "    [\n",
        "        AddSpectatorMeasures(backend.coupling_map, add_barrier=True),\n",
        "        AddPostSelectionMeasures(x_pulse_type=\"rx\"),\n",
        "    ]\n",
        ")\n",
        "\n",
        "template_circuit_ps = post_selection_pm.run(transpiled_circuit)\n",
        "template_circuit_ps.draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e0fdf29d",
      "metadata": {},
      "source": [
        "## Execute quantum program\n",
        "\n",
        "Next, prepare a `QuantumProgram` object containing the circuit to be executed.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "649aef44",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Job ID: d82dumugbeec73alm5g0\n"
          ]
        }
      ],
      "source": [
        "from qiskit_ibm_runtime import QuantumProgram, Executor\n",
        "\n",
        "shots = 4000\n",
        "\n",
        "program = QuantumProgram(shots=shots)\n",
        "program.append_circuit_item(template_circuit_ps)\n",
        "\n",
        "# Initialize the Executor job and run\n",
        "executor = Executor(backend)\n",
        "executor_job = executor.run(program)\n",
        "print(f\"Job ID: {executor_job.job_id()}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4068dd8f",
      "metadata": {},
      "source": [
        "Now you can interpret the results. The executor result is a dictionary with several keys.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "64f52429",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "dict_keys(['meas', 'spec', 'meas_ps', 'spec_ps'])"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "executor_result = executor_job.result()[0]\n",
        "executor_result.keys()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6b460888",
      "metadata": {},
      "source": [
        "These keys correspond to the active and spectator qubits before the `rx` instructions (`meas` and `spec`) and after the `rx` instructions (`meas_ps` and `spec_ps`). Each of these is an array of arrays based on the number of shots and qubits. In this case, the shape is (1000, 4).\n",
        "\n",
        "## Create postselection mask\n",
        "\n",
        "From these measurements, you can create a mask using the  [`PostSelector`](https://qiskit.github.io/qiskit-addon-utils/apidocs/qiskit_addon_utils.noise_management.html#qiskit_addon_utils.noise_management.PostSelector) class from `qiskit-addon-utils`. This mask is a boolean array where each shot is marked as either `True` or `False` based on one of two postselection strategies. The first strategy, `node`, uses qubit information to decide whether a measurement shot should be discarded — and the second, `edge`, uses nearest-neighbor connectivity information to make this decision.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "299d460d",
      "metadata": {},
      "outputs": [],
      "source": [
        "post_selector = PostSelector.from_circuit(\n",
        "    circuit=template_circuit_ps, coupling_map=backend.coupling_map\n",
        ")\n",
        "\n",
        "mask_node = post_selector.compute_mask(executor_result, strategy=\"node\")\n",
        "mask_edge = post_selector.compute_mask(executor_result, strategy=\"edge\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "023cb624",
      "metadata": {},
      "source": [
        "Both the node and the edge strategies often discard different shots. You can choose to select any of them. This notebook takes a bitwise AND, which is a conservative strategy that retains a shot only if it is passed by both node and edge strategies.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "5ec9bc4a",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The combined mask: [ True  True  True ...  True  True  True]\n",
            "Percentage of the shots retained is after post selection 75.225\n"
          ]
        }
      ],
      "source": [
        "mask = mask_node & mask_edge\n",
        "print(f\"The combined mask: {mask}\")\n",
        "count_retained = 0\n",
        "\n",
        "for m in mask:\n",
        "    count_retained += m\n",
        "\n",
        "print(\n",
        "    f\"Percentage of the shots retained is after post selection {100 * count_retained / shots}\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "77a9c1af",
      "metadata": {},
      "source": [
        "Compare the probability distribution with and without postselection. The following snippet computes the probability distribution before and after postselection, as well as the distance between the measured and ideal distributions.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "43704cfc",
      "metadata": {},
      "outputs": [],
      "source": [
        "counts = {}\n",
        "counts_ps = {}\n",
        "\n",
        "\n",
        "for idx, measurement in enumerate(executor_result[\"meas\"]):\n",
        "    bitstring = \"\"\n",
        "    for bit in measurement:\n",
        "        bitstring += str(int(bit))\n",
        "\n",
        "    if bitstring in counts:\n",
        "        counts[bitstring] += 1\n",
        "    else:\n",
        "        counts[bitstring] = 1\n",
        "\n",
        "    # Compute count data for postselected shots based on the mask\n",
        "    if mask[idx]:\n",
        "        bitstring = \"\"\n",
        "        for bit in measurement:\n",
        "            bitstring += str(int(bit))\n",
        "\n",
        "        if bitstring in counts_ps:\n",
        "            counts_ps[bitstring] += 1\n",
        "        else:\n",
        "            counts_ps[bitstring] = 1\n",
        "\n",
        "for key, val in counts.items():\n",
        "    counts[key] = val / shots\n",
        "\n",
        "\n",
        "for key, val in counts_ps.items():\n",
        "    counts_ps[key] = float(val / count_retained)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "16551af2",
      "metadata": {},
      "source": [
        "To demonstrate how the postselection changed your results, compute the distance between the ideal probability distribution and the measured ones.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "b1ba31b9",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Distance from ideal distribution before postselection: 0.9015\n",
            "Distance from ideal distribution before after-selection: 0.9416749750747756\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/guides/post-selection/extracted-outputs/b1ba31b9-1.svg\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 8,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "import itertools\n",
        "from qiskit.visualization import plot_histogram\n",
        "\n",
        "bitstrings = [\"\".join(i) for i in itertools.product(\"01\", repeat=4)]\n",
        "counts_ideal = {}\n",
        "for bitstring in bitstrings:\n",
        "    counts_ideal[bitstring] = 0.0\n",
        "counts_ideal[\"1111\"] = 0.5\n",
        "counts_ideal[\"0000\"] = 0.5\n",
        "\n",
        "\n",
        "prob_distance = 0.0\n",
        "prob_distance_ps = 0.0\n",
        "\n",
        "for bitstring in counts_ideal.keys():\n",
        "    dist = 0.0\n",
        "    dist_ps = 0.0\n",
        "    if bitstring in counts:\n",
        "        dist = abs(counts[bitstring] - counts_ideal[bitstring])\n",
        "    if bitstring in counts_ps:\n",
        "        dist_ps = abs(counts_ps[bitstring] - counts_ideal[bitstring])\n",
        "    prob_distance += dist\n",
        "    prob_distance_ps += dist_ps\n",
        "\n",
        "\n",
        "print(\n",
        "    f\"Distance from ideal distribution before postselection: {1-prob_distance*0.5}\"\n",
        ")\n",
        "print(\n",
        "    f\"Distance from ideal distribution before after-selection: {1-prob_distance_ps*0.5}\"\n",
        ")\n",
        "\n",
        "\n",
        "plot_histogram([counts, counts_ps], legend=[\"Normal\", \"Post selected\"])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c205afea",
      "metadata": {},
      "source": [
        "While postselection can significantly improve result quality by filtering out outcome measurements that were affected by non-Markovian noise, it is not a complete solution to error mitigation on its own. Postselection reduces the impact of certain errors by discarding invalid measurement results, but this comes at the cost of increased sampling overhead and does not address all error mechanisms present in near-term quantum hardware. As a result, it is likely insufficient to rely solely on postselection for more complex or deeper circuits. Instead, postselection is most effective when used as part of a broader error mitigation strategy — complementing techniques such as measurement error mitigation, noise-aware circuit compilation, or probabilistic error cancellation — to improve the reliability of quantum workloads while balancing accuracy and resource cost.\n",
        "\n",
        "## Next steps\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recommendations\">\n",
        "  * Understand how to incorporate [noise learning](/docs/guides/noise-learning) into a quantum workload.\n",
        "  * Read through other available [error mitigation and suppression](/docs/guides/error-mitigation-and-suppression-techniques) techniques.\n",
        "  * Learn how to use [spacetime codes](/docs/tutorials/ghz-spacetime-codes) for a low-overhead approach to error detection\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
}