From f08ab04a67e89a66b83e37c018b015e7841499c9 Mon Sep 17 00:00:00 2001 From: Lucy Xing <88128115+LucyXing@users.noreply.github.com> Date: Wed, 1 Sep 2021 14:35:27 -0400 Subject: [PATCH] Update tutorials (#65) * update tutorials * return final result * fix test * use returned result * lint fix * add to docstring * add return type Co-authored-by: Lucy-Xing --- .../circuit_runner/circuit_runner.py | 4 +- qiskit_runtime/qka/qka.py | 2 +- .../sample_program/sample_program.py | 8 +- test/circuit_runner/test_circuit_runner.py | 6 +- test/sample_program/test_sample_program.py | 2 +- tutorials/00_introduction.ipynb | 230 +++++------- tutorials/02_uploading_program.ipynb | 351 ++++++++---------- 7 files changed, 270 insertions(+), 333 deletions(-) diff --git a/qiskit_runtime/circuit_runner/circuit_runner.py b/qiskit_runtime/circuit_runner/circuit_runner.py index ca9768e31..82a9aaf7d 100644 --- a/qiskit_runtime/circuit_runner/circuit_runner.py +++ b/qiskit_runtime/circuit_runner/circuit_runner.py @@ -20,7 +20,7 @@ def main( backend, - user_messenger, + user_messenger, # pylint: disable=unused-argument circuits, initial_layout=None, seed_transpiler=None, @@ -65,4 +65,4 @@ def main( # Performs measurement error mitigation. pass - user_messenger.publish(result.to_dict(), final=True) + return result.to_dict() diff --git a/qiskit_runtime/qka/qka.py b/qiskit_runtime/qka/qka.py index 3d5e68a4f..bb3e87a48 100644 --- a/qiskit_runtime/qka/qka.py +++ b/qiskit_runtime/qka/qka.py @@ -515,4 +515,4 @@ def main(backend, user_messenger, **kwargs): C=C, ) - user_messenger.publish(qka_results, final=True) + return qka_results diff --git a/qiskit_runtime/sample_program/sample_program.py b/qiskit_runtime/sample_program/sample_program.py index f86fe6bef..a9e090f8d 100644 --- a/qiskit_runtime/sample_program/sample_program.py +++ b/qiskit_runtime/sample_program/sample_program.py @@ -13,6 +13,7 @@ """A sample runtime program that submits random circuits for user-specified iterations.""" import random +from typing import Any from qiskit import transpile from qiskit.circuit.random import random_circuit @@ -31,7 +32,7 @@ def prepare_circuits(backend): return transpile(circuit, backend) -def main(backend, user_messenger, **kwargs): +def main(backend, user_messenger, **kwargs) -> Any: """Main entry point of the program. Args: @@ -39,6 +40,9 @@ def main(backend, user_messenger, **kwargs): user_messenger (qiskit.providers.ibmq.runtime.UserMessenger): Used to communicate with the program consumer. kwargs: User inputs. + + Returns: + Final result of the program. """ iterations = kwargs.pop("iterations", 5) for it in range(iterations): @@ -46,4 +50,4 @@ def main(backend, user_messenger, **kwargs): result = backend.run(qc).result() user_messenger.publish({"iteration": it, "counts": result.get_counts()}) - user_messenger.publish("All done!", final=True) + return "All done!" diff --git a/test/circuit_runner/test_circuit_runner.py b/test/circuit_runner/test_circuit_runner.py index 7ce3d2863..3cc074611 100644 --- a/test/circuit_runner/test_circuit_runner.py +++ b/test/circuit_runner/test_circuit_runner.py @@ -46,8 +46,8 @@ def test_circuit_runner(self): } serialized_inputs = json.dumps(inputs, cls=RuntimeEncoder) unserialized_inputs = json.loads(serialized_inputs, cls=RuntimeDecoder) - circuit_runner.main( + result = circuit_runner.main( backend=self.backend, user_messenger=self.user_messenger, **unserialized_inputs ) - self.assertEqual(self.user_messenger.call_count, 1) - self.assertTrue(isinstance(Result.from_dict(self.user_messenger.message), Result)) + self.assertEqual(self.user_messenger.call_count, 0) + self.assertTrue(isinstance(Result.from_dict(result), Result)) diff --git a/test/sample_program/test_sample_program.py b/test/sample_program/test_sample_program.py index d85619263..bc8af8af4 100644 --- a/test/sample_program/test_sample_program.py +++ b/test/sample_program/test_sample_program.py @@ -35,4 +35,4 @@ def test_sample_program(self): serialized_inputs = json.dumps(inputs, cls=RuntimeEncoder) unserialized_inputs = json.loads(serialized_inputs, cls=RuntimeDecoder) sample_program.main(self.backend, self.user_messenger, **unserialized_inputs) - self.assertEqual(self.user_messenger.call_count, inputs["iterations"] + 1) + self.assertEqual(self.user_messenger.call_count, inputs["iterations"]) diff --git a/tutorials/00_introduction.ipynb b/tutorials/00_introduction.ipynb index b4861e2d7..1f76804f3 100644 --- a/tutorials/00_introduction.ipynb +++ b/tutorials/00_introduction.ipynb @@ -2,16 +2,13 @@ "cells": [ { "cell_type": "markdown", - "id": "28ba7d77", - "metadata": {}, "source": [ "# Qiskit Runtime" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "1a870dc9", - "metadata": {}, "source": [ "Qiskit Runtime is a new architecture offered by IBM Quantum that streamlines computations requiring many iterations. These experiments will execute significantly faster within this improved hybrid quantum/classical process.\n", "\n", @@ -22,53 +19,48 @@ "others to use. A Qiskit quantum program, also called a Qiskit runtime program, is a piece of Python code that takes certain inputs, performs\n", "quantum and maybe classical computation, and returns the processing results. The same or other\n", "authorized users can then invoke these quantum programs by simply passing in the required input parameters." - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "eefc8e33", - "metadata": {}, "source": [ "
\n", "Note: Qiskit Runtime is only available to select IBM Quantum providers. You can use the `has_service()` method to check if a provider has access:\n", "
" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 1, - "id": "a0b2650e", - "metadata": {}, - "outputs": [], "source": [ "from qiskit import IBMQ\n", "\n", "IBMQ.load_account()\n", "provider = IBMQ.get_provider(project='qiskit-runtime') # Change this to your provider.\n", "can_use_runtime = provider.has_service('runtime')" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "markdown", - "id": "e35e0ef5", - "metadata": {}, "source": [ "\n", "If you don't have an IBM Quantum account, you can sign up for one on the [IBM Quantum](https://quantum-computing.ibm.com/) page." - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "63cf458e", - "metadata": {}, "source": [ "## Listing programs " - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "48ab239c", - "metadata": {}, "source": [ "The `provider.runtime` object is an instance of the [`IBMRuntimeService`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.IBMRuntimeService.html#qiskit.providers.ibmq.runtime.IBMRuntimeService) class and serves as the main entry point to using the runtime service. It has three methods that can be used to find metadata of available programs:\n", "- `pprint_programs()`: pretty prints metadata of all available programs\n", @@ -76,25 +68,26 @@ "- `program()`: returns a single `RuntimeProgram` instance\n", "\n", "The metadata of a runtime program includes its ID, name, description, version, input parameters, return values, interim results, maximum execution time, and backend requirements. Maximum execution time is the maximum amount of time, in seconds, a program can run before being forcibly terminated." - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "8af412f3", - "metadata": {}, "source": [ "To print the metadata of all available programs:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 2, - "id": "25b35716", - "metadata": {}, + "source": [ + "provider.runtime.pprint_programs()" + ], "outputs": [ { - "name": "stdout", "output_type": "stream", + "name": "stdout", "text": [ "==================================================\n", "circuit-runner:\n", @@ -302,27 +295,26 @@ ] } ], - "source": [ - "provider.runtime.pprint_programs()" - ] + "metadata": {} }, { "cell_type": "markdown", - "id": "7090db0f", - "metadata": {}, "source": [ "To print the metadata of the program `sample-program`:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 3, - "id": "276c3ebb", - "metadata": {}, + "source": [ + "program = provider.runtime.program('sample-program')\n", + "print(program)" + ], "outputs": [ { - "name": "stdout", "output_type": "stream", + "name": "stdout", "text": [ "sample-program:\n", " Name: sample-program\n", @@ -349,31 +341,24 @@ ] } ], - "source": [ - "program = provider.runtime.program('sample-program')\n", - "print(program)" - ] + "metadata": {} }, { "cell_type": "markdown", - "id": "dc4339b3", - "metadata": {}, "source": [ "As you can see from above, the program `sample-program` is a simple program that has only 1 input parameter `iterations`, which indicates how many iterations to run. For each iteration it generates and runs a random 5-qubit circuit and returns the counts as well as the iteration number as the interim results. When the program finishes, it returns the sentence `All done!`. This program can only run for 300 seconds (5 minutes), and requires a backend that has at least 5 qubits." - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "21293c6f", - "metadata": {}, "source": [ "## Invoking a runtime program " - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "a77eeee3", - "metadata": {}, "source": [ "You can use the [`IBMRuntimeService.run()`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.IBMRuntimeService.html#qiskit.providers.ibmq.runtime.IBMRuntimeService.run) method to invoke a runtime program. This method takes the following parameters:\n", "\n", @@ -382,55 +367,38 @@ "- `options`: Runtime options. These options control the execution environment. Currently the only available option is `backend_name`, which is required.\n", "- `callback`: Callback function to be invoked for any interim results. The callback function will receive 2 positional parameters: job ID and interim result.\n", "- `result_decoder`: Optional class used to decode job result." - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "82900e09", - "metadata": {}, "source": [ "Before we run a quantum program, we may want to define a callback function that would process interim results, which are intermediate data provided by a program while its still running. \n", "\n", "As we saw earlier, the metadata of `sample-program` says that its interim results are the iteration number and the counts of the randomly generated circuit. Here we define a simple callback function that just prints these interim results:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 4, - "id": "573565bd", - "metadata": {}, - "outputs": [], "source": [ "def interim_result_callback(job_id, interim_result):\n", " print(f\"interim result: {interim_result}\")" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "markdown", - "id": "e2a98178", - "metadata": {}, "source": [ "The following example runs the `sample-program` program with 3 iterations on `ibmq_montreal` and waits for its result. You can also use a different backend that supports Qiskit Runtime:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 5, - "id": "acc94050", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "job id: c2ajo0m0lb0ph8orsprg\n", - "interim result: {'iteration': 0, 'counts': {'00000': 92, '00001': 15, '10000': 194, '10001': 21, '10010': 181, '10011': 33, '10100': 50, '10101': 9, '10110': 68, '10111': 12, '11000': 11, '11001': 5, '11010': 14, '11011': 5, '11100': 13, '11101': 2, '11110': 14, '11111': 4, '00010': 128, '00011': 16, '00100': 33, '00101': 11, '00110': 46, '00111': 9, '01000': 6, '01001': 5, '01010': 10, '01011': 3, '01100': 10, '01110': 3, '01111': 1}}\n", - "interim result: {'iteration': 1, 'counts': {'00000': 15, '00001': 9, '10000': 8, '10001': 9, '10010': 11, '10011': 6, '10100': 9, '10101': 9, '10110': 7, '10111': 3, '11000': 23, '11001': 26, '11010': 193, '11011': 113, '11100': 14, '11101': 13, '11110': 30, '11111': 25, '00010': 6, '00011': 4, '00100': 6, '00101': 2, '00110': 4, '00111': 3, '01000': 202, '01001': 97, '01010': 31, '01011': 23, '01100': 54, '01101': 45, '01110': 10, '01111': 14}}\n", - "interim result: {'iteration': 2, 'counts': {'00000': 33, '00001': 46, '10000': 27, '10001': 33, '10010': 42, '10011': 38, '10100': 40, '10101': 35, '10110': 27, '10111': 33, '11000': 38, '11001': 26, '11010': 27, '11011': 24, '11100': 34, '11101': 25, '11110': 20, '11111': 19, '00010': 41, '00011': 29, '00100': 32, '00101': 36, '00110': 28, '00111': 28, '01000': 45, '01001': 27, '01010': 36, '01011': 44, '01100': 29, '01101': 34, '01110': 21, '01111': 27}}\n", - "All done!\n" - ] - } - ], "source": [ "backend = provider.get_backend('ibmq_montreal')\n", "program_inputs = {\n", @@ -445,12 +413,24 @@ "print(f\"job id: {job.job_id()}\")\n", "result = job.result()\n", "print(result)" - ] + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "job id: c2ajo0m0lb0ph8orsprg\n", + "interim result: {'iteration': 0, 'counts': {'00000': 92, '00001': 15, '10000': 194, '10001': 21, '10010': 181, '10011': 33, '10100': 50, '10101': 9, '10110': 68, '10111': 12, '11000': 11, '11001': 5, '11010': 14, '11011': 5, '11100': 13, '11101': 2, '11110': 14, '11111': 4, '00010': 128, '00011': 16, '00100': 33, '00101': 11, '00110': 46, '00111': 9, '01000': 6, '01001': 5, '01010': 10, '01011': 3, '01100': 10, '01110': 3, '01111': 1}}\n", + "interim result: {'iteration': 1, 'counts': {'00000': 15, '00001': 9, '10000': 8, '10001': 9, '10010': 11, '10011': 6, '10100': 9, '10101': 9, '10110': 7, '10111': 3, '11000': 23, '11001': 26, '11010': 193, '11011': 113, '11100': 14, '11101': 13, '11110': 30, '11111': 25, '00010': 6, '00011': 4, '00100': 6, '00101': 2, '00110': 4, '00111': 3, '01000': 202, '01001': 97, '01010': 31, '01011': 23, '01100': 54, '01101': 45, '01110': 10, '01111': 14}}\n", + "interim result: {'iteration': 2, 'counts': {'00000': 33, '00001': 46, '10000': 27, '10001': 33, '10010': 42, '10011': 38, '10100': 40, '10101': 35, '10110': 27, '10111': 33, '11000': 38, '11001': 26, '11010': 27, '11011': 24, '11100': 34, '11101': 25, '11110': 20, '11111': 19, '00010': 41, '00011': 29, '00100': 32, '00101': 36, '00110': 28, '00111': 28, '01000': 45, '01001': 27, '01010': 36, '01011': 44, '01100': 29, '01101': 34, '01110': 21, '01111': 27}}\n", + "All done!\n" + ] + } + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "e96067a1", - "metadata": {}, "source": [ "The `run()` method returns a [`RuntimeJob`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.RuntimeJob.html#qiskit.providers.ibmq.runtime.RuntimeJob) instace, which is similar to the `Job` instance returned by regular `backend.run()`. `RuntimeJob` supports the following methods:\n", "\n", @@ -458,127 +438,121 @@ "- `result()`: Wait for the job to finish and return the final result.\n", "- `cancel()`: Cancel the job.\n", "- `wait_for_final_state()`: Wait for the job to finish.\n", - "- `stream_results()`: Stream interim results. This can be used to start streaming the interim results if a `callback` function was not passed to the `run()` method.\n", + "- `stream_results()`: Stream interim results. This can be used to start streaming the interim results if a `callback` function was not passed to the `run()` method. This method can also be used to reconnect a lost websocket connection.\n", "- `job_id()`: Return the job ID.\n", - "- `backend()`: Return the backend where the job is run." - ] + "- `backend()`: Return the backend where the job is run.\n", + "- `logs()`: Return job logs.\n", + "- `error_message()`: Returns the reason if the job failed and `None` otherwise." + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "97da7ca3", - "metadata": {}, "source": [ "## Retrieving old jobs" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "d7d09960", - "metadata": {}, "source": [ "You can use the [`IBMRuntimeService.job()`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.IBMRuntimeService.html#qiskit.providers.ibmq.runtime.IBMRuntimeService.job) method to retrieve a previously executed runtime job. Attributes of this [`RuntimeJob`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.RuntimeJob.html#qiskit.providers.ibmq.runtime.RuntimeJob) instace can tell you about the execution:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 6, - "id": "676f664b", - "metadata": {}, + "source": [ + "retrieved_job = provider.runtime.job(job.job_id())\n", + "print(f\"Job {retrieved_job.job_id()} is an execution instance of runtime program {retrieved_job.program_id}.\")\n", + "print(f\"This job ran on backend {retrieved_job.backend()} and had input parameters {retrieved_job.inputs}\")" + ], "outputs": [ { - "name": "stdout", "output_type": "stream", + "name": "stdout", "text": [ "Job c2ajo0m0lb0ph8orsprg is an execution instance of runtime program sample-program.\n", "This job ran on backend ibmq_montreal and had input parameters {'iterations': 3}\n" ] } ], - "source": [ - "retrieved_job = provider.runtime.job(job.job_id())\n", - "print(f\"Job {retrieved_job.job_id()} is an execution instance of runtime program {retrieved_job.program_id}.\")\n", - "print(f\"This job ran on backend {retrieved_job.backend()} and had input parameters {retrieved_job.inputs}\")" - ] + "metadata": {} }, { "cell_type": "markdown", - "id": "f672021d", - "metadata": {}, "source": [ "Similarly, you can use [`IBMRuntimeService.jobs()`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.IBMRuntimeService.html#qiskit.providers.ibmq.runtime.IBMRuntimeService.jobs) to get a list of jobs. You can specify a limit on how many jobs to return. The default limit is 10:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 7, - "id": "2e05256b", - "metadata": {}, + "source": [ + "retrieved_jobs = provider.runtime.jobs(limit=1)\n", + "for rjob in retrieved_jobs:\n", + " print(rjob.job_id())" + ], "outputs": [ { - "name": "stdout", "output_type": "stream", + "name": "stdout", "text": [ "c2ajo0m0lb0ph8orsprg\n", "c2ajjgm0lb0ph8orspdg\n" ] } ], - "source": [ - "retrieved_jobs = provider.runtime.jobs(limit=1)\n", - "for rjob in retrieved_jobs:\n", - " print(rjob.job_id())" - ] + "metadata": {} }, { "cell_type": "markdown", - "id": "c4ebd6d1", - "metadata": {}, "source": [ "## Deleting a job" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "90c9ab70", - "metadata": {}, "source": [ "You can use the [`IBMRuntimeService.delete_job()`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.IBMRuntimeService.html#qiskit.providers.ibmq.runtime.IBMRuntimeService.delete_job) method to delete a job. You can only delete your own jobs, and this action cannot be reversed. " - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 8, - "id": "3984de3d", - "metadata": {}, - "outputs": [], "source": [ "provider.runtime.delete_job(job.job_id())" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "code", "execution_count": 9, - "id": "8be9e46a", - "metadata": {}, + "source": [ + "import qiskit.tools.jupyter\n", + "%qiskit_version_table" + ], "outputs": [ { + "output_type": "display_data", "data": { + "text/plain": [ + "" + ], "text/html": [ "

Version Information

Qiskit SoftwareVersion
QiskitNone
Terra0.17.1
Aer0.8.2
IgnisNone
AquaNone
IBM Q Provider0.13.1
System information
Python3.9.1 (default, Feb 5 2021, 11:23:59) \n", "[Clang 12.0.0 (clang-1200.0.32.28)]
OSDarwin
CPUs8
Memory (Gb)16.0
Fri May 07 09:22:18 2021 EDT
" - ], - "text/plain": [ - "" ] }, - "metadata": {}, - "output_type": "display_data" + "metadata": {} } ], - "source": [ - "import qiskit.tools.jupyter\n", - "%qiskit_version_table" - ] + "metadata": {} } ], "metadata": { @@ -602,4 +576,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/tutorials/02_uploading_program.ipynb b/tutorials/02_uploading_program.ipynb index 4e8a645df..11c4e5e87 100644 --- a/tutorials/02_uploading_program.ipynb +++ b/tutorials/02_uploading_program.ipynb @@ -2,53 +2,45 @@ "cells": [ { "cell_type": "markdown", - "id": "5cd4ee77", - "metadata": {}, "source": [ "# Uploading a Qiskit runtime program" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "ffdd1afe", - "metadata": {}, "source": [ "
\n", "Note: Access to the Qiskit Runtime service may not mean you have access to upload a runtime program, but check back, as we'll be releasing it publicly soon!\n", "
" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "248cd52a", - "metadata": {}, "source": [ - "Here we provide an overview on how to construct and upload a runtime program. A runtime program is a piece of Python code that lives in the cloud and can be invoked by other authorized users. Currently all runtime programs are **public**, meaning they are available to be used by authorized users as soon as they are uploaded." - ] + "Here we provide an overview on how to construct and upload a runtime program. A runtime program is a piece of Python code that lives in the cloud and can be invoked by other authorized users. Runtime programs are **private** by default, but the program visibility can be changed during upload or at a later time." + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "035cdbcc", - "metadata": {}, "source": [ "## Constructing a runtime program" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "79d64290", - "metadata": {}, "source": [ "Below is a template of a runtime program. You can find the template file in the \n", "[`qiskit-ibmq-provider`](https://github.com/Qiskit/qiskit-ibmq-provider/blob/master/qiskit/providers/ibmq/runtime/program/program_template.py) repository." - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 1, - "id": "84c321ac", - "metadata": {}, - "outputs": [], "source": [ "import sys\n", "import json\n", @@ -76,35 +68,32 @@ " \"\"\"\n", " # Massage the input if necessary.\n", " result = program(backend, user_messenger, **kwargs)\n", - " # UserMessenger can be used to publish final results.\n", - " user_messenger.publish(result, final=True) " - ] + " # Final result can be directly returned\n", + " return result\n" + ], + "outputs": [], + "metadata": {} }, { "cell_type": "markdown", - "id": "bb3207c3", - "metadata": {}, "source": [ "Each runtime program must have a `main()` function, which serves as the entry point to the program. This function must have `backend` and `user_messenger` as the first two positional arguments:\n", "\n", "- `backend` is an instance of [`ProgramBackend`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.ProgramBackend.html#qiskit.providers.ibmq.runtime.ProgramBackend) and has a `run()` method that can be used to submit circuits.\n", - "- `user_messenger` is an instance of [`UserMessenger`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.UserMessenger.html#qiskit.providers.ibmq.runtime.UserMessenger) and has a `publish()` method that can be used to send interim and final results to the program user. This method takes a parameter `final` that indicates whether it's a final result. Currently only final results are stored after a program execution finishes." - ] + "- `user_messenger` is an instance of [`UserMessenger`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.UserMessenger.html#qiskit.providers.ibmq.runtime.UserMessenger) and has a `publish()` method that can be used to send interim and final results to the program user. This method takes a parameter `final` that indicates whether it's a final result. However, it is recommended to return the final result directly from the `main()` function. Currently only final results are stored after a program execution finishes." + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "2d6c91ec", - "metadata": {}, "source": [ "There are several runtime programs in the `programs` directory in this repository. `sample_program.py` is one of them. It is a sample runtime program that submits random circuits for user-specified iterations:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 2, - "id": "6b1c8281", - "metadata": {}, - "outputs": [], "source": [ "\"\"\"A sample runtime program that submits random circuits for user-specified iterations.\"\"\"\n", "\n", @@ -142,51 +131,46 @@ " result = backend.run(qc).result()\n", " user_messenger.publish({\"iteration\": it, \"counts\": result.get_counts()})\n", "\n", - " user_messenger.publish(\"All done!\", final=True)\n" - ] + " return \"All done!\"\n" + ], + "outputs": [], + "metadata": {} }, { "cell_type": "markdown", - "id": "3c8bb52e", - "metadata": {}, "source": [ "## Data serialization" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "64fad8f8", - "metadata": {}, "source": [ "Runtime programs live in the cloud, and JSON is the standard way of passing data to cloud services. Therefore, when a user invokes a runtime program, the input parameters must first be serialized into the JSON format before passed to the server and then deserialized once received by the server. This serialization and deserialization is done using the [`RuntimeEncoder`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.RuntimeEncoder.html#qiskit.providers.ibmq.runtime.RuntimeEncoder) and [`RuntimeDecoder`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.RuntimeDecoder.html#qiskit.providers.ibmq.runtime.RuntimeDecoder) classes, respectively.\n", "\n", "Similarly, results passed back by a runtime program need to be serialized. You can choose to use the default `RuntimeEncoder`, or pass your own encoder to `user_messenger.publish()`. Keep in mind that if you choose to use your own encoder, you must document the corresponding decoder needed for the users of your program to deserialize the results." - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "8d938353", - "metadata": {}, "source": [ "If you want to pass custom Python classes, the `RuntimeEncoder` and `RuntimeDecoder` methods have some support for that. In your custom class, you can define a `to_json()` method that returns a JSON string representation of the object, and a `from_json()` class method that accepts a JSON string and returns the corresponding object. When `RuntimeEncoder` serializes a Python object, it checks whether the object has a `to_json()` method. If so, it uses the method for serialization. `RuntimeDecoder`, however, does _not_ invoke `from_json()` to convert the data back because it doesn't know how to import your custom class. You can, however, create your own decoder.\n", "\n", "You decoder should inherit the [`ResultDecoder`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.ResultDecoder.html#qiskit.providers.ibmq.runtime.ResultDecoder) class and overwrites its `decode()` method. This `decode()` method is called to deserialize job results. Your subclass can call the parent's ``decode()`` method to handle deserialization of other result data, and the subclass can handle just the custom class. " - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "196cdc32", - "metadata": {}, "source": [ "Here is an example of using a custom class `MyCustomClass`:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 3, - "id": "cb420e55", - "metadata": {}, - "outputs": [], "source": [ "import json\n", "\n", @@ -202,31 +186,20 @@ " @classmethod\n", " def from_json(cls, json_str):\n", " return cls(**json.loads(json_str))" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "markdown", - "id": "6fd2a274", - "metadata": {}, "source": [ "If an instance of `MyCustomClass` is used as the input, `RuntimeEncoder` will invoke its `to_json()` method to serialize the data. It also adds a `__type__` field to differentiate it from other types of data:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 4, - "id": "6ba36c4b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "This is what gets passed to the program:\n", - " {\"my_obj\": {\"__type__\": \"to_json\", \"__value__\": \"{\\\"foo\\\": \\\"my foo\\\", \\\"bar\\\": \\\"my bar\\\"}\"}}\n" - ] - } - ], "source": [ "from qiskit.providers.ibmq.runtime import RuntimeEncoder\n", "\n", @@ -245,63 +218,66 @@ "# This shows what the encoded value looks like:\n", "serialized = json.dumps(program_inputs, cls=RuntimeEncoder)\n", "print(f\"This is what gets passed to the program:\\n {serialized}\")" - ] + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "This is what gets passed to the program:\n", + " {\"my_obj\": {\"__type__\": \"to_json\", \"__value__\": \"{\\\"foo\\\": \\\"my foo\\\", \\\"bar\\\": \\\"my bar\\\"}\"}}\n" + ] + } + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "6b9355a6", - "metadata": {}, "source": [ "Your program can then use the `from_json()` method to restore the object:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 5, - "id": "7e37f2cf", - "metadata": {}, - "outputs": [], "source": [ "def main(backend, user_messenger, **kwargs):\n", " \"\"\"Main entry point of the program.\"\"\"\n", " my_obj_str = kwargs.pop('my_obj')\n", " my_obj = MyCustomClass.from_json(my_obj_str)" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "markdown", - "id": "f68d2922", - "metadata": {}, "source": [ "Similarly, if an instance of `MyCustomClass` is passed back as a program result:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 6, - "id": "c48db0db", - "metadata": {}, - "outputs": [], "source": [ "def main(backend, user_messenger, **kwargs):\n", " \"\"\"Main entry point of the program.\"\"\"\n", " user_messenger.publish({\"my_obj\": MyCustomClass(\"this foo\", \"that bar\")}, final=True)" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "markdown", - "id": "aaae9883", - "metadata": {}, "source": [ "Then you can define a custom `ResultDecoder.decode()` to reconstruct the object:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 7, - "id": "a04ad1a4", - "metadata": {}, - "outputs": [], "source": [ "from qiskit.providers.ibmq.runtime import ResultDecoder\n", "\n", @@ -312,33 +288,20 @@ " decoded = super().decode(data) # Call parent method to handle other data.\n", " decoded[\"my_obj\"] = MyCustomClass.from_json(decoded[\"my_obj\"])\n", " return decoded" - ] + ], + "outputs": [], + "metadata": {} }, { "cell_type": "markdown", - "id": "7a9c87db", - "metadata": {}, "source": [ "Users of your program can then use `MyResultDecoder` to decode the results:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 8, - "id": "9d869b7b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'my_obj': <__main__.MyCustomClass at 0x133192a60>}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ "# The following comments shows how it's normally used by the program user:\n", "\n", @@ -350,53 +313,49 @@ "\n", "# This shows what the decoded value looks like:\n", "MyResultDecoder.decode(serialized)" - ] + ], + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "{'my_obj': <__main__.MyCustomClass at 0x133192a60>}" + ] + }, + "metadata": {}, + "execution_count": 8 + } + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "29bc9c66", - "metadata": {}, "source": [ "## Testing your runtime program" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "2b23fbdf", - "metadata": {}, "source": [ "You can test your runtime program using a local simulator or a real backend before uploading it. Simply import and invoke the `main()` function of your program and pass the following parameters:\n", "\n", "- the `backend` instance you want to use\n", "- a new `UserMessenger` instance.\n", "- program input parameters that are serialized and then deserialized using the correct encoder and decoder. While this may seem redundant, it is to ensure input parameters can be passed to your program properly once it's uploaded to the cloud.\n" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "d229d7cf", - "metadata": {}, "source": [ "The following example tests the `sample-program` program we saw earlier. It uses the `qasm_simulator` from Qiskit Aer as the test backend. It serializes and unserializes input data using `RuntimeEncoder` and `RuntimeDecoder`, which are the default en/decoders used by runtime." - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 9, - "id": "9838965d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\"iteration\": 0, \"counts\": {\"10111\": 269, \"00110\": 240, \"00100\": 242, \"10101\": 273}}\n", - "{\"iteration\": 1, \"counts\": {\"10000\": 506, \"00000\": 518}}\n", - "{\"iteration\": 2, \"counts\": {\"00001\": 408, \"00011\": 616}}\n", - "\"All done!\"\n" - ] - } - ], "source": [ "import sys\n", "sys.path.insert(0, '..') # Add qiskit_runtime directory to the path\n", @@ -414,20 +373,30 @@ "unserialized_inputs = json.loads(serialized_inputs, cls=RuntimeDecoder)\n", "\n", "sample_program.main(backend, user_messenger, **unserialized_inputs)" - ] + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "{\"iteration\": 0, \"counts\": {\"10111\": 269, \"00110\": 240, \"00100\": 242, \"10101\": 273}}\n", + "{\"iteration\": 1, \"counts\": {\"10000\": 506, \"00000\": 518}}\n", + "{\"iteration\": 2, \"counts\": {\"00001\": 408, \"00011\": 616}}\n", + "\"All done!\"\n" + ] + } + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "e5e99fbb", - "metadata": {}, "source": [ "## Defining program metadata" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "2a909182", - "metadata": {}, "source": [ "Program metadata is data that helps users to understand how to invoke your program. It includes:\n", "\n", @@ -441,25 +410,33 @@ "- `interim_results`: Describes the interim results\n", "\n", "When uploading a program, you must specify at least `name`, `maximum execution time`, and `description`. It is strongly encouraged to also specify `parameters`, `return values`, and `interim results` if the program has them. You can specify the metadata fields individually, or use a JSON file." - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "39e87075", - "metadata": {}, "source": [ "Below shows the metadata JSON file of the `sample-program` program as an example:" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 10, - "id": "6eee0dc7", - "metadata": {}, + "source": [ + "import os\n", + "\n", + "sample_program_json = os.path.join(os.getcwd(), \"../qiskit_runtime/sample_program/sample_program.json\")\n", + "\n", + "with open(sample_program_json, 'r') as file:\n", + " data = file.read()\n", + "\n", + "print(data)" + ], "outputs": [ { - "name": "stdout", "output_type": "stream", + "name": "stdout", "text": [ "{\n", " \"name\": \"sample-program\",\n", @@ -482,55 +459,32 @@ ] } ], - "source": [ - "import os\n", - "\n", - "sample_program_json = os.path.join(os.getcwd(), \"../qiskit_runtime/sample_program/sample_program.json\")\n", - "\n", - "with open(sample_program_json, 'r') as file:\n", - " data = file.read()\n", - "\n", - "print(data)" - ] + "metadata": {} }, { "cell_type": "markdown", - "id": "df82efb7", - "metadata": {}, "source": [ "Note that the return value has `\"name\": \"-\"` because there is only 1 return value. If the program has multiple return values, you should specify a meaningful name for each one." - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "0432c5a8", - "metadata": {}, "source": [ "## Uploading a program" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "dfea7bc0", - "metadata": {}, "source": [ "You can use the [`IBMRuntimeService.upload_program()`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.IBMRuntimeService.html#qiskit.providers.ibmq.runtime.IBMRuntimeService.upload_program) method to upload your program. In the example below, the program data lives in the file `sample_program.py`, and its metadata, as described above, is in `sample_program.json`. " - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 11, - "id": "6f11e0e0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "sample-program\n" - ] - } - ], "source": [ "import os\n", "from qiskit import IBMQ\n", @@ -546,64 +500,69 @@ " metadata=sample_program_json\n", ")\n", "print(program_id)" - ] + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "sample-program\n" + ] + } + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "2babeff3", - "metadata": {}, "source": [ "`upload_program()` returns a program ID, which uniquely identifies the program. It is derived from the program name but is not guaranteed to be the same. Program ID is needed for users to invoke the program." - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "a0259d71", - "metadata": {}, "source": [ "## Deleting a program" - ] + ], + "metadata": {} }, { "cell_type": "markdown", - "id": "628b1d62", - "metadata": {}, "source": [ "You can use the [`IBMRuntimeService.delete_program()`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.IBMRuntimeService.html#qiskit.providers.ibmq.runtime.IBMRuntimeService.delete_program) method to delete a program. Only the original author of a program can delete it. \n" - ] + ], + "metadata": {} }, { "cell_type": "code", "execution_count": 12, - "id": "dd156a50", - "metadata": {}, + "source": [ + "import qiskit.tools.jupyter\n", + "%qiskit_version_table" + ], "outputs": [ { + "output_type": "display_data", "data": { + "text/plain": [ + "" + ], "text/html": [ "

Version Information

Qiskit SoftwareVersion
QiskitNone
Terra0.17.1
Aer0.8.2
IgnisNone
AquaNone
IBM Q Provider0.13.0
System information
Python3.9.1 (default, Feb 5 2021, 11:23:59) \n", "[Clang 12.0.0 (clang-1200.0.32.28)]
OSDarwin
CPUs8
Memory (Gb)16.0
Thu May 06 20:23:17 2021 EDT
" - ], - "text/plain": [ - "" ] }, - "metadata": {}, - "output_type": "display_data" + "metadata": {} } ], - "source": [ - "import qiskit.tools.jupyter\n", - "%qiskit_version_table" - ] + "metadata": {} }, { "cell_type": "code", "execution_count": null, - "id": "61352aae", - "metadata": {}, + "source": [], "outputs": [], - "source": [] + "metadata": {} } ], "metadata": { @@ -627,4 +586,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file