メインコンテンツへスキップ

TEM関数でキック・イジング・モデルをシミュレートする

AlgorithmiqのTensor-network Error Mitigation(TEM)手法は、古典的な後処理段階で完全に誤り軽減を行うハイブリッド量子古典アルゴリズムです。TEMを使用すると、ユーザーは量子ハードウェアで避けられないノイズによる誤りを軽減した観測量の期待値を、精度とコスト効率を向上させて計算できます。これにより、量子研究者や産業実践者にとって非常に魅力的な選択肢となっています。

このチュートリアルでは、TEMが誤り軽減なしにはアクセスできず、PECやZNEなどの他の誤り軽減手法を使用した場合に大幅に多くの量子リソースが必要となる量子システムのダイナミクスについて、意味のある結果を得られることを示します。

使用量の見積もり:このノートブックはHeron r3デバイスで約10 QPU分を使用します。ランタイムは選択したデバイスによって大幅に異なる場合があります。セクションごとの使用量の見積もりは以下に記載しています。

TEMファンクションで誤り軽減された多体物理実験を実行する

このチュートリアルは次の参考文献に基づいています:L. E. Fischer et al., Nat. Phys. (2026)。この参考文献では、最大91量子ビットの量子ハードウェア上での実際のシミュレーションについて論じています。このチュートリアルでは、より小さな回路サイズで同様のシミュレーションを再現します。

キック・イジング・モデルは通常のイジング・モデルに対応します:

H^I=Jn=0N2Z^nZ^n+1+hn=0N1Z^n\hat{H}_{\text{I}} = J \sum_{n=0}^{N-2} \hat{Z}_n \hat{Z}_{n+1} + h \sum_{n=0}^{N-1} \hat{Z}_n

にトランスバーキックが加えられます:

H^K=bn=0N1X^n\hat{H}_{K} = b \sum_{n=0}^{N-1} \hat{X}_n

目標は、トランスバーキック・イジング・ハミルトニアンの下での状態のダイナミクスをシミュレートすることです。その時間発展はFloquetユニタリ U^KI=eiH^KeiH^I\hat{U}_{\text{KI}} = e^{-i \hat{H}_K} e^{-i \hat{H}_I} で実装できます。進化させる初期状態は、最初の量子ビットが +|+\rangle の状態にあり、他の量子ビットはペアになってベル状態 (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2} に設定されています。

観測したい量は相関関数です。参考論文では、この量が nthn^{th} 量子ビットの X^\hat{X} パウリ演算子として書き直せることを説明しています。 いくつかの物理的時間ステップ tt の後、パウリ演算子 X^n=t\hat{X}_{n=t} の値を計算します。 システムのパラメータによって、この観測量の値は厳密に計算できるか、または近似手法でのみシミュレートできます。具体的には、J=b=π/4|J|=|b|=\pi/4 のとき [cos(2h)]t[\cos(2h)]^t に等しくなります。これはこのチュートリアルの結果をベンチマークするために使用する値です。さらに、与えられた時間ステップ tt において、X^nt\langle\hat{X}_{n\neq t}\rangle はゼロです。これらの値を得るための詳細、およびこれらのパラメータ以外での近似古典シミュレーション結果との比較については、L. E. Fischer et al., Nat. Phys. (2026)を参照してください。

TEMはまず、回路内の2量子ビットゲートの各ユニークな層のノイズを特性評価し、読み出し誤りを特性評価することから始まります。次に、回路が量子機械で実行されます。最後に、テンソルネットワーク誤り軽減がIBM Cloud®上の古典的なリソースで実行され、軽減された値が返されます。この例では、回路に特性評価する2つのユニークな層があります。

セットアップ

前提条件として、必要な依存関係がインストールされていることを確認してください。

%pip install numpy matplotlib qiskit qiskit-ibm-catalog qiskit-ibm-runtime pylatexenc qiskit_qasm3_import
import os
from matplotlib import pyplot as plt
import numpy as np

from qiskit.quantum_info import SparsePauliOp
from qiskit.qasm3 import load

from qiskit_ibm_catalog import QiskitFunctionsCatalog

TEMによる誤り軽減

