Zum Hauptinhalt springen

Grundzustandsenergieschätzung der Heisenberg-Kette mit VQE

Geschätzter Ressourcenverbrauch: Zwei Minuten auf einem Eagle-r3-Prozessor (HINWEIS: Dies ist nur eine Schätzung. Deine Laufzeit kann abweichen.)

Hintergrund

Dieses Tutorial zeigt, wie du ein Qiskit pattern zur Simulation einer Heisenberg-Kette und zur Schätzung ihrer Grundzustandsenergie erstellst, deployest und ausführst. Weitere Informationen zu Qiskit patterns und dazu, wie Qiskit Serverless genutzt werden kann, um sie für die verwaltete Ausführung in die Cloud zu deployen, findest du auf unserer Dokumentationsseite zur IBM Quantum® Platform.

Voraussetzungen

Bevor du mit diesem Tutorial beginnst, stelle sicher, dass du Folgendes installiert hast:

  • Qiskit SDK v1.2 oder höher, mit Unterstützung für Visualisierung
  • Qiskit Runtime v0.28 oder höher (pip install qiskit-ibm-runtime)
  • Qiskit Serverless (pip install qiskit_serverless)
  • IBM Catalog (pip install qiskit-ibm-catalog)

Setup

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-catalog qiskit-ibm-runtime scipy
import numpy as np
import matplotlib.pyplot as plt

from scipy.optimize import minimize
from typing import Sequence

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives.base import BaseEstimatorV2
from qiskit.circuit.library import XGate
from qiskit.circuit.library import efficient_su2
from qiskit.transpiler import PassManager
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.passes.scheduling import (
ALAPScheduleAnalysis,
PadDynamicalDecoupling,
)

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Session, Estimator

from qiskit_ibm_catalog import QiskitServerless, QiskitFunction
def visualize_results(results):
plt.plot(results["cost_history"], lw=2)
plt.xlabel("Iteration")
plt.ylabel("Energy")
plt.show()

def build_callback(
ansatz: QuantumCircuit,
hamiltonian: SparsePauliOp,
estimator: BaseEstimatorV2,
callback_dict: dict,
):
def callback(current_vector):
# Keep track of the number of iterations
callback_dict["iters"] += 1
# Set the prev_vector to the latest one
callback_dict["prev_vector"] = current_vector
# Compute the value of the cost function at the current vector
current_cost = (
estimator.run([(ansatz, hamiltonian, [current_vector])])
.result()[0]
.data.evs[0]
)
callback_dict["cost_history"].append(current_cost)
# Print to screen on single line
print(
"Iters. done: {} [Current cost: {}]".format(
callback_dict["iters"], current_cost
),
end="\r",
flush=True,
)

return callback

Schritt 1: Klassische Eingaben auf ein Quantenproblem abbilden

  • Eingabe: Anzahl der Spins
  • Ausgabe: Ansatz und Hamiltonian zur Modellierung der Heisenberg-Kette

Konstruiere einen Ansatz und einen Hamiltonian, die eine 10-Spin-Heisenberg-Kette modellieren. Zunächst importieren wir einige allgemeine Pakete und erstellen ein paar Hilfsfunktionen.

num_spins = 10
ansatz = efficient_su2(num_qubits=num_spins, reps=3)

# Remember to insert your token in the QiskitRuntimeService constructor
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, min_num_qubits=num_spins, simulator=False
)

coupling = backend.target.build_coupling_map()
reduced_coupling = coupling.reduce(list(range(num_spins)))

edge_list = reduced_coupling.graph.edge_list()
ham_list = []

for edge in edge_list:
ham_list.append(("ZZ", edge, 0.5))
ham_list.append(("YY", edge, 0.5))
ham_list.append(("XX", edge, 0.5))

for qubit in reduced_coupling.physical_qubits:
ham_list.append(("Z", [qubit], np.random.random() * 2 - 1))

hamiltonian = SparsePauliOp.from_sparse_list(ham_list, num_qubits=num_spins)

ansatz.draw("mpl", style="iqp")

Ausgabe der vorherigen Code-Zelle

Schritt 2: Problem für die Ausführung auf Quantenhardware optimieren

  • Eingabe: Abstrakter Circuit, Observable
  • Ausgabe: Ziel-Circuit und Observable, optimiert für den ausgewählten QPU

