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

AQC + Trotterハミルトニアン・ダイナミクス用のQiskit Function テンプレートをデプロイして実行する

概要

これはハミルトニアン・ダイナミクスのための実験非依存のQiskit Functionテンプレートです。1次元最近接ペアのパウリ・ハミルトニアン、(オプションで)準備された初期状態、および観測量のセットが与えられると、Trotter時間発展、近似量子コンパイル(AQC)回路圧縮、および軽減された実行を行い、各観測量の時系列を返します。セットアップ(PRE)と分析(POST)を入れ替えることで、同じコアで異なる実験を駆動できます。

PRE (セットアップ)FUNCTION (ここでデプロイ)POST (分析)
回路または積状態として状態を準備し、任意でローカルキックを加えるTrotter合成 → AQC圧縮 → statevectorfake、またはruntimeでの実行、O(t)\langle O \rangle(t)を返す中性子散乱のためのS(q,ω)S(q, \omega)、または磁化、輸送、クエンチダイナミクスなど

このテンプレートは、他のアプリケーションテンプレートとともにQiskit Functionテンプレート・リポジトリで公開されています。このノートブックは、それをご自身のQiskit Serverlessアカウントにデプロイします。一度実行すれば、あとはどのノートブックからでも serverless.load("aqc-dynamics-function") を使って関数を呼び出せます。

実際の科学的な例については、この関数を呼び出してKCuF3_3の動的構造因子を計算するAQC + Trotterダイナミクス・サーバーレス・ワークフローによる中性子散乱のシミュレーションを参照してください。このノートブックでは、代わりにデプロイと入力契約について説明します。

要件

開始する前に、このノートブックのカーネル環境に以下が含まれていることを確認してください。

  • Qiskit SDK v2.0以降(pip install qiskit)。

  • Qiskit Serverlessでワークロードをデプロイして実行するQiskit IBM Catalogクライアント(pip install qiskit-ibm-catalog)。

関数自体の科学的依存関係(qiskit-addon-aqc-tensorcotengrustqiskit-aer)は、ローカルにインストールする必要はありません。

テンプレートのソースファイルを取得する

この関数は、Qiskit Serverlessがクラウドで実行する小さなPythonパッケージであるため、そのソースはデプロイ時にアップロードされるローカルファイルとして存在する必要があります。このパッケージは、Qiskit Functionテンプレート・リポジトリで公開されています。

**source_files**をダウンロードしてください

ダウンロードは、リポジトリ内のディレクトリのフルパスにちなんで名付けられた単一のzipファイルです。

qiskit-community qiskit-function-templates main physics aqc_trotter source_files.zip

  1. このノートブックが置かれているディレクトリに解凍します。

  2. 展開されたフォルダーのその長い名前をsource_filesに変更します。

作業ディレクトリは次のようになります。

your-working-directory/
├── function-template-aqc-trotter.ipynb <- this notebook
└── source_files/ <- the renamed folder
├── __init__.py
├── program.py
└── source/
├── __init__.py
├── _serverless.py
├── app_function.py
├── aqc.py
├── build.py
├── execute.py
└── hamiltonian.py

この名前は正確に source_files である必要があります。なぜなら、それがステップ3でアップロードされる working_dir だからです。

program.pyはゲートウェイが呼び出すエントリーポイントです。source/以下のすべては実装であり、ステージごとに分割されています:ハミルトニアンとTrotter合成、AQC圧縮、実行です。以降の例を実行するために編集する必要はありません。ステップ3はディレクトリ全体をアップロードするため、ファイルを変更するたびにこのステップを繰り返してください。

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-catalog

1. 認証

qiskit-ibm-catalogを使用して、APIキー(トークン)とCRN(インスタンス)でQiskitServerlessに認証します。これらはIBM Quantum® Platformのダッシュボードで確認できます。これらの認証情報を使用して、選択した関数をアップロードまたは実行するためにサーバーレスクライアントをローカルにインスタンス化できます。

