Quantum Approximate Optimization Algorithm
Bruukstied-Schatting: 22 Minuten op'n Heron r3 Prozessor (HENWIES: Dat is blots 'ne Schatting. Dien Looptied kann variern.)
Achtergrund
Dit Tutorial wiest, wo wi den Quantum Approximate Optimization Algorithm (QAOA) – 'ne hybride (quanten-klassische) iterative Methode – binnen den Kontext vun Qiskit-Patterns implementeern. Wi lööst eerst dat Maximum-Cut (oder Max-Cut) Problem för 'n lütten Graphen un leert denn, wo wi dat op Utility-Skala utföhren. All Hardware-Utföhrungen in dit Tutorial schöölt binnen de Tiedgrenz för den free tögänglichen Open Plan funkeern.
Dat Max-Cut-Problem is 'n Optimierungsproblem, wat swoor to lösen is (mehr seggt, dat is 'n NP-hard Problem) mit 'ne Reeg vun verscheden Anwennungen in Clustering, Netzwerkwetenschap un statistischer Physik. Dit Tutorial betrackt 'n Graphen vun Knoten, de dör Kanten verbunnen sünd, un will de Knoten in twee Mengen optelen, sodass de Tall vun de dör dissen Schnitt dörchreisd Kanten maximiert warrt.