Verwende die Funktion generate_preset_pass_manager aus Qiskit, um automatisch eine Optimierungsroutine für unseren Circuit bezüglich des ausgewählten QPU zu erzeugen. Wir wählen optimization_level=3, das die höchste Optimierungsstufe der vordefinierten Pass-Manager bietet. Außerdem fügen wir die Scheduling-Passes ALAPScheduleAnalysis und PadDynamicalDecoupling ein, um Dekohärenzfehler zu unterdrücken.

target = backend.target
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
pm.scheduling = PassManager(
[
ALAPScheduleAnalysis(durations=target.durations()),
PadDynamicalDecoupling(
durations=target.durations(),
dd_sequence=[XGate(), XGate()],
pulse_alignment=target.pulse_alignment,
),
]
)
ansatz_ibm = pm.run(ansatz)
observable_ibm = hamiltonian.apply_layout(ansatz_ibm.layout)
ansatz_ibm.draw("mpl", scale=0.6, style="iqp", fold=-1, idle_wires=False)

Ausgabe der vorherigen Code-Zelle

Schritt 3: Ausführung mit Qiskit-Primitiven

  • Eingabe: Ziel-Circuit und Observable
  • Ausgabe: Ergebnisse der Optimierung

Minimiere die geschätzte Grundzustandsenergie des Systems, indem du die Circuit-Parameter optimierst. Verwende das Estimator-Primitiv aus Qiskit Runtime, um die Kostenfunktion während der Optimierung auszuwerten.

Für diese Demo führen wir die Berechnungen auf einem QPU mit qiskit-ibm-runtime-Primitiven aus. Um stattdessen qiskit-Zustandsvektor-basierte Primitive zu verwenden, ersetze den Codeblock mit den Qiskit-IBM-Runtime-Primitiven durch den auskommentierten Block.

# SciPy minimizer routine
def cost_func(
params: Sequence,
ansatz: QuantumCircuit,
hamiltonian: SparsePauliOp,
estimator: BaseEstimatorV2,
) -> float:
"""Ground state energy evaluation."""
return (
estimator.run([(ansatz, hamiltonian, [params])])
.result()[0]
.data.evs[0]
)

num_params = ansatz_ibm.num_parameters
params = 2 * np.pi * np.random.random(num_params)

callback_dict = {
"prev_vector": None,
"iters": 0,
"cost_history": [],
}

# Evaluate the problem on a QPU by using Qiskit IBM Runtime
with Session(backend=backend) as session:
estimator = Estimator()
callback = build_callback(
ansatz_ibm, observable_ibm, estimator, callback_dict
)
res = minimize(
cost_func,
x0=params,
args=(ansatz_ibm, observable_ibm, estimator),
callback=callback,
method="cobyla",
options={"maxiter": 100},
)

visualize_results(callback_dict)

Schritt 4: Nachverarbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format

  • Eingabe: Grundzustandsenergieschätzungen während der Optimierung
  • Ausgabe: Geschätzte Grundzustandsenergie
print(f'Estimated ground state energy: {res["fun"]}')

Das Qiskit-Pattern in die Cloud deployen

Verschiebe dazu den obigen Quellcode in eine Datei, ./source/heisenberg.py, verpacke den Code in ein Skript, das Eingaben entgegennimmt und die finale Lösung zurückgibt, und lade es abschließend mit der Klasse QiskitFunction aus qiskit-ibm-catalog auf einen Remote-Cluster hoch. Hinweise zur Angabe externer Abhängigkeiten, zur Übergabe von Eingabeargumenten und mehr findest du in den Qiskit-Serverless-Anleitungen.

Die Eingabe des Patterns ist die Anzahl der Spins in der Kette. Die Ausgabe ist eine Schätzung der Grundzustandsenergie des Systems.

# Authenticate to the remote cluster and submit the pattern for remote execution
serverless = QiskitServerless()
heisenberg_function = QiskitFunction(
title="ibm_heisenberg",
entrypoint="heisenberg.py",
working_dir="./source/",
)
serverless.upload(heisenberg_function)

Das Qiskit-Pattern als verwalteten Dienst ausführen

Sobald wir das Pattern in die Cloud hochgeladen haben, können wir es ganz einfach mit dem QiskitServerless-Client ausführen.

# Run the pattern on the remote cluster

ibm_heisenberg = serverless.load("ibm_heisenberg")
job = serverless.run(ibm_heisenberg)
solution = job.result()

print(solution)
print(job.logs())

Tutorial-Umfrage

Bitte nimm an dieser kurzen Umfrage teil, um Feedback zu diesem Tutorial zu geben. Deine Einschätzungen helfen uns, unsere Inhalte und die Benutzererfahrung zu verbessern.

Link zur Umfrage