{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "ce3d197d-7b14-4c60-8d39-a202146d0663",
      "metadata": {},
      "source": [
        "---\n",
        "title: Executor inputs and outputs\n",
        "description: Understand the inputs and outputs to the Executor primitive.\n",
        "---\n",
        "\n",
        "# Executor inputs and outputs\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "44219323-9419-471a-86c4-488ef0162420",
      "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",
        "    samplomatic~=0.18.0\n",
        "    ```\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "43c044dc-259d-4759-b6a7-a05bd9d3667f",
      "metadata": {},
      "source": [
        "The Executor primitive is part of the [directed execution model](/docs/guides/directed-execution-model), which provides more flexibility when customizing an error mitigation workflow.\n",
        "\n",
        "The inputs and outputs of the Executor primitive are very different from those of the [Sampler](/docs/guides/sampler-input-output) and [Estimator](/docs/guides/estimator-input-output) primitives. For example, instead of taking a list of PUBs as the input, Executor takes a `QuantumProgram`, which contains a list of `QuantumProgramItem` objects. These container classes give you more flexibility than a PUB, which is a simple tuple data structure.\n",
        "\n",
        "Executor's output is a `QuantumProgramResult`, which is an iterable and contains one element for each input `QuantumProgramItem`.\n",
        "\n",
        "<span id=\"programs\" />\n",
        "\n",
        "## Inputs: Quantum programs\n",
        "\n",
        "As stated previously, the input to an Executor primitive is a  [`QuantumProgram`](/docs/api/qiskit-ibm-runtime/quantum-program-quantum-program), which is an iterable of\n",
        "[`QuantumProgramItem`](/docs/api/qiskit-ibm-runtime/quantum-program-quantum-program-item) objects.  These objects can be of two types:\n",
        "\n",
        "* `CircuitItem`, which typically stores a circuit and its parameter values (if any).\n",
        "* `SamplexItem`, which typically stores the following:\n",
        "  * A template circuit\n",
        "  * A samplex object, which is used to generate randomized sets of parameters at runtime (for example to perform twirling or inject noise)\n",
        "  * Arguments for the samplex, which might include parameter values for the original circuit\n",
        "\n",
        "Each of these items represents a different task for Executor to perform.\n",
        "\n",
        "### Before you begin\n",
        "\n",
        "Some of the code examples on this page use `samplex`, which is part of the Samplomatic package.  Therefore, before running those code block, you must install Samplomatic, as shown in the following code block.  For more information, see the [Samplomatic documentation](https://qiskit.github.io/samplomatic).\n",
        "\n",
        "```python\n",
        "pip install samplomatic\n",
        "\n",
        "# For visualization support, include the visualization dependencies.\n",
        "# pip install samplomatic[vis]\n",
        "```\n",
        "\n",
        "### Example: Create a `QuantumProgram` with two different tasks\n",
        "\n",
        "First initialize your quantum program, then append program items to it by using either `append_circuit_item` or `append_samplex_item` (if a samplex is present), as shown in the following examples.\n",
        "\n",
        "The following cell initializes a `QuantumProgram` and specifies that it should run 1024 shots for every configuration of each item in the program.\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  Unlike with Sampler, a `QuantumProgram` takes only a single shot value. If you want a different shot value, you need a separate `QuantumProgram`, which would be a separate job.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "81fc7c9e-2cc9-416a-b3d5-74f45eab48fc",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "from qiskit_ibm_runtime.quantum_program import QuantumProgram\n",
        "from qiskit_ibm_runtime import Executor, QiskitRuntimeService\n",
        "from qiskit.circuit import Parameter, QuantumCircuit\n",
        "import numpy as np\n",
        "from samplomatic import build\n",
        "from samplomatic.transpiler import generate_boxing_pass_manager\n",
        "\n",
        "# Initialize an empty program\n",
        "program = QuantumProgram(shots=1024)\n",
        "\n",
        "# Initialize and transpile a 3-qubit quantum circuit with 2 parameters.\n",
        "circuit = QuantumCircuit(3)\n",
        "circuit.h(0)\n",
        "circuit.cx(0, 1)\n",
        "circuit.cx(1, 2)\n",
        "circuit.rz(Parameter(\"theta\"), 0)\n",
        "circuit.rz(Parameter(\"phi\"), 1)\n",
        "\n",
        "# `measure_all` adds a 3-bit classical register named \"meas\"\n",
        "circuit.measure_all()\n",
        "\n",
        "# Choose the least busy backend\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(operational=True, simulator=False)\n",
        "\n",
        "# Generate a preset pass manager\n",
        "# This will be used to convert the abstract circuit to an\n",
        "# equivalent Instruction Set Architecture (ISA) circuit.\n",
        "preset_pass_manager = generate_preset_pass_manager(\n",
        "    backend=backend, optimization_level=0\n",
        ")\n",
        "\n",
        "# Transpile the circuit\n",
        "isa_circuit = preset_pass_manager.run(circuit)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1cc6580-3f31-4322-b0ea-f6bcf032a872",
      "metadata": {},
      "source": [
        "### Append a `CircuitItem`\n",
        "\n",
        "Next, append the target circuit, which was transpiled according to the backend's instruction set architecture (ISA), to the `QuantumProgram`. Since this circuit has two parameters, we must also provide the parameter values (10 sets in this example). Running this `CircuitItem` is the first task that the program will perform.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "e6f6762f-1627-450a-b74b-96ad4a3b1c9c",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Append the transpiled circuit and an array\n",
        "# containing 10 sets of parameter values to the program\n",
        "program.append_circuit_item(\n",
        "    isa_circuit,\n",
        "    circuit_arguments=np.random.rand(\n",
        "        10, 2\n",
        "    ),  # 10 sets of parameter values and 2 parameters\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "96f9ac43-8ce4-4389-bff4-5bd68b799974",
      "metadata": {},
      "source": [
        "### Append a `SamplexItem`\n",
        "\n",
        "Circuit items are executed without any sort of randomization. On the contrary, samplex items let you specify how to randomize their content. The next cell uses the `generate_boxing_pass_manager()` function to group the circuit's gates and measurements into boxes and add a twirling annotation to each box. It then generates a template circuit and a samplex pair using the `build()` function.\n",
        "\n",
        "Running this `SamplexItem` is the second task that the program will perform.\n",
        "\n",
        "See the Samplomatic [API](https://github.com/Qiskit/samplomatic/) documentation for full details about `samplex` and its arguments. See the Samplomatic [Transpiler guide](https://qiskit.github.io/samplomatic/guides/transpiler.html) for information about using the  `generate_boxing_pass_manager()` function.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "4ac19f46-226b-4b13-b278-1e39e719484b",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TensorInterface(<\n",
            "  - 'parameter_values' <float64[2]>: Input parameter values to use during sampling.\n",
            ">)\n"
          ]
        }
      ],
      "source": [
        "# Transpile the circuit, additionally grouping gates and measurements into annotated boxes\n",
        "preset_pass_manager = generate_preset_pass_manager(\n",
        "    backend=backend, optimization_level=0\n",
        ")\n",
        "\n",
        "# Use the boxing pass manager to group gates\n",
        "# and measurements into boxes and add\n",
        "# a`Twirl` annotation.\n",
        "preset_pass_manager.post_scheduling = generate_boxing_pass_manager(\n",
        "    # Add gate twirling\n",
        "    enable_gates=True,\n",
        "    # Add measurement twirling\n",
        "    enable_measures=True,\n",
        ")\n",
        "boxed_circuit = preset_pass_manager.run(circuit)\n",
        "\n",
        "# Build the template circuit and the samplex.  The template circuit has parametric gates\n",
        "# without fixed values and the samplex randomly generates the parameter\n",
        "# values on the server side at runtime to perform twirling.\n",
        "template_circuit, samplex = build(boxed_circuit)\n",
        "\n",
        "# Determine what arguments are required by the samplex.\n",
        "# Input the arguments in samplex_arguments.\n",
        "print(samplex.inputs())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "0e3b4d3d-12ea-47f2-a605-b64eed03cf95",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Append the template circuit and samplex as a samplex item\n",
        "program.append_samplex_item(\n",
        "    template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments={\n",
        "        # the arguments required by the samplex.sample method\n",
        "        \"parameter_values\": np.random.rand(10, 2),\n",
        "    },\n",
        "    shape=(28, 10),  # 28 randomizations and 10 sets of parameter values\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "eb850401-3d4f-4fac-8965-fb9ac4827ebd",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Initialize an Executor with the default options\n",
        "executor = Executor(mode=backend)\n",
        "\n",
        "# Submit the job\n",
        "job = executor.run(program)\n",
        "\n",
        "# Retrieve the result\n",
        "result = job.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "541f5d2d-b974-4ae5-81e0-ef2b6e1dbe82",
      "metadata": {},
      "source": [
        "## Outputs\n",
        "\n",
        "Executor's output is a [`QuantumProgramResult`](/docs/api/qiskit-ibm-runtime/results-quantum-program-result), which is an iterable. It contains one entry per input `QuantumProgramItem` in the same order as the input items. Each of these output items is a dictionary where the keys are strings that correspond the classical registers' names in the input circuits (among others), so you no longer need to memorize these names like you did with Sampler output. The dictionary values are of type `np.ndarray`.\n",
        "\n",
        "The result for the previous example contains these items:\n",
        "\n",
        "### `CircuitItem` result\n",
        "\n",
        "The first item contains the results of running the first task (a `CircuitItem`) in the program. It contains a single key, `meas`, which is the name of the classical register in the input circuit. The value of this key maps to an `np.ndarray` of shape `(parameter sets, shots, register bits)`, which is (10, 1024, 3) for the above example.\n",
        "\n",
        "The following code illustrates how to access this information:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "d652d69a-7a4e-4e5c-8469-97cb9ec92010",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Result shape: (10, 1024, 3)\n"
          ]
        }
      ],
      "source": [
        "# Access the results of the classical register of task #0, a CircuitItem\n",
        "result_0 = result[0][\"meas\"]\n",
        "print(f\"Result shape: {result_0.shape}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7185353a-e7a0-4bb2-b605-664184e684ed",
      "metadata": {},
      "source": [
        "### `SamplexItem` result\n",
        "\n",
        "The second item contains the results of running the second task (a `SamplexItem`) in the program. This item contains multiple keys. The `meas` key, which is the name of the input circuit's classical register, maps to that register's array of results. This array has the shape `(randomizations, parameter sets, shots, classical bits)`, or (28, 10, 1024, 3) in this example. Additionally, the output contains a `measurement_flips.meas` key, which is the bit-flip corrections to undo the measurement twirling for the `meas` register.  This output shape will be (28, 10, 1, 3) for our example because only one shot is required to perform the bit-flip.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "884af6e2-614a-4b18-83dc-04505ddbc488",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Result shape: (28, 10, 1024, 3)\n",
            "Bit-flip corrections shape: (28, 10, 1, 3)\n"
          ]
        }
      ],
      "source": [
        "# Access the results of the classical register of task #1\n",
        "result_1 = result[1][\"meas\"]\n",
        "print(f\"Result shape: {result_1.shape}\")\n",
        "\n",
        "# Access the bit-flip corrections\n",
        "flips_1 = result[1][\"measurement_flips.meas\"]\n",
        "print(f\"Bit-flip corrections shape: {flips_1.shape}\")\n",
        "\n",
        "# Undo the bit flips via classical XOR\n",
        "unflipped_result_1 = result_1 ^ flips_1"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bb0c9225-9a52-4d94-b75d-6ae408f4e029",
      "metadata": {},
      "source": [
        "## Next steps\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recommendations\">\n",
        "  * Explore [examples](/docs/guides/executor-examples) that use Executor.\n",
        "  * Learn about the [directed execution model](/docs/guides/directed-execution-model).\n",
        "  * Understand [Executor broadcasting](/docs/guides/executor-broadcasting).\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": 4
}