diff --git a/tutorials/00_introduction.ipynb b/tutorials/00_introduction.ipynb new file mode 100644 index 000000000..b3597b348 --- /dev/null +++ b/tutorials/00_introduction.ipynb @@ -0,0 +1,344 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2b3fb1b1", + "metadata": {}, + "source": [ + "# Qiskit Runtime" + ] + }, + { + "cell_type": "markdown", + "id": "8fb23fc2", + "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", + "Using Qiskit Runtime, for example, a research team at IBM Quantum was able to achieve \n", + "[120x speed up](https://research.ibm.com/blog/120x-quantum-speedup) in their lithium hydride simulation. \n", + "\n", + "Qiskit Runtime allows authorized users to upload their Qiskit quantum programs for themselves or \n", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "233c286a", + "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." + ] + }, + { + "cell_type": "markdown", + "id": "1612dac1", + "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." + ] + }, + { + "cell_type": "markdown", + "id": "61d9f293", + "metadata": {}, + "source": [ + "## Listing programs " + ] + }, + { + "cell_type": "markdown", + "id": "2ed6ce38", + "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 summary metadata of available programs\n", + "- `programs()`: returns a list of `RuntimeProgram` instances\n", + "- `program()`: returns a single `RuntimeProgram` instance\n", + "\n", + "The metadata of a runtime program includes its ID, name, description, maximum execution time, backend requirements, input parameters, return values, and interim results. Maximum execution time is the maximum amount of time, in seconds, a program can run before being forcibly terminated." + ] + }, + { + "cell_type": "markdown", + "id": "2abfb988", + "metadata": {}, + "source": [ + "To print the summary metadata of the programs (by default first 20 programs are displayed):" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a420f91c", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "==================================================\nqasm3-runner:\n Name: qasm3-runner\n Description: A runtime program that takes one or more circuits, converts them to OpenQASM3, compiles them, executes them, and optionally applies measurement error mitigation. This program can also take and execute one or more OpenQASM3 strings. Note that this program can only run on a backend that supports OpenQASM3.\n==================================================\nsampler:\n Name: sampler\n Description: Sample distributions generated by given circuits executed on the target backend.\n==================================================\nestimator:\n Name: estimator\n Description: Expectation value estimator. A runtime program that estimates the value of an observable for an input quantum circuit. This program is in beta mode and is only available to select accounts.\n==================================================\nsample-expval:\n Name: sample-expval\n Description: A sample expectation value program.\n==================================================\nvqe:\n Name: vqe\n Description: Variational Quantum Eigensolver (VQE) to find the minimal eigenvalue of a Hamiltonian.\n==================================================\ncircuit-runner:\n Name: circuit-runner\n Description: A runtime program that takes one or more circuits, compiles them, executes them, and optionally applies measurement error mitigation.\n==================================================\nhello-world:\n Name: hello-world\n Description: A sample runtime program.\n==================================================\nquantum-kernel-alignment:\n Name: quantum-kernel-alignment\n Description: Quantum kernel alignment algorithm that learns, on a given dataset, a quantum kernel maximizing the SVM classification margin.\n" + } + ], + "source": [ + "provider.runtime.pprint_programs()" + ] + }, + { + "cell_type": "markdown", + "id": "0a5ca5f5", + "metadata": {}, + "source": [ + "You can use the `limit` and `skip` parameters in `pprint_programs()` and `programs()` to page through the remaining programs. You can pass `detailed = True` parameter to `pprint_programs()` to view all the metadata for the programs. The program metadata once fetched, is cached for performance reasons, so you can pass `refresh = True` parameter to `pprint_programs()` or `programs()` methods in order to get the latest programs from the server. To print the metadata of the program `hello-world`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f8302b63", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "hello-world:\n Name: hello-world\n Description: A sample runtime program.\n Creation date: 2021-07-02T13:45:13Z\n Update date: 2021-07-02T13:45:13Z\n Max execution time: 300\n Input parameters:\n Properties:\n - iterations:\n Description: Number of iterations to run. Each iteration generates a runs a random circuit.\n Minimum: 0\n Type: integer\n Required: True\n Interim results:\n Properties:\n - counts:\n Description: Histogram data of the circuit result.\n Type: object\n Required: False\n - iteration:\n Description: Iteration number.\n Type: integer\n Required: False\n Returns:\n Description: A string that says 'All done!'.\n Type: string\n" + } + ], + "source": [ + "program = provider.runtime.program('hello-world')\n", + "print(program)" + ] + }, + { + "cell_type": "markdown", + "id": "ec37a66c", + "metadata": {}, + "source": [ + "As you can see from above, the program `hello-world` 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." + ] + }, + { + "cell_type": "markdown", + "id": "8097db3a", + "metadata": {}, + "source": [ + "## Invoking a runtime program " + ] + }, + { + "cell_type": "markdown", + "id": "b6d680a3", + "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", + "- `program_id`: ID of the program to run\n", + "- `inputs`: Program input parameters. These input values are passed to the runtime program.\n", + "- `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." + ] + }, + { + "cell_type": "markdown", + "id": "abe247c4", + "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 `hello-world` 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:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "92f46214", + "metadata": {}, + "outputs": [], + "source": [ + "def interim_result_callback(job_id, interim_result):\n", + " print(f\"interim result: {interim_result}\")" + ] + }, + { + "cell_type": "markdown", + "id": "3f9e793f", + "metadata": {}, + "source": [ + "The following example runs the `hello-world` program with 3 iterations on `ibmq_montreal` and waits for its result. You can also use a different backend that supports Qiskit Runtime:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d622e53c", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "job id: c618jdik2ih5ha3l6mog\ninterim result: {'iteration': 0, 'counts': {'00000': 31, '00001': 11, '10000': 10, '10001': 10, '10010': 19, '10011': 11, '10100': 4, '10101': 6, '10110': 11, '10111': 8, '11000': 16, '11001': 4, '11010': 45, '11011': 16, '11100': 8, '11101': 9, '11110': 18, '11111': 8, '00010': 104, '00011': 47, '00100': 5, '00101': 10, '00110': 25, '00111': 10, '01000': 60, '01001': 35, '01010': 260, '01011': 119, '01100': 13, '01101': 12, '01110': 39, '01111': 40}}\ninterim result: {'iteration': 1, 'counts': {'00000': 99, '00001': 64, '10000': 13, '10001': 12, '10010': 9, '10011': 4, '10100': 21, '10101': 89, '10110': 6, '10111': 19, '11000': 19, '11001': 9, '11010': 5, '11011': 5, '11100': 26, '11101': 61, '11110': 11, '11111': 17, '00010': 37, '00011': 23, '00100': 73, '00101': 13, '00110': 20, '00111': 3, '01000': 105, '01001': 83, '01010': 30, '01011': 26, '01100': 79, '01101': 11, '01110': 22, '01111': 10}}\ninterim result: {'iteration': 2, 'counts': {'00000': 30, '00001': 5, '10000': 8, '10001': 3, '10010': 1, '10011': 3, '10100': 7, '10101': 5, '10110': 3, '11000': 22, '11001': 6, '11010': 1, '11011': 3, '11100': 66, '11101': 8, '11110': 11, '11111': 7, '00010': 2, '00011': 2, '00100': 51, '00101': 5, '00110': 5, '00111': 2, '01000': 136, '01001': 16, '01010': 11, '01011': 2, '01100': 534, '01101': 31, '01110': 31, '01111': 7}}\nAll done!\n" + } + ], + "source": [ + "backend = provider.get_backend('ibmq_montreal')\n", + "program_inputs = {\n", + " 'iterations': 3\n", + "}\n", + "options = {'backend_name': backend.name()}\n", + "job = provider.runtime.run(program_id=\"hello-world\",\n", + " options=options,\n", + " inputs=program_inputs,\n", + " callback=interim_result_callback\n", + " )\n", + "print(f\"job id: {job.job_id()}\")\n", + "result = job.result()\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "id": "1cb7b160", + "metadata": {}, + "source": [ + "The `run()` method returns a [`RuntimeJob`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.RuntimeJob.html#qiskit.providers.ibmq.runtime.RuntimeJob) instance, which is similar to the `Job` instance returned by regular `backend.run()`. Some of the `RuntimeJob` methods:\n", + "\n", + "- `status()`: Return job status.\n", + "- `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. 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.\n", + "- `logs()`: Return job logs.\n", + "- `error_message()`: Returns the reason if the job failed and `None` otherwise.\n", + "\n", + "Refer to the [`RuntimeJob` API documentation](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.RuntimeJob.html#qiskit.providers.ibmq.runtime.RuntimeJob) for a full list of methods and usage. " + ] + }, + { + "cell_type": "markdown", + "id": "present-creature", + "metadata": {}, + "source": [ + "
\n", + "Note: To ensure fairness, there is a maximum execution time for each Qiskit Runtime job. If a job exceeds this time limit, it is forcibly terminated. The maximum execution time is calculated based on 1) a maximum system limit, 2) the `max_execution_time` defined by the program, and 3) the fair-share value in your hub/group/project. \n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "821630b0", + "metadata": {}, + "source": [ + "## Retrieving old jobs" + ] + }, + { + "cell_type": "markdown", + "id": "ed6efcd4", + "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) instance can tell you about the execution:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4336b881", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "Job c618jdik2ih5ha3l6mog is an execution instance of runtime program hello-world.\nThis 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}\")" + ] + }, + { + "cell_type": "markdown", + "id": "5c00514e", + "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:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "919862b8", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "c618jdik2ih5ha3l6mog\n" + } + ], + "source": [ + "retrieved_jobs = provider.runtime.jobs(limit=1)\n", + "for rjob in retrieved_jobs:\n", + " print(rjob.job_id())" + ] + }, + { + "cell_type": "markdown", + "id": "d6f8f1d2", + "metadata": {}, + "source": [ + "## Deleting a job" + ] + }, + { + "cell_type": "markdown", + "id": "81234913", + "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. " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b1095852", + "metadata": {}, + "outputs": [], + "source": [ + "provider.runtime.delete_job(job.job_id())" + ] + } + ], + "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.9.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/01_circuit_runner.ipynb b/tutorials/01_circuit_runner.ipynb new file mode 100644 index 000000000..8f1a3d334 --- /dev/null +++ b/tutorials/01_circuit_runner.ipynb @@ -0,0 +1,623 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f041e8ea", + "metadata": {}, + "source": [ + "# Circuit runner program" + ] + }, + { + "cell_type": "markdown", + "id": "d16ddf88", + "metadata": {}, + "source": [ + "## `run_circuits` convenience method\n", + "\n", + "Evaluating quantum circuits is the fundamental operation on quantum computing systems. The `circuit-runner` program facilitates compiling, executing, and (possibly) post-processing circuit entirely in the cloud. The easiest way to use the `circuit-runner` is via the convenience method accessible through a provider with `runtime` access:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "fafcaba8", + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit import IBMQ, QuantumCircuit\n", + "from qiskit.visualization import plot_histogram\n", + "from qiskit.quantum_info import hellinger_fidelity\n", + "from qiskit.ignis.mitigation.expval import expectation_value" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1b07aacd", + "metadata": {}, + "outputs": [], + "source": [ + "IBMQ.load_account();" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9b552135", + "metadata": {}, + "outputs": [], + "source": [ + "# Replace by your provider\n", + "provider = IBMQ.get_provider(project='qiskit-runtime')" + ] + }, + { + "cell_type": "markdown", + "id": "ee1e3511", + "metadata": {}, + "source": [ + "The runner is accessbile via the `run_circuits` method of the provider, and supports the same functionality that transpiling and executing a circuit in Qiskit does, as well as a few additional arguments:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "84785ee9", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "Execute the input circuit(s) on a backend using the runtime service.\n\n Note:\n This method uses the IBM Quantum runtime service which is not\n available to all accounts.\n\n Args:\n circuits: Circuit(s) to execute.\n\n backend_name: Name of the backend to execute circuits on.\n Transpiler options are automatically grabbed from backend configuration\n and properties unless otherwise specified.\n\n shots: Number of repetitions of each circuit, for sampling. If not specified,\n the backend default is used.\n\n initial_layout: Initial position of virtual qubits on physical qubits.\n\n layout_method: Name of layout selection pass ('trivial', 'dense',\n 'noise_adaptive', 'sabre').\n Sometimes a perfect layout can be available in which case the layout_method\n may not run.\n\n routing_method: Name of routing pass ('basic', 'lookahead', 'stochastic', 'sabre')\n\n translation_method: Name of translation pass ('unroller', 'translator', 'synthesis')\n\n seed_transpiler: Sets random seed for the stochastic parts of the transpiler.\n\n optimization_level: How much optimization to perform on the circuits.\n Higher levels generate more optimized circuits, at the expense of longer\n transpilation time.\n If None, level 1 will be chosen as default.\n\n init_qubits: Whether to reset the qubits to the ground state for each shot.\n\n rep_delay: Delay between programs in seconds. Only supported on certain\n backends (``backend.configuration().dynamic_reprate_enabled`` ). If supported,\n ``rep_delay`` will be used instead of ``rep_time`` and must be from the\n range supplied by the backend (``backend.configuration().rep_delay_range``).\n Default is given by ``backend.configuration().default_rep_delay``.\n\n transpiler_options: Additional transpiler options.\n\n measurement_error_mitigation: Whether to apply measurement error mitigation.\n\n use_measure_esp: Whether to use excited state promoted (ESP) readout for measurements\n which are the final instruction on a qubit. ESP readout can offer higher fidelity\n than standard measurement sequences. See\n `here `_.\n\n **run_config: Extra arguments used to configure the circuit execution.\n\n Returns:\n Runtime job.\n \n" + } + ], + "source": [ + "print(provider.run_circuits.__doc__)" + ] + }, + { + "cell_type": "markdown", + "id": "65f667d6", + "metadata": {}, + "source": [ + "of the possible arguments, `measurement_error_mitigation` is one that is unique to the Qiskit Runtime, and will be used below.\n", + "\n", + "We now construct an example circuit to execute via the circuit runner:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e01c0503", + "metadata": { + "tags": [ + "nbsphinx-thumbnail" + ] + }, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "
", + "image/svg+xml": "\n\n\n \n \n \n \n 2021-11-02T14:33:38.178360\n image/svg+xml\n \n \n Matplotlib v3.4.2, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "image/png": "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\n" + }, + "metadata": {}, + "execution_count": 5 + } + ], + "source": [ + "N = 6\n", + "qc = QuantumCircuit(N)\n", + "\n", + "qc.x(range(0, N))\n", + "qc.h(range(0, N))\n", + "\n", + "for kk in range(N//2,0,-1):\n", + " qc.ch(kk, kk-1)\n", + "for kk in range(N//2, N-1):\n", + " qc.ch(kk, kk+1)\n", + "qc.measure_all()\n", + "qc.draw('mpl',fold=-1)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f36f8eb8", + "metadata": {}, + "outputs": [], + "source": [ + "exact_dist = {'000000': 0.015624999999999986,\n", + " '000001': 0.015624999999999986,\n", + " '000011': 0.031249999999999965,\n", + " '000111': 0.06249999999999992,\n", + " '100000': 0.015624999999999986,\n", + " '100001': 0.015624999999999986,\n", + " '100011': 0.031249999999999965,\n", + " '100111': 0.06249999999999992,\n", + " '110000': 0.031249999999999965,\n", + " '110001': 0.031249999999999965,\n", + " '110011': 0.06249999999999992,\n", + " '110111': 0.12499999999999982,\n", + " '111111': 0.4999999999999991}" + ] + }, + { + "cell_type": "markdown", + "id": "f8186fc9", + "metadata": {}, + "source": [ + "Having defined our circuit(s), we can now compile and execute them on a given backend via the `run_circuits` method:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "08de9773", + "metadata": {}, + "outputs": [], + "source": [ + "backend = provider.backend.ibmq_montreal" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8356e156", + "metadata": {}, + "outputs": [], + "source": [ + "job = provider.run_circuits(qc, backend.name(), shots=2048, initial_layout=[0,1,4,7,10,12])" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "738dbad1", + "metadata": {}, + "outputs": [], + "source": [ + "res = job.result()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "4cf377ee", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA98AAAGXCAYAAACqZ+OYAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Z1A+gAAAACXBIWXMAAAsTAAALEwEAmpwYAACdH0lEQVR4nOzdd3wUdf7H8dc32SSbELrU0HtvRhGFiAri6Z2oZz8PFRU90Dt7B9QD9WyniF1PrFhPQU8R8QREODCgIKB0+AHSeyQ9398f38262Wwg2WQ3JLyfj8c+YGfnM98yk5n5znznO8Zai4iIiIiIiIhETkxlZ0BERERERESkulPjW0RERERERCTC1PgWERERERERiTA1vkVEREREREQiTI1vERERERERkQhT41tEREREREQkwtT4FhEREREREYmwqDe+jTEjjTHrjDFZxpiFxpgBh5k/3hjzgC8m2xjzf8aYvwbN80djzHLf78uNMedGthQiIiIiIiIipRfVxrcx5iLgKeBBoDcwF/jcGNPiEGHvAGcAI4COwAXAkoBl9gPeBd4Cevn+fd8Y0zcCRRAREREREREpM2OtjV5ixswHllhrrwmYtgr4wFp7V4j5TwfeB9paa3eWsMx3gXrW2sEB02YAO6y1l1R0GURERERERETKKmp3vo0x8cCxwPSgn6YDJ5YQdg7wHXCzMWaTMWaVMWaCMSY5YJ5+IZb5xSGWKSIiIiIiIhJVniimdQwQC2wLmr4NGFRCTBugP5AN/BGoAzwNNAXO983TuIRlNg61QGPMCFwXdpKSko5t1qwZAAkJCcTGxnLw4EEAPB4PiYmJHDhwoDCO5ORkDh48SH5+PgA1atQgNzeXnJwcALxeL8YYMjMzAYiLiyMhIYGMjAwAYmJiqFGjRoUs49dff6WgoACA5ORksrOzyc3NBSAxMRFrLVlZWQDEx8cTFxfHr7/+CkBsbCxJSUkVsoyMjAwKe0/UrFmTzMxM8vLy8NUv+fn5ZGdnl6qOK2IZWk9aT1pPWk9aT1pPWk9aT1pPWk9aT6Wv45UrV+601jbgEBYuXNjQ4/G8DHRDg3aXxBpj9uXn579aUFDw3LHHHpsTPEM0G9/hiAEscKm1dh+AMeZ64AtjTCNrbXCj+7CstS8CLwKkpqba9PT0isyviIiIiIhIlWGM2XC4eTwez8uNGzfu3KBBgz0xMTHRe265CrHWkpOTE/fLL7/csH///j7A5cHzRPOqxU4gH2gUNL0RsLWEmC3A5sKGt89Pvn8LB2nbWsZlioiIiIiISOl1a9CgwX41vEtmjCEhISG3ZcuW+3C9t4uJWuPbWpsDLAQGB/00GDfqeSjfAk2DnvHu4Pu38ArNvDIuU0REREREREovRg3v0vHVU2zI36KclyeAK4wxVxtjOhtjnsI9v/08gDHmdWPM6wHzvw3sAl41xnQ1xpyEe1XZB9ba7b55ngJONcbcaYzpZIy5CzgFeDJKZRIRERERERE5pKg+822tfdcYUx+4F2gCLAXOtNYW3sVuETR/hjFmEG6Qte+APcDHwJ0B88w1xlwMjAMeANYAF1lr50e4OCIiIiIiIiKlEvUB16y1zwLPlvDbwBDTVgCnH2aZHwAfVET+REREREREpGTXPMmxkVz+SzeyMJLLrywaJl5EREREREQkwtT4FhERERERkSqv8N3oRyo1vkVERERERKRKSklJ6X7PPfc07tChQ5caNWr0uf3225s0b968W40aNXq3bdu26+uvv16ncN6mTZt2/+abb5IAnnvuuXrGmGPT09O9AP/85z+PGTRoUNtI5lWNbxEREREREamyPvzww3qfffbZqt27d3/fqVOnrG+++WbF/v37v7/zzjt/ufbaa1tv2LAhDqBv374HZsyYURNg9uzZNZs1a5b91VdfFX5PHjBgwIFI5lONbxEREREREamyrrvuum3t2rXLTU5OtsOHD9/TqlWr3NjYWK655po9LVu2zP7mm29qAKSlpWV88803NQHmz5+ffNNNN20N+F7ztNNOy4hkPtX4FhERERERkSqrZcuW/oe9J06cWL9Tp05datas2atmzZq9Vq9enbhjxw4PwODBgw+kp6cnb9iwIa6goMBcccUVu9PT05NXrFgRf+DAgdh+/fodjGQ+o/6qMREREREREZGKYoyxACtXroy/+eabW37yyScrTzvttAyPx0OnTp26WGsB6NatW7bX6y149NFHG/bt2/dAvXr1Co455pjcp59+ukFqampGbGxsRPOpO98iIiIiIiJS5R04cCDGGEPjxo1zAZ566qn6q1evTgycp2/fvgcmTZrUMC0t7QDASSeddGDSpEkN+/fvH9HnvUF3vkVERERERKQMXrqRhZWdh1COPfbYrBEjRmxLS0vrHBMTY//4xz/u6t27d5HnuNPS0g58+umn9QYPHpwBMHDgwAMvvvhio1NOOSWiz3sDmMJb8Eej1NRUm56eXtnZEBERERERqRTGmIXW2tRDzbN48eL1PXv23BmtPFV1ixcvPqZnz56tgqer27mIiIiIiIhIhKnxLSIiIiIiIhJhanyLiIiIiIiIRJga3yIiIiIiIiIRpsa3iIiIiIiISISp8S0iIiIiIiISYWp8i4iIiIiIiESYGt8iIiIiIiIiEeap7AyIiIiIiIhI1bGm433HRnL5bVfctzCSy68suvMtIiIiIiIiUgp//OMfW/31r39tGk6sGt8iIiIiIiIiEabGt4iIiIiIiFRZ69evjxsyZEjbunXr9kxJSek+bty4htu2bYtt1KhRj7fffrs2wL59+2JatGjRbeLEifUB3nnnndqdO3fukpyc3Ltx48Y9br755iJ3s7/44ovk3r17d6pZs2avxo0b95gwYUL9xx577JgpU6bUe+655xonJSX1PvXUU9uVJZ965ltERERERESqpPz8fM4666x2v/vd7/ZOmTJl7dq1a+NOP/30jp07d8567rnn1o8YMaL1ySefvOyWW25J6dKly8Hrr79+F0BycnLBa6+9tu7YY4/NTE9PTzzzzDM79O7d++Cf//znvStXrow/77zz2j/xxBMbrrjiij179uyJWbt2bfyJJ56YOW/evOSUlJScCRMm/FLWvKrxLSIiIiIiIlXSrFmzauzevdvz2GOPbQHo0qVLzp///OcdkydPrvfBBx+snzJlyu5TTjmlw969ez1LlixZVhj3+9///kDh//v27Zs5dOjQ3TNnzqz55z//ee+rr75a78QTT9x/7bXX7gZo3LhxfuPGjTPLm1d1OxcRERERESmFadOm0bFjR9q1a8fDDz9c7PdJkybRoEEDevXqRa9evXj55Zf9v7322mu0b9+e9u3b89prr/mnL1y4kO7du9OuXTv++te/Yq2NSlmqi7Vr18bv2LEjvmbNmr0KPxMmTGiyY8cOD8CoUaN2rlq1KvGiiy7a1bhx4/zCuP/+9781+vbt26Fu3bo9a9as2eutt95qsGvXLg/Axo0b41u3bp1d0XlV41tEREREROQw8vPzGTVqFJ9//jnLly9n8uTJLF++vNh8F110ET/88AM//PADV199NQC7d+/m/vvvZ/78+SxYsID777+fPXv2APCXv/yFl156iVWrVrFq1SqmTZsW1XJVda1atcpJSUnJPnDgwA+Fn19//fX7WbNmrc7Ly2PEiBEtzz333F2vvfZag6VLlyYUxl1++eWtzzzzzL2bN29ecuDAgR/+9Kc/7Si88NG8efOcdevWJYRKzxgTdl7V+BYRERERETmMBQsW0K5dO9q0aUN8fDwXX3wxU6ZMKVXsF198weDBg6lXrx5169Zl8ODBTJs2jS1btrB//35OOOEEjDEMGzaMjz/+OLIFqWYGDhz4a40aNfLvueeexhkZGSYvL4/vvvvOO2vWrKS77rqriTGG9957b/3IkSO3/vnPf26dl5cHwK+//hpbr169/KSkJPv1118nffzxx/UKlzl8+PDdc+fOrfXyyy/Xzc3NZevWrbFz585NBGjYsGFuSQ3zw9Ez3yIiIiIiIoexefNmmjdv7v/erFkz5s+fX2y+Dz/8kNmzZ9OhQwf++c9/0rx585CxmzdvZvPmzTRr1qzY9CNd2xX3LazsPBTyeDx89tlnq2+44YZmrVq16pGTk2Nat26d9fvf/37vCy+80Gju3Lk/eTwexo0bt3X69Ol17rnnnsb/+Mc/tj7++OP/d8899zS78847Wxx//PEHfv/73+/Zt29fLED79u1zPvzww1W33357sxtvvLFVcnJy/j333LP5xBNPzPzLX/6y84ILLmhbs2bNXn379j0wY8aMNaXOa+SqQURERERE5Ojxhz/8gUsuuYSEhAReeOEFLr/8cv773/9WdraqvVatWuV+8skn64Knjx8/fmvh/z0eD4sWLfq58PuVV16558orr9xT0jLPOOOMjDPOOOPn4Ondu3fP/vnnn4s/b1AK6nYuIiIiIiJyGCkpKWzcuNH/fdOmTaSkpBSZp379+iQkuB7JV199NQsXLjxkbEpKCps2bTrkMqX6UONbRERERETkMI477jhWrVrFunXryMnJ4Z133uHss88uMs+WLVv8/586dSqdO3cGYMiQIUyfPp09e/awZ88epk+fzpAhQ2jSpAm1atXif//7H9ZaXn/9dYYOHRrVckn0qNu5iIiIiIjIYXg8HiZOnMiQIUPIz89n+PDhdO3alTFjxpCamsrZZ5/NhAkTmDp1Kh6Ph3r16jFp0iQA6tWrx+jRoznuuOMAGDNmDPXqufG9nn32Wa644goyMzP53e9+x+9+97vKKqJEmDma3yOXmppq09PTKzsbIiIiIiIilcIYs9Bam3qoeRYvXry2e/fue2JiYo7exmMpFRQUmB9//LFOz5492wb/pm7nIiIiIiIicihLd+zYUbugoCD8l1xXc9ZasrOz4zZs2FAHmBNqHnU7FxERERERkRLl5eVdvXXr1pe3bt3aDd3ALUmBMWZffn7+hIKCgudCzaDGt4iIiIiIiJTo2GOP3Q6cfdgZ5ZB01UJEREREREQkwtT4FhEREREREYkwNb5FREREREREIkyNbxEREREREZEIU+NbREREREREJMLU+BYRERERERGJMDW+RURERERERCJMjW8RERERERGRCFPjW0RERERERCTC1PgWERERERERiTA1vkVEREREREQizFPZGRAREREREalO1nS8r9i0tiuKT5Oji+58i4iIiIiIiESYGt8iIiIiIiIiEabGt4iIiIiIiEiEqfEtIiIiIiIiEmFqfIuIiIiIiIhEmBrfIiIiIiIiIhGmxreIiIiIiIhIhKnxLSIiIiIiIhJhanyLiIiIiIiIRJga3yIiIiIiIiIRpsa3iIiIiIiISISp8S0iIiIiIiISYWp8i4iIiIiIiESYGt8iIiIiIiIiEabGt4iIiIiIiEiEqfEtIiIiIiIiEmFqfIuIiIiIiIhEmBrfIiIiIiIiIhGmxreIiIiIiIhIhKnxLSIiIiIiIhJhanyLiIiIiIiIRJga3yIiIiIiIiIRpsa3iIiIiIiISIRFvfFtjBlpjFlnjMkyxiw0xgwoZVx/Y0yeMWZp0PQrjDE2xMcbmRKIiIiIiIiIlE1UG9/GmIuAp4AHgd7AXOBzY0yLw8TVBV4HviphloNAk8CPtTarovItIiIiIiIiUh7RvvN9MzDJWvuStfYna+0NwBbgL4eJewV4DZhXwu/WWrs18FOBeRYREREREREpF0+0EjLGxAPHAo8F/TQdOPEQcSOBRsA4YHQJsyUaYzYAscAPwGhr7fclLG8EMAKgadOmzJw5E4A2bdpQs2ZNFi9eDED9+vXp2rUrs2fPBsDj8dC/f38WLVrE/v37AUhNTWXbtm1s3LgRgPbt25OQkMDSpa5nfMOGDenQoQNz5swBICEhgX79+pGenk5GRgYAffv2ZdOmTWzevBmAjh07Ehsby/LlywFo3LgxrVu3Zt48d90hMTGRvn37Mn/+fDIzMwHo168f69atY+tWd82hS5cu5Ofns2LFCgBSUlJo1qwZ8+fPByA5OZnU1FTmzZtHdnY2AP3792flypVs374dgG7dupGdnc2qVasAaN68OY0aNSI9PR2AWrVq0adPH+bMmUNeXh4AaWlpLFu2jF27dgHQs2dPDhw4wNq1awFo1aoV9erVY9GiRQDUrVuXnj17MmvWLKy1GGM4+eSTWbx4MXv27AGgT58+7N69m/Xr12s9aT1pPWk9aT1pPWk9aT1pPWk9VYn1FEdxM2fOPGLXk0SHsdZGJyFjmgKbgZOttbMDpo8B/mSt7RgipjswAzjBWrvOGHMfcL61tlvAPP2ADsBioCbwN+BMoKe1dtWh8pSammoLN1oREREREZGKsKbjfcWmtV1RfNqRwBiz0FqbWtn5OBpE7c53WRljEoB3gVuttetKms9aO4+A7ujGmLm4u983AH+NcDZFREREREREDiuaje+dQD6uC3mgRkCoZ7SbAJ2BV40xr/qmxQDGGJMHnGmtnR4cZK3NN8akA+0rLOciIiIiIiIi5RC1AdestTnAQmBw0E+DcaOeB9sMdAd6BXyeB1b7/h8qBmOMAXrgBnITERERERERqXTR7nb+BPCGMWYB8C1wHdAU16jGGPM6gLV2mLU2Fwh+p/d2INtauzRg2ljgf8AqoBauq3kPDj+CuoiIiIiIiEhURLXxba191xhTH7gX1618Ka77+AbfLId833cJ6gAvAo2BfcD3QJq1dkH5cywiIiIiIiJSflEfcM1a+yzwbAm/DTxM7H3AfUHTbgJuqpjciYiIiIiIiFS8qD3zLSIiIiIiInK0UuNbREREREREJMLU+BYRERERERGJMDW+RURERERERCJMjW8RERERERGRCFPjW0RERERERCTC1PgWERERERERiTA1vkVEREREREQiTI1vERERERERkQhT41tEREREREQkwtT4FhEREREREYkwNb5FREREREREIkyNbxEREREREZEIU+NbREREREREJMLU+BYRERERERGJMDW+RURERERERCJMjW8RERERERGRCFPjW0RERERERCTC1PgWERERERERiTA1vkVEREREREQiTI1vERERERERkQhT41tEREREREQkwtT4FhEREREREYkwNb5FREREREREIkyNbxEREREREZEIU+NbREREREREJMLU+BYRERERERGJMDW+RURERERERCJMjW8RERERERGRCFPjW0RERERERCTC1PgWERERERERiTA1vkVEREREREQiTI1vERERERERkQhT41tEREREREQkwtT4FhEREREREYmwMjW+jTExxpiYgO+NjTFXG2NOqvisiYiIiIiIiFQPZb3z/R/gBgBjTDKQDjwKzDTGDKvgvImIiIiIiIhUC2VtfKcC//X9/zxgP9AQuAa4tQLzJSIiIiIiIlJtlLXxnQzs9f3/dOAja20urkHetgLzJSIiIiIiIlJtlLXx/X/AScaYGsAQ4Evf9HrAwYrMmIiIiIiIiEh1UdbG9xPAG8AmYDMw2zc9DfixAvMlIiIiIiJSacaNG0d8fDxxcXGcccYZxX6/9NJL8Xq9JCYmUqtWLaZOnQrAww8/TLdV4+myahzdVo3n5d1z/TF/+9vfSExMxOv10rdv36iVRY4MZWp8W2tfAPoBw4H+1toC309rgNEVnDcREREREZGoy8nJ4f7772f69Ons2bOHWbNm+RvXhR555BGysrLIzMzkhhtuYPjw4QC0bNmSd5oPZ3n7e3mi8Xk8snMGAKtWreKZZ55h0aJFZGVlsXPnTh599NGol00qT5nf822tTbfWfmStzQiY9h9r7bcVmzUREREREZHomzRpErVr12bgwIEkJyeTlpbGM888U2SeZs2a+f+/f/9+jDEAXHLJJXTzNgFgUI2OWCwH8rOYO3cutWrVonPnzgAMHjyY1157LUolkiNBmRvfxpiRxphlxpiDxpg2vml3GGMurPjsiYiIiIiIRNeKFSuoX7++/3vr1q3ZsmVLsfkuvPBC4uLieO6553j77beL/f7wzhnUivFSM9ZLWloa+/btY86cOWRlZfHJJ5+wY8eOiJZDjixlanwbY24E7gVeBEzAT78A11dctkRERERERI5s7733Hrm5uVx77bVcd911RX778sDPvLZ3PhObuHuUrVu35u677+aMM86gQYMGNGnShJiYMt8LlSqsrGv7OuAaa+1TQF7A9EVA1wrLlYiIiIiISCXp2LEju3bt8n9ft24dTZo0KXH+p556irVr1/q/L87czA1b3mdMwzPoV6O1f/rf//53MjIyOHDgAJ06daJ58+aRKYAckcra+G4JLA0xPRdILH92REREREREKtewYcPYt28fs2fPJiMjg9mzZzNy5Mgi83z55Zf+/99///0kJSUBsGHDBi7dNIkr65zAn+ocVyRm2bJlgGvMf/jhh/z973+PcEnkSOIp4/xrgT7AhqDpZwLLKyRHIiIiIiIilcjr9XLvvfcyaNAgrLUMHDiQoUOHkpaWxoABAxg/fjy33XYbP//8M8YYvF6v/5nvq666imybx+v7FvD6vgUAfNziGtoCZ555Jlu3bgXg2muvZciQIZVVRKkExlpb+pmNuRIYB9wOvABcC7TzfR9urX03EpmMlNTUVJuenl7Z2RARERERkWpkTcf7ik1ru6L4tCOBMWahtTa1svNxNCjTnW9r7avGGA/wIJAEvIEbbO2vVa3hLSIiIiIiIhItZe12jrX2JeAlY8wxQIy1dnvFZ0tERERERESk+ihz47uQtXZnRWZEREREREREpLo6bOPbGLMEONlau8cY8yNQ4kPi1toeFZk5ERERERERkeqgNHe+PwSyA/5f+hHaREREREREROTwjW9r7f0B/78vorkRERERERERqYZiyjKzMea/xpg6IabXMsb8t8JyJSIiIiIiIlKNlKnxDQwE4kNM9wIDyp0bERERERERkWqoVKOdG2P6BHztYYzZHfA9FhgCbK7IjImIiIiIiIhUF6V91Vg6bqA1C0wP8XsmcENFZUpERERERKSquObJot/vrJRcyJGutI3v1oAB1gLHAzsCfssBtltr8ys4byIiIiIiIiLVQqka39baDb7/lvUZcREREREREZGj3mEb38aY84BPrLW5vv+XyFr77wrLmYiIiIiIiEg1UZo73x8AjYHtvv+XxOIGXxMRERERERGRAIdtfFtrY0L9X0RERERERERKR41pERERERERkQgr7TPfpaJnvkVERERERESKK+0z36WhZ75FREREREREQijTM98iIiIiIiIiUnZqWIuIiIiIiIhEmN7zLSIiIiIiIhJhpbnz/QFQN+D/JX3eL02CxpiRxph1xpgsY8xCY8yAQ8x7sjFmrjFmlzEm0xjzszHm1hDz/dEYs9wYk+3799zS5EVEREREREQkGg7b+LbWxlhrtwf8v6TPYQdbM8ZcBDwFPAj0BuYCnxtjWpQQkgFMANKALsA44H5jzMiAZfYD3gXeAnr5/n3fGNP3cPkRERERERERiYZoP/N9MzDJWvuStfYna+0NwBbgL6FmttYutNa+Y61dZq1dZ619E/gCCLxbfiPwtbV2vG+Z44GZvukiIiIiIiIi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8huv18u9997LoEGDqFu3Lv3796dt27YsXryYe+65B3AN67vvvps1a9bwz3/+E6/XC8BXX33Fe++9x6WXXkqXLl248MIL+eMf/wjA8ccfT0yMO33v2LEjAwcOBOCee+6hR48e3HTTTf5Bu0TkyBPVxre19l3gRuBe3Kjk/YEzrbUbfLO08H0Cfe/7DAD+4Pv/ZwG/1wFeBH7CjZyeAqRZaxdEogwiIiIiIoczduxYcnJyyM3N5csvvwTg97//PePHjwdgwIABjBo1CmstBQUFZGVlAXDnnXfy5z//mbfffpvMzEzefPNN/zKnTJlC+/btmTdvHgUFBcycOZOHHnqIn3/+me+++47du3fzj3/8I/qFFZFSidqrxgpZa58Fni3ht4EhppnDLO8m4KYKyZyIiIiISASkpKT4X4MFsGnTJlJSUkodv3//fs466yzGjx/PCSec4J/epEkTwL0668orr+Sxx4Lf6isiR4oqP9q5iIiIiMiR7rjjjmPVqlWsW7eOnJwc3nnnHc4+++xSxebk5HDuuecybNgwzj///CK/bdnihjmy1vLxxx/TrVu3Cs+7iFQMNb5FRERERCLM4/EwceJEhgwZQufOnbnwwgvp2rUrY8aMYerUqQB89913NGvWjPfff59rr72Wrl27AvDee+8xe/ZsJk2aVOyVYn/605/o3r073bt3Z+fOndx7772VVUQROQxjra3sPFSa1NRUm56eXtnZEBERERGRKuyaJ4t+v/O5+4rN03ZF8WlHAmPMQmttamXn42gQ9We+RURERETECW60Abx0Y7RzISLRoG7nIiIiIiIiIhGmxreIiIiIiIhIhKnbuYiIiIjIEWRNx/uKTTtSnxcWkdLTnW8RERERERGRCFPjW0RERERERCTC1PgWERERERERiTA1vkVEREREREQiTI1vERERERERkQhT41tEREREREQkwtT4FhEREREREYkwNb5FREREREREIkyNbxEREREREZEIU+NbREREREREJMLU+BYRERERERGJMDW+RURERERERCJMjW8RERERERGRCFPjW0RERERERCTC1PgWERERkagaN24c8fHxxMXFccYZZxT7ff/+/bRo0YK4uDiSk5OZM2cOACNHjiQxMdH/Mcbw7rvv8ssvvxSZHhMTQ+/evaNdLBGRQ1LjW0RERESiJicnh/vvv5/p06ezZ88eZs2axdSpU4vMM2LECJKTk8nNzWX48OFceumlADz77LNkZmaSmZnJm2++icfj4aKLLqJp06b+6ZmZmXi9XoYPH14ZxRMRKZEa3yIiIiISNZMmTaJ27doMHDiQ5ORk0tLSeOaZZ4rMM2PGDG688UYAHnnkETZt2kRBQUGReR5//HGOP/74Ysv/4osvyMnJYdSoURErg4hIONT4FhEREZGoWbFiBfXr1/d/b926NVu2bCkyT0ZGhr/buNfrJTY2llWrVhWZ57vvvuPWW28ttvx//OMf9OrVi5gYneaKyJHFU9kZEBEREREpi1deeYXY2FjOPffcYr99++23vPLKK5WQKxGRQ9MlQRERERGJmo4dO7Jr1y7/93Xr1tGkSZMi8yQnJ/P9998DkJWVRX5+Pu3bt/f//swzz5CWllZs2e+99x7WWi677LII5V5EJHxqfIuIiIhI1AwbNox9+/Yxe/ZsMjIymD17NiNHjiwyz6mnnsqTTz4JwO23305KSoq/G3leXh6LFy/m7rvvLrbsJ554gn79+kW8DCIi4VC3cxERERGJGq/Xy7333sugQYOw1jJw4ECGDh1KWloaAwYMYPz48bz44ot069aNuLg4EhIS+PTTT/3xEydOJDExkYEDBxZb9sKFC5kyZUoUSyMiUnq68y3iM23aNDp27Ei7du14+OGHi/2enZ3NRRddRLt27ejbty/r16/3/7ZkyRL69etH165d6d69O1lZWQCcccYZ9OzZk65du3LdddeRn58freKIiBxVtA+vWsaOHUtOTg65ubl8+eWXANx999188MEHtGvXjueff55NmzaRm5tLRkYGAwcO9K/DiRMn0rVr15DrsEOHDtxxxx1ahyJyRFLjWwTIz89n1KhRfP755yxfvpzJkyezfPnyIvO88sor1K1bl9WrV3PTTTdxxx13AK7722WXXcbzzz/PsmXLmDlzJnFxcYB79mzx4sUsXbqUHTt28P7770e9bCIi1Z324VWf1qGIHA3U+BYBFixYQLt27WjTpg3x8fFcfPHFxbqtTZkyhcsvvxyA888/n6+++gprLdOnT6dHjx707NkTgPr16xMbGwtArVq1AHdikJOTgzEmiqUSETk6aB9e9WkdisjRQI1vOSqNGzeO+Ph44uLiOOOMM9i8eTPNmzf3/16/fn0efPBB4uLiSE5OZs6cOWzevJktW7Zg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iaFfv35FYnJycrjqqqv8V6rbt2/vj9u1axennHIKycnJXH/99cXiRowYQYcOHejUqRMffvhhRMosIlKVTZkyhaVLl9KgQYMi+++EhARmzJhBbm4uTzzxBNZa/va3vzF69Gjatm3LE088wYYNG7juuuvo0aMHMTEx1K5dm5NOOont27dzzjnnMHz4cM444wzatWtHnz59KCgo8A88BG4ffvLJJzNx4kT++Mc/kpmZSZs2bbj22mvLvA+/5sniHzlyhDs2wapVq6hbty7GGHr06FEkpk6dOsTHx5OYmMirtyWyZ8sy/299zijaYwLwj/VSGBs41svAgQMBeP7558nKyiIzM7PYWC/BvTCkegk1xsTIkSOLzHPqqacWeTtOSkoKMTExbNiwgbS0NG688UZ/wxsgJiaG5s2b89RTTwHw0ksv+XtMilQHanxHUfCBND8/n1GjRvH555+zfPly3nrrLZo0aVLkQPrKK6/w008/sWbNGhYsWED79u3Jz8/nzjvvZNKkSUyYMIGVK1cSExPjH+Tn2muvZfPmzfz+978nKSmJ1atX06tXL8Dd0fn73/8OuAOm1+ulS5cu5OTkMH78eBo2bMjKlStZvnw5J598cljlXNPxviIfEZEjRajRwIOfQ01OTub7778HfusCecIJJ7Br1y6+++47LrjgAjp27Mj+/fv9++/Jkyfj9XpZs2YNZ511Fm+++Sb5+fkcPHiQ+vXrc9lll/G3v/2Nzp0787///Y9du3Zx//33c+mll7J06VLq16/PRx99RJ06dfj111+56aabuPTSS1mzZg3du3dn69atgNuHjx492p+3goICMjIyaNasWYXsw4P339qHV47yjCJdu3Ztxo8fzyWXXBJy2S+//DKZmZlc+WgmdZt0LfJbuOte283RKdQYE0OHDi3ydpwXX3yxyNtx3njjDQCuuuoqsrKyePrpp0lMTCQxMdE/EOTkyZN54IEHSExM5N///jfvv/9+pZVRpKKp8R0loQ6kjz/+OO3ataNNmzbEx8djjCE/P7/IgfSjjz5i+fLl/O1vf+O8884jLy+Pxx57jPbt23PhhReSnJzMwYMHMcaQkJDA8OHDeeONN8jLy+Oyyy7jwQcf5KKLLiIjIwNrLTVq1KB///488MADXHfddRw8eJB9+/Zx66238q9//Yu77roLcFcedaVRRKqbcO/UXH75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\n", + "text/plain": [ + "
" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plot_histogram([res.get_counts(), exact_dist], legend=['raw', 'exact'], figsize=(15,6))" + ] + }, + { + "cell_type": "markdown", + "id": "6c9901a9", + "metadata": {}, + "source": [ + "The overall fidelity between the ideal and raw distributions is:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3dfd52a7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.839082737475692" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hellinger_fidelity(res.get_counts(), exact_dist)" + ] + }, + { + "cell_type": "markdown", + "id": "b0707ef3", + "metadata": {}, + "source": [ + "## The circuit-runner program\n", + "\n", + "The `run_circuits` method executes a `circuit-runner` for you internally. It is also possible to directly call this program via the `program_id`. First, let's print the program description:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "23de57a7", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "circuit-runner:\n Name: circuit-runner\n Description: A runtime program that takes one or more circuits, compiles them, executes them, and optionally applies measurement error mitigation.\n Creation date: 2021-07-02T13:46:10Z\n Update date: 2021-07-02T13:46:10Z\n Max execution time: 14400\n Input parameters:\n Properties:\n - seed_transpiler:\n Type: integer\n Description: Sets random seed for the stochastic parts of the transpiler.\n Required: False\n - translation_method:\n Enum: ['unroller', 'translator', 'synthesis']\n Type: string\n Description: Name of translation pass ('unroller', 'translator', 'synthesis').\n Required: False\n - transpiler_options:\n Type: object\n Description: Additional compilation options.\n Required: False\n - shots:\n Default: 1024\n Description: Number of repetitions of each circuit, for sampling. Default: 1024.\n Type: integer\n Required: False\n - circuits:\n Description: A circuit or a list of QuantumCircuits.\n Type: ['object', 'array']\n Required: True\n - init_qubits:\n Type: boolean\n Description: Whether to reset the qubits to the ground state for each shot.\n Required: False\n - layout_method:\n Enum: ['trivial', 'dense', 'noise_adaptive', 'sabre']\n Type: string\n Description: Name of layout selection pass ('trivial', 'dense', 'noise_adaptive', 'sabre')\n Required: False\n - initial_layout:\n Description: Initial position of virtual qubits on physical qubits.\n Type: ['object', 'array']\n Required: False\n - rep_delay:\n Type: number\n Description: Delay between programs in seconds.\n Required: False\n - routing_method:\n Enum: ['basic', 'lookahead', 'stochastic', 'sabre']\n Type: string\n Description: Name of routing pass ('basic', 'lookahead', 'stochastic', 'sabre').\n Required: False\n - optimization_level:\n Min: 0\n Type: integer\n Default: 1\n Description: How much optimization to perform on the circuits (0-3). Higher levels generate more optimized circuits. Default is 1.\n Max: 3\n Required: False\n - measurement_error_mitigation:\n Type: boolean\n Default: False\n Description: Whether to apply measurement error mitigation. Default is False.\n Required: False\n Interim results:\n none\n Returns:\n Description: Circuit execution results in a RunnerResult object.\n Type: object\n" + } + ], + "source": [ + "program = provider.runtime.program('circuit-runner')\n", + "print(program)" + ] + }, + { + "cell_type": "markdown", + "id": "19df4562", + "metadata": {}, + "source": [ + "To run the program, we need the custom circuit runner results class, `RunnerResult`, from the IBM Quantum package:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "204aad90", + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit.providers.ibmq import RunnerResult\n", + "\n", + "# Specify the program inputs here.\n", + "program_inputs = {\n", + " 'circuits': qc,\n", + " 'shots': 2048,\n", + " 'optimization_level': 0,\n", + " 'initial_layout': [0,1,4,7,10,12],\n", + " 'measurement_error_mitigation': False\n", + "}\n", + "# Specify the backend.\n", + "options = {'backend_name': backend.name()}\n", + "\n", + "# Send circuits to the cloud for execution by the circuit-runner program.\n", + "job2 = provider.runtime.run(program_id=\"circuit-runner\",\n", + " options=options,\n", + " inputs=program_inputs,\n", + " result_decoder=RunnerResult\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "9955d4d3", + "metadata": {}, + "outputs": [], + "source": [ + "res2 = job2.result()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "55c59d81", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.794535071278472" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hellinger_fidelity(res2.get_counts(), exact_dist)" + ] + }, + { + "cell_type": "markdown", + "id": "b35132ff", + "metadata": {}, + "source": [ + "## Measurement error mitigation in the Cloud\n", + "\n", + "One of the unique options for the Qiskit Runtime circuit-runner is the ability to correct for measurement errors automatically in the cloud. To enable this, just set `measurement_error_mitigation=True` in the `run_circuits` method or `measurement_error_mitigation: True` in the `program_inputs` for the `circuit-runner` program." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "b2c1990e", + "metadata": {}, + "outputs": [], + "source": [ + "job3 = provider.run_circuits(qc, backend, shots=2048, initial_layout=[0,1,4,7,10,12], \n", + " measurement_error_mitigation=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "e0b476e8", + "metadata": {}, + "outputs": [], + "source": [ + "res3 = job3.result()" + ] + }, + { + "cell_type": "markdown", + "id": "adb9712b", + "metadata": {}, + "source": [ + "The mitigated results are returned as quasiprobabilities; a distribution that may contain negative values but nevertheless sums to one. These can be accessed similar to counts, using the `get_quasiprobabilities` method:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "fb8676ac", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'000000': 0.015020697020227713,\n", + " '000001': 0.01503968527760154,\n", + " '000010': -0.00012627678196427706,\n", + " '000011': 0.030574525060850996,\n", + " '000100': 0.000659834155234161,\n", + " '000101': -0.001063355958793566,\n", + " '000110': -0.0004775999777916211,\n", + " '000111': 0.06031549309142881,\n", + " '001000': 0.0003777367742875846,\n", + " '001001': -0.00018911714421758157,\n", + " '001011': -0.0005497404302188685,\n", + " '001111': 0.002569807761895156,\n", + " '010000': 0.0009733012442038954,\n", + " '010001': 0.0011091309301552864,\n", + " '010011': 0.0028983164662437686,\n", + " '010101': 0.0006953842032121027,\n", + " '010111': 0.0062289131505383925,\n", + " '011011': 0.0005211534611385364,\n", + " '011101': 1.8085574676836907e-05,\n", + " '011110': 0.00010457485981474027,\n", + " '011111': 0.009008930948687141,\n", + " '100000': 0.020757733894230336,\n", + " '100001': 0.013550397423734102,\n", + " '100010': 0.00022053106121490537,\n", + " '100011': 0.038644305848702515,\n", + " '100100': 0.0003334666950873213,\n", + " '100101': -6.858956101837708e-05,\n", + " '100110': -0.0005943967601188724,\n", + " '100111': 0.05970117602149442,\n", + " '101000': -0.0004924095356996335,\n", + " '101001': 0.0008826058626688284,\n", + " '101011': -0.0013611362001075304,\n", + " '101101': 0.001737025111214627,\n", + " '101110': 0.00023664577533407804,\n", + " '101111': 0.0011200992316059554,\n", + " '110000': 0.03567791385094045,\n", + " '110001': 0.030199327570111845,\n", + " '110010': 0.0012285103619610381,\n", + " '110011': 0.07161663042226933,\n", + " '110100': 0.00021431875863761756,\n", + " '110101': 0.0019190227680874988,\n", + " '110110': 0.0006237863586354301,\n", + " '110111': 0.12786449020370413,\n", + " '111000': 0.0020301695201953808,\n", + " '111001': 0.0029065130997377527,\n", + " '111010': 0.00025079508226566666,\n", + " '111011': 0.022803871645841876,\n", + " '111100': 0.006655111831611918,\n", + " '111101': 0.0024129361534944403,\n", + " '111110': 0.002880228496461319,\n", + " '111111': 0.4123394393204907}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "quasi = res3.get_quasiprobabilities()\n", + "quasi_binary = quasi.binary_probabilities()\n", + "quasi_binary" + ] + }, + { + "cell_type": "markdown", + "id": "240bc428", + "metadata": {}, + "source": [ + "Quasiprobabilities can be directly used to compute things like expectation values:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "fd8b7452", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Raw expectation value: 0.2158203125\n", + "Mitigated expectation value: 0.32066344764961213\n", + "Exact expectation value: 0.4374999999999999\n" + ] + } + ], + "source": [ + "print(\"Raw expectation value:\", expectation_value(res3.get_counts())[0])\n", + "print(\"Mitigated expectation value:\", expectation_value(quasi_binary)[0])\n", + "print(\"Exact expectation value:\", expectation_value(exact_dist)[0])" + ] + }, + { + "cell_type": "markdown", + "id": "5d22a386", + "metadata": {}, + "source": [ + "It is also possible to compute the closest true probability distribution (in terms of the Euclidean norm):" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c5daccf0", + "metadata": {}, + "outputs": [], + "source": [ + "nearest_probs = quasi.nearest_probability_distribution().binary_probabilities()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "e5c7f20e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA/AAAAGSCAYAAABE/Cp2AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Z1A+gAAAACXBIWXMAAAsTAAALEwEAmpwYAACjhklEQVR4nOzdd3gVVf7H8fdJbiohJAQCpFCS0EFaEAuGSLdhQ2QtWEAXsKy6LrIrUuw/dRUVXNe22FHRFWwsyAqBFemggIYWhNANJYT05Pz+uMk1N7kBAqnweT3PfZI7d84958zMnZnvnDNnjLUWEREREREREandvGq6ACIiIiIiIiJyYgrgRUREREREROoABfAiIiIiIiIidYACeBEREREREZE6QAG8iIiIiIiISB2gAF5ERERERESkDnDUdAGqSqNGjWzLli1ruhgiIiIiIiI1YtWqVb9ZaxufYJ5wh8PxBtAJNfDWtEJgfX5+/qgePXrs9zTDGRvAt2zZkpUrV9Z0MURERERERGqEMebXE83jcDjeaNq0afvGjRsf8vLystVRLvGssLDQHDhwoMPevXvfAIZ4mkdXWERERERERM5enRo3bpyu4L3meXl52caNGx/B2RvC8zzVWB4RERERERGpXbwUvNceReui3DhdAbyIiIiIiIhIHaAAXkRERERERKQOOGMHsRMREREREZGKuWMqPary+1+/j1VV+f1nOrXAi4iIiIiISK2Wl5dX00WoFRTAi4iIiIiISK0TGRnZ+eGHH27apk2bDvXq1es+bty4ZtHR0Z3q1avXLTY2tuM777wTUjxvRERE58WLFwcC/OMf/2hojOmxcuVKf4AXXnihUf/+/WNrqBqVSgG8iIiIiIiI1Eqffvppw6+//nrzwYMH17Rr1y578eLFyenp6WvGjx+/+49//GOrX3/91QegV69eR7/99tv6AElJSfWjoqJyFixYUPw+6KKLLjpak/WoLArgRUREREREpFYaPXr0vri4uLygoCB7++23H2rZsmWet7c3d9xxx6EWLVrkLF68uB5AQkJCxuLFi+sDLFu2LOj+++/fW+J9/X79+mXUZD0qiwJ4ERERERERqZVatGjhuvl92rRpYe3atetQv379rvXr1++6ZcuWgAMHDjgABgwYcHTlypVBv/76q09hYaG59dZbD65cuTIoOTnZ9+jRo97nn39+Zs3VovJoFHoRERERERGplYwxFmDTpk2+DzzwQIsvvvhiU79+/TIcDgft2rXrYK0FoFOnTjn+/v6Fzz77bHivXr2ONmzYsLBRo0Z5L7/8cuP4+PgMb2/vGq1HZVELvIiIiIiIiNRqR48e9TLG0LRp0zyAF198MWzLli0BJefp1avX0RkzZoQnJCQcBbjwwguPzpgxI7x3795nxP3voABeREREREREarkePXpk33nnnfsSEhLaN27cuMtPP/0U0K1bN7f72hMSEo4eO3bMa8CAARkAiYmJR48dO+Z18cUXnxH3vwOY4i4HZ5r4+Hi7cuXKmi6GiIiIiIhIjTDGrLLWxh9vnnXr1m3v0qXLb9VVJjmxdevWNerSpUtLT5+pBV5ERERERESkDlAALyIiIiIiIlIHKIAXERERERERqQMUwIuIiIiIiIjUAQrgRUREREREROoABfAiIiIiIiIidYACeBEREREREZE6QAG8iIiIiIiISB2gAF5ERERERETqrM2bN/sGBgZ2y8/PL3eewMDAbhs3bvStxmJ5lJyc7GuM6ZGXl3dK6R2VXB4RERERERGpo7a2ndyjKr8/Nnnyqsr+ztatW+dmZmauKX5/7rnnth0+fHjaAw888FvxtJKfn44HHnggYuvWrX6zZ89OqYzvqyi1wIuIiIiIiIjUAQrgRUREREREpNaJjIzs/MgjjzRp06ZNh4CAgG7Dhg1rsXPnTkdCQkLrevXqdbvgggvaHDhwwLtkt/R77rknctWqVUF//etfmwcGBnYbMWJEcwBjTI/169f7Aezdu9e7b9++cUFBQd06derU/t57743o0aNH2+J8b7vttuimTZueExQU1K1jx47t586dGwQwa9as4JdffrnpV199FRoYGNitbdu2HQDS0tK8hw0b1qJx48bnhIeHn3PvvfdGFHfnz8/P584774wKDQ3tEhUV1fmzzz5rcDrLRAG8iIiIiIiI1Epz5swJXbBgwaaNGzeu//bbb0MGDRrU+qmnnko9cODA2sLCQp5++unwkvO//PLLu3r06JHx1FNP7cjMzFzzzjvv7Cj9naNGjWoRGBhYuGfPnnVvv/12yscff9yo5Oc9e/Y8tnbt2g2HDh1aM3To0IM33XRTbGZmphk6dGj6Pffcs/eyyy47lJmZuSY5OXkjwPDhw1s6HA62bt26fs2aNRu/++67Bi+88EIjgOeff77x/PnzG6xYsWLjqlWrNn7++eehp7M8FMCLiIiIiIhIrTR69Oj90dHR+a1atcrr2bNnRrdu3Y5deOGFWYGBgfaKK644vG7dusCKfF9+fj5z584NeeKJJ3bVr1+/sEePHtnDhg37reQ8Y8eOPdi0adMCHx8fpkyZsi83N9esW7fO39P37dy507Fo0aIGr7322o7g4ODCyMjI/LvvvnvfrFmzGgJ89tlnoWPGjNkfFxeX16RJk4KHHnpo76kvDQ1iJyIiIiIiIrVUs2bNXMO1+/v7FzZp0sQ11HxAQEBhZmamd0W+b/fu3Y6CggITExPj+t7o6OjckvNMnDixyXvvvdfowIEDPgDHjh3z3r9/v8fYecuWLb75+fmmWbNmXYqnWWtN06ZNcwH27dvn07x5c9f3x8bG5lSkvKWpBV5ERERERM56c+fOpW3btsTFxfH000+XO9+nn36KMYaVK1cCkJaWxsUXX0xQUBB33323a77MzEwuu+wy2rVrR8eOHRk/fnyV10GcjDG2vM8iIiLyvb29bUpKik/xtJ07d7oeLzd37tygadOmNZ05c+a2w4cPrz169OjaoKCgAmutx++OiYnJ8/X1tQcPHlx79OjRtUePHl2bkZGxZsuWLRsAwsPD83bs2OH6/m3btvmdTt0UwIuIiIiIyFmtoKCAu+66i2+++YaNGzfy4YcfsnHjxjLzHT16lBdffJFevXq5pvn7+/PYY4/x3HPPlZn/wQcf5JdffmHNmjX873//45tvvqnSeohT48aN88sLlB0OB4MGDTr88MMPRxw9etRrzZo1/p988klY8edHjhzxdjgctmnTpnl5eXnmwQcfbHbs2DFXK3+TJk3yU1NTfQsKCgBo0aJF3oUXXnjkzjvvjD548KBXQUEBGzZs8Pvqq6+CAK655ppD//znP8O3bt3qc+DAAe9nnnmm6enUTQG8iIiIiIic1ZYvX05cXBwxMTH4+voyfPhwZs+eXWa+Rx55hIceegh//99vh65Xrx69e/d2mwYQGBjIxRdfDICvry/du3cnNTW1aisiANx33337vvzyy9Dg4OCut956a3Tpz19//fUdR48e9W7WrFmXm2++udVVV1110NfX1wJce+21R/r06ZPeoUOHztHR0Z39/f0Li7vDA4wYMeIgQGhoaNcOHTq0B/j444+35+bmmvbt23cKCQnpOnTo0Nhdu3b5ADzwwAMHEhMT03v06NGxa9euHYYMGXLodOpmirsCnGni4+NtcbcWERERERGR8syaNYu5c+fyxhtvAPDuu++ybNkypk2b5ppn9erVPPHEE3z66ackJiby3HPPER8f7/p8xowZrFy50i1NscOHD9O9e3e+/fZbYmJiqr5CRYwxq6y18cebZ926ddu7dOny2/HmOdONGTMmct++fT6fffbZ9pouC8C6desadenSpaWnz9QCLyIiIiIichyFhYU88MAD/P3vf69w2vz8fP7whz9w7733VmvwLuVbs2aN/7JlywIKCwv57rvvAmfOnNno6quvPlzT5ToZGoVeRERERETOapGRkezcudP1PjU1lcjISNf7o0ePsn79ehITEwHYu3cvQ4YMYc6cOW6t8J7ceeedtG7dmvvuu68qii6nID093evmm2+OOXDggE/Dhg3zR48eve/GG288XNPlOhkK4EVERERE5KzWs2dPNm/eTEpKCpGRkcycOZMPPvjA9XmDBg347bffe5l76kLvyYQJEzhy5Iira77UDn369MncsWPH+poux6lQAC8iIiIiImc1h8PBtGnTGDRoEAUFBdx+++107NiRiRMnEh8fz5AhQ46bvmXLlqSnp5Obm8vnn3/OvHnzCA4O5oknnqBdu3Z0794dgLvvvptRo0ZVR5XkDKVB7ERERERERM5AJzmI3bbOnTsf8vLyOjMDwzqmsLDQ/PTTT6FdunTxOGCCBrETERERERE5e60/cOBAg8LCQlPTBTnbFRYWmgMHDjQAyu3ery70IiIiIiIiZ6n8/PxRe/fufWPv3r2dUANvTSsE1ufn55d7n4UCeBERERERkbNUjx499gPHv8lfag1dYRERERERERGpAxTAi4iIiIiIiNQBCuBFRERERERE6gAF8CIiIiIiIiJ1gAJ4ERERERERkTpAAbyIiIiIiIhIHaAAXkRERERERKQOUAAvIiIiIiIiUgcogBcRERERERGpAxTAi4iIiIiIiNQBCuBFRERERERE6gBHTRdARERERESkrtjadnKZabHJZaeJVAW1wIuIiIiIiIjUAQrgRUREREREROoABfAiIiIiIiIidUC1B/DGmLHGmBRjTLYxZpUx5qKTTNfbGJNvjFlf1WUUERERERERqW2qNYA3xlwPvAg8CXQDvge+McY0P0G6UOAdYEGVF1JERERERESkFqruFvgHgBnW2tettT9ba+8B9gBjTpDuTeBtYGlVF1BERERERESkNqq2AN4Y4wv0AOaV+mgecMFx0o0FmgCPV13pRERERERERGq36nwOfCPAG9hXavo+oL+nBMaYzsAk4DxrbYEx5rgZGGPuBO4EiIiIYOHChQDExMRQv3591q1bB0BYWBgdO3YkKSkJAIfDQe/evVm9ejXp6ekAxMfHs2/fPnbu3AlA69at8fPzY/165y344eHhtGnThiVLlgDg5+fH+eefz8qVK8nIyACgV69epKamsmvXLgDatm2Lt7c3GzduBKBp06a0atWKpUudHQsCAgLo1asXy5YtIysrC4Dzzz+flJQU9u7dC0CHDh0oKCggOTkZgMjISKKioli2bBkAQUFBxMfHs3TpUnJycgDo3bs3mzZtYv/+/QB06tSJnJwcNm/eDEB0dDRNmjRh5cqVAAQHB9O9e3eWLFlCfn4+AAkJCWzYsIG0tDQAunTpwtGjR9m2bRsALVu2pGHDhqxevRqA0NBQunTpwqJFi7DWYoyhT58+rFu3jkOHDgHQvXt3Dh48yPbt27WetJ60nrSetJ60nrSetJ60nrSe6sR68qGshQsX1tr1JGcWY62tnoyMiQB2AX2stUklpk8EbrTWti01vx+wBnjKWvtu0bTJwFBrbacT5RcfH2+LN3wREREREZHKsLXt5DLTYpPLTqsNjDGrrLXxNV0OqTzV2QL/G1CAszt8SU2AvR7mbwa0B/5ljPlX0TQvwBhj8oFLrbWlu+OLiIiIiIiInJGq7R54a20usAoYUOqjAThHoy9tF9AZ6Fri9Sqwpeh/T2lEREREREREzkjV2QIP8DzwrjFmOfA/YDQQgTMwxxjzDoC1doS1Ng9we+a7MWY/kGOt1bPgRURERERE5KxSrQG8tfYjY0wYMAFnF/n1OLvC/1o0y3GfBy8iIiIiIiJytqruFnista8Ar5TzWeIJ0k4GJld6oURERERERERquWq7B15ERERERERETp0CeBEREREREZE6QAG8iIiIiIiISB2gAF5ERERERESkDlAALyIiIiIiIlIHKIAXERERERERqQMUwIuIiIiIiIjUAQrgRURERETkrPL444/j6+uLj48PgwcPLvP5DTfcgL+/PwEBAQQHBzNnzhy3z9dk7SRu0xTuSP3ANe2qq67C398ff39/WrRoweHDh6u6GnIWUgAvIiIiIiJnjdzcXKZMmcK8efM4dOgQixYtKhOgP/PMM2RnZ5OVlcU999zD7bff7vb53bs/oYl3fdf7lStX8tVXX7F7926ys7MpLCzkwQcfrJb6yNlFAbyIiIiIiJw1ZsyYQYMGDUhMTCQoKIiEhASmT5/uNk9UVJTr//T0dIwxrvfPHviWcEcQLXwauqWx1nLo0CGys7PJzc0lNja2aisiZyUF8CIiIiIictZITk4mLCzM9b5Vq1bs2bOnzHzDhg3Dx8eHf/zjH3zwgbOr/N69e3n38Apei7zBbd74+HiGDBlCXFwcgYGBBAQE8Ne//rVqKyJnJQXwIiIiIiIipXz88cfk5eXxxz/+kdGjRwNw+eWXM6xBNxo7gtzmTUlJ4bvvvmPjxo1kZGSQk5PDmDFjaqLYcoZz1HQBREREREREqkvbtm15++23Xe9TUlJo1qxZufO/+OKL+Pj4ALB582bWph/l3cPLKcBicN4P3/7ldBo3bkz79u0BuOKKK1i8eHGV1kPOTmqBFxERERGRs8aIESM4cuQISUlJZGRkkJSUxNixY93mmT9/vuv/KVOmEBgYCMCRI0dIbjOR5DYTOde/BYmBrZkWcR0dO3Zkx44d/PbbbxQWFrJgwQLatWtXrfWSs4MCeBEREREROWv4+/szYcIE+vfvT2hoKL179+bKK68kISGBhx9+GIC//OUvrsfITZs2zXUPfHlGjhxJr169iIyMJDAwEGstb731VnVUR84yxlpb02WoEvHx8XblypU1XQwRERERETmDbG07ucy02OSy02oDY8wqa218TZdDKo9a4EVERERERETqAAXwIiIiIiIiInWAAngRERERERGROkABvIiIiIiIiEgdoABeREREREREpA5QAC8iIiIiIiJSByiAFxEREREREakDFMCLiIiIiIiI1AEK4EVERERERETqAAXwIiIiIiIiInWAAngRERERERGROkABvIiIiIiIiEgd4KjpAoiIiIiIiNRGd0wtO218tZdC5HdqgRcRERERERGpAxTAi4iIiIiIiNQBCuBFRERERERE6gAF8CIiIiIiIiJ1gAJ4ERERERERkTpAAbyIiIiIiIhIHaAAXkRERERERKQOUAAvIiIiIiIiUgcogBcRERERERGpAxTAi4iIiIiIiNQBCuBFRERERERE6gAF8CIiIiIiIiJ1gAJ4ERERERERkTpAAbyIiIiIiIhIHaAAXkRERERERKQOUAAvIiIiIiIiUgcogBcRERERERGpAxTAi4iIiIiIiNQBCuBFRERERERE6gAF8CIiIiIiIiJ1gAJ4ERERERERkTpAAbyIiIiIiIhIHaAAXkRERERERKQOUAAvIiIiIiIiUgcogBcRERERERGpAyoUwBtjvIwxXiXeNzXGjDLGXFj5RRMRERERERGRYhVtgf8KuAfAGBMErASeBRYaY0aczBcYY8YaY1KMMdnGmFXGmIuOM28fY8z3xpg0Y0yWMeYXY8yDFSyziIiIiIiISJ1X0QA+Hvhv0f/XAOlAOHAHcMLA2hhzPfAi8CTQDfge+MYY07ycJBnAS0AC0AF4HJhijBlbwXKLiIiIiIiI1GkVDeCDgMNF/w8E/m2tzcMZ1MeeRPoHgBnW2tettT9ba+8B9gBjPM1srV1lrZ1prd1grU2x1r4H/Acot9VeRERERERE5ExU0QB+B3ChMaYeMAiYXzS9IZB5vITGGF+gBzCv1EfzgAtOJnNjTLeieRdVoMwiIiIiIiIidZ6jgvM/D7yLs2v7r0BS0fQE4KcTpG0EeAP7Sk3fB/Q/XkJjTCrQuKi8U6y1r5Yz353AnQAREREsXLgQgJiYGOrXr8+6desACAsLo2PHjiQlOYvvcDjo3bs3q1evJj09HYD4+Hj27dvHzp07AWjdujV+fn6sX78egPDwcNq0acOSJUsA8PPz4/zzz2flypVkZGQA0KtXL1JTU9m1axcAbdu2xdvbm40bNwLQtGlTWrVqxdKlSwEICAigV69eLFu2jKysLADOP/98UlJS2Lt3LwAdOnSgoKCA5ORkACIjI4mKimLZsmUABAUFER8fz9KlS8nJyQGgd+/ebNq0if379wPQqVMncnJy2Lx5MwDR0dE0adKElStXAhAcHEz37t1ZsmQJ+fn5ACQkJLBhwwbS0tIA6NKlC0ePHmXbtm0AtGzZkoYNG7J69WoAQkND6dKlC4sWLcJaizGGPn36sG7dOg4dOgRA9+7dOXjwINu3b9d60nrSetJ60nrSetJ60nrSetJ6qnXr6WQtXLiw1q4nObMYa23FEhgTD0QD8621GUXTLgMOW2v/d5x0EcAuoI+1NqnE9InAjdbatsdJ2wpn9/3zgP8D/mStffd45YyPj7fFG76IiIiIiEhF3TG17LTx/5hcZlpsctlptYExZpW1Nr6myyGVp6It8FhrV+Icfb7ktK9OIulvQAHQpNT0JsDeE+SZUvTvT8aYJsBknD0BRERERERERM4KFb0HvvgxcBuMMZnGmJiiaQ8ZY4YdL521NhdYBQwo9dEAnKPRnywvwK8iZRYRERERERGp6yoUwBtj7gMmAK8BpsRHu4G7T+IrngduNcaMMsa0N8a8CEQArxZ9/zvGmHdK5HePMeZyY0zrotdInI+re68i5RYRERERERGp6yrahX40cIe19itjzOMlpq8GOp4osbX2I2NMGM6LAM2A9cCl1tpfi2Yp/Tx4b5z3vLcE8oGtwHiKAn4RERERERGRs0VFA/gWOIPu0vKAgJP5AmvtK8Ar5XyWWOr9VGBqRQooIiIiIiIiciaq6D3w24DuHqZfCmw8/eKIiIiIiIiIiCcVbYF/DphmjAnEeQ/8+caYm4FxwO2VXTgRERERERERcapQAG+t/ZcxxgE8CQTifJTbbuBea+1HVVA+EREREREREeHUngP/OvC6MaYR4GWt3V/5xRIRERERERGRkiocwBez1v5WmQURERERERERkfKdMIA3xvwI9LHWHjLG/ATY8ua11p5TmYUTEREREREREaeTaYH/FMgp8X+5AbyIiIiIiIiIVI0TBvDW2ikl/p9cpaUREREREREREY8q9Bx4Y8x/jTEhHqYHG2P+W2mlEhERERERERE3FQrggUTA18N0f+Ci0y6NiIiIiIiIiHh0UqPQG2O6l3h7jjHmYIn33sAgYFdlFkxEREREREREfneyj5FbiXPwOgvM8/B5FnBPZRVKRERERERERNydbADfCjDANuBc4ECJz3KB/dbagkoum4iIiIiIiIgUOakA3lr7a9G/Fb1nXkREREREREQqwQkDeGPMNcAX1tq8ov/LZa39rNJKJiIiIiIiIiIuJ9MCPwtoCuwv+r88FueAdiIiIiIiIiJSyU4YwFtrvTz9LyIiIiIiIiLVRwG5iIiIiIiISB1wsvfAnxTdAy8iIiIiIiJSNU72HviToXvgRURERERERKpIhe6BFxEREREREZGaoeBcREREREREpA7Qc+BFRERERERE6gA9B15ERERERESkDtBz4EVERERERETqAAXkIiIiIiIiInVAhQN4Y0x3Y8w7xpiVRa93jTHdq6JwIiIiIiIiIuJUoQDeGHMjsAJoBnxd9GoCLDfG3FT5xRMREREREREROLlB7Ep6AnjEWvtkyYnGmL8CjwPvVVbBREREREREROR3Fe1C3xj42MP0T4Dw0y+OiIiIiIiIiHhS0QD+OyDRw/REYNHpFkZEREREREREPDthF3pjzDUl3n4DPGWMiQd+KJp2HnANMLnSSyciIiIiIiIiwMndAz/Lw7Q7i14lvQy8ctolEhEREREREZEyThjAW2v1rHgRERERERGRGqbgXERERERERKQOqOhj5DDGhAKXAM0B35KfWWsfraRyiYiIiIiIiEgJFQrgjTHnAV8BOTgfKbcLaFb0fjugAF5ERERERESkClS0C/2zwPtAJJAN9MXZEr8S+L/KLZqIiIiIiIiIFKtoAH8OMM1aa4ECwM9auw94CD1GTkRERERERKTKVDSAzy3x/z6gRdH/GUBEpZRIRERERERERMqo6CB2q4GewCZgIfC4MaYJcBPwY+UWTURERERERESKVbQF/mFgd9H/E4ADwMtAKHBnJZZLREREREREREqoUAu8tXZlif8P4HycnIiIiIiIiIhUsQo/Bx7AGBMLtC96u9Fau63yiiQiIiIiIiIipVX0OfBhwJvAEKDw98nmS+B2a21aJZdPRERERERERKj4PfBvAHHARYB/0SsBaAW8XrlFExEREREREZFiFe1CPwjoZ61dWmLa/4wxfwS+rbxiiYiIiIiIiEhJFW2BPwAc8zA9E1D3eREREREREZEqUtEA/lFgqjEmsnhC0f9/L/pMRERERERERKrACbvQG2N+AmyJSa2A7caYXUXvI4FsIBznPfIiIiIiIiIiUslO5h74WVVeChERERERERE5rhMG8NbaKdVREBEREREREREpX0VHoQfAGNMX6ICza/0Ga+3CyiyUiIiIiIiIiLirUABfNGDdv4EewO6iyRHGmJXA1dba3eUmFhEREREREZFTVtFR6F8CCoA4a220tTYaaF007aXKLpyIiIiIiIiIOFU0gB8A3GWtTSmeYK3dBtxb9NkJGWPGGmNSjDHZxphVxpiLjjPvNcaYecaYA8aYo8aYZcaYIRUss4iIiIiIiEidV9EAHtwfKXe8aWUYY64HXgSeBLoB3wPfGGOal5OkD/Bf4LKi+b8G/n28oF9ERERERETkTFTRAH4B8LIxJrp4QlHwPbXosxN5AJhhrX3dWvuztfYeYA8wxtPM1to/WWufttYut9ZuKRoRfxVwVQXLLSIiIiIiIlKnVTSAvxeoB2wzxvxqjPkV2Fo07d7jJTTG+OIc/G5eqY/mARdUoAz1gUMVmF9ERERERESkzqvoY+TSgHOBRKBd0bSfrbXfnkTaRoA3sK/U9H1A/5PJ3BhzFxAFvFvO53cCdwJERESwcOFCAGJiYqhfvz7r1q0DICwsjI4dO5KUlASAw+Ggd+/erF69mvT0dADi4+PZt28fO3fuBKB169b4+fmxfv16AMLDw2nTpg1LliwBwM/Pj/PPP5+VK1eSkZEBQK9evUhNTWXXrl0AtG3bFm9vbzZu3AhA06ZNadWqFUuXLgUgICCAXr16sWzZMrKysgA4//zzSUlJYe/evQB06NCBgoICkpOTAYiMjCQqKoply5YBEBQURHx8PEuXLiUnJweA3r17s2nTJvbv3w9Ap06dyMnJYfPmzQBER0fTpEkTVq5cCUBwcDDdu3dnyZIl5OfnA5CQkMCGDRtIS0sDoEuXLhw9epRt27YB0LJlSxo2bMjq1asBCA0NpUuXLixatAhrLcYY+vTpw7p16zh0yHn9pXv37hw8eJDt27drPWk9aT1pPWk9aT1pPWk9aT1pPdW69XSyFi5cWGvXk5xZjLUndfs6xhhvIBvoYq3dWOGMjIkAdgF9rLVJJaZPBG601rY9QfprcQbu11trvzhRfvHx8bZ4wxcREREREamoO6aWnTb+H5PLTItNLjutNjDGrLLWxtd0OaTynHQXemttAfAr4HuKef2G83FzTUpNbwLsPV5CY8xQnMH7iJMJ3kVERERERETONBW9B/4x4GljTKOKZmStzcU5AF3px80NwDkavUfGmGE4g/dbrbWzKpqviIiIiIiIyJmgovfAPwi0AnYZY1KBYyU/tNaec4L0zwPvGmOWA/8DRgMRwKsAxph3ir5nRNH74TiD9weBJGNM06LvybXWHqxg2UVERERERETqrIoG8LNwPvPdnEpm1tqPjDFhwASgGbAeuNRa+2vRLKWfBz+6qIxTi17FFuEcSE9ERERERETkrHBSAbwxJhB4Fufz131wPvP9HmvtbxXN0Fr7CvBKOZ8lHu+9iIiIiIiIyNnqZO+BnwLcCnwFfIjzsW//qKIyiYiIiIiIiEgpJ9uF/hpgpLV2JoAx5n3gf8YY76LR6UVERERERESkCp1sC3w0sLj4jbV2OZCPcwA6EREREREREaliJxvAewO5pablU/FB8ERERERERETkFJxsAG6A94wxOSWm+QOvG2MyiydYa4dUZuFERERERERExOlkA/i3PUx7rzILIiIiIiIiIiLlO6kA3lp7W1UXRERERERERETKd7L3wIuIiIiIiIhIDVIALyIiIiIiIlIHKIAXERERERERqQMUwIuIiIiIiIjUAQrgRUREREREROoABfAiIiIiIiIidYACeBEREREREZE6QAG8iIiIiIiISB2gAF5ERERERESkDlAALyIiIiIiIlIHKIAXERERERERqQMUwIuIiIiIiIjUAQrgRUREREREROoABfAiIiIiIiIidYACeBEREREREZE6QAG8iIiIiIiISB2gAF5ERERERESkDlAALyIiIiIiIlIHKIAXERERESnl8ccfx9fXFx8fHwYPHlzm85dffpnAwECMMfz5z392Tf/f//5HYGAgAQEB+Pv7c8MNN7g+e++99/D398fHx4cuXbpQWFhYLXURkTOHAngRERERkRJyc3OZMmUK8+bN49ChQyxatIg5c+a4zdOtWzfeeecdYmJi3KZ36dKFvXv3kpWVxfbt2/n4449ZvXo1AKNHj2batGnk5OSwe/duHn/88Wqrk4icGRTAi4iIiIiUMGPGDBo0aEBiYiJBQUEkJCQwffp05s6dS9u2bYmLi2PJkiUMHToUY4wrXVJSEgkJCTRs2JBZs2Zx9OhRAJKTkznnnHPIzMzkpZde4pNPPmH48OF8+OGHLFiwgO7du9O1a1d69+7Nli1baqraIlIHKIAXERERESkhOTmZsLAw1/tWrVqxe/du7rrrLr755hs2btzIhx9+yMaNG93SNW/enBkzZjBw4EBuuOEG2rRpwzXXXEOPHj246aabCAkJYe7cudx3331ER0dz8OBBxowZw/vvv8/atWu54YYb1CovIselAF5ERERE5AQyMzOJi4sjJiYGX19fhg8fzuzZs93madmyJeeccw7h4eF88MEHrFq1iq+//prc3FyaNm0KQEREBOHh4WRkZABgjCE9PR2AI0eOEBERUb0VE5E6xVHTBRARERERqU3atm3L22+/7XqfkpJCUFAQ0dHRrmlRUVEsW7bsuN/TvXt3IiIi+Ne//sXNN9/MsWPHWL58Obm5uRw8eJCGDRvy2muvcemllxIQEEBwcDA//PBDldVLROo+tcCLiIiIiJQwYsQIjhw5QlJSEhkZGSQlJXkcid6TFStWkJOTAzgD/+3bt3PBBRfQtWtXvL29GTJkCG+++SYfffQRw4cP54UXXuDrr78mNTWV2267jQceeKAqqyYidZwCeBERERGREvz9/ZkwYQL9+/cnNDSU3r17c9VVV/Hpp5/y8MMPAzBnzhxeeeUVtm7dygsvvIC/vz8ACxYs4OOPP+aGG26gQ4cODBs2jGuvvZb09HTCw8NJS0ujT58+NGvWjNGjR7Nu3Tp69eoFwPXXX8/3339fY/UWkdpPAbyIiIiISCmTJk0iNzeXvLw85s+fT8+ePQkNDWXUqFHk5uayadMmfvrpJ6y1FBYWkp2dDcD48eO5+eab+eCDD8jKyuK9994jNzeXq6++mvvuu4+8vDzy8vL48ccfCQsL48iRI2zatAmA+fPn0759+5qstojUcroHXkRERETkBBwOB9OmTWPQoEEUFBRw++2307FjRyZOnEh8fDxDhgxhxYoVXH311Rw6dIgvvviCSZMmsWHDBj7++GOSkpJIS0tjxowZgPNRdV27duX111/n2muvxcvLi9DQUN56662araiI1GrGWlvTZagS8fHxduXKlTVdDBERERERqaPumFp22vh/TC4zLTa57LTawBizylobX9PlkMqjLvQiIiIiIiIidYACeBEREREREZE6QAG8iIiIiIiISB2gQexERERERE6Rp3ukX7+vukshImcLtcCLiIiIiIiI1AFqgRcRERERqURb204uM622jlIuInWLWuBFRERERERE6gAF8CIiIiIiIiJ1gAJ4ERERERERkTpAAbyIiIiIiIhIHaAAXkRERERERKQOUAAvIiIiIiIiUgcogBcRERERERGpAxTAi4iIiIiIiNQBCuBFRERERERE6gAF8CIiIiIiIiJ1gAJ4ERERERERkTpAAbyIiIiIiIhIHaAAXkRERERERKQOqPYA3hgz1hiTYozJNsasMsZcdJx5mxljPjDG/GKMKTDGzKjGooqIiIiIiIjUGtUawBtjrgdeBJ4EugHfA98YY5qXk8QP+A14GlhWLYUUERERERERqYWquwX+AWCGtfZ1a+3P1tp7gD3AGE8zW2u3W2vvtdbOAA5WYzlFREREREREapVqC+CNMb5AD2BeqY/mARdUVzlERERE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IiIicrIUwIuU4umEFnRSKyIiIiIiNUvPgRcRkbPS3Llzadu2LXFxcTz99NNlPs/JyeH6668nLi6OXr16sX37dgCWL19O165d6dq1K126dOHf//63K82LL75Ip06d6NixI1OnTq2mmoiIiMjZQgG8iIic8R5//HF8fX3x8fFh8ODBFBQUcNddd/HNN9+wceNG3n//fZo1a4aPjw9BQUEsWbKEN998k/3797Nr1y7WrFlDbGwsDz30EJ06deLxxx8nOTmZ5ORkrrnmGowx9OvXj9dff53ly5ezbt06vvzyS7Zs2VLTVRcREZEziAJ4ERE5o+Xm5jJlyhTmzZvHoUOHWLRoEX//+9+Ji4sjJiYGX19fjDEUFBSQl5fH7bffzg033MDs2bMZN24chw4dIjMzk+DgYJ555hm8vLy4/PLLycrK4ueffyY8PBxjDImJifTq1YvAwEAcDgd9+vThs88+q+nqSx1SFb1CDh8+zNChQ2nXrh3t27dn6dKl1VUdqWNKX+gsLT09nebNm7td6AT417/+RUBAgOv10EMPudI4HA78/f0JCAigXr161VYXkTOZAngRETmjzZgxgwYNGpCYmEhQUBAJCQm8//77REdHu+ZJSUmhY8eOADzzzDOkpqaya9cuOnfujL+/Pw6Hg8DAQNf8y5Yto2PHjnTu3JkBAwYQFBTE0KFDWbx4MWlpaWRmZvL111+zc+fOaq+v1E2le4V8+OGHbNy40W2eN998k9DQULZs2cL999/vCpQ6derEypUrWbt2LXPnzuWPf/wj+fn5APzpT39i8ODB/PLLL6xbt4727dtXe92k9vN0oXPOnDlu89x5550EBQW5XegEuOKKKzh06BBZWVksXbqUZ599luzsbFe6devWkZWVxbFjx6q1TiJnKgXwIiJyRktOTiYsLMz1vlWrVhw+fNhtnuzsbBo3bgyAv78/3t7e5ObmAs6gyd/fn927d3P33Xfj7+9Pr1692LBhAytWrGDWrFn079+f9u3b89BDDzFw4EAGDx5M165d8fb2rrZ6St22fPlyt14hw4cPZ/bs2W7zzJ49m1tuuQWAoUOHsmDBAqy1rl4f4NyWjTEAHDlyhKSkJEaOHAmAr68vISEh1VcpqTM8XeicPn262zzffvst9913H/D7hc7CwkIaNWqEv78/4GylF5GqpQBe3FRF970TfaeISHVz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4Yf4+voSExPD0KFDWbBgAaGhoSxYsIDhw4e7uuFt376dSy+9FGMMU6dOdbWsJycnc9ttt9G3b1/q16+PMYbU1FS+/PJLV0vabbfd5joBKdmSlpubyyOPPOJqLfnuu+9cJ0GvvPIKd999N++99x7e3t788MMPWGtZvXo1a9asYd++fSxatKjM76Hkstq2bRtffvklc+bMqdLt6r///a9bV/aZM2eWuSDYotMQNi93dmWfNWuW6+LOkCFDmDlzJjk5OaSkpLB582bOPfdcALKO7gcg49AOtv/4GUPqdy63jJWpNuzvPv30U4+/Bfh9f5efn1+hfWVVbBuno2S+AwcOZMqUKTz++OO0bNmSefPmMWLEiCpfVp7S3nDDDdx111389a9/ZcaMGRQUFHjsvnvkyBGsta7uu5MnT2bdunV8++23vPnmm2zbto2IiAi++OILFixYQPv27dmyZQvJycn87W9/Y+PGjWWW84mWe/GFg7i4OFfgUay4pbR47IxiSR/eznuPhPPp/53e2DWnE1iW17r73nvv4evrS8uWLXnrrbf48ssv+XX9HLcyn87vaOvWrWRlZbl+CytXrmTv3r38/e9/57nnniMnJwdjDElJSW69zGbNmkV+fj5+fn5MmjSJRx99lICAAJYvX87//vc/rLU8+uijGGNo1KgRo0eP5pNPPqGgoIDvv/+eDz74gO7du/P9999zxx13UFhY6DqeFN+2mJmZSWFhYZnHVHpqkX738DK34++GDRsYNmwY4H4xet++fQQGBrqCcF9fX1atWuV6ctF//vMf19hI3t7evPDCCzgcDlq2bMmxY8cICQnxeBy8/vrrmT59Oo0aNXI9brPkcfDnn3/GGFNt50nFGjRoQG5uLklJSVhrycrKomHDhixatAhrLdOmTcPHx4djx45x7bXXMm7cOCIiIvjss89o1aqVa0DU22+/ndzcXBo2bEhubi6ff/656ze0detWHnnkEZ588kmMMXTu3JmCggJ+/PFH17JKS0tzXZQODw9nwIABtG7dml69erEvZQk+fkEAFBbkUViQhx7uKDWqZMvFmfTq0aOHrY1GvVD2taXNpDKvyk6bk5NjHQ6H/e677+zRo0etv7+//eyzz2xMTIzdunWrzcnJseecc47dsGHDCfO9cNg/bYhXgCu/3oExtndgjFuasLAw+89//tNaa21WVpY1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L/Xq1WPUqFHk5uYye/Zsvv/+e7eTniFDhriu9D/88MNcffXV7N69m3vvvZfly5dz+PBhNm7cSL169QgICMBaS8+ePQkKCuLPf/4zkydPpqCggDVr1jB37lzy8/NJTEwkIiKCzp07Ex0dzVVXXVXu9nHo0CEuvvhiFixYADhHl77xxhuZMWMGGRkZPPTQQ+zbt4+//vWvfPDBByQmJroeM5aSkkKPHj3YsmULEydOpGPHjtx+++1ERkbSsmVLvv76a7799lsaN27M9u3bOeecc1xdKWNiYk7593Cqz4Iu7/dQclkVd1+sq7+HmlBb9nfl/RZK5uvl5VWj28bpKJ2vj48PhYWF/Pjjj67fQteuXd3yrYpl5Slt//79ycjIcN3CApTbfTcpKYl33nmHzp07k56ezqRJk5g5cyadOnUiPDycoKAgzj33XNq0acOvv/5K+/bteeCBBzwu5+LbgI633E/laQ6V5XQCy/J6Oe3YscO1TbZr185jmSv7vKFnz55kZGS48vPx8fF4i9PMmTOZMGECAwcO5NChQ1x00UWMHz+eJ554goceeoiUlBT8/f3Jy8vD4XDw2muvcfnll/PAAw9w5ZVX0qVLF9577z369+/PI488wt69ewFo3749AQEBRERE0KNHD3pc8qhb3p5apEc2vICkpCTuuusucnNzsda6nn5Q+mL0xRdf7Bocb8qUKQwdOpSwsDDef/99/Pz8OHbsGImJifztb3/j3HPP5cILL+SDDz4o9zhYskfYe++9x/79+yv1OFjZevbsecqDql599dU0aNCAgQMH0rp1a3x8fBg+fDgrv36Eb14dSEiT9lx88wfkZKYRFxfn1nuu9LKaPn063t7eZKbv4evpF/PpM+cw+4WeRLYdQN+gNmXKLVJtqvume2AskAJkA6uAi04wf5+i+bKBbcDok8lHg9i5y8rKsg6Hwy5atMg1yNGnn35qW7VqZbdt2+YarGT9+vUnle+9DftYB17WGy97YUAru6XNJHvRRRfZv/3tb9Zaaw8dOmQjIyOtw+Gw9erVs999953r+/r27WsB63A47COPPOLKt3nz5tbLy8sC1svLy954442nXObKrm91LevqSPvKK6+c1EBloaGh1hhjvb29bf/+/a211rZo0cKGhITYNm3a2I8//thGRkZab29vC7gGy+nSpYtt0qSJBSxgW7VqZc85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\n", + "text/plain": [ + "
" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plot_histogram([res3.get_counts(),nearest_probs], legend=['raw', 'mitigated'], figsize=(15,6))" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "32e72f51", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.9255895675305238" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hellinger_fidelity(nearest_probs, exact_dist)" + ] + }, + { + "cell_type": "markdown", + "id": "a59a1f7c", + "metadata": {}, + "source": [ + "### Additional information on measurement mitigation\n", + "\n", + "From the results object, it is also possible to determine the execution time of the mitigation process (not including calibration time), on the per experiment level:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "6ffd5b04", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.008146638981997967" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "res3.results[0].header.measurement_mitigation_time" + ] + }, + { + "cell_type": "markdown", + "id": "c1973f47", + "metadata": {}, + "source": [ + "It is also possible to view the final measurment mapping that shows which physical qubit measurements correspond to classical bit values:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "1251442e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'0': 0, '1': 1, '4': 2, '7': 3, '10': 4, '12': 5}" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "res3.results[0].header.final_measurement_mapping" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8f2a61e", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.9.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/02_uploading_program.ipynb b/tutorials/02_uploading_program.ipynb new file mode 100644 index 000000000..34e647007 --- /dev/null +++ b/tutorials/02_uploading_program.ipynb @@ -0,0 +1,654 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "55f13cc7", + "metadata": {}, + "source": [ + "# Uploading a Qiskit runtime program" + ] + }, + { + "cell_type": "markdown", + "id": "4ff8d2da", + "metadata": {}, + "source": [ + "
\n", + "Note: Qiskit Runtime allows authorized users to upload runtime programs. Access to the Qiskit Runtime service may not mean you have access to upload a runtime program.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "2077b996", + "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 passing in just its parameters. Runtime programs are private by default, which means only you can see and access your programs. Some authorized users can also mark their programs as public, making them visible and accessible by everyone." + ] + }, + { + "cell_type": "markdown", + "id": "cf42076e", + "metadata": {}, + "source": [ + "## Constructing a runtime program" + ] + }, + { + "cell_type": "markdown", + "id": "e282eccc", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1206f612", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import json\n", + "\n", + "from qiskit.providers.ibmq.runtime import UserMessenger, ProgramBackend\n", + "\n", + "\n", + "def program(backend: ProgramBackend, user_messenger: UserMessenger, **kwargs):\n", + " \"\"\"Function that does classical-quantum calculation.\"\"\"\n", + " # UserMessenger can be used to publish interim results.\n", + " user_messenger.publish(\"This is an interim result.\")\n", + " return \"final result\"\n", + "\n", + "\n", + "def main(backend: ProgramBackend, user_messenger: UserMessenger, **kwargs):\n", + " \"\"\"This is the main entry point of a runtime program.\n", + "\n", + " The name of this method must not change. It also must have ``backend``\n", + " and ``user_messenger`` as the first two positional arguments.\n", + "\n", + " Args:\n", + " backend: Backend for the circuits to run on.\n", + " user_messenger: Used to communicate with the program user.\n", + " kwargs: User inputs.\n", + " \"\"\"\n", + " # Massage the input if necessary.\n", + " result = program(backend, user_messenger, **kwargs)\n", + " # Final result can be directly returned\n", + " return result\n" + ] + }, + { + "cell_type": "markdown", + "id": "bb4cfa8b", + "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()`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.ProgramBackend.run.html#qiskit.providers.ibmq.runtime.ProgramBackend.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()`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.UserMessenger.publish.html#qiskit.providers.ibmq.runtime.UserMessenger.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." + ] + }, + { + "cell_type": "markdown", + "id": "705ccef3", + "metadata": {}, + "source": [ + "There are several runtime programs in the `qiskit_runtime` directory in this repository. `qiskit_runtime/hello_world/hello_world.py` is one of them. It is a sample runtime program that submits random circuits for user-specified iterations:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "181916df", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"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", + " \"\"\"Generate a random circuit.\n", + "\n", + " Args:\n", + " backend: Backend used for transpilation.\n", + "\n", + " Returns:\n", + " Generated circuit.\n", + " \"\"\"\n", + " circuit = random_circuit(num_qubits=5, depth=4, measure=True,\n", + " seed=random.randint(0, 1000))\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 \"All done!\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "1281b655", + "metadata": {}, + "source": [ + "## Data serialization" + ] + }, + { + "cell_type": "markdown", + "id": "9edb1023", + "metadata": {}, + "source": [ + "Runtime programs live in the cloud, and JSON is the standard way of passing data to and from cloud services. Therefore, when a user invokes a runtime program, the input parameters must first be serialized into the JSON format and then deserialized once received by the server. By default, this serialization and deserialization is done automatically 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.\n" + ] + }, + { + "cell_type": "markdown", + "id": "8b4aeb2b", + "metadata": {}, + "source": [ + "### Custom classes" + ] + }, + { + "cell_type": "markdown", + "id": "1d7fbdf5", + "metadata": {}, + "source": [ + "`RuntimeEncoder` and `RuntimeDecoder` only support types commonly used in Qiskit, such as complex numbers and numpy arrays. If your program uses custom Python classes for input or output, these two methods only have partial support for that. \n", + "\n", + "Your custom class should have the following methods:\n", + "\n", + "- a `to_json()` method that returns a JSON string representation of the object\n", + "- a `from_json()` class method that accepts a JSON string and returns the corresponding object. \n", + "\n", + "When `RuntimeEncoder` serializes a Python object, it checks whether the object has a `to_json()` method. If so, it calls the method to serialize the object. `RuntimeDecoder`, however, does _not_ invoke `from_json()` to convert the data back because it doesn't know how to import your custom class. Therefore the deserialization needs to be done explicitly. " + ] + }, + { + "cell_type": "markdown", + "id": "c3e2d6e2", + "metadata": {}, + "source": [ + "Here is an example of serializing and deserializing a custom class. First we define the class `MyCustomClass`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ca9047fc", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "class MyCustomClass:\n", + " \n", + " def __init__(self, foo, bar):\n", + " self._foo = foo\n", + " self._bar = bar\n", + " \n", + " def to_json(self):\n", + " \"\"\"Convert this instance to a JSON string.\"\"\"\n", + " return json.dumps({\"foo\": self._foo, \"bar\": self._bar})\n", + " \n", + " @classmethod\n", + " def from_json(cls, json_str):\n", + " \"\"\"Return a MyCustomClass instance based on the input JSON string.\"\"\"\n", + " return cls(**json.loads(json_str))" + ] + }, + { + "cell_type": "markdown", + "id": "31f967a4", + "metadata": {}, + "source": [ + "Note that it has the `to_json()` method that converts a `MyCustomClass` instance to a JSON string, and a `from_json()` class method that converts a JSON string back to a `MyCustomClass` instance." + ] + }, + { + "cell_type": "markdown", + "id": "2fdf8941", + "metadata": {}, + "source": [ + "Here is how one would use `MyCustomClass` as an **input** to your program:" + ] + }, + { + "cell_type": "markdown", + "id": "258fcd02", + "metadata": {}, + "source": [ + "```\n", + "program_inputs = {\n", + " 'my_obj': MyCustomClass(\"my foo\", \"my bar\")\n", + "}\n", + "\n", + "options = {\"backend_name\": \"ibmq_qasm_simulator\"}\n", + "job = provider.runtime.run(program_id=\"some-program\",\n", + " options=options,\n", + " inputs=program_inputs\n", + " )\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "dc86356b", + "metadata": {}, + "source": [ + "Since `MyCustomClass` has a `to_json()` method, the method is automatically called to convert the instance to a JSON string when `provider.runtime.run()` is invoked. \n", + "\n", + "Your program can then use the `from_json()` method to restore the JSON string back to a `MyCustomClass` instance:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "681a1798", + "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)" + ] + }, + { + "cell_type": "markdown", + "id": "b71c40bc", + "metadata": {}, + "source": [ + "Similarly, if you pass a `MyCustomClass` instance as an **output** of your program, it is automatically converted to a JSON string (via the `to_json()` method):" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f4cf18b4", + "metadata": {}, + "outputs": [], + "source": [ + "def main(backend, user_messenger, **kwargs):\n", + " \"\"\"Main entry point of the program.\"\"\"\n", + " return MyCustomClass(\"this foo\", \"that bar\")" + ] + }, + { + "cell_type": "markdown", + "id": "8e0fdb4a", + "metadata": {}, + "source": [ + "Now when the user of this program calls `job.result()`, they will receive a JSON string rather than a `MyCustomClass` instance. The user can convert the string back to `MyCustomClass` themselves:" + ] + }, + { + "cell_type": "markdown", + "id": "abf149cc", + "metadata": {}, + "source": [ + "```\n", + "output_str = job.result()\n", + "output = MyCustomClass.from_json(output_str)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "32cd0c7e", + "metadata": {}, + "source": [ + "Alternatively, you can provide a decoder for the users. Your decoder class should inherit [`ResultDecoder`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.ResultDecoder.html#qiskit.providers.ibmq.runtime.ResultDecoder) and overwrites the `decode()` method:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a2f8564d", + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit.providers.ibmq.runtime import ResultDecoder\n", + "\n", + "class MyResultDecoder(ResultDecoder):\n", + "\n", + " @classmethod\n", + " def decode(cls, data):\n", + " data = super().decoded(data) # Perform any preprocessing.\n", + " return MyCustomClass.from_json(data)" + ] + }, + { + "cell_type": "markdown", + "id": "3d0de7d0", + "metadata": {}, + "source": [ + "Your user can then use this `MyResultDecoder` to decode the result of your program:\n", + "\n", + "```\n", + "output = job.result(decoder=MyResultDecoder)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "ecdda3bf", + "metadata": {}, + "source": [ + "## Testing your runtime program" + ] + }, + { + "cell_type": "markdown", + "id": "2a18f49e", + "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" + ] + }, + { + "cell_type": "markdown", + "id": "f197d48e", + "metadata": {}, + "source": [ + "The following example tests the `hello-world` program we saw earlier. It uses the `qasm_simulator` from Qiskit Aer as the test backend. It serializes and deserializes input data using `RuntimeEncoder` and `RuntimeDecoder`, which are the default en/decoders used by runtime." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "12c32ba7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"iteration\": 0, \"counts\": {\"01000\": 4, \"00000\": 12, \"00011\": 872, \"01011\": 136}}\n", + "{\"iteration\": 1, \"counts\": {\"01000\": 6, \"00000\": 19, \"00011\": 871, \"01011\": 128}}\n", + "{\"iteration\": 2, \"counts\": {\"00001\": 1024}}\n" + ] + }, + { + "data": { + "text/plain": [ + "'All done!'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import sys\n", + "sys.path.insert(0, '..') # Add qiskit_runtime directory to the path\n", + "\n", + "from qiskit_runtime.hello_world import hello_world\n", + "from qiskit import Aer\n", + "from qiskit.providers.ibmq.runtime.utils import RuntimeEncoder, RuntimeDecoder\n", + "from qiskit.providers.ibmq.runtime import UserMessenger\n", + "\n", + "inputs = {\"iterations\": 3}\n", + "\n", + "backend = Aer.get_backend('qasm_simulator')\n", + "user_messenger = UserMessenger()\n", + "serialized_inputs = json.dumps(inputs, cls=RuntimeEncoder)\n", + "deserialized_inputs = json.loads(serialized_inputs, cls=RuntimeDecoder)\n", + "\n", + "hello_world.main(backend, user_messenger, **deserialized_inputs)" + ] + }, + { + "cell_type": "markdown", + "id": "501a66f3", + "metadata": {}, + "source": [ + "## Defining program metadata" + ] + }, + { + "cell_type": "markdown", + "id": "f98640c1", + "metadata": {}, + "source": [ + "Program metadata helps users to understand how to use your program. It includes:\n", + "\n", + "- `name`: Name of the program.\n", + "- `max_execution_time`: Maximum amount of time, in seconds, a program can run before being forcibly terminated.\n", + "- `description`: Describes the program.\n", + "- `spec`: Detailed information about the program, which includes the following attributes:\n", + " - `backend_requirements`: Describes the backend attributes needed to run the program.\n", + " - `parameters`: Describes the program input parameters as a JSON schema\n", + " - `return_values`: Describes the return values as a JSON schema\n", + " - `interim_results`: Describes the interim results as a JSON schema\n", + "\n", + "When uploading a program, you must specify at least `name`, `max_execution_time`, and `description`. It is strongly encouraged to also specify `parameters`, `return_values`, and `interim_results` within `spec` if the program has them." + ] + }, + { + "cell_type": "markdown", + "id": "55db2a39", + "metadata": {}, + "source": [ + "Below shows the metadata JSON file of the `hello-world` program as an example:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3cdce276", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"name\": \"hello-world\",\n", + " \"description\": \"A sample runtime program.\",\n", + " \"max_execution_time\": 300,\n", + " \"spec\": {\n", + " \"backend_requirements\": {\n", + " \"min_num_qubits\": 5\n", + " },\n", + " \"parameters\": {\n", + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n", + " \"properties\": {\n", + " \"iterations\": {\n", + " \"type\": \"integer\",\n", + " \"minimum\": 0,\n", + " \"description\": \"Number of iterations to run. Each iteration generates a runs a random circuit.\"\n", + " }\n", + " },\n", + " \"required\": [\n", + " \"iterations\"\n", + " ]\n", + " },\n", + " \"return_values\": {\n", + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n", + " \"description\": \"A string that says 'All done!'.\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"interim_results\": {\n", + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n", + " \"properties\": {\n", + " \"iteration\": {\n", + " \"type\": \"integer\",\n", + " \"description\": \"Iteration number.\"\n", + " },\n", + " \"counts\": {\n", + " \"description\": \"Histogram data of the circuit result.\",\n", + " \"type\": \"object\"\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n" + ] + } + ], + "source": [ + "import os\n", + "\n", + "hello_world_json = os.path.join(os.getcwd(), \"../qiskit_runtime/hello_world/hello_world.json\")\n", + "\n", + "with open(hello_world_json, 'r') as file:\n", + " data = file.read()\n", + "\n", + "print(data)" + ] + }, + { + "cell_type": "markdown", + "id": "43dd6a77", + "metadata": {}, + "source": [ + "## Uploading a program" + ] + }, + { + "cell_type": "markdown", + "id": "d1865c58", + "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 `hello_world.py`, and its metadata, as described above, is in `hello_world.json`. " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "864ab085", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello-world-nQ9dgRjGEe\n" + ] + } + ], + "source": [ + "import os\n", + "from qiskit import IBMQ\n", + "\n", + "IBMQ.load_account()\n", + "provider = IBMQ.get_provider(project='qiskit-runtime') # Substitute with your provider.\n", + "\n", + "hello_world_data = os.path.join(os.getcwd(), \"../qiskit_runtime/hello_world/hello_world.py\")\n", + "hello_world_json = os.path.join(os.getcwd(), \"../qiskit_runtime/hello_world/hello_world.json\")\n", + "\n", + "program_id = provider.runtime.upload_program(\n", + " data=hello_world_data,\n", + " metadata=hello_world_json\n", + ")\n", + "print(program_id)" + ] + }, + { + "cell_type": "markdown", + "id": "45ffd6c8", + "metadata": {}, + "source": [ + "`upload_program()` returns a program ID, which uniquely identifies the program. It is derived from the program name, usually with a randomly-generated suffix. Program ID is needed for users to invoke the program" + ] + }, + { + "cell_type": "markdown", + "id": "chubby-infection", + "metadata": {}, + "source": [ + "## Updating a program" + ] + }, + { + "cell_type": "markdown", + "id": "measured-catalyst", + "metadata": {}, + "source": [ + "You can use the [`IBMRuntimeService.update_program()`](https://qiskit.org/documentation/stubs/qiskit.providers.ibmq.runtime.IBMRuntimeService.update_program.html#qiskit.providers.ibmq.runtime.IBMRuntimeService.update_program) method to update the source code and/or metadata of a program:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "southern-farmer", + "metadata": {}, + "outputs": [], + "source": [ + "provider.runtime.update_program(program_id=program_id, description=\"A new description.\")" + ] + }, + { + "cell_type": "markdown", + "id": "becoming-badge", + "metadata": {}, + "source": [ + "This method allows you to make changes to your program while retaining the same program ID." + ] + }, + { + "cell_type": "markdown", + "id": "f841041c", + "metadata": {}, + "source": [ + "## Deleting a program" + ] + }, + { + "cell_type": "markdown", + "id": "a20cd296", + "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 person who uploaded the program can delete it. \n" + ] + } + ], + "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.9.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/images/chip.png b/tutorials/images/chip.png new file mode 100644 index 000000000..0fa4a271f Binary files /dev/null and b/tutorials/images/chip.png differ diff --git a/tutorials/images/subgraphs.png b/tutorials/images/subgraphs.png new file mode 100644 index 000000000..7ccd9e3e2 Binary files /dev/null and b/tutorials/images/subgraphs.png differ diff --git a/tutorials/qka.ipynb b/tutorials/qka.ipynb new file mode 100644 index 000000000..c55b47081 --- /dev/null +++ b/tutorials/qka.ipynb @@ -0,0 +1,634 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quantum Kernel Alignment with Qiskit Runtime\n", + "\n", + "
\n", + "\n", + "**Classification with Support Vector Machines**
\n", + "Classification problems are widespread in machine learning applications. Examples include credit card risk, handwriting recognition, and medical diagnosis. One approach to tackling classification problems is the support vector machine (SVM) [1,2]. This supervised learning algorithm uses labeled data samples to train a model that can predict to which class a test sample belongs. It does this by finding a separating hyperplane maximizing the margin between data classes. Often, data is not linearly separable in the original space. In these cases, the kernel trick is used to implicitly encode a transformation of the data into a higher-dimensional feature space, through the inner product between pairs of data points, where the data may become separable.\n", + "\n", + "**Quantum Kernels**
\n", + "Quantum computers can be used to encode classical data in a quantum-enhanced feature space. In 2019, IBM introduced an algorithm called the quantum kernel estimator (QKE) for computing quantum kernels [3]. This algorithm uses quantum circuits with data provided classically and offers an efficient way to evaluate inner products between data in a quantum feature space. For two data samples $\\theta$ and $\\theta'$, the kernel matrix is given as\n", + "\n", + "$$\n", + " K(\\theta, \\theta') = \\lvert\\langle 0^n \\rvert U^\\dagger(\\theta) U(\\theta') \\lvert 0^n \\rangle \\rvert^2,\n", + "$$\n", + "\n", + "where $U(\\theta)$ prepares the quantum feature state. Quantum kernels used in a classification framework inherit the convex optimization program of the SVM and avoid common limitations of variational quantum classifiers. A key observation of this paper was that a necessary condition for a computational advantage requires quantum circuits for the kernel that are hard to simulate classically. More recently, IBM proved that quantum kernels can offer superpolynomial speedups over any classical learner on a learning problem based on the hardness of the discrete logarithm problem [4]. This means that quantum kernels can someday offer quantum advantage on suitable problems. \n", + "\n", + "\n", + "**Quantum Kernels that Exploit Structure in Data**
\n", + "An important approach in the search for practical quantum advantage in machine learning is to identify quantum kernels for learning problems that have underlying structure in the data. We've taken a step in this direction in our recent paper [5], where we introduced a broad class of quantum kernels that exploit group structure in data. Examples of learning problems for data with group structure could include learning permutations or classifying translations. We call this new class of kernels _covariant quantum kernels_ as they are related to covariant quantum measurements. The quantum feature map is defined by a unitary representation $D(\\theta)$ of a group $G$ for some element $\\theta \\in G$, and a fiducial reference state $\\lvert\\psi\\rangle = V\\lvert0^n\\rangle$ prepared by a unitary circuit $V$. The kernel matrix is given as\n", + "\n", + "$$\n", + " K(\\theta, \\theta') = \\vert\\langle 0^n \\rvert V^\\dagger D^\\dagger(\\theta) D(\\theta') V \\lvert 0^n \\rangle \\rvert^2. \\qquad (1)\n", + "$$\n", + "\n", + "In general, the choice of the fiducial state is not known _a priori_ and can significantly impact the performance of the classifier. Here, we use a method called quantum kernel alignment (QKA) to find a good fiducial state for a given group.\n", + "\n", + "**Aligning Quantum Kernels on a Dataset**
\n", + "In practice, SVMs require a choice of the kernel function. Sometimes, symmetries in the data can inform this selection, other times it is chosen in an ad hoc manner. Kernel alignment is one approach to learning a kernel on a given dataset by iteratively adapting it to have high similarity to a target kernel informed from the underlying data distribution [6]. As a result, the SVM with an aligned kernel will likely generalize better to new data than with an unaligned kernel. Using this concept, we introduced in [5] an algorithm for quantum kernel alignment, which provides a way to learn a quantum kernel from a family of kernels. Specifically, the algorithm optimizes the parameters in a quantum circuit to maximize the alignment of a kernel while converging to the maximum SVM margin. In the context of covariant quantum kernels, we extend Eq. $(1)$ to\n", + "\n", + "$$\n", + " K_\\lambda(\\theta,\\theta') = \\lvert\\langle 0^n \\rvert V^\\dagger_\\lambda D^\\dagger(\\theta) D(\\theta') V_\\lambda \\lvert 0^n \\rangle \\rvert^2, \\qquad (2)\n", + "$$\n", + "\n", + "and use QKA to learn a good fiducial state parametrized by $\\lambda$ for a given group. \n", + "\n", + "\n", + "**Covariant Quantum Kernels on a Specific Learning Problem**
\n", + "Let's try out QKA on a learning problem. In the following, we'll consider a binary classification problem we call _labeling cosets with error_ [5]. In this problem, we will use a group and a subgroup to form two cosets, which will represent our data classes. We take the group $G = SU(2)^{\\otimes n}$ for $n$ qubits, which is the special unitary group of $2\\times2$ matrices and has wide applicability in nature, for example, the Standard Model of particle physics and in many condensed matter systems. We take the graph-stabilizer subgroup $S_{\\mathrm{graph}} \\in G$ with $S_{\\mathrm{graph}} = \\langle \\{ X_i \\otimes_{k:(k,i) \\in \\mathcal{E}} Z_k \\}_{i \\in \\mathcal{V}} \\rangle$ for a graph $(\\mathcal{E},\\mathcal{V})$ with edges $\\mathcal{E}$ and vertices $\\mathcal{V}$. Note that the stabilizers fix a stabilizer state such that $D_s \\lvert \\psi\\rangle = \\lvert \\psi\\rangle$. This observation will be useful a bit later. \n", + "\n", + "To generate the dataset, we write the rotations of the group as $D(\\theta_1, \\theta_2, 0)=\\exp(i \\theta_1 X) \\exp(i \\theta_2 Z) \\in SU(2)$, so that each qubit is parametrized by the first two Euler angles (the third we set to zero). Then, we draw randomly two sets of angles $\\mathbf{\\theta}_\\pm \\in [-\\pi/4, \\pi/4]^{2n}$ for the $n$-qubit problem. From these two sets, we construct a binary classification problem by forming two left-cosets (representing the two classes) with those angles, $C_\\pm = D(\\mathbf{\\theta}_\\pm) S_{\\mathrm{graph}}$ where $D(\\mathbf{\\theta}_\\pm) = \\otimes_{k=1}^n D(\\theta_\\pm^{2k-1}, \\theta_\\pm^{2k}, 0)$. Note that the elements of the cosets can again be written in terms of Euler angles. We build training and testing sets by randomly drawing elements from $C_\\pm$ such that the dataset has samples $i=1,...,m$ containing the first two Euler angles for each qubit $\\mathbf{\\theta}_{y_i} = (\\theta_{y_i}^{1}, \\theta_{y_i}^{2}, \\theta_{y_i}^{3}, \\theta_{y_i}^{4}, ..., \\theta_{y_i}^{2n-1}, \\theta_{y_i}^{2n})$ and labels $y_i \\in \\{-1,1\\}$ that indicate to which coset a sample belongs.\n", + "\n", + "Next, we select a fiducial state. A natural candidate is the stabilizer state we encountered above. Why? Because this is a subgroup invariant state, $D_s\\lvert\\psi\\rangle = \\lvert\\psi\\rangle$, which causes the data for a given coset to be mapped to a unique state: $D(\\mathbf{\\theta}_\\pm)D_s \\lvert\\psi\\rangle = D(\\mathbf{\\theta}_\\pm) \\lvert\\psi\\rangle$. This means the classifier only needs to distinguish the _two_ states $D(\\mathbf{\\theta}_\\pm) \\lvert\\psi\\rangle \\langle \\psi\\rvert D^\\dagger(\\mathbf{\\theta}_\\pm)$ for every element of the coset. In this tutorial, we will add a small Gaussian error with variance $0.01$ to the Euler angles of the dataset. This noise will perturb these two states, but if the variance is sufficiently small, we expect the states will still be classified correctly. Let's consider a parametrized version of the stabilizer state, associated with the coupling graph $(\\mathcal{E},\\mathcal{V})$ given by the device connectivity, as our fiducial state and then use kernel alignment to find its optimal parameters. Specifically, we'll replace the initial layers of Hadamards in the graph state with $y$-rotations by an angle $\\lambda$,\n", + "\n", + "$$\n", + "\\lvert \\psi_\\lambda\\rangle = V_\\lambda \\lvert 0^n\\rangle = \\prod_{(k,t) \\in \\mathcal{E}} CZ_{k,t} \\prod_{k \\in \\mathcal{V}} \\exp\\left(i \\frac{\\lambda}{2} Y_k\\right)\\lvert 0^n\\rangle,\n", + "$$\n", + "\n", + "where $CZ=\\mathrm{diag}(1,1,1,-1)$. Then, given two samples from our dataset, $\\mathbf{\\theta}$ and $\\mathbf{\\theta}'$, the kernel matrix is evaluated as in Eq. $(2)$. If we initialize the kernel with $\\lambda \\approx 0$, we expect the quantum kernel alignment algorithm to converge towards the optimal $\\lambda = \\pi/2$ and the classifier to yield 100\\% test accuracy.\n", + "\n", + "Let's define two specific problem instances to test these ideas out. We'll be using the quantum device `ibmq_montreal`, with coupling map shown below:\n", + "\n", + "
\n", + "\n", + "
\n", + "\n", + "We'll pick two different subgraphs, one for 7 qubits and one for 10, to define our problem instances. Using these subgraphs, we'll generate the corresponding datasets as described above, and then align the quantum kernel with QKA to learn a good fiducial state.\n", + "\n", + "
\n", + "\n", + "
\n", + "\n", + "**Speeding up Algorithms with Qiskit Runtime**
\n", + "QKA is an iterative quantum-classical algorithm, in which quantum hardware is used to execute parametrized quantum circuits for evaluating the quantum kernel matrices with QKE, while a classical optimizer tunes the parameters of those circuits to maximize the alignment. Iterative algorithms of this type can be slow due to latency between the quantum and classical calculations. Qiskit Runtime is a new architecture that can speed up iterative algorithms like QKA by co-locating classical computations with the quantum hardware executions. In this tutorial, we'll use QKA with Qiskit Runtime to learn a good quantum kernel for the _labeling cosets with error_ problem defined above.\n", + "\n", + "
\n", + "\n", + "**References**
\n", + "[1] B. E. Boser, I. M. Guyon, and V. N. Vapnik, Proceedings of the Fifth Annual Workshop on Computational Learning Theory, COLT ’92 (Association for Computing Machinery, New York, NY, USA, 1992) pp. 144-152 [link](https://doi.org/10.1145/130385.130401)
\n", + "[2] V. Vapnik, The Nature of Statistical Learning Theory, Information Science and Statistics (Springer New York, 2013) [link](https://books.google.com/books?id=EqgACAAAQBAJ)
\n", + "[3] V. Havlíček, A. D. Córcoles, K. Temme, A. W. Harrow, A. Kandala, J. M. Chow, and J. M. Gambetta, Nature 567, 209-212 (2019) [link](https://doi.org/10.1038/s41586-019-0980-2)
\n", + "[4] Y. Liu, S. Arunachalam, and K. Temme, arXiv:2010.02174 (2020) [link](https://arxiv.org/abs/2010.02174)
\n", + "[5] J. R. Glick, T. P. Gujarati, A. D. Córcoles, Y. Kim, A. Kandala, J. M. Gambetta, K. Temme, arXiv:2105.03406 (2021) [link](https://arxiv.org/abs/2105.03406)
\n", + "[6] N. Cristianini, J. Shawe-taylor, A. Elisseeff, and J. Kandola, Advances in Neural Information Processing Systems 14 (2001) [link](https://proceedings.neurips.cc/paper/2001/file/1f71e393b3809197ed66df836fe833e5-Paper.pdf)
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Load your IBM Quantum account and get the quantum backend\n", + "\n", + "We'll be using the 27-qubit device `ibmq_montreal` for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.insert(0, '..') # Add qiskit_runtime directory to the path\n", + "\n", + "from qiskit import IBMQ\n", + "IBMQ.load_account()\n", + "provider = IBMQ.get_provider(project='qiskit-runtime') # Change this to your provider.\n", + "backend = provider.get_backend('ibmq_montreal')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Invoke the Quantum Kernel Alignment program\n", + "\n", + "Before executing the runtime program for QKA, we need to prepare the dataset and configure the input parameters for the algorithm.\n", + "\n", + "### 1. Prepare the dataset\n", + "\n", + "First, we load the dataset from the `csv` file and then extract the labeled training and test samples. Here, we'll look at the 7-qubit problem, shown above in subfigure a). A second dataset is also available for the 10-qubit problem in b)." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.read_csv('../qiskit_runtime/qka/aux_file/dataset_graph7.csv',sep=',', header=None) # alterative problem: dataset_graph10.csv\n", + "data = df.values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's take a look at the data to see how it's formatted. Each row of the dataset contains a list of Euler angles, followed by the class label $\\pm1$ in the last column. For an $n$-qubit problem, there are $2n$ features corresponding to the first two Euler angles for each qubit (recall discussion above). The rows alternate between class labels." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 0 1 2 3 4 5 6 \\\n", + "0 -0.193574 0.113979 -0.005927 0.300957 -0.358603 -0.087866 -0.156226 \n", + "1 -0.100006 0.002431 0.244218 0.126870 -0.063891 -0.085588 0.072490 \n", + "2 -1.774448 -0.047642 -0.025880 0.252708 -0.350689 -1.604509 -0.114874 \n", + "3 -0.211585 -0.043782 -1.560226 0.018510 -0.051867 -0.128508 0.218609 \n", + "\n", + " 7 8 9 10 11 12 13 14 \n", + "0 0.342442 -0.016003 0.143113 0.256422 -0.164125 -0.136743 0.014674 1.0 \n", + "1 0.042986 -0.052714 0.019754 -0.159314 -0.409991 -0.199615 0.053845 -1.0 \n", + "2 0.347631 0.059501 -0.168956 0.351014 -0.128586 0.098897 -0.047799 1.0 \n", + "3 -0.075632 -0.183656 -1.715292 -0.105361 -0.300758 -0.566431 0.046542 -1.0 \n" + ] + } + ], + "source": [ + "print(df.head(4))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's explicitly construct the training and test samples (denoted `x`) and their labels (denoted `y`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "# choose number of training and test samples per class:\n", + "num_train = 10\n", + "num_test = 10\n", + "\n", + "# extract training and test sets and sort them by class label\n", + "train = data[:2*num_train, :]\n", + "test = data[2*num_train:2*(num_train+num_test), :]\n", + "\n", + "ind=np.argsort(train[:,-1])\n", + "x_train = train[ind][:,:-1]\n", + "y_train = train[ind][:,-1]\n", + "\n", + "ind=np.argsort(test[:,-1])\n", + "x_test = test[ind][:,:-1]\n", + "y_test = test[ind][:,-1]\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Configure the QKA algorithm\n", + "\n", + "The first task is to set up the feature map and its entangler map, which specifies the arrangement of $CZ$ gates in the fiducial state. We will choose this to match the connectivity of the problem subgraph, pictured above. We also initialize the fiducial state parameter $\\lambda$ with `initial_point`." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit_runtime.qka import FeatureMap\n", + "\n", + "d = np.shape(data)[1]-1 # feature dimension is twice the qubit number\n", + "\n", + "em = [[0,2],[3,4],[2,5],[1,4],[2,3],[4,6]] # we'll match this to the 7-qubit graph \n", + "# em = [[0,1],[2,3],[4,5],[6,7],[8,9],[1,2],[3,4],[5,6],[7,8]] # we'll match this to the 10-qubit graph\n", + "\n", + "fm = FeatureMap(feature_dimension=d, entangler_map=em) # define the feature map\n", + "initial_point = [0.1] # set the initial parameter for the feature map" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's print out the circuit for the feature map (the circuit for the kernel will be a feature map for one data sample composed with an inverse feature map for a second sample). The first part of the feature map is the fiducial state, which is prepared with a layer of $y$ rotations followed by $CZ$s. Then, the last two layers of $z$ and $x$ rotations in the circuit denote the group representation $D(\\theta)$ for a data sample $\\theta$. Note that a single-qubit rotation is defined as $RP(\\phi) = \\exp(- i [\\phi/2] P)$ for $P \\in {X, Y, Z}$." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
     ┌──────────┐   ┌──────────────┐ ┌────────────┐                                                 \n",
+       "q_0: ┤ RY(-0.1) ├─■─┤ RZ(-0.38383) ├─┤ RX(3.3378) ├─────────────────────────────────────────────────\n",
+       "     ├──────────┤ │ └──────────────┘ └────────────┘┌──────────────┐ ┌──────────────┐                \n",
+       "q_1: ┤ RY(-0.1) ├─┼────────────────────────■───────┤ RZ(-0.11811) ├─┤ RX(-0.20449) ├────────────────\n",
+       "     ├──────────┤ │                        │       └──────────────┘ └┬────────────┬┘┌─────────────┐ \n",
+       "q_2: ┤ RY(-0.1) ├─■────────■───────────────┼──────────────■──────────┤ RZ(3.4802) ├─┤ RX(0.15495) ├─\n",
+       "     ├──────────┤          │               │              │         ┌┴────────────┤ ├─────────────┴┐\n",
+       "q_3: ┤ RY(-0.1) ├─■────────┼───────────────┼──────────────■─────────┤ RZ(0.34764) ├─┤ RX(-0.54085) ├\n",
+       "     ├──────────┤ │        │               │                        ├─────────────┤ ├──────────────┤\n",
+       "q_4: ┤ RY(-0.1) ├─■────────┼───────────────■──────────────■─────────┤ RZ(0.34312) ├─┤ RX(-0.14015) ├\n",
+       "     ├──────────┤          │        ┌─────────────┐       │        ┌┴─────────────┴┐└──────────────┘\n",
+       "q_5: ┤ RY(-0.1) ├──────────■────────┤ RZ(0.51497) ├───────┼────────┤ RX(-0.029293) ├────────────────\n",
+       "     ├──────────┤                   └─────────────┘       │        └┬──────────────┤┌─────────────┐ \n",
+       "q_6: ┤ RY(-0.1) ├─────────────────────────────────────────■─────────┤ RZ(-0.42725) ├┤ RX(0.44115) ├─\n",
+       "     └──────────┘                                                   └──────────────┘└─────────────┘ 
" + ], + "text/plain": [ + " ┌──────────┐ ┌──────────────┐ ┌────────────┐ \n", + "q_0: ┤ RY(-0.1) ├─■─┤ RZ(-0.38383) ├─┤ RX(3.3378) ├─────────────────────────────────────────────────\n", + " ├──────────┤ │ └──────────────┘ └────────────┘┌──────────────┐ ┌──────────────┐ \n", + "q_1: ┤ RY(-0.1) ├─┼────────────────────────■───────┤ RZ(-0.11811) ├─┤ RX(-0.20449) ├────────────────\n", + " ├──────────┤ │ │ └──────────────┘ └┬────────────┬┘┌─────────────┐ \n", + "q_2: ┤ RY(-0.1) ├─■────────■───────────────┼──────────────■──────────┤ RZ(3.4802) ├─┤ RX(0.15495) ├─\n", + " ├──────────┤ │ │ │ ┌┴────────────┤ ├─────────────┴┐\n", + "q_3: ┤ RY(-0.1) ├─■────────┼───────────────┼──────────────■─────────┤ RZ(0.34764) ├─┤ RX(-0.54085) ├\n", + " ├──────────┤ │ │ │ ├─────────────┤ ├──────────────┤\n", + "q_4: ┤ RY(-0.1) ├─■────────┼───────────────■──────────────■─────────┤ RZ(0.34312) ├─┤ RX(-0.14015) ├\n", + " ├──────────┤ │ ┌─────────────┐ │ ┌┴─────────────┴┐└──────────────┘\n", + "q_5: ┤ RY(-0.1) ├──────────■────────┤ RZ(0.51497) ├───────┼────────┤ RX(-0.029293) ├────────────────\n", + " ├──────────┤ └─────────────┘ │ └┬──────────────┤┌─────────────┐ \n", + "q_6: ┤ RY(-0.1) ├─────────────────────────────────────────■─────────┤ RZ(-0.42725) ├┤ RX(0.44115) ├─\n", + " └──────────┘ └──────────────┘└─────────────┘ " + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from qiskit.tools.visualization import circuit_drawer\n", + "circuit_drawer(fm.construct_circuit(x=x_train[0], parameters=initial_point), \n", + " output='text', fold=200)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we set the values for the SVM soft-margin penalty `C` and the number of SPSA iterations `maxiters` we use to align the quantum kernel." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "C = 1 # SVM soft-margin penalty\n", + "maxiters = 10 # number of SPSA iterations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we decide how to map the virtual qubits of our problem graph to the physical qubits of the hardware. For example, in the 7-qubit problem, we can directly map the virtual qubits `[0, 1, 2, 3, 4, 5, 6]` to the physical qubits `[10, 11, 12, 13, 14, 15, 16]` of the device. This allows us to avoid introducing SWAP gates for qubits that are not connected, which can increase the circuit depth. " + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [], + "source": [ + "initial_layout = [10, 11, 12, 13, 14, 15, 16] # see figure above for the 7-qubit graph\n", + "# initial_layout = [9, 8, 11, 14, 16, 19, 22, 25, 24, 23] # see figure above for the 10-qubit graph" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3. Set up and run the program\n", + "\n", + "We're almost ready to run the program. First, let's take a look at the program metadata, which includes a description of the input parameters and their default values." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "quantum-kernel-alignment:\n", + " Name: quantum-kernel-alignment\n", + " Description: Quantum kernel alignment algorithm that learns, on a given dataset, a quantum kernel maximizing the SVM classification margin.\n", + " Version: 1\n", + " Creation date: 2021-05-06T14:56:53Z\n", + " Max execution time: 28800\n", + " Input parameters:\n", + " - feature_map:\n", + " Description: An instance of FeatureMap in dictionary format used to map classical data into a quantum state space.\n", + " Type: dict\n", + " Required: True\n", + " - data:\n", + " Description: NxD array of training data, where N is the number of samples and D is the feature dimension.\n", + " Type: numpy.ndarray\n", + " Required: True\n", + " - labels:\n", + " Description: Nx1 array of +/-1 labels of the N training samples.\n", + " Type: numpy.ndarray\n", + " Required: True\n", + " - initial_kernel_parameters:\n", + " Description: Initial parameters of the quantum kernel. If not specified, an array of randomly generated numbers is used.\n", + " Type: numpy.ndarray\n", + " Required: False\n", + " - maxiters:\n", + " Description: Number of SPSA optimization steps. Default is 1.\n", + " Type: int\n", + " Required: False\n", + " - C:\n", + " Description: Penalty parameter for the soft-margin support vector machine. Default is 1.\n", + " Type: float\n", + " Required: False\n", + " - initial_layout:\n", + " Description: Initial position of virtual qubits on the physical qubits of the quantum device. Default is None.\n", + " Type: list or dict\n", + " Required: False\n", + " Interim results:\n", + " none\n", + " Returns:\n", + " - aligned_kernel_parameters:\n", + " Description: The optimized kernel parameters found from quantum kernel alignment.\n", + " Type: numpy.ndarray\n", + " - aligned_kernel_matrix:\n", + " Description: The aligned quantum kernel matrix evaluated with the optimized kernel parameters on the training data.\n", + " Type: numpy.ndarray\n" + ] + } + ], + "source": [ + "print(provider.runtime.program('quantum-kernel-alignment'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that this program has several input parameters, which we'll configure below. To run the program, we'll set up its two main components: `inputs` (the input parameters from the program metadata) and `options` (the quantum backend). We'll also define a callback function so that the intermediate results of the algorithm will be printed as the program runs. Note that each step of the algorithm for the settings we've selected here takes approximately 11 minutes." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def interim_result_callback(job_id, interim_result):\n", + " print(f\"interim result: {interim_result}\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": { + "scrolled": false, + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "c2at64rhejjp7co0uc9g\n", + "interim result: {'cost': 10.870283985111303, 'kernel_parameters': array([0.24000164])}\n", + "\n", + "interim result: {'cost': 10.333206932017534, 'kernel_parameters': array([0.42813036])}\n", + "\n", + "interim result: {'cost': 9.080271557433964, 'kernel_parameters': array([0.62392269])}\n", + "\n", + "interim result: {'cost': 7.651520327865867, 'kernel_parameters': array([0.76737064])}\n", + "\n", + "interim result: {'cost': 6.6448212932491355, 'kernel_parameters': array([0.91182299])}\n", + "\n", + "interim result: {'cost': 5.958753300709191, 'kernel_parameters': array([1.03833353])}\n", + "\n", + "interim result: {'cost': 5.265464439204466, 'kernel_parameters': array([1.11397698])}\n", + "\n", + "interim result: {'cost': 4.899536249549028, 'kernel_parameters': array([1.15494826])}\n", + "\n", + "interim result: {'cost': 4.848342921952558, 'kernel_parameters': array([1.1975977])}\n", + "\n", + "interim result: {'cost': 4.743013044149239, 'kernel_parameters': array([1.221689])}\n", + "\n" + ] + } + ], + "source": [ + "program_inputs = {\n", + " 'feature_map': fm,\n", + " 'data': x_train,\n", + " 'labels': y_train,\n", + " 'initial_kernel_parameters': initial_point,\n", + " 'maxiters': maxiters,\n", + " 'C': C,\n", + " 'initial_layout': initial_layout\n", + "}\n", + "\n", + "options = {'backend_name': backend.name()}\n", + "\n", + "job = provider.runtime.run(program_id=\"quantum-kernel-alignment\",\n", + " options=options,\n", + " inputs=program_inputs,\n", + " callback=interim_result_callback,\n", + " )\n", + "\n", + "print(job.job_id())\n", + "result = job.result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. Retrieve the results of the program\n", + "\n", + "Now that we've run the program, we can retrieve the output, which is the aligned kernel parameter and the aligned kernel matrix. Let's also plot this kernel matrix (we'll subtract off the diagonal to show the contrast between the remaining entries). The kernel matrix is expected to have a block-diagonal structure. This reflects the fact that the kernel maps the input data effectively to just two states (modulo the small noise we added to the data; recall the discussion above). That is, data in the same coset (same class label) have a larger overlap than do data from different cosets." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "aligned_kernel_parameters: [1.221689]\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "print(f\"aligned_kernel_parameters: {result['aligned_kernel_parameters']}\")\n", + "\n", + "from matplotlib import pyplot as plt\n", + "from pylab import cm\n", + "plt.rcParams['font.size'] = 20\n", + "plt.imshow(result['aligned_kernel_matrix']-np.identity(2*num_train), cmap=cm.get_cmap('bwr', 20))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Use the results of the program to test an SVM on new data\n", + "\n", + "Equipped with the aligned kernel and its optimized parameter, we can use the `sklearn` package to train an SVM and then evaluate its classification accuracy on new test points. Note that a second kernel matrix built from the test points is needed for the SVM decision function." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": { + "scrolled": true, + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "accuracy test: 1.0\n" + ] + } + ], + "source": [ + "from qiskit_runtime.qka import KernelMatrix\n", + "from sklearn.svm import SVC\n", + "from sklearn import metrics\n", + "\n", + "# train the SVM with the aligned kernel matrix:\n", + "\n", + "kernel_aligned = result['aligned_kernel_matrix']\n", + "model = SVC(C=C, kernel='precomputed')\n", + "model.fit(X=kernel_aligned, y=y_train)\n", + "\n", + "# test the SVM on new data:\n", + "\n", + "km = KernelMatrix(feature_map=fm, backend=backend, initial_layout=initial_layout)\n", + "kernel_test = km.construct_kernel_matrix(x1_vec=x_test, x2_vec=x_train, parameters=result['aligned_kernel_parameters'])\n", + "labels_test = model.predict(X=kernel_test)\n", + "accuracy_test = metrics.balanced_accuracy_score(y_true=y_test, y_pred=labels_test)\n", + "print(f\"accuracy test: {accuracy_test}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

Version Information

Qiskit SoftwareVersion
Qiskit0.25.0
Terra0.17.0
Aer0.8.0
Ignis0.6.0
Aqua0.9.0
IBM Q Provider0.13.0
System information
Python3.7.10 (default, Feb 26 2021, 10:16:00) \n", + "[Clang 10.0.0 ]
OSDarwin
CPUs4
Memory (Gb)16.0
Mon May 03 13:47:57 2021 EDT
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

This code is a part of Qiskit

© Copyright IBM 2017, 2021.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import qiskit.tools.jupyter\n", + "%qiskit_version_table\n", + "%qiskit_copyright" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:envruntime2]", + "language": "python", + "name": "conda-env-envruntime2-py" + }, + "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.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorials/sample_expval_program/qiskit_runtime_expval_program.ipynb b/tutorials/sample_expval_program/qiskit_runtime_expval_program.ipynb new file mode 100644 index 000000000..53c723b7f --- /dev/null +++ b/tutorials/sample_expval_program/qiskit_runtime_expval_program.ipynb @@ -0,0 +1,823 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "33be72af", + "metadata": {}, + "source": [ + "# Custom Expectation Value Program for the Qiskit Runtime\n", + "\n", + "\n", + "

\n", + "Paul Nation\n", + "

\n", + "

\n", + "IBM Quantum Partners Technical Enablement Team\n", + "

\n", + "\n", + "Here we will show how to make a program that takes a circuit, or list of circuits, and computes the expectation values of one or more diagonal operators." + ] + }, + { + "cell_type": "markdown", + "id": "118e72f0", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "- You must have Qiskit 0.32+ installed.\n", + "- You must have an IBM Quantum Experience account with the ability to upload a Runtime program. You can upload a program if you have access to more than just the open hub/group/project (ibm-q/open/main)." + ] + }, + { + "cell_type": "markdown", + "id": "6368c90a", + "metadata": {}, + "source": [ + "## Background\n", + "\n", + "The primary method by which information is obtained from quantum computers is via expectation values. Indeed, the samples that come from executing a quantum circuit multiple times, once converted to probabilities, can be viewed as just a finite sample approximation to the expectation value for the projection operators corresponding to each bitstring. More practically, many quantum algorithms require computing expectation values over Pauli operators, e.g. Variational Quantum Eigensolvers, and thus having a runtime program that computes these quantities is of fundamental importance. Here we look at one such example, where an user passes one or more circuits and expectation operators and gets back the computed expectation values, and possibly error bounds.\n", + "\n", + "### Expectation value of a diagonal operator\n", + "\n", + "Consider a generic observable given by the tensor product of diagonal operators over $N$ qubits $O = O_{N-1}\\dots O_{0}$ where the subscript indicates the qubit on which the operator acts. Then for a set of observed $M$ bitstrings $\\{b_{0}, \\dots b_{M-1}\\}$, where $M \\leq 2^N $, with corresponding approximate probabilites $p_{m}$ the expectation value is given by\n", + "\n", + "$$\n", + "\\langle O\\rangle \\simeq \\sum_{m=0}^{M-1} p_{m}\\prod_{n=0}^{N-1}O_{n}[b_{m}[N-n-1], b_{m}[N-n-1]],\n", + "$$\n", + "\n", + "where $O_{n}[b_{m}[N-n-1], b_{m}[N-n-1]]$ is the diagonal element of $O_{n}$ specified by the $N-n-1$th bit in bitstring $b_{m}$. The reason for the complicated indexing in `b_{m}` is because Qiskit uses least-sginificant bit indexing where the zeroth element of the bit-strings is given by the right-most bit.\n", + "\n", + "Here we will use built-in routines to compute these expectation values. However, it is not hard to do yourself, with plenty of examples to be found." + ] + }, + { + "cell_type": "markdown", + "id": "42df9e62", + "metadata": {}, + "source": [ + "## Main program\n", + "\n", + "Here we define our main function for the expectation value runtime program. As always, our program must start with the `backend`, and `user_messenger` arguements, followed by the actual inputs we pass to the program. Here our options are quite simple:\n", + "\n", + "- `circuits`: A single QuantumCircuit or list of QuantumCircuits to be executed on the target backend.\n", + "\n", + "\n", + "- `expectation_operators`: The operators we want to evaluate. These can be strings of diagonal Pauli's, eg, `ZIZZ`, or custom operators defined by dictionarys. For example, the projection operator on the all ones state of 4 qubits is `{'1111': 1}`.\n", + "\n", + "\n", + "- `shots`: Howe many times to sample each circuit.\n", + "\n", + "\n", + "- `transpiler_config`: A dictionary that passes additional arguments on to the transpile function, eg. `optimization_level`.\n", + "\n", + "\n", + "- `run_config`: A dictionary that passes additional arguments on to `backend.run()`.\n", + "\n", + "\n", + "- `skip_transpilation`: A flag to skip transpilation altogether and just run the circuits. This is useful for situations where you need to transpile parameterized circuits once, but must bind parameters multiple times and evaluate. \n", + "\n", + "\n", + "- `return_stddev`: Flag to return bound on standard deviation. If using measurement mitigation this adds some overhead to the computation.\n", + "\n", + "\n", + "- `use_measurement_mitigation`: Use M3 measurement mitigation and compute expecation value and standard deviation bound from quasi-probabilities.\n", + "\n", + "At the top of the cell below you will see a commented out `%%writefile sample_expval.py`. We will use this to convert the cell to a Python module named `sample_expval.py` to upload." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "06b48e43", + "metadata": {}, + "outputs": [], + "source": [ + "#%%writefile sample_expval.py\n", + "import mthree\n", + "from qiskit import transpile\n", + "\n", + "# The entrypoint for our Runtime Program\n", + "def main(backend, user_messenger,\n", + " circuits,\n", + " expectation_operators='',\n", + " shots = 8192,\n", + " transpiler_config={},\n", + " run_config={},\n", + " skip_transpilation=False,\n", + " return_stddev=False,\n", + " use_measurement_mitigation=False,\n", + " ):\n", + " \n", + " \"\"\"Compute expectation \n", + " values for a list of operators after\n", + " executing a list of circuits on the target backend.\n", + " \n", + " Parameters:\n", + " backend (ProgramBackend): Qiskit backend instance.\n", + " user_messenger (UserMessenger): Used to communicate with the program user.\n", + " circuits: (QuantumCircuit or list): A single list of QuantumCircuits.\n", + " expectation_operators (str or dict or list): Expectation values to evaluate.\n", + " shots (int): Number of shots to take per circuit.\n", + " transpiler_config (dict): A collection of kwargs passed to transpile().\n", + " run_config (dict): A collection of kwargs passed to backend.run().\n", + " skip_transpilation (bool): Skip transpiling of circuits, default=False.\n", + " return_stddev (bool): Return upper bound on standard devitation,\n", + " default=False. \n", + " use_measurement_mitigation (bool): Improve resulting using measurement\n", + " error mitigation, default=False.\n", + " \n", + " Returns:\n", + " array_like: Returns array of expectation values or a list of (expval, stddev)\n", + " tuples if return_stddev=True.\n", + " \"\"\"\n", + " \n", + " # transpiling the circuits using given transpile options\n", + " if not skip_transpilation:\n", + " trans_circuits = transpile(circuits, backend=backend,\n", + " **transpiler_config)\n", + " # Make sure everything is a list\n", + " if not isinstance(trans_circuits, list):\n", + " trans_circuits = [trans_circuits]\n", + " # If skipping set circuits -> trans_circuits\n", + " else:\n", + " if not isinstance(circuits, list):\n", + " trans_circuits = [circuits]\n", + " else:\n", + " trans_circuits = circuits\n", + "\n", + " # If we are given a single circuit but requesting multiple expectation\n", + " # values, then set flag to make multiple pointers to same result.\n", + " duplicate_results = False\n", + " if isinstance(expectation_operators, list):\n", + " if len(expectation_operators) and len(trans_circuits) == 1:\n", + " duplicate_results = True\n", + " \n", + " # If doing measurement mitigation we must build and calibrate a\n", + " # mitigator object. Will also determine which qubits need to be\n", + " # calibrated.\n", + " if use_measurement_mitigation:\n", + " # Get an the measurement mappings at end of circuits\n", + " meas_maps = mthree.utils.final_measurement_mapping(trans_circuits)\n", + " # Get an M3 mitigator\n", + " mit = mthree.M3Mitigation(backend)\n", + " # Calibrate over the set of qubits measured in the transpiled circuits.\n", + " mit.cals_from_system(meas_maps)\n", + "\n", + " # Compute raw results\n", + " result = backend.run(trans_circuits, shots=shots, **run_config).result()\n", + " raw_counts = result.get_counts()\n", + "\n", + " # When using measurement mitigation we need to apply the correction and then\n", + " # compute the expectation values from the computed quasi-probabilities.\n", + " if use_measurement_mitigation:\n", + " quasi_dists = mit.apply_correction(raw_counts, meas_maps,\n", + " return_mitigation_overhead=return_stddev)\n", + " \n", + " if duplicate_results:\n", + " quasi_dists = mthree.classes.QuasiCollection(\n", + " [quasi_dists]*len(expectation_operators))\n", + " # There are two different calls depending on what we want returned.\n", + " if return_stddev:\n", + " return quasi_dists.expval_and_stddev(expectation_operators)\n", + " return quasi_dists.expval(expectation_operators)\n", + " \n", + " # If the program didn't return in the mitigation loop above it means\n", + " # we are processing the raw_counts data. We do so here using the\n", + " # mthree utilities\n", + " if duplicate_results:\n", + " raw_counts = [raw_counts]*len(expectation_operators)\n", + " if return_stddev:\n", + " return mthree.utils.expval_and_stddev(raw_counts, expectation_operators)\n", + " return mthree.utils.expval(raw_counts, expectation_operators)" + ] + }, + { + "cell_type": "markdown", + "id": "edadd3f8", + "metadata": {}, + "source": [ + "## Local testing\n", + "\n", + "Here we test with a local \"Fake\" backend that mimics the noise properties of a real system and a 4-qubit GHZ state." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2a25b3ac", + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit import QuantumCircuit\n", + "from qiskit.test.mock import FakeSantiago\n", + "from qiskit.providers.ibmq.runtime import UserMessenger\n", + "msg = UserMessenger()\n", + "backend = FakeSantiago()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "6339a144", + "metadata": {}, + "outputs": [], + "source": [ + "qc = QuantumCircuit(4)\n", + "qc.h(2)\n", + "qc.cx(2, 1)\n", + "qc.cx(1, 0)\n", + "qc.cx(2, 3)\n", + "qc.measure_all()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3966f447", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.974614 , 1. , 0.02428596])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "main(backend, msg,\n", + " qc,\n", + " expectation_operators=['ZZZZ', 'IIII', 'IZZZ'],\n", + " transpiler_config={'optimization_level':3, 'layout_method': 'sabre',\n", + " 'routing_method': 'sabre'},\n", + " run_config={},\n", + " skip_transpilation=False,\n", + " return_stddev=False,\n", + " use_measurement_mitigation=True\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "fafe3295", + "metadata": {}, + "source": [ + "If we have done our job correctly, the above should print out two expectation values close to one and a final expectation value close to zero." + ] + }, + { + "cell_type": "markdown", + "id": "f1fa15d4", + "metadata": {}, + "source": [ + "## Program metadata\n", + "\n", + "Next we add the needed program data to a dictionary for uploading with our program." + ] + }, + { + "cell_type": "code", + "execution_count": 297, + "id": "cdb8037d", + "metadata": {}, + "outputs": [], + "source": [ + "meta = {\n", + " \"name\": \"sample-expval\",\n", + " \"description\": \"A sample expectation value program.\",\n", + " \"max_execution_time\": 1000,\n", + " \"spec\": {}\n", + "}\n", + "\n", + "meta[\"spec\"][\"parameters\"] = {\n", + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n", + " \"properties\": {\n", + " \"circuits\": {\n", + " \"description\": \"A single or list of QuantumCircuits.\",\n", + " \"type\": [\n", + " \"array\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"expectation_operators\": {\n", + " \"description\": \"One or more expectation values to evaluate.\",\n", + " \"type\": [\n", + " \"string\",\n", + " \"object\",\n", + " \"array\"\n", + " ]\n", + " },\n", + " \"shots\": {\n", + " \"description\": \"Number of shots per circuit.\",\n", + " \"type\": \"integer\"\n", + " },\n", + " \"transpiler_config\": {\n", + " \"description\": \"A collection of kwargs passed to transpile.\",\n", + " \"type\": \"object\"\n", + " },\n", + " \"run_config\": {\n", + " \"description\": \"A collection of kwargs passed to backend.run. Default is False.\",\n", + " \"type\": \"object\",\n", + " \"default\": False\n", + " },\n", + " \"return_stddev\": {\n", + " \"description\": \"Return upper-bound on standard deviation. Default is False.\",\n", + " \"type\": \"boolean\",\n", + " \"default\": False\n", + " },\n", + " \"use_measurement_mitigation\": {\n", + " \"description\": \"Use measurement mitigation to improve results. Default is False.\",\n", + " \"type\": \"boolean\",\n", + " \"default\": False\n", + " }\n", + " },\n", + " \"required\": [\n", + " \"circuits\"\n", + " ]\n", + "}\n", + "\n", + "meta[\"spec\"][\"return_values\"] = {\n", + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n", + " \"description\": \"A list of expectation values and optionally standard deviations.\",\n", + " \"type\": \"array\"\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "09521947", + "metadata": {}, + "source": [ + "## Upload the program\n", + "\n", + "We are now in a position to upload the program. To do so we first uncomment and excute the line `%%writefile sample_expval.py` giving use the `sample_expval.py` file we need to upload. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "935a21d6", + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit import IBMQ\n", + "IBMQ.load_account();" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d93c23d6", + "metadata": {}, + "outputs": [], + "source": [ + "provider = IBMQ.get_provider(group='deployed')" + ] + }, + { + "cell_type": "code", + "execution_count": 276, + "id": "59a3e697", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": "'sample-expval-KLGD4Kbn97'" + }, + "execution_count": 276, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "program_id = provider.runtime.upload_program(data='sample_expval.py', metadata=meta)\n", + "program_id" + ] + }, + { + "cell_type": "markdown", + "id": "58430b3e", + "metadata": {}, + "source": [ + "### Delete program if needed" + ] + }, + { + "cell_type": "code", + "execution_count": 255, + "id": "4ec662be", + "metadata": {}, + "outputs": [], + "source": [ + "#provider.runtime.delete_program(program_id)" + ] + }, + { + "cell_type": "markdown", + "id": "8542a282", + "metadata": {}, + "source": [ + "## Wrapping the runtime program\n", + "\n", + "As always, it is best to wrap the call to the runtime program with a function (or possibly a class) that makes input easier and does some validation." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c685ef09", + "metadata": {}, + "outputs": [], + "source": [ + "def expectation_value_runner(backend,\n", + " circuits,\n", + " expectation_operators='',\n", + " shots = 8192,\n", + " transpiler_config={},\n", + " run_config={},\n", + " skip_transpilation=False,\n", + " return_stddev=False,\n", + " use_measurement_mitigation=False):\n", + " \n", + " \"\"\"Compute expectation values for a list of operators after\n", + " executing a list of circuits on the target backend.\n", + " \n", + " Parameters:\n", + " backend (Backend or str): Qiskit backend instance or name.\n", + " circuits: (QuantumCircuit or list): A single or list of QuantumCircuits.\n", + " expectation_operators (str or dict or list): Expectation values to evaluate.\n", + " shots (int): Number of shots to take per circuit.\n", + " transpiler_config (dict): A collection of kwargs passed to transpile().\n", + " run_config (dict): A collection of kwargs passed to backend.run().\n", + " return_stddev (bool): Return upper bound on standard devitation,\n", + " default=False. \n", + " skip_transpilation (bool): Skip transpiling of circuits, default=False.\n", + " use_measurement_mitigation (bool): Improve resulting using measurement\n", + " error mitigation, default=False.\n", + " \n", + " Returns:\n", + " array_like: Returns array of expectation values or a list of (expval, stddev)\n", + " pairs if return_stddev=True.\n", + " \"\"\"\n", + " if not isinstance(backend, str):\n", + " backend = backend.name()\n", + " options = {'backend_name': backend}\n", + " \n", + " if isinstance(circuits, list) and len(circuits) != 1:\n", + " if isinstance(expectation_operators, list):\n", + " if len(circuits) != 1 and len(expectation_operators) == 1:\n", + " expectation_operators = expectation_operators*len(circuits)\n", + " elif len(circuits) != len(expectation_operators): \n", + " raise ValueError('Number of circuits must match number of expectation \\\n", + " values if more than one of each')\n", + " inputs = {}\n", + " inputs['circuits'] = circuits\n", + " inputs['expectation_operators'] = expectation_operators\n", + " inputs['shots'] = shots\n", + " inputs['transpiler_config'] = transpiler_config\n", + " inputs['run_config'] = run_config\n", + " inputs['return_stddev'] = return_stddev\n", + " inputs['skip_transpilation'] = skip_transpilation\n", + " inputs['use_measurement_mitigation'] = use_measurement_mitigation\n", + " \n", + " return provider.runtime.run('sample-expval', options=options, inputs=inputs)" + ] + }, + { + "cell_type": "markdown", + "id": "766a8961", + "metadata": {}, + "source": [ + "### Trying it out\n", + "\n", + "Because we made our program public anyone can try it out. Lets do so here with our previously made GHZ state and running on the simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b9f2a955", + "metadata": {}, + "outputs": [], + "source": [ + "backend = provider.backend.ibmq_qasm_simulator\n", + "\n", + "all_zeros_proj = {'0000': 1}\n", + "all_ones_proj = {'1111': 1}\n", + "job = expectation_value_runner(backend, qc, [all_zeros_proj, all_ones_proj, 'ZZZZ'])" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "beb8550b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.50012207, 0.49987793, 1. ])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job.result()" + ] + }, + { + "cell_type": "markdown", + "id": "29268312", + "metadata": {}, + "source": [ + "The first two projectors should be nearly $0.50$ as they tell use the probability of being in the all zeros and ones states, respectively, which should be 50/50 for our GHZ state. The final expectation value of `ZZZZ` should be one since this is a GHZ over an even number of qubits. It should go close to zero for an odd number." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "5494d586", + "metadata": {}, + "outputs": [], + "source": [ + "qc2 = QuantumCircuit(3)\n", + "qc2.h(2)\n", + "qc2.cx(2, 1)\n", + "qc2.cx(1, 0)\n", + "qc2.measure_all()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "2c326f9d", + "metadata": {}, + "outputs": [], + "source": [ + "all_zeros_proj = {'000': 1}\n", + "all_ones_proj = {'111': 1}\n", + "job2 = expectation_value_runner(backend, qc2, [all_zeros_proj, all_ones_proj, 'ZZZ'])" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "58d9a637", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.50415039, 0.49584961, 0.00830078])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job2.result()" + ] + }, + { + "cell_type": "markdown", + "id": "05ff6e0d", + "metadata": {}, + "source": [ + "## Quantum Volume as an expectation value\n", + "\n", + "Here we formulate QV as an expectation value of a projector onto the heavy-output elements on a distribution. We can then use our expectation value routine to compute whether a given circuit has passed the QV metric.\n", + "\n", + "QV is defined in terms of heavy-ouputs of a distribution. Heavy-outputs are those bit-strings that are those that have probabilities above the median value of the distribution. Below we define the projection operator onto the set of bit-strings that are heavy-outputs for a given distribution." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "778b6a77", + "metadata": {}, + "outputs": [], + "source": [ + "def heavy_projector(qv_probs):\n", + " \"\"\"Forms the projection operator onto the heavy-outputs of a given probability distribution.\n", + "\n", + " Parameters:\n", + " qv_probs (dict): A dictionary of bitstrings and associated probabilities.\n", + "\n", + " Returns:\n", + " dict: Projector onto the heavy-set.\n", + " \"\"\"\n", + " median_prob = np.median(list(qv_probs.values()))\n", + " heavy_strs = {}\n", + " for key, val in qv_probs.items():\n", + " if val > median_prob:\n", + " heavy_strs[key] = 1\n", + " return heavy_strs" + ] + }, + { + "cell_type": "markdown", + "id": "1c05e1b2", + "metadata": {}, + "source": [ + "Now we generate 10 QV circuits as our dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "20fab8af", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from qiskit.quantum_info import Statevector\n", + "from qiskit.circuit.library import QuantumVolume\n", + "# Generate QV circuits\n", + "N = 10\n", + "qv_circs = [QuantumVolume(5) for _ in range(N)]" + ] + }, + { + "cell_type": "markdown", + "id": "927a0946", + "metadata": {}, + "source": [ + "Next, we have to determine the heavy-set of each circuit from the ideal answer, and then pass this along to our heavy-set projector function that we defined above." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "73d822cc", + "metadata": {}, + "outputs": [], + "source": [ + "ideal_probs = [Statevector.from_instruction(circ).probabilities_dict() for circ in qv_circs]\n", + "heavy_projectors = [heavy_projector(probs) for probs in ideal_probs]" + ] + }, + { + "cell_type": "markdown", + "id": "33559bc5", + "metadata": {}, + "source": [ + "QV circuits have no meaasurements on them so need to add them:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "bf818575", + "metadata": {}, + "outputs": [], + "source": [ + "circs = [circ.measure_all(inplace=False) for circ in qv_circs]" + ] + }, + { + "cell_type": "markdown", + "id": "dd9579fb", + "metadata": {}, + "source": [ + "With a list of circuits and projection operators we now need only to pass both sets to our above expection value runner targeting the desired backend. We will also set the best transpiler arguments to give us a sporting chance of getting some passing scores." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "8d692921", + "metadata": {}, + "outputs": [], + "source": [ + "backend = provider.backend.ibmq_manila" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "5c169661", + "metadata": {}, + "outputs": [], + "source": [ + "job3 = expectation_value_runner(backend, circs, heavy_projectors,\n", + " transpiler_config={'optimization_level':3, 'layout_method': 'sabre',\n", + " 'routing_method': 'sabre'})" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "98f6efd9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.52392578, 0.60400391, 0.57189941, 0.57897949, 0.7734375 ,\n", + " 0.65844727, 0.56225586, 0.73706055, 0.69030762, 0.61193848])" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qv_scores = job3.result()\n", + "qv_scores" + ] + }, + { + "cell_type": "markdown", + "id": "947b5149", + "metadata": {}, + "source": [ + "A passing QV score is one where the value of the heavy-set projector is above $2/3$. So let us see who passed:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "3f6394d6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([False, False, False, False, True, False, False, True, True,\n", + " False])" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qv_scores > 2/3" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "0a8fe223", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

This code is a part of Qiskit

© Copyright IBM 2017, 2021.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from qiskit.tools.jupyter import *\n", + "%qiskit_copyright" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c482e31", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/sample_expval_program/sample_expval.py b/tutorials/sample_expval_program/sample_expval.py new file mode 100644 index 000000000..72ba1edd6 --- /dev/null +++ b/tutorials/sample_expval_program/sample_expval.py @@ -0,0 +1,91 @@ +import mthree +from qiskit import transpile + +# The entrypoint for our Runtime Program +def main(backend, user_messenger, + circuits, + expectation_operators='', + shots = 8192, + transpiler_config={}, + run_config={}, + skip_transpilation=False, + return_stddev=False, + use_measurement_mitigation=False, + ): + + """Compute expectation values for a list of operators after + executing a list of circuits on the target backend. + + Parameters: + backend (ProgramBackend): Qiskit backend instance. + user_messenger (UserMessenger): Used to communicate with the program user. + circuits: (QuantumCircuit or list): A single list of QuantumCircuits. + expectation_operators (str or dict or list): Expectation values to evaluate. + shots (int): Number of shots to take per circuit. + transpiler_config (dict): A collection of kwargs passed to transpile(). + run_config (dict): A collection of kwargs passed to backend.run(). + skip_transpilation (bool): Skip transpiling of circuits, default=False. + return_stddev (bool): Return upper bound on standard devitation, + default=False. + use_measurement_mitigation (bool): Improve resulting using measurement + error mitigation, default=False. + + Returns: + array_like: Returns array of expectation values or a list of (expval, stddev) + tuples if return_stddev=True. + """ + + # transpiling the circuits using given transpile options + if not skip_transpilation: + trans_circuits = transpile(circuits, backend=backend, + **transpiler_config) + + if not isinstance(trans_circuits, list): + trans_circuits = [trans_circuits] + # If skipping set circuits -> trans_circuits + else: + if not isinstance(circuits, list): + trans_circuits = [circuits] + else: + trans_circuits = circuits + + # If we are given a single circuit but requesting multiple expectation values + # Then set flag to make multiple pointers to same result. + duplicate_results = False + if isinstance(expectation_operators, list): + if len(expectation_operators) and len(trans_circuits) == 1: + duplicate_results = True + + if use_measurement_mitigation: + # Get an the measurement mappings at end of circuits + meas_maps = mthree.utils.final_measurement_mapping(trans_circuits) + # Get an M3 mitigator + mit = mthree.M3Mitigation(backend) + # Calibrate over the set of qubits measured in the transpiled circuits. + mit.cals_from_system(meas_maps) + + # Compute raw results + result = backend.run(trans_circuits, shots=shots, **run_config).result() + raw_counts = result.get_counts() + + # When using measurement mitigation we need to apply the correction and then + # compute the expectation values from the computed quasi-probabilities. + if use_measurement_mitigation: + quasi_dists = mit.apply_correction(raw_counts, meas_maps, + return_mitigation_overhead=return_stddev) + + if duplicate_results: + quasi_dists = mthree.classes.QuasiCollection([quasi_dists]*len(expectation_operators)) + # There are two different calls depending on what we want returned. + if return_stddev: + return quasi_dists.expval_and_stddev(expectation_operators) + return quasi_dists.expval(expectation_operators) + + # If the program didn't return in the mitigation loop above it means + # we are processing the raw_counts data. We do so here using the + # mthree utilities + if duplicate_results: + raw_counts = [raw_counts]*len(expectation_operators) + if return_stddev: + return mthree.utils.expval_and_stddev(raw_counts, expectation_operators) + return mthree.utils.expval(raw_counts, expectation_operators) diff --git a/tutorials/sample_vqe_program/qiskit_runtime_vqe_program.ipynb b/tutorials/sample_vqe_program/qiskit_runtime_vqe_program.ipynb new file mode 100644 index 000000000..4c3ab084a --- /dev/null +++ b/tutorials/sample_vqe_program/qiskit_runtime_vqe_program.ipynb @@ -0,0 +1,1381 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "98d4d396", + "metadata": {}, + "source": [ + "# Creating Custom Programs for Qiskit Runtime\n", + "\n", + "

\n", + "Paul Nation\n", + "

\n", + "

\n", + "IBM Quantum Partners Technical Enablement Team\n", + "

\n", + "\n", + "Here we will demonstrate how to create, upload, and use a custom Program for Qiskit Runtime. As the utility of the Runtime execution engine lies in its ability to execute many quantum circuits with low latencies, this tutorial will show how to create your own Variational Quantum Eigensolver (VQE) program from scratch." + ] + }, + { + "cell_type": "markdown", + "id": "96b86d47", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "- You must have Qiskit 0.30+ installed.\n", + "- You must have an IBM Quantum account with the ability to upload a runtime program. You have this ability if you belong to more than one provider." + ] + }, + { + "cell_type": "markdown", + "id": "46817643", + "metadata": {}, + "source": [ + "## Current limitations\n", + "\n", + "The runtime execution engine currently has the following limitations that must be kept in mind:\n", + "\n", + "- The Docker images used by the runtime include only Qiskit and its dependencies, with few exceptions. One exception is the inclusion of the `mthree` measurement mitigation package.\n", + "\n", + "\n", + "- For security reasons, the runtime cannot make internet calls outside of the environment.\n", + "\n", + "\n", + "- Your runtime program name must not contain an underscore`_`, otherwise it will cause an error when you try to execute it.\n", + "\n", + "As Qiskit Runtime matures, these limitations will be removed." + ] + }, + { + "cell_type": "markdown", + "id": "98e4b3ac", + "metadata": {}, + "source": [ + "## Simple VQE\n", + "\n", + "VQE is an hybrid quantum-classical optimization procedure that finds the lowest eigenstate and eigenenergy of a linear system defined by a given Hamiltonian of Pauli operators. For example, consider the following two-qubit Hamiltonian:\n", + "\n", + "\n", + "$$\n", + "H = A X_{1}\\otimes X_{0} + A Y_{1}\\otimes Y_{0} + A Z_{1}\\otimes Z_{0},\n", + "$$\n", + "\n", + "where $A$ is numerical coefficient and the subscripts label the qubits on which the operators act. The zero index being farthest right is the ordering used in Qiskit. The Pauli operators tell us which measurement basis to to use when measuring each of the qubits.\n", + "\n", + "We want to find the ground state (lowest energy state) of this Hamiltonian, and the associated eigenvector. To do this we must start at a given initial state and iteratively vary the parameters that define this state using a classical optimizer, such that the computed energies of subsequent steps are nominally lower than those previously. The parameterized state of the system is defined by an ansatz quantum circuit that should have non-zero support in the direction of the ground state. Because in general we do not know the solution, the choice of ansatz circuit can be highly problem-specific with a form dictated by additional information. For further information about variational algorithms, we point the reader to [Nature Reviews Physics volume 3, 625 (2021)](https://doi.org/10.1038/s42254-021-00348-9).\n", + "\n", + "\n", + "Thus we need at least the following inputs to create our VQE quantum program:\n", + "\n", + "1. A representation of the Hamiltonian that specifies the problem.\n", + "\n", + "\n", + "2. A choice of parameterized ansatz circuit, and the ability to pass configuration options, if any.\n", + "\n", + "\n", + "However, the following are also beneficial inputs that users might want to have:\n", + "\n", + "3. Add the ability to pass an initial state.\n", + "\n", + "\n", + "4. Vary the number of shots that are taken.\n", + "\n", + "\n", + "5. Ability to select which classical optimizer is used, and set configuraton values, if any. \n", + "\n", + "\n", + "6. Ability to turn on and off measurement mitigation.\n" + ] + }, + { + "cell_type": "markdown", + "id": "d15ad0da", + "metadata": {}, + "source": [ + "## Specifying the form of the input values\n", + "\n", + "All inputs to runtime programs must be serializable objects. That is to say, whatever you pass into a runtime program must be able to be converted to JSON format. It is thus beneficial to keep inputs limited to basic data types and structures unless you have experience with custom object serialization, or they are common Qiskit types such as ``QuantumCircuit`` etc that the built-in `RuntimeEncoder` can handle. Fortunately, the VQE program described above can be made out of simple Python components.\n", + "\n", + "First, it is possible to represent any Hamiltonian using a list of values with each containing the numerical coefficeint for each term and the string representation for the Pauli operators. For the above example, the ground state energy with $A=1$ is $-3$ and we can write it as:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "78b7a519", + "metadata": {}, + "outputs": [], + "source": [ + "H = [(1, 'XX'), (1, 'YY'), (1, 'ZZ')]" + ] + }, + { + "cell_type": "markdown", + "id": "fca6d7c5", + "metadata": {}, + "source": [ + "Next we have to provide the ability to specify the parameterized Ansatz circuit. Here we will take advange of the fact that many ansatz circuits are pre-defined in the Qiskit Circuit Library. Examples can be found in the [N-local circuits section](https://qiskit.org/documentation/apidoc/circuit_library.html#n-local-circuits).\n", + "\n", + "We would like the user to be able to select between ansatz options such as: `NLocal`, `TwoLocal`, and `EfficientSU2`. We could have the user pass the whole ansatz circuit to the program; however, in order to reduce the size of the upload we will pass the ansatz by name. In the runtime program, we can take this name and get the class that it corresponds to from the library using, for example, " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "421f2f51", + "metadata": {}, + "outputs": [], + "source": [ + "import qiskit.circuit.library.n_local as lib_local\n", + "\n", + "ansatz = getattr(lib_local, 'EfficientSU2')" + ] + }, + { + "cell_type": "markdown", + "id": "48c7ebae", + "metadata": {}, + "source": [ + "For the ansatz configuration, we will pass a simple `dict` of values." + ] + }, + { + "cell_type": "markdown", + "id": "a592ac05", + "metadata": {}, + "source": [ + "### Optionals \n", + "\n", + "- If we want to add the ability to pass an initial state, then we will need to add the ability to pass a 1D list/ NumPy array. Because the number of parameters depends on the ansatz and its configuration, the user would have to know what ansatz they are doing ahead of time.\n", + "\n", + "\n", + "- Selecting a number of shots requires simply passing an integer value.\n", + "\n", + "\n", + "- Here we will allow selecting a classical optimizer by name from those in SciPy, and a `dict` of configuration parameters. Note that for execution on an actual system, the noise inherent in today's quantum systems makes having a stochastic optimizer crucial to success. SciPy does not have such a choice, and the one built into Qiskit is wrapped in such a manner as to make it difficult to use elsewhere. As such, here we will use an SPSA optimizer written to match the style of those in SciPy. This function is given in [Appendix A](#Appendix-A)." + ] + }, + { + "cell_type": "markdown", + "id": "8c5964c1", + "metadata": {}, + "source": [ + "- Finally, for measurement error mitigation we can simply pass a boolean (True/False) value." + ] + }, + { + "cell_type": "markdown", + "id": "00df4e79", + "metadata": {}, + "source": [ + "## Main program\n", + "\n", + "We are now in a position to start building our main program. However, before doing so we point out that it makes the code cleaner to make a separate fuction that takes strings of Pauli operators that define our Hamiltonian and convert them to a list of circuits with single-qubit gates that change the measurement basis for each qubit, if needed. This function is given in [Appendix B](#Appendix-B)." + ] + }, + { + "cell_type": "markdown", + "id": "3ddbadd8", + "metadata": {}, + "source": [ + "### Required signature\n", + "\n", + "Every runtime program is defined via the `main` function, and must have the following input signature:\n", + "\n", + "```\n", + "main(backend, user_message, *args, **kwargs)\n", + "```\n", + "\n", + "where `backend` is the backend that the program is to be executed on, and `user_message` is the class by which interim (and possibly final) results are communicated back to the user. After these two items, we add our program-specific arguments and keyword arguments." + ] + }, + { + "cell_type": "markdown", + "id": "28ba84cc", + "metadata": {}, + "source": [ + "### The main VQE program\n", + "\n", + "Here is the main program for our sample VQE. What each element of the function does is written in the comments before the element appears." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "96bb7811", + "metadata": {}, + "outputs": [], + "source": [ + "# Grab functions and modules from dependencies\n", + "import numpy as np\n", + "import scipy.optimize as opt\n", + "from scipy.optimize import OptimizeResult\n", + "import mthree\n", + "\n", + "# Grab functions and modules from Qiskit needed\n", + "from qiskit import QuantumCircuit, transpile\n", + "import qiskit.circuit.library.n_local as lib_local\n", + "\n", + "# The entrypoint for our Runtime Program\n", + "def main(backend, user_messenger,\n", + " hamiltonian,\n", + " ansatz='EfficientSU2',\n", + " ansatz_config={},\n", + " x0=None,\n", + " optimizer='SPSA',\n", + " optimizer_config={'maxiter': 100},\n", + " shots = 8192,\n", + " use_measurement_mitigation=False\n", + " ):\n", + " \n", + " \"\"\"\n", + " The main sample VQE program.\n", + " \n", + " Parameters:\n", + " backend (ProgramBackend): Qiskit backend instance.\n", + " user_messenger (UserMessenger): Used to communicate with the\n", + " program user.\n", + " hamiltonian (list): Hamiltonian whose ground state we want to find.\n", + " ansatz (str): Optional, name of ansatz quantum circuit to use,\n", + " default='EfficientSU2'\n", + " ansatz_config (dict): Optional, configuration parameters for the\n", + " ansatz circuit.\n", + " x0 (array_like): Optional, initial vector of parameters.\n", + " optimizer (str): Optional, string specifying classical optimizer,\n", + " default='SPSA'.\n", + " optimizer_config (dict): Optional, configuration parameters for the\n", + " optimizer.\n", + " shots (int): Optional, number of shots to take per circuit.\n", + " use_measurement_mitigation (bool): Optional, use measurement mitigation,\n", + " default=False.\n", + " \n", + " Returns:\n", + " OptimizeResult: The result in SciPy optimization format. \n", + " \"\"\"\n", + " \n", + " # Split the Hamiltonian into two arrays, one for coefficients, the other for\n", + " # operator strings\n", + " coeffs = np.array([item[0] for item in hamiltonian], dtype=complex)\n", + " op_strings = [item[1] for item in hamiltonian]\n", + " # The number of qubits needed is given by the number of elements in the strings\n", + " # the defiune the Hamiltonian. Here we grab this data from the first element.\n", + " num_qubits = len(op_strings[0])\n", + " \n", + " # We grab the requested ansatz circuit class from the Qiskit circuit library\n", + " # n_local module and configure it using the number of qubits and options\n", + " # passed in the ansatz_config.\n", + " ansatz_instance = getattr(lib_local, ansatz)\n", + " ansatz_circuit = ansatz_instance(num_qubits, **ansatz_config)\n", + " \n", + " # Here we use our convenence function from Appendix B to get measurement circuits\n", + " # with the correct single-qubit rotation gates.\n", + " meas_circs = opstr_to_meas_circ(op_strings)\n", + " \n", + " # When computing the expectation value for the energy, we need to know if we\n", + " # evaluate a Z measurement or and identity measurement. Here we take and X and Y\n", + " # operator in the strings and convert it to a Z since we added the rotations\n", + " # with the meas_circs.\n", + " meas_strings = [string.replace('X', 'Z').replace('Y', 'Z') for string in op_strings]\n", + " \n", + " # Take the ansatz circuits, add the single-qubit measurement basis rotations from\n", + " # meas_circs, and finally append the measurements themselves.\n", + " full_circs = [ansatz_circuit.compose(mcirc).measure_all(inplace=False) for mcirc in meas_circs]\n", + " \n", + " # Get the number of parameters in the ansatz circuit.\n", + " num_params = ansatz_circuit.num_parameters\n", + " \n", + " # Use a given initial state, if any, or do random initial state.\n", + " if x0:\n", + " x0 = np.asarray(x0, dtype=float)\n", + " if x0.shape[0] != num_params:\n", + " raise ValueError('Number of params in x0 ({}) does not match number \\\n", + " of ansatz parameters ({})'. format(x0.shape[0],\n", + " num_params))\n", + " else:\n", + " x0 = 2*np.pi*np.random.rand(num_params)\n", + " \n", + " # Because we are in general targeting a real quantum system, our circuits must be transpiled\n", + " # to match the system topology and, hopefully, optimize them.\n", + " # Here we will set the transpiler to the most optimal settings where 'sabre' layout and\n", + " # routing are used, along with full O3 optimization.\n", + "\n", + " # This works around a bug in Qiskit where Sabre routing fails for simulators (Issue #7098)\n", + " trans_dict = {}\n", + " if not backend.configuration().simulator:\n", + " trans_dict = {'layout_method': 'sabre', 'routing_method': 'sabre'}\n", + " trans_circs = transpile(full_circs, backend, optimization_level=3, **trans_dict)\n", + " \n", + " # If using measurement mitigation we need to find out which physical qubits our transpiled\n", + " # circuits actually measure, construct a mitigation object targeting our backend, and\n", + " # finally calibrate our mitgation by running calibration circuits on the backend.\n", + " if use_measurement_mitigation:\n", + " maps = mthree.utils.final_measurement_mapping(trans_circs)\n", + " mit = mthree.M3Mitigation(backend)\n", + " mit.cals_from_system(maps)\n", + " \n", + " # Here we define a callback function that will stream the optimizer parameter vector\n", + " # back to the user after each iteration. This uses the `user_messenger` object.\n", + " # Here we convert to a list so that the return is user readable locally, but\n", + " # this is not required.\n", + " def callback(xk):\n", + " user_messenger.publish(list(xk))\n", + " \n", + " # This is the primary VQE function executed by the optimizer. This function takes the \n", + " # parameter vector as input and returns the energy evaluated using an ansatz circuit\n", + " # bound with those parameters.\n", + " def vqe_func(params):\n", + " # Attach (bind) parameters in params vector to the transpiled circuits.\n", + " bound_circs = [circ.bind_parameters(params) for circ in trans_circs]\n", + " \n", + " # Submit the job and get the resultant counts back\n", + " counts = backend.run(bound_circs, shots=shots).result().get_counts()\n", + " \n", + " # If using measurement mitigation apply the correction and\n", + " # compute expectation values from the resultant quasiprobabilities\n", + " # using the measurement strings.\n", + " if use_measurement_mitigation:\n", + " quasi_collection = mit.apply_correction(counts, maps)\n", + " expvals = quasi_collection.expval(meas_strings)\n", + " # If not doing any mitigation just compute expectation values\n", + " # from the raw counts using the measurement strings.\n", + " # Since Qiskit does not have such functionality we use the convenence\n", + " # function from the mthree mitigation module.\n", + " else:\n", + " expvals = mthree.utils.expval(counts, meas_strings)\n", + " \n", + " # The energy is computed by simply taking the product of the coefficients\n", + " # and the computed expectation values and summing them. Here we also\n", + " # take just the real part as the coefficients can possibly be complex,\n", + " # but the energy (eigenvalue) of a Hamiltonian is always real.\n", + " energy = np.sum(coeffs*expvals).real\n", + " return energy\n", + " \n", + " # Here is where we actually perform the computation. We begin by seeing what\n", + " # optimization routine the user has requested, eg. SPSA verses SciPy ones,\n", + " # and dispatch to the correct optimizer. The selected optimizer starts at\n", + " # x0 and calls 'vqe_func' everytime the optimizer needs to evaluate the cost\n", + " # function. The result is returned as a SciPy OptimizerResult object.\n", + " # Additionally, after every iteration, we use the 'callback' function to\n", + " # publish the interm results back to the user. This is important to do\n", + " # so that if the Program terminates unexpectedly, the user can start where they\n", + " # left off.\n", + " \n", + " # Since SPSA is not in SciPy need if statement\n", + " if optimizer == 'SPSA':\n", + " res = fmin_spsa(vqe_func, x0, args=(), **optimizer_config,\n", + " callback=callback)\n", + " # All other SciPy optimizers here\n", + " else:\n", + " res = opt.minimize(vqe_func, x0, method=optimizer,\n", + " options=optimizer_config, callback=callback)\n", + " # Return result. OptimizeResult is a subclass of dict.\n", + " return res" + ] + }, + { + "cell_type": "markdown", + "id": "8ab3432a", + "metadata": {}, + "source": [ + "## Local testing\n", + "\n", + "
\n", + "Important: You need to execute the code blocks in Appendices A and B before continuing.\n", + "
\n", + "\n", + "We can test whether our routine works by simply calling the `main` function with a backend instance, a `UserMessenger`, and sample arguments." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c146a02e", + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit.providers.ibmq.runtime import UserMessenger\n", + "msg = UserMessenger()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "ea291c28", + "metadata": {}, + "outputs": [], + "source": [ + "# Use the local Aer simulator\n", + "from qiskit import Aer\n", + "backend = Aer.get_backend('qasm_simulator')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3f071e0f", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1.419780432710152, 2.3984284215892018, 1.1306533554149105, 1.8357672762510684, 5.414120644000338, 6.107301966755861, -0.013391355872252708, 5.615586607539193, 4.211781149943555, 1.792388243059789, 4.203949657158362, 0.1038271369149637, 2.4220098073658884, 4.617958787629208, 2.9969591661895865, 1.5490655190231735]\n", + "[2.1084925021737537, 3.0871404910528035, 0.4419412859513089, 2.52447934571467, 4.725408574536736, 5.418589897292259, -0.7021034253358543, 6.3042986770027944, 3.523069080479953, 1.1036761735961873, 3.5152375876947604, 0.7925392063785653, 3.11072187682949, 5.30667085709281, 3.685671235653188, 0.8603534495595718]\n", + "[1.7365578685005831, 3.459075124725974, 0.8138759196244794, 2.8964139793878405, 4.353473940863566, 5.046655263619089, -1.0740380590090248, 5.932364043329624, 3.1511344468067826, 1.475610807269358, 3.8871722213679307, 1.1644738400517358, 2.73878724315632, 4.934736223419639, 4.057605869326359, 1.2322880832327423]\n", + "[1.7839871181735734, 3.4116458750529834, 0.766446669951489, 2.84898472971485, 4.306044691190576, 5.094084513292079, -1.0266088093360346, 5.884934793656634, 3.198563696479773, 1.5230400569423481, 3.8397429716949403, 1.1170445903787456, 2.6913579934833294, 4.887306973746649, 4.105035118999349, 1.2797173329057325]\n", + "[1.122687940285629, 4.072945052940928, 1.4277458478394336, 2.1876855518269056, 3.6447455133026314, 5.755383691180024, -1.687907987223979, 6.546233971544579, 2.5372645185918286, 2.1843392348302926, 4.501042149582885, 1.7783437682666903, 3.352657171371274, 4.226007795858704, 4.766334296887294, 0.618418155017788]\n" + ] + }, + { + "data": { + "text/plain": [ + " fun: -1.72705078125\n", + " message: 'Optimization terminated successfully.'\n", + " nfev: 10\n", + " nit: 5\n", + " success: True\n", + " x: array([ 1.12268794, 4.07294505, 1.42774585, 2.18768555, 3.64474551,\n", + " 5.75538369, -1.68790799, 6.54623397, 2.53726452, 2.18433923,\n", + " 4.50104215, 1.77834377, 3.35265717, 4.2260078 , 4.7663343 ,\n", + " 0.61841816])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Execute the main routine for our simple two-qubit Hamiltonian H, and perform 5 iterations of the SPSA solver.\n", + "main(backend, msg, H, optimizer_config={'maxiter': 5})" + ] + }, + { + "cell_type": "markdown", + "id": "1460bc50", + "metadata": {}, + "source": [ + "Having executed the above, we see that there are 5 parameter arrays returned, one for each callback, along with the final optimization result. The parameter arrays are the interim results, and the `UserMessenger` object prints these values to the cell output. The output itself is the answer we obtained, expressed as a SciPy `OptimizerResult` object." + ] + }, + { + "cell_type": "markdown", + "id": "d5d0151f", + "metadata": {}, + "source": [ + "## Program metadata\n", + "\n", + "Program metadata is essentially the docstring for a runtime program. It describes overall program information such as the program `name`, `description`, and the `max_execution_time` the program is allowed to run, as well as details the inputs and the outputs the program expects. At a bare minimum the values described above are required" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "2b3aa8f2", + "metadata": {}, + "outputs": [], + "source": [ + "meta = {\n", + " \"name\": \"sample-vqe\",\n", + " \"description\": \"A sample VQE program.\",\n", + " \"max_execution_time\": 100000,\n", + " \"spec\": {}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "be4e4dd2", + "metadata": {}, + "source": [ + "It is important to set the `max_execution_time` high enough so that your program does not get terminated unexpectedly. Additionally, one should make sure that interim results are sent back to the user so that, if something does happen, the user can start where they left off.\n", + "\n", + "It is, however, good form to detail the parameters and return types, as well as interim results. That being said, if making a runtime intended to be used by others, this information would also likely be mirrored in the signature of a function or class that the user would interact with directly; end users should not directly call runtime programs. We will see why below. Nevertheless, let us add to our metadata. First, the `parameters` section details the inputs the user is able to pass:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "d10c1b1a", + "metadata": {}, + "outputs": [], + "source": [ + "meta[\"spec\"][\"parameters\"] = {\n", + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n", + " \"properties\": {\n", + " \"hamiltonian\": {\n", + " \"description\": \"Hamiltonian whose ground state we want to find.\", \n", + " \"type\": \"array\"\n", + " },\n", + " \"ansatz\": {\n", + " \"description\": \"Name of ansatz quantum circuit to use, default='EfficientSU2'\",\n", + " \"type\": \"string\",\n", + " \"default\": \"EfficientSU2\"\n", + " },\n", + " \"ansatz_config\": {\n", + " \"description\": \"Configuration parameters for the ansatz circuit.\",\n", + " \"type\": \"object\"\n", + " },\n", + " \"optimizer\": {\n", + " \"description\": \"Classical optimizer to use, default='SPSA'.\",\n", + " \"type\": \"string\",\n", + " \"default\": \"SPSA\"\n", + " },\n", + " \"x0\": {\n", + " \"description\": \"Initial vector of parameters. This is a numpy array.\", \n", + " \"type\": \"array\"\n", + " },\n", + " \"optimizer_config\": {\n", + " \"description\": \"Configuration parameters for the optimizer.\", \n", + " \"type\": \"object\"\n", + " },\n", + " \"shots\": {\n", + " \"description\": \"The number of shots used for each circuit evaluation.\",\n", + " \"type\": \"integer\"\n", + " },\n", + " \"use_measurement_mitigation\": {\n", + " \"description\": \"Use measurement mitigation, default=False.\",\n", + " \"type\": \"boolean\",\n", + " \"default\": False\n", + " }\n", + " },\n", + " \"required\": [\n", + " \"hamiltonian\"\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "0ee9bb79", + "metadata": {}, + "source": [ + "Next, the `return_values` section tells about the return types:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "404e6d1b", + "metadata": {}, + "outputs": [], + "source": [ + "meta[\"spec\"][\"return_values\"] = {\n", + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n", + " \"description\": \"Final result in SciPy optimizer format\",\n", + " \"type\": \"object\"\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "affde208", + "metadata": {}, + "source": [ + "and finally let us specify what comes back when an interim result is returned:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "99a56745", + "metadata": {}, + "outputs": [], + "source": [ + "meta[\"spec\"][\"interim_results\"] = {\n", + " \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n", + " \"description\": \"Parameter vector at current optimization step. This is a numpy array.\", \n", + " \"type\": \"array\"\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "c55bbf04", + "metadata": {}, + "source": [ + "## Uploading the program\n", + "\n", + "We now have all the ingredients needed to upload our program. To do so we need to collect all of our code in one file, here called `sample_vqe.py` for uploading. This limitation will be removed in later versions of Qiskit Runtime. Alternatively, if the entire code is contained within a single Jupyter notebook cell, this can be done using the magic function\n", + "\n", + "```\n", + "%%writefile my_program.py\n", + "```\n", + "\n", + "To actually upload the program we need to get a Provider from our IBM Quantum account:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "9166cb5a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from qiskit import IBMQ\n", + "IBMQ.load_account()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "c2da132b", + "metadata": {}, + "outputs": [], + "source": [ + "provider = IBMQ.get_provider(group='deployed')" + ] + }, + { + "cell_type": "markdown", + "id": "cf539336", + "metadata": {}, + "source": [ + "### Program upload\n", + "\n", + "The call to `program_upload` takes the target Python file as `data` and the metadata as inputs." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "46ce62a5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": "'sample-vqe-G3YBjmvlPr'" + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "program_id = provider.runtime.upload_program(data='sample_vqe.py', metadata=meta)\n", + "program_id" + ] + }, + { + "cell_type": "markdown", + "id": "0d73dc6e", + "metadata": {}, + "source": [ + "Here the `upload_program()` method returns a `program_id`, which is how you should reference your program." + ] + }, + { + "cell_type": "markdown", + "id": "375651ac", + "metadata": {}, + "source": [ + "### Program information\n", + "\n", + "We can query the program for information and see that our metadata is correctly being attached:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "0e08a4d0", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "sample-vqe-G3YBjmvlPr:\n Name: sample-vqe\n Description: A sample VQE program.\n Creation date: 2021-11-10T17:10:18.903742Z\n Update date: 2021-11-10T17:10:18.903742Z\n Max execution time: 100000\n Input parameters:\n Properties:\n - ansatz:\n Default: EfficientSU2\n Description: Name of ansatz quantum circuit to use, default='EfficientSU2'\n Type: string\n Required: False\n - ansatz_config:\n Description: Configuration parameters for the ansatz circuit.\n Type: object\n Required: False\n - hamiltonian:\n Description: Hamiltonian whose ground state we want to find.\n Type: array\n Required: True\n - optimizer:\n Default: SPSA\n Description: Classical optimizer to use, default='SPSA'.\n Type: string\n Required: False\n - optimizer_config:\n Description: Configuration parameters for the optimizer.\n Type: object\n Required: False\n - shots:\n Description: The number of shots used for each circuit evaluation.\n Type: integer\n Required: False\n - use_measurement_mitigation:\n Default: False\n Description: Use measurement mitigation, default=False.\n Type: boolean\n Required: False\n - x0:\n Description: Initial vector of parameters. This is a numpy array.\n Type: array\n Required: False\n Interim results:\n Description: Parameter vector at current optimization step. This is a numpy array.\n Type: array\n Returns:\n Description: Final result in SciPy optimizer format\n Type: object\n" + } + ], + "source": [ + "prog = provider.runtime.program(program_id)\n", + "print(prog)" + ] + }, + { + "cell_type": "markdown", + "id": "70a9e564", + "metadata": {}, + "source": [ + "### Deleting a program\n", + "\n", + "If you make a mistake and need to delete and/or re-upload the program, you can run the following, passing the `program_id`:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "ec56e36b", + "metadata": {}, + "outputs": [], + "source": [ + "#provider.runtime.delete_program(program_id)" + ] + }, + { + "cell_type": "markdown", + "id": "4025eb01", + "metadata": {}, + "source": [ + "## Running the program\n", + "\n", + "### Specify parameters\n", + "\n", + "To run the program we need to specify the `options` that are used in the runtime environment (not the program variables). At present, only the `backend_name` is required." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "2efca1b3", + "metadata": {}, + "outputs": [], + "source": [ + "backend = provider.backend.ibmq_qasm_simulator\n", + "options = {'backend_name': backend.name()}" + ] + }, + { + "cell_type": "markdown", + "id": "c8b0b300", + "metadata": {}, + "source": [ + "The `inputs` dictionary is used to pass arguments to the `main` function itself. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "381794fa", + "metadata": {}, + "outputs": [], + "source": [ + "inputs = {}\n", + "inputs['hamiltonian'] = H\n", + "inputs['optimizer_config']={'maxiter': 10}" + ] + }, + { + "cell_type": "markdown", + "id": "8443e1ae", + "metadata": {}, + "source": [ + "### Execute the program\n", + "\n", + "We now can execute the program and grab the result." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "3b419193", + "metadata": {}, + "outputs": [], + "source": [ + "job = provider.runtime.run(program_id, options=options, inputs=inputs)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "0addcac0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'fun': -1.93994140625,\n", + " 'x': array([-1.28984461, 3.73974929, 3.52327612, 1.74979783, 3.13519544,\n", + " 2.43577395, 1.30425595, 0.04847941, 6.17766827, 1.92879213,\n", + " 1.95707213, 2.8097762 , 1.95108352, 1.20067124, 7.01868106,\n", + " 4.36507161]),\n", + " 'nit': 10,\n", + " 'nfev': 20,\n", + " 'message': 'Optimization terminated successfully.',\n", + " 'success': True}" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job.result()" + ] + }, + { + "cell_type": "markdown", + "id": "9e6e0ec0", + "metadata": {}, + "source": [ + "A few things need to be pointed out. First, we did not get back any interim results, and second, the return object is a plain dictionary. This is because we did not listen for the return results, and we did not tell the job how to format the return result." + ] + }, + { + "cell_type": "markdown", + "id": "917857da", + "metadata": {}, + "source": [ + "### Listening for interim results\n", + "\n", + "To listen for interm results we need to pass a callback function to `provider.runtime.run` that stores the results. The callback takes two arguments `job_id` and the returned data:" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "358bdc84", + "metadata": {}, + "outputs": [], + "source": [ + "interm_results = []\n", + "def vqe_callback(job_id, data):\n", + " interm_results.append(data)" + ] + }, + { + "cell_type": "markdown", + "id": "9ed2dabd", + "metadata": {}, + "source": [ + "Executing again we get:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "34b61c28", + "metadata": {}, + "outputs": [], + "source": [ + "job2 = provider.runtime.run(program_id, options=options, inputs=inputs, callback=vqe_callback)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "8d331f69", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'fun': -2.635986328125,\n", + " 'x': array([ 1.39625003, 3.10967996, 2.46291361, -0.09150619, 1.89013366,\n", + " 0.48872864, 5.60656903, 1.12770301, 4.04603538, 2.85551118,\n", + " 0.45677689, 3.46054286, 4.10740117, 4.163728 , 1.53949656,\n", + " 3.46634995]),\n", + " 'nit': 10,\n", + " 'nfev': 20,\n", + " 'message': 'Optimization terminated successfully.',\n", + " 'success': True}" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job2.result()" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "e83bf7ba", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1.1839280526666394, 2.391820224610454, 2.7491281736833244, 0.5771768054969294, 2.349087960882593, 0.20251406828095217, 5.3527505036344865, 1.80726551800796, 2.8686317344166947, 2.4545878612072003, -0.04047464122825306, 4.2780676963333795, 3.27599724292225, 3.5527489679560844, 2.1472927005219273, 3.1637626657075555], [1.1855194978035488, 2.3902287794735444, 2.750719618820234, 0.5755853603600198, 2.3506794060195024, 0.20092262314404263, 5.351159058497577, 1.8088569631448694, 2.870223179553604, 2.452996416070291, -0.04206608636516258, 4.27647625119647, 3.2775886880591596, 3.554340413092994, 2.148884145658837, 3.165354110844465], [1.0411904999135912, 2.534557777363502, 2.8950486167101914, 0.7199143582499773, 2.206350408129545, 0.05659362525408518, 5.206830060607619, 1.664527965254912, 3.0145521774435617, 2.5973254139602484, 0.10226291152479487, 4.420805249086427, 3.133259690169202, 3.6986694109829514, 2.004555147768879, 3.0210251129545074], [1.005580093753927, 2.5701681835231662, 2.9306590228698557, 0.7555247644096416, 2.241960814289209, 0.020983219094420913, 5.242440466767284, 1.7001383714145764, 3.050162583603226, 2.561715007800584, 0.13787331768445915, 4.456415655246091, 3.0976492840095378, 3.663059004823287, 2.0401655539285435, 3.0566355191141716], [1.07047876838977, 2.6350668581590093, 2.8657603482340126, 0.8204234390454845, 2.177062139653366, 0.08588189373026392, 5.307339141403126, 1.6352396967787333, 2.985263908967383, 2.496816333164741, 0.20277199232030216, 4.521314329881934, 3.162547958645381, 3.7279576794591303, 1.9752668792927004, 2.9917368444783285], [1.3994411335364108, 2.96402922330565, 3.1947227133806533, 0.4914610738988439, 2.5060245048000067, -0.2430804714163767, 5.636301506549767, 1.3062773316320926, 3.3142262741140236, 2.8257786983113817, -0.12619037282633846, 4.192351964735293, 3.4915103237920215, 3.3989953143124896, 2.304229244439341, 3.3206992096249692], [1.325020213130704, 3.0384501437113567, 3.1203017929749466, 0.5658819943045507, 2.5804454252057134, -0.16865955101066996, 5.710722426955474, 1.231856411226386, 3.3886471945197303, 2.751357777905675, -0.2006112932320452, 4.117931044329586, 3.417089403386315, 3.4734162347181963, 2.2298083240336344, 3.395120130030676], [1.031941029864989, 2.7453709604456416, 2.8272226097092314, 0.2728028110388356, 2.2873662419399983, 0.12441963225504513, 6.003801610221189, 1.524935594492101, 3.6817263777854454, 2.45827859463996, 0.09246789003366987, 3.8248518610638707, 3.71016858665203, 3.7664954179839114, 1.9367291407679192, 3.102040946764961], [1.4127118235825624, 3.126141754163215, 2.446451815991658, -0.10796798267873797, 1.9065954482224248, 0.5051904259726187, 5.623030816503616, 1.1441648007745275, 4.062497171503019, 2.8390493883575334, 0.47323868375124345, 3.444081067346297, 4.090939380369604, 4.147266211701485, 1.5559583470503457, 3.4828117404825343], [1.3962500340466297, 3.1096799646272824, 2.4629136055275906, -0.09150619314280523, 1.890133658686492, 0.4887286364366859, 5.606569026967683, 1.1277030112385948, 4.046035381967086, 2.855511177893466, 0.4567768942153107, 3.46054285688223, 4.107401169905537, 4.163728001237418, 1.539496557514413, 3.4663499509466016]]\n" + ] + } + ], + "source": [ + "print(interm_results)" + ] + }, + { + "cell_type": "markdown", + "id": "43f69e8a", + "metadata": {}, + "source": [ + "### Formatting the returned results\n", + "\n", + "In order to format the return results into the desired format, we need to specify a decoder. This decoder must have a `decode` method that gets called to do the actual conversion. In our case `OptimizeResult` is a simple sub-class of `dict` so the formatting is simple." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "ccdc7af3", + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit.providers.ibmq.runtime import ResultDecoder\n", + "from scipy.optimize import OptimizeResult\n", + "\n", + "class VQEResultDecoder(ResultDecoder):\n", + " @classmethod\n", + " def decode(cls, data):\n", + " data = super().decode(data) # This is required to preformat the data returned.\n", + " return OptimizeResult(data)" + ] + }, + { + "cell_type": "markdown", + "id": "5e0f0aae", + "metadata": {}, + "source": [ + "We can then use this when returning the job result:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "951eed95", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + " fun: -0.645751953125\n", + " message: 'Optimization terminated successfully.'\n", + " nfev: 20\n", + " nit: 10\n", + " success: True\n", + " x: array([ 5.72140052, 2.29687026, 4.13837683, 3.22216958, 4.76184762,\n", + " 1.20943004, 5.74244574, 2.22665936, 4.34308411, 3.8390838 ,\n", + " -0.50949471, 2.15587397, 3.19045035, 5.82751179, 1.95972168,\n", + " 3.75821819])" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job3 = provider.runtime.run(program_id, options=options, inputs=inputs)\n", + "job3.result(decoder=VQEResultDecoder)" + ] + }, + { + "cell_type": "markdown", + "id": "5ca7d039", + "metadata": {}, + "source": [ + "## Simplifying program execution with wrapping functions\n", + "\n", + "While runtime programs are powerful and flexible, they are not the most friendly things to interact with. Therefore, if your program is intended to be used by others, it is best to make wrapper functions and/or classes that simplify the user experience. Moreover, such wrappers allow for validation of user inputs on the client side, which can quickly find errors that would otherwise be raised later during the execution process - something that might have taken hours waiting in queue to get to.\n", + "\n", + "Here we will make two helper routines. First, a job wrapper that allows us to attach and retrieve the interim results directly from the job object itself, as well as decodes for us so that the end user need not worry about formatting the results themselves." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "c3460583", + "metadata": {}, + "outputs": [], + "source": [ + "class RuntimeJobWrapper():\n", + " \"\"\"A simple Job wrapper that attaches interm results directly to the job object itself\n", + " in the `interm_results attribute` via the `_callback` function.\n", + " \"\"\"\n", + " def __init__(self):\n", + " self._job = None\n", + " self._decoder = VQEResultDecoder\n", + " self.interm_results = []\n", + " \n", + " def _callback(self, job_id, xk):\n", + " \"\"\"The callback function that attaches interm results:\n", + " \n", + " Parameters:\n", + " job_id (str): The job ID.\n", + " xk (array_like): A list or NumPy array to attach.\n", + " \"\"\"\n", + " self.interm_results.append(xk)\n", + " \n", + " def __getattr__(self, attr):\n", + " if attr == 'result':\n", + " return self.result\n", + " else:\n", + " if attr in dir(self._job):\n", + " return getattr(self._job, attr)\n", + " raise AttributeError(\"Class does not have {}.\".format(attr))\n", + " \n", + " def result(self):\n", + " \"\"\"Get the result of the job as a SciPy OptimizerResult object.\n", + " \n", + " This blocks until job is done, cancelled, or errors.\n", + " \n", + " Returns:\n", + " OptimizerResult: A SciPy optimizer result object.\n", + " \"\"\"\n", + " return self._job.result(decoder=self._decoder)" + ] + }, + { + "cell_type": "markdown", + "id": "0f336b6d", + "metadata": {}, + "source": [ + "Next, we create the actual function we want users to call to execute our program. To this function we will add a series of simple validation checks (not all checks will be done for simplicity), as well as use the job wrapper defined above to simply the output." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "aa386027", + "metadata": {}, + "outputs": [], + "source": [ + "import qiskit.circuit.library.n_local as lib_local\n", + "\n", + "def vqe_runner(backend, hamiltonian,\n", + " ansatz='EfficientSU2', ansatz_config={},\n", + " x0=None, optimizer='SPSA',\n", + " optimizer_config={'maxiter': 100},\n", + " shots = 8192,\n", + " use_measurement_mitigation=False):\n", + " \n", + " \"\"\"Routine that executes a given VQE problem via the sample-vqe program on the target backend.\n", + " \n", + " Parameters:\n", + " backend (ProgramBackend): Qiskit backend instance.\n", + " hamiltonian (list): Hamiltonian whose ground state we want to find.\n", + " ansatz (str): Optional, name of ansatz quantum circuit to use, default='EfficientSU2'\n", + " ansatz_config (dict): Optional, configuration parameters for the ansatz circuit.\n", + " x0 (array_like): Optional, initial vector of parameters.\n", + " optimizer (str): Optional, string specifying classical optimizer, default='SPSA'.\n", + " optimizer_config (dict): Optional, configuration parameters for the optimizer.\n", + " shots (int): Optional, number of shots to take per circuit.\n", + " use_measurement_mitigation (bool): Optional, use measurement mitigation, default=False.\n", + " \n", + " Returns:\n", + " OptimizeResult: The result in SciPy optimization format. \n", + " \"\"\"\n", + " options = {'backend_name': backend.name()}\n", + " \n", + " inputs = {}\n", + " \n", + " # Validate Hamiltonian is correct\n", + " num_qubits = len(H[0][1])\n", + " for idx, ham in enumerate(hamiltonian):\n", + " if len(ham[1]) != num_qubits:\n", + " raise ValueError('Number of qubits in Hamiltonian term {} does not match {}'.format(idx,\n", + " num_qubits))\n", + " inputs['hamiltonian'] = hamiltonian\n", + " \n", + " # Validate ansatz is in the module\n", + " ansatz_circ = getattr(lib_local, ansatz, None)\n", + " if not ansatz_circ:\n", + " raise ValueError('Ansatz {} not in n_local circuit library.'.format(ansatz))\n", + " \n", + " inputs['ansatz'] = ansatz\n", + " inputs['ansatz_config'] = ansatz_config\n", + " \n", + " # If given x0, validate its length against num_params in ansatz:\n", + " if x0:\n", + " x0 = np.asarray(x0)\n", + " ansatz_circ = ansatz_circ(num_qubits, **ansatz_config)\n", + " num_params = ansatz_circ.num_parameters\n", + " if x0.shape[0] != num_params:\n", + " raise ValueError('Length of x0 {} does not match number of params in ansatz {}'.format(x0.shape[0],\n", + " num_params))\n", + " inputs['x0'] = x0\n", + " \n", + " # Set the rest of the inputs\n", + " inputs['optimizer'] = optimizer\n", + " inputs['optimizer_config'] = optimizer_config\n", + " inputs['shots'] = shots\n", + " inputs['use_measurement_mitigation'] = use_measurement_mitigation\n", + " \n", + " rt_job = RuntimeJobWrapper()\n", + " job = provider.runtime.run(program_id, options=options, inputs=inputs, callback=rt_job._callback)\n", + " rt_job._job = job\n", + " \n", + " return rt_job\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "d4c8982f", + "metadata": {}, + "source": [ + "We can now execute our runtime program via this runner function:" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "8e81851d", + "metadata": {}, + "outputs": [], + "source": [ + "job4 = vqe_runner(backend, H, optimizer_config={'maxiter': 15})" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "9bf5499c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + " fun: -1.349853515625\n", + " message: 'Optimization terminated successfully.'\n", + " nfev: 30\n", + " nit: 15\n", + " success: True\n", + " x: array([0.09925502, 1.40473727, 1.61291267, 3.45519813, 2.65167136,\n", + " 4.4163485 , 1.98523376, 5.94459488, 6.46103911, 1.76878845,\n", + " 1.96124064, 3.31830748, 2.06192779, 4.28293342, 3.2448137 ,\n", + " 1.63457609])" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "job4.result()" + ] + }, + { + "cell_type": "markdown", + "id": "ef281bc5", + "metadata": {}, + "source": [ + "The interim results are now attached to the job `interm_results` attribute and, as expected, we see that the length matches the number of iterations performed." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "958dbc84", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "15" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(job4.interm_results)" + ] + }, + { + "cell_type": "markdown", + "id": "dd78b903", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "We have demonstrated how to create, upload, and use a custom Qiskit Runtime by creating our own VQE solver from scratch. This tutorial was meant to touch upon every aspect of the process for a real-world example. Within the current limitations of the runtime environment, this example should enable readers to develop their own single-file runtime program. This program is also a good starting point for exploring additional flavors of VQE runtime. For example, it is straightforward to vary the number of shots per iteration, increasing shots as the number of iterations increases. Those looking to go deeper can consider implimenting an [adaptive VQE](https://doi.org/10.1038/s41467-019-10988-2), where the ansatz is not fixed at initialization." + ] + }, + { + "cell_type": "markdown", + "id": "0a7a50d4", + "metadata": {}, + "source": [ + "## Appendix A\n", + "\n", + "Here we code a simple simultaneous perturbation stochastic approximation (SPSA) optimizer for use on noisy quantum systems. Most optimizers do not handle fluctuating cost functions well, so this is a needed addition for executing on real quantum hardware." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9d8788af", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from scipy.optimize import OptimizeResult\n", + "\n", + "def fmin_spsa(func, x0, args=(), maxiter=100,\n", + " a=1.0, alpha=0.602, c=1.0, gamma=0.101,\n", + " callback=None):\n", + " \"\"\"\n", + " Minimization of scalar function of one or more variables using simultaneous\n", + " perturbation stochastic approximation (SPSA).\n", + "\n", + " Parameters:\n", + " func (callable): The objective function to be minimized.\n", + "\n", + " ``fun(x, *args) -> float``\n", + "\n", + " where x is an 1-D array with shape (n,) and args is a\n", + " tuple of the fixed parameters needed to completely \n", + " specify the function.\n", + "\n", + " x0 (ndarray): Initial guess. Array of real elements of size (n,), \n", + " where ‘n’ is the number of independent variables.\n", + " \n", + " maxiter (int): Maximum number of iterations. The number of function\n", + " evaluations is twice as many. Optional.\n", + "\n", + " a (float): SPSA gradient scaling parameter. Optional.\n", + "\n", + " alpha (float): SPSA gradient scaling exponent. Optional.\n", + "\n", + " c (float): SPSA step size scaling parameter. Optional.\n", + " \n", + " gamma (float): SPSA step size scaling exponent. Optional.\n", + "\n", + " callback (callable): Function that accepts the current parameter vector\n", + " as input.\n", + "\n", + " Returns:\n", + " OptimizeResult: Solution in SciPy Optimization format.\n", + "\n", + " Notes:\n", + " See the `SPSA homepage `_ for usage and\n", + " additional extentions to the basic version implimented here.\n", + " \"\"\"\n", + " A = 0.01 * maxiter\n", + " x0 = np.asarray(x0)\n", + " x = x0\n", + "\n", + " for kk in range(maxiter):\n", + " ak = a*(kk+1.0+A)**-alpha\n", + " ck = c*(kk+1.0)**-gamma\n", + " # Bernoulli distribution for randoms\n", + " deltak = 2*np.random.randint(2, size=x.shape[0])-1\n", + " grad = (func(x + ck*deltak, *args) - func(x - ck*deltak, *args))/(2*ck*deltak)\n", + " x -= ak*grad\n", + " \n", + " if callback is not None:\n", + " callback(x)\n", + "\n", + " return OptimizeResult(fun=func(x, *args), x=x, nit=maxiter, nfev=2*maxiter, \n", + " message='Optimization terminated successfully.',\n", + " success=True)" + ] + }, + { + "cell_type": "markdown", + "id": "40b4d8c2", + "metadata": {}, + "source": [ + "## Appendix B\n", + "\n", + "This is a helper function that converts the Pauli operators in the strings that define the Hamiltonian operators into the appropriate measurements at the end of the circuits. For $X$ operators this involves adding an $H$ gate to the qubits to be measured, whereas a $Y$ operator needs $S^{+}$ followed by a $H$. Other choices of Pauli operators require no additional gates prior to measurement." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "15654ed8", + "metadata": {}, + "outputs": [], + "source": [ + "def opstr_to_meas_circ(op_str):\n", + " \"\"\"Takes a list of operator strings and makes circuit with the correct post-rotations for measurements.\n", + "\n", + " Parameters:\n", + " op_str (list): List of strings representing the operators needed for measurements.\n", + "\n", + " Returns:\n", + " list: List of circuits for measurement post-rotations\n", + " \"\"\"\n", + " num_qubits = len(op_str[0])\n", + " circs = []\n", + " for op in op_str:\n", + " qc = QuantumCircuit(num_qubits)\n", + " for idx, item in enumerate(op):\n", + " if item == 'X':\n", + " qc.h(num_qubits-idx-1)\n", + " elif item == 'Y':\n", + " qc.sdg(num_qubits-idx-1)\n", + " qc.h(num_qubits-idx-1)\n", + " circs.append(qc)\n", + " return circs" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "715a3fec", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

This code is a part of Qiskit

© Copyright IBM 2017, 2021.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from qiskit.tools.jupyter import *\n", + "%qiskit_copyright" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8141d8a", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/sample_vqe_program/sample_vqe.py b/tutorials/sample_vqe_program/sample_vqe.py new file mode 100644 index 000000000..684eba51f --- /dev/null +++ b/tutorials/sample_vqe_program/sample_vqe.py @@ -0,0 +1,251 @@ +# This code is part of qiskit-runtime. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# Grab functions and modules from dependencies +import numpy as np +import scipy.optimize as opt +from scipy.optimize import OptimizeResult +import mthree + +# Grab functions and modules from Qiskit needed +from qiskit import QuantumCircuit, transpile +import qiskit.circuit.library.n_local as lib_local + + +# The entrypoint for our Runtime Program +def main(backend, user_messenger, + hamiltonian, + ansatz='EfficientSU2', + ansatz_config={}, + x0=None, + optimizer='SPSA', + optimizer_config={'maxiter': 100}, + shots = 8192, + use_measurement_mitigation=False + ): + + """ + The main sample VQE program. + + Parameters: + backend (ProgramBackend): Qiskit backend instance. + user_messenger (UserMessenger): Used to communicate with the program user. + hamiltonian (list): Hamiltonian whose ground state we want to find. + ansatz (str): Optional, name of ansatz quantum circuit to use, default='EfficientSU2' + ansatz_config (dict): Optional, configuration parameters for the ansatz circuit. + x0 (array_like): Optional, initial vector of parameters. + optimizer (str): Optional, string specifying classical optimizer, default='SPSA'. + optimizer_config (dict): Optional, configuration parameters for the optimizer. + shots (int): Optional, number of shots to take per circuit. + use_measurement_mitigation (bool): Optional, use measurement mitigation, default=False. + + Returns: + OptimizeResult: The result in SciPy optimization format. + """ + + # Split the Hamiltonian into two arrays, one for coefficients, the other for + # operator strings + coeffs = np.array([item[0] for item in hamiltonian], dtype=complex) + op_strings = [item[1] for item in hamiltonian] + # The number of qubits needed is given by the number of elements in the strings + # the defiune the Hamiltonian. Here we grab this data from the first element. + num_qubits = len(op_strings[0]) + + # We grab the requested ansatz circuit class from the Qiskit circuit library + # n_local module and configure it using the number of qubits and options + # passed in the ansatz_config. + ansatz_instance = getattr(lib_local, ansatz) + ansatz_circuit = ansatz_instance(num_qubits, **ansatz_config) + + # Here we use our convenence function from Appendix B to get measurement circuits + # with the correct single-qubit rotation gates. + meas_circs = opstr_to_meas_circ(op_strings) + + # When computing the expectation value for the energy, we need to know if we + # evaluate a Z measurement or and identity measurement. Here we take and X and Y + # operator in the strings and convert it to a Z since we added the rotations + # with the meas_circs. + meas_strings = [string.replace('X', 'Z').replace('Y', 'Z') for string in op_strings] + + # Take the ansatz circuits, add the single-qubit measurement basis rotations from + # meas_circs, and finally append the measurements themselves. + full_circs = [ansatz_circuit.compose(mcirc).measure_all(inplace=False) for mcirc in meas_circs] + + # Get the number of parameters in the ansatz circuit. + num_params = ansatz_circuit.num_parameters + + # Use a given initial state, if any, or do random initial state. + if x0: + x0 = np.asarray(x0, dtype=float) + if x0.shape[0] != num_params: + raise ValueError('Number of params in x0 ({}) does not match number of ansatz parameters ({})'. format( + x0.shape[0], num_params)) + else: + x0 = 2*np.pi*np.random.rand(num_params) + + # Because we are in general targeting a real quantum system, our circuits must be transpiled + # to match the system topology and, hopefully, optimize them. + # Here we will set the transpiler to the most optimal settings where 'sabre' layout and + # routing are used, along with full O3 optimization. + + # This works around a bug in Qiskit where Sabre routing fails for simulators (Issue #7098) + trans_dict = {} + if not backend.configuration().simulator: + trans_dict = {'layout_method': 'sabre', 'routing_method': 'sabre'} + trans_circs = transpile(full_circs, backend, optimization_level=3, **trans_dict) + + # If using measurement mitigation we need to find out which physical qubits our transpiled + # circuits actually measure, construct a mitigation object targeting our backend, and + # finally calibrate our mitgation by running calibration circuits on the backend. + if use_measurement_mitigation: + maps = mthree.utils.final_measurement_mapping(trans_circs) + mit = mthree.M3Mitigation(backend) + mit.cals_from_system(maps) + + # Here we define a callback function that will stream the optimizer parameter vector + # back to the user after each iteration. This uses the `user_messenger` object. + # Here we convert to a list so that the return is user readable locally, but + # this is not required. + def callback(xk): + user_messenger.publish(list(xk)) + + # This is the primary VQE function executed by the optimizer. This function takes the + # parameter vector as input and returns the energy evaluated using an ansatz circuit + # bound with those parameters. + def vqe_func(params): + # Attach (bind) parameters in params vector to the transpiled circuits. + bound_circs = [circ.bind_parameters(params) for circ in trans_circs] + + # Submit the job and get the resultant counts back + counts = backend.run(bound_circs, shots=shots).result().get_counts() + + # If using measurement mitigation apply the correction and + # compute expectation values from the resultant quasiprobabilities + # using the measurement strings. + if use_measurement_mitigation: + quasi_collection = mit.apply_correction(counts, maps) + expvals = quasi_collection.expval(meas_strings) + # If not doing any mitigation just compute expectation values + # from the raw counts using the measurement strings. + # Since Qiskit does not have such functionality we use the convenence + # function from the mthree mitigation module. + else: + expvals = mthree.utils.expval(counts, meas_strings) + + # The energy is computed by simply taking the product of the coefficients + # and the computed expectation values and summing them. Here we also + # take just the real part as the coefficients can possibly be complex, + # but the energy (eigenvalue) of a Hamiltonian is always real. + energy = np.sum(coeffs*expvals).real + return energy + + # Here is where we actually perform the computation. We begin by seeing what + # optimization routine the user has requested, eg. SPSA verses SciPy ones, + # and dispatch to the correct optimizer. The selected optimizer starts at + # x0 and calls 'vqe_func' everytime the optimizer needs to evaluate the cost + # function. The result is returned as a SciPy OptimizerResult object. + # Additionally, after every iteration, we use the 'callback' function to + # publish the interm results back to the user. This is important to do + # so that if the Program terminates unexpectedly, the user can start where they + # left off. + + # Since SPSA is not in SciPy need if statement + if optimizer == 'SPSA': + res = fmin_spsa(vqe_func, x0, args=(), **optimizer_config, callback=callback) + # All other SciPy optimizers here + else: + res = opt.minimize(vqe_func, x0, method=optimizer, options=optimizer_config, callback=callback) + # Return result. OptimizeResult is a subclass of dict. + return res + + +def opstr_to_meas_circ(op_str): + """Takes a list of operator strings and makes circuit with the correct post-rotations for measurements. + + Parameters: + op_str (list): List of strings representing the operators needed for measurements. + + Returns: + list: List of circuits for measurement post-rotations + """ + num_qubits = len(op_str[0]) + circs = [] + for op in op_str: + qc = QuantumCircuit(num_qubits) + for idx, item in enumerate(op): + if item == 'X': + qc.h(num_qubits-idx-1) + elif item == 'Y': + qc.sdg(num_qubits-idx-1) + qc.h(num_qubits-idx-1) + circs.append(qc) + return circs + + +def fmin_spsa(func, x0, args=(), maxiter=100, + a=1.0, alpha=0.602, c=1.0, gamma=0.101, + callback=None): + """ + Minimization of scalar function of one or more variables using simultaneous + perturbation stochastic approximation (SPSA). + + Parameters: + func (callable): The objective function to be minimized. + + ``fun(x, *args) -> float`` + + where x is an 1-D array with shape (n,) and args is a + tuple of the fixed parameters needed to completely + specify the function. + + x0 (ndarray): Initial guess. Array of real elements of size (n,), + where ‘n’ is the number of independent variables. + + maxiter (int): Maximum number of iterations. The number of function + evaluations is twice as many. Optional. + + a (float): SPSA gradient scaling parameter. Optional. + + alpha (float): SPSA gradient scaling exponent. Optional. + + c (float): SPSA step size scaling parameter. Optional. + + gamma (float): SPSA step size scaling exponent. Optional. + + callback (callable): Function that accepts the current parameter vector + as input. + + Returns: + OptimizeResult: Solution in SciPy Optimization format. + + Notes: + See the `SPSA homepage `_ for usage and + additional extentions to the basic version implimented here. + """ + A = 0.01 * maxiter + x0 = np.asarray(x0) + x = x0 + + for kk in range(maxiter): + ak = a * (kk+1.0+A)**-alpha + ck = c * (kk+1.0)**-gamma + # Bernoulli distribution for randoms + deltak = 2*np.random.randint(2, size=x.shape[0])-1 + grad = (func(x + ck*deltak, *args) - func(x - ck*deltak, *args))/(2*ck*deltak) + x -= ak*grad + + if callback is not None: + callback(x) + + return OptimizeResult(fun=func(x, *args), x=x, nit=maxiter, nfev=2*maxiter, + message='Optimization terminated successfully.', + success=True) diff --git a/tutorials/vqe.ipynb b/tutorials/vqe.ipynb new file mode 100644 index 000000000..b95c691be --- /dev/null +++ b/tutorials/vqe.ipynb @@ -0,0 +1,705 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# VQE\n", + "\n", + "The Variational Quantum Eigensolver (VQE) is a central algorithm in many applications from e.g. quantum chemistry or optimization.\n", + "This tutorial shows you how to run the VQE as a Qiskit Runtime program. We'll start off by defining the algorithm settings, such as the Hamiltonian and ansatz, and then run a VQE both locally, on your machine, and remotely, using the Qiskit Runtime.\n", + "\n", + "**Note:** You can find tutorials on solving more comprehensive problems, such as finding the ground state of the lithium hydride molecule, using the VQE (and Qiskit Runtime) within [the tutorials of Qiskit Nature](https://github.com/Qiskit/qiskit-nature/tree/main/docs/tutorials)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## System Hamiltonian" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's start by defining the operator of which we want to determine the ground state. Here we'll chose a simple diagonal Hamiltonian $\\hat H$ acting with Pauli-Z operators on the first two qubits\n", + "\n", + "$$\n", + "\\hat H = \\hat Z_0 \\otimes \\hat Z_1.\n", + "$$\n", + "\n", + "We can construct this Hamiltonian with Qiskit's `opflow` module:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit.opflow import Z, I\n", + "\n", + "num_qubits = 4\n", + "hamiltonian = (Z ^ Z) ^ (I ^ (num_qubits - 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This Hamiltonian has a ground state energy of -1." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "target_energy = -1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Parameterized Ansatz Circuit" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we choose a parameterized quantum circuit $\\hat U(\\theta)$ to prepare the ansatz wavefunction\n", + "\n", + "$$\n", + "|\\psi(\\theta)\\rangle = \\hat U(\\theta)|0\\rangle.\n", + "$$\n", + "\n", + "We'll use the `EfficientSU2` circuit from Qiskit's circuit library, which is a hardware efficient, heuristic ansatz with alternating rotation and entanglement layers." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAegAAADWCAYAAAAaVxFlAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAzNUlEQVR4nO3dd0AUd/o/8PcuSxUVDfYepQiKIgJ2YklEzC+KLdH7egbN2Yk1JuqpQRK8GGNJYowenuWMJPYYKSY2YkNQiUYFiYIggiAq4lKEZff3h+cqbXaBWT6f2X1e/1yYmR3ecg/Pw85OkWk0Gg0IIYQQwhU56wCEEEIIqYgGNCGEEMIhGtCEEEIIh2hAE0IIIRyiAU0IIYRwiAY0IYQQwiEa0IQQQgiHaEATQgghHKIBTQghhHCIBjQhhBDCIRrQhBBCCIdoQBNCCCEcogFNCCGEcIgGNCGEEMIhGtCEEEIIh2hAE0IIIRyiAU0IIYRwiAY0IYQQwiEa0IQQQgiHFKwDEKKPuLg4ndts2bIFU6dOFdzG09NTrEiE1BrVNRFC76CJ0QgNDWUdgRDRUV2bLhrQhBBCCIdoQBNCCCEcogFNjMaOHTtYRyBEdFTXposGNCGEEMIhGtDEaEyaNIl1BEJER3VtuugyKx0uLNuGR9dTmHzvxq4d4B0cUKPXsspdm8yEED5Q36seQ/U9GtA6PLqegqzzN1jHqDap5iaEsCfV/iHV3FWhQ9zEaHzwwQesIxAiOqpr00UDmhgNXXdbIkSKqK5NFw1oYjT8/PxYRyBEdFTXposGNDEaOTk5rCMQIjqqa9NFJ4mJxHd/EJp4OEKtUkFTqoYyLRtXNhxA6pHzrKNVSYqZCSH8kGIPkVJmGtAiurJ+H66u3w+ZmRydJw+Dz3dzcPBaCp7euc86WpWkmLkqzs7OrCMQIjre61qKPUQqmekQtwFoStVI2nUMcnMFGru2Zx1HL1LMXN7OnTtZRyBEdFKpayn2EN4z04A2ALm5Ak6ThgIA8pIzGafRjxQzlxcSEsI6AiGik0pdS7GH8J6ZDnGLyO3DUegy/R0obK2gKSnF2fnf4XFCKgBgwMY5SD54BunHLgEABm1bhMTtR5ERfYVlZMHMDuMHoeMYH+22tu2aIutCIk7P2sAqrqBDhw5hyZIlrGNUi0ajgUwmYx2DcIz3uqa+Zzhcv4NWq9VYs2YNHBwcYGVlhW7duiE6OhpOTk5cXht49esD2O08CT+6Tkb6iXg079tFuy52+Ta4L3oPChsrtPXzRnFeAfMiBYQz/xV2AlGjVyBq9ApET18HVcEzxK/azTCtcUhMycWM4LOw67MTZt3/g1ZDwvDpd5fx4FEh62iEVBv1PcPhekBPmTIFwcHBmDZtGiIjIzFu3DiMHz8eycnJ8PDwYB2vSsVP8nF2wSa0HtwDbYZ6AgCKHuYhITQc3p9NRre5oxG7YjvbkOVUlllLJkP/jXNwOeQHKNMfsAloJI6eTUf3sYeweV8inihLoNEAGdkFCPo+Hu7jDiE5PY91REJqhPqe+Lgd0GFhYdi+fTsOHz6MhQsXYuDAgVi6dCl69+4NlUqFHj16sI4oqDhXietbjqDH4gnA/w5h3tpzCg06tkBCaASKc5WME1ZUWWYA6L5gLHITU5EWFccwnW7h4eGsIwh68KgQ/vOOoaSkFBpNxfWZOYXwn3MMmspWEpPFe12/ivqeuLgd0CEhIfD19YWPj0+Z5Z06dYK5uTnc3NwAAHfu3IGPjw8cHR3RtWtXnD59mkXcSiX8Oxw2Te3QaezLf8PTlPvI4+xU/leVz9yiX1e09OmGi8G7GCfTLTExkXUEQVsPJqGwqBTqKuavWq3B1b8e48zlrLoNRrjGe12XR31PPDINh3+up6eno02bNti6dSsmT55cZt348eORmJiI+Ph4AMDQoUMxYsQIzJw5E+fOncPYsWORkpICCwsLwe+h74k5HzceAGeLJjX7h1Si3/pZSNp9HNmxun/pEosf4ItHv9fo+4iR27qJHYbu/xTHJnyu9yGe2mQWos8DA0JDQ3VuFxoaKlak6uvwEVDPAZAJ/F2s0QAPIoCsg3WXizDDa11T3zNs39N37HJ5Fnd6ejoAoHnz5mWWFxYWIjo6GsOGDQPw/BZ4Z86cweHDhwEAffr0QcuWLXHy5EkMHTq0bkMbIbd5Y2BR3wb9NszWLnty+x7OL9rCMJWEyRTCwxkAoHm+HSGECZ76HpfvoG/dugUHBwesW7cOc+fO1S4PCgrCp59+io0bN2LmzJm4fPkyxowZg+TkZO0248aNw5AhQ0Q7yzty1HJmzxdt1tsFww6srNFrWeWuTWYhcXG6Pwfy8vJCbGys4Daenp6C6w3pH5+exn8OJlV5iPuF/6zsj4CRjnUTijDFa11T36seQ/U9Lv9Uf/311+Hm5oaQkBA0btwYrVq1wr59+xAREQEAXJ/BTdhZvHgx6wiCpo/rjNADSVWul8kAWxtzvDv09TpMRXjHe10Tw+HyJDG5XI69e/fC1dUVM2bMQEBAAOzt7TFr1iyYmZlpTxBr27YtsrKy8OzZM+1rU1JS0K5dO1bRCUP+/v6sIwjycLHH7PEula6TyZ5//Lzpn31gY83l382EEd7rmhgOlwMaABwdHXHy5Enk5+cjLS0NwcHB+PPPP+Hi4gJra2sAgL29Pfr27YutW7cCAM6dO4d79+5h4MCBLKMTRry8vFhH0GnDx72wak5PNGpQ9iTG11vXx4F1g/G34Z0YJSO8kkJdE8OQ1J/qFy9eRK9evcos+/777/H+++9j/fr1sLCwQFhYmM4zuAlhRS6X4ZMp3TBvYhdY9dwOADiz42306d6UbvlJCClDMgNaqVQiKSkJM2fOLLP89ddfx++/i39ZT3X0WDIBTT2dkR2XiLxbGega6I9zizYj6/wNuM54B219PaFMz8GZOd/CzMIcQ/csR96d+zg9+2tucwNAWz9veK8MwN6e06GwseImtzGwtDDT/ndf92YMkxBSc1X2kJgE9P8mEPVa2UNdrEL0jHUoLSph1kOqypkddxPDDq5Eo85tcXjIR3h65z5s2zRF/28CodFoUJDxEKcDv4FGrcbgnYth0dAGkSOW1Vlubg9xl2dra4vS0lIEBgayjlKGnVMbmNvaIMp/OSwb1YfCxgrXNh1G1vkbsHqtAVr07YLIEcvw+EYq2vp6QVVQhOjp61jHFsz9QvvhvZCf8RAAuMktpF+/fqwjECI6XutaqIc07tIe6mIVovyX49ZPJ/H6qP7MeohQTo2qFCcCViP1SIx2++K8fBybuApR/svx9G42Wg12BwAc//uqOs8umQHNq2benbU3f8+IvgqNWq1d91q3jrh/7joAIPP0VTTtyc+lM0K5AaDVIHdknP4TGo26spdzae3atawjECI6XutaqIcUZD6CzOz5eLFoUA/PHj9lkhHQ3euKcp6U+br4ST5KnhYAADQlpdCUsuuBNKBrycLOFu4Lx8F3fxC6zRkFCzvbl+sa1kOJ8vkTiorzCmDRoB6rmBUI5QaATuPeQPJ+th8dVNf8+fNZRyBEdLzWtVAPefboKcysLDDy9/VwmvQWUiMucJlTiHWzRmgxwI3p07ck8xk0r4pzlYj/8ifc/fUiWg/xQL2Wr2nXleQVoF6L51+b17dGcV4+q5gVCOVu3rcLHlxKgrpExTBh9Z05c4Z1BEJEx2tdC/WQlm90Q9HDPBwaMBfthvdCl+nv4Mq6fdzlrIrcQoH+G2bj3Eff0ztoKcu6kIBmvZ5f29q8jytk8pc/0pw/bqFZ7+frWvZ3w4NLVd+koq4J5W7k3BZt3uqJN3cvhZ1jG7h//B6rmIQQTgn1EADaJ1c9e/QU5g1s6jzfC7pyVqbPl9ORuD0KT5LSDR1PEA3oWsq9eRdqlQq++4OgVqmgKijSrit6mIesmAQM+zkYjV3bc/W4RqHcCVsjcHRsEH6b8Dlyk+4i/osfGSYlhPBIqIdkRF9BQ4dW8N0fhO6L3sXN7Ue5zAkAPpvno6VPN/TbMBtthnqiiYcj2vl5w+Ufb8N3fxDaDmN3HTod4hbB5ZDd2v9uN7wXugb6Iy8lE1nnb+DaxkO4tvGQdr3Cxgr9N36InD9uM0hallDuF15cUsBT7qroul8xIVLEc10L9ZBT//iqzLYse4hQzuhpFU/C+8FhYoVlg3cuRmFWriFjVkADWmSp4TFIDY+pcr2qoKhOr6PTl1Rzv+rgwYN0W0RidKRS11LpIbpyVoXFZVY0oHVo7NpBkt+bVW6WP69Vq1ZJopERUh0s6pr6Hh/flwa0Dt7BAawj1IhUcxNC2JNq/5Bq7qrQSWKEEEIIh2hAE6Px1Vdf6d6IEImhujZdNKCJ0XB2dmYdgRDRUV2bLhrQxGgMHz6cdQRCREd1bbpoQBNCCCEcogFNCCGEcIgGNDEa7u7urCMQIjqqa9NFA5oYjfj4eNYRCBEd1bXpogFNCCGEcIgGNCGEEMIhGtDEaOzYsYN1BEJER3VtumhAE0IIIRyiAU2MxqRJk1hHIER0VNemi55mpcOFZdvw6HoKk+/d2LVDjZ/Owip3bTITQvhAfa96DNX3aEDr8Oh6CrLO32Ado9qkmpsQwp5U+4dUc1eFDnETo/HBBx+wjkCI6KiuTRcNaGI0pk6dyjoCIaKjujZdNKCJ0fDz82MdQac8ZTH2/ZqCT9bHaZe9+9EJfLYlHr+eS0dJiZphOsIjKdQ1MQz6DFokvvuD0MTDEWqVCppSNZRp2biy4QBSj5xnHa1KUswsJCcnh3WEKt3LysdnW/7Af4/cQn6hqsy6PUdTsOfo8xNbWja1wfSxzljw966wsaZfT8J3XUuxh0gpM72DFtGV9fvwQ6eJCHMJwK09p+Dz3RzUb9+cdSxBUswsNTsP/wVX/wP4fm9iheFcXkZ2AZZvvIzu4w7i3B9ZdZSQkJqTYg+RSmYa0AagKVUjadcxyM0VaOzannUcvUgxc3nOzs6sI1Sw7NtLmPTP3/FEWVyt1/2VmoeBUyLw88lUAyUjUsFjXVdGij2E98w0oA1Abq6A06ShAIC85EzGafQjxczl7dy5k3WEMr7ZfR2fbfmjxq8vLlFj3MITOBtP76RNGW91XRUp9hDeM9OHXCJy+3AUukx/BwpbK2hKSnF2/nd4nPD8HdCAjXOQfPAM0o9dAgAM2rYIiduPIiP6CsvIgpkdxg9CxzE+2m1t2zVF1oVEnJ61gVVcQSEhIViyZAnrGACAxJRcfLQ2VnAbzdUpAACZ29YqtykuUeP9Zb/jyl5/+kzaRPFU15Whvmc4XL+DVqvVWLNmDRwcHGBlZYVu3bohOjoaTk5OXF56cPXrA9jtPAk/uk5G+ol4NO/bRbsudvk2uC96DwobK7T180ZxXgHzIgWEM/8VdgJRo1cgavQKRE9fB1XBM8Sv2s0wrbBDhw6xjqA154sYPCsW54zsW2l5WLPjT1H2VRtFz1TYdeQWPlkfh2XfXsKZy/eh0WhYxzJ6PNV1ZajvGQ7XA3rKlCkIDg7GtGnTEBkZiXHjxmH8+PFITk6Gh4cH63hVKn6Sj7MLNqH14B5oM9QTAFD0MA8JoeHw/mwyus0djdgV29mGLKeyzFoyGfpvnIPLIT9Amf6ATUAJuZmSi1/P3RN1n9/vTWR6CdbeX1PQYlAYJi6JxuptV/HZlj/Q//1w9Bh3CMnpecxyEX5Q3xMftwM6LCwM27dvx+HDh7Fw4UIMHDgQS5cuRe/evaFSqdCjRw/WEQUV5ypxfcsR9Fg8AZDJAAC39pxCg44tkBAageJcJeOEFVWWGQC6LxiL3MRUpEXFCbyavLAr/Lbo+8x8UIDjFzJE368+jkSn4d2PTmhPdHv1TfPVvx5jwPvhyH5YyCQb4Qv1PXFxO6BDQkLg6+sLHx+fMss7deoEc3NzuLm5AQCWL18OR0dHyOVy7Nu3j0XUKiX8Oxw2Te3QaezLf8PTlPvIu3OfYSph5TO36NcVLX264WLwLsbJdAsPD2cdAQAQe80wf20bar9CNBoNPvoqFjKUHcwvqNUa3MsuwMYfE+o8m6ngpa71RX1PPFyedZKeno5r165h3rx5FdalpaXB1dUVlpaWAABfX1+8//77mDx5cl3HLCNq9IoKy0qUhQhz4ffJTroyWzexg3fIFByb8DnUJcLX7/IgMTERTZo0YR0Df/712CD7vZr0yCD7FRL75wMk3nmic7st+xMRNIvvo1pSxUtdV4b6nmFxO6ABoHnzsheOFxYWIjo6GsOGDdMu69OnT42+h+yVQxlCPm48AM4WbH45oqOj4adnzvLEyO02bwws6tug34bZ2mVPbt/D+UVbqnxNbTIL0eeBAaGhoTq38/LyEitS1Vy+AcystV++OFu7KlWtL3929/6DRyBbN6T2+aqjgQfQbobOze7nFEImkwOgk8aqg9e6pr5n2L6n78mVXA5oe3t7AEBSUlKZ+9CuXr0amZmZXJ8gpsuZuRtZR9DbhSWhuLAklHUM6dEY6K9uQ+1XiFrPz5bVz0DDmVSF+l7NyDQcXiehVqvh7u6OzMxMrFmzBq1atcK+ffsQERGBtLQ0xMTEwNvbu8xr3njjDcyePRtjxowRNUvkqOXMni/arLcLhh1YWaPXsspdm8xC4uJ0n6jh5eWF2Fjha489PT0F14uhz8RfcP5Kts7t9LkO+lULJ3XFlwvq4AjAK54Vl6LVkDA8zH1W5TZyGRAw0hGhQf3rMJlx4LWuqe9Vj6H6Hpcnicnlcuzduxeurq6YMWMGAgICYG9vj1mzZsHMzEx7ghghr1q8eDHrCAAADxd7A+33NYPsV4ilhRk+er9rlevlMkChkGPu/7nWYSrTwktdk7rH5SFuAHB0dMTJkyfLLJs4cSJcXFxgbW1dxauIKfP392cdAQAwekh7fBsm7l/x1pZm8O3bWtR96uuj991w934+Nv6YALkMUL9yzM3C3Ax7vxqELg6NmWQzBbzUNal7XL6DrsrFixcrfP68bNkytG7dGufPn8e0adPQunVr3L4t/nWohH91cgKYHnx6Nkfn1+1E3ecEv46wa2Ap6j71JZfL8O2SPojd/Q4mjXDQLg+e3QMpUePwtk9bJrlMBS91Teoet++gy1MqlUhKSsLMmTPLLA8ODkZwcDCjVM/1WDIBTT2dkR2XiLxbGega6I9zizYjO+4mhh1ciUad2+LwkI/w9M59KGysMHTPcuTduY/Ts7/mMnfW+RuYcHMHHl27AwA4MeVLFOcqMXjnYlg0tEHkiGVMc/NOJpNhzQIvDJ/1qyj7q1/PHMunu4uyr9rw7NIEnl2aYNuhvwAA/5zKPhNhR6h/eH82GY06t8PT1CycW/g9NGo10/5RnR5t2bg+Bm//BGqVCsV5BYievg4yuZxJ35bMO2hbW1uUlpYiMDCQdZQy7JzawNzWBlH+y2HZqD4UNla4tukwss7fgEZVihMBq5F6JEa7vaqgCNHT1zFM/JxQbgB4nJimvR/ti7v/HP/7KpaRJcWvfxsEjHTQvaEe1i70RtsWtqLsixAxCPWP17p1hNxCgajRK5B78y5av/n8qCer/lHdHl2cm4+IEf9E1KgVeHg1Ga2HeDDr25IZ0Lxq5t1Ze/P3jOir0KjL3i+5KEf3TR5Y0JW7YafWGHYoGB5L/sYiXo3069ePdYQyvlvaB0N6taxyvcxtq84zuBcFdMWUUY5iRyMSwltdA8L9o367Znh8Iw0A8Oj6HTTt6cQk4wvV7dEatVp72zyZmRxPU9g9hpIGdC1Z2NnCfeE4+O4PQrc5o2BhJ413OrpyH+gbiMiRy2BhVw9t3urJKGX1rF27lnWEMqwsFfjlmzfx/ojqv5O2MJdjzQIv/Guup9431SHGibe6BoT7R97tDDTr7QIAaN6vCywa2rCKCaBmPdq+eye8HfUFWvTrgqdpui+ZNBQa0LVUnKtE/Jc/IWr0Clz+4kcubwZfGV25X3ydFhUHO2dpnAQ0f/581hEqsLJUYFvwABz59k04d2io12sGebXApR9HYMGkrjScCZd1LdQ/Hl2/g9zENAzd9yksbK1R+IDtUcSa9OicP27hiO/HSIuIhcP4QXWQsnI0oGsp60ICmvX631+LfVwhk0vjRyqUW2Ftqf26qacTnnJ8k/tXnTlzhnWEKg0f0BY3Do3GidBhmPluZ3h3bQK7+hawtjJDk0ZWGOzdEp9MccP1g6NwPNSPLlsiWjzWta6+d2XdPhwd8ymePVYi/dhlFhG1qtuj5eYvz50uVhaitKjYoPmESOYsbl7l3rwLtUoF3/1ByL50E6qCIsgUZtr1Ppvno5mXM+p3aIFr3/2Mu0f5eGSjUO4Gr7dA33UzUZJfBGVaNv74cg/jtMZBJpNhoFdLDPSq+nNpQqRAsO/JZPDd9ynUpWpknvkTOfF/8ZsVFXt0YdZj9Fw+ERq1Bs9ylTgdyO5qGxrQIrgcslv73+2G90LXQH/kpWQi6/wNRE8r+/mRwsYK/Td+iJw/2F+rLZT7l7cWVdh+8M7FKMzKrcOEhBBeCfWPyp4YxbJ/VKdHA0DUqLL5WfVtGtAiSw2PQWp4TJXrVQVFXF5HrCs3wP9lVrruV0yIFEmhrqXUP/TJWh6rvk0DWofGrh0k+b1Z5Wb58zp48CDdFpEYHRZ1TX2Pj+/L5dOsCCmP16f+mKIX127res410Y3qmgiRxinHhBBCiImhAU0IIYRwiAY0MRpfffUV6wiEiI7q2nTRgCZGw9nZmXUEQkRHdW26aEATozF8+HDWEQgRHdW16aIBTQghhHCIBjQxGu7u7qwjECI6qmvTRQOaGI34+HjWEQgRHdW16aIBTQghhHCIBjQhhBDCIRrQxGjs2LGDdQRCREd1bbpoQBNCCCEcogFNjMakSZNYRyBEdFTXposeN6nDhWXb8Oh6CpPv3di1A7yDA2r0Wla5a5OZEMIH6nvVY6i+RwNah0fXU5B1/gbrGNUm1dyEEPak2j+kmrsqdIibGI0PPviAdQRCREd1bbpoQBOjMXXqVNYRCBEd1bXpogFNjIafnx/rCEatpESNq0mPtF/fufcUGo2GYSLTQHVtuugzaGI0cnJyWEcwOkXPVNhzNAWhB24i9loOnhWXatd1GLYHr9lZ4q3erTDz3c7o694MMpmMYVrjRHVtumhAi8R3fxCaeDhCrVJBU6qGMi0bVzYcQOqR86yjVUmKmUndiTh9F1ODzuBedkGV2zzMfYawyGSERSZjsHdLbA3qh3Yt69dhSsKSFHuIlDLTIW4RXVm/Dz90mogwlwDc2nMKPt/NQf32zVnHEiTFzFWhB9uLQ63WYO4XMRg+61fB4Vze8QsZ6DLqAI5Epxkwnenhva6l2EOkkpkGtAFoStVI2nUMcnMFGru2Zx1HL1LMXN7OnTtZR5A8jUaDmZ+fw4Yfrtfo9coCFfznHkP47zSkxSKVupZiD+E9Mw1oA5CbK+A0aSgAIC85k3Ea/Ugxc3khISGsI0jef3+5hc17EwW30VydAs3VKVWuV5Vq8LdPonEvK1/seCZJKnUtxR7Ce2auP4NWq9VYu3YtNm/ejLt378LJyQlff/01pk6dCh8fH2zZsoV1xDLcPhyFLtPfgcLWCpqSUpyd/x0eJ6QCAAZsnIPkg2eQfuwSAGDQtkVI3H4UGdFXWEYWzOwwfhA6jvHRbmvbrimyLiTi9KwNrOIKOnToEJYsWcI6hmTdzynAnC9iRNnXE2UxpgWfxZFv3xJlf7X1NL8YBUWlaNzAEubm0npfwntdU98zHK4rdcqUKQgODsa0adMQGRmJcePGYfz48UhOToaHhwfreBVc/foAdjtPwo+uk5F+Ih7N+3bRrotdvg3ui96DwsYKbf28UZxXwLxIAeHMf4WdQNToFYgavQLR09dBVfAM8at2M0xLDGnTT4nIfVos2v7Cf7+LKzcfira/mog6k44h/4hEg97/RfOBu2E/YBfmfxmDzAf6f7ZOhFHfMxxuB3RYWBi2b9+Ow4cPY+HChRg4cCCWLl2K3r17Q6VSoUePHqwjVqn4ST7OLtiE1oN7oM1QTwBA0cM8JISGw/uzyeg2dzRiV2xnG7KcyjJryWTov3EOLof8AGX6AzYBiUGpVGps2S98aLsmvt8j/j71tWHXNQybeRSn4l4euszLL8H6XdfR872fcefeU2bZjBH1PfFxO6BDQkLg6+sLHx+fMss7deoEc3NzuLm54fHjx3j77bfh6OiIbt264a233sKtW7cYJS6rOFeJ61uOoMfiCcD/rg29tecUGnRsgYTQCBTnKhknrKiyzADQfcFY5CamIi0qjmE63cLDw1lHkKwbybm4n1Mo+n6Px2aIvk99xCfkYO7qCwCAUnXZm6loNMD9h4X4vyWnGCSrPinVNfU9cXE5oNPT03Ht2jWMHTu2wrq0tDS4urrC0tISMpkMc+fORVJSEq5cuYK3334bAQH8PEkp4d/hsGlqh05jX/6R8TTlPvLu3GeYSlj5zC36dUVLn264GLyLcTLdEhPZvVuTuks3DHMzjL9S8/BExMPm+vrupwTIBe6ZolZrcDY+m/kheH1Ira6p74lHpuHwXn0xMTHo3bs3wsPDy9zmrrCwEB07dsSwYcOwdevWCq+7ePEiRo4cifT0dJ3fQ987Hn3ceACcLZroH16HfutnIWn3cWTH6v6lSyx+gC8e/V6j7yNGbusmdhi6/1Mcm/C53od4apNZiD4PDAgNDdW5XWhoqFiRjEvTt4FmI8ssEjpTW4jMrdzvZtIy4FkdnyHr9C/Awl73dhlhwMPjhs9TBV7rmvqeYfuevmOXy7O47e2f/2IlJSWVGdCrV69GZmZmlSeIrV+/HiNHjqyLiCbBbd4YWNS3Qb8Ns7XLnty+h/OL+Dp7npCK9Dw4KOPyICJhiKe+x+U7aLVaDXd3d2RmZmLNmjVo1aoV9u3bh4iICKSlpSEmJgbe3t5lXhMUFITIyEicOHECNjY2omWJHLWc2fNFm/V2wbADK2v0Wla5a5NZSFyc7s+BvLy8EBsbK7iNp6en4HpT9f2eBMz47Jxe2754Z13hnXIVsk9NQJPG1jXOVhNjFxzHgeOpUKuF29up//jBp2eLOkpVEa91TX2vegzV97j881Eul2Pv3r1wdXXFjBkzEBAQAHt7e8yaNQtmZmZwc3Mrs/1nn32GI0eOICoqStThTKRl8eLFrCNIVo/OehwOroE2zevV+XAGgBnjOgsOZ7lcBuf2DTHAg7/bO5ZHdW26uDzEDQCOjo44efJkmWUTJ06Ei4sLrK1f/sIHBQUhIiICv/32G+zs7Oo4JeGJv78/6wiS5ebYCA1szZGnLBF1v/17sBmAA71aYNZ7nbHxxwTIALw6quVyGawszLAzxEcST9+iujZdXL6DrsrFixfLfP58/fp1fPrpp3j48CHeeOMNdO/eHd27d2cXkDDl5eXFOoJkWVkqMOkdB9H3O3WMk+j71IdMJsM3i3tjw8e90KpZvVeWA0P7tMK5/74Nzy7inQRlSFTXpovbd9DlKZVKJCUlYebMmdplrq6u9MB4QkQSON4Fm/cmorhELcr+erraMz2ELJPJ8OHfXDHrvc5QuG8DAKQefRdtmtsyy0RIdUhmQNva2qK0tFT3hgz0WDIBTT2dkR2XiLxbGega6I9zizYjPz0H/b8JhEajQUHGQ5wO/AYatRqDdy6GRUMbRI5YxmXurPM30GKAG9wC/SGTyxEXtANPbmVg6J7lyLtzH6dnf800NzEMh3YN8emMHljy9cVa78tcIcd/gvpzcQjZzOzlgUIazrVXVd/IjruJYQdXolHntjg85CM8/d91z64z3kFbX08o03NwZs63MLMwr/NeUt3MANDWzxveKwOwt+d0KGysmPQ/SR3i5pGdUxuY29ogyn85LBvVh8LGCtc2HUbW+RsozsvHsYmrEOW/HE/vZqPVYHcAwPG/r2KcWji3mZUFnCa+iV/fDUbU6BV4eDUZqoIiRE9fxzq2oH79+rGOIHkfvd8VQ/u0EtxG5rZV5xncGz7uha6OjcWMZrJ4qmuhvqFRleJEwGqkHnn5wBWr1xqgRd8uiByxDI9vpKKtr1ed95LqZn6h/fBeyM94fiMbVv2PBnQtNfPurL35e0b0VWjULw8PFj/JR8nT5zfl15SUQlMqzqFDMQjlbuLhCI1agyG7l6L/N4FQWFuyilkta9euZR1B8hQKOQ6sGwK//q1r9HqZDFi/yBsz3u0scjLTxVNdC/UNACjKeVLm69e6dcT9c8+fLZ55+iqa9nSsm6CvqG5mAGg1yB0Zp/+ERsO2Z9OAriULO1u4LxwH3/1B6DZnFCzsKh5Cs27WCC0GuHHxFJcXhHJbN2kIm2aNcGzC58iOuwnHv7/JMKn+5s+fzzqCUbCxVuDw129i7UfesLI00/t1ndo2wO/bhmPO/3XRvTHRG091rU+/K7N9w3ooUT6/x3txXgEsGtQT3N4QqpsZADqNewPJ+8W/I2J1SeYzaF4V5yoR/+VPuPvrRbQe4oF6LV8rs15uoUD/DbNx7qPvuXoHLZS7OK8AWbEJ0KjVyDx7DV1mvMMwqf7OnDnDOoLRMDOTY97ELhg9pD02701E6IGbyH5UVOm2PTq/hpnvdsYEv46wtqKWIjae6lpXvyuvJK8A9Vo838a8vjWK8/LrImYZ1c3cvG8XPLiUBHWJqo4SVo3eQddS1oUENOvlAgBo3scVMnnZH2mfL6cjcXsUniTpvj94XRLKnXPlNuwcnh/ibOzaHk/TsplkJOy1bWGLzz/sicwTE5D0yxj89OVAbPpnH2xe3hdRm4Yi+9QEXPppJKaMcqLhbAJ09bvycv64hWa9n2/fsr8bHlxKMnjG8qqbuZFzW7R5qyfe3L0Udo5t4P7xe3URs1L0G1VLuTfvQq1SwXd/ELIv3YSqoAgyxfPDgk08HNHOzxu2rZvA5R9v40ZoONIihW/ZV1eEcj97mIf752/A9+BKlBY+Q/TMDYzTEtbkchkc2jWEQ7uGrKMQhoT6BgD4bJ6PZl7OqN+hBa599zPuHo1DVkwChv0cjPz0HNz4d90/OrO6mRO2RiBhawQAYNjPwYj/4sc6z/wCDWgRXA7Zrf3vdsN7oWugP/JSMpF1/gZ+cJhYYfvBOxejMCu3DhNWTij3jS1HcGPLEe16hY0V+m/8EDl/3GYRVS+67ldMiBTxVtdCfSN6WsUT2q5tPIRrGw9pv2bRS6qb+YUXl8Ky6n80oEWWGh6D1PCKp+y/iofLrMrTlVtVUMT8um1dDh48SLdFJEaH57rWp9+Vx7qXSCkzDWgdGrt2kOT3ZpWb5c9r1apV3DYyQmqKRV1T3+Pj+9KA1sE7OIB1hBqRam5CCHtS7R9SzV0VOoubEEII4RANaGI0vvrqK9YRCBEd1bXpogFNjIazszPrCISIjuradNGAJkZj+PDhrCMQIjqqa9NFA5oQQgjhEA1oQgghhEM0oInRcHd3Zx2BENFRXZsuGtDEaMTHx7OOQIjoqK5NFw1oQgghhEM0oAkhhBAO0YAmRmPHjh2sIxAiOqpr00UDmhBCCOEQDWhiNCZNmsQ6AiGio7o2XfQ0Kx0uLNuGR9dTmHzvxq4davx0Fla5a5OZEMIH6nvVY6i+RwNah0fXU5B1/gbrGNUm1dyEEPak2j+kmrsqdIibGI0PPviAdQRCREd1bbpoQBOjMXXqVNYRCBEd1bXpogFNjIafnx/rCIQjSXeeYO3OPzHh45PaZQOnRGDuFzH4MfI2CgpVDNPpj+radNFn0CLx3R+EJh6OUKtU0JSqoUzLxpUNB5B65DzraFWSYmYhOTk5rCMQDly4mo1lGy/ht/MZFdadisvEqbhMbPgBaNTAAv8Y7YR/Tu2O+vUsGCTVD891LcUeIqXM9A5aRFfW78MPnSYizCUAt/acgs93c1C/fXPWsQRJMTMhlSkpUePjdbHo8/cjlQ7n8h7nFWP1tj/RdfRBRF/MrIOExkmKPUQqmWlAG4CmVI2kXccgN1egsWt71nH0IsXM5Tk7O7OOQBgpKVFj7MLjWL3tT6jVmmq9NjVDiTenRuHnk6kGSlc7UqlrKfYQ3jPTgDYAubkCTpOGAgDykqXxl7kUM5e3c+dO1hEII7NXncPPJ9Nq/PoSlRrjFp5A3LUHIqYSh1TqWoo9hPfM9Bm0iNw+HIUu09+BwtYKmpJSnJ3/HR4nPP+rfMDGOUg+eAbpxy4BAAZtW4TE7UeREX2FZWTBzA7jB6HjGB/ttrbtmiLrQiJOz9rAKq6gkJAQLFmyhHUMUsciTt/Fln03BbfRXJ0CAJC5ba1ym+ISNd5f9jsu/TgCVpb8tEbe65r6nuFw/Q5arVZjzZo1cHBwgJWVFbp164bo6Gg4OTlxeenB1a8PYLfzJPzoOhnpJ+LRvG8X7brY5dvgvug9KGys0NbPG8V5BcyLFBDO/FfYCUSNXoGo0SsQPX0dVAXPEL9qN8O0wg4dOsQ6AqljarUGH/5LvJN7btzOxaY9iaLtTwy81zX1PcPhekBPmTIFwcHBmDZtGiIjIzFu3DiMHz8eycnJ8PDwYB2vSsVP8nF2wSa0HtwDbYZ6AgCKHuYhITQc3p9NRre5oxG7YjvbkOVUlllLJkP/jXNwOeQHKNP5OwRITNev5+7h9t2nou5z056Ean+OTajvGQK3AzosLAzbt2/H4cOHsXDhQgwcOBBLly5F7969oVKp0KNHD9YRBRXnKnF9yxH0WDwBkMkAALf2nEKDji2QEBqB4lwl44QVVZYZALovGIvcxFSkRcUxTEdIRbsjbou+z79S83DxOr+XNvGM+p64uB3QISEh8PX1hY+PT5nlnTp1grm5Odzc3AAAI0eOhJubG9zd3eHl5YVjx46xiFuphH+Hw6apHTqNfflveJpyH3l37jNMJax85hb9uqKlTzdcDN7FOJlu4eHhrCOQOhZ33TDvbC4aaL81IbW6pr4nHn7OhHhFeno6rl27hnnz5lVYl5aWBldXV1haWgIAtm/fDjs7OwBAfHw83njjDTx69AhmZmZ1GRlRo1dUWFaiLESYC79PdtKV2bqJHbxDpuDYhM+hLuH/rkuJiYlo0qQJ6xikjqhUaty888Qg+75267FB9lsTPNc19T3D4nZAA0Dz5mUvHC8sLER0dDSGDRumXfZiOAPAkydPIJPJoNHo/vxI9sqhDCEfNx4AZws2vxzR0dHw0zNneWLkdps3Bhb1bdBvw2ztsie37+H8oi1VvqY2mYXo88CA0NBQndt5eXmJFYmwJrMAunxXZtGLs7WrUtX68md3b/p+KzYt61e7fHrgta6p7xm27+kzowBOB7S9vT0AICkpqcx9aFevXo3MzMwKJ4jNmjULkZGRePLkCfbv3w+Fgst/FgDgzNyNrCPo7cKSUFxYEso6BiGV05T+7381ZT47FGffJeLuz8RR36sZmUbfUV6H1Go13N3dkZmZiTVr1qBVq1bYt28fIiIikJaWhpiYGHh7e1d4XXR0NObNm4fff/8dtra2omSJHLWc2fNFm/V2wbADK2v0Wla5a5NZSFyc7hM1vLy8EBsbK7iNp6en4HoiLY7/by/+Ss3TuZ0+10G/6utPeiFwgmutsumD17qmvlc9hup7XJ4kJpfLsXfvXri6umLGjBkICAiAvb09Zs2aBTMzM+0JYuX5+PhALpfj7NmzdZyY8GDx4sWsI5A61tPF3jD7dTXMfmuC6tp0cXss2NHRESdPniyzbOLEiXBxcYG1tTUAQKlU4uHDh2jXrh2A5yeJ3b59G507d67zvIQ9f39/1hFIHXvX93WERSaLus92LW3h1YWfk7Kork0XtwO6MhcvXkSvXr20X+fn5+Pdd9+FUqmEQqGAlZUVdu3ahbZt2zJMSVjR51AgMS7D+7dB2xb1kJaZL9o+p491hpkZPwcXqa5Nl2QGtFKpRFJSEmbOnKld1qxZM8TExDBM9VyPJRPQ1NMZ2XGJyLuVga6B/ji3aDNyb97F4O2fQK1SoTivANHT10Eml2PonuXIu3Mfp2d/zWXukrwCeK18fslBvdb2SAiNwI1/h2PwzsWwaGiDyBHLmOYm5AWFQo61C70xZsEJUfbXsU19BI53EWVfxqqqvpEddxPDDq5Eo85tcXjIR3h65z5kCrMKyxQ2VnXeA6uTGQAm3NyBR9fuAABOTPkSxblKJv2Pnz8TdbC1tUVpaSkCAwNZRynDzqkNzG1tEOW/HJaN6kNhY4Vrmw4j6/wNFOfmI2LEPxE1agUeXk1G6yEeUBUUIXr6OtaxBXM/un5Hey/axzfScPe35ze6P/73VYxTE1LR6Dc7YIJfR8FtZG5bdZ4gZmYmw7aVA1DPxlzMeEZFqG9oVKU4EbAaqUdevmmqbFld98DqZgaAx4lp2h744u5nLPqfZAY0r5p5d9be/D0j+io0arV2nUatfn4JCACZmRxPU/h5nJlQ7hcU1pawbmqn/auSd/36Gf66VcKnrUH9MKRXyxq/Xi6XYcdnA9Dfo7nujesYT3Wtq28U5VS8cUxly+pSTTI37NQaww4Fw2PJ3+okY1Ukc4ibVxZ2tnCa+CZcp74NiwY2SPnlfJn/w+27d0Kvf/0Dpc+Kcf37XxgmLUtXbgBoNcgd907GM0pYfWvXrmUdgTBiZanAL9+8ibmrL2Dz3uo9jarZa9b4z8r+8OvfxkDpaoenutanb/CmJpkP9A1Eca4SvVdPRZu3euLurxfrKG1Z9A66lopzlYj/8idEjV6By1/8WOFm8Dl/3MIR34+RFhELh/GDGKWsSFduAGjr543UiAsM0tXM/PnzWUcgDFlZKvD9sr74bYsvvLvqPgvb2tIM08Y64/rBUdwOZ4Cvutanb/CmJplfbJMWFQc7Z3YnHdOArqWsCwlo1uv5SSXN+7hCJn/5I5WbvzxAUawsRGlRcZ3nq4pQbgCQKcxg59AKj2+ksohXI2fOnGEdgXBgSK9WiPnhHVz8cQRWTHfH8AFt4NS+IV5vXR/uzq8hYKQDNi/vi3vHx+P7ZX3xmp0V68iCeKprXX2DR9XNrLC21G7T1NOJ6Ud8dIi7lnJv3oVapYLv/iBkX7oJVUERZIrnD+po7NoePZdPhEatwbNcJU4Hsj1r+1VCuQGgRb8uyDxzjWFCQmrHw8UeHga6kYmp0tU3fDbPRzMvZ9Tv0ALXvvsZd4/GVbqM58z56Q/Qd91MlOQXQZmWjT++3FOneV9FA1oEl0N2a/+73fBe6Broj7yUTGSdv4GoUWWfnKKwsUL/jR8i5w/xn2NbXUK5M05dQcapK2W2H7xzMQqzcus4JSGEJ0J9I3paxc/Lyy9j0QOrm/mXtxZVWMai/9GAFllqeAxSw6u+NltVUMTldcS6cgP8X2ZFN3Mgxojnutanb5THugfWJDPApv/RgNahsWsHSX5vVrlZ/rwOHjxIt0UkRodFXVPf4+P7cvk0K0LK4/WpP4TUBtU1EcL/KXiEEEKICaIBTQghhHCIBjQxGl999RXrCISIjuradNGAJkbD2dmZdQRCREd1bbpoQBOjMXz4cNYRCBEd1bXpogFNCCGEcIiugyaSoM9lJCtWrKDLTYikUF0TIXQdNCGEEMIhOsRNCCGEcIgGNCGEEMIhGtCEEEIIh2hAE0IIIRyiAU0IIYRwiAY0IYQQwiEa0IQQQgiHaEATQgghHKIBTQghhHCIBjQhhBDCof8PSkKXX8dj3ggAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from qiskit.circuit.library import EfficientSU2\n", + "\n", + "# the rotation gates are chosen randomly, so we set a seed for reproducibility\n", + "ansatz = EfficientSU2(num_qubits, reps=1, entanglement='linear', insert_barriers=True) \n", + "ansatz.draw('mpl', style='iqx')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solve with the VQE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have the problem and ansatz specified we can use the Variational Quantum Eigensolver (VQE) to solve for the minimal eigenvalue of our Hamiltonian.\n", + "\n", + "The VQE requires a classical optimization routine, along with an initial point, to calculate the parameter updates." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from qiskit.algorithms.optimizers import SPSA\n", + "\n", + "optimizer = SPSA(maxiter=50)\n", + "\n", + "np.random.seed(10) # seed for reproducibility\n", + "initial_point = np.random.random(ansatz.num_parameters)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To access intermediate information we can pass a callback into the VQE. The callback is given the current number of function evaluations, the current parameters, function values and standard deviation in the expectation evaluation. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "intermediate_info = {\n", + " 'nfev': [],\n", + " 'parameters': [],\n", + " 'energy': [],\n", + " 'stddev': []\n", + "}\n", + "\n", + "def callback(nfev, parameters, energy, stddev):\n", + " intermediate_info['nfev'].append(nfev)\n", + " intermediate_info['parameters'].append(parameters)\n", + " intermediate_info['energy'].append(energy)\n", + " intermediate_info['stddev'].append(stddev)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Local VQE\n", + "\n", + "Before running the Qiskit Runtime VQE program, let's first simulate this system locally using Qiskit's `VQE` class." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit.providers.basicaer import QasmSimulatorPy # local simulator\n", + "from qiskit.algorithms import VQE\n", + "\n", + "local_vqe = VQE(ansatz=ansatz,\n", + " optimizer=optimizer,\n", + " initial_point=initial_point,\n", + " quantum_instance=QasmSimulatorPy(),\n", + " callback=callback)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/jul/opt/miniconda3/envs/stable/lib/python3.7/site-packages/qiskit/utils/run_circuits.py:564: UserWarning: Option max_credits is not used by this backend\n", + " return backend.run(circuits, **backend_options, **noise_config, **run_config)\n" + ] + } + ], + "source": [ + "local_result = local_vqe.compute_minimum_eigenvalue(hamiltonian)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Eigenvalue: -1.0\n", + "Target: -1\n" + ] + } + ], + "source": [ + "print('Eigenvalue:', local_result.eigenvalue)\n", + "print('Target:', target_energy)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With the information from the callback, we can for instance compute the average of the estimation errors in the expectation evaluations. For an exact (statevector) simulation this would be 0, but for a shot-based readout as we have on real hardware, this will be a small finite error." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mean error: 0.013757962876791466\n" + ] + } + ], + "source": [ + "print('Mean error:', np.mean(intermediate_info['stddev']))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Runtime VQE: ``VQEProgram``\n", + "\n", + "To run the VQE using Qiskit Runtime, we only have to do very few changes from the local VQE run and mainly have to replace the `VQE` class by the `VQEProgram` class. Both follow the same `MinimumEigensolver` interface and thus share the `compute_minimum_eigenvalue` method to execute the algorithm and return the same type of result object. Merely the signature of the initializer differs sligthly.\n", + "\n", + "We start by choosing the provider with access to the Qiskit Runtime service and the backend to execute the circuits on. \n", + "\n", + "**Note:** To run this tutorial, replace the provider by your provider." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit import IBMQ\n", + "\n", + "IBMQ.load_account()\n", + "provider = IBMQ.get_provider(project='qiskit-runtime') # replace by your designated provider" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We're using the IBM Montreal device, but you can replace this with another device that support the Qiskit Runtime." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "backend = provider.get_backend('ibmq_montreal')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's not forget to reset the callback!" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "intermediate_info = {\n", + " 'nfev': [],\n", + " 'parameters': [],\n", + " 'energy': [],\n", + " 'stddev': []\n", + "}\n", + "\n", + "def callback(nfev, parameters, energy, stddev):\n", + " intermediate_info['nfev'].append(nfev)\n", + " intermediate_info['parameters'].append(parameters)\n", + " intermediate_info['energy'].append(energy)\n", + " intermediate_info['stddev'].append(stddev)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since the Qiskit Runtime VQE runs on real hardware, we might want to do measurement error mitigation. For this purpose the `VQEProgram` supports a boolean flag `measurement_error_mitigation` that can be set to `True` to enable error mitigation with a complete measurement fitter." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "measurement_error_mitigation = True" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit_nature.runtime import VQEProgram\n", + "\n", + "# in this first release, the optimizer must be specified as dictionary\n", + "optimizer = {'name': 'SPSA',\n", + " 'maxiter': 50}\n", + "\n", + "runtime_vqe = VQEProgram(ansatz=ansatz,\n", + " optimizer=optimizer,\n", + " initial_point=initial_point,\n", + " provider=provider,\n", + " backend=backend,\n", + " shots=1024,\n", + " measurement_error_mitigation=measurement_error_mitigation,\n", + " callback=callback)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "runtime_result = runtime_vqe.compute_minimum_eigenvalue(hamiltonian)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Eigenvalue: -0.9756263578485963\n", + "Target: -1\n" + ] + } + ], + "source": [ + "print('Eigenvalue:', runtime_result.eigenvalue)\n", + "print('Target:', target_energy)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mean error: (0.012858397132816049+0j)\n" + ] + } + ], + "source": [ + "print('Mean error:', np.mean(intermediate_info['stddev']))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Additionally to final results, the `VQEProgram` also returns the history of the optimization, such that we can visualize the " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "history = runtime_result.optimizer_history\n", + "\n", + "loss = history['loss']\n", + "timestamps = history['time']\n", + "\n", + "runtimes = np.concatenate(([0], np.diff(timestamps)))\n", + "runtimes_in_min = runtimes / 60" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From this, we can extract the times per iteration and statistics on it:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total time taken: 23.09min\n", + "\n", + "Median time of the iterations: 27.12s\n", + "Average time per iteration: 27.71s\n", + "Standard deviation: 5.21s\n" + ] + } + ], + "source": [ + "print(f'Total time taken: {np.sum(runtimes_in_min):.2f}min\\n')\n", + "\n", + "# note that the median and average might differ, since the device get's calibrated every hour\n", + "# resulting in some iteration times that are much larger than the rest\n", + "print(f'Median time of the iterations: {np.median(runtimes):.2f}s')\n", + "print(f'Average time per iteration: {np.mean(runtimes):.2f}s')\n", + "print(f'Standard deviation: {np.std(runtimes):.2f}s')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or plot the loss against the total time taken:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "image/png": "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u/9cxN9n5TKqf3ygwxEREelQu3fvZseOHQwYMIDo6GjMLNAhibQr5xxVVVVs2bIFoM0JvIZzg9ygxBicg+ufWURZpUYgRESkeyssLGTAgAHExMQocZduycyIiYlhwIABFBYWtnl/Je9BLrVXJA9eMpptZVXc8u8leL0u0CGJiIh0mLq6OqKjowMdhkiHi46OPqTSMCXvXcCYjER+duZIPlhZyF9mrw50OCIiIh1KI+7SExzqz7mS9y7i0gkZnH3sAP74XgFzCooCHY6IiIiIBICS9y7CzPjN2UeS06cX339+MZtKKgMdkoiIiByCyy67jDPPPLNVfZ988kni4uL2u339+vWYGQsWLGiv8DpNW/4c5GtK3ruQ6IhQHv7OGDxex/X/XEh1nSfQIYmIiIjfli1buOaaaxg4cCAREREMGDCAq6++ms2bN+/V74EHHuCZZ55pl3MOGjSIbdu2ccwxxwCQl5eHmVFcXHzA/RqS/oZHfHw8EyZM4LXXXmuXuJraX0zt+efQWrW1taSkpPB///d/LW7/61//SkxMDGVlZQB4vV7+/Oc/c8wxxxAdHU3v3r05/vjjeeutt/bar+EztvRYuXJlu34GJe9dTGZKLPedfwzLtuzmF//9KtDhiIiICLBu3Tpyc3NZtmwZTz31FKtXr+aZZ57hq6++YuzYsaxfv76xb3x8PAkJCe1y3tDQUPr27UtY2KHN/v3222+zbds25s+fz7hx4zj33HNZtmxZu8R2MO3559BaERERXHrppTz55JM4t+8kII899hjf/va3iY/3Tc998cUX87Of/Yxrr72Wr776ik8++YSxY8dy5pln8te//nWf/b/66iu2bdu21yMrK6t9P4RzTo9WPsaMGeOCxT1vr3QZd7zunpu/IdChiIiItJvly5cHOoRDctppp7n+/fu7ioqKvdorKipc//793emnn97YNmvWLHfGGWc0vp8zZ44bP368i42Ndb1793Zjx451X375pXPOuSeeeMLFxsY29i0pKXETJ050J598sisvL3fr1q1zgPv8888bXzd9zJo1q8V4m+7XYPfu3Q5wf/rTn/bbxznnAPef//xnrz4vvPCCO/HEE110dLQbMWKEe+edd/ba3lJMzf8cpk2b5q677jr3gx/8wCUmJrqUlBR3//33u+rqanfDDTe4+Ph4N2jQIPf000/vFc/mzZvd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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt \n", + "\n", + "plt.rcParams['font.size'] = 14\n", + "\n", + "fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(12,8))\n", + "\n", + "# plot loss and reference value\n", + "ax1.plot(loss, label='Qiskit Runtime VQE')\n", + "ax1.axhline(y=target_energy, color='tab:red', ls='--', label='Target energy')\n", + "\n", + "# plot time taken\n", + "ax2.plot(np.cumsum(runtimes_in_min))\n", + "\n", + "# settings\n", + "ax1.set_title('Qiskit Runtime VQE')\n", + "ax1.set_ylabel('Eigenvalue')\n", + "ax1.legend(loc='best')\n", + "ax2.set_ylabel('Total Qiskit Runtime [min]')\n", + "ax2.set_xlabel('Iteration')\n", + "ax2.grid();\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Qiskit Runtime VQE: Direct call\n", + "\n", + "Instead of interacting with the Qiskit Runtime via the `VQEProgram` we can also directly call the the Qiskit Program in the cloud -- like running the Quantum Kernel Alignment (QKA) or Circuit Runner. Under the hood, `VQEProgram` is nothing but a proxy that takes care of creating the dictionaries interaction with the Qiskit Program running in the Qiskit Runtime on the cloud." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To call the VQE code on the Qiskit Runtime on the cloud, we interact with the `run` method of the provider\n", + "```\n", + "provider.run(progam_id, inputs, options, callback)\n", + "```\n", + "where \n", + "* `program_id` determines the Qiskit Runtime program run, i.e. `'vqe'` for the VQE\n", + "* `inputs` contains the input for the VQE algorithm\n", + "* `options` specifies the backend\n", + "* `callback` is the callback used inside the VQE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's gather all the inputs to the VQE in a dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "vqe_inputs = {\n", + " 'ansatz': ansatz,\n", + " 'operator': hamiltonian,\n", + " 'optimizer': {'name': 'SPSA', 'maxiter': 5}, # let's only do a few iterations!\n", + " 'initial_point': initial_point,\n", + " 'measurement_error_mitigation': True,\n", + " 'shots': 1024\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The backend options only need to contain the name of the backend." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "backend_options = {\n", + " 'backend_name': backend.name()\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The callback acts a little different than before. Instead of only being passed the VQE callback arguments, it is passed a tuple with the job ID as first argument, followed by the VQE arguments." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "intermediate_info = {\n", + " 'nfev': [],\n", + " 'parameters': [],\n", + " 'energy': [],\n", + " 'stddev': []\n", + "}\n", + "\n", + "def raw_callback(*args):\n", + " job_id, (nfev, parameters, energy, stddev) = args\n", + " intermediate_info['nfev'].append(nfev)\n", + " intermediate_info['parameters'].append(parameters)\n", + " intermediate_info['energy'].append(energy)\n", + " intermediate_info['stddev'].append(stddev)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can call the Qiskit Runtime VQE directly, without going through the convenience of the `VQEProgram`. \n", + "Note, that the result will not be of the same type as `VQE` or `VQEProgram`, but a plain dictionary." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job ID: c2d4ut8likuqc7s4nqog\n" + ] + } + ], + "source": [ + "job = provider.runtime.run(\n", + " program_id='vqe',\n", + " inputs=vqe_inputs,\n", + " options=backend_options,\n", + " callback=raw_callback\n", + ")\n", + "print('Job ID:', job.job_id())" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "result = job.result()" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reached -0.0805234521211203 after 16 evaluations.\n" + ] + } + ], + "source": [ + "print(f'Reached {result[\"optimal_value\"]} after {result[\"optimizer_evals\"]} evaluations.')" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available keys: ['optimizer_evals', 'optimizer_time', 'optimal_value', 'optimal_point', 'optimal_parameters', 'cost_function_evals', 'eigenstate', 'eigenvalue', 'aux_operator_eigenvalues', 'optimizer_history']\n" + ] + } + ], + "source": [ + "print('Available keys:', list(result.keys()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.7.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}