Vörutsettungen
Bevör wi mit dit Tutorial anfangen, seht to, dat ji dat Folgende installeert hebbt:
- Qiskit SDK v1.0 oder later, mit Visualisierungs-Ünnerstütten
- Qiskit Runtime v0.22 oder later (
pip install qiskit-ibm-runtime)
Daarto bruukt ji Togäng to 'ne Instanz op de IBM Quantum Platform. Acht darop, dat dit Tutorial nich in'n Open Plan utföhrt warrn kann, wieldat dat Workloads mit Sessions utföhrt, de blots mit Premium Plan-Togäng verfögbor sünd.
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
Deel I. QAOA in'n lütten Mootstav
De eerste Deel vun dit Tutorial bruukt 'n lütt Max-Cut-Problem, üm de Schritten för de Lösung vun 'n Optimierungsproblem mit 'n Quantencomputer to verdüütlichen.
Üm 'n beter Kontext to geven, bevör wi dit Problem op 'n Quantenalgorithmus afbilden, köönt ji beter verstahn, wo dat Max-Cut-Problem to 'n klassischen kombinatorischen Optimierungsproblem warrt, wenn wi eerst de Minimierung vun 'ne Funkschoon betrachten
wobei de Ingav 'n Vektor is, wo sien Komponenten elkeen Knoten vun 'n Graphen entsprecht. Denn warrt jede vun disse Komponenten op entweder oder beschränkt (wat representeert, of se in'n Schnitt entholln sünd oder nich). Dissen lüttskalige Biespielfall bruukt 'n Graphen mit Knoten.
Wi köönt 'ne Funkschoon vun 'n Knotenpaar schrieven, wat anwiest, of de entsprechend Kant in'n Schnitt liggt. To'n Biespeel is de Funkschoon nau denn 1, wenn entweder oder gliek 1 is (wat bedü üdt, dat de Kant in'n Schnitt liggt) un süss null. Dat Problem, de Kanten in'n Schnitt to maximeren, kann formuleert warrn as
wat as Minimierung ümschreven warrn kann in de Form
Dat Minimum vun in dissen Fall liggt vör, wenn de Tall vun de dör den Schnitt dörchreisd Kanten maximal is. As ji seht, hett dat noch nix mit Quantencomputing to doon. Wi mööt dit Problem in wat ümformuleren, wat 'n Quantencomputer verstahn kann. Initiali seert juu Problem, indem wi 'n Graphen mit Knoten maakt.
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 Ingav op 'n Quantenproblem afbilden
De eerste Schritt vun't Pattern besteiht darin, dat klassische Problem (Graph) op quantenmechanische Schaltkreise un Operatoren aftobilden. Daarto sünd dree Hövdschritten to ünnernahmen:
- Bruuk vun 'ne Reeg vun mathematische Ümformulierungen, üm dit Problem mit de Notation vun Quadratic Unconstrained Binary Optimization (QUBO) Problemen dartostellen.
- Ümformulierung vun't Optimierungsproblem as Hamilton-Operator, för den de Grundtostänn de Lösung entspricht, wo de Kostenfunkschoon minimieeert.
- Maaken vun 'n Quantenschaltkreis, de den Grundtostänn vun dissen Hamilton-Operator över 'n Prozess ähnlich dat Quantum Annealing vörbereit.
Acht darop: In de QAOA-Methodik wüllt wi letztlich 'n Operator (Hamilton-Operator) hemm, de de Kostenfunkschoon vun uns hybride Algorithmus darstellt, sowiet as 'n parametriseerten Schaltkreis (Ansatz), de Quantentostänne mit Kandidatenlösungen för dit Problem darstellt. Wi köönt ut disse Kandidatentostänne sampeln un se denn mit de Kostenfunkschoon bewerten.
Graph → Optimierungsproblem
De eerste Schritt vun de Afbildung is 'ne Notationsännerung. Dat Folgende drückt dit Problem in QUBO-Notation ut:
wobei 'ne Matrix vun reelle Tallen is, de Tall vun de Knoten in juu Graphen entspricht, de boven inföhrde Vektor vun binäre Variable is un de Transponeerde vun den Vektor bedüüdt.
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 → Hamilton-Operator
Wi köönt denn dat QUBO-Problem as Hamilton-Operator ümformuleren (hier 'ne Matrix, wo de Energie vun 'n System darstellt):
Ümformulierungsschritten vun't QAOA-Problem to'n Hamilton-Operator
Üm to wiesen, wo dat QAOA-Problem op disse Wies ümschreven warrn kann, ersettt wi eerst de binären Variable dör 'n nijen Sett vun Variable över
Hier köönt ji sehn, dat wenn gliek is, denn gliek sien mutt. Wenn de dör de in't Optimierungsproblem () ersett warrt, kann 'ne äquivalente Formulierung kregen warrn.
Wenn wi nu definiern, den Vörfaktor wegnehmen un den konstanten Term weglaten, kriegen wi de beiden äquivalenten Formulierungen vun't sülve Optimierungsproblem.
Hier hängt vun af. Acht darop, dat wi för de Erlangung vun den Faktor 1/4 un 'n konstanten Offset vun weggelaten hebbt, de bi de Optimierung keen Roll speelt.
Üm nu 'ne Quantenformulierung vun't Problem to kriegen, erheven wi de Variable to 'ne Pauli Matrix, liek as 'ne Matrix vun de Form
Wenn wi disse Matrizen in't boven Optimierungsproblem insetten, kriegen wi den folgenden Hamilton-Operator
Acht ok darop, dat de Matrizen in den Rekenruum vun den Quantencomputer inbedd sünd, dat heet in 'n Hilbert-Ruum vun de Gröte . Dorüm schöölt wi Termen as as dat Tensorprodukt verstahn, wat in den Hilbert-Ruum inbedd is. To'n Biespeel warrt in 'n Problem mit fief Entscheidungsvariabeln de Term as verstahn, wobei de Eenheitsmatrix is.
Dissen Hamilton-Operator warrt as Kostenfunkschoons-Hamilton-Operator beseekent. He hett de Egenschop, dat sien Grundtostänn de Lösung entspricht, wo de Kostenfunkschoon minimieeert. Üm juu Optimierungsproblem to lösen, mööt wi nu den Grundtostänn vun (oder 'n Tostänn mit hoge Överlappung dormit) op'n Quantencomputer präpareren. Dat Sampeln ut dissen Tostänn warrt denn mit hoge Wahrschienlichkeit de Lösung to levern. Betrach wi nu den Hamilton-Operator för dat Max-Cut Problem. Elkeen Knoten vun den Graphen warrt 'n Qubit in'n Tostänn oder toordnet, wobei de Wert de Meng anwiest, to de de Knoten hört. Dat Ziel vun't Problem is dat, de Tall vun de Kanten to maximeren, för wo un gellt, oder ümkehrt. Wenn wi den Operator elkeen Qubit toordnen, wobei
denn hört 'ne Kant to'n Schnitt, wenn de Eigenwert vun is; mit anner Wöör, de mit un assozieerten Qubits sünd verscheden. Ebenso hört nich to'n Schnitt, wenn de Eigenwert vun is. Acht darop, dat uns de genau Qubit-Tostänn, de elkeen Knoten toordnet is, nich interesseert, süss blots, of se över 'ne Kant henweg gliek sünd oder nich. Dat Max-Cut-Problem verlang vun uns, 'ne Toordnung vun de Qubits op de Knoten to finnen, sodass de Eigenwert vun den folgenden Hamilton-Operator minimiiert warrt
Mit anner Wöör, för all in't Max-Cut-Problem. De Wert vun bedüüdt dat Gewicht vun de Kant. In dit Tutorial betrachten wi 'n ungewichteten Graphen, dat heet för all .
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])
Hamilton-Operator → Quantenschaltkreis
De Hamilton-Operator enthält de Quantendefinischoon vun juu Problem. Nu köönt wi 'n Quantenschaltkreis maken, de dorbi hülpt, gode Lösungen vun den Quantencomputer to sampeln. De QAOA is inspireert vun't Quantum Annealing un wendt afwesselnde Schichten vun Operatoren in'n Quantenschaltkreis an.
De allgemene Idee besteiht darin, in'n Grundtostänn vun 'n bekannten System to beginnen, boven, un denn dat System in den Grundtostänn vun den Kostenoperator to stüern, an den wi interessiert sünd. Dat schütt dör Anwendung vun de Operatoren un mit Winkeln un .
De vun uns genereert Quantenschaltkreis is parametriseert dör un , sodass wi verscheden Werte vun un utproberen un ut den resultierenden Tostänn sampeln köönt.
In dissen Fall warrt wi 'n Biespeel mit 'ne QAOA-Schicht proberen, wo twee Parameter enthölt: un .
circuit = QAOAAnsatz(cost_operator=cost_hamiltonian, reps=2)
circuit.measure_all()
circuit.draw("mpl")

circuit.parameters
ParameterView([ParameterVectorElement(β[0]), ParameterVectorElement(β[1]), ParameterVectorElement(γ[0]), ParameterVectorElement(γ[1])])
Schritt 2: Problem för Quanten-Hardware-Utföhrung optimeren
De boven Schaltkreis enthölt 'ne Reeg vun Abstraktionen, de nützlich sünd, üm över Quantenalgorithmen notodenken, aver op de Hardware nich utföhrbar sünd. Üm op 'ne QPU utföhrt warrn to könen, mutt de Schaltkreis 'ne Reeg vun Operationen dörlopen, wo den Transpilations- oder Schaltkreis-Optimierungs-Schritt vun't Pattern utmaakt.
De Qiskit-Bibliothek beed 'ne Reeg vun Transpilations-Pässen, wo 'ne breed Palette vun Schaltkreistransformationen afdeckt. Wi mööt sicherstellen, dat juu Schaltkreis för juu Tweck optimiiert is.
De Transpilation kann mehrere Schritten ümfaten, as to'n Biespeel:
- Initialet Mapping vun de Qubits in'n Schaltkreis (as Entscheidungsvariabeln) op physische Qubits op't Gerät.
- Unrolling vun de Anwiesungen in'n Quantenschaltkreis to de hardware-native Anwiesungen, wo dat Backend versteih.
- Routing vun beliebige Qubits in'n Schaltkreis, de interageern, to physische Qubits, de benachbart toenanner sünd.
- Fehlerünnerdrückung dör Hintofögen vun Enkelqubit-Gates för Ruschünnerdrückung mit dynamische Entkopplung.
Mehr Informatschonen to de Transpilation finnt ji in uns Dokumentation.
De folgende Code transformiert un optimiiert den abstrakten Schaltkreis in 'n Format, wat för de Utföhrung op een vun de över de Cloud tögänglichen Geräten mit den Qiskit IBM Runtime Service klaar is.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
print(backend)
# Create pass manager for transpilation
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
candidate_circuit = pm.run(circuit)
candidate_circuit.draw("mpl", fold=False, idle_wires=False)
<IBMBackend('test_heron_pok_1')>

Schritt 3: Utföhrung mit Qiskit Primitives
In'n QAOA-Workflow warrt de optimalen QAOA-Parameter in 'ne iterative Optimierungsslöp funnen, wo 'ne Reeg vun Schaltkreisbewertungen utföhrt un 'n klassischen Optimierer bruukt, üm de optimalen un Parameter to finnen. Disse Utföhrungsslöp warrt över de folgenden Schritten utföhrt:
- Defineren vun de initialen Parameter
- Instanziierung vun 'ne nije
Session, wo de Optimierungsslöp un dat Primitive enthölt, wat to'n Sampeln vun den Schaltkreis bruukt warrt - Sobald 'n optimalen Parametersett funnen is, föhrt wi den Schaltkreis 'n letzt Mal ut, üm 'ne finale Verdelung to kriegen, wo in'n Post-Processing-Schritt bruukt warrt.
Schaltkreis mit initialen Parametern defineren
Wi beginnt mit willkürlich wählten Parametern.
initial_gamma = np.pi
initial_beta = np.pi / 2
init_params = [initial_beta, initial_beta, initial_gamma, initial_gamma]
Backend un Utföhrungs-Primitive defineren
Bruuk de Qiskit Runtime Primitives, üm mit IBM® Backends to interageren. De beiden Primitives sünd Sampler un Estimator, un de Wahl vun't Primitive hängt dorvan af, welk Oort vun Meeten wi op'n Quantencomputer utföhren wüllt. För de Minimierung vun bruukt wi den Estimator, dat de Meeten vun de Kostenfunkschoon eenfach de Verwachtungswert vun is.
Utföhren
De Primitives beedt 'ne Vielheit vun Utföhrungsmodi to de Planung vun Workloads op Quantengeräten, un 'n QAOA-Workflow löppt iterativ in 'ne Session.

Wi köönt de sampler-baseerte Kostenfunkschoon in de SciPy-Minimierungsroutine insteken, üm de optimalen Parameter to finnen.
def cost_func_estimator(params, ansatz, hamiltonian, estimator):
# transform the observable defined on virtual qubits to
# an observable defined on all physical qubits
isa_hamiltonian = hamiltonian.apply_layout(ansatz.layout)
pub = (ansatz, isa_hamiltonian, params)
job = estimator.run([pub])
results = job.result()[0]
cost = results.data.evs
objective_func_vals.append(cost)
return cost
objective_func_vals = [] # Global variable
with Session(backend=backend) as session:
# If using qiskit-ibm-runtime<0.24.0, change `mode=` to `session=`
estimator = Estimator(mode=session)
estimator.options.default_shots = 1000
# Set simple error suppression/mitigation options
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XY4"
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
result = minimize(
cost_func_estimator,
init_params,
args=(candidate_circuit, cost_hamiltonian, estimator),
method="COBYLA",
tol=1e-2,
)
print(result)
message: Return from COBYLA because the trust region radius reaches its lower bound.
success: True
status: 0
fun: -1.6295230263157894
x: [ 1.530e+00 1.439e+00 4.071e+00 4.434e+00]
nfev: 26
maxcv: 0.0
De Optimierer kunn de Kosten reduzeern un beter Parameter för den Schaltkreis finnen.
plt.figure(figsize=(12, 6))
plt.plot(objective_func_vals)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.show()

Sobald wi de optimalen Parameter för den Schaltkreis funnen hebbt, köönt wi disse Parameter towies un de mit de optimierten Parametern erhollen finale Verdelung sampeln. Hier schull dat Sampler Primitive bruukt warrn, dat dat de Wahrschienlichkeitsverdelung vun Bitstring-Metungen is, wo den optimalen Schnitt vun den Graphen entspricht.
Acht darop: Dat bedüüdt, 'n Quantentostänn in'n Computer to präpareren un em denn to meten. 'ne Meeten warrt den Tostänn in 'n enkel ten Berekenungsbasistostänn kollab eren laten - to'n Biespeel 010101110000... - de 'ne Kandidatenlösung för uns urs prüngliche Optimierungsproblem ( oder je na Opgav) entspricht.
optimized_circuit = candidate_circuit.assign_parameters(result.x)
optimized_circuit.draw("mpl", fold=False, idle_wires=False)

# If using qiskit-ibm-runtime<0.24.0, change `mode=` to `backend=`
sampler = Sampler(mode=backend)
sampler.options.default_shots = 10000
# Set simple error suppression/mitigation options
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XY4"
sampler.options.twirling.enable_gates = True
sampler.options.twirling.num_randomizations = "auto"
pub = (optimized_circuit,)
job = sampler.run([pub], shots=int(1e4))
counts_int = job.result()[0].data.meas.get_int_counts()
counts_bin = job.result()[0].data.meas.get_counts()
shots = sum(counts_int.values())
final_distribution_int = {key: val / shots for key, val in counts_int.items()}
final_distribution_bin = {key: val / shots for key, val in counts_bin.items()}
print(final_distribution_int)
{28: 0.0328, 11: 0.0343, 2: 0.0296, 25: 0.0308, 16: 0.0303, 27: 0.0302, 13: 0.0323, 7: 0.0312, 4: 0.0296, 9: 0.0295, 26: 0.0321, 30: 0.031, 23: 0.0324, 31: 0.0303, 21: 0.0335, 15: 0.0317, 12: 0.0309, 29: 0.0297, 3: 0.0313, 5: 0.0312, 6: 0.0274, 10: 0.0329, 22: 0.0353, 0: 0.0315, 20: 0.0326, 8: 0.0322, 14: 0.0306, 17: 0.0295, 18: 0.0279, 1: 0.0325, 24: 0.0334, 19: 0.0295}
Schritt 4: Nabearbeiten un Törüchgeven vun't Resultat in't wünschte klassische Format
De Nabearbetensch ritt interpre teert de Sampling-Utgav, üm 'ne Lösung för juu ursprüngliche Problem törüchtogeven. In dissen Fall sünd wi an den Bitstring mit de högste Wahrschienlichkeit interessiert, dat de den optimalen Schnitt bestimmt. De Symmetrien in't Problem erlöövt veer mögliche Lösungen, un de Sampling-Prozess warrt een dorvun mit 'ne etwat högere Wahrschienlichkeit törüchgeven, aver ji köönt in de ünner darstellte Verdelung sehn, dat veer vun de Bitstrings düütlich wahrschienlicher sünd as de Rest.
# auxiliary functions to sample most likely bitstring
def to_bitstring(integer, num_bits):
result = np.binary_repr(integer, width=num_bits)
return [int(digit) for digit in result]
keys = list(final_distribution_int.keys())
values = list(final_distribution_int.values())
most_likely = keys[np.argmax(np.abs(values))]
most_likely_bitstring = to_bitstring(most_likely, len(graph))
most_likely_bitstring.reverse()
print("Result bitstring:", most_likely_bitstring)
Result bitstring: [0, 1, 1, 0, 1]
matplotlib.rcParams.update({"font.size": 10})
final_bits = final_distribution_bin
values = np.abs(list(final_bits.values()))
top_4_values = sorted(values, reverse=True)[:4]
positions = []
for value in top_4_values:
positions.append(np.where(values == value)[0])
fig = plt.figure(figsize=(11, 6))
ax = fig.add_subplot(1, 1, 1)
plt.xticks(rotation=45)
plt.title("Result Distribution")
plt.xlabel("Bitstrings (reversed)")
plt.ylabel("Probability")
ax.bar(list(final_bits.keys()), list(final_bits.values()), color="tab:grey")
for p in positions:
ax.get_children()[int(p[0])].set_color("tab:purple")
plt.show()

Besten Schnitt visualiseren
Ut den optimalen Bit-String köönt wi denn dissen Schnitt op'n originalen Graphen visualiseren.
# auxiliary function to plot graphs
def plot_result(G, x):
colors = ["tab:grey" if i == 0 else "tab:purple" for i in x]
pos, _default_axes = rx.spring_layout(G), plt.axes(frameon=True)
rx.visualization.mpl_draw(
G, node_color=colors, node_size=100, alpha=0.8, pos=pos
)
plot_result(graph, most_likely_bitstring)

Un bereken den Wert vun den Schnitt:
def evaluate_sample(x: Sequence[int], graph: rx.PyGraph) -> float:
assert len(x) == len(
list(graph.nodes())
), "The length of x must coincide with the number of nodes in the graph."
return sum(
x[u] * (1 - x[v]) + x[v] * (1 - x[u])
for u, v in list(graph.edge_list())
)
cut_value = evaluate_sample(most_likely_bitstring, graph)
print("The value of the cut is:", cut_value)
The value of the cut is: 5
Deel II. Grötter maken!
Wi hebbt Togäng to veel Geräden mit över 100 Qubits op de IBM Quantum® Platform. Wähl een ut, op den wi Max-Cut op 'n 100-Knoten gewichteten Graphen löst. Dat is 'n "utility-scale" Problem. De Schritten för de Workflow to boen warrt genau so befögt as boven, aver mit 'n veel grötteren Graphen.
n = 100 # Number of nodes in graph
graph_100 = rx.PyGraph()
graph_100.add_nodes_from(np.arange(0, n, 1))
elist = []
for edge in backend.coupling_map:
if edge[0] < n and edge[1] < n:
elist.append((edge[0], edge[1], 1.0))
graph_100.add_edges_from(elist)
draw_graph(graph_100, node_size=200, with_labels=True, width=1)

Schritt 1: Klassische Ingaven op 'n Quantenproblem afbilden
Graph → Hamilton-Operator
Eerst konvertern wi den Graphen, den wi lösen wüllt, direkt in 'n Hamilton-Operator, de för QAOA geegnet is.
max_cut_paulis_100 = build_max_cut_paulis(graph_100)
cost_hamiltonian_100 = SparsePauliOp.from_sparse_list(max_cut_paulis_100, 100)
print("Cost Function Hamiltonian:", cost_hamiltonian_100)
Cost Function Hamiltonian: SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ', ...],
coeffs=[1.+0.j, 1.+0.j, ...])
Hamilton-Operator → Quantenschaltkreis
circuit_100 = QAOAAnsatz(cost_operator=cost_hamiltonian_100, reps=1)
circuit_100.measure_all()
circuit_100.draw("mpl", fold=False, scale=0.2, idle_wires=False)

