Перейти до основного вмісту
Цей проєкт у бета-версії. Знайшов(-ла) помилку? Відкрити issue на GitHub. Є запитання чи ідея? Запитати в Discussions — будемо раді твоєму відгуку!
Яка якість перекладу на цій сторінці?

Квантовий алгоритм наближеної оптимізації

Оцінка використання: 22 хвилини на процесорі Heron r3 (ПРИМІТКА: Це лише оцінка. Твій час виконання може відрізнятися.)

Результати навчання​

  • Як відобразити класичну задачу комбінаторної оптимізації (max-cut) на квантовий гамільтоніан

  • Як реалізувати та запустити Квантовий алгоритм наближеної оптимізації (QAOA) з використанням сесій IBM Quantum Compute Service

  • Як масштабувати робочий процес QAOA від невеликого прикладу на симуляторі до виконання на обладнанні утилітарного масштабу

Передумови​

Передумови​

Квантовий алгоритм наближеної оптимізації (QAOA) — це гібридний квантово-класичний ітеративний метод для розв'язання задач комбінаторної оптимізації. У цьому посібнику ти використаєш QAOA для розв'язання задачі максимального розрізу (max-cut) — NP-важкої задачі оптимізації із застосуваннями в кластеризації, мережевій науці та статистичній фізиці. Маючи граф вузлів, з'єднаних ребрами, мета полягає в тому, щоб розбити вузли на два набори так, щоб кількість ребер, що перетинають розбиття, була максимальною.

Комбінаторна оптимізація також трапляється в задачах сталого розвитку, наприклад, у плануванні маршрутів доставки для зменшення витрат пального та в розподілі ресурсів для обмеження матеріальних відходів. Приклад max-cut демонструє процес подання цільової функції у вигляді гамільтоніана вартості та дослідження розв'язків-кандидатів за допомогою QAOA. Застосування цього підходу до задачі сталого розвитку потребує цільової функції та обмежень, які відображають цільове застосування.

Ілюстрація задачі max-cut

Від класичної оптимізації до квантових контурів​

Max-cut можна виразити як класичну задачу бінарної оптимізації. Кожному вузлу присвоюється бінарна змінна xi∈{0,1}x_i \in \{0, 1\}, що вказує, до якого набору він належить. Мета полягає в максимізації кількості ребер, кінці яких знаходяться в різних наборах:

max⁡x∈{0,1}n∑(i,j)xi+xj−2xixj.\max_{x \in \{0,1\}^n} \sum_{(i,j)} x_i + x_j - 2x_ix_j.

Це еквівалентно задачі квадратичної необмеженої бінарної оптимізації (QUBO) вигляду min⁡x xTQx\min_x\, x^T Q x. Через стандартну заміну змінних (xi→(1−Zi)/2x_i \to (1 - Z_i)/2) QUBO можна переписати як гамільтоніан вартості, основний стан якого кодує оптимальний розв'язок. Загалом цей гамільтоніан має як квадратичні, так і лінійні члени:

HC=∑ijQij ZiZj+∑ibi Zi.H_C = \sum_{ij} Q_{ij} \, Z_i Z_j + \sum_i b_i \, Z_i.

Для незваженої задачі max-cut, яка тут розглядається, лінійні коефіцієнти зникають (bi=0b_i = 0), а Qij=1Q_{ij} = 1 для кожного ребра, що залишає простішу форму HC=∑(i,j)∈EZiZjH_C = \sum_{(i,j) \in E} Z_i Z_j, яку ти побудуєш у коді нижче. Більш загальна форма вище — це те, що тобі знадобиться, щоб адаптувати цей робочий процес до зважених графів чи інших задач, виразних через QUBO.

Як працює QAOA​

QAOA готує розв'язки-кандидати, застосовуючи почергові шари двох операторів до початкового стану суперпозиції H⊗n∣0⟩H^{\otimes n}|0\rangle: оператора вартості e−iγkHCe^{-i\gamma_k H_C} та оператора-змішувача e−iβkHme^{-i\beta_k H_m}. Кути γk\gamma_k і βk\beta_k оптимізуються в класичному циклі зворотного зв'язку; квантовий комп'ютер обчислює функцію вартості, а класичний оптимізатор оновлює параметри до збіжності. Цей ітеративний цикл виконується в межах сесії Quantum Compute, яка тримає квантовий пристрій зарезервованим протягом ітерацій для нижчої затримки.

Діаграма контуру з шарами QAOA

Для глибшого розгляду теорії QAOA, включно з повним виведенням від QUBO до гамільтоніана, дивись модуль курсу QAOA.

У цьому посібнику ти спочатку розв'яжеш max-cut на невеликому графі з п'яти вузлів, а потім масштабуєш той самий робочий процес до задачі утилітарного масштабу зі 100 вузлами на реальному обладнанні. Примітка щодо доступу за планом: Цей посібник використовує сесії Quantum Compute, які доступні лише на Преміум плані. Якщо ти на Відкритому плані, ти не можеш виконати цей посібник у такому вигляді; натомість тобі потрібно буде замінити Session на job mode (тобто надсилати кожну ітерацію як незалежне завдання, а не загортати цикл оптимізації в with Session(...)). Робочий процес усе одно виконується, але кожна ітерація зазнає повної затримки черги замість повторного використання зарезервованого пристрою. Дивись Огляд доступних планів для додаткової інформації.

Вимоги​

Перед початком цього посібника переконайся, що в тебе встановлено таке:

  • Qiskit SDK v2.0 або пізніше, з підтримкою візуалізації

  • Qiskit Runtime v0.22 або пізніше (pip install qiskit-ibm-runtime)

Крім того, тобі знадобиться доступ до екземпляра на IBM Quantum® Platform.

