ข้ามไปยังเนื้อหาหลัก
โครงการนี้อยู่ในช่วงเบต้า พบข้อผิดพลาด? สร้าง GitHub Issue มีคำถามหรือไอเดีย? ถามใน Discussions — ยินดีรับฟังความคิดเห็น!
คุณภาพการแปลในหน้านี้เป็นอย่างไร?

แก้โมเดล Sherrington-Kirkpatrick ด้วย ParityQC Parity Twine Optimizer

ประมาณการเวลาใช้งาน: 10 วินาที บนโปรเซสเซอร์ Nighthawk r2 (หมายเหตุ: นี่เป็นเพียงการประมาณการเท่านั้น เวลาที่ใช้จริงอาจแตกต่างกันไป)

ผลลัพธ์การเรียนรู้​

  • ใช้ Parity Twine Optimizer เพื่อแก้โมเดล Sherrington-Kirkpatrick

  • เรียนรู้ว่ามีตัวเลือกใดบ้างสำหรับ Parity Twine Optimizer และผลลัพธ์ที่ได้คืออะไร

ความเป็นมา​

บทเรียนนี้สาธิตวิธีการแก้โมเดล Sherrington-Kirkpatrick โดยใช้ ParityQC Parity Twine Optimizer

โดยมีโค้ดสำหรับกำหนดปัญหาบนเครื่องของคุณเองทั้งหมด ในรูปแบบที่ส่งต่อให้ Parity Twine Optimizer ได้

โมเดล Sherrington-Kirkpatrick​

โมเดล Sherrington-Kirkpatrick (SK) เป็นโมเดลพื้นฐานในกลศาสตร์สถิติ โดยเฉพาะในการศึกษาเรื่อง spin glass ต่างจากโมเดล Ising มาตรฐานที่การปฏิสัมพันธ์มักจำกัดอยู่เฉพาะเพื่อนบ้านที่ใกล้ที่สุด โมเดล SK เป็นโมเดลที่มีระยะปฏิสัมพันธ์ไม่จำกัด กล่าวคือ spin ทุกตัวมีปฏิสัมพันธ์กับ spin อื่นทุกตัวในระบบ ซึ่งนำไปสู่ภูมิทัศน์พลังงานที่ซับซ้อนสูงและ "ขรุขระ" ซึ่งมีค่าต่ำสุดเฉพาะที่จำนวนมาก อันเป็นลักษณะเฉพาะของพฤติกรรมแบบ glassy

คุณลักษณะหลักของโมเดล SK อยู่ที่ frustration ในโมเดลนี้ ความแรงของปฏิสัมพันธ์ JijJ_{ij} ระหว่าง spin จะกระจายแบบสุ่มระหว่างค่าบวกและค่าลบ ซึ่งนำไปสู่สถานการณ์ (เช่น ในการจัดเรียงแบบสามเหลี่ยม) ที่ spin ไม่สามารถจัดเรียงให้ลดปฏิสัมพันธ์ทั้งหมดพร้อมกันได้ ในโมเดล SK เนื่องจาก spin ทุกตัวมีปฏิสัมพันธ์กับ spin อื่นทุกตัว frustration นี้จึงทวีความรุนแรงขึ้นทั่วทั้งระบบ นำไปสู่ข้อจำกัดที่ขัดแย้งกันเป็นโครงข่าย

การกำหนดสูตรทางคณิตศาสตร์​

สถานะของระบบถูกกำหนดโดยชุดของ NN Ising spin, si∈{+1,−1}s_i \in \{+1, -1 \} พลังงานของการจัดเรียงที่กำหนดจะได้มาจาก Hamiltonian:

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

โดยที่ JijJ_{ij} คือค่าคู่ควบระหว่าง spin ii และ spin jj

ในโมเดล SK ค่าคู่ควบ JijJ_{ij} เป็นตัวแปรสุ่มที่เป็นอิสระต่อกันและมีการกระจายเหมือนกัน เพื่อให้แน่ใจว่าพลังงานยังคงเป็น extensive (แปรผันตาม NN) เมื่อ N→∞N \rightarrow \infty ความแปรปรวนของค่าคู่ควบจะต้องปรับตามจำนวนอนุภาค:

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

สถานะพื้นสำหรับ HH ที่กำหนดคือการจัดเรียง spin (s1,s2,...sns_1, s_2, ...s_n) เฉพาะที่ทำให้พลังงานต่ำที่สุด การหาสถานะพื้นเป็นปัญหาการหาค่าที่เหมาะสมที่สุดแบบ NP-hard

ข้อกำหนดเบื้องต้น​

ก่อนเริ่มบทเรียนนี้ ตรวจสอบให้แน่ใจว่าได้ติดตั้งสิ่งต่อไปนี้แล้ว:

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

  • Qiskit addon 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:

catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
function = catalog.load("parityqc/parity-twine-optimizer")

ขั้นตอนที่ 1: กำหนดปัญหาเป็นฟังก์ชันวัตถุประสงค์​

แทนที่จะรับปัญหา SK จากไลบรารีเหมือนที่เราทำสำหรับปัญหา Market Split คุณจะกำหนดปัญหานี้โดยตรง

ฟังก์ชัน generate_sk_problem กำหนดปัญหา SK โดยตรงในรูปแบบ dictionary ที่ต้องการ อินพุตเดียวที่จำเป็นคือ n ซึ่งเป็นจำนวน spin ในโมเดล

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 เลือกว่าปัญหาจะถูก transpile เป็นวงจรเท่านั้น (โดยไม่แก้ปัญหา) หรือไม่ และ job_tags เป็นป้ายกำกับที่ใช้ระบุ job บน IBM Quantum Platform

ขนาดของโมเดล SK ถูกกำหนดโดย NN ซึ่งเป็นจำนวน spin ที่มีปฏิสัมพันธ์กัน เมื่อคุณเลือก NN แล้ว โค้ดข้างต้นจะสร้างปัญหาสำหรับ 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}")

ตรวจสอบสถานะของ job:

# 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},
},
}
}

โดยที่ dictionary solution สอดคล้องกับ qubit ที่กำหนดไว้ในปัญหา และให้ค่า spin ที่ผ่านการปรับให้เหมาะสมแล้วสำหรับ Hamiltonian ของโมเดล SK ลำดับของ spin เฉพาะนี้ในคำตอบที่เหมาะสมที่สุดแสดงถึงการจัดเรียงที่ทำให้พลังงานรวมของระบบต่ำที่สุด โดยอิงจากความแรงของปฏิสัมพันธ์แบบสุ่มที่กำหนด ในโมเดล SK สิ่งนี้สามารถถือได้ว่าเป็นสถานะพลังงานต่ำที่สุดของระบบแม่เหล็กที่ไม่เป็นระเบียบ

metadata ให้ข้อมูลเกี่ยวกับการ transpile (จำนวน/ความลึกของ two qubit gate, gate ที่ใช้, qubit ที่ใช้งาน) และเวลาการทำงานต่างๆ

ขั้นตอนถัดไป​

คำแนะนำ