Quantum Approximate Optimization Algorithm
Geschätzte Ausführungszeit: 22 Minuten auf einem Heron r3 Prozessor (HINWEIS: Das ist nur eine Schätzung. Bei dir kann's auch länger oder kürzer dauern.)
Hintergrund
In dem Tutorial zeigen wir dir, wie ma den Quantum Approximate Optimization Algorithm (QAOA) – a hybride (quanten-klassische) iterative Methode – im Rahmen von Qiskit-Patterns implementiert. Du löst zuerst das Maximum-Cut-Problem (kurz Max-Cut) für a kleines Graphen und lernst dann, wie ma's in einem größeren Maßstab ausführt. Alle Hardware-Ausführungen im Tutorial sollten im Zeitlimit vom frei zugänglichen Open Plan bleiben.
Das Max-Cut-Problem ist a Optimierungsproblem, das schwer zu lösen ist (genauer gesagt, es ist a NP-schweres Problem) und Anwendungen in der Clusteranalyse, Netzwerkwissenschaft und statistischen Physik hat. In dem Tutorial betrachten wir a Graphen aus Knoten, die durch Kanten verbunden san, und wollen die Knoten so in zwei Mengen aufteilen, dass die Anzahl der Kanten, die von diesem Schnitt überquert werden, maximiert wird.

Voraussetzungen
Bevor du mit dem Tutorial anfängst, stell sicher, dass folgendes installiert ist:
- Qiskit SDK v1.0 oder neuer, mit Visualisierungs-Unterstützung
- Qiskit Runtime v0.22 oder neuer (
pip install qiskit-ibm-runtime)
Außerdem brauchst du Zugang zu einer Instanz auf der IBM Quantum Platform. Beachte, dass dieses Tutorial nicht auf dem Open Plan ausgeführt werden kann, weil's Workloads über Sessions betreibt, die nur mit dem Premium Plan verfügbar san.
Setup
import matplotlib
import matplotlib.pyplot as plt
import rustworkx as rx
from rustworkx.visualization import mpl_draw as draw_graph
import numpy as np
from scipy.optimize import minimize
from collections import defaultdict
from typing import Sequence
from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit.library import QAOAAnsatz
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as Sampler
Teil I. Kleinskaliger QAOA
Im ersten Teil von dem Tutorial verwenden wir a kleinskaliges Max-Cut-Problem, um die Schritte zur Lösung eines Optimierungsproblems auf'm Quantencomputer zu veranschaulichen.
Um a bisschen Kontext zu geben, bevor wir das Problem auf einen Quantenalgorithmus abbilden: Du kannst besser verstehen, wie das Max-Cut-Problem zu einem klassischen kombinatorischen Optimierungsproblem wird, wenn du zuerst die Minimierung einer Funktion betrachtest:
wobei die Eingabe a Vektor ist, dessen Komponenten den einzelnen Knoten eines Graphen entsprechen. Dann schränken wir jede dieser Komponenten auf oder ein (die bedeuten, ob der Knoten im Schnitt enthalten ist oder nicht). Dieses kleinskalige Beispiel verwendet a Graphen mit Knoten.
Du könntest a Funktion für ein Knotenpaar schreiben, die angibt, ob die entsprechende Kante im Schnitt liegt. Zum Beispiel ist die Funktion genau dann 1, wenn entweder oder gleich 1 ist (was bedeutet, dass die Kante im Schnitt liegt), sonst null. Das Problem der Maximierung der Kanten im Schnitt lässt sich formulieren als
was man als Minimierung umschreiben kann:
Das Minimum von ist in dem Fall dann erreicht, wenn die Anzahl der vom Schnitt überquerten Kanten maximal ist. Wie du siehst, hat das noch nix mit Quantencomputing zu tun. Du musst das Problem in etwas umformulieren, das a Quantencomputer verstehen kann. Erstell dein Problem, indem du a Graphen mit Knoten anlegst.
n = 5
graph = rx.PyGraph()
graph.add_nodes_from(np.arange(0, n, 1))
edge_list = [
(0, 1, 1.0),
(0, 2, 1.0),
(0, 4, 1.0),
(1, 2, 1.0),
(2, 3, 1.0),
(3, 4, 1.0),
]
graph.add_edges_from(edge_list)
draw_graph(graph, node_size=600, with_labels=True)

Schritt 1: Klassische Eingaben auf's Quantenproblem abbilden
Der erste Schritt von dem Pattern ist es, das klassische Problem (den Graphen) auf Quanten-Circuits und Operatoren abzubilden. Dafür gibt's drei Hauptschritte:
- Eine Reihe von mathematischen Umformulierungen verwenden, um das Problem in der Notation der Quadratic Unconstrained Binary Optimization (QUBO) darzustellen.
- Das Optimierungsproblem als Hamiltonian umschreiben, dessen Grundzustand der Lösung entspricht, die die Kostenfunktion minimiert.
- Einen Quanten-Circuit erstellen, der den Grundzustand dieses Hamiltonians ähnlich wie beim Quanten-Annealing vorbereitet.
Hinweis: In der QAOA-Methodik willst du letztendlich a Operator (Hamiltonian) haben, der die Kostenfunktion unseres hybriden Algorithmus darstellt, sowie a parametrisierten Circuit (Ansatz), der Quantenzustände mit Kandidatenlösungen für das Problem repräsentiert. Du kannst aus diesen Kandidatenzuständen samplen und sie dann mit der Kostenfunktion bewerten.
Graph → Optimierungsproblem
Der erste Schritt der Abbildung ist a Notationsänderung. Das Folgende drückt das Problem in QUBO-Notation aus:
wobei a -Matrix aus reellen Zahlen ist, der Anzahl der Knoten in deinem Graphen entspricht, der Vektor der oben eingeführten binären Variablen ist und die Transponierte des Vektors bezeichnet.
Maximize
-2*x_0*x_1 - 2*x_0*x_2 - 2*x_0*x_4 - 2*x_1*x_2 - 2*x_2*x_3 - 2*x_3*x_4 + 3*x_0
+ 2*x_1 + 3*x_2 + 2*x_3 + 2*x_4
Subject to
No constraints
Binary variables (5)
x_0 x_1 x_2 x_3 x_4
Optimierungsproblem → Hamiltonian
Dann kannst du das QUBO-Problem als Hamiltonian umformulieren (hier a Matrix, die die Energie eines Systems darstellt):
Umformulierungsschritte vom QAOA-Problem zum Hamiltonian
Um zu zeigen, wie das QAOA-Problem so umgeschrieben werden kann, ersetzen wir zuerst die binären Variablen durch a neue Menge von Variablen via
Hier siehst du: wenn gleich ist, dann muss gleich sein. Wenn die 's durch die 's im Optimierungsproblem () ersetzt werden, erhält man a äquivalente Formulierung.
Wenn wir jetzt definieren, den Vorfaktor weglassen und den konstanten -Term entfernen, kommen wir zu den zwei äquivalenten Formulierungen desselben Optimierungsproblems:
Dabei hängt von ab. Beachte, dass wir zum Erhalt von den Faktor 1/4 und einen konstanten Offset von weggelassen haben, die für die Optimierung keine Rolle spielen.
Um jetzt a Quantenformulierung des Problems zu erhalten, promoten wir die -Variablen zu einer Pauli--Matrix, also a -Matrix der Form
Wenn du diese Matrizen in das obige Optimierungsproblem einsetzt, erhältst du den folgenden Hamiltonian:
Beachte auch, dass die -Matrizen im Rechenraum des Quantencomputers eingebettet san, also in einem Hilbert-Raum der Größe . Daher solltest du Terme wie als Tensorprodukt im -Hilbert-Raum verstehen. Zum Beispiel bedeutet der Term in a Problem mit fünf Entscheidungsvariablen , wobei die -Einheitsmatrix ist.
Dieser Hamiltonian heißt Kostenfunktions-Hamiltonian. Er hat die Eigenschaft, dass sein Grundzustand der Lösung entspricht, die die Kostenfunktion minimiert. Um dein Optimierungsproblem zu lösen, musst du jetzt den Grundzustand von (oder a Zustand mit hoher Überlappung damit) auf dem Quantencomputer vorbereiten. Dann liefert das Sampling aus diesem Zustand mit hoher Wahrscheinlichkeit die Lösung für . Schauen wir uns jetzt den Hamiltonian für das Max-Cut-Problem an. Jedem Knoten des Graphen ordnen wir a Qubit im Zustand oder zu, wobei der Wert angibt, in welcher Menge der Knoten ist. Das Ziel des Problems ist es, die Anzahl der Kanten zu maximieren, für die und gilt, oder umgekehrt. Wenn wir den -Operator jedem Qubit zuordnen, wobei
dann gehört a Kante zum Schnitt, wenn der Eigenwert von ist; mit anderen Worten: die Qubits von und san verschieden. Entsprechend gehört nicht zum Schnitt, wenn der Eigenwert von ist. Es kommt uns nicht auf den genauen Qubit-Zustand jedes Knotens an, sondern nur darauf, ob sie über a Kante gleich oder verschieden san. Das Max-Cut-Problem verlangt, a Zuweisung der Qubits auf die Knoten zu finden, sodass der Eigenwert des folgenden Hamiltonians minimiert wird:
Mit anderen Worten: für alle im Max-Cut-Problem. Der Wert von gibt das Gewicht der Kante an. In dem Tutorial betrachten wir a ungewichteten Graphen, also für alle .
def build_max_cut_paulis(
graph: rx.PyGraph,
) -> list[tuple[str, list[int], float]]:
"""Convert the graph to Pauli list.
This function does the inverse of `build_max_cut_graph`
"""
pauli_list = []
for edge in list(graph.edge_list()):
weight = graph.get_edge_data(edge[0], edge[1])
pauli_list.append(("ZZ", [edge[0], edge[1]], weight))
return pauli_list
max_cut_paulis = build_max_cut_paulis(graph)
cost_hamiltonian = SparsePauliOp.from_sparse_list(max_cut_paulis, n)
print("Cost Function Hamiltonian:", cost_hamiltonian)
Cost Function Hamiltonian: SparsePauliOp(['IIIZZ', 'IIZIZ', 'ZIIIZ', 'IIZZI', 'IZZII', 'ZZIII'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
Hamiltonian → Quanten-Circuit
Der Hamiltonian enthält die Quantendefinition deines Problems. Jetzt kannst du a Quanten-Circuit erstellen, der dabei hilft, gute Lösungen vom Quantencomputer zu samplen. Der QAOA ist vom Quanten-Annealing inspiriert und wendet abwechselnde Schichten von Operatoren im Quanten-Circuit an.
Die allgemeine Idee ist es, im Grundzustand eines bekannten Systems zu starten, (wie oben), und das System dann in den Grundzustand des Kostenoperators zu steuern, der uns interessiert. Das wird durch Anwenden der Operatoren und mit Winkeln und erreicht.
Der Circuit, den du generierst, ist durch und parametrisiert, sodass du verschiedene Werte für