Sampler から Executor への移行
このガイドでは、量子サンプリング・ワークロードを IBM Quantum® Sampler プリミティブから Executor プリミティブに移行する方法を説明します。
Executor プリミティブは、有向実行モデル の一部です。有向実行モデルのすべてのコンポーネントは現在ベータ版であり、安定していない可能性があります。Samplomatic または qiskit-ibm-runtime の GitHub リポジトリで課題を作成し、テストとフィードバックの提供にご協力ください。
移行すべきか?
誰もが Sampler から Executor に移行すべきというわけではありません。これらのプリミティブには多くの違いがありますが、次のガイダンスは移行するかどうかを判断する際に役立ちます。
ユーティリティ・スケールの実験を実行し、パウリ・ツイリング、ノイズ・モデルの学習と注入、基底変換などの手法について、きめ細かく再現性のある制御が必要な量子情報科学者、または Executor が提供する追加機能のいずれかが必要な場合は、Executor に移行してください。
シンプルで高水準なインターフェースを望み、エラー抑制と緩和をプリミティブに管理させたい場合は、引き続き Sampler を使用してください。
制限事項と注意点
Executor と有向実行モデルはベータ版であるため、移行を決定する前に、次の点に注意してください。
-
シミュレーター未対応: ローカル・シミュレーション用に
qiskit-aerにAerSampler実装を持つ Sampler とは異なり、Executor には現在シミュレーター Backend がありません。シミュレーターのサポートは近日中に提供される予定です。それまでの間、ハードウェアに送信する前に、テンプレート回路をローカルでサンプリング してワークフローを検証することができます。 -
このガイドは Sampler のみを対象とし、Estimator は対象としません。 Estimator は生のサンプルを返すのではなく期待値を計算するため、Estimator から Executor への移行は Sampler からの移行よりもかなり複雑です。Executor で Estimator の動作を再現するには、追加の後処理が必要です。Estimator から Executor への移行を支援するユーティリティ関数はまだ開発中であるため、このガイドでは意図的に Sampler のワークフローのみを説明しています。
Executor と Sampler の主な違い
Sampler と Executor はどちらも量子回路の出力レジスターをサンプリングしますが、対象とするユーザーが異なります。
-
Sampler は高水準の抽象化です。次のような特徴があります。
-
組み込みのエラー抑制機能(ダイナミカル・デカップリングとツイリング)があります。
-
暗黙的な決定を代わりに行います。
-
アルゴリズム開発者がデータ変換ではなくイノベーションに集中できるように設計されています。
-
-
Executor は 有向実行モデル の一部です。Sampler とは多くの点で異なり、次のような特徴があります。
-
組み込みのエラー抑制や緩和機能はありません。代わりに、クライアント側で(回路アノテーションと samplex を使用して)設計意図を捉え、コストのかかる回路バリアントの生成はサーバー側に移されます。
-
暗黙的な決定は行いません。指示どおりに正確に従うため、完全な制御性と透明性が得られます。
-
Executor と Samplomatic を組み合わせることで、Sampler にはない次のような追加機能が提供されます(ただしこれらに限定されません)。
- より多くのツイリング・グループ: Samplomatic では、Sampler が代わりに適用する単一の戦略に限定されることなく、ボックスごとに適用するツイリング・グループを選択できます。また、
"local_c1"ツイリング・グループなど、パウリ以外のツイリング・グループもサポートしています。 - カーネル化測定と分類済み測定の併用:
QuantumProgram.meas_level = "both"(qiskit-ibm-runtimev0.48.0 で追加)を設定すると、ジョブごとに単一の測定タイプを選択する代わりに、分類済みとカーネル化の両方の測定を結果に含めるよう要求できます。 - フラクショナル・ゲートを含む回路のツイリング: Executor は、フラクショナル・ゲートを含む回路にツイリングを適用できます。
- きめ細かく組み合わせ可能なエラー緩和: 例えば、緩和する回路レイヤーの選択や、回路に注入するノイズ・レートの調整などです。
注記- 今後の新機能は Executor に先行してリリースされ、Sampler には移植されない可能性があります。最新機能へのアクセスに依存する場合は、Executor がより将来性のある選択肢です。
- Qiskit の基本パッケージは、Executor プリミティブの基底クラスをまだ提供していません(
SamplerV2には提供されています)。
- より多くのツイリング・グループ: Samplomatic では、Sampler が代わりに適用する単一の戦略に限定されることなく、ボックスごとに適用するツイリング・グループを選択できます。また、
-
概念のマッピング
次の表は、Sampler の概念が Executor にどのようにマッピングされるかを示しています。
| 概念 | Sampler | Executor |
|---|---|---|
| インポート | from qiskit_ibm_runtime import SamplerV2 | from qiskit_ibm_runtime import Executor |
| 入力 | PUB のリスト(タプル) | QuantumProgramItem オブジェクトの QuantumProgram |
| 回路とパラメーター | (circuit, params, shots) タプル | program.append_circuit_item(circuit, circuit_arguments=...) |
| ツイリング | TwirlingOptions | アノテーション付きボックスと samplex による明示的な指定(append_samplex_item) |
| 実行呼び出し | sampler.run([pub, ...]) | executor.run(program) |
| 結果の型 | SamplerPubResult の PrimitiveResult | QuantumProgramResult(反復可能) |
| データへのアクセス | result[0].data.<register>(BitArray) | result[0]["<register>"](np.ndarray) |
| ノイズの管理 | 組み込みオプション | 手動で構成する必要がある(アノテーション、samplex、NoiseLearnerV3) |
移行手順の概要
ステップ1. 必要なパッケージのインストール
Executor と有向実行モデルには samplomatic パッケージが必要です。
pip install qiskit qiskit-ibm-runtime samplomatic
# For visualization support:
# pip install samplomatic[vis]
qiskit-ibm-runtimev0.48.0 は、meas_level = "both"オプションとlocal_c1ツイリング・グループを追加しているため推奨されます。qiskit >= 2.3.0が必要です。samplomatic >= 0.18.0が必要です。
ステップ2. インポートの変更
Sampler:
from qiskit_ibm_runtime import SamplerV2 as Sampler
Executor:
from qiskit_ibm_runtime import Executor, QuantumProgram
ステップ3. PUB タプルを QuantumProgram に置き換える
Executor を使用する場合、タプル(PUB)のリストを渡す代わりに、QuantumProgram を構築し、そこに アイテム を追加します。
QuantumProgram は、circuit アイテムと samplex アイテムを受け付けます。
-
append_circuit_item: 回路と(オプションで)そのパラメーター値であるCircuitItemを追加します。これはランダム化されることなく、そのまま実行されます。Sampler がツイリングを行わない PUB で行うのとまったく同じように、単純に回路をサンプリングしたい場合に使用します。例えば、単純なサンプリング・ジョブを送信する場合や、必要なバリアントをすでに手動で含めている場合などです。
-
append_samplex_item: テンプレート回路 と、サーバー側でランダム化されたパラメーター・セットを生成する samplex から成るsamplexItemを追加します。回路の内容をランダム化したい場合に使用します。主な用途は、(ゲートまたは測定の)ツイリングやノイズの注入です。この機能は Sampler の組み込みツイリングを置き換えるものです。
1つの QuantumProgram は両方のアイテム・タイプを受け付けることができます。追加された各アイテムは独立したタスクとして実行され、結果に独自のエントリーを生成します。一般に、回路をランダム化する必要がない場合は append_circuit_item を使用します。それ以外の場合は append_samplex_item を使用してください。
次のセクションでは、append_circuit_item を使用するパラメーター化された回路と、append_samplex_item を使用したツイリングの移行について、それぞれ順に説明します。
以下のコード例では、isa_circuit は、対象の Backend の 命令セット・アーキテクチャー(ISA)に準拠するようトランスパイルされた回路を指します。この isa_circuit には2つのパラメーターが含まれています。
ステップ3a. パラメーター化された回路の移行
Sampler では、パラメーター値は PUB タプルの2番目の要素です。Executor では、これらを circuit_arguments として append_circuit_item に渡します。
Sampler:
params = np.random.rand(10, circuit.num_parameters) # 10 parameter sets
pubs = (isa_circuit, params)
Executor
program = QuantumProgram(shots=1024)
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(10, circuit.num_parameters), # 10 sets
)
# CircuitItem result shape: (parameter_sets, shots, register_bits) -> (10, 1024, 2)
result_0 = result[0]["meas"]
ステップ3b. 組み込みツイリングを明示的なアノテーションに移行する
これが最も重要な変更点です。Sampler はオプションを使用してツイリングを自動的に適用します。Executor では、注釈付きボックスと samplex(Samplomatic から)を使用して、その意図を明示的に宣言します。
Sampler(オプションを使用したツイリング):
sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = True
Executor(ボックスと samplex を使用したツイリング):
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager
# 1. Group gates and measurements into annotated boxes with twirling annotations
boxes_pm = generate_boxing_pass_manager(
enable_gates=True, # gate twirling
enable_measures=True, # measurement twirling
)
boxed_circuit = boxes_pm.run(isa_circuit)
# 2. Build the (template circuit, samplex) pair.
# The template circuit's single-qubit gates are replaced by parameterized gates;
# the samplex encodes how to generate the randomized parameters at runtime.
template_circuit, samplex = build(boxed_circuit)
# 3. Append as a samplex item, specifying the number of randomizations
program = QuantumProgram(shots=1024)
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"parameter_values": np.random.rand(10, 2), # original circuit params
},
shape=(28, 10), # 28 randomizations x 10 parameter sets
)
テンプレート Circuit と samplex はクライアント側で構築されるため、ハードウェアに何かを送信する前に、ローカルで検査・サンプリングして出力を確認できます。
検証:テンプレート Circuit をローカルでサンプリングする
samplex からランダム化をドローし、それをテンプレート Circuit にバインドすることで、samplex が期待どおりのパラメーター値を生成していることを確認できます。samplex.sample によって返されるパラメーター値は、テンプレート Circuit のパラメーターと直接互換性があります。
# Check which inputs the samplex requires (for the twirling example above,
# this is just the original circuit's parameter values).
print(samplex.inputs())
# Bind the required inputs, then draw a few randomizations locally.
inputs = samplex.inputs().bind(
parameter_values=np.random.rand(2), # one set of the original circuit's params
)
outputs = samplex.sample(inputs, num_randomizations=3)
# Assign one randomization's parameter values to the template circuit and inspect it.
bound_template = template_circuit.assign_parameters(outputs["parameter_values"][0])
bound_template.draw("mpl", idle_wires=False)
さらに検証を進めるには、たとえば両方を Operator オブジェクトに変換してユニタリー実装を比較する(測定ツイリングを打ち消す outputs["measurement_flips.<register>"] 補正を考慮した上で)か、ローカルの StatevectorSampler または StatevectorEstimator の実行から得られる期待値を比較することで、各ランダム化が元の Circuit と論理的に等価であることを検証できます。完全な手順については、Samplomatic の Samplex inputs and outputs ガイドを参照してください。
ステップ4. ショットのリクエスト方法を変更する
ショットを PUB から QuantumProgram(shots=...) に移動します。Executor では、shots はジョブ全体に適用されます。異なるショット数が必要な場合は、複数のジョブを送信してください。
Sampler:
# Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit, None, 25)])
Executor:
# Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)
ステップ5. 必要に応じてオプションを更新する
エラー緩和の選択が現在はオプションではなく、注釈と samplex に存在するため、Executor で使用可能なオプションは Sampler よりも少なくなっています。
また、設定がどこにあるかという構造的な違いもあります。
-
Sampler では、結果の後処理に影響する選択を含め、すべてがプリミティブのオプションまたは PUB で設定されます。
-
Executor では、ジョブ結果の形状と後処理に影響する選択は、
ExecutorOptionsではなくQuantumProgramに設定されます。
Examples:
| Sampler | Executor |
|---|---|
shots | QuantumProgram(shots=...) |
meas_type | QuantumProgram(meas_level=...) |
ExecutorOptions には、返されるデータの構造を変更しない、より低レベルの実行設定と環境設定のみが含まれます。トップレベルのグループは3つあります。
-
environment(EnvironmentOptions) -
execution(ExecutionOptions):Sampler よりも含まれるオプションが少なくなっています。たとえば、Executor にはmeas_typeオプションがありません。
特に、twirling および dynamical_decoupling オプションは Sampler には存在しますが、Executor には存在しません。代わりに、それらのオプション値はディレクテッド実行モデルを通じて表現されます。
Example:
from qiskit_ibm_runtime import Executor, ExecutorOptions
options = ExecutorOptions(
environment={"log_level": "INFO"},
execution={"init_qubits": True},
)
# or mutate after construction:
options = ExecutorOptions()
options.environment.log_level = "INFO"
options.execution.init_qubits = True
executor = Executor(mode=backend, options=options)
ステップ6. run コマンドを更新する
Executor ジョブへの入力は、PUB ではなくプログラムです。
Sampler:
# Submit a job
sampler.run([(isa_circuit, parameter_values)])
Executor:
# Submit a job
executor.run(program)
ステップ7. 結果へのアクセス方法を変更する
Executor では、結果は BitArray オブジェクトではなく NumPy 配列です。名前の文字列をインデックスとして使用し(result[0]["meas"])、np.ndarray を取得します。.data.<register> という属性パスを覚える必要はありません。
Sampler から Executor に更新するには、result[i].data.<reg> (BitArray) を result[i]["<reg>"] (np.ndarray) に変更し、get_counts ベースの後処理を NumPy 操作として書き直します。
| Task | Sampler | Executor |
|---|---|---|
| Get register data | result[0].data.meas | result[0]["meas"] |
| Data type | BitArray | np.ndarray |
| Counts dictionary | result[0].data.meas.get_counts() | Post-process the array manually |
| Multiple registers | result[0].data.<name> per register | result[0]["<name>"] per register |
| CircuitItem array shape | - | (parameter_sets, shots, register_bits) |
| SamplexItem array shape | - | (randomizations, parameter_sets, shots, register_bits) |
| Undo measurement twirling | Automatic | result[i]["measurement_flips.<name>"] + XOR |
SamplerのBitArrayは、ヘルパー(get_counts、slice_bits、slice_shots、expectation_values、事後選択マスク)を提供します。Executorは生のNumPy配列を返すため、標準のNumPy操作でこの後処理を実行できます。
ステップ8. ツイリングされた結果を処理する(ビット反転補正)
SamplexItem を通じて測定ツイリングを適用すると、Executor は生の(ツイリングされた)測定値に加えて、ツイリングを打ち消すために必要なビット反転補正を返します。これらは手動で適用する必要があります。暗黙的に補正されることはありません。
Executor を使用する場合は、次の例に示すように measurement_flips.<reg> 補正と XOR を使用して、明示的にツイリングを打ち消します。
# SamplexItem result shape: (randomizations, parameter_sets, shots, register_bits)
result_1 = result[1]["meas"] # example: (28, 10, 1024, 2)
# Bit-flip corrections to undo measurement twirling
flips_1 = result[1]["measurement_flips.meas"] # example: (28, 10, 1, 2)
# Undo the twirling through classical XOR (broadcasts over the shots axis)
unflipped_result_1 = result_1 ^ flips_1
Sampler には、これに相当するステップはありません。Sampler がツイリングを自動的に打ち消すためです。
完全な例:基本的なサンプリングジョブを移行する
Sampler
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
# 1. Account + backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# 2. Circuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()
# 3. Transpile to ISA
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
# 4. Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit,)], shots=25)
result = job.result()
# 5. Access results: a BitArray keyed by register name
counts = result[0].data.meas.get_counts()
Executor
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
# 1. Account + backend (unchanged)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# 2. Circuit (unchanged)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()
# 3. Transpile to ISA (unchanged)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
# 4. Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)
# 5. Run
executor = Executor(mode=backend)
job = executor.run(program)
result = job.result()
# 6. Access results: a plain np.ndarray keyed by register name
# shape = (shots, register_bits)
meas = result[0]["meas"]