Sari la conținutul principal
Acest proiect este în versiune beta. Ai găsit o eroare? Deschide un issue pe GitHub. Ai o întrebare sau o idee? Întreabă în Discussions — ne-ar plăcea să aflăm părerea ta!
Cum este calitatea traducerii pe această pagină?

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 JijJ_{ij} 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 NN spini Ising, si∈{+1,−1}s_i \in \{+1, -1 \} . Energia unei configurații specifice este dată de Hamiltonian:

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

unde JijJ_{ij} este intensitatea cuplajului dintre spinul ii și spinul jj.

În modelul SK, cuplajele JijJ_{ij} sunt variabile aleatoare independente și identic distribuite. Pentru a asigura că energia rămâne extensivă (proporțională cu NN) pe măsură ce N→∞N \rightarrow \infty, varianța cuplajelor trebuie să scaleze cu numărul de particule:

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

Starea fundamentală pentru un HH dat este configurația specifică a spinilor (s1,s2,...sns_1, s_2, ...s_n) 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 NN, numărul de spini care interacționează. Odată ce alegi NN, 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​

Recomandări