Schritt 2: Problem för Quantenutföhrung optimeren
Üm den Schaltkreis-Optimierungsschritt för utility-scale Problemen to skaleren, köönt wi de hoge Performance-Transpilationsstrategien nutzen, wo in't Qiskit SDK v1.0 inföhrt worrn. Anner Warktüüch sünd de nije Transpiler-Service mit AI-verstärkde Transpiler-Pässen.
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
candidate_circuit_100 = pm.run(circuit_100)
candidate_circuit_100.draw("mpl", fold=False, scale=0.1, idle_wires=False)

Schritt 3: Utföhrung mit Qiskit Primitives
Üm QAOA to lopen, mööt wi de optimalen Parameter un weten, wo wi in den variational Schaltkreis insetten. Optimert disse Parameter, indem wi 'ne Optimierungsslöp op't Gerät lopen laat. De Zell schickt Jobs af, bet de Wert vun de Kostenfunkschoon konvergiiert is un de optimalen Parameter för un bestimmt sünd.
Kandidatenlösung finnen dör Lopen vun de Optimierung op't Gerät
Eerst lopen wi de Optimierungsslöp för de Schaltkreisparameter op't Gerät.
initial_gamma = np.pi
initial_beta = np.pi / 2
init_params = [initial_beta, initial_gamma]
objective_func_vals = [] # Global variable
with Session(backend=backend) as session:
estimator = Estimator(mode=session)
estimator.options.default_shots = 1000
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XY4"
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
result = minimize(
cost_func_estimator,
init_params,
args=(candidate_circuit_100, cost_hamiltonian_100, estimator),
method="COBYLA",
)
print(result)
message: Return from COBYLA because the trust region radius reaches its lower bound.
success: True
status: 0
fun: -3.9939191365979383
x: [ 1.571e+00 3.142e+00]
nfev: 29
maxcv: 0.0
Sobald de optimalen Parameter ut't Lopen vun QAOA op't Gerät funnen sünd, wiest wi disse Parameter den Schaltkreis to.
optimized_circuit_100 = candidate_circuit_100.assign_parameters(result.x)
optimized_circuit_100.draw("mpl", fold=False, idle_wires=False)

