{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "d9634544",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Deploy your first managed workflow\"\n",
        "description: \"Write, upload, and run your first managed workflow, then get its results.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore writefile */}\n",
        "\n",
        "# Deploy your first managed workflow\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0804b498",
      "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.6.1\n",
        "    qiskit-serverless~=0.38.0\n",
        "    ```\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f5ca9773",
      "metadata": {},
      "source": [
        "Use this page to upload a workflow, run it, and get its result. The example workflow optimizes and simulates a Qiskit circuit. It simulates the circuit on classical hardware, so it does not use QPU time.\n",
        "\n",
        "Before you begin, [install the client package and authenticate](/docs/managed-workflows/install-managed-workflows).\n",
        "\n",
        "<Admonition type=\"note\" title=\"Use AI skills (optional)\">\n",
        "  To deploy your own code with help from an AI assistant, use the AI skills in the [Qiskit Function templates repository](https://github.com/qiskit-community/qiskit-function-templates).\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6e162657",
      "metadata": {},
      "source": [
        "## Write a workflow\n",
        "\n",
        "### Create the folder structure\n",
        "\n",
        "A managed workflow is a folder of Python files with one entrypoint file. A managed workflow uploads everything in the folder, up to 50 MB, and runs the entrypoint as a script. Because a workflow uploads the whole folder, keep your local files, such as `deploy.ipynb`, outside the folder. Create the following layout:\n",
        "\n",
        "```text\n",
        "hello_world_project/\n",
        "├── deploy.ipynb         # Your local notebook: upload and run the workflow\n",
        "└── source_files/         # The working directory that you upload\n",
        "    └── hello_world.py   # The entrypoint\n",
        "```\n",
        "\n",
        "### Write the hello world entrypoint\n",
        "\n",
        "The entrypoint reads a circuit, optimizes and simulates it, and saves the counts and two-qubit depth. Save the following code as `source_files/hello_world.py`:\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  If you read this page as a notebook, the code cell for `hello_world.py` starts with the `%%writefile` [magic command](https://ipython.readthedocs.io/en/stable/interactive/magics.html#cellmagic-writefile). When you run this cell, it saves the code to the file instead of running the code.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "54326895",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Writing ./source_files/hello_world.py\n"
          ]
        }
      ],
      "source": [
        "%%writefile ./source_files/hello_world.py\n",
        "from qiskit.primitives import StatevectorSampler\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "from qiskit_serverless import Job, get_arguments, save_result, update_status\n",
        "\n",
        "# Read the arguments that you pass to run()\n",
        "arguments = get_arguments()\n",
        "circuit = arguments[\"circuit\"]\n",
        "shots = arguments.get(\"shots\", 1024)\n",
        "\n",
        "# Optimize the circuit. Printed text goes to the job's logs.\n",
        "update_status(Job.OPTIMIZING_HARDWARE)\n",
        "pass_manager = generate_preset_pass_manager(\n",
        "    optimization_level=1, basis_gates=[\"cz\", \"rz\", \"sx\", \"x\"]\n",
        ")\n",
        "isa_circuit = pass_manager.run(circuit)\n",
        "two_qubit_depth = isa_circuit.depth(lambda instruction: instruction.operation.num_qubits == 2)\n",
        "print(f\"Two-qubit depth after optimization: {two_qubit_depth}\", flush=True)\n",
        "\n",
        "# Simulate the circuit and return the counts\n",
        "sampler = StatevectorSampler(seed=1234)\n",
        "pub_result = sampler.run([isa_circuit], shots=shots).result()[0]\n",
        "save_result({\"counts\": pub_result.data.meas.get_counts(), \"two_qubit_depth\": two_qubit_depth})"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8547bd58",
      "metadata": {},
      "source": [
        "The entrypoint uses three functions from `qiskit_serverless`:\n",
        "\n",
        "* `get_arguments()` returns the keyword arguments that you pass to `run()`, as a dictionary. Each value must be JSON-serializable, or an object that the client can encode, such as a `QuantumCircuit`. Encoded objects arrive as the same objects that you passed.\n",
        "* `update_status(Job.OPTIMIZING_HARDWARE)` sets the detailed status `RUNNING: OPTIMIZING_FOR_HARDWARE`, which you can see while the job runs. See [Report progress with update\\_status()](/docs/managed-workflows/manage-quantum-classical-workflows#report-progress-with-update_status).\n",
        "* `save_result()` stores a JSON-serializable value that you read later with `job.result()`. The workflow returns the counts as a dictionary and the two-qubit depth of the optimized circuit as an integer.\n",
        "\n",
        "Anything that the workflow writes to standard output, such as the two-qubit depth, goes to the job's logs. To add timestamps and log levels, use the `logging` module. See [Write logs and handle errors](/docs/managed-workflows/managed-workflow-logs-and-errors).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b8f1916f",
      "metadata": {},
      "source": [
        "## Upload the workflow\n",
        "\n",
        "In your local notebook, run the following code from the `hello_world_project` folder. The code connects with your saved account, defines the workflow, and uploads it. `entrypoint` is the file that the workflow runs, and `working_dir` is the folder that you upload:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "da07b3fd",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_ibm_catalog import ManagedWorkflow, ManagedWorkflowClient\n",
        "\n",
        "client = ManagedWorkflowClient()\n",
        "\n",
        "hello_world = ManagedWorkflow(\n",
        "    title=\"hello-world\",\n",
        "    entrypoint=\"hello_world.py\",\n",
        "    working_dir=\"./source_files/\",\n",
        ")\n",
        "\n",
        "hello_world = client.upload(hello_world)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5933a882",
      "metadata": {},
      "source": [
        "## Run the workflow with inputs\n",
        "\n",
        "Construct a three-qubit GHZ circuit to pass to `hello_world` as an input:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "7b43e625",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "\n",
        "circuit = QuantumCircuit(3)\n",
        "circuit.h(0)\n",
        "circuit.cx(0, 1)\n",
        "circuit.cx(1, 2)\n",
        "circuit.measure_all()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c3e99486",
      "metadata": {},
      "source": [
        "Run `hello_world` with the circuit and the number of shots as keyword arguments. The argument names match the keys that the entrypoint reads with `get_arguments()`:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "c11cef31",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "54705348-5117-44b7-935b-e6986ef182fa\n"
          ]
        }
      ],
      "source": [
        "job = hello_world.run(circuit=circuit, shots=1024)\n",
        "print(job.job_id)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ab013215",
      "metadata": {},
      "source": [
        "`run()` returns a `Job` after the workflow queues the job. It does not wait for the job to run. The job continues in the cloud after you close your Python session. To find it again later, list your recent jobs with `client.jobs()`.\n",
        "\n",
        "{/* REVIEW: add the web UI for job history when it is available. */}\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1a8bfacb",
      "metadata": {},
      "source": [
        "## Get the status, result, and logs\n",
        "\n",
        "Immediately after you call `run()`, the status is usually `QUEUED`:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "5676dbd3",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'QUEUED'"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "job.status()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0d23fe9f",
      "metadata": {},
      "source": [
        "The status changes from `QUEUED` to `INITIALIZING`, `RUNNING`, and then `DONE`.\n",
        "\n",
        "`job.result()` waits until the job ends, then returns the value that the workflow passed to `save_result()`:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "4e6fa05d",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'counts': {'111': 514, '000': 510}, 'two_qubit_depth': 2}"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "job.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "b4e7a2fe",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/managed-workflows/deploy-first-managed-workflow/extracted-outputs/b4e7a2fe-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 9,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.visualization import plot_histogram\n",
        "\n",
        "plot_histogram(job.result()[\"counts\"])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d2a58cff",
      "metadata": {},
      "source": [
        "You can read the logs while the job runs. The workflow uploads new lines about every 15 seconds. The logs include the line that the workflow printed:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "feae974c",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Two-qubit depth after optimization: 2\n",
            "\n"
          ]
        }
      ],
      "source": [
        "print(job.logs())"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5c8a7795",
      "metadata": {},
      "source": [
        "## Update the workflow\n",
        "\n",
        "To change a workflow, edit its files and call `upload()` again with the same `title`. The new upload replaces the earlier one, and jobs that already started are not affected.\n",
        "\n",
        "Fields such as `arguments_schema` and `sizes_map` keep their stored values when you omit them from the new upload.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bba236c5",
      "metadata": {},
      "source": [
        "## Next steps\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recommendations\">\n",
        "  * [Define inputs and outputs](/docs/managed-workflows/managed-workflow-inputs-and-outputs) to learn which argument and result types a workflow supports.\n",
        "  * [Declare dependencies](/docs/managed-workflows/managed-workflow-dependencies) to use more Python packages in your workflow.\n",
        "  * [Run quantum jobs from a workflow](/docs/managed-workflows/manage-quantum-classical-workflows) to run your circuits on a QPU, report each stage, and track the QPU jobs.\n",
        "  * [Run a managed workflow](/docs/managed-workflows/run-managed-workflow) to learn all job statuses, debug a failed job, and find a job later.\n",
        "  * To start from working code, deploy one of the [Qiskit Function templates](/docs/managed-workflows/qiskit-function-templates), or adapt a template for your own workflow.\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
}