Qiskit Runtime V2 プリミティブへの移行
オリジナルのプリミティブ(V1 プリミティブとも呼ばれます)である V1 Sampler および V1 Estimator は、qiskit-ibm-runtime 0.23 で非推奨となりました。
これらのサポートは 2024 年 8 月 15 日に終了しました。
V1 プリミティブの非推奨化に伴い、すべてのコードを V2 インターフェースを使用するよう移行する必要があります。このガイドでは、Qiskit Runtime V2 プリミティブ(qiskit-ibm-runtime 0.21.0 以降で利用可能)の変更点とその理由を説明し、新しい各プリミティブの詳細を解説するとともに、レガシープリミティブから V2 プリミティブへのコード移行を支援するサンプルを提供します。このガイドのサンプルはすべて Qiskit Runtime プリミティブを使用していますが、一般的に同じ変更が他のプリミティブ実装にも適用されます。エラー軽減など Qiskit Runtime 固有の機能は、引き続き Qiskit Runtime に固有のものとなります。
Qiskit リファレンスプリミティブ(現在は statevector プリミティブと呼ばれます)への変更については、Qiskit 1.0 機能変更ページの qiskit.primitives セクションをご覧ください。V2 プリミティブのリファレンス実装については、StatevectorSampler および StatevectorEstimator を参照してください。
概要
プリミティブのバージョン 2 は、Sampler と Estimator の両方に対する新しい基底クラス(BaseSamplerV2 および BaseEstimatorV2)と、入出力の新しい型とともに導入されました。
新しいインターフェースでは、1 つの Circuit と複数のオブザーバブル(Estimator を使用する場合)および当該 Circuit のパラメータ値セットを指定できるため、パラメータ値セットとオブザーバブルのスイープを効率的に指定できます。以前は、結合するデータのサイズに合わせて同じ Circuit を複数回指定する必要がありました。また、引き続き resilience_level(Estimator を使用する場合)をシンプルなノブとして使用できますが、V2 プリミティブでは個別のエラー軽減・抑制メソッドをオン/オフして、ニーズに合わせてカスタマイズする柔軟性も提供されます。
ジョブの総実行時間を削減するため、V2 プリミティブは対象の QPU(量子処理ユニット)がサポートする命令を使用した Circuit とオブザーバブルのみを受け付けます。このような Circuit とオブザーバブルは、命令セ ットアーキテクチャ(ISA)Circuit およびオブザーバブルと呼ばれます。V2 プリミティブはレイアウト、ルーティング、変換の各操作を実行しません。Circuit を変換する手順については、トランスパイルのドキュメントを参照してください。
Sampler V2 は、量子 Circuit の実行から出力レジスタをサンプリングするというコアタスクに集中するよう簡素化されました。プログラムで定義された型のサンプルを、重みなしで返します。出力データは、プログラムで定義された出力レジスタ名ごとに分離されています。この変更により、古典的な制御フローを持つ Circuit の将来的なサポートが可能となります。
詳細については、EstimatorV2 API リファレンスおよび SamplerV2 API リファレンスを参照してください。
主な変更点
インポート
後方互換性のため、V2 プリミティブは明示的にインポートする必要があります。import <primitive>V2 as <primitive> と指定することは必須ではありませんが、コードを V2 へ移行しやすくなります。
V1 プリミティブのサポートが終了した後は、import <primitive> と記述すると指定したプリミティブの V2 バージョンがインポートされます。
- Estimator V2
- Estimator (V1)
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import Estimator
- Sampler V2
- Sampler (V1)
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import Sampler
入出力
入力
SamplerV2 と EstimatorV2 はどちらも、1 つ以上の プリミティブ統合ブロック(PUB)を入力として受け取ります。各 PUB は、1 つの Circuit と、その Circuit にブロードキャストされるデータ(複数のオブザーバブルとパラメータ)を含むタプルです。各 PUB は 1 つの結果を返します。
- Sampler V2 の PUB フォーマット:(
<circuit>、<parameter values>、<shots>)。<parameter values>と<shots>は省略可能です。 - Estimator V2 の PUB フォーマット:(
<circuit>、<observables>、<parameter values>、<precision>)。<parameter values>と<precision>は省略可能です。 オブザーバブルとパラメータ値を組み合わせる際は、Numpy のブロードキャストルールが使用されます。
また、以下の変更も行われています。
- Estimator V2 では、期待値推定の目標精度を指定する
precision引数がrun()メソッドに追加されました。 - Sampler V2 では、
run()メソッドにshots引数が追加されました。
サンプル
run() で precision を使用する Estimator V2 のサンプル:
# Estimate expectation values for two PUBs, both with 0.05 precision.
estimator.run([(circuit1, obs_array1), (circuit2, obs_array_2)], precision=0.05)
run() で shots を使用する Sampler V2 のサ ンプル:
# Sample two circuits at 128 shots each.
sampler.run([circuit1, circuit2], shots=128)
# Sample two circuits at different amounts of shots.
# The "None"s are necessary as placeholders
# for the lack of parameter values in this example.
sampler.run([
(circuit1, None, 123),
(circuit2, None, 456),
])
出力
出力は PubResult フォーマットになりました。PubResult は、単一の PUB の実行から得られるデータとメタデータです。
-
Estimator V2 は引き続き期待値を返します。
-
Estimator V2 の PubResult の
data部分には、期待値と標準誤差(stds)の両方が含まれます。V1 ではメタデータに分 散が返されていました。 -
Sampler V2 は、V1 インターフェースの準確率分布の代わりに、ビット列の形式でショットごとの測定値を返します。ビット列は測定結果を示し、測定されたショットの順序を保持します。
-
Sampler V2 には、移行を支援する
get_counts()などの便利なメソッドが追加されました。 -
Sampler V2 の結果オブジェクトは、動的 Circuit との互換性のため、入力 Circuit の古典レジスタ名ごとにデータを整理します。デフォルトでは、以下の例に示すように古典レジスタ名は
measです。Circuit を定義する際に、デフォルトでない名前の古典レジスタを 1 つ以上作成した場合は、その名前を使用して結果を取得します。古典レジスタ名は<circuit_name>.cregsを実行することで確認できます(例:qc.cregs)。# Define a quantum circuit with 2 qubits
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()┌───┐ ░ ┌─┐
q_0: ┤ H ├──■───░─┤M├───
└───┘┌─┴─┐ ░ └╥┘┌─┐
q_1: ─────┤ X ├─░──╫─┤M├
└───┘ ░ ║ └╥┘
meas: 2/══════════════╩══╩═
0 1
Estimator のサンプル(入出力)
- 1 circuit, 4 observables
- 1 circuit, 4 observables, 2 parameter sets
- 2 circuits, 2 observables
# Estimator V1: Execute 1 circuit with 4 observables
job = estimator_v1.run([circuit] * 4, [obs1, obs2, obs3, obs4])
evs = job.result().values
# Estimator V2: Execute 1 circuit with 4 observables
job = estimator_v2.run([(circuit, [obs1, obs2, obs3, obs4])])
evs = job.result()[0].data.evs
# Estimator V1: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v1.run([circuit] * 8, [obs1, obs2, obs3, obs4] * 2, [vals1, vals2] * 4)
evs = job.result().values
# Estimator V2: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v2.run([(circuit, [[obs1], [obs2], [obs3], [obs4]], [[vals1], [vals2]])])
evs = job.result()[0].data.evs
# Estimator V1: Cannot execute 2 circuits with different observables
# Estimator V2: Execute 2 circuits with 2 different observables. There are
# two PUBs because each PUB can have only one circuit.
job = estimator_v2.run([(circuit1, obs1), (circuit2, obs2)])
evs1 = job.result()[0].data.evs # result for pub 1 (circuit 1)
evs2 = job.result()[1].data.evs # result for pub 2 (circuit 2)
Sampler のサンプル(入出力)
- 1 circuit, 3 parameter sets
- 2 circuits, 1 parameter set
- Convert V2 output to V1 format
# Sampler V1: Execute 1 circuit with 3 parameter sets
job = sampler_v1.run([circuit] * 3, [vals1, vals2, vals3])
dists = job.result().quasi_dists
# Sampler V2: Executing 1 circuit with 3 parameter sets
job = sampler_v2.run([(circuit, [vals1, vals2, vals3])])
counts = job.result()[0].data.meas.get_counts()
# Sampler V1: Execute 2 circuits with 1 parameter set
job = sampler_v1.run([circuit1, circuit2], [vals1] * 2)
dists = job.result().quasi_dists
# Sampler V2: Execute 2 circuits with 1 parameter set
job = sampler_v2.run([(circuit1, vals1), (circuit2, vals1)])
counts1 = job.result()[0].data.meas.get_counts() # result for pub 1 (circuit 1)
counts2 = job.result()[1].data.meas.get_counts() # result for pub 2 (circuit 2)
V1 の出力フォーマットは、キーにビット列(整数)、値に各 Circuit の準確率を持つ辞書でした。V2 フォーマットでは同じキー(ただし文字列として)とカウント数が値として使用されます。V2 フォーマットを V1 に変換するには、カウント数をショット数で割ります。ショット数の選択方法については、オプションの指定ガイドを参照してください。
v2_result = sampler_v2_job.result()
v1_format = []
for pub_result in v2_result:
counts = pub_result.data.meas.get_counts()
v1_format.append( {int(key, 2): val/shots for key, val in counts.items()} )
異なる出力レジスタを使用するサンプル
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
alpha = ClassicalRegister(5, "alpha")
beta = ClassicalRegister(7, "beta")
qreg = QuantumRegister(12)
circuit = QuantumCircuit(qreg, alpha, beta)
circuit.h(0)
circuit.measure(qreg[:5], alpha)
circuit.measure(qreg[5:], beta)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=12)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
print(f" >> Counts for the alpha output register: {pub_result.data.alpha.get_counts()}")
print(f" >> Counts for the beta output register: {pub_result.data.beta.get_counts()}")
オプション
V2 プリミティブでは、オプションの指定方法が以下のように変更されています。
SamplerV2とEstimatorV2はそれぞれ独立したオプションクラスを持つようになりました。プリミティブの初期化時および初期化後に、利用可能なオプションを確認してオプション値を更新できます。set_options()メソッドの代わりに、V2 プリミティブのオプションにはoptions属性への変更を適用するupdate()メソッドが用意されています。- オプションの値を指定しない場合、そのオプションには特別な値
Unsetが設定され、サーバーのデフォルト値が使用されます。 - V2 プリミティブでは、
options属性は Python のdataclass型です。組み込みのasdictメソッドを使用して辞書に変換できます。
利用可能なオプションの一覧については、API リファレンスを参照してください。
- Estimator V2
- Estimator (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
estimator = Estimator(backend, options={"resilience_level": 2})
# Setting options after primitive initialization
# This uses auto complete.
estimator.options.default_shots = 4000
# This does bulk update.
estimator.options.update(default_shots=4000, resilience_level=2)
# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(estimator.options))
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
estimator = Estimator(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
estimator.set_options(shots=4000)
- Sampler V2
- Sampler (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
sampler = Sampler(backend, options={"default_shots": 4096})
# Setting options after primitive initialization
# This uses auto complete.
sampler.options.dynamical_decoupling.enable = True
# Turn on gate twirling. Requires qiskit_ibm_runtime 0.23.0 or later.
sampler.options.twirling.enable_gates = True
# This does bulk update. The value for default_shots is overridden if you specify shots with run() or in the PUB.
sampler.options.update(default_shots=1024, dynamical_decoupling={"sequence_type": "XpXm"})
# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(sampler.options))
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
sampler = Sampler(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
sampler.set_options(shots=2000)
エラー軽減と抑制
-
Sampler V2 は後処理なしでサンプルを返すため、レジリエンスレベルをサポートしません。
-
Sampler V2 は
optimization_levelをサポートしません。 -
Estimator V2 は 2024 年 9 月 30 日前後に
optimization_levelのサポートを終了する予定です。 -
Estimator V2 はレジリエンスレベル 3 をサポートしません。これは、V1 Estimator のレジリエンスレベル 3 が確率的エラーキャンセル(PEC)を使用しており、指数関数的な処理時間を代償として偏りのない結果を提供することが証明されているためです。レベル 3 はこのトレードオフに注意を促すために削除されました。ただし、
pec_mitigationオプションを指定することで、引き続き PEC をエラー軽減手法として使用できます。 -
Estimator V2 は、以下の表に示すように
resilience_level0~2 をサポートしています。これらのオプションは V1 の対応するオプションよりも高度です。個別のエラー軽減・抑制メソッドを明示的にオン/オフす ることもできます。レベル 1 レベル 2 測定 Twirling 測定 Twirling 読み出しエラー軽減 読み出しエラー軽減 ZNE
- Estimator V2
- Estimator (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
estimator = Estimator(backend)
# Set resilience_level to 0
estimator.options.resilience_level = 0
# Turn on measurement error mitigation
estimator.options.resilience.measure_mitigation = True
from qiskit_ibm_runtime import Estimator, Options
estimator = Estimator(backend, options=options)
options = Options()
options.resilience_level = 2
- Sampler V2
- Sampler (V1)
from qiskit_ibm_runtime import SamplerV2 as Sampler
sampler = Sampler(backend)
# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"
print(f">> dynamical decoupling sequence to use: {sampler.options.dynamical_decoupling.sequence_type}")
from qiskit_ibm_runtime import Sampler, Options
sampler = Sampler(backend, options=options)
options = Options()
options.resilience_level = 2
トランスパイル
V2 プリミティブは、特定の Backend の命令セットアーキテクチャ(ISA)に準拠した Circuit のみをサポートします。プリミティブはレイアウト、ルーティング、変換の各操作を実行しないため、V1 の対応するトランスパイルオプションはサポートされていません。
ジョブのステータス
V2 プリミティブには、BasePrimitiveJob を継承した新しい RuntimeJobV2 クラスが追加されました。この新しいクラスの status() メソッドは、Qiskit の JobStatus 列挙型の代わりに文字列を返します。詳細については、RuntimeJobV2 API リファレンスを参照してください。
- V2 primitives
- V1 primitives
job = estimator.run(...)
# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")
from qiskit.providers.jobstatus import JobStatus
job = estimator.run(...)
#check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() is JobStatus.RUNNING}")
Estimator V2 への移行手順
-
from qiskit_ibm_runtime import Estimatorをfrom qiskit_ibm_runtime import EstimatorV2 as Estimatorに置き換えます。 -
from qiskit_ibm_runtime import Optionsの記述をすべて削除します。Optionsクラスは V2 プリミティブでは使用されないためです。代わりに、EstimatorV2クラスの初期化時にオプションを辞書として渡す(例:estimator = Estimator(backend, options={"dynamical_decoupling": {"enable": True}}))か、初期化後に設定することができま す。estimator = Estimator(backend)
estimator.options.dynamical_decoupling.enable = True -
サポートされているオプションをすべて確認し、必要に応じて更新します。
-
実行したい各 Circuit を、そのCircuit に適用したい オブザーバブルとパラメータ値とともにタプル(PUB)にまとめます。たとえば、
circuit1をobservable1とparameter_set1で実行したい場合は(circuit1, observable1, parameter_set1)を使用します。 -
外積を適用したい場合は、オブザーバブルの配列やパラメータセットの配列を整形し直す必要があるかもしれません。たとえば、形状 (4, 1) のオブザーバブル配列と形状 (1, 6) のパラメータセット配列を組み合わせると、(4, 6) の期待値が得られます。詳しくは Numpy のブロードキャストルールを参照してください。
-
任意で、その特定の PUB に対して必要な精度を指定することもできます。
-
Estimator の
run()メソッドを更新し、PUB のリストを渡すようにします。たとえばrun([(circuit1, observable1, parameter_set1)])のようにします。 任意で、すべての PUB に適用されるprecisionをここで指定することもできます。 -
Estimator V2 のジョブ結果は PUB ごとにグループ化されています。インデックスを指定することで、各 PUB の期待値と標準誤差を確認できます。たとえば:
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
Estimator の完全な使用例
単一の実験を実行する
Estimator を使って、単一の Circuit とオブザーバブルのペアの期待値を求めます。
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
estimator = Estimator(backend)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
job = estimator.run([(isa_circuit, isa_observable)])
result = job.result()
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
estimator = Estimator(backend)
job = estimator.run(isa_circuit, isa_observable)
result = job.result()
print(f" > Observable: {observable.paulis}")
print(f" > Expectation value: {result.values}")
print(f" > Metadata: {result.metadata}")
単一のジョブで複数の実験を実行する
Estimator を使って、複数の Circuit とオブザーバブルのペアの期待値を求めます。
- Estimator V2
- Estimator (V1)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
n_qubits = 3
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]
isa_circuits = pm.run(circuits)
isa_observables = [ob.apply_layout(isa_circuits[0].layout) for ob in observables]
estimator = Estimator(backend)
job = estimator.run([(isa_circuits[0], isa_observables[0]),(isa_circuits[1], isa_observables[1]),(isa_circuits[2], isa_observables[2])])
job_result = job.result()
for idx in range(len(job_result)):
pub_result = job_result[idx]
print(f">>> Expectation values for PUB {idx}: {pub_result.data.evs}")
print(f">>> Standard errors for PUB {idx}: {pub_result.data.stds}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]
# Get ISA circuits
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
isa_observables = [ob.apply_layout(isa_circuits[0].layout) for ob in observables]
estimator = Estimator(backend)
job = estimator.run(isa_circuits, isa_observables)
result = job.result()
print(f" > Expectation values: {result.values}")
パラメータ化された Circuit を実行する
Estimator を使って、パラメータ値を活 用して Circuit の再利用性を高めながら、単一のジョブで複数の実験を実行します。以下の例では、ステップ 1 と 2 が V1 と V2 で同じであることに注目してください。
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
theta = Parameter("θ")
chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)
number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]
ZZ = SparsePauliOp.from_list([("ZZ", 1)])
ZX = SparsePauliOp.from_list([("ZX", 1)])
XZ = SparsePauliOp.from_list([("XZ", 1)])
XX = SparsePauliOp.from_list([("XX", 1)])
ops = [ZZ, ZX, XZ, XX]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [operator.apply_layout(chsh_isa_circuit.layout) for operator in ops]
from qiskit_ibm_runtime import EstimatorV2 as Estimator
# Step 3: Execute using Qiskit primitives.
# Reshape observable array for broadcasting
reshaped_ops = np.fromiter(isa_observables, dtype=object)
reshaped_ops = reshaped_ops.reshape((4, 1))
estimator = Estimator(backend, options={"default_shots": int(1e4)})
job = estimator.run([(chsh_isa_circuit, reshaped_ops, individual_phases)])
# Get results for the first (and only) PUB
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
print(f">>> Metadata: {pub_result.metadata}")
import numpy as np
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
theta = Parameter("θ")
chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)
number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]
ZZ = SparsePauliOp.from_list([("ZZ", 1)])
ZX = SparsePauliOp.from_list([("ZX", 1)])
XZ = SparsePauliOp.from_list([("XZ", 1)])
XX = SparsePauliOp.from_list([("XX", 1)])
ops = [ZZ, ZX, XZ, XX]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [operator.apply_layout(chsh_isa_circuit.layout) for operator in ops]
from qiskit_ibm_runtime import Estimator
# Step 3: Execute using Qiskit Primitives.
num_ops = len(isa_observables)
batch_circuits = [chsh_isa_circuit] * number_of_phases * num_ops
batch_ops = [op for op in isa_observables for _ in individual_phases]
batch_phases = individual_phases * num_ops
estimator = Estimator(backend, options={"shots": int(1e4)})
job = estimator.run(batch_circuits, batch_ops, batch_phases)
expvals = job.result().values
セッションと高度なオプションを使用する
セッションと高度なオプションを活用して、QPU 上でのCircuitのパフォーマンスを最適化します。
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, EstimatorV2 as Estimator
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
estimator = Estimator()
estimator.options.resilience_level = 1
job = estimator.run([(isa_circuit, isa_observable)])
another_job = estimator.run([(another_isa_circuit, another_isa_observable)])
result = job.result()
another_result = another_job.result()
# first job
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
# second job
print(f" > Another Expectation value: {another_result[0].data.evs}")
print(f" > More Metadata: {another_result[0].metadata}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)
options = Options()
options.optimization_level = 2
options.resilience_level = 2
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
estimator = Estimator(options=options)
job = estimator.run(isa_circuit, isa_observable)
another_job = estimator.run(another_isa_circuit, another_isa_observable)
result = job.result()
another_result = another_job.result()
# first job
print(f" > Expectation values job 1: {result.values}")
# second job
print(f" > Expectation values job 2: {another_result.values}")
Sampler V2 への移行手順
from qiskit_ibm_runtime import Samplerをfrom qiskit_ibm_runtime import SamplerV2 as Samplerに置き換えます。Optionsクラスは V2 プリミティブでは使用しないため、from qiskit_ibm_runtime import Optionsの文をすべて削除します。代わりに、SamplerV2クラスの初期化時にオプションをディクショナリとして渡すか(例:sampler = Sampler(backend, options={"default_shots": 1024}))、初期化後に設定します:sampler = Sampler(backend)
sampler.options.default_shots = 1024- サポートされているオプションをすべて確認し、必要に応じて更新します。
- 実行したい各 Circuit を、適用したいオブザーバブルとパラメーター値とともにタプル(PUB)にまとめます。たとえば、
circuit1をparameter_set1で実行したい場合は(circuit1, parameter_set1)を使用します。 その PUB に対して指定したいショット数を任意で設定することもできます。 - PUB のリストを渡すように Sampler の
run()メソッドを更新します。たとえばrun([(circuit1, parameter_set1)])のようにします。 ここでshotsを任意で指定することもでき、これはすべての PUB に適用されます。 - Sampler V2 のジョブ結果は PUB ごとにグループ化されます。インデックスを指定することで各 PUB の出力データを参照できます。Sampler V2 は重みなしのサンプルを返しますが、結果クラスにはカウントを取得する便利なメソッドがあります。たとえば:
pub_result = job.result()[0]
print(f">>> Counts: {pub_result.data.meas.get_counts()}")
print(f">>> Per-shot measurement: {pub_result.data.meas.get_counts()}")
結果を取得するには古典レジスタ名が必要です。measure_all() を使用した場合、デフォルトでは meas という名前になります。Circuit を定義する際に、デフォルト以外の名前で古典レジスタを 1 つ以上作成した場合は、その名前を使用して結果を取得してください。古典レジスタ名は <circuit_name>.cregs を実行することで確認できます(例:qc.cregs)。
Sampler の完全なサンプル
単一の実験を実行する
Sampler を使用して 、単一の Circuit のカウントまたは準確率分布を求めます。
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()
sampler = Sampler(backend)
job = sampler.run(circuit)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
1 つのジョブで複数の実験を実行する
Sampler を使用して、1 つのジョブで複数の Circuit のカウントまたは準確率分布を求めます。
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
sampler = Sampler(backend)
job = sampler.run(isa_circuits)
result = job.result()
for idx, pub_result in enumerate(result):
print(f" > Counts for pub {idx}: {pub_result.data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()
sampler = Sampler(backend)
job = sampler.run(circuits)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
パラメーター化された Circuit を実行する
パラメーター値を活用して Circuit の再利用性を高め、1 つのジョブで複数の実験を実行します。
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
num_qubits = 127
circuit = RealAmplitudes(num_qubits=num_qubits, reps=2)
circuit.measure_all()
# Define three sets of parameters for the circuit
rng = np.random.default_rng(1234)
parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=num_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
# Step 3: Execute using Qiskit primitives.
from qiskit_ibm_runtime import SamplerV2 as Sampler
sampler = Sampler(backend)
job = sampler.run([(isa_circuit, parameter_values)])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
# Get counts from the classical register "meas".
print(f" >> Counts for the meas output register: {pub_result.data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
num_qubits = 5
circuit = RealAmplitudes(num_qubits=num_qubits, reps=2)
circuit.measure_all()
# Define three sets of parameters for the circuit
rng = np.random.default_rng(1234)
parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=num_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
# Step 3: Execute using Qiskit primitives.
from qiskit_ibm_runtime import Sampler
sampler = Sampler(backend)
job = sampler.run([isa_circuit] * 3, parameter_values)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
セッションと高度なオプションを使用する
セッションと高度なオプションを活用して、QPU 上での Circuit のパフォーマンスを最適化します。
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler, Session
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
circuit.measure_all()
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
another_circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
service = QiskitRuntimeService()
# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
sampler = Sampler()
job = sampler.run([isa_circuit])
another_job = sampler.run([another_isa_circuit])
result = job.result()
another_result = another_job.result()
# first job
print(f" > Counts for job 1: {result[0].data.meas.get_counts()}")
# second job
print(f" > Counts for job 2: {another_result[0].data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Session, Options
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
circuit.measure_all()
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
another_circuit.measure_all()
options = Options()
options.optimization_level = 2
options.resilience_level = 0
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
sampler = Sampler(options=options)
job = sampler.run(circuit)
another_job = sampler.run(another_circuit)
result = job.result()
another_result = another_job.result()
# first job
print(f" > Quasi-probability distribution job 1: {result.quasi_dists}")
# second job
print(f" > Quasi-probability distribution job 2: {another_result.quasi_dists}")
次のステップ
- オプションの指定ガイドで、オプションの設定についてさらに詳しく学びましょう。
- プリミティブの入出力の詳細を確認しましょう。
- CHSH 不等式チュートリアルで実際に試してみましょう。