انتقل إلى المحتوى الرئيسي

خوارزمية التحسين التقريبي الكمومي

تقدير الاستخدام: 22 دقيقة على معالج Heron r3 (ملاحظة: هذا تقدير فحسب. قد يختلف وقت التشغيل الفعلي.)

نواتج التعلم

بعد إكمال هذا البرنامج التعليمي، يمكنك أن تتوقع فهم المعلومات التالية:

  • كيفية تحويل مسألة تحسين تركيبي كلاسيكية (القطع الأقصى) إلى هاميلتوني كمومي
  • كيفية تنفيذ وتشغيل خوارزمية التحسين التقريبي الكمومي (QAOA) باستخدام جلسات Qiskit Runtime
  • كيفية توسيع سير عمل QAOA من مثال صغير على المحاكي إلى تنفيذ على نطاق الاستخدام العملي على الأجهزة

المتطلبات المسبقة

يُنصح بأن تطّلع على هذه الموضوعات:

الخلفية النظرية

خوارزمية التحسين التقريبي الكمومي (QAOA) هي طريقة تكرارية هجينة (كمومية-كلاسيكية) لحل مسائل التحسين التركيبي. في هذا البرنامج التعليمي، ستستخدم QAOA لحل مسألة القطع الأقصى (max-cut) — وهي مسألة تحسين من نوع NP-hard لها تطبيقات في التجميع وعلم الشبكات والفيزياء الإحصائية. بإعطاء رسم بياني من عقد مرتبطة بحواف، الهدف هو تقسيم العقد إلى مجموعتين بحيث يكون عدد الحواف التي يقطعها التقسيم أقصى ما يمكن.

توضيح مسألة القطع الأقصى

من التحسين الكلاسيكي إلى الدوائر الكمومية

يمكن التعبير عن القطع الأقصى كمسألة تحسين ثنائي كلاسيكية. تُخصَّص لكل عقدة متغيرة ثنائية xi{0,1}x_i \in \{0, 1\} تشير إلى المجموعة التي تنتمي إليها. الهدف هو تعظيم عدد الحواف التي تكون نقاطها الطرفية في مجموعتين مختلفتين:

maxx{0,1}n(i,j)xi+xj2xixj.\max_{x \in \{0,1\}^n} \sum_{(i,j)} x_i + x_j - 2x_ix_j.

وهذا يكافئ مسألة التحسين الثنائي التربيعي غير المقيّد (QUBO) من الشكل minxxTQx\min_x\, x^T Q x. عبر استبدال متغيرات قياسي (xi(1Zi)/2x_i \to (1 - Z_i)/2)، يمكن إعادة كتابة QUBO على شكل هاميلتوني التكلفة الذي تشفّر حالته الأساسية الحل الأمثل. بصفة عامة، يحتوي هذا الهاميلتوني على حدود تربيعية وخطية معاً:

HC=ijQijZiZj+ibiZi.H_C = \sum_{ij} Q_{ij} \, Z_i Z_j + \sum_i b_i \, Z_i.

