ParityQC の「Parity Twine Optimizer」を使用して、シェリントン・カークパトリックモデルを解く
推定実行時間:Nighthawk r2 プロセッサで10秒。 (注:これはあくまで概算です。 (実行時間は状況によって異なる場合があります。)
学習成果
- Parity Twine Optimizer を使用して、シェリントン・カークパトリック・モデルを解きます。
- Parity Twine Optimizer で利用可能なオプションと、どのような結果が出力されるかを確認します。
背景
このチュートリアルでは、 ParityQC の「Parity Twine Optimizer」を使用して、シェリントン・カークパトリックモデルを解く方法について解説します。
このコードは、問題を完全にローカルな範囲で定式化し、Parity Twine Optimizer と連携可能な形式にするためのものです。
シェリントン・カークパトリック・モデル
シェリントン・カークパトリック(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 :func:`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:
A ``ProblemRepresentation`` 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, fieldsステップ 2:パリティ・トワイン・オプティマイザーを使用して問題を解く
上記の機能を使えば、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リファレンスをご覧ください。
- ガイドをお読みください。
- 「Market Split」問題にParity Twine Optimizerを適用する方法について、 チュートリアルを試してみてください。
- Drier ら(2025)による 「接続性を考慮した量子アルゴリズムの合成」 ArXiv のプレプリントを参照のこと。