from qiskit_ibm_catalog import QiskitServerless
serverless = QiskitServerless(channel="ibm_quantum_platform", token="MY_TOKEN", instance="MY_CRN")

オプションでsave_account()を使用して、認証情報をローカル環境に保存できます(IBM Cloud®アカウントを設定するガイドを参照してください)。これにより、認証情報がQiskitRuntimeService.save_account()と同じファイルに書き込まれることに注意してください。

QiskitServerless.save_account(channel="ibm_quantum_platform", token="MY_TOKEN", instance="MY_CRN")

アカウントが保存されている場合、認証するためにトークンを提供する必要はありません。

from qiskit_ibm_catalog import QiskitServerless

# Authenticate to the remote cluster
# In this case, loading a saved account
serverless = QiskitServerless()

# REPLACE WITH YOUR OWN CREDENTIALS or SAVED ACCOUNT
# serverless = QiskitServerless(channel="ibm_quantum_platform", token="MY_TOKEN", instance="MY_CRN")

2. 依存関係を宣言する

管理されたベース・サーバーレスイメージに加えて、関数が必要とするパッケージです。

備考

ゲートウェイは、許可リスト(requirements-dynamic-dependencies.txt)にある名前のみをインストールします。これはパッケージ名で照合され、==で許可されたバージョンに固定されます。それ以外は、推移的に(許可リストにあるパッケージの依存関係として)到着する必要があります。[extras]構文は尊重されます。qiskit-addon-aqc-tensor[quimb-jax]quimbjaxをインストールするものです。cotengrustは、テンソルネットワーク・シミュレーション中のメモリ効率のために必要です。qiskit-aerは、fake Backend(ローカルなノイズありシミュレーション)のために別途リストされています。

DEPENDENCIES = [
"qiskit-addon-aqc-tensor[quimb-jax]==0.3.1",
"qiskit-aer==0.17.2",
"cotengrust==0.2.0",
]

3. 関数を定義してアップロードする

from qiskit_ibm_catalog import QiskitFunction

fn = QiskitFunction(
title="aqc-dynamics-function",
entrypoint="program.py",
working_dir="source_files/",
dependencies=DEPENDENCIES,
)
serverless.upload(fn)
QiskitFunction(aqc-dynamics-function)

4. 登録されたことを確認する

next(p for p in serverless.list() if p.title == "aqc-dynamics-function")
QiskitFunction(aqc-dynamics-function)

関数リファレンス

これは簡単な紹介です。すべてのフィールドは、AQC Dynamicsテンプレート READMEで完全に文書化されています。検証ルールを含む完全な入力表、出力フィールド、実行Backend、およびさらなる実例です。以下は簡略版で、続く例を読むのに十分な内容です。

入力

すべての実行は単一のfn.run(...)呼び出しです。表の最初の3つの入力のみが必須です:hamiltoniant_stepsaqc_segments。それ以降はすべてオプションであり、表示されているデフォルトにフォールバックするため、最小限の呼び出しは3つの引数であり、表の残りはオプトインできる機能です。ハミルトニアンのnum_qubitsがチェーンの長さを設定するため、個別のサイズ入力はありません。