ここでは、上記のキック・イジング・モデルを実装する回路を提供します。回路は以下のように準備されます。まず、状態準備フェーズがあります。最初の量子ビットは +|+\rangle の状態にあり、他の量子ビットはベル対 (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2} の状態にあります。これに続いて、ユニタリ発展 U^KI\hat{U}_{\text{KI}} を実装するブリックワーク構造があります。物理的時間ステップの数は t/2t/2 回路層に対応します。 次のコードは、このチュートリアルに必要な2つのQASMファイルをダウンロードします。

# Download required QASM files
import urllib

urllib.request.urlretrieve(
"https://ibm.box.com/shared/static/swy5jtq309b0xpzluzlmsmj908yphes8.qasm",
"ki_30q.qasm",
)
urllib.request.urlretrieve(
"https://ibm.box.com/shared/static/et3gkodonw6gsp2trs43lzaozrdtiu7s.qasm",
"ki_12q.qasm",
)

12量子ビットと6タイムステップを持つ小さなバージョンの回路を視覚化できます:

# Parameters of the kicked Ising model
h = 0.0
num_qubits = 12
t_steps = 6

# Load the circuit for the kicked Ising model
small_circuit = load("ki_12q.qasm")

# Draw the circuit
small_circuit.draw("mpl", scale=0.25, fold=-1)

Output of the previous code cell

次に、観測量 X^n=t\hat{X}_{n=t} を構築します。これはQiskitが使用する順序に合わせたシンプルなパウリ文字列として構築されます:

def xt_observable(n_qubits, t_steps):
pauli_str = "".join(["I" * t_steps, "X", "I" * (n_qubits - t_steps - 1)])
pauli_str = pauli_str[::-1] # Reverse the string to match qiskit order
return SparsePauliOp(data=pauli_str, coeffs=1.0)

12量子ビットの小さな例では、観測量は次のようになります:

# Build the observable for the kicked Ising model
small_observable = xt_observable(n_qubits=12, t_steps=6)
print(small_observable)
SparsePauliOp(['IIIIIXIIIIII'],
coeffs=[1.+0.j])

Qiskit FunctionsはPUBを入力を収集する方法として使用します。この場合、単一の回路と観測量をPUBとして考えましょう:

# Collect the input PUBs, in this case composed of a
# single circuit and observable
pubs = [(small_circuit, [small_observable])]

次に、TEM関数にアクセスします。まず、IBM Cloudへの必要な認証を設定し、利用可能なデバイスからバックエンドを選択します。トークン、利用可能なバックエンド、対応するクラウドリソース名(CRN)は、IBM Quantum Platformダッシュボードでアカウントにログインすることで取得できます。

# Set IBM Quantum credentials and backend configuration
personal_token = os.environ.get(
"QISKIT_IBM_TOKEN", "<API-KEY>"
) # Replace with your personal token or set the environment variable
channel = "ibm_quantum_platform"
crn = "your_crn" # Replace with the Cloud Resource Name (CRN)

# Select the QPU backend
backend_name = "ibm_qpu_name" # Replace with your desired backend's name

Qiskit Functions CatalogからTEM関数を読み込みます:

# Load the TEM function from the Qiskit Functions Catalog
catalog = QiskitFunctionsCatalog(
channel=channel,
token=personal_token,
instance=crn,
)
tem = catalog.load("algorithmiq/tem")

TEMが提供する誤り軽減でキック・イジング回路の実験を実行できます。デフォルト設定を使用すると、TEMはQPUによって異なりますが、約2.5分のQPU実行時間を期待して簡単に実行できます:

tem_job = tem.run(pubs=pubs, backend_name=backend_name)

デフォルトオプションでは、TEM関数は量子コンピューター上で3つのジョブを実行します:ノイズ学習、読み出し軽減、回路サンプリングです。これらのそれぞれで使用されるショット数は、関数に渡されるオプションで変更できます。デフォルトでは、これらのパラメータは軽減された期待値の精度0.05を達成するように設定されています。 ジョブのステータスはIBM Quantum Platformダッシュボードで確認するか、以下で確認できます:

print(tem_job.status())
QUEUED

ステータスが DONE になったら、未加工の結果と軽減された結果を確認できます。以下で定義される tem_evs は要求された観測量の期待値(この場合は1つの観測量 X^n=t\langle \hat X_{n=t}\rangle のみ)であり、tem_std は対応する標準偏差です。

# Get the results of the TEM job
tem_results = tem_job.result()[
0
] # Get the first and only result from the job
tem_evs = tem_results.data.evs[0]
tem_std = tem_results.data.stds[0]

