{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "db3ca932-4023-422d-96c0-8675de193fa9",
      "metadata": {},
      "source": [
        "---\n",
        "title: Compare transpiler settings\n",
        "description: In this tutorial, we'll explore the transpilation pipeline and take you through the full process of creating, transpiling, and submitting circuits.\n",
        "---\n",
        "\n",
        "# Compare transpiler settings\n",
        "\n",
        "*Usage estimate: under one minute on an Eagle r3 processor (NOTE: This is an estimate only. Your runtime might vary.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c909d475-622d-4526-80ef-1d14328237c1",
      "metadata": {
        "tags": [
          "version-info"
        ]
      },
      "source": [
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3609dc34-10de-47ea-9d1d-f516493a7b91",
      "metadata": {},
      "source": [
        "## Background\n",
        "\n",
        "To ensure faster and more efficient results, as of 1 March 2024, circuits and observables need to be transformed to only use instructions supported by the QPU (quantum processing unit) before being submitted to the Qiskit Runtime primitives. We call these *instruction set architecture* (ISA) circuits and observables. One common way to do this is to use the transpiler's `generate_preset_pass_manager` function.  However, you might choose to follow a more manual process.\n",
        "\n",
        "For example, you might want to target a specific subset of qubits on a specific device. This walkthrough tests the performance of different transpiler settings by completing the full process of creating, transpiling, and submitting circuits.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c1d3bffc-214a-4da5-910d-d6dfb5c0916f",
      "metadata": {},
      "source": [
        "## Requirements\n",
        "\n",
        "Before you begin, ensure that you have the following installed:\n",
        "\n",
        "* Qiskit SDK v1.2 or later, with [visualization](/docs/api/qiskit/visualization) support\n",
        "* Qiskit Runtime v0.28 or later (`pip install qiskit-ibm-runtime`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "433481dc-c54e-4579-8300-57861bbef0fc",
      "metadata": {},
      "source": [
        "## Setup\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "88a34c2e-1f52-4c14-9a1b-20dc4ca3e048",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Create circuit to test transpiler on\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit.circuit.library import GroverOperator, Diagonal\n",
        "\n",
        "# Use Statevector object to calculate the ideal output\n",
        "from qiskit.quantum_info import Statevector\n",
        "from qiskit.visualization import plot_histogram\n",
        "from qiskit.transpiler import PassManager\n",
        "\n",
        "from qiskit.circuit.library import XGate\n",
        "from qiskit.quantum_info import hellinger_fidelity\n",
        "\n",
        "# Qiskit Runtime\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    Batch,\n",
        "    SamplerV2 as Sampler,\n",
        ")\n",
        "from qiskit_ibm_runtime.transpiler.passes.scheduling import (\n",
        "    ASAPScheduleAnalysis,\n",
        "    PadDynamicalDecoupling,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "75d41e90-8ca2-484c-bab5-2dd3ae63013d",
      "metadata": {},
      "source": [
        "## Step 1: Map classical inputs to a quantum problem\n",
        "\n",
        "Create a small circuit for the transpiler to try to optimize. This example creates a circuit that carries out Grover's algorithm with an oracle that marks the state `111`. Next, simulate the ideal distribution (what you'd expect to measure if you ran this on a perfect quantum computer an infinite number of times) for comparison later.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "60306c2e-2dc7-4726-8460-a7d1fd085372",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'ibm_brisbanse'"
            ]
          },
          "execution_count": 28,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# To run on hardware, select the backend with the fewest number of jobs in the queue\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=127\n",
        ")\n",
        "backend.name"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "7e7944c5-68ac-40cf-a0eb-5f4a44d53931",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/guides/circuit-transpilation-settings/extracted-outputs/7e7944c5-68ac-40cf-a0eb-5f4a44d53931-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 29,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "oracle = Diagonal([1] * 7 + [-1])\n",
        "qc = QuantumCircuit(3)\n",
        "qc.h([0, 1, 2])\n",
        "qc = qc.compose(GroverOperator(oracle))\n",
        "\n",
        "qc.draw(output=\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "id": "761afe09-b669-453f-8363-55070d6c8f57",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/guides/circuit-transpilation-settings/extracted-outputs/761afe09-b669-453f-8363-55070d6c8f57-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 30,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()\n",
        "\n",
        "plot_histogram(ideal_distribution)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "13d5a298-9128-4ed4-8d18-b1158e2f2bbc",
      "metadata": {},
      "source": [
        "## Step 2: Optimize problem for quantum hardware execution\n",
        "\n",
        "Next, transpile the circuits for the QPU. You will compare the performance of the transpiler with `optimization_level` set to `0` (lowest) against `3` (highest). The lowest optimization level does the bare minimum needed to get the circuit running on the device; it maps the circuit qubits to the device qubits and adds swap gates to allow all two-qubit operations. The highest optimization level is much smarter and uses lots of tricks to reduce the overall gate count. Since multi-qubit gates have high error rates and qubits decohere over time, the shorter circuits should give better results.\n",
        "\n",
        "The following cell transpiles `qc` for both values of `optimization_level`, prints the number of two-qubit gates, and adds the transpiled circuits to a list. Some of the transpiler's algorithms are randomized, so it sets a seed for reproducibility.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "a8e2bcf3-dd96-45bf-8a90-508f035b8ba6",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Two-qubit gates (optimization_level=0):  21\n",
            "Two-qubit gates (optimization_level=3):  14\n"
          ]
        }
      ],
      "source": [
        "# Need to add measurements to the circuit\n",
        "qc.measure_all()\n",
        "\n",
        "# Find the correct two-qubit gate\n",
        "twoQ_gates = set([\"ecr\", \"cz\", \"cx\"])\n",
        "for gate in backend.basis_gates:\n",
        "    if gate in twoQ_gates:\n",
        "        twoQ_gate = gate\n",
        "\n",
        "circuits = []\n",
        "for optimization_level in [0, 3]:\n",
        "    pm = generate_preset_pass_manager(\n",
        "        optimization_level, backend=backend, seed_transpiler=0\n",
        "    )\n",
        "    t_qc = pm.run(qc)\n",
        "    print(\n",
        "        f\"Two-qubit gates (optimization_level={optimization_level}): \",\n",
        "        t_qc.count_ops()[twoQ_gate],\n",
        "    )\n",
        "    circuits.append(t_qc)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f29c2ac9-779f-4599-8bad-559368c7949e",
      "metadata": {},
      "source": [
        "Since CNOTs usually have a high error rate, the circuit transpiled with `optimization_level=3` should perform much better.\n",
        "\n",
        "Another way you can improve performance is through [dynamic decoupling](/docs/api/qiskit/qiskit.transpiler.passes.PadDynamicalDecoupling), by applying a sequence of gates to idling qubits. This cancels out some unwanted interactions with the environment. The following cell adds dynamic decoupling to the circuit transpiled with `optimization_level=3` and adds it to the list.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3a6689e0-2d5f-4348-87d1-831127534c9f",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Get gate durations so the transpiler knows how long each operation takes\n",
        "durations = backend.target.durations()\n",
        "\n",
        "# This is the sequence we'll apply to idling qubits\n",
        "dd_sequence = [XGate(), XGate()]\n",
        "\n",
        "# Run scheduling and dynamic decoupling passes on circuit\n",
        "pm = PassManager(\n",
        "    [\n",
        "        ASAPScheduleAnalysis(durations),\n",
        "        PadDynamicalDecoupling(durations, dd_sequence),\n",
        "    ]\n",
        ")\n",
        "circ_dd = pm.run(circuits[1])\n",
        "\n",
        "# Add this new circuit to our list\n",
        "circuits.append(circ_dd)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "4ada6498-b9d7-4d88-b8a9-ef1dc0a85bf7",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/guides/circuit-transpilation-settings/extracted-outputs/4ada6498-b9d7-4d88-b8a9-ef1dc0a85bf7-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 33,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "circ_dd.draw(output=\"mpl\", style=\"iqp\", idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b9e3950e-3baa-4f58-bccd-6b3b42845f09",
      "metadata": {},
      "source": [
        "## Step 3: Execute using Qiskit primitives\n",
        "\n",
        "At this point, you have a list of circuits transpiled for the specified QPU. Next, create an instance of the sampler primitive and start a batched job using the context manager (`with ...:`), which automatically opens and closes the batch.\n",
        "\n",
        "Within the context manager, sample the circuits and store the results to `result`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "da300dfa-caff-4501-94d3-efaaf3599744",
      "metadata": {},
      "outputs": [],
      "source": [
        "with Batch(backend=backend):\n",
        "    sampler = Sampler()\n",
        "    job = sampler.run(\n",
        "        [(circuit) for circuit in circuits],  # sample all three circuits\n",
        "        shots=8000,\n",
        "    )\n",
        "    result = job.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e64bd136-ac2b-4bf6-9169-1553902b8de3",
      "metadata": {},
      "source": [
        "## Step 4: Post-process and return result in desired classical format\n",
        "\n",
        "Finally, plot the results from the device runs against the ideal distribution. You can see the results with `optimization_level=3` are closer to the ideal distribution due to the lower gate count, and `optimization_level=3 + dd` is even closer due to the dynamic decoupling.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "id": "525777ea-d438-4f3b-acb6-53e579f24a0e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/guides/circuit-transpilation-settings/extracted-outputs/525777ea-d438-4f3b-acb6-53e579f24a0e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 35,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "binary_prob = [\n",
        "    {\n",
        "        k: v / res.data.meas.num_shots\n",
        "        for k, v in res.data.meas.get_counts().items()\n",
        "    }\n",
        "    for res in result\n",
        "]\n",
        "plot_histogram(\n",
        "    binary_prob + [ideal_distribution],\n",
        "    bar_labels=False,\n",
        "    legend=[\n",
        "        \"optimization_level=0\",\n",
        "        \"optimization_level=3\",\n",
        "        \"optimization_level=3 + dd\",\n",
        "        \"ideal distribution\",\n",
        "    ],\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3f2a2b7b-e543-467c-8cb4-fae53279b50c",
      "metadata": {},
      "source": [
        "You can confirm this by computing the [Hellinger fidelity](/docs/api/qiskit/quantum_info) between each set of results and the ideal distribution (higher is better, and 1 is perfect fidelity).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "083a309a-f24e-443c-bba2-6c2ccad77e7f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "0.848\n",
            "0.945\n",
            "0.990\n"
          ]
        }
      ],
      "source": [
        "for prob in binary_prob:\n",
        "    print(f\"{hellinger_fidelity(prob, ideal_distribution):.3f}\")"
      ]
    },
    {
      "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": 4
}