Algorithme d'optimisation approximative quantique
Estimation de l'utilisation : 22 minutes sur un processeur Heron r3 (REMARQUE : il s'agit uniquement d'une estimation). Votre durée d'exécution peut varier.)
Résultats d'apprentissage
À l'issue de ce tutoriel, vous devriez être en mesure de comprendre les points suivants :
- Comment transposer un problème classique d'optimisation combinatoire (coupe maximale) en un hamiltonien quantique
- Comment implémenter et exécuter l'algorithme d'optimisation quantique approximative (QAOA) à l'aide de sessions du service de calcul d' IBM Quantum
- Comment faire évoluer un workflow QAOA, depuis un exemple sur petit simulateur jusqu'à une exécution sur du matériel à l'échelle industrielle
Prérequis
Nous vous recommandons de vous familiariser avec les sujets suivants :
- Les bases des circuits quantiques
- Algorithmes variationnels
- QAOA en détail — pour une présentation exhaustive de l'algorithme QAOA et de son application à l'échelle industrielle
Arrière-plan
L 'algorithme d'optimisation approximative quantique (QAOA) est une méthode itérative hybride quantique-classique permettant de résoudre des problèmes d'optimisation combinatoire. Dans ce tutoriel, vous utiliserez QAOA pour résoudre le problème de la coupe maximale (max-cut) — un problème d'optimisation NP-difficile qui trouve des applications dans le regroupement de données, la science des réseaux et la physique statistique. Étant donné un graphe composé de nœuds reliés par des arêtes, l'objectif est de partitionner les nœuds en deux ensembles de manière à maximiser le nombre d'arêtes traversant la partition.
De l'optimisation classique aux circuits quantiques
Le problème de Max-cut peut être formulé comme un problème d'optimisation binaire classique. À chaque nœud est attribuée une variable binaire indiquant à quel ensemble il appartient. L'objectif est de maximiser le nombre d'arêtes dont les extrémités appartiennent à des ensembles différents :
Cela revient à un problème d'optimisation binaire quadratique sans contrainte (QUBO) de la forme . Grâce à une substitution de variables standard ( ), le problème QUBO peut être réécrit sous la forme d'un hamiltonien de coût dont l'état fondamental code la solution optimale. En général, cet hamiltonien comporte à la fois des termes quadratiques et linéaires :
Pour le problème de coupe maximale non pondérée considéré ici, les coefficients linéaires s'annulent ( ) et pour chaque arête, ce qui donne la forme simplifiée que vous allez implémenter dans le code ci-dessous. La forme plus générale ci-dessus est celle dont vous aurez besoin pour adapter ce workflow aux graphes pondérés ou à d'autres problèmes pouvant être formulés sous forme de QUBO.
Comment fonctionne le QAOA
La méthode QAOA élabore des solutions candidates en appliquant des couches alternées de deux opérateurs à un état de superposition initial : l' opérateur de coût et un opérateur de mélange . Les angles et sont optimisés dans une boucle de rétroaction classique; l'ordinateur quantique évalue la fonction de coût, et un optimiseur classique met à jour les paramètres jusqu'à convergence. Cette boucle itérative s'exécute au sein d'une session de calcul quantique, ce qui permet de maintenir le dispositif quantique réservé d'une itération à l'autre afin de réduire la latence.
Pour une étude plus approfondie de la théorie QAOA, y compris la déduction complète du modèle QUBO vers l'hamiltonien, consultez le module de cours sur la QAOA.
Dans ce tutoriel, vous allez d'abord résoudre le problème du « max-cut » sur un petit graphe à cinq nœuds, puis adapter ce même processus à un problème à grande échelle portant sur 100 nœuds, sur du matériel réel. Remarque concernant l'accès aux formules : ce tutoriel utilise des sessions Quantum Compute, qui ne sont disponibles que dans la formule Premium. Si vous utilisez le plan « Open Plan », vous ne pouvez pas suivre ce tutoriel tel quel; vous devrez plutôt passer en Session mode « job » (c'est-à-dire soumettre chaque itération sous forme de tâche indépendante plutôt que d'encapsuler la boucle d'optimisation dans Session(...)). Le workflow continue de s'exécuter, mais chaque itération subit la latence totale de la file d'attente au lieu de réutiliser un périphérique réservé. Pour plus d'informations, consultez la rubrique « Présentation des formules disponibles ».
Exigences
Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont installés :
- Qiskit SDK v2.0 ou version ultérieure, avec prise en charge de la visualisation
- Qiskit Runtime v0.22 ou plus tard (
pip install qiskit-ibm-runtime)
De plus, vous devrez disposer d'un accès à une instance sur IBM Quantum® Platform.
Configuration
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 SamplerExemple à petite échelle
Cette section présente étape par étape le déroulement du processus QAOA sur une petite instance de problème de coupe maximale à cinq nœuds. Même s'il est qualifié de « petite échelle », cet exemple s'exécute tout de même sur du matériel d' IBM Quantum s réel : le code sélectionne un backend comportant au moins 127 qubits et y exécute le circuit.
Initialisez votre problème en créant un graphe avec nœuds.
n_small = 5
graph = rx.PyGraph()
graph.add_nodes_from(np.arange(0, n_small, 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)Output:
Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique
Représentez le graphe classique sous forme de circuits quantiques et d'opérateurs. Comme indiqué dans la section « Contexte », pour le problème du « max-cut » non pondéré, l'hamiltonien de coût se réduit à , et la méthode QAOA utilise un circuit d'ansatz paramétré pour préparer des états fondamentaux candidats de .
Construire l'hamiltonien des coûts
Convertissez les arêtes du graphe en termes Pauli pour construire (voir Contexte pour la dérivation).
def build_max_cut_paulis(
graph: rx.PyGraph,
) -> list[tuple[str, list[int], float]]:
"""Convert graph edges to a list of ZZ Pauli terms.
The returned list is in the sparse format expected by
``SparsePauliOp.from_sparse_list``: each element is
``(pauli_string, qubit_indices, coefficient)``.
"""
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_small)
print("Cost Function Hamiltonian:", cost_hamiltonian)Output:
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])
Construire le circuit de l'approche QAOA
Utilisez QAOAAnsatz pour construire le circuit QAOA paramétré à partir de l'hamiltonien de coût. Nous utilisons reps=2 ici (deux couches QAOA, quatre paramètres : ).
circuit = QAOAAnsatz(cost_operator=cost_hamiltonian, reps=2)
circuit.measure_all()
circuit.draw("mpl")Output:
circuit.parametersOutput:
ParameterView([ParameterVectorElement(β[0]), ParameterVectorElement(β[1]), ParameterVectorElement(γ[0]), ParameterVectorElement(γ[1])])
Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique
Convertir le circuit abstrait en instructions natives du matériel. Cette étape gère le mappage des qubits, la décomposition des portes, le routage et la suppression des erreurs. Pour plus d'informations, consultez la documentation sur la transpilation.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
print(backend)
# Create pass manager for transpilation. Level 3 is the most aggressive
# preset: slower to transpile, but produces shorter circuits that are
# more robust to hardware noise.
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
candidate_circuit = pm.run(circuit)
candidate_circuit.draw("mpl", fold=False, idle_wires=False)Output:
<IBMBackend('ibm_pittsburgh')>
Étape 3 : Exécutez à l'aide d' Qiskit primitives
La boucle d'optimisation QAOA s'exécute au sein d'une session Quantum Compute afin de garantir que le périphérique reste réservé d'une itération à l'autre. Un estimateur évalue l' e à chaque étape, et un optimiseur classique (COBYLA) met à jour les paramètres jusqu'à convergence.
Définissez les paramètres initiaux et lancez la boucle d'optimisation :
# QAOA doesn't prescribe principled default angles — any bounded choice
# works as a warm start for problems this small. beta and gamma are
# periodic (beta in [0, pi] and gamma in [0, 2*pi] modulo the underlying
# Pauli-rotation periods), and pi/2 and pi are just midpoints of those
# ranges. For harder problems you would typically warm start from known
# good angles or transfer parameters from smaller instances.
initial_gamma = np.pi
initial_beta = np.pi / 2
init_params = [initial_beta, initial_beta, initial_gamma, initial_gamma]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 costobjective_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"
estimator.options.environment.job_tags = ["TUT_QAOA"]
result = minimize(
cost_func_estimator,
init_params,
args=(candidate_circuit, cost_hamiltonian, estimator),
method="COBYLA",
tol=1e-2,
)
print(result)Output:
message: Return from COBYLA because the trust region radius reaches its lower bound.
success: True
status: 0
fun: -2.0402211719947774
x: [ 3.041e+00 1.212e+00 2.081e+00 4.471e+00]
nfev: 36
maxcv: 0.0
L'optimiseur a permis de réduire le coût et de trouver de meilleurs paramètres pour le circuit.
Une courbe qui diminue progressivement avant de se stabiliser est le signe d'une convergence. Une courbe bruyante et non monotone indique généralement qu'il faut se pencher sur un problème en amont; les causes courantes sont un nombre insuffisant d'itérations par évaluation (variance élevée de l'estimateur), des paramètres initiaux inadéquats ou un circuit dont la profondeur est dominée par le bruit matériel. COBYLA ne fait appel à aucun dérivé et résiste assez bien à un bruit modéré, mais lorsque le bruit l'emporte sur les gains réels de coût par itération, son modèle d'approximation linéaire ne parvient plus à distinguer la descente réelle des fluctuations aléatoires, et l'optimiseur s'égare.
plt.figure(figsize=(12, 6))
plt.plot(objective_func_vals)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.show()Output:
Attribuez les paramètres optimisés et échantillonnez la distribution finale à l'aide de la primitive Sampler.
optimized_circuit = candidate_circuit.assign_parameters(result.x)
optimized_circuit.draw("mpl", fold=False, idle_wires=False)Output:
# 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"
sampler.options.environment.job_tags = ["TUT_QAOA"]
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)Output:
{18: 0.039, 5: 0.0665, 20: 0.0973, 29: 0.0063, 9: 0.0899, 13: 0.0379, 2: 0.0047, 1: 0.0153, 11: 0.0932, 14: 0.0327, 12: 0.0314, 25: 0.0193, 21: 0.0398, 6: 0.0224, 4: 0.0197, 10: 0.0387, 3: 0.0181, 26: 0.07, 17: 0.0327, 19: 0.0332, 22: 0.0914, 24: 0.007, 0: 0.0033, 8: 0.0066, 30: 0.0158, 28: 0.0169, 27: 0.0222, 16: 0.0073, 7: 0.0057, 23: 0.0062, 15: 0.0054, 31: 0.0041}
Étape 4 : Post-traitement et restitution du résultat dans le format classique souhaité
Extraire la chaîne binaire la plus probable de la distribution échantillonnée. Il s'agit de la meilleure version trouvée par QAOA.
# 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)Output:
Result bitstring: [0, 0, 1, 0, 1]
plt.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()Output:
Visualiser la meilleure coupe
À partir de la chaîne binaire optimale, vous pouvez ensuite visualiser cette découpe sur le graphe d'origine.
# 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)Output:
Calculez maintenant la valeur de la découpe :
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)Output:
The value of the cut is: 5
Pour un graphe aussi petit, il est facile de trouver le véritable optimum par force brute; vous pouvez donc vérifier les résultats en comparant le résultat obtenu par l'algorithme QAOA à la réponse exacte.
# Classical baseline: enumerate all 2**n_small bitstrings and take the best cut.
def brute_force_max_cut(graph: rx.PyGraph) -> tuple[int, list[int]]:
n = len(list(graph.nodes()))
best_cut = -1
best_x: list[int] = []
for i in range(2**n):
x = [(i >> k) & 1 for k in range(n)]
cut = evaluate_sample(x, graph)
if cut > best_cut:
best_cut = int(cut)
best_x = x
return best_cut, best_x
classical_best, classical_x = brute_force_max_cut(graph)
print(f"Classical optimum (brute force): {classical_best}")
print(f"QAOA cut value: {cut_value}")Output:
Classical optimum (brute force): 5
QAOA cut value: 5
Exemple de matériel à grande échelle
Sur IBM Quantum Platform, vous avez accès à de nombreux appareils dotés de plus de 100 qubits. Choisissez-en un sur lequel résoudre le problème du « max-cut » sur un graphe pondéré de 100 nœuds. Il s'agit d'un problème à « grande échelle ». Le processus suit les mêmes étapes que celles décrites ci-dessus, mais s'applique à un graphe beaucoup plus vaste.
Flux de travail de bout en bout à l'échelle industrielle
Les quatre étapes sont présentées ci-dessous, appliquées au graphe de 100 nœuds. La structure est la même que celle de la démonstration à petite échelle : création de la carte, compilation, exécution, post-traitement — mais avec un problème plus complexe, réparti entre les quatre cellules ci-dessous pour plus de clarté.
# Precomputed parity lookup table: _PARITY[b] = +1 if popcount(b) is even, else -1.
# We use this to vectorize expectation-value evaluation across all Pauli terms.
_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:
"""Expectation value of a SparsePauliOp on a single computational-basis state.
For a Z-only observable (which QAOA cost Hamiltonians are, after the
QUBO-to-Hamiltonian mapping), the eigenvalue of each Pauli term on a
computational-basis state is simply (-1)**popcount(z_mask AND state),
i.e., the parity of the bitwise-AND of the term's Z-support and the
measured bitstring.
This routine packs the Z-support of every Pauli term into bytes, ANDs
them against the measured state in a single vectorized op, and looks up
the parity in _PARITY. For a 100-qubit / ~hundreds-of-terms Hamiltonian
over 10_000 samples, this is dramatically faster than calling
SparsePauliOp.expectation_value per sample.
"""
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):
"""Return the sampled bitstring (as int) with the lowest Hamiltonian cost."""
min_cost = float("inf")
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_cost = fval
min_sol = candidate_sol
return min_sol
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):
"""Convert the samples to values of the objective function."""
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Étape 1 : Construire le graphe, l'hamiltonien de coût et l'ansatz.
# Step 1: build the 100-node graph, cost Hamiltonian, and QAOA ansatz.
n_large = 100
graph_100 = rx.PyGraph()
graph_100.add_nodes_from(np.arange(0, n_large, 1))
elist = []
for edge in backend.coupling_map:
if edge[0] < n_large and edge[1] < n_large:
elist.append((edge[0], edge[1], 1.0))
graph_100.add_edges_from(elist)
max_cut_paulis_100 = build_max_cut_paulis(graph_100)
cost_hamiltonian_100 = SparsePauliOp.from_sparse_list(
max_cut_paulis_100, n_large
)
circuit_100 = QAOAAnsatz(cost_operator=cost_hamiltonian_100, reps=1)
circuit_100.measure_all()Étape 2 : Transpiler pour le backend matériel sélectionné.
# Step 2: transpile for hardware.
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
candidate_circuit_100 = pm.run(circuit_100)Étape 3 : Exécutez la boucle d'optimisation QAOA au sein d'une session, puis effectuez un échantillonnage.
# Step 3: run the QAOA optimization loop on the device, then sample the
# final distribution with the optimized parameters.
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
# 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"
estimator.options.environment.job_tags = ["TUT_QAOA"]
result = minimize(
cost_func_estimator,
init_params,
args=(candidate_circuit_100, cost_hamiltonian_100, estimator),
method="COBYLA",
)
print(result)
# Assign optimal parameters and sample the final distribution.
optimized_circuit_100 = candidate_circuit_100.assign_parameters(result.x)
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"
# Add a unique tag to the job execution
sampler.options.environment.job_tags = ["TUT_QAOA"]
pub = (optimized_circuit_100,)
job = sampler.run([pub], shots=int(1e4))
counts_int = job.result()[0].data.meas.get_int_counts()
shots = sum(counts_int.values())
final_distribution_100_int = {
key: val / shots for key, val in counts_int.items()
}Output:
message: Return from COBYLA because the trust region radius reaches its lower bound.
success: True
status: 0
fun: -17.172689238986344
x: [ 2.574e+00 4.166e+00]
nfev: 28
maxcv: 0.0
Étape 4 : Traiter la distribution échantillonnée afin d'en extraire la meilleure coupure.
# Step 4: find the best-cost sample and evaluate its cut value.
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)
cut_value_100 = evaluate_sample(best_sol_bitstring_100, graph_100)
print("The value of the cut is:", cut_value_100)Output:
Result bitstring: [1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0]
The value of the cut is: 156
Vérifiez que le coût minimisé dans la boucle d'optimisation a convergé, puis visualisez les résultats.
# Plot convergence
plt.figure(figsize=(12, 6))
plt.plot(objective_func_vals)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.show()
# Visualize the cut
plot_result(graph_100, best_sol_bitstring_100)
# Plot cumulative distribution function
result_dist = samples_to_objective_values(
final_distribution_100_int, cost_hamiltonian_100
)
fig, ax = plt.subplots(1, 1, figsize=(8, 6))
plot_cdf(result_dist, ax, backend.name)Output:
Etapes suivantes
Si ce travail vous a paru intéressant, les ressources suivantes pourraient vous intéresser :
- Techniques avancées pour le QAOA — exploration de stratégies avancées visant à améliorer les performances du QAOA
- Défi d'optimisation multi-objectifs — mettez vos compétences à l'épreuve avec ce défi communautaire consacré à l'optimisation quantique multi-objectifs
- Documentation sur la transpilation pour l'optimisation fine des circuits
- Suppression et atténuation des erreurs pour améliorer les résultats matériels