{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "ae98cef5",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Run a parameter sweep\"\n",
        "description: \"Run a managed workflow one time for each set of inputs, collect the results, and stay under the active job limit.\"\n",
        "---\n",
        "\n",
        "# Run a parameter sweep\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "02e55839",
      "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-serverless~=0.38.0\n",
        "    ```\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0e07d727",
      "metadata": {},
      "source": [
        "A parameter sweep works like a Slurm job array. Call `run()` one time for each set of inputs, and the jobs run at the same time, each on its own node. Use the same pattern for any batch of independent inputs, such as one job for each molecule or each backend. Jobs cannot send data to each other, so split the work into independent inputs.\n",
        "\n",
        "The examples on this page use the workflow from [Run a managed workflow](/docs/managed-workflows/run-managed-workflow), which reads one angle, `theta`:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "4459c077",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Writing ./source_files/my_workflow.py\n"
          ]
        }
      ],
      "source": [
        "%%writefile ./source_files/my_workflow.py\n",
        "import math\n",
        "\n",
        "from qiskit_serverless import get_arguments, save_result\n",
        "\n",
        "arguments = get_arguments()\n",
        "theta = arguments[\"theta\"]\n",
        "if not 0 <= theta <= math.pi:\n",
        "    raise ValueError(f\"theta must be between 0 and pi, got {theta}\")\n",
        "\n",
        "print(f\"Computing the expectation value for theta={theta}\", flush=True)\n",
        "save_result({\"theta\": theta, \"expectation_value\": math.cos(theta)})"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "48065d27",
      "metadata": {},
      "source": [
        "## Start the jobs\n",
        "\n",
        "Get the workflow by its title, and call `run()` one time for each input. Save the job IDs to a file:\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "82610e4a",
      "metadata": {},
      "source": [
        "<CodeTitle title=\"run_workflow_remotely.ipynb\" />\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "e4e550b3",
      "metadata": {},
      "outputs": [],
      "source": [
        "import json\n",
        "\n",
        "from qiskit_ibm_catalog import ManagedWorkflowClient\n",
        "\n",
        "client = ManagedWorkflowClient()\n",
        "my_workflow = client.load(\"my-workflow\")\n",
        "\n",
        "thetas = [0.1, 0.2, 0.3]\n",
        "\n",
        "job_ids = {\n",
        "    f\"theta-{theta}\": my_workflow.run(theta=theta).job_id for theta in thetas\n",
        "}\n",
        "\n",
        "with open(\"job_ids.json\", \"w\") as f:\n",
        "    json.dump(job_ids, f)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "83f6787c",
      "metadata": {},
      "source": [
        "* **Check every input first.** If the workflow has an [argument schema](/docs/managed-workflows/managed-workflow-inputs-and-outputs#best-practice-validate-arguments-with-a-schema), call `validate_arguments()` on each input before the loop.\n",
        "* **Save the job IDs to a file.** You can then collect the results from a new session.\n",
        "* **Give each run's files a unique name.** All runs share the workflow's file storage. See [Best practice: give each run's files a unique name](/docs/managed-workflows/manage-files-across-managed-workflows#best-practice-give-each-runs-files-a-unique-name).\n",
        "* **Stay under the [active job limit](#active-job-limit).** A managed workflow does not queue jobs over the limit, so when you reach it, start more jobs only after earlier jobs end.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2daa6ac8",
      "metadata": {},
      "source": [
        "## Collect the results\n",
        "\n",
        "Get each job by its ID, and catch failed jobs so that one failure does not stop the loop:\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "25c1ad71",
      "metadata": {},
      "source": [
        "<CodeTitle title=\"run_workflow_remotely.ipynb\" />\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "3edc40cf",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "3 jobs finished, 0 failed\n"
          ]
        }
      ],
      "source": [
        "from qiskit_serverless.exception import QiskitServerlessException\n",
        "\n",
        "with open(\"job_ids.json\") as f:\n",
        "    job_ids = json.load(f)\n",
        "\n",
        "results, errors = {}, {}\n",
        "for run_name, sweep_job_id in job_ids.items():\n",
        "    try:\n",
        "        results[run_name] = client.job(sweep_job_id).result()\n",
        "    except QiskitServerlessException as error:\n",
        "        errors[run_name] = str(error)\n",
        "\n",
        "print(f\"{len(results)} jobs finished, {len(errors)} failed\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c8e40c49",
      "metadata": {},
      "source": [
        "To rerun the inputs that failed, call `run()` again with the same arguments.\n",
        "\n",
        "## Active job limit\n",
        "\n",
        "You can have up to 50 jobs that are queued, initializing, running, or canceling at the same time. This limit counts all of your jobs, including jobs of other workflows. If you exceed this limit, `run()` raises a `QiskitServerlessException` and does not create or queue the job:\n",
        "\n",
        "```text\n",
        "| Message: Http bad request.\n",
        "| Code: 429\n",
        "| Details: Active job limit reached. The maximum allowed is 50.\n",
        "```\n",
        "\n",
        "## Next steps\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recommendations\">\n",
        "  * [Manage files across runs](/docs/managed-workflows/manage-files-across-managed-workflows) to share input files between runs and give each run's files a unique name.\n",
        "  * [Choose a compute profile](/docs/managed-workflows/managed-workflow-compute-profiles) to run each job of the sweep on a size that matches its input.\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
}