diff --git a/docs/guides/_toc.json b/docs/guides/_toc.json
index 738db97a12..693b875efb 100644
--- a/docs/guides/_toc.json
+++ b/docs/guides/_toc.json
@@ -109,7 +109,7 @@
{
"title": "Multiverse Computing Singularity",
"url": "/guides/multiverse-computing-singularity"
- },
+ },
{
"title": "Q-CTRL Optimization Solver",
"url": "/guides/q-ctrl-optimization-solver"
@@ -512,6 +512,10 @@
{
"title": "Port code to Qiskit Serverless",
"url": "/guides/serverless-port-code"
+ },
+ {
+ "title": "Build a Function template for Hamiltonian simulation",
+ "url": "/guides/serverless-hamsim-template"
}
]
},
diff --git a/docs/guides/serverless-hamsim-template.ipynb b/docs/guides/serverless-hamsim-template.ipynb
new file mode 100644
index 0000000000..b3cff66637
--- /dev/null
+++ b/docs/guides/serverless-hamsim-template.ipynb
@@ -0,0 +1,1087 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "b1fde164-2b2b-473b-93b8-30168882076b",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "# Build a function template for Hamiltonian simulation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4bbc25a8-5315-417f-bf35-7ac715ebdf98",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": [
+ "version-info"
+ ]
+ },
+ "source": [
+ "\n",
+ "Package versions
\n",
+ "\n",
+ "The code on this page was developed using the following requirements.\n",
+ "We recommend using these versions or newer.\n",
+ "\n",
+ "```\n",
+ "qiskit[all]~=1.2.4\n",
+ "qiskit-ibm-runtime~=0.32.0\n",
+ "qiskit-serverless~=0.18.0\n",
+ "qiskit-ibm-catalog~=0.2.0\n",
+ "qiskit-addon-aqc-tensor[quimb-jax]~=0.1.0\n",
+ "mergedeep==1.3.4\n",
+ "```\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5092090f-0fb8-436e-8099-3d3059367efc",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "By now you've seen a basic example of how to get started writing, uploading, and running a program with Qiskit Serverless. If you haven't, start with [What is Qiskit Serverless?](/guides/serverless) for some background information first.\n",
+ "\n",
+ "The workloads you are building for your own use cases are likely not as simple as the examples previously shown. For example, you might need to carefully consider what domain-level inputs and outputs are relevant for your application, how to make sure your program can be reusable across a range of those inputs, and what kind of information you need returned during the execution of the job so you can better evaluate the progress of your workload. You might even want to incorporate a \"dry run\" mode that lets you test the impact of a set of particular inputs before sending the resulting circuits to hardware.\n",
+ "\n",
+ "A function template is an example of a realistic workload that uses a specific application domain to contextualize these aspects. It is meant to be a starting point for you to modify for your own needs so you don't have to start from scratch.\n",
+ "\n",
+ "This guide walks through a function template for addressing Hamiltonian simulation problems."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "efde8ea3-8ba4-4859-b7cd-7c4e5c049783",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## Write the function template\n",
+ "\n",
+ "First, write a function template for Hamiltonian simulation that uses the [AQC-Tensor Qiskit addon](https://qiskit.github.io/qiskit-addon-aqc-tensor/) to map the problem description to a reduced-depth circuit for execution on hardware.\n",
+ "\n",
+ "Throughout, the code is saved to `./source_files/function_template_hamsim.py`. This file is the function template you can upload to and run remotely with Qiskit Serverless."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "921071c7-910c-48b7-87f1-f9f6f4a5ca61",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": [
+ "remove-cell"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "# This cell is hidden from users, it just creates a new folder\n",
+ "from pathlib import Path\n",
+ "\n",
+ "Path(\"./source_files\").mkdir(exist_ok=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "227d4f76-14cd-4853-b986-f6b09cecb26a",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "### Get the domain-level inputs\n",
+ "\n",
+ "Start by getting the inputs for the template. This example has domain-specific inputs relevant for Hamiltonian simulation (such as the Hamiltonian and observable) and capability-specific options (such as how much you want to compress the initial layers of the Trotter circuit using AQC-Tensor or advanced options for fine-tuning error suppression and mitigation beyond the defaults that are part of this example)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "0123aa49-25ee-44d3-93ce-9b1af13487df",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Overwriting ./source_files/function_template_hamsim.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile ./source_files/function_template_hamsim.py\n",
+ "\n",
+ "from qiskit import QuantumCircuit\n",
+ "from qiskit_serverless import get_arguments, save_result\n",
+ "\n",
+ "\n",
+ "# Extract parameters from arguments\n",
+ "#\n",
+ "# Do this at the top of the program so it fails early if any required arguments are missing or invalid.\n",
+ "\n",
+ "arguments = get_arguments()\n",
+ "\n",
+ "dry_run = arguments.get(\"dry_run\", False)\n",
+ "backend_name = arguments[\"backend_name\"]\n",
+ "\n",
+ "aqc_evolution_time = arguments[\"aqc_evolution_time\"]\n",
+ "aqc_ansatz_num_trotter_steps = arguments[\"aqc_ansatz_num_trotter_steps\"]\n",
+ "aqc_target_num_trotter_steps = arguments[\"aqc_target_num_trotter_steps\"]\n",
+ "\n",
+ "remainder_evolution_time = arguments[\"remainder_evolution_time\"]\n",
+ "remainder_num_trotter_steps = arguments[\"remainder_num_trotter_steps\"]\n",
+ "\n",
+ "# Stop if this fidelity is achieved\n",
+ "aqc_stopping_fidelity = arguments.get(\"aqc_stopping_fidelity\", 1.0)\n",
+ "# Stop after this number of iterations, even if stopping fidelity is not achieved\n",
+ "aqc_max_iterations = arguments.get(\"aqc_max_iterations\", 500)\n",
+ "\n",
+ "hamiltonian = arguments[\"hamiltonian\"]\n",
+ "observable = arguments[\"observable\"]\n",
+ "initial_state = arguments.get(\"initial_state\", QuantumCircuit(hamiltonian.num_qubits))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "68724647-f6aa-4977-ab40-5be3de997499",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Appending to ./source_files/function_template_hamsim.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile --append ./source_files/function_template_hamsim.py\n",
+ "\n",
+ "import numpy as np\n",
+ "import json\n",
+ "from mergedeep import merge\n",
+ "\n",
+ "\n",
+ "# Configure `EstimatorOptions`, to control the parameters of the hardware experiment\n",
+ "#\n",
+ "# Set default options\n",
+ "estimator_default_options = {\n",
+ " \"resilience\": {\n",
+ " \"measure_mitigation\": True,\n",
+ " \"zne_mitigation\": True,\n",
+ " \"zne\": {\n",
+ " \"amplifier\": \"gate_folding\",\n",
+ " \"noise_factors\": [1, 2, 3],\n",
+ " \"extrapolated_noise_factors\": list(np.linspace(0, 3, 31)),\n",
+ " \"extrapolator\": [\"exponential\", \"linear\", \"fallback\"],\n",
+ " },\n",
+ " \"measure_noise_learning\": {\n",
+ " \"num_randomizations\": 512,\n",
+ " \"shots_per_randomization\": 512,\n",
+ " },\n",
+ " },\n",
+ " \"twirling\": {\n",
+ " \"enable_gates\": True,\n",
+ " \"enable_measure\": True,\n",
+ " \"num_randomizations\": 300,\n",
+ " \"shots_per_randomization\": 100,\n",
+ " \"strategy\": \"active\",\n",
+ " },\n",
+ "}\n",
+ "# Merge with user-provided options\n",
+ "estimator_options = merge(\n",
+ " arguments.get(\"estimator_options\", {}), estimator_default_options\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "939b9b9c-34b7-40a1-92fc-2b1639aee349",
+ "metadata": {},
+ "source": [
+ "When the function template is running, it is helpful to return information in the logs by using print statements, so that you can better evaluate the workload's progress. Following is a simple example of printing the `estimator_options` so there is a record of the actual Estimator options used. There are many more similar examples throughout the program to report progress during execution, including the value of the objective function during the iterative component of AQC-Tensor, and the two-qubit depth of the final instruction set architecture (ISA) circuit intended for execution on hardware."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d8d67489-9c92-42c0-a7fc-a347077ac72e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%writefile --append ./source_files/function_template_hamsim.py\n",
+ "\n",
+ "print(\"estimator_options =\", json.dumps(estimator_options, indent=4))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a6b70531-d5c4-42c5-8fcc-9e19c32f32e8",
+ "metadata": {},
+ "source": [
+ "### Validate the inputs\n",
+ "\n",
+ "An important aspect of ensuring that the template can be reused across a range of inputs is to validate them. The following code is an example of verifying that the stopping fidelity during AQC-Tensor has been specified appropriately and if not, return an informative error message for how to fix the error."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f1b690c4-2e06-4f2a-916c-827237a4632a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%writefile --append ./source_files/function_template_hamsim.py\n",
+ "\n",
+ "# Perform parameter validation\n",
+ "\n",
+ "if not 0.0 < aqc_stopping_fidelity <= 1.0:\n",
+ " raise ValueError(\n",
+ " f\"Invalid stopping fidelity: {aqc_stopping_fidelity}. It must be a positive float no greater than 1.\"\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7f4bfe7f-e457-433a-9420-06a680323a33",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "### Perform the Hamiltonian simulation workflow using AQC-Tensor\n",
+ "\n",
+ "First, prepare a dictionary to hold all of the function template output. Keys will be added to this dictionary throughout the workflow, and it is returned at the end of the program."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "932e4d9c-2493-4cf4-84c3-54e679814842",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Appending to ./source_files/function_template_hamsim.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile --append ./source_files/function_template_hamsim.py\n",
+ "\n",
+ "output = {}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8d09b59d-8065-446c-966d-b67f49eba6ec",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "#### Step 1: Map\n",
+ "\n",
+ "The AQC-Tensor optimization happens in step 1 of a Qiskit pattern. First, a target state is constructed. In this example, it is constructed from a target circuit that evolves the same Hamiltonian for the same time period as the AQC portion. Then, an ansatz is generated from an equivalent circuit but with fewer Trotter steps. In the main portion of the AQC algorithm, that ansatz is iteratively brought closer to the target state. Finally, the result is combined with the remainder of the Trotter steps needed to reach the desired evolution time.\n",
+ "\n",
+ "Note the additional examples of logging incorporated in the following code."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "05926050-da5d-4c16-a0e7-ea5a875ea9b0",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Appending to ./source_files/function_template_hamsim.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile --append ./source_files/function_template_hamsim.py\n",
+ "\n",
+ "import datetime\n",
+ "import quimb.tensor\n",
+ "from scipy.optimize import OptimizeResult, minimize\n",
+ "from qiskit.synthesis import SuzukiTrotter\n",
+ "from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit\n",
+ "from qiskit_addon_aqc_tensor.ansatz_generation import (\n",
+ " generate_ansatz_from_circuit,\n",
+ " AnsatzBlock,\n",
+ ")\n",
+ "from qiskit_addon_aqc_tensor.simulation import (\n",
+ " tensornetwork_from_circuit,\n",
+ " compute_overlap,\n",
+ ")\n",
+ "from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator\n",
+ "from qiskit_addon_aqc_tensor.objective import OneMinusFidelity\n",
+ "\n",
+ "print(\"Hamiltonian:\", hamiltonian)\n",
+ "print(\"Observable:\", observable)\n",
+ "simulator_settings = QuimbSimulator(quimb.tensor.CircuitMPS, autodiff_backend=\"jax\")\n",
+ "\n",
+ "# Construct the AQC target circuit\n",
+ "aqc_target_circuit = initial_state.copy()\n",
+ "if aqc_evolution_time:\n",
+ " aqc_target_circuit.compose(\n",
+ " generate_time_evolution_circuit(\n",
+ " hamiltonian,\n",
+ " synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),\n",
+ " time=aqc_evolution_time,\n",
+ " ),\n",
+ " inplace=True,\n",
+ " )\n",
+ "\n",
+ "# Construct matrix-product state representation of the AQC target state\n",
+ "aqc_target_mps = tensornetwork_from_circuit(aqc_target_circuit, simulator_settings)\n",
+ "print(\"Target MPS maximum bond dimension:\", aqc_target_mps.psi.max_bond())\n",
+ "output[\"target_bond_dimension\"] = aqc_target_mps.psi.max_bond()\n",
+ "\n",
+ "# Generate an ansatz and initial parameters from a Trotter circuit with fewer steps\n",
+ "aqc_good_circuit = initial_state.copy()\n",
+ "if aqc_evolution_time:\n",
+ " aqc_good_circuit.compose(\n",
+ " generate_time_evolution_circuit(\n",
+ " hamiltonian,\n",
+ " synthesis=SuzukiTrotter(reps=aqc_ansatz_num_trotter_steps),\n",
+ " time=aqc_evolution_time,\n",
+ " ),\n",
+ " inplace=True,\n",
+ " )\n",
+ "aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(aqc_good_circuit)\n",
+ "print(\"Number of AQC parameters:\", len(aqc_initial_parameters))\n",
+ "output[\"num_aqc_parameters\"] = len(aqc_initial_parameters)\n",
+ "\n",
+ "# Calculate the fidelity of ansatz circuit vs. the target state, before optimization\n",
+ "good_mps = tensornetwork_from_circuit(aqc_good_circuit, simulator_settings)\n",
+ "starting_fidelity = abs(compute_overlap(good_mps, aqc_target_mps)) ** 2\n",
+ "print(\"Starting fidelity of AQC portion:\", starting_fidelity)\n",
+ "output[\"aqc_starting_fidelity\"] = starting_fidelity\n",
+ "\n",
+ "# Optimize the ansatz parameters by using MPS calculations\n",
+ "def callback(intermediate_result: OptimizeResult):\n",
+ " fidelity = 1 - intermediate_result.fun\n",
+ " print(f\"{datetime.datetime.now()} Intermediate result: Fidelity {fidelity:.8f}\")\n",
+ " if intermediate_result.fun < stopping_point:\n",
+ " raise StopIteration\n",
+ "\n",
+ "\n",
+ "objective = OneMinusFidelity(aqc_target_mps, aqc_ansatz, simulator_settings)\n",
+ "stopping_point = 1.0 - aqc_stopping_fidelity\n",
+ "\n",
+ "result = minimize(\n",
+ " objective,\n",
+ " aqc_initial_parameters,\n",
+ " method=\"L-BFGS-B\",\n",
+ " jac=True,\n",
+ " options={\"maxiter\": aqc_max_iterations},\n",
+ " callback=callback,\n",
+ ")\n",
+ "if result.status not in (\n",
+ " 0,\n",
+ " 1,\n",
+ " 99,\n",
+ "): # 0 => success; 1 => max iterations reached; 99 => early termination via StopIteration\n",
+ " raise RuntimeError(\n",
+ " f\"Optimization failed: {result.message} (status={result.status})\"\n",
+ " )\n",
+ "print(f\"Done after {result.nit} iterations.\")\n",
+ "output[\"num_iterations\"] = result.nit\n",
+ "aqc_final_parameters = result.x\n",
+ "output[\"aqc_final_parameters\"] = list(aqc_final_parameters)\n",
+ "\n",
+ "# Construct an optimized circuit for initial portion of time evolution\n",
+ "aqc_final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)\n",
+ "\n",
+ "# Calculate fidelity after optimization\n",
+ "aqc_final_mps = tensornetwork_from_circuit(aqc_final_circuit, simulator_settings)\n",
+ "aqc_fidelity = abs(compute_overlap(aqc_final_mps, aqc_target_mps)) ** 2\n",
+ "print(\"Fidelity of AQC portion:\", aqc_fidelity)\n",
+ "output[\"aqc_fidelity\"] = aqc_fidelity\n",
+ "\n",
+ "# Construct final circuit, with remainder of time evolution\n",
+ "final_circuit = aqc_final_circuit.copy()\n",
+ "if remainder_evolution_time:\n",
+ " remainder_circuit = generate_time_evolution_circuit(\n",
+ " hamiltonian,\n",
+ " synthesis=SuzukiTrotter(reps=remainder_num_trotter_steps),\n",
+ " time=remainder_evolution_time,\n",
+ " )\n",
+ " final_circuit.compose(remainder_circuit, inplace=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5ae54748-f104-4022-9383-f54570be833f",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "#### Step 2: Optimize\n",
+ "\n",
+ "After the AQC portion of the workflow, the `final_circuit` is [transpiled for the hardware](/guides/transpile#instruction-set-architecture) as usual."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "971344cd-455e-4017-9faa-ec66f6b58316",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Appending to ./source_files/function_template_hamsim.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile --append ./source_files/function_template_hamsim.py\n",
+ "\n",
+ "from qiskit_ibm_runtime import QiskitRuntimeService\n",
+ "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
+ "\n",
+ "service = QiskitRuntimeService()\n",
+ "backend = service.backend(backend_name)\n",
+ "\n",
+ "# Transpile PUBs (circuits and observables) to match ISA\n",
+ "pass_manager = generate_preset_pass_manager(backend=backend, optimization_level=3)\n",
+ "isa_circuit = pass_manager.run(final_circuit)\n",
+ "isa_observable = observable.apply_layout(isa_circuit.layout)\n",
+ "\n",
+ "isa_2qubit_depth = isa_circuit.depth(lambda x: x.operation.num_qubits == 2)\n",
+ "print(\"ISA circuit two-qubit depth:\", isa_2qubit_depth)\n",
+ "output[\"twoqubit_depth\"] = isa_2qubit_depth"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "226d20da-391f-4131-8711-9cb7adad908b",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "#### Exit early if using dry run mode\n",
+ "\n",
+ "If dry run mode has been selected, then the program is stopped before executing on hardware. This can be useful if, for example, you want first to inspect the two-qubit depth of the ISA circuit before deciding to execute on hardware."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "8f6c84e3-3b18-4328-9f64-1a54a218946f",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Appending to ./source_files/function_template_hamsim.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile --append ./source_files/function_template_hamsim.py\n",
+ "\n",
+ "# Exit now if dry run; don't execute on hardware\n",
+ "if dry_run:\n",
+ " import sys\n",
+ "\n",
+ " print(\"Exiting before hardware execution since `dry_run` is True.\")\n",
+ " save_result(output)\n",
+ " sys.exit(0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "34751113-6ad1-43a6-b2ac-fac10fe02eae",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "#### Step 3: Execute on hardware"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "18fa45c7-9f4e-4513-a9ff-2796aef65e69",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Appending to ./source_files/function_template_hamsim.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile --append ./source_files/function_template_hamsim.py\n",
+ "\n",
+ "# ## Step 3: Execute quantum experiments on backend\n",
+ "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n",
+ "\n",
+ "\n",
+ "estimator = Estimator(backend, options=estimator_options)\n",
+ "\n",
+ "# Submit the underlying Estimator job. Note that this is not the\n",
+ "# actual function job.\n",
+ "job = estimator.run([(isa_circuit, isa_observable)])\n",
+ "print(\"Job ID:\", job.job_id())\n",
+ "output[\"job_id\"] = job.job_id()\n",
+ "\n",
+ "# Wait until job is complete\n",
+ "hw_results = job.result()\n",
+ "hw_results_dicts = [pub_result.data.__dict__ for pub_result in hw_results]\n",
+ "\n",
+ "# Save hardware results to serverless output dictionary\n",
+ "output[\"hw_results\"] = hw_results_dicts\n",
+ "\n",
+ "# Reorganize expectation values\n",
+ "hw_expvals = [pub_result_data[\"evs\"].tolist() for pub_result_data in hw_results_dicts]\n",
+ "\n",
+ "# Save expectation values to Qiskit Serverless\n",
+ "print(\"Hardware expectation values\", hw_expvals)\n",
+ "output[\"hw_expvals\"] = hw_expvals[0]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1ea7d04b-2696-42bd-9247-d20d67d47af3",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "#### Save the output\n",
+ "\n",
+ "This function template returns the relevant domain-level output for this Hamiltonian simulation workflow (expectation values) in addition to important metadata generated along the way."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "bc05e9a9-2b9e-4aef-b6d5-30858445d774",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Appending to ./source_files/function_template_hamsim.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile --append ./source_files/function_template_hamsim.py\n",
+ "\n",
+ "save_result(output)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3eae249e-94a2-46db-942f-4935c8d70642",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## Deploy to IBM Quantum Platform\n",
+ "\n",
+ "The previous section created a program to be run remotely. The code in this section uploads that program to Qiskit Serverless.\n",
+ "\n",
+ "Use `qiskit-ibm-catalog` to authenticate to `QiskitServerless` with your API token, which you can find in your [IBM Quantum™ account](https://quantum.ibm.com/account), and upload the program.\n",
+ "\n",
+ "You can optionally use `save_account()` to save your credentials (See the [\"Authenticate to the service\" step](./setup-channel#save-account)) in the Set up to use IBM Quantum Platform guide. Note that this writes your credentials to the same file as [`QiskitRuntimeService.save_account()`.](/api/qiskit-ibm-runtime/qiskit_ibm_runtime.QiskitRuntimeService#save_account)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "b3017f23-b5a9-40c1-80b9-d12232f723a8",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "from qiskit_ibm_catalog import QiskitServerless, QiskitFunction\n",
+ "\n",
+ "# Authenticate to the remote cluster and submit the pattern for remote execution\n",
+ "serverless = QiskitServerless()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "faf301a3-32db-4a05-ae8b-45fc3caef05e",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "This program has custom `pip` dependencies. Add them to a `dependencies` array when constructing the `QiskitFunction` instance:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "id": "08693aba-eeaf-4f0c-b7d9-b9ce709a36b3",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "template = QiskitFunction(\n",
+ " title=\"function_template_hamsim\",\n",
+ " entrypoint=\"function_template_hamsim.py\",\n",
+ " working_dir=\"./source_files/\",\n",
+ " dependencies=[\n",
+ " \"qiskit-addon-utils==0.1.0\",\n",
+ " \"qiskit-addon-aqc-tensor[quimb-jax]==0.1.0\",\n",
+ " \"mergedeep==1.3.4\",\n",
+ " ],\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "id": "2407a391-4199-45d5-bf0f-4cd582860554",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "QiskitFunction(function_template_hamsim)"
+ ]
+ },
+ "execution_count": 30,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "serverless.upload(template)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2fff9c39-620c-4307-b0b0-8f29ff3454b3",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "To check if the program successfully uploaded, use `serverless.list()`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "id": "10f9ee91-2539-4dc7-bd16-3c6b455865d8",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[QiskitFunction(function_template_hamsim)]"
+ ]
+ },
+ "execution_count": 31,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "serverless.list()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c81a3d08-b43b-4160-b4c8-5b196ab0b204",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## Run the function template remotely\n",
+ "\n",
+ "The function template has been uploaded, so you can run it remotely with Qiskit Serverless. First, load the template by name:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "id": "8f06ef41-e551-484d-843c-23e9d3347757",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "template = serverless.load(\"function_template_hamsim\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d1f30310-ff43-42b5-9711-764d12deff09",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "Next, run the template with the domain-level inputs for Hamiltonian simulation. This example specifies simple four-qubit Hamiltonian and an observable."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "id": "560eed26-78cb-4c48-9f35-7ac6e88aa648",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "from qiskit.quantum_info import SparsePauliOp\n",
+ "\n",
+ "hamiltonian = SparsePauliOp.from_sparse_list(\n",
+ " [(\"XX\", (i, i + 1), 1.0) for i in range(3)], num_qubits=4\n",
+ ") + SparsePauliOp.from_sparse_list(\n",
+ " [(\"YY\", (i, i + 1), 1.0) for i in range(3)], num_qubits=4\n",
+ ")\n",
+ "observable = SparsePauliOp.from_sparse_list(\n",
+ " [(\"ZZ\", (1, 2), 1.0)], num_qubits=4\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "id": "f7f8cb70-8b81-4758-a105-0911faf0c9fa",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "bd22fb47-ee0d-499f-8e85-2b55b526e23b\n"
+ ]
+ }
+ ],
+ "source": [
+ "job = template.run(\n",
+ " dry_run=True,\n",
+ " hamiltonian=hamiltonian,\n",
+ " observable=observable,\n",
+ " backend_name=\"ibm_fez\",\n",
+ " estimator_options={},\n",
+ " aqc_evolution_time=0.2,\n",
+ " aqc_ansatz_num_trotter_steps=1,\n",
+ " aqc_target_num_trotter_steps=32,\n",
+ " remainder_evolution_time=0.2,\n",
+ " remainder_num_trotter_steps=4,\n",
+ " aqc_max_iterations=300,\n",
+ ")\n",
+ "print(job.job_id)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "efbf4bda-e434-4b51-a47a-9cd8b220c903",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "Check the status of the job:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "42fff535-84c7-4312-8fb9-e0f774a60405",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "job.status()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "df723b45-5083-4ee4-9194-1d2822f26982",
+ "metadata": {},
+ "source": [
+ "After the job is running, you can fetch logs created from the `print()` outputs. These can provide actionable information about the progress of the Hamiltonian simulation workflow. For example, the value of the objective function during the iterative component of AQC or the two-qubit depth of the final ISA circuit intended for execution on hardware."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "37ffbb99-cfde-4e0b-90b5-2ae935897a15",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "job.logs()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ee48f1c7-2239-4a85-b907-37e27d6a98fb",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "Block the rest of the program until a result is available. After the job is done, you can retrieve the results. These include the domain-level output of Hamiltonian simulation (expectation value) and useful metadata."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "adac32a9-e231-489a-a59e-2ecf449ea90a",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "job.result()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "98def6cc-186e-4b4a-982c-8d87e8fd6e09",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## Next steps\n",
+ "\n",
+ "\n",
+ "\n",
+ "For a deeper dive into the AQC-Tensor Qiskit addon, check out the [Improved Trotterized Time Evolution with Approximate Quantum Compilation](https://learning.quantum.ibm.com/tutorial/improved-trotterized-time-evolution-with-approximate-quantum-compilation) tutorial.\n",
+ "\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c0984a9e-15e9-44ca-b41e-a2116fab00b0",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": [
+ "remove-cell"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "# This cell is hidden from users, it just deletes the working folder we created\n",
+ "import shutil\n",
+ "\n",
+ "shutil.rmtree(\"./source_files/\")"
+ ]
+ }
+ ],
+ "metadata": {
+ "description": "How to create a parallel transpilation program and deploy it to IBM Quantum Platform to use as a reusable remote service.",
+ "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"
+ },
+ "title": "Write your first Qiskit Serverless program"
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/qiskit_bot.yaml b/qiskit_bot.yaml
index 60b3901be2..9097aab9e8 100644
--- a/qiskit_bot.yaml
+++ b/qiskit_bot.yaml
@@ -133,6 +133,10 @@ notifications:
"docs/guides/monitor-job":
- "@jyu00"
- "@beckykd"
+ "docs/guides/serverless-hamsim-template":
+ - "@jenglick"
+ - "@garrison"
+ - "@beckykd"
"docs/guides/operator-class":
- "`@mtreinish`"
"docs/guides/online-lab-environments":
diff --git a/scripts/config/cspell/dictionaries/qiskit.txt b/scripts/config/cspell/dictionaries/qiskit.txt
index e0cdc29274..29d4ef6397 100644
--- a/scripts/config/cspell/dictionaries/qiskit.txt
+++ b/scripts/config/cspell/dictionaries/qiskit.txt
@@ -56,6 +56,7 @@ Qedma
Qiskit
Qiskit
Quna
+quimb
RCCX
SLSQP
SPSA
diff --git a/scripts/config/notebook-testing.toml b/scripts/config/notebook-testing.toml
index 7ad0c88233..fb384de64e 100644
--- a/scripts/config/notebook-testing.toml
+++ b/scripts/config/notebook-testing.toml
@@ -47,6 +47,7 @@ notebooks_exclude = [
"docs/guides/ibm-circuit-function.ipynb",
"docs/guides/qiskit-addons-sqd-get-started.ipynb",
"docs/guides/fractional-gates.ipynb",
+ "docs/guides/serverless-hamsim-template.ipynb",
"docs/guides/save-jobs.ipynb",
]
diff --git a/scripts/js/commands/checkPatternsIndex.ts b/scripts/js/commands/checkPatternsIndex.ts
index 8fac25215f..dbf4564889 100644
--- a/scripts/js/commands/checkPatternsIndex.ts
+++ b/scripts/js/commands/checkPatternsIndex.ts
@@ -21,6 +21,7 @@ const ALLOWLIST_MISSING_FROM_INDEX: Set = new Set([
"/guides/qiskit-code-assistant-jupyterlab",
"/guides/qiskit-code-assistant-vscode",
"/guides/addons",
+ "/guides/serverless-hamsim-template",
]);
// URLs that show up in the INDEX_PAGES, but are not in the left ToC under