حل نموذج شيرينغتون-كيركباتريك باستخدام ParityQC Parity Twine Optimizer
تقدير الاستخدام: 10 ثوانٍ على معالج Nighthawk r2. (ملاحظة: هذا مجرد تقدير. قد يختلف وقت التشغيل الفعلي لديك.)
نتائج التعلّم
-
استخدم Parity Twine Optimizer لحل نموذج شيرينغتون-كيركباتريك.
-
تعرّف على الخيارات المتاحة لـ Parity Twine Optimizer والنتائج التي يتم إخراجها.
الخلفية
يوضح هذا الدرس التعليمي كيفية حل نموذج شيرينغتون-كيركباتريك باستخدام ParityQC Parity Twine Optimizer.
يوفر شيفرةً لصياغة المسألة بالكامل محليًا بصيغة يمكنها التواصل مع Parity Twine Optimizer.
نموذج شيرينغتون-كيركباتريك
نموذج شيرينغتون-كيركباتريك (SK) هو نموذج أساسي في الميكانيكا الإحصائية، وتحديدًا في دراسة الزجاج المغزلي (spin glasses). على عكس نموذج إيزينغ (Ising) القياسي، حيث تقتصر التفاعلات عادةً على أقرب الجيران، فإن نموذج SK هو نموذج ذو مدى لا نهائي، بمعنى أن كل مغزل (spin) يتفاعل مع كل مغزل آخر في النظام. يؤدي هذا إلى مشهد طاقة معقد للغاية و"وعر"، يتميز بالعديد من الحدود الدنيا المحلية، وهو السمة المميزة للسلوك الزجاجي.
تكمن السمة الأساسية لنموذج SK في الإحباط (frustration). في النموذج، توزَّع قوى التفاعل بين المغازل عشوائيًا بين قيم موجبة وسالبة. يؤدي هذا إلى حالات (على سبيل المثال، في الترتيبات المثلثية) لا يمكن فيها ترتيب المغازل لتقليل جميع التفاعلات في آنٍ واحد. في نموذج SK، ولأن كل مغزل يتفاعل مع كل مغزل آخر، يتضاعف هذا الإحباط على نطاق شامل، مما يؤدي إلى شبكة من القيود المتضاربة.
الصياغة الرياضية
تُعرَّف حالة النظام بمجموعة من مغازل إيزينغ (Ising spins)، . تُعطى طاقة تهيئة معينة بواسطة الهاملتوني (Hamiltonian):
حيث هي قوة الاقتران بين المغزل والمغزل .
في نموذج SK، تكون الاقترانات متغيرات عشوائية مستقلة وموزعة توزيعًا متطابقًا. لضمان بقاء الطاقة شاملة (متناسبة مع ) مع ، يجب أن يتناسب تباين الاقترانات مع عدد الجسيمات:
الحالة الأرضية لـ معينة هي التهيئة المحددة للمغازل ()، التي تقلل الطاقة إلى أدنى حد. إيجاد الحالة الأرضية هو مسألة تحسين من النوع NP-hard.
المتطلبات
قبل بدء هذا الدرس التعليمي، تأكد من تثبيت ما يلي:
-
عميل IBM لكتالوج Qiskit Functions (
pip install qiskit-ibm-catalog) -
إضافة Qiskit لمخطط التحسين (
pip install qiskit_addon_opt_mapper) -
NumPy (
pip install numpy)
تحتاج أيضًا إلى إذن للوصول إلى دالة ParityQC Twine Optimizer. لطلب الوصول، أكمل هذا النموذج.
الإعداد
(تفترض هذه الشيفرة أنك حفظت حسابك مسبقًا في بيئتك المحلية.)
أولاً، استورد جميع الحزم المطلوبة لهذا الدرس التعليمي.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "qiskit_ibm_catalog": "qiskit-ibm-catalog"}
_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 numpy as np
from qiskit_ibm_catalog import QiskitFunctionsCatalog
حمّل Parity Twine Optimizer من كتالوج Qiskit Functions:
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
function = catalog.load("parityqc/parity-twine-optimizer")
الخطوة 1: تعريف المسألة كدالة هدف
بدلاً من الحصول على مسألة SK من مكتبة كما نفعل مع مسألة تقسيم السوق، ستقوم بصياغتها مباشرةً.
تصوغ دالة generate_sk_problem مسألة SK مباشرةً بصيغة القاموس المطلوبة. المدخل الوحيد المطلوب هو n، عدد المغازل في النموذج.
def generate_sk_problem(
n: int,
coupling_mean: float = 0.0,
coupling_std: float = 1.0,
local_fields_mean: float = 0.0,
local_fields_std: float = 0.0,
edge_density: float = 1.0,
ensure_extensivity: bool = False,
seed: int | None = None,
) -> dict:
"""Generate the Sherrington-Kirkpatrick (SK) model with varying
edge density.
Samples couplings and local fields via ``generate_couplings_sk_model``
and assembles the corresponding Ising Hamiltonian
H = -∑_{i<j} J_ij z_i z_j - ∑_i h_i z_i,
where z_i ∈ {-1, +1}.
Args:
n: Number of spins (>= 2).
coupling_mean: Mean coupling before optional SK scaling.
coupling_std: Coupling std before optional SK scaling.
local_fields_mean: Mean longitudinal field.
local_fields_std: Std of the longitudinal fields.
edge_density: Fraction of non-zero couplings, in ``[2/n, 1]``.
ensure_extensivity: Whether to apply the SK 1/n scaling.
seed: random number generator seed.
Returns:
An encoding the SK Hamiltonian.
Raises:
ValueError: If ``n < 2``, ``coupling_std < 0``, ``local_fields_std < 0``,
or ``edge_density`` is outside ``[2/n, 1]``.
"""
couplings, local_fields = _generate_couplings_sk_model(
n=n,
coupling_mean=coupling_mean,
coupling_std=coupling_std,
local_fields_mean=local_fields_mean,
local_fields_std=local_fields_std,
edge_density=edge_density,
ensure_extensivity=ensure_extensivity,
seed=seed,
)
# Handle quadratic terms: coupling[i, j] * zj[i] * zj[j]
# Only iterate over the upper triangle (i < j)
sk_problem = {
str((i, j)): float(couplings[i, j])
for i in range(n)
for j in range(i + 1, n)
if couplings[i, j] != 0
}
# Handle linear terms: local_fields[i] * zj[i]
sk_problem.update(
{
str((i,)): float(local_fields[i])
for i in range(n)
if local_fields[i] != 0
}
)
return sk_problem
استخدم الدالة _generate_couplings_sk_model لحساب حدود الاقتران العشوائية في نموذج SK. لمزيد من التحكم في الاقترانات، يمكنك استخدام وسائط اختيارية موضحة في توثيق الدالة (docstring).
def _generate_couplings_sk_model(
n: int,
coupling_mean: float = 0.0,
coupling_std: float = 1.0,
local_fields_mean: float = 0.0,
local_fields_std: float = 0.0,
edge_density: float = 1.0,
ensure_extensivity: bool = False,
seed: int | None = None,
) -> tuple[np.ndarray, np.ndarray]:
"""Generate random couplings and local fields for an Ising / SK model.
Couplings are Gaussian. With ``ensure_extensivity=True`` they follow the
Sherrington-Kirkpatrick scaling ``J_ij ~ N(coupling_mean/n, coupling_std^2/n)``
(extensive energy, O(n)); otherwise ``J_ij ~ N(coupling_mean, coupling_std^2)``
(energy O(n^2)). Fields are ``h_i ~ N(local_fields_mean, local_fields_std^2)``.
``edge_density`` sets the fraction of the ``n*(n-1)/2`` possible couplings that
are non-zero (1 = fully dense). The kept edges always include a random spanning
tree, so the interaction graph is guaranteed connected. This requires at least
``n-1`` edges, so ``edge_density`` must be at least ``2/n``.
Args:
n: Number of spins (>= 2).
coupling_mean: Mean coupling before optional SK scaling.
coupling_std: Coupling std before optional SK scaling.
local_fields_mean: Mean longitudinal field.
local_fields_std: Std of the longitudinal fields.
edge_density: Fraction of non-zero couplings, in ``[2/n, 1]``.
ensure_extensivity: Whether to apply the SK 1/n scaling.
seed: random number generator seed.
Returns:
Tuple ``(couplings, fields)``: a symmetric ``(n, n)`` matrix with zero
diagonal, and an ``(n,)`` field vector.
Raises:
ValueError: If ``n < 2``, ``coupling_std < 0``, ``local_fields_std < 0``,
or ``edge_density`` is outside ``[2/n, 1]``.
"""
if n < 2:
raise ValueError(f"n must be >= 2, got {n}")
if coupling_std < 0 or local_fields_std < 0:
raise ValueError(
"coupling_std and local_fields_std must be non-negative"
)
# A connected graph on n nodes needs at least n-1 of the n*(n-1)/2 possible
# edges, so edge_density has a hard lower bound of 2/n.
min_edge_density = 2.0 / n
if not min_edge_density <= edge_density <= 1.0:
raise ValueError(
f"edge_density must be in [{min_edge_density:.4g}, 1] for n={n} "
f"(at least n-1 edges are needed to keep the graph connected), "
f"got {edge_density}"
)
rng = np.random.default_rng(seed)
j_loc, j_scale = (
(coupling_mean / n, coupling_std / np.sqrt(n))
if ensure_extensivity
else (coupling_mean, coupling_std)
)
upper_idx = np.triu_indices(n, k=1)
n_edges = len(upper_idx[0])
# Select which edges are present.
if edge_density < 1.0:
n_keep = int(round(edge_density * n_edges))
# Map each (i, j) node pair to its position in the flat upper-triangle list.
pair_to_flat = {
(int(i), int(j)): idx
for idx, (i, j) in enumerate(
zip(upper_idx[0], upper_idx[1], strict=False)
)
}
# Random spanning tree: node perm[k] links to a random earlier node.
perm = rng.permutation(n)
keep = np.zeros(n_edges, dtype=bool)
for k in range(1, n):
child, parent = perm[k], perm[rng.integers(0, k)]
i, j = min(child, parent), max(child, parent)
keep[pair_to_flat[(int(i), int(j))]] = True
# Fill the remaining budget with random non-tree edges.
remaining = n_keep - (n - 1)
if remaining > 0:
keep[
rng.choice(
np.flatnonzero(~keep), size=remaining, replace=False
)
] = True
else:
keep = np.ones(n_edges, dtype=bool)
n_present = int(keep.sum())
if j_scale == 0.0:
vals = np.full(n_present, j_loc)
else:
vals = rng.normal(loc=j_loc, scale=j_scale, size=n_present)
couplings = np.zeros((n, n))
couplings[upper_idx[0][keep], upper_idx[1][keep]] = vals
couplings += couplings.T # symmetrize; diagonal stays zero
fields = (
np.full(n, local_fields_mean)
if local_fields_std == 0.0
else rng.normal(loc=local_fields_mean, scale=local_fields_std, size=n)
)
return couplings, fields
الخطوة 2: حل المسألة باستخدام Parity Twine Optimizer
باستخدام الدوال أعلاه، يمكنك إعداد مسألة SK وإيجاد حل باستخدام Twine Optimizer وخلفية (backend) IBM Quantum® التي تختارها.
لتشغيل الدالة، اختر خلفية (backend) مناسبة؛ على سبيل المثال، ibm_phoenix.
اختياريًا، استخدم options للتحكم الإضافي في الإرسال:
options = {
"shots": 100000,
"postprocessing_level": 1,
"transpile_only": False,
"job_tags": ["sk"],
}
حيث shots هو عدد صحيح يحدد عدد مرات تنفيذ الدائرة، وpostprocessing_level يحدد ما إذا كانت المعالجة اللاحقة تُطبَّق على النتيجة،
وtranspile_only يحدد ما إذا كانت المسألة تُحوَّل (transpile) فقط إلى دائرة (ولا يتم حلها)، وjob_tags هو التصنيف المستخدم لتحديد المهمة على IBM Quantum Platform.
يُحدَّد حجم نموذج SK بواسطة ، عدد المغازل المتفاعلة. بمجرد اختيار ، تُنشئ الشيفرة أعلاه المسألة لـ n_spins.
شغّل المُحسِّن (optimizer):
n_spins = 50
sk_problem = generate_sk_problem(n_spins)
function_job = function.run(
problem=sk_problem,
variable_type="spin",
backend_name="ibm_phoenix",
options=options,
)
print(f"Job ID: {function_job.job_id}")
تحقق من حالة المهمة:
# Monitor the job status
function_job.status()
استرجع النتائج:
result = function_job.result()
result
النتيجة على الشكل التالي:
{
'solution': {'0': 1, '1': 1, '10': 1, '11': 1, ... },
'objective_value': -240.5425312543882,
'solution_bitstring': '00001101110100100111001110101001101111111011001110',
'metadata': {
'circuit_metrics': {
'depth': 523,
'gate_count': 10118,
'two_qubit_gate_depth': 196,
'two_qubit_gate_count': 2499,
'num_qubits': 50,
'operations': {'sx': 3353, 'rz': 3320, 'cz': 2499, 'delay': 894, 'measure': 50, 'x': 2},
},
'solver_info': {
'variable_mapping': {'0': 0, '1': 1, '10': 2, '11': 3, ... },
'bitstring_distributions': {
'before_postprocessing': {'011101110010110111001110011000': 1, ... },
'after_postprocessing': {'011011110000110101001111011000': 1, ... }
},
'best_parameters': {
'beta': [-0.4602084830507902],
'gamma': [1.8500357096574955]
}
},
'resource_usage': {
'RUNNING: MAPPING': {'CPU_TIME': 290.272},
'RUNNING: OPTIMIZING_FOR_HARDWARE': {'CPU_TIME': 0.494},
'RUNNING: WAITING_FOR_QPU': {'CPU_TIME': 8.775},
'RUNNING: EXECUTING_QPU': {'QPU_TIME': 31.0},
'RUNNING: POST_PROCESSING': {'CPU_TIME': 162.96},
},
}
}
حيث يتوافق قاموس solution مع الكيوبتات المعرّفة في المسألة ويعطي قيم المغازل المُحسَّنة الخاصة بها لهاملتوني نموذج SK.
يمثل هذا التسلسل المحدد للمغازل في الحل الأمثل التهيئة التي تقلل طاقة النظام الكلية إلى أدنى حد بناءً على قوى التفاعل العشوائية المعطاة.
في نموذج SK، يمكن اعتبار هذا أدنى حالة طاقة لنظام مغناطيسي غير منتظم.
metadata يعطي معلومات حول التحويل (transpilation) (عدد بوابات الكيوبتين وعمقها، البوابات المستخدمة، الكيوبتات النشطة) وأوقات تشغيل مختلفة.
الخطوات التالية
- اطلب الوصول إلى الدالة عبر تعبئة هذا النموذج.
- قم بزيارة مرجع API الخاص بدالة Qiskit هذه.
- اقرأ الدليل.
- جرّب الدرس التعليمي لتطبيق Parity Twine Optimizer على مسألة تقسيم السوق.
- راجع المقالة الأولية Connectivity-aware Synthesis of Quantum Algorithms, Drier et al. (2025) على ArXiv.