433 lines
14 KiB
Plaintext
433 lines
14 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "eb419bc5-908c-4f0d-a4ff-c4d13f04e332",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"source": [
|
|
"# Ground-state energy estimation of the Heisenberg chain with VQE\n",
|
|
"*Usage estimate: 2 minutes on IBM Cusco (NOTE: This is an estimate only. Your runtime may vary.)*"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "49d868bf",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Background\n",
|
|
"\n",
|
|
"In this tutorial, we will show how to build, deploy, and run a `Qiskit Pattern` for simulating a Heisenberg chain and estimating its ground state energy. For more information on `Qiskit Patterns` and how `Qiskit Serverless` can be used to deploy them to the cloud for managed execution, visit our [docs page on the IBM Quantum Platform](/docs/guides/serverless)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "bc52f763",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Requirements\n",
|
|
"\n",
|
|
"Before starting this tutorial, ensure that you have the following installed:\n",
|
|
"\n",
|
|
"* Qiskit SDK 1.2 or later, with visualization support (`pip install 'qiskit[visualization]'`)\n",
|
|
"* Qiskit Runtime 0.28 or later (`pip install qiskit-ibm-runtime`) 0.22 or later"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "a46e9e3e",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Setup"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "e7754922",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import numpy as np\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"\n",
|
|
"from scipy.optimize import minimize\n",
|
|
"from typing import Sequence\n",
|
|
"\n",
|
|
"\n",
|
|
"from qiskit import QuantumCircuit\n",
|
|
"from qiskit.quantum_info import SparsePauliOp\n",
|
|
"from qiskit.primitives.base import BaseEstimatorV2\n",
|
|
"from qiskit.circuit.library import XGate\n",
|
|
"from qiskit.circuit.library import efficient_su2\n",
|
|
"from qiskit.transpiler import PassManager\n",
|
|
"from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
|
|
"from qiskit.transpiler.passes.scheduling import (\n",
|
|
" ALAPScheduleAnalysis,\n",
|
|
" PadDynamicalDecoupling,\n",
|
|
")\n",
|
|
"\n",
|
|
"from qiskit_ibm_runtime import QiskitRuntimeService\n",
|
|
"from qiskit_ibm_runtime import Session, Estimator\n",
|
|
"\n",
|
|
"from qiskit_ibm_catalog import QiskitServerless, QiskitFunction\n",
|
|
"\n",
|
|
"\n",
|
|
"def visualize_results(results):\n",
|
|
" plt.plot(results[\"cost_history\"], lw=2)\n",
|
|
" plt.xlabel(\"Iteration\")\n",
|
|
" plt.ylabel(\"Energy\")\n",
|
|
" plt.show()\n",
|
|
"\n",
|
|
"\n",
|
|
"def build_callback(\n",
|
|
" ansatz: QuantumCircuit,\n",
|
|
" hamiltonian: SparsePauliOp,\n",
|
|
" estimator: BaseEstimatorV2,\n",
|
|
" callback_dict: dict,\n",
|
|
"):\n",
|
|
" def callback(current_vector):\n",
|
|
" # Keep track of the number of iterations\n",
|
|
" callback_dict[\"iters\"] += 1\n",
|
|
" # Set the prev_vector to the latest one\n",
|
|
" callback_dict[\"prev_vector\"] = current_vector\n",
|
|
" # Compute the value of the cost function at the current vector\n",
|
|
" current_cost = (\n",
|
|
" estimator.run([(ansatz, hamiltonian, [current_vector])])\n",
|
|
" .result()[0]\n",
|
|
" .data.evs[0]\n",
|
|
" )\n",
|
|
" callback_dict[\"cost_history\"].append(current_cost)\n",
|
|
" # Print to screen on single line\n",
|
|
" print(\n",
|
|
" \"Iters. done: {} [Current cost: {}]\".format(\n",
|
|
" callback_dict[\"iters\"], current_cost\n",
|
|
" ),\n",
|
|
" end=\"\\r\",\n",
|
|
" flush=True,\n",
|
|
" )\n",
|
|
"\n",
|
|
" return callback"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "132fb15f-10b4-4d7e-83d8-f512a6f675d1",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 1: Map classical inputs to a quantum problem\n",
|
|
"\n",
|
|
"* Input: Number of spins\n",
|
|
"* Output: Ansatz and Hamiltonian modeling the Heisenberg chain\n",
|
|
"\n",
|
|
"Construct an ansatz and Hamiltonian which model a 10-spin Heisenberg chain. First, we import some generic packages and create a couple of helper functions."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "7e8d2f10-f1d6-4ec2-bac9-9db23499c9e1",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/spin-chain-vqe/extracted-outputs/7e8d2f10-f1d6-4ec2-bac9-9db23499c9e1-0.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"num_spins = 10\n",
|
|
"ansatz = efficient_su2(num_qubits=num_spins, reps=3)\n",
|
|
"\n",
|
|
"# Remember to insert your token in the QiskitRuntimeService constructor\n",
|
|
"service = QiskitRuntimeService()\n",
|
|
"backend = service.least_busy(\n",
|
|
" operational=True, min_num_qubits=num_spins, simulator=False\n",
|
|
")\n",
|
|
"\n",
|
|
"coupling = backend.target.build_coupling_map()\n",
|
|
"reduced_coupling = coupling.reduce(list(range(num_spins)))\n",
|
|
"\n",
|
|
"edge_list = reduced_coupling.graph.edge_list()\n",
|
|
"ham_list = []\n",
|
|
"\n",
|
|
"for edge in edge_list:\n",
|
|
" ham_list.append((\"ZZ\", edge, 0.5))\n",
|
|
" ham_list.append((\"YY\", edge, 0.5))\n",
|
|
" ham_list.append((\"XX\", edge, 0.5))\n",
|
|
"\n",
|
|
"for qubit in reduced_coupling.physical_qubits:\n",
|
|
" ham_list.append((\"Z\", [qubit], np.random.random() * 2 - 1))\n",
|
|
"\n",
|
|
"hamiltonian = SparsePauliOp.from_sparse_list(ham_list, num_qubits=num_spins)\n",
|
|
"\n",
|
|
"ansatz.draw(\"mpl\", style=\"iqp\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "ab79119b-5e56-49d8-a20e-1c8e665baec0",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 2: Optimize problem for quantum hardware execution\n",
|
|
"\n",
|
|
"* Input: Abstract circuit, observable\n",
|
|
"* Output: Target circuit and observable, optimized for the selected QPU\n",
|
|
"\n",
|
|
"Use the `generate_preset_pass_manager` function from Qiskit to automatically generate an optimization routine for our circuit with respect to the selected QPU. We choose `optimization_level=3`, which provides the highest level of optimization of the preset pass managers. We also include `ALAPScheduleAnalysis` and `PadDynamicalDecoupling` scheduling passes to suppress decoherence errors."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "a0a5f1c8-5c31-4d9f-ae81-37bd67271d44",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/spin-chain-vqe/extracted-outputs/a0a5f1c8-5c31-4d9f-ae81-37bd67271d44-0.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"target = backend.target\n",
|
|
"pm = generate_preset_pass_manager(optimization_level=3, backend=backend)\n",
|
|
"pm.scheduling = PassManager(\n",
|
|
" [\n",
|
|
" ALAPScheduleAnalysis(durations=target.durations()),\n",
|
|
" PadDynamicalDecoupling(\n",
|
|
" durations=target.durations(),\n",
|
|
" dd_sequence=[XGate(), XGate()],\n",
|
|
" pulse_alignment=target.pulse_alignment,\n",
|
|
" ),\n",
|
|
" ]\n",
|
|
")\n",
|
|
"ansatz_ibm = pm.run(ansatz)\n",
|
|
"observable_ibm = hamiltonian.apply_layout(ansatz_ibm.layout)\n",
|
|
"ansatz_ibm.draw(\"mpl\", scale=0.6, style=\"iqp\", fold=-1, idle_wires=False)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "9e889d0b-30b5-4e6b-84c9-d1f096abf132",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 3: Execute using Qiskit primitives\n",
|
|
"\n",
|
|
"* Input: Target circuit and observable\n",
|
|
"* Output: Results of optimization\n",
|
|
"\n",
|
|
"Minimize the estimated ground state energy of the system by optimizing the circuit parameters. Use the `Estimator` primitive from Qiskit Runtime to evaluate the cost function during optimization.\n",
|
|
"\n",
|
|
"Since we optimized the circuit for the backend in Step 2, we can avoid doing transpilation on the Runtime server by setting `skip_transpilation=True` and passing the optimized circuit. For this demo, we will run on a QPU using `qiskit-ibm-runtime` primitives. To run with `qiskit` statevector-based primitives, replace the block of code using Qiskit IBM Runtime primitives with the commented block."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "4c4b1b0b-5c61-4587-986c-7a9108bc2505",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Iters. done: 101 [Current cost: -2.5127326712407005]\r"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/spin-chain-vqe/extracted-outputs/4c4b1b0b-5c61-4587-986c-7a9108bc2505-1.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"# SciPy minimizer routine\n",
|
|
"def cost_func(\n",
|
|
" params: Sequence,\n",
|
|
" ansatz: QuantumCircuit,\n",
|
|
" hamiltonian: SparsePauliOp,\n",
|
|
" estimator: BaseEstimatorV2,\n",
|
|
") -> float:\n",
|
|
" \"\"\"Ground state energy evaluation.\"\"\"\n",
|
|
" return (\n",
|
|
" estimator.run([(ansatz, hamiltonian, [params])])\n",
|
|
" .result()[0]\n",
|
|
" .data.evs[0]\n",
|
|
" )\n",
|
|
"\n",
|
|
"\n",
|
|
"num_params = ansatz_ibm.num_parameters\n",
|
|
"params = 2 * np.pi * np.random.random(num_params)\n",
|
|
"\n",
|
|
"callback_dict = {\n",
|
|
" \"prev_vector\": None,\n",
|
|
" \"iters\": 0,\n",
|
|
" \"cost_history\": [],\n",
|
|
"}\n",
|
|
"\n",
|
|
"# Evaluate the problem using a QPU via Qiskit IBM Runtime\n",
|
|
"with Session(backend=backend) as session:\n",
|
|
" estimator = Estimator()\n",
|
|
" callback = build_callback(\n",
|
|
" ansatz_ibm, observable_ibm, estimator, callback_dict\n",
|
|
" )\n",
|
|
" res = minimize(\n",
|
|
" cost_func,\n",
|
|
" x0=params,\n",
|
|
" args=(ansatz_ibm, observable_ibm, estimator),\n",
|
|
" callback=callback,\n",
|
|
" method=\"cobyla\",\n",
|
|
" options={\"maxiter\": 100},\n",
|
|
" )\n",
|
|
"\n",
|
|
"visualize_results(callback_dict)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "33abbb3f-6245-4610-a05d-e2bc4cc551f0",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 4: Post-process and return result in desired classical format\n",
|
|
"\n",
|
|
"* Input: Ground state energy estimates during optimization\n",
|
|
"* Output: Estimated ground state energy"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"id": "e5b58771-d543-4e75-9746-fbc7b28e4360",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Estimated ground state energy: -2.594437119769288\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"print(f'Estimated ground state energy: {res[\"fun\"]}')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "4548f97e-352e-4a8e-b2c7-3c85f12099ab",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Deploy the Qiskit Pattern to the cloud\n",
|
|
"\n",
|
|
"To do this, move the source code above to a file, `./source/heisenberg.py`, wrap the code in a script which takes inputs and returns the final solution, and finally upload it to a remote cluster using the `QiskitFunction` class from `qiskit-ibm-catalog`. For guidance on specifying external dependencies, passing input arguments, and more, check out the [Qiskit Serverless guides](https://quantum.cloud.ibm.com/docs/en/guides/serverless).\n",
|
|
"\n",
|
|
"The input to the Pattern is the number of spins in the chain. The output is an estimation of the ground state energy of the system."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "970c51c8-dac5-4b64-9f20-4067666dfddc",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Authenticate to the remote cluster and submit the pattern for remote execution\n",
|
|
"serverless = QiskitServerless()\n",
|
|
"heisenberg_function = QiskitFunction(\n",
|
|
" title=\"ibm_heisenberg\",\n",
|
|
" entrypoint=\"heisenberg.py\",\n",
|
|
" working_dir=\"./source/\",\n",
|
|
")\n",
|
|
"serverless.upload(heisenberg_function)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e1b5c8d0-229a-4a39-8a8a-daf1762fca54",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Run the Qiskit Pattern as a managed service\n",
|
|
"\n",
|
|
"Once we have uploaded the pattern to the cloud, we can easily run it using the `QiskitServerless` client."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "9d9e5218-bdfe-4897-8920-7d0578a32c7f",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Run the pattern on the remote cluster\n",
|
|
"\n",
|
|
"ibm_heisenberg = serverless.load(\"ibm_heisenberg\")\n",
|
|
"job = serverless.run(ibm_heisenberg)\n",
|
|
"solution = job.result()\n",
|
|
"\n",
|
|
"print(solution)\n",
|
|
"print(job.logs())"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c14b0e0a",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Tutorial survey\n",
|
|
"\n",
|
|
"Please take one minute to provide feedback on this tutorial. Your insights will help us improve our content offerings and user experience.\n",
|
|
"\n",
|
|
"[Link to survey](https://your.feedback.ibm.com/jfe/form/SV_bfuBwfNeeFBxnim)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c1c57978",
|
|
"metadata": {},
|
|
"source": [
|
|
"© IBM Corp. 2023, 2024"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"description": "Build, deploy, and run a Qiskit Pattern for simulating a Heisenberg chain and estimating its ground state energy.",
|
|
"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"
|
|
},
|
|
"platform": "cloud",
|
|
"title": "Ground state energy estimation of the Heisenberg chain with VQE"
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|