Letztlich föhrt wi den Schaltkreis mit de optimalen Parameter ut, üm ut de entsprechend Verdelung to sampeln.
sampler = Sampler(mode=backend)
sampler.options.default_shots = 10000
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XY4"
sampler.options.twirling.enable_gates = True
sampler.options.twirling.num_randomizations = "auto"
pub = (optimized_circuit_100,)
job = sampler.run([pub], shots=int(1e4))
counts_int = job.result()[0].data.meas.get_int_counts()
counts_bin = job.result()[0].data.meas.get_counts()
shots = sum(counts_int.values())
final_distribution_100_int = {
key: val / shots for key, val in counts_int.items()
}
Pröövt, dat de Kosten, wo in de Optimierungsslöp minimiiert worrn, to 'n bestimmten Wert konvergiiert sünd.
plt.figure(figsize=(12, 6))
plt.plot(objective_func_vals)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.show()

Schritt 4: Nabearbeiten un Törüchgeven vun't Resultat in't wünschte klassische Format
Geven, dat de Wahrschienlichkeit vun elkeen Lösung neddrig is, extraheren wi de Lösung, wo de neddrigsten Kosten entspricht.
_PARITY = np.array(
[-1 if bin(i).count("1") % 2 else 1 for i in range(256)],
dtype=np.complex128,
)
def evaluate_sparse_pauli(state: int, observable: SparsePauliOp) -> complex:
packed_uint8 = np.packbits(observable.paulis.z, axis=1, bitorder="little")
state_bytes = np.frombuffer(
state.to_bytes(packed_uint8.shape[1], "little"), dtype=np.uint8
)
reduced = np.bitwise_xor.reduce(packed_uint8 & state_bytes, axis=1)
return np.sum(observable.coeffs * _PARITY[reduced])
def best_solution(samples, hamiltonian):
min_cost = 1000
min_sol = None
for bit_str in samples.keys():
candidate_sol = int(bit_str)
fval = evaluate_sparse_pauli(candidate_sol, hamiltonian).real
if fval <= min_cost:
min_sol = candidate_sol
return min_sol
best_sol_100 = best_solution(final_distribution_100_int, cost_hamiltonian_100)
best_sol_bitstring_100 = to_bitstring(int(best_sol_100), len(graph_100))
best_sol_bitstring_100.reverse()
print("Result bitstring:", best_sol_bitstring_100)
Result bitstring: [1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1]
As nächste visualiseren wi den Schnitt. Knoten mit de sülve Farv hört to de sülve Grupp.
plot_result(graph_100, best_sol_bitstring_100)

