qiskit-documentation/docs/tutorials/spin-chain-vqe.ipynb

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
}