入力デフォルト説明
hamiltonian必須SparsePauliOpとしての1次元最近接ペアのパウリ・ハミルトニアン。文字列はパウリ演算子であるため、暗黙の2分の1の係数はありません。
t_steps必須Trotterステップの合計数。T = t_steps * dtまで発展し、各t_k = k * dtで各観測量を報告します。
aqc_segments必須圧縮計画:{"n_steps": k, "ansatz_steps": m}のリスト。sum(n_steps)ステップが圧縮され、残りは通常のTrotterとして実行されます。
dt0.21つのTrotterステップで進む物理時間。
initial_state|0...0>発展させる準備済みのQuantumCircuit。任意のローカルキックはこの回路に組み込んでください。
observablesサイトごとのZEstimatorV2observables引数として受け入れるもの。出力の各列に1つの観測量。
trotter_options2次のSuzuki{"method": ..., "synthesis_settings": {...}}repstimeは関数が所有します。
aqc_options説明を参照max_bond32)、cutoff1e-8)、autodiff_backend"jax")、fidelity_targetNone)、optimizer_settings(L-BFGS-B、jac=Truemaxiter=300)。
estimator_optionsDD、トワリング、TREXEstimatorV2.options、そのまま渡されます。指定された辞書は、デフォルトにマージされるのではなく、まるごと置き換えます。
transpiler_options{"optimization_level": 3}generate_preset_pass_managerのキーワード引数。実行パスがそれらを所有するため、backendtargetは拒否されます。
backend"runtime""statevector""fake"、または"runtime"
backend_name最も空いているものruntime用のIBM® Backend名、または名前付きのフェイクBackend。
batches1回路をN個のランタイムジョブに分割します。1つのバッチは単一のジョブを送信し、セッションを作成しません。
parallel_simFalseRayを使用して、利用可能なすべてのコアにローカルシミュレーターのパスを展開します。runtimeには影響しません。
return_circuitsFalse観測量の時系列に加えて、結果に論理的なAQC + Trotter回路を返します。

実行Backend

3つのパスはすべて同じコードと同じ軽減設定を共有します。回路が実行される場所のみが異なります。

backend内容認証情報注記
"statevector"正確なStatevectorEstimatorServerlessアカウントのみ正確な参照パス。QPU時間なし。
"fake"QiskitフェイクBackendでのノイズありローカルシミュレーションServerlessアカウントのみ軽減されたruntimeパスの忠実なリハーサル。qiskit-aerが必要です。デフォルトは127-Qubitのfake_sherbrookeです。
"runtime"(デフォルト)実際のQPUに対する軽減されたEstimatorV2ServerlessアカウントとQPUアクセス権を持つインスタンスbackend_nameはオプションです。省略すると最も空いているデバイスが選択されます。

両方のシミュレーターパスもデプロイされた関数を呼び出すため、QPU時間を使用しなくても保存されたServerlessアカウントが必要です。以下の2つの例では、まずstatevectorで同じワークロードを実行し、次にruntimeで実行します。

出力

job.result()はプレーンな辞書を返します。

{
"times": [...], # length t_steps + 1, t_k = k * dt (t=0 is the prepared state)
"expectation_values": [[...]], # shape (n_times, n_observables)
"observable_labels": [...], # for example: ["Z_0", "ZZ_0_1"]
"metadata": {
"n", "t_steps", "dt", "tier",
"aqc_compressed_steps": 5, # total compressed steps (= sum of segment n_steps)
"aqc_segments": [ # per segment: the plan plus its own results
{"n_steps": 3, "ansatz_steps": 1, "steps": [1, 2, 3], "n_params": 133,
"fidelities": {"1": ..., "2": ..., "3": ...}},
{"n_steps": 2, "ansatz_steps": 2, "steps": [4, 5], "n_params": 245,
"fidelities": {"4": ..., "5": ...}},
],
"execution_backend",
"aqc_fidelities": {"1": ..., "2": ...}, # flat per-step fidelity, all compressed steps
"circuit_stats": { # per-step 2q depth and gate count, full Trotter vs AQC
"1": {"full_trotter": {"depth_2q": ..., "num_2q_gates": ...},
"aqc_trotter": {"depth_2q": ..., "num_2q_gates": ...}},
"2": {...},
},
"warnings": [...], # non-fatal notices; for example, a cotengrust fallback
"resource_usage": { # per stage; QPU_TIME is the charged QPU time
"RUNNING: OPTIMIZING_FOR_HARDWARE": {"CPU_TIME": ...},
"RUNNING: WAITING_FOR_QPU": {"CPU_TIME": ...},
"RUNNING: EXECUTING_QPU": {"QPU_TIME": ...},
},
},
# present only when return_circuits=True
"circuits": [QuantumCircuit, ...], # one per evolved step; circuits[i] is at times[i + 1]
}

