Rezolvă modelul Sherrington-Kirkpatrick cu ParityQC Parity Twine Optimizer
Estimare de utilizare: 10 secunde pe un procesor Nighthawk r2. (NOTĂ: Aceasta este doar o estimare. Timpul tău de execuție poate varia.)
Rezultate ale învățării
-
Folosește Parity Twine Optimizer pentru a rezolva modelul Sherrington-Kirkpatrick.
-
Află ce opțiuni sunt disponibile pentru Parity Twine Optimizer și ce rezultate sunt generate.
Context
Acest tutorial demonstrează cum se rezolvă modelul Sherrington-Kirkpatrick folosind ParityQC Parity Twine Optimizer.
Oferă cod pentru a formula problema în întregime local, într-o formă care poate interacționa cu Parity Twine Optimizer.
Modelul Sherrington-Kirkpatrick
Modelul Sherrington-Kirkpatrick (SK) este un model fundamental în mecanica statistică, în special în studiul sticlelor de spin (spin glasses). Spre deosebire de modelul Ising standard, unde interacțiunile sunt de obicei limitate la cei mai apropiați vecini, modelul SK este un model cu rază infinită, ceea ce înseamnă că fiecare spin interacționează cu fiecare alt spin din sistem. Acest lucru duce la un peisaj energetic extrem de complex, "accidentat", caracterizat de multe minime locale, care este marca distinctivă a comportamentului de tip sticlă.
Caracteristica principală a modelului SK constă în frustrare. În model, intensitățile de interacțiune dintre spini sunt distribuite aleatoriu între valori pozitive și negative. Acest lucru duce la situații (de exemplu, în aranjamente triunghiulare) în care spinii nu pot fi aranjați pentru a minimiza toate interacțiunile simultan. În modelul SK, deoarece fiecare spin interacționează cu fiecare alt spin, această frustrare este amplificată global, ducând la o rețea de constrângeri conflictuale.
Formulare matematică
Starea sistemului este definită de un set de spini Ising, . Energia unei configurații specifice este dată de Hamiltonian:
unde este intensitatea cuplajului dintre spinul și spinul .
În modelul SK, cuplajele sunt variabile aleatoare independente și identic distribuite. Pentru a asigura că energia rămâne extensivă (proporțională cu ) pe măsură ce , varianța cuplajelor trebuie să scaleze cu numărul de particule:
Starea fundamentală pentru un dat este configurația specifică a spinilor () care minimizează energia. Găsirea stării fundamentale este o problemă de optimizare NP-hard.
Cerințe
Înainte de a începe acest tutorial, asigură-te că sunt instalate următoarele:
-
Qiskit Functions Catalog IBM Client (
pip install qiskit-ibm-catalog) -
Qiskit addon Optimization Mapper (
pip install qiskit_addon_opt_mapper) -
NumPy (
pip install numpy)
De asemenea, ai nevoie de permisiune pentru a accesa funcția ParityQC Twine Optimizer. Pentru a solicita acces, completează acest formular.
Configurare
(Acest cod presupune că ți-ai salvat deja contul în mediul local.)
Mai întâi, importă toate pachetele necesare pentru acest tutorial.
# 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
Încarcă Parity Twine Optimizer din catalogul Qiskit Functions:
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
function = catalog.load("parityqc/parity-twine-optimizer")
Pasul 1: Definește problema ca o funcție obiectiv
În loc să obții problema SK dintr-o bibliotecă, așa cum facem pentru problema Market Split, o formulezi direct.
Funcția generate_sk_problem formulează problema SK direct în formatul de dicționar necesar. Singura intrare necesară este n, numărul de spini din model.
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
Folosește funcția _generate_couplings_sk_model pentru a calcula termenii de cuplaj aleatorii din modelul SK. Pentru mai mult control asupra cuplajelor, poți folosi argumente opționale, care sunt explicate în docstring-ul funcției.
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
Pasul 2: Rezolvă problema folosind Parity Twine Optimizer
Cu funcțiile de mai sus, poți configura problema SK și găsi o soluție folosind Twine Optimizer și un backend IBM Quantum® ales.
Pentru a rula funcția, alege un backend potrivit; de exemplu, ibm_phoenix.
Opțional, folosește options (opțiunile) pentru control suplimentar asupra trimiterii:
options = {
"shots": 100000,
"postprocessing_level": 1,
"transpile_only": False,
"job_tags": ["sk"],
}
unde shots este un număr întreg care specifică numărul de execuții ale circuitului, postprocessing_level determină dacă se aplică post-procesare rezultatului,
transpile_only alege dacă problema este doar transpilată într-un circuit (și nu rezolvată), iar job_tags este eticheta folosită pentru a identifica job-ul pe IBM Quantum Platform.
Dimensiunea modelului SK este definită de , numărul de spini care interacționează. Odată ce alegi , codul de mai sus generează problema pentru n_spins.
Rulează optimizatorul:
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}")
Verifică starea job-ului:
# Monitor the job status
function_job.status()
Preia rezultatele:
result = function_job.result()
result
Rezultatul are forma:
{
'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},
},
}
}
unde dicționarul solution corespunde qubiților definiți în problemă și oferă valorile lor de spin optimizate pentru Hamiltonianul modelului SK.
Această secvență specifică de spini din soluția optimă reprezintă configurația care minimizează energia totală a sistemului pe baza intensităților de interacțiune aleatorii date.
În modelul SK, aceasta poate fi considerată starea de energie minimă a unui sistem magnetic dezordonat.
metadata oferă informații despre transpilare (numărul/adâncimea porților cu doi qubiți, porțile folosite, qubiții activi) și diverși timpi de execuție.
Pașii următori
- Solicită acces la funcție completând acest formular.
- Vizitează referința API pentru această funcție Qiskit.
- Citește ghidul.
- Încearcă tutorialul pentru aplicarea Parity Twine Optimizer la problema Market Split.
- Consultă preprintul ArXiv Connectivity-aware Synthesis of Quantum Algorithms, Drier et al. (2025).