{ "cells": [ { "cell_type": "markdown", "id": "7d054012-24ae-4249-8263-ba857939e9ca", "metadata": {}, "source": [ "# Converting from Qiskit Runtime Programs\n", "\n", "This tutorial will be a demonstation of converting your custom Qiskit Runtime Program into a Qiskit Serverless `QiskitPattern`.\n", "\n", "If you were using Qiskit Runtime Programs before, your code probably looks similar to the following example:\n", "\n", "```python\n", "\"\"\"A sample runtime program that submits random circuits for user-specified iterations.\"\"\"\n", "\n", "import random\n", "\n", "from qiskit import transpile\n", "from qiskit.circuit.random import random_circuit\n", "\n", "\n", "def prepare_circuits(backend):\n", " circuit = random_circuit(\n", " num_qubits=5, depth=4, measure=True, seed=random.randint(0, 1000)\n", " )\n", " return transpile(circuit, backend)\n", "\n", "\n", "def main(backend, user_messenger, **kwargs):\n", " \"\"\"Main entry point of the program.\n", "\n", " Args:\n", " backend: Backend to submit the circuits to.\n", " user_messenger: Used to communicate with the program consumer.\n", " kwargs: User inputs.\n", " \"\"\"\n", " iterations = kwargs.pop(\"iterations\", 5)\n", " for it in range(iterations):\n", " qc = prepare_circuits(backend)\n", " result = backend.run(qc).result()\n", " user_messenger.publish({\"iteration\": it, \"counts\": result.get_counts()})\n", "\n", " return \"Hello, World!\"\n", "```\n", "\n", "\n", "All Qiskit Runtime Programs have a `main` method which accepts `backend`, `user_messenger` and `**kwargs`. This method is not required for Qiskit Serverless patterns.\n", "\n", "Qiskit Serverless handles backends, logging, and input arguments a bit differently than Qiskit Runtime:\n", "\n", "- `backend`. For Qiskit Serverless programs you are not limited to single backend for a program. You can call any\n", " number of backends from single program. Since `Backend.run` is deprecated, we will be using Qiskit Primitives to do our calculation.\n", "- `user_messenger` were used in Qiskit Runtime Programs to facilitate retrieving logs from the program. Qiskit Serverless does not\n", " require passing such an object. Instead, all contents of `stdout` (e.g. print statements, logging messages) will be provided to the\n", " user via the Qiskit Serverless job handler.\n", "- `**kwargs` was a variable used to capture program inputs from the user. Users should now input their arguments to the `ServerlessProvider.run` method,\n", " and the arguments should be retrieved within the pattern using the `get_arguments` function from Qiskit Serverless.\n", "- To save the results of a pattern, the `save_result` function should be used. It accepts a python dictionary and can be accessed via the job handler.\n", "\n", "Let's use the guidelines above to transform the above Qiskit Runtime Program into a Qiskit Serverless QiskitPattern.\n", "\n", "```python\n", "# migrated_pattern.py\n", "\"\"\"A sample runtime pattern that submits random circuits for user-specified iterations.\"\"\"\n", "\n", "import random\n", "\n", "from qiskit import transpile\n", "from qiskit.circuit.random import random_circuit\n", "from qiskit.primitives import Sampler\n", "\n", "from qiskit_serverless import get_arguments, save_result\n", "\n", "\n", "def prepare_circuits():\n", " circuit = random_circuit(\n", " num_qubits=5, depth=4, measure=True, seed=random.randint(0, 1000)\n", " )\n", " return transpile(circuit)\n", "\n", "\n", "arguments = get_arguments()\n", "iterations = arguments.get(\"iterations\", 5)\n", "\n", "for it in range(iterations):\n", " qc = prepare_circuits()\n", " result = Sampler.run(qc).result()\n", " print({\"iteration\": it, \"dists\": result.quasi_dists})\n", "\n", "save_result({\"result\": \"Hello, World!\"})\n", "```\n", "\n", "Let's save this code as `./src/migrated_pattern.py` and execute it using the `QiskitPattern` class from the `qiskit_serverless` package." ] }, { "cell_type": "code", "execution_count": 1, "id": "50fb2a64-751d-40fb-bac8-db49dc92fbca", "metadata": {}, "outputs": [], "source": [ "from qiskit_serverless import QiskitPattern\n", "\n", "pattern = QiskitPattern(\n", " title=\"migrated-pattern\", entrypoint=\"migrated_pattern.py\", working_dir=\"./src/\"\n", ")" ] }, { "cell_type": "code", "execution_count": 2, "id": "162c482e-8f86-4224-a9b9-f6567b34acf4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from qiskit_serverless import ServerlessProvider\n", "import os\n", "\n", "serverless = ServerlessProvider(\n", " token=os.environ.get(\"GATEWAY_TOKEN\", \"awesome_token\"),\n", " host=os.environ.get(\"GATEWAY_HOST\", \"http://localhost:8000\"),\n", ")\n", "serverless" ] }, { "cell_type": "code", "execution_count": 3, "id": "0e88737d-a94c-487a-881c-c884ba0a3099", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'migrated-pattern'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "serverless.upload(pattern)" ] }, { "cell_type": "markdown", "id": "31e22bdd-2625-494a-a697-b1e26fcd066a", "metadata": {}, "source": [ "While Qiskit Runtime programs required users to upload their program and call it in two separate steps, the ``QiskitPattern`` class allows users to send a job for remote execution in a single step." ] }, { "cell_type": "code", "execution_count": 4, "id": "9f6eddae-a889-4958-8f0a-7e9f8ec29800", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "job = serverless.run(\"migrated-pattern\", arguments={\"iterations\": 3})\n", "job" ] }, { "cell_type": "code", "execution_count": 5, "id": "2223b57b-1dbc-45c7-8fd6-8e2ebfb843aa", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'result': 'Hello, World!'}" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "job.result()" ] }, { "cell_type": "code", "execution_count": 6, "id": "314e7716-8495-41e0-92dc-b2404ec860d4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "OpenBLAS WARNING - could not determine the L2 cache size on this system, assuming 256k\n", "OpenBLAS WARNING - could not determine the L2 cache size on this system, assuming 256k\n", "{'iteration': 0, 'dists': [{0: 0.01953568379784, 1: 0.003434588839427, 2: 0.000962772320948, 3: 0.022007500316319, 4: 0.018613812117662, 5: 0.003272513622768, 6: 0.000917339944671, 7: 0.020968985795759, 8: 0.089342856396779, 9: 0.100647254570651, 10: 0.004403062114611, 11: 0.015707460288483, 12: 0.085126845839344, 13: 0.095897799438366, 14: 0.004195285498675, 15: 0.014966239097696, 16: 0.018613812117662, 17: 0.003272513622768, 18: 0.007093750939097, 19: 0.014792574801333, 20: 0.01953568379784, 21: 0.003434588839427, 22: 0.007445077580607, 23: 0.01552519505666, 24: 0.085126845839344, 25: 0.067651119863038, 26: 0.032441965074003, 27: 0.014966239097696, 28: 0.089342856396779, 29: 0.071001623840398, 30: 0.034048692844864, 31: 0.015707460288483}]}\n", "{'iteration': 1, 'dists': [{0: 0.179254126288168, 1: 0.382666923582299, 4: 0.031208803971925, 5: 0.066623721595232, 16: 0.092444479011949, 17: 0.197348005974499, 20: 0.016094924471265, 21: 0.034359015104663}]}\n", "{'iteration': 2, 'dists': [{0: 0.003593062771524, 2: 0.037032118667734, 4: 0.032043767985561, 6: 0.330261031899324, 8: 0.005805731220397, 10: 0.059837119799466, 12: 0.002779551551986, 14: 0.028647616104007, 16: 0.003593062771524, 18: 0.037032118667734, 20: 0.032043767985561, 22: 0.330261031899324, 24: 0.005805731220397, 26: 0.059837119799466, 28: 0.002779551551986, 30: 0.028647616104007}]}\n", "\n" ] } ], "source": [ "print(job.logs())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.9.16" } }, "nbformat": 4, "nbformat_minor": 5 }