Bereken den Wert vun den Schnitt.
cut_value_100 = evaluate_sample(best_sol_bitstring_100, graph_100)
print("The value of the cut is:", cut_value_100)
The value of the cut is: 124
Nu mööt wi den Zielfunktionswert vun elkeen Sample bereken, dat wi op'n Quantencomputer meten hebbt. Dat Sample mit den neddrigsten Zielfunktionswert is de Lösung, wo vun den Quantencomputer törüchgeven warrt.
def _plot_cdf(objective_values: dict, ax, color):
x_vals = sorted(objective_values.keys(), reverse=True)
y_vals = np.cumsum([objective_values[x] for x in x_vals])
ax.plot(x_vals, y_vals, color=color)
def plot_cdf(dist, ax, title):
_plot_cdf(dist, ax, "C1")
ax.vlines(min(list(dist.keys())), 0, 1, "C1", linestyle="--")
ax.set_title(title)
ax.set_xlabel("Objective function value")
ax.set_ylabel("Cumulative distribution function")
ax.grid(alpha=0.3)
def samples_to_objective_values(samples, hamiltonian):
objective_values = defaultdict(float)
for bit_str, prob in samples.items():
candidate_sol = int(bit_str)
fval = evaluate_sparse_pauli(candidate_sol, hamiltonian).real
objective_values[fval] += prob
return objective_values
result_dist = samples_to_objective_values(
final_distribution_100_int, cost_hamiltonian_100
)
Letztlich köönt wi de kumulative Verdelingsfunkschoon plotten, üm to visualiseren, wo elkeen Sample to de totale Wahrschienlichkeitsverdelung un den entsprechenden Zielfunktionswert bidreggt. De horizontal Utdehnung wiest den Bereich vun Zielfunktionswerte vun de Samples in de finale Verdelung. Ideal Wies schöölt wi sehn, dat de kumulative Verdelingsfunkschoon "Sprünge" an't ünnere Enn vun de Zielfunktionswertass hett. Dat schull bedüden, dat wenig Lösungen mit neddrige Kosten 'ne hoge Wahrschienlichkeit hebbt, sampelt to warrn. 'ne glatte, breed Kurv wiest an, dat elkeen Sample ähnlich wahrschienlich is un se veel verscheden Zielfunktionswerte hemm köönt, neddrig oder hoog.
fig, ax = plt.subplots(1, 1, figsize=(8, 6))
plot_cdf(result_dist, ax, "Eagle device")

Sluss
Dit Tutorial hett wiest, wo wi 'n Optimierungsproblem mit 'n Quantencomputer löst mit dat Qiskit Patterns Framework. De Demonstration enthölt 'n utility-scale Biespeel, mit Schaltkreisgröten, de nich genau klassisch simuleert warrn köönt. Momentan övverdoot Quantencomputer nich de klassischen Computer för kombinatorische Optimierung wegen Rusch. Aver de Hardware verbetert stetig, un nije Algorithmen för Quantencomputer warrt ständig ent wickelt. Tatsächlich warrt veel vun de Forschung, de an Quanten-Heuristiken för kombinatorische Optimierung arbeidt, mit klassische Simulationen testet, de blots 'ne lütte Tall vun Qubits erlöövt, typisch üm de 20 Qubits. Nu, mit grötteren Qubit-Tallen un Geräden mit weniger Rusch, warrt Forschers doran arbeiden könen, disse Quanten-Heuristiken bi groten Problemgröten op Quanten-Hardware to benchmarken.
Tutorial-Ümmfraag
Nimm doran deel an disse korte Ümmfraag, üm Feedback to dit Tutorial to geven. Juu Insichten warrt uns helpen, uns Inholtsanbod un Brukerfarung to verbeten.