Налаштування​

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "rustworkx": "rustworkx", "scipy": "scipy"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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

Приклад малого масштабу​

Цей розділ проходить через кожен крок робочого процесу QAOA на невеликому екземплярі max-cut з п'яти вузлів. Попри позначку «малий масштаб», цей приклад усе одно виконується на реальному обладнанні IBM Quantum — код обирає Backend зі 127 або більше qubit і виконує там Circuit. Ініціалізуй свою задачу, створивши граф з n=5n=5 вузлами.

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)

Вивід попередньої комірки коду

Крок 1: Відображення класичних входів на квантову задачу​

Відобрази класичний граф у квантові Circuit та оператори. Як описано в розділі Передумови, для незваженого max-cut гамільтоніан вартості зводиться до HC=∑(i,j)∈EZiZjH_C = \sum_{(i,j) \in E} Z_i Z_j, а QAOA використовує параметризований контур-ансац для підготовки основних станів-кандидатів HCH_C.

Побудова гамільтоніана вартості​

Перетвори ребра графа на члени Паулі ZiZjZ_iZ_j, щоб побудувати HCH_C (дивись Передумови для виведення).

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)
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])

Побудова контуру-ансацу QAOA​

Використовуй QAOAAnsatz, щоб побудувати параметризований Circuit QAOA з гамільтоніана вартості. Тут ми використовуємо reps=2 (два шари QAOA, чотири параметри: β0,β1,γ0,γ1\beta_0, \beta_1, \gamma_0, \gamma_1).

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])])

Крок 2: Оптимізація задачі для виконання на квантовому обладнанні​

Транспілюй абстрактний Circuit у нативні для обладнання інструкції. Цей крок обробляє відображення qubit, розкладання Gate, маршрутизацію та придушення помилок. Дивись документацію з транспіляції для додаткової інформації.

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)
<IBMBackend('ibm_pittsburgh')>

Вивід попередньої комірки коду

Крок 3: Виконання з використанням примітивів Qiskit​

Цикл оптимізації QAOA виконується всередині сесії Quantum Compute, щоб тримати пристрій зарезервованим протягом ітерацій. Estimator обчислює ⟨HC⟩\langle H_C \rangle на кожному кроці, а класичний оптимізатор (COBYLA) оновлює параметри до збіжності.

Ілюстрація, що показує поведінку режимів виконання Single job, Batch та Session. Визнач початкові параметри та запусти цикл оптимізації:

# 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 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"
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)
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

Оптимізатор зміг зменшити вартість і знайти кращі параметри для Circuit.

Плавно спадна крива, що виходить на плато, є ознакою збіжності. Шумна, немонотонна крива зазвичай вказує на те, що щось вище за течією потребує уваги; поширені причини — це замало запусків (shots) на одну оцінку (висока дисперсія Estimator), погані початкові параметри або Circuit, глибина якого визначається апаратним шумом. COBYLA не використовує похідні й досить стійкий до помірного шуму, але коли шум перекриває фактичні покращення вартості на кроці, його лінійно-апроксимаційна модель більше не може відрізнити справжній спуск від випадкового тремтіння, і оптимізатор блукає.

plt.figure(figsize=(12, 6))
plt.plot(objective_func_vals)
plt.xlabel("Iteration")
plt.ylabel("Cost")
plt.show()

Вивід попередньої комірки коду

Признач оптимізовані параметри та отримай вибірку фінального розподілу за допомогою примітиву Sampler.

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"

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)
{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}

Крок 4: Постобробка та повернення результату в бажаному класичному форматі​

Витягни найбільш імовірний бітовий рядок із вибраного розподілу. Він представляє найкращий розріз, знайдений 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)
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()

Вивід попередньої комірки коду

Візуалізація найкращого розрізу​

З оптимального бітового рядка ти можеш потім візуалізувати цей розріз на початковому графі.

# 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)

Вивід попередньої комірки коду

Тепер обчисли значення розрізу:

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

Для такого малого графа справжній оптимум легко знайти повним перебором, тож ти можеш перевірити результати, порівнявши результат QAOA з точною відповіддю.

# 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}")
Classical optimum (brute force): 5
QAOA cut value: 5

Приклад на обладнанні великого масштабу​

Ти маєш доступ до багатьох пристроїв із понад 100 qubit на IBM Quantum Platform. Обери один із них, щоб розв'язати max-cut на зваженому графі зі 100 вузлів. Це задача «утилітарного масштабу». Робочий процес повторює ті самі кроки, що й вище, застосовані до значно більшого графа.

Наскрізний робочий процес на утилітарному масштабі​

Усі чотири кроки показані нижче, застосовані до графа зі 100 вузлів. Структура така сама, як у покроковому огляді малого масштабу: відобразити, транспілювати, виконати, постобробити — але з більшою задачею та розділена на чотири комірки нижче для ясності.

# 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

Крок 1: Побудуй граф, гамільтоніан вартості та ансац.

# 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()

Крок 2: Транспілюй для обраного апаратного Backend.

# Step 2: transpile for hardware.
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
candidate_circuit_100 = pm.run(circuit_100)

Крок 3: Запусти цикл оптимізації QAOA всередині сесії, а потім отримай вибірку.

# 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()
}
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

Крок 4: Постобробка вибраного розподілу для витягання найкращого розрізу.

# 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)
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

Перевір, що вартість, мінімізована в циклі оптимізації, зійшлася, та візуалізуй результати.

# 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)

Вивід попередньої комірки коду

Вивід попередньої комірки коду

Вивід попередньої комірки коду

Подальші кроки​

Якщо ця робота здалася тобі цікавою, тебе можуть зацікавити такі матеріали:

Рекомендації