Executor の入力と出力
パッケージ・バージョン
このページのコードは、以下の要件を使用して開発されました。 これらのバージョン以降の使用をお勧めします。
qiskit[all]~=2.6.1
qiskit-ibm-runtime~=0.50.0
samplomatic~=0.21.0
Executor プリミティブは、エラー緩和ワークフローをカスタマイズする際により高い柔軟性を提供する指示型実行モデルの一部です。
Executor プリミティブの入力と出力は、Sampler や Estimator プリミティブとは大きく異なります。例えば、PUB のリストを入力として受け取る代わりに、Executor は QuantumProgramItem オブジェクトのリストを含む QuantumProgram を受け取ります。これらのコンテナ・クラスは、単純なタプル・データ構造である PUB よりも高い柔軟性を提供します。
Executor の出力は QuantumProgramResult で、これはイテラブルであり、各入力 QuantumProgramItem に対して 1 つの要素を含んでいます。
入力:量子プログラム
前述のように、Executor プリミティブへの入力は QuantumProgram です。これは QuantumProgramItem オブジェクトのイテラブルです。これらのオブジェクトは 2 種類あります。
CircuitItem:通常、回路とそのパラメーター値(あれば)を格納します。SamplexItem:通常、以下を格納します。- テンプレート回路
- ランタイムにランダム化されたパラメーターセットを生成するために使用される samplex オブジェクト(例えばツワーリングの実行やノイズの注入に使用)
- samplex の引数(元の回路のパラメーター値を含む場合があります)
これらの各アイテムは、Executor が実行する異なるタスクを表します。
始める前に
このページのコード例の一部では、Samplomatic パッケージの一部である samplex を使用しています。そのため、これらのコード・ブロックを実行する前に、以下のコード・ブロックに示すように Samplomatic をインストールする必要があります。詳細については、Samplomatic ドキュメントを参照してください。
pip install samplomatic
# For visualization support, include the visualization dependencies.
# pip install samplomatic[vis]
例:2 つの異なるタスクを持つ QuantumProgram の作成
まず量子プログラムを初期化し、次に append_circuit_item または append_samplex_item(samplex がある場合)を使用してプログラム・アイテムを追加します。以下の例を参照してください。
次のセルは QuantumProgram を初期化し、プログラム内の各アイテムのすべての設定に対して 1024 ショットを実行するよう指定します。
Sampler とは異なり、QuantumProgram は単一のショット値のみを受け取ります。異なるショット値が必要な場合は、別の QuantumProgram が必要であり、それは別のジョブとなります。
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "samplomatic": "samplomatic"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit_ibm_runtime import Executor, QiskitRuntimeService
from qiskit.circuit import Parameter, QuantumCircuit
import numpy as np
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager
# Initialize an empty program
program = QuantumProgram(shots=1024)
# Initialize and transpile a 3-qubit quantum circuit with 2 parameters.
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.rz(Parameter("theta"), 0)
circuit.rz(Parameter("phi"), 1)
# `measure_all` adds a 3-bit classical register named "meas"
circuit.measure_all()
# Choose the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Generate a preset pass manager
# This will be used to convert the abstract circuit to an
# equivalent Instruction Set Architecture (ISA) circuit.
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=0
)
# Transpile the circuit
isa_circuit = preset_pass_manager.run(circuit)
CircuitItem の追加
次に、バックエンドの命令セット・アーキテクチャー(ISA)に従ってトランスパイルされたターゲット回路を QuantumProgram に追加します。この回路には 2 つのパラメーターがあるため、パラメーター値も提供する必要があります(この例では 10 セット)。この CircuitItem の実行が、プログラムで最初に実行されるタスクです。
# Append the transpiled circuit and an array
# containing 10 sets of parameter values to the program
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(
10, 2
), # 10 sets of parameter values and 2 parameters
)
SamplexItem の追加
回路アイテムはランダム化なしで実行されます。一方、samplex アイテムを使用すると、その内容をランダム化する方法を指定できます。次のセルでは、generate_boxing_pass_manager() 関数を使用して回路のゲートと測定をボックスにグループ化し、各ボックスにツワーリング・アノテーションを追加します。次に、build() 関数を使用してテンプレート回路と samplex ペアを生成します。
この SamplexItem の実行がプログラムが 2 番目に実行するタスクです。
samplex とその引数の詳細については、Samplomatic の API ドキュメントを参照してください。generate_boxing_pass_manager() 関数の使用方法については、Samplomatic の Transpiler ガイドを参照してください。
# Transpile the circuit, additionally grouping gates and measurements into annotated boxes
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=0
)
# Use the boxing pass manager to group gates
# and measurements into boxes and add
# a`Twirl` annotation.
preset_pass_manager.post_scheduling = generate_boxing_pass_manager(
# Add gate twirling
enable_gates=True,
# Add measurement twirling
enable_measures=True,
)
boxed_circuit = preset_pass_manager.run(circuit)
# Build the template circuit and the samplex. The template circuit has parametric gates
# without fixed values and the samplex randomly generates the parameter
# values on the server side at runtime to perform twirling.
template_circuit, samplex = build(boxed_circuit)
# Determine what arguments are required by the samplex.
# Input the arguments in samplex_arguments.
print(samplex.inputs())
TensorInterface(<
- 'parameter_values' <float64[2]>: Input parameter values to use during sampling.
>)
# Append the template circuit and samplex as a samplex item
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
# the arguments required by the samplex.sample method
"parameter_values": np.random.rand(10, 2),
},
shape=(28, 10), # 28 randomizations and 10 sets of parameter values
)
# Initialize an Executor with the default options
executor = Executor(mode=backend)
# Submit the job
job = executor.run(program)
# Retrieve the result
result = job.result()
出力
Executor の出力は QuantumProgramResult で、イテラブルです。入力の順序と同じ順序で、各入力 QuantumProgramItem に対して 1 つのエントリを含みます。これらの出力アイテムはそれぞれ辞書で、キーは入力回路の古典レジスターの名前(およびその他)に対応する文字列なので、Sampler の出力のようにこれらの名前を覚えておく必要はありません。辞書の値は np.ndarray 型です。
前の例の結果には以下のアイテムが含まれています。
CircuitItem の結果
最初のアイテムには、プログラム内で最初のタスク(CircuitItem)を実行した結果が含まれます。これは単一のキーmeasを含み、これは入力回路の古典レジスターの名前です。このキーの値は、(parameter sets, shots, register bits)の形状を持つnp.ndarrayにマッピングされ、上記の例では(10, 1024, 3)になります。
以下のコードでこの情報へのアクセス方法を示します。
# Access the results of the classical register of task #0, a CircuitItem
result_0 = result[0]["meas"]
print(f"Result shape: {result_0.shape}")
Result shape: (10, 1024, 3)
result_0 の最も右の軸は、古典ビットをリトルエンディアン順に走査します。したがって、インデックス 0 には古典ビット 0、つまり最下位ビットの測定結果が格納されます。
SamplexItem の結果
2番目のアイテムには、プログラム内で2番目のタスク(SamplexItem)を実行した結果が含まれます。このアイテムには複数のキーが含まれます。入力回路の古典レジスターの名前であるmeasキーは、そのレジスターの結果の配列にマッピングされます。この配列は、この例では(randomizations, parameter sets, shots, classical bits)の形状、つまり(28, 10, 1024, 3)を持ちます。
さらに、出力にはmeasurement_flips.measキーが含まれており、これはmeasレジスターの測定ツワーリングを元に戻すためのビットフリップ補正です。この出力の形状は、ビットフリップを実行するのに1ショットのみが必要なため、この例では(28, 10, 1, 3)になります。
result_1 の最も右の軸も、古典ビットをリトルエンディアン順に走査することに注意してください。
# Access the results of the classical register of task #1
result_1 = result[1]["meas"]
print(f"Result shape: {result_1.shape}")
# Access the bit-flip corrections
flips_1 = result[1]["measurement_flips.meas"]
print(f"Bit-flip corrections shape: {flips_1.shape}")
# Undo the bit flips via classical XOR
unflipped_result_1 = result_1 ^ flips_1
Result shape: (28, 10, 1024, 3)
Bit-flip corrections shape: (28, 10, 1, 3)
実行スパンを表示する
1回のExecutor.run()呼び出しで、1つのジョブが送信されます。ただしサーバー側では、サービスは量子プログラムのすべての構成を必ずしも同時に実行するわけではありません。代わりに、プログラムアイテムのバインド済みCircuitを_チャンク_にグループ化し、QPU上で一度に1つのチャンクを実行します。プログラムを分割したり、追加のジョブを送信したりする必要はありません。チャンク化は、1つのジョブの中で自動的に行われます。結果のmetadata属性のresult.metadata.chunk_timingには、これらのチャンクに関するタイミング情報が含まれています。これらのタイムスタンプは、QPU上での各チャンクの実際の開始時刻と終了時刻です。この情報を使って、特定のデータがQPU上でいつ収集されたかを確認したり、Executorが作業をどのように分配したかを理解したり、性能の問題を診断したりできます。
chunk_timingは、実行されたチャンクごとに1つのChunkSpanを含むリストです。各ChunkSpanには、次の属性があります。
-
startとstop:チャンクがQPU上で実行を開始および終了したUTCのタイムスタンプ。 -
parts:どのプログラムアイテムがチャンクに寄与したかを示すChunkPartオブジェクトのリスト。各ChunkPartには次が含まれます。-
idx_item:プログラムアイテムのインデックス。アイテムがQuantumProgramに追加された順序に基づきます。 -
size:そのアイテムのうち、このチャンクで実行された構成(アイテムのシェイプの要素)の数。たとえば、前の例のSamplexItemのシェイプは(28, 10)なので、構成は合計で280個です。
-
次のコードは、先ほど送信したジョブについて、各チャンクの所要時間と各チャンクの内容を出力します。
for idx, span in enumerate(result.metadata.chunk_timing):
duration = (span.stop - span.start).total_seconds()
print(f"Chunk {idx}: {span.start} to {span.stop} ({duration:.3f} s)")
for part in span.parts:
print(f" Item {part.idx_item}: {part.size} configurations")
Chunk 0: 2026-11-06 01:47:46.009718+00:00 to 2026-11-06 01:49:05.577720+00:00 (79.568 s)
Item 0: 10 configurations
Item 1: 280 configurations
QuantumProgramResult.timingプロパティは、同じリストをChunkTimingオブジェクトでラップします。このオブジェクトは、チャンク全体に関するグローバルな情報も報告します。
timing = result.timing
print("Number of chunks:", len(timing))
print(" Start of the first chunk:", timing.start)
print(" End of the last chunk:", timing.stop)
print(" Total duration (s):", timing.duration)
Number of chunks: 1
Start of the first chunk: 2026-11-06 01:47:46.009718+00:00
End of the last chunk: 2026-11-06 01:49:05.577720+00:00
Total duration (s): 79.568002
実行スパンを可視化する
draw_chunk_timings関数を使うと、チャンクのタイミングをインタラクティブな棒グラフで可視化できます。x軸は時間で、各チャンクは開始時刻から終了時刻までの水平なセグメントとして描画されます。y軸には、完了した構成の累積数(normalize_y=Trueを設定した場合は、ワークロード全体に対する割合)が表示されます。セグメントにカーソルを合わせると、そのチャンクのタイムスタンプ、所要時間、partsが表示されます。
この関数はplotlyのfigureを返します。これは、そのまま表示することも、ファイルに保存することも、その両方を行うこともできます。chunk_timingリストとChunkTimingオブジェクトのどちらも受け付けます。たとえば異なるジョブのものなど、複数を渡して同じプロットに描画できます。common_start=Trueを設定すると、比較のために共通の開始時刻に揃えられます。
draw_chunk_timingsにはplotlyが必要です。pip install plotlyを実行してインストールできます。
可視化では、チャンクの実行間に隙間が表示されることがあります。実際のQPUに送信されるすべてのジョブは、ハードウェアに送信できるパルスへの変換など、何らかの古典的な前処理を経ます。この前処理は並列に行えるため、サービスは複数のジョブを同時にキューから取り出して開始します。しかしQPUは、一度に1つの作業単位しか処理できません。そのため、ジョブが複数のチャンクを生成する場合、そのチャンクは他のユーザーのジョブのチャンクと交互に実行されることがあり、各チャンクがQPU上で順番を待たなければならないことがあります。これが、チャンク間に隙間が現れる理由です。
from qiskit_ibm_runtime.visualization import draw_chunk_timings
fig = draw_chunk_timings(result.timing, names="Executor job")
# Alternatively, call the `draw()` method of the ChunkTiming object
# fig = result.timing.draw()
# Uncomment the following line to display the figure
# fig.show(renderer="notebook")
# Save to a file
# fig.write_html("chunk_timings.html")
次のステップ
- Executor を使用する例を確認します。
- 指示型実行モデルについて学びます。
- Executor ブロードキャストを理解します。