ParityQC 의 Parity Twine Optimizer를 사용하여 셔링턴-커크패트릭 모델을 풀기
예상 실행 시간: Nighthawk r2 프로세서에서 10초. (참고: 이는 단지 추정치일 뿐입니다.) (실행 시간은 다를 수 있습니다.)
학습 성과
- Parity Twine Optimizer를 사용하여 Sherrington-Kirkpatrick 모델을 해결하십시오.
- Parity Twine Optimizer에서 사용할 수 있는 옵션과 출력되는 결과가 무엇인지 알아보세요.
배경
이 튜토리얼에서는 ‘ ParityQC ’의 Parity Twine 최적화기를 사용하여 Sherrington-Kirkpatrick 모델을 해결하는 방법을 설명합니다.
이 코드는 문제를 Parity Twine 최적화 도구와 연동할 수 있는 형태로, 전적으로 로컬에서 정의할 수 있도록 해줍니다.
셰링턴-커크패트릭 모델
셰링턴-커크패트릭(SK) 모델은 통계역학, 특히 스핀 글라스 연구 분야에서 기초가 되는 모델입니다. 상호작용이 대개 가장 가까운 이웃으로만 제한되는 표준 이징 모델과 달리, SK 모델은 무한 범위 모델로, 시스템 내의 모든 스핀이 다른 모든 스핀과 상호작용함을 의미한다. 이로 인해 수많은 국부적 최소값을 특징으로 하는 매우 복잡하고 “거친” 에너지 지형이 형성되는데, 이는 유리질 거동의 대표적인 특징이다.
SK 모델의 핵심 특징은 좌절감에 있다. 이 모델에서, 스핀 간의 상호작용 강도 는 양의 값과 음의 값 사이에서 무작위로 분포한다. 이로 인해 ( 예를 들어, 삼각 배열의 경우처럼) 모든 상호작용을 동시에 최소화하도록 스핀을 배열할 수 없는 상황이 발생한다. SK 모델에서는 모든 스핀이 다른 모든 스핀과 상호작용하기 때문에, 이러한 좌절 현상이 전체적으로 가중되어 상충되는 제약 조건들의 그물망이 형성된다.
수학적 공식화
시스템의 상태는 개의 이징 스핀 집합, 으로 정의된다. 특정 구성의 에너지는 다음 해밀토니안으로 주어진다:
여기서 는 스핀 과 스핀 사이의 결합 강도이다.
SK 모델에서 결합 상수 는 서로 독립적이고 동일하게 분포된 확률변수이다. 에너지가 일 때 에 비례하는 광범위한 값을 유지하도록 하려면, 결합 상수의 분산은 입자 수에 비례해야 한다:
주어진 자기 쌍극자( )의 기저 상태는 에너지를 최소화하는 특정 스핀 배열( )이다. 기저 상태를 찾는 것은 NP-어려운 최적화 문제입니다.
요구사항
이 튜토리얼을 시작하기 전에 다음 항목이 설치되어 있는지 확인하십시오:
- Qiskit Functions Catalog IBM 클라이언트 (
pip install qiskit-ibm-catalog) - Qiskit 애드온 Optimization Mapper (
pip install qiskit_addon_opt_mapper) - NumPy (
pip install numpy)
또한 ParityQC 의 Twine Optimizer 기능에 접근하려면 권한이 필요합니다. 접근 권한을 요청하려면 이 양식을 작성해 주십시오.
설정
(이 코드는 사용자가 이미 계정을 로컬 환경에 저장해 두었다고 가정합니다.)
먼저, 이 튜토리얼에 필요한 모든 패키지를 가져옵니다.
import numpy as np
from qiskit_ibm_catalog import QiskitFunctionsCatalogQiskit Functions 카탈로그에서 Parity Twine Optimizer를 불러오세요:
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_problemSK 모델에서 무작위 결합 항을 계산하려면 _generate_couplings_sk_model 이 함수를 사용하십시오. 커플링을 더 세밀하게 제어하려면, 함수의 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, fields2단계: Parity Twine Optimizer를 사용하여 문제를 해결합니다
위의 기능을 활용하면 SK 문제를 설정하고, Twine Optimizer와 선택한 IBM Quantum® 백엔드를 사용하여 해를 구할 수 있습니다.
함수를 실행하려면 적절한 백엔드를 선택하십시오. 예를 들어, ibm_phoenix와 같이 선택할 수 있습니다.
선택적으로, 제출을 더 세밀하게 제어하기 위한 옵션을 사용할 수 있습니다:
options = {
"shots": 100000,
"postprocessing_level": 1,
"transpile_only": False,
"job_tags": ["sk"],
}여기서 shots 는 회로 실행 횟수를 나타내는 정수이며, 는 결과에 후처리가 적용될지 여부를 결정하고 postprocessing_level ,
transpile_only 는 문제가 회로로만 변환될지(해결되지 않을지) 여부를 선택하며, 는 IBM Quantum Platform 에서 해당 작업을 식별하기 위한 라벨입니다 job_tags .
SK 모델의 크기는 상호작용하는 스핀의 수인 로 정의된다. 를 선택하면, 위의 코드는 해당 항목에 대한 문제를 생성합니다 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 트랜스파일레이션(2 큐비트 게이트 수/깊이, 사용된 게이트, 활성 큐비트) 및 다양한 실행 시간에 대한 정보를 제공합니다.
다음 단계
- 이 양식을 작성하여 해당 기능에 대한 접근 권한을 요청해 주십시오.
- 이 Qiskit 함수에 대한 API 참조 를 확인해 보세요.
- 가이드를 읽어보세요.
- 마켓 분할 문제에 패리티 트와인 최적화기(Parity Twine Optimizer)를 적용하는 방법에 대한 튜토리얼 을 살펴보세요.
- Drier 외 (2025)의 “연결성을 고려한 양자 알고리즘 합성(Connectivity-aware Synthesis of Quantum Algorithms)”( ArXiv ) 프리프린트를 검토하십시오.