print(f"TEM Result: {tem_evs:.3f} ± {tem_std:.3f}")
TEM Result: 1.031 ± 0.046

また、IBM Quantum Platformの各呼び出しで使用された量子ランタイムを確認するか、Pythonコードから結果メタデータを検査することもできます。

# Get the TEM job runtime
tem_runtime = tem_job.result().metadata["resource_usage"][
"RUNNING: EXECUTING_QPU"
]["QPU_TIME"]

print(f"TEM Runtime: {tem_runtime} seconds")
TEM Runtime: 155.0 seconds

TEMパラメータと高度なオプションのカスタマイズ

TEM関数は、誤り軽減ワークフローをカスタマイズするためのいくつかの高度なオプションを提供します。これらのオプションにより、精度、ショット数、ノイズ学習戦略、その他のパラメータを制御して、実験の要件と利用可能な量子リソースにより適合させることができます。

一般的な高度なオプションは次のとおりです:

  • precision:軽減された期待値の目標精度を指定します。
  • default_shotsprecision の代わりに、測定ジョブで使用されるショット数を指定できます。
  • tem_max_bond_dimension:テンソルネットワークで使用される最大ボンド次元。
  • tem_compression_cutoff:テンソルネットワークに使用されるカットオフ値。
  • ノイズ学習オプション:反復回数や特定のキャリブレーション回路など、ノイズをどのように特性評価するかを設定します。
  • private:回路と実験結果をプライベートにして、ジョブ結果の複数のダウンロードを無効にします。

サポートされているオプションの完全なリストと説明については、TEMドキュメントまたはQiskit Functions Catalogを参照してください。これらのパラメータを調整して、実行時間、リソース使用量、結果精度のバランスを取ることができます。 これらのオプションをTEM関数を実行する際の options 引数に辞書として渡すことができます:

options = {
"default_shots": 10_000,
"tem_max_bond_dimension": 512,
"tem_compression_cutoff": 1e-16,
# This option helps optimizing the measurement
# stage since the observable is strongly biased
# toward the X operator for all the qubits.
"compute_shadows_bias_from_observable": True,
# set to True to keep experiment results private,
# recommended for confidential circuits
"private": False,
}

ノイズ学習器のカスタムオプションも渡すことができます。これらはQiskit RuntimeのNoiseLearnerOptionsで使用される定義に従います:

nl_options = {
"num_randomizations": 32,
"max_layers_to_learn": 2,
"shots_per_randomization": 128,
"layer_pair_depths": [0, 1, 2, 4, 16, 32],
}

# add noise learning options to the overall options
options |= nl_options

これらのカスタムオプションを回路に合わせて調整して実験を再実行します。予想される実行時間は約4 QPU分です。

tem_job_custom = tem.run(
pubs=pubs, backend_name=backend_name, options=options
)

ジョブがプライベートに設定されていない場合、後で結果を取得できます。そのためには、ここで表示されるジョブIDを保存し、tem_job_custom = catalog.get_job_by_id("your-job-id") を使用します。

job_id = tem_job_custom.job_id
print(f"Job ID: {job_id}")
Job ID: 1ba10094-a541-457a-9287-dbd49306d12d
results_custom = tem_job_custom.result()
tem_evs = results_custom[0].data.evs[0]
tem_std = results_custom[0].data.stds[0]

print(f"TEM Result: {tem_evs:.3f} ± {tem_std:.3f}")
TEM Result: 0.956 ± 0.018

結果とメタデータを調べて、実験に対する洞察を得ることができます:

metadata_custom = results_custom[0].metadata

unmitigated_evs = metadata_custom["evs_non_mitigated"][0]
unmitigated_stds = metadata_custom["stds_non_mitigated"][0]
print(f"Unmitigated Result: {unmitigated_evs:.3f} ± {unmitigated_stds:.3f}")

# Exact result for the kicked Ising model from the reference paper
exact_evs = np.cos(2 * h) ** t_steps
print("Exact Result:", exact_evs)
Unmitigated Result: 0.894 ± 0.015
Exact Result: 1.0
# Plot comparing the different expectation values
plt.bar(
["Unmitigated", "TEM"],
[unmitigated_evs, tem_evs],
yerr=[unmitigated_stds, tem_std],
color=["grey", "c"],
)
plt.hlines(y=exact_evs, xmin=-0.5, xmax=1.5, colors="r", linestyles="dashed")
plt.ylabel("Expectation Value")
plt.ylim(0, 1.1)
plt.show()

