דלג לתוכן הראשי
פרויקט זה בשלב בטא. מצאת באג? פתח issue ב-GitHub. יש שאלה או רעיון? שאל ב-Discussions — נשמח לשמוע ממך!
מה איכות התרגום בדף זה?

פתרון מודל שרינגטון-קירקפטריק באמצעות Parity Twine Optimizer של ParityQC

הערכת שימוש: 10 שניות על מעבד Nighthawk r2. (הערה: זו הערכה בלבד. זמן הריצה שלכם עשוי להשתנות.)

תוצרי למידה​

  • השתמשו ב-Parity Twine Optimizer כדי לפתור את מודל שרינגטון-קירקפטריק.

  • למדו אילו אפשרויות זמינות עבור Parity Twine Optimizer ואילו תוצאות מופקות.

רקע​

מדריך זה מדגים כיצד לפתור את מודל שרינגטון-קירקפטריק באמצעות Parity Twine Optimizer של ParityQC.

הוא מספק קוד לניסוח הבעיה באופן מקומי לחלוטין בצורה שיכולה לתקשר עם Parity Twine Optimizer.

מודל שרינגטון-קירקפטריק​

מודל שרינגטון-קירקפטריק (SK) הוא מודל יסודי במכניקה סטטיסטית, ובפרט בחקר זגוגיות ספין (spin glasses). בניגוד למודל אייזינג הסטנדרטי, שבו האינטראקציות מוגבלות בדרך כלל לשכנים הקרובים ביותר, מודל SK הוא מודל בעל טווח אינסופי, כלומר כל ספין מקיים אינטראקציה עם כל ספין אחר במערכת. הדבר מוביל לנוף אנרגיה מורכב ו"מחוספס" ביותר, המאופיין במינימומים מקומיים רבים — סימן ההיכר של התנהגות זגוגית.

התכונה המרכזית של מודל SK טמונה בתסכול (frustration). במודל, עוצמות האינטראקציה JijJ_{ij} בין ספינים מפוזרות באקראי בין ערכים חיוביים ושליליים. הדבר מוביל למצבים (לדוגמה, בסידורים משולשים) שבהם לא ניתן לסדר את הספינים כך שכל האינטראקציות ימוזערו בו-זמנית. במודל SK, מכיוון שכל ספין מקיים אינטראקציה עם כל ספין אחר, התסכול הזה מוגבר באופן גלובלי, ומוביל לרשת של אילוצים סותרים.

ניסוח מתמטי​

מצב המערכת מוגדר על ידי קבוצה של NN ספיני אייזינג, si∈{+1,−1}s_i \in \{+1, -1 \} . האנרגיה של תצורה ספציפית ניתנת על ידי ההמילטוניאן:

H=−∑1≤i≤j≤NJijsisjH = - \sum_{1 \leq i \leq j \leq N} J_{ij} s_i s_j

כאשר JijJ_{ij} הוא עוצמת הצימוד (coupling) בין ספין ii לספין jj.

במודל SK, הצימודים JijJ_{ij} הם משתנים אקראיים בלתי תלויים ובעלי התפלגות זהה. כדי להבטיח שהאנרגיה תישאר אקסטנסיבית (פרופורציונלית ל-NN) כאשר N→∞N \rightarrow \infty, השונות של הצימודים חייבת להשתנות בהתאם למספר החלקיקים:

Jij∼N(0,J2N)J_{ij} \sim \mathcal{N} \left( 0, \frac{J^2}{N} \right)

מצב היסוד (ground state) עבור HH נתון הוא התצורה הספציפית של הספינים (s1,s2,...sns_1, s_2, ...s_n), הממזערת את האנרגיה. מציאת מצב היסוד היא בעיית אופטימיזציה מסוג NP-hard.

דרישות​

לפני תחילת מדריך זה, ודאו שהפריטים הבאים מותקנים:

  • Qiskit Functions Catalog IBM Client (pip install qiskit-ibm-catalog)

  • תוסף Qiskit Optimization Mapper (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 קובע האם מופעל עיבוד מאוחר (post-processing) על התוצאה, transpile_only קובע האם הבעיה רק מתורגמת (transpiled) למעגל (ולא נפתרת), ו-job_tags הוא התווית לזיהוי העבודה (job) ב-IBM Quantum Platform.

גודל מודל ה-SK מוגדר על ידי NN, מספר הספינים המקיימים אינטראקציה. לאחר שתבחרו NN, הקוד שלמעלה יוצר את הבעיה עבור n_spins.

הריצו את האופטימייזר:

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) (ספירת/עומק שערי שני קיוביטים, שערים בשימוש, קיוביטים פעילים) וזמני ריצה שונים.

הצעדים הבאים​

המלצות