بالنسبة لمسألة القطع الأقصى غير الموزونة المطروحة هنا، تتلاشى المعاملات الخطية (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 حلولاً مرشحة بتطبيق طبقات متناوبة من عاملين على حالة تراكب أولية Hn0H^{\otimes n}|0\rangle: عامل التكلفة eiγkHCe^{-i\gamma_k H_C} وعامل الخلط eiβkHme^{-i\beta_k H_m}. تُحسَّن الزوايا γk\gamma_k وβk\beta_k في حلقة تغذية راجعة كلاسيكية؛ يقيّم الحاسوب الكمومي دالة التكلفة، ويحدّث محسّن كلاسيكي المعاملات حتى التقارب. تُشغَّل هذه الحلقة التكرارية ضمن جلسة (Session) Qiskit Runtime، التي تُبقي الجهاز الكمومي محجوزاً عبر التكرارات لتقليل زمن الاستجابة.

مخطط دائرة بطبقات QAOA

لمعالجة أعمق لنظرية QAOA، بما في ذلك الاشتقاق الكامل من QUBO إلى الهاميلتوني، راجع وحدة دورة QAOA.

في هذا البرنامج التعليمي ستحل أولاً مسألة القطع الأقصى لرسم بياني صغير من خمس عقد، ثم ستوسّع سير العمل ذاته إلى مسألة على نطاق الاستخدام العملي بـ 100 عقدة على أجهزة حقيقية. ملاحظة حول صلاحية الوصول للخطة: يستخدم هذا البرنامج التعليمي جلسات Qiskit Runtime، المتاحة فقط ضمن الخطة المميزة. إذا كنت على الخطة المفتوحة، فلا يمكنك تشغيل هذا البرنامج التعليمي كما هو مكتوب؛ بدلاً من ذلك، ستحتاج إلى استبدال Session بـ وضع المهام (أي تقديم كل تكرار كمهمة مستقلة بدلاً من تغليف حلقة التحسين بـ Session(...)). يظل سير العمل قابلاً للتشغيل، لكن كل تكرار يتكبّد زمن انتظار الطابور كاملاً بدلاً من إعادة استخدام جهاز محجوز. راجع نظرة عامة على الخطط المتاحة لمزيد من المعلومات.

المتطلبات

قبل البدء في هذا البرنامج التعليمي، تأكد من تثبيت ما يلي:

  • Qiskit SDK الإصدار v2.0 أو أحدث، مع دعم التصور المرئي
  • Qiskit Runtime الإصدار v0.22 أو أحدث (pip install qiskit-ibm-runtime)

بالإضافة إلى ذلك، ستحتاج إلى صلاحية الوصول إلى نسخة على IBM Quantum® Platform.

الإعداد

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime rustworkx scipy
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 على نسخة صغيرة من مسألة القطع الأقصى بخمس عقد. ورغم وصفه بأنه "صغير النطاق"، فإن هذا المثال يظل يُنفَّذ على أجهزة IBM Quantum الحقيقية — إذ يختار الكود معالجاً خلفياً Backend بـ 127 كيوبت أو أكثر وينفّذ الدائرة عليه. هيّئ مسألتك بإنشاء رسم بياني بـ 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: تحويل المدخلات الكلاسيكية إلى مسألة كمومية

حوّل الرسم البياني الكلاسيكي إلى دوائر (Circuits) وعوامل كمومية. كما هو موضح في الخلفية النظرية، بالنسبة للقطع الأقصى غير الموزون يُختزَل هاميلتوني التكلفة إلى HC=(i,j)EZiZjH_C = \sum_{(i,j) \in E} Z_i Z_j، وتستخدم QAOA دائرة Ansatz ذات معاملات لتهيئة الحالات الأساسية المرشحة لـ HCH_C.

بناء هاميلتوني التكلفة

حوّل حواف الرسم البياني إلى حدود Pauli من النوع 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 Ansatz

استخدم QAOAAnsatz لإنشاء دائرة 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: تحسين المسألة لتنفيذها على الأجهزة الكمومية

حوّل برمجياً الدائرة المجردة إلى تعليمات أصيلة للأجهزة. تتولى هذه الخطوة تعيين الكيوبتات، وفك تسلسل البوابات، والتوجيه، وقمع الأخطاء. راجع توثيق التحويل البرمجي لمزيد من المعلومات.

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 داخل جلسة (Session) Qiskit Runtime لإبقاء الجهاز محجوزاً عبر التكرارات. يقيّم Estimator القيمة HC\langle H_C \rangle في كل خطوة، ويحدّث محسّن كلاسيكي (COBYLA) المعاملات حتى التقارب.

توضيح يبيّن سلوك أوضاع التشغيل: المهمة المفردة والدُّفعة والجلسة. حدّد المعاملات الأولية وشغّل حلقة التحسين:

# 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

تمكّن المحسّن من تقليل التكلفة والعثور على معاملات أفضل للدائرة.

المنحنى المتناقص بسلاسة الذي يستقر عند مستوى ثابت هو علامة التقارب. أما المنحنى المتشوش غير الرتيب فيشير عادةً إلى وجود مشكلة في إحدى الخطوات السابقة تستدعي الانتباه؛ ومن الأسباب الشائعة: قلة عدد اللقطات (shots) لكل تقييم (تباين عالٍ في Estimator)، أو معاملات أولية رديئة، أو دائرة يهيمن على عمقها ضوضاء الأجهزة. خوارزمية 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 كيوبت على IBM Quantum Platform. اختر أحدها لحل القطع الأقصى لرسم بياني موزون بـ 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: ابنِ الرسم البياني وهاميلتوني التكلفة ودائرة 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()

الخطوة 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)

ناتج خلية الكود السابقة

ناتج خلية الكود السابقة

ناتج خلية الكود السابقة

الخطوات التالية

إذا وجدت هذا العمل مثيراً للاهتمام، فقد تهتم بالمواد التالية:

توصيات