Output of the previous code cell

最後に、カスタムオプションがQPUと古典的な実行時間に与える影響を確認できます:

# Get the metadata of the TEM job
job_metadata = results_custom.metadata

# Get the runtime of the TEM job
qpu_runtime = job_metadata["resource_usage"]["RUNNING: EXECUTING_QPU"][
"QPU_TIME"
]
classical_runtime = (
job_metadata["resource_usage"]["RUNNING: OPTIMIZING_FOR_HARDWARE"][
"CPU_TIME"
]
+ job_metadata["resource_usage"]["RUNNING: POST_PROCESSING"]["CPU_TIME"]
)

print(f"QPU Runtime: {qpu_runtime} seconds")
print(f"Classical Runtime: {classical_runtime} seconds")
QPU Runtime: 342.0 seconds
Classical Runtime: 107.632604 seconds

TEMを大規模回路にスケールする

大規模な回路は、原理的にはTEM関数で実行できます。しかし、TEMはIBM Cloudランナーで実行されるため、非常に長い実行時間が発生する可能性があることから、古典的なリソースの制限に注意することが重要です。非常に大規模な回路については、qiskit_ibm@algorithmiq.fiのTEMサポートチームに連絡してください。

ここでは、より大きなユーティリティスケールサイズの30量子ビット回路の例を実行し、精度よりも速度のためにTEMパラメータを最適化します。

# Kicked Ising model parameters
n_qubits = 30
t_steps = 15
h = 0.0

# Load the circuit for the kicked Ising model
circuit = load("ki_30q.qasm")

# Build the observable for the kicked Ising model
observable = xt_observable(n_qubits=n_qubits, t_steps=t_steps)

# Collect the input PUBs, in this case composed of a
# single circuit and observable
pubs = [(circuit, [observable])]

パフォーマンス指向のオプションをいくつか定義しましょう:

options = {
"num_randomizations": 32,
"max_layers_to_learn": 2,
"shots_per_randomization": 128,
"layer_pair_depths": [0, 1, 2, 4, 16, 32, 64],
"default_shots": 5_000,
"tem_max_bond_dimension": 128,
"tem_compression_cutoff": 1e-10,
"compute_shadows_bias_from_observable": True,
"private": False,
}

最後に、実験を実行し、結果を取得して視覚化します。これには約3.5 QPU分かかります。

tem_job_large = tem.run(pubs=pubs, backend_name=backend_name, options=options)
job_id = tem_job_large.job_id
print(f"Job ID: {job_id}")
Job ID: 9f3f190f-f4b0-4dcb-bb83-5f71f37d0d77
results_large = tem_job_large.result()
tem_evs = results_large[0].data.evs[0]
tem_std = results_large[0].data.stds[0]

print(f"TEM Result: {tem_evs:.3f} ± {tem_std:.3f}")

# Get the metadata of the TEM job
job_metadata = tem_job_large.result().metadata

# Get the runtime of the TEM job
qpu_runtime = job_metadata["resource_usage"]["RUNNING: EXECUTING_QPU"][
"QPU_TIME"
]
classical_runtime = (
job_metadata["resource_usage"]["RUNNING: OPTIMIZING_FOR_HARDWARE"][
"CPU_TIME"
]
+ job_metadata["resource_usage"]["RUNNING: POST_PROCESSING"]["CPU_TIME"]
)

print(f"QPU Runtime: {qpu_runtime} seconds")
print(f"Classical Runtime: {classical_runtime} seconds")
TEM Result: 0.794 ± 0.026
QPU Runtime: 203.0 seconds
Classical Runtime: 251.71805499999996 seconds
# Plot comparing the different expectation values
metadata_large = results_large[0].metadata
unmitigated_evs = metadata_large["evs_non_mitigated"][0]
unmitigated_stds = metadata_large["stds_non_mitigated"][0]

exact_evs = np.cos(2 * h) ** t_steps

plt.bar(
["Unmitigated", "TEM"],
[unmitigated_evs, tem_evs],
yerr=[unmitigated_stds, tem_std],
color=["grey", "c"],
)
plt.hlines(y=exact_evs, xmin=-0.5, xmax=1.5, colors="r", linestyles="dashed")
plt.ylabel("Expectation Value")
plt.ylim(0, 1.1)
plt.show()

Output of the previous code cell