aqc_fidelitiescircuit_statsは最初に読むべき2つです。これらを合わせると、圧縮が忠実であったかどうか、そして実際にデプス(回路の深さ)を削減できたかどうかがわかります。runtimeでは、resource_usageは課金対象のQPU時間とは別にキュー待ち時間を報告します。拒否された入力は、構造化されたServerlessError(コード4615)で即座に失敗します。

シミュレーターの例

まず、正確なstatevector Backendで関数を実行します。QPU時間を使用せず、デプロイをエンドツーエンドで検証します。ここでのモデルは8-Qubitの横磁場イジング鎖であり、observablesは省略されているため、関数はデフォルトのサイトごとのZZを測定します。

圧縮計画は理解する価値のある入力です。各セグメント{"n_steps": k, "ansatz_steps": m}は、k個の連続するTrotterステップを、mステップのTrotterターゲットから構築されたアンザッツに圧縮し、sum(n_steps)を超えるステップは通常のTrotterとして実行されます。初期の、エンタングルメントの少ないステップは浅い単層アンザッツによく圧縮されます。後の、よりエンタングルメントの多いステップにはより深いものが必要です。

from qiskit.quantum_info import SparsePauliOp

fn = serverless.load("aqc-dynamics-function")

n = 8
H = SparsePauliOp.from_sparse_list(
[("ZZ", [i, i + 1], 1.0) for i in range(n - 1)]
+ [("X", [i], 0.8) for i in range(n)],
num_qubits=n,
)

job = fn.run(
t_steps=8,
aqc_segments=[
{
"n_steps": 4,
"ansatz_steps": 1,
}, # early steps -> shallow 1-layer ansatz
{
"n_steps": 2,
"ansatz_steps": 2,
}, # later steps -> deeper 2-layer ansatz
],
hamiltonian=H,
aqc_options={"max_bond": 32},
backend="statevector",
)
print("job ID:", job.job_id)
job ID: ee1f3793-e995-427d-81d1-5924549beb38

実行を追跡して結果を読む

status()は、粗いジョブのライフサイクルと、関数が実行中に公開するステージごとのサブステータスの両方を報告します。同じステージが、このガイドの後半のハードウェア実行にも適用されます。

QUEUED -> INITIALIZING -> RUNNING: OPTIMIZING_FOR_HARDWARE -> RUNNING: WAITING_FOR_QPU -> RUNNING: EXECUTING_QPU -> RUNNING: POST_PROCESSING -> DONE

status()の値ステージ
RUNNING: OPTIMIZING_FOR_HARDWARE状態準備、Trotter構築、AQC圧縮
RUNNING: WAITING_FOR_QPUQPUでキュー待ち中(runtime Backendのみ)
RUNNING: EXECUTING_QPU回路の実行中(ローカルシミュレーターはこれを直接示します)
RUNNING: POST_PROCESSING結果の辞書を組み立て中

終端状態はDONEERRORCANCELEDです。このstatevector実行にはQPUキューがないため、RUNNING: WAITING_FOR_QPUをスキップします。いつでもjob.logs()を使用して、各ステップで達成されたAQCフィデリティを含む、ステージごとのログを確認できます。

print(job.status()) # re-run until this reports DONE
DONE
import numpy as np

result = job.result()
ev = np.array(result["expectation_values"])

print("observables:", result["observable_labels"])
print("shape:", ev.shape, "-> (n_times, n_observables)")
print("first row (t = 0, the prepared state):", np.round(ev[0], 4))
print("last row (t = t_steps * dt):", np.round(ev[-1], 4))
print(
"AQC fidelities:",
{k: round(v, 4) for k, v in result["metadata"]["aqc_fidelities"].items()},
)

