Algoritmo cuántico de optimización aproximada
Estimación de uso: 22 minutos en un procesador Heron r3 (NOTA: Esto es sólo una estimación. Su tiempo de ejecución puede variar)
Resultados del aprendizaje
Una vez completado este tutorial, habrás adquirido los siguientes conocimientos:
- Cómo mapear un problema clásico de optimización combinatoria (corte máximo) a un hamiltoniano cuántico
- Cómo implementar y ejecutar el algoritmo de optimización cuántica aproximada (QAOA) mediante sesiones de Qiskit Runtime
- Cómo ampliar un flujo de trabajo QAOA desde un ejemplo de simulador a pequeña escala hasta la ejecución en hardware a escala industrial
Requisitos previos
Se recomienda que te familiarices con estos temas:
- Conceptos básicos de los circuitos cuánticos
- Algoritmos variacionales
- QAOA en profundidad : un análisis exhaustivo del algoritmo QAOA y su aplicación a gran escala
En segundo plano
El algoritmo de optimización cuántica aproximada (QAOA) es un método iterativo híbrido cuántico-clásico para resolver problemas de optimización combinatoria. En este tutorial, utilizarás QAOA para resolver el problema del corte máximo (max-cut), un problema de optimización NP-difícil con aplicaciones en la agrupación de datos, la ciencia de redes y la física estadística. Dado un grafo formado por nodos conectados por aristas, el objetivo es dividir los nodos en dos conjuntos de tal manera que se maximice el número de aristas que cruzan la división.
De la optimización clásica a los circuitos cuánticos
Max-cut puede plantearse como un problema clásico de optimización binaria. A cada nodo se le asigna una variable binaria que indica a qué conjunto pertenece. El objetivo es maximizar el número de aristas cuyos extremos pertenezcan a conjuntos diferentes:
Esto equivale a un problema de optimización binaria cuadrática sin restricciones (QUBO) de la forma . Mediante una sustitución de variables estándar ( ), el QUBO puede reescribirse como un hamiltoniano de coste cuyo estado fundamental codifica la solución óptima. En general, este hamiltoniano contiene tanto términos cuadráticos como lineales:
En el problema del corte máximo no ponderado que nos ocupa, los coeficientes lineales se anulan ( ) y para cada arista, lo que da lugar a la forma más simple , que desarrollarás en el código a continuación. La forma más general anterior es la que necesitarías para adaptar este flujo de trabajo a grafos ponderados u otros problemas expresables en QUBO.
Cómo funciona QAOA
El método QAOA elabora soluciones candidatas aplicando capas alternas de dos operadores a un estado de superposición inicial : el operador de coste y un operador mezclador . Los ángulos y se optimizan en un bucle de retroalimentación clásico; el ordenador cuántico evalúa la función de coste y un optimizador clásico actualiza los parámetros hasta alcanzar la convergencia. Este bucle iterativo se ejecuta dentro de una sesión de « Qiskit Runtime », lo que permite mantener el dispositivo cuántico reservado a lo largo de todas las iteraciones para reducir la latencia.
Para un análisis más detallado de la teoría de la QAOA, incluida la derivación completa del QUBO al hamiltoniano, consulte el módulo del curso sobre QAOA.
En este tutorial, primero resolverás el problema del corte máximo en un pequeño grafo de cinco nodos y, a continuación, ampliarás ese mismo flujo de trabajo a un problema a escala industrial de 100 nodos en hardware real. Nota sobre el acceso al plan: Este tutorial utiliza sesiones de « Qiskit Runtime », que solo están disponibles en el plan Premium. Si estás en el plan Open, no podrás seguir este tutorial tal y como está escrito; en su lugar, tendrás que cambiar Session al modo de trabajo (es decir, enviar cada iteración como un trabajo independiente en lugar de envolver el bucle de optimización con Session(...)). El flujo de trabajo sigue ejecutándose, pero cada iteración sufre toda la latencia de la cola, en lugar de reutilizar un dispositivo reservado. Consulte la descripción general de los planes disponibles para obtener más información.
Requisitos
Antes de empezar este tutorial, asegúrate de tener instalado lo siguiente:
- Qiskit SDK v2.0 o posterior, con soporte para visualización
- Qiskit Runtime v0.22 o posterior (
pip install qiskit-ibm-runtime)
Además, necesitarás tener acceso a una instancia en IBM Quantum® Platform.
Configuración
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 SamplerEjemplo a pequeña escala
En esta sección se describe paso a paso el flujo de trabajo de QAOA utilizando una pequeña instancia de max-cut de cinco nodos. A pesar de la etiqueta de «pequeña escala», este ejemplo se ejecuta en un hardware real de IBM Quantum : el código selecciona un backend con 127 o más qubits y ejecuta el circuito en él.
Inicialice su problema creando un grafo con nodos.
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:
Paso 1: Asignar entradas clásicas a un problema cuántico
Representa el grafo clásico en circuitos y operadores cuánticos. Tal y como se describe en la sección «Antecedentes», para el problema del corte máximo no ponderado, el hamiltoniano de coste se reduce a , y el QAOA utiliza un circuito de ansatz parametrizado para preparar estados fundamentales candidatos de .
Calcular el hamiltoniano de costes
Convertir los arcos del grafo en términos de Pauli- o para construir un grafo de tipo « » (véase la sección «Antecedentes» para consultar la derivación).
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])
Construir el circuito del modelo QAOA
Utiliza QAOAAnsatz para construir el circuito QAOA parametrizado a partir del hamiltoniano de coste. Aquí utilizamos reps=2 (dos capas QAOA, cuatro parámetros: ).
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])])
Paso 2: Optimizar el problema para la ejecución en hardware cuántico
Convertir el circuito abstracto en instrucciones nativas del hardware. Este paso se encarga de la asignación de qubits, la descomposición de puertas, el enrutamiento y la supresión de errores. Consulta la documentación sobre la transpilación para obtener más información.
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')>
Paso 3: Ejecutar utilizando Qiskit primitives
El bucle de optimización de QAOA se ejecuta dentro de una sesión de « Qiskit Runtime » para mantener el dispositivo reservado a lo largo de las iteraciones. Un estimador evalúa un e en cada paso, y un optimizador clásico (COBYLA) actualiza los parámetros hasta alcanzar la convergencia.
Define los parámetros iniciales y ejecuta el ciclo de optimización:
# 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
El optimizador consiguió reducir el coste y encontrar mejores parámetros para el circuito.
Una curva que desciende suavemente y luego se estabiliza es el indicio de la convergencia. Una curva ruidosa y no monótona suele indicar que hay algún problema en las etapas anteriores que requiere atención; las causas más comunes son un número insuficiente de iteraciones por evaluación (alta varianza del estimador), parámetros iniciales inadecuados o un circuito cuya profundidad está dominada por el ruido del hardware. COBYLA no utiliza derivadas y es bastante resistente al ruido moderado, pero cuando el ruido supera las mejoras reales en el coste por paso, su modelo de aproximación lineal ya no puede distinguir el descenso real de las fluctuaciones aleatorias y el optimizador se desvía.
plt.figure(figsize=(12, 6))
plt.plot(objective_func_vals)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.show()Output:
Asigna los parámetros optimizados y genera una muestra de la distribución final utilizando la primitiva «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}
Paso 4: Procesamiento posterior y devolución del resultado en el formato clásico deseado
Extrae la cadena de bits más probable de la distribución muestreada. Este es el mejor corte que ha encontrado 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:
Visualizar el mejor corte
A partir de la cadena de bits óptima, puedes visualizar este corte en el gráfico original.
# 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:
Ahora, calcula el valor del corte:
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
En un grafo tan pequeño, es fácil hallar el óptimo verdadero mediante un método de fuerza bruta, por lo que puedes verificar los resultados comparando el resultado del algoritmo QAOA con la respuesta exacta.
# 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
Ejemplo de hardware a gran escala
En IBM Quantum Platform tienes acceso a numerosos dispositivos con más de 100 qubits. Elige uno en el que resolver el problema del corte máximo en un grafo ponderado de 100 nodos. Se trata de un problema a «escala industrial». El flujo de trabajo sigue los mismos pasos que los anteriores, pero aplicados a un gráfico mucho más grande.
Flujo de trabajo integral a escala industrial
A continuación se muestran los cuatro pasos, aplicados al gráfico de 100 nodos. La estructura es la misma que la del ejemplo a pequeña escala: mapa, transpilación, ejecución y posprocesamiento, pero con un problema más complejo y dividido entre las cuatro celdas siguientes para mayor claridad.
# 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_valuesPaso 1 : Construir el grafo, el hamiltoniano de costes y el 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()Paso 2 : Compilar para el backend de hardware seleccionado.
# Step 2: transpile for hardware.
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
candidate_circuit_100 = pm.run(circuit_100)Paso 3 : Ejecuta el bucle de optimización de QAOA dentro de una sesión y, a continuación, toma una muestra.
# 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
Paso 4 : Procesar posteriormente la distribución muestreada para extraer el mejor corte.
# 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
Comprueba que el coste minimizado en el bucle de optimización haya convergido y visualiza los resultados.
# 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:
Próximos pasos
Si te ha parecido interesante este trabajo, quizá te interese el siguiente material:
- Técnicas avanzadas para QAOA : análisis de estrategias avanzadas para mejorar el rendimiento de QAOA
- Reto de optimización multiobjetivo : pon a prueba tus habilidades con este reto comunitario sobre optimización cuántica multiobjetivo
- Documentación sobre la transpilación para el ajuste fino de la optimización de circuitos
- Supresión y mitigación de errores para mejorar los resultados del hardware