# What the compression bought: 2-qubit depth at the final time step.
stats = result["metadata"]["circuit_stats"][
str(result["metadata"]["t_steps"])
]
print(
"2q depth at the final step:",
stats["full_trotter"]["depth_2q"],
"(full Trotter) ->",
stats["aqc_trotter"]["depth_2q"],
"(AQC + Trotter)",
)
observables: ['Z_0', 'Z_1', 'Z_2', 'Z_3', 'Z_4', 'Z_5', 'Z_6', 'Z_7']
shape: (9, 8) -> (n_times, n_observables)
first row (t = 0, the prepared state): [1. 1. 1. 1. 1. 1. 1. 1.]
last row (t = t_steps * dt): [0.1442 0.2956 0.4686 0.4877 0.4869 0.4686 0.2963 0.1441]
AQC fidelities: {'1': 1.0, '2': 1.0, '3': 1.0, '4': 1.0, '5': 1.0, '6': 0.9999}
2q depth at the final step: 210 (full Trotter) -> 79 (AQC + Trotter)

ハードウェアの例

backend="runtime"を指定した関数呼び出しは、実際のIBM Quantumプロセッサーでトランスパイルと実行を行い、関数に組み込まれたエラー軽減が適用されます:ダイナミカルデカップリング(XY4)、ゲートトワリング、およびトワリング読み出しエラー消去(TREX)です。backend_nameはデバイスを選択します。省略すると、関数は最も空いているものを選択します。

科学的なコードについては何も変わりません。シミュレーターの例と異なるのは、チェーンの長さ、Trotterステップの数、圧縮計画、Backend、および次のセクションで扱う明示的な軽減設定です。

制御ハードウェアに合わせてジョブをサイズ調整する

estimator_optionsは意図的に設定する価値のある入力です。ゲートトワリングは、すべてのPUBに対してnum_randomizations個の別々のランダム化された回路を構築し、ジョブ全体、すべてのランダム化を含むすべてのPUBは、QPUの古典制御システムの命令メモリに収まる必要があります。この関数のデフォルトは1000回のランダム化であるため、10ステップの発展では、それぞれ1000個の回路からなる11個のPUBが送信されます:単一ジョブで約11,000個の回路インスタンスです。

制御システムが保持できる量を超えると、ジョブはエラー6073で失敗します。ジョブの制限には、しきい値とそれらに対するカウント方法が記載されており、主なものはQubitあたり2680万個の制御システム命令で、PUBごとではなくジョブごとに適用されます。ダイナミカルデカップリングは、これにカウントされるゲートを追加します。

2つの入力がサイズを制御します。

  • estimator_optionsはショット予算を設定します。合計ショット数はnum_randomizations * shots_per_randomizationであるため、ランダム化の回数とランダム化あたりのショット数をトレードオフし、統計を維持しながらプログラムを縮小できます。以下のセルでは、それぞれ200ショットで100回のランダム化を使用しており、これは観測量あたり20,000ショットで、デフォルトが送信する回路インスタンスの約10分の1です。全フィールドについては、TwirlingOptionsEstimatorオプションを参照してください。

  • batchesは、PUBをその数の別々のランタイムジョブに分割します。これは、エラー6073自体が示唆する対策であり、ジョブごとの枠組みが重要である理由です。batches=4を設定すると、一度に11個ではなく、ジョブあたり約3つのPUBが送信され、ジョブは1つのバッチとして一緒に送信されるため、グループは各ジョブが個別にキューに入るのではなく、一度だけキューに入ります。

指定されたestimator_optionsは、関数のデフォルトにマージされるのではなく、まるごと置き換えることを覚えておいてください。そのため、ダイナミカルデカップリングとTREXを有効のままにするために、以下のセルで再度指定されています。

from qiskit.quantum_info import SparsePauliOp

fn = serverless.load("aqc-dynamics-function")

n = 10
H = SparsePauliOp.from_sparse_list(
[("ZZ", [i, i + 1], 1.0) for i in range(n - 1)]
+ [("X", [i], 0.8) for i in range(n)],
num_qubits=n,
)

job = fn.run(
t_steps=10,
aqc_segments=[
{
"n_steps": 3,
"ansatz_steps": 1,
}, # early steps -> shallow 1-layer ansatz
{
"n_steps": 3,
"ansatz_steps": 2,
}, # later steps -> deeper 2-layer ansatz
],
hamiltonian=H,
aqc_options={"max_bond": 32},
backend="runtime",
backend_name="ibm_marrakesh",
# The function defaults to 1000 twirling randomizations, which was too large
# for this device. Total shots is num_randomizations *
# shots_per_randomization, so this is 20,000 shots per observable.
estimator_options={
"dynamical_decoupling": {"enable": True, "sequence_type": "XY4"},
"twirling": {
"enable_gates": True,
"num_randomizations": 100,
"shots_per_randomization": 200,
},
"resilience": {"measure_mitigation": True},
},
)
print("job ID (save this to reconnect later):", job.job_id)
job ID (save this to reconnect later): 7229a8bf-9f83-4785-8dd4-489844abc2d9
長時間実行中のジョブへの再接続

ハードウェア実行は速くはなく、ほとんどの時間はQPU上ではなく古典的なものです。AQC圧縮は、何かがQPUに到達する前に関数内で実行され、QPUキューはその上にさらに加わります。実行中にこのノートブックやカーネルを開いたままにしておく必要はありません。

前のセルで印刷されたジョブIDをコピーして保存してください。次の3つのセルで、後で実行状況を再確認できます。

  1. 再接続します(新しいカーネルセッションでのみ必要です):認証セルを再実行してserverlessを再作成し、保存したIDからjobハンドルを再構築します。まだ送信したセッションにいる場合は、ハンドルはすでに有効であるため、このセルはスキップしてください。

  2. ステータスを確認します:DONEと報告されるまで再実行してください。

  3. 結果を取得します: ステータスが DONE になってから一度だけ実行してください。

保存しておいた ID を、以下の再接続セルのプレースホルダーに貼り付けます。

# Reconnect to a previously submitted job by its ID. Only needed in a NEW kernel
# session; if you are still in the session where you submitted, the `job` handle
# from the preceding cell is already live, so skip this cell. Replace the ID that follows with your own.
job = serverless.get_job_by_id("<your job ID>")
# Re-run this until it reports DONE, then fetch the result in the following cell.
print(job.status())
DONE
import numpy as np

# Run this only once the preceding status cell reports DONE. result() blocks until
# the job finishes, so calling it earlier just waits.
result = job.result()
ev = np.array(result["expectation_values"])

print("backend:", result["metadata"]["execution_backend"])
print("shape:", ev.shape, "-> (n_times, n_observables)")
print("last row (t = t_steps * dt):", np.round(ev[-1], 4))
print(
"AQC fidelities:",
{k: round(v, 4) for k, v in result["metadata"]["aqc_fidelities"].items()},
)

# What the compression bought: 2-qubit depth at the final time step.
stats = result["metadata"]["circuit_stats"][
str(result["metadata"]["t_steps"])
]
print(
"2q depth at the final step:",
stats["full_trotter"]["depth_2q"],
"(full Trotter) ->",
stats["aqc_trotter"]["depth_2q"],
"(AQC + Trotter)",
)
backend: runtime
shape: (11, 10) -> (n_times, n_observables)
last row (t = t_steps * dt): [0.1504 0.1361 0.218 0.2144 0.2275 0.1783 0.1749 0.1599 0.0915 0.0922]
AQC fidelities: {'1': 1.0, '2': 1.0, '3': 1.0, '4': 1.0, '5': 0.9999, '6': 0.9999}
2q depth at the final step: 342 (full Trotter) -> 171 (AQC + Trotter)

次のステップ

推奨事項