量子ビット選択のためのリアルタイムベンチマーク
使用時間の目安:Eagle r2 プロセッサーで4分(注:これはあくまでも目安です。 ランタイムは異なるかもしれない)。
背景
このチュートリアルでは、QPU上の物理量子ビットに回路をマッピングする際に、量子ビットの選択を改善するために、リアルタイムの特性評価実験を実行し、バックエンドのプロパティを更新する方法を示します。 QPUのプロパティを決定するために使用される基本的な特性評価実験、Qiskitでの実験方法、およびこれらの実験に基づいてQPUを表すバックエンドオブジェクトに保存されたプロパティを更新する方法を学びます。
QPUが報告するプロパティは1日1回更新されるが、システムは更新間隔よりも速くドリフトする可能性がある。 これは、パスマネージャーの Layout ステージにおける量子ビット選択ルーチンの信頼性に影響を与える可能性があります。これは、QPUの現在の状態を表していない報告されたプロパティを使用しているためです。 Layout このような理由から、QPUの特性実験に時間を割く価値があるかもしれない。
要件
このチュートリアルを始める前に、以下のものがインストールされていることを確認してください:
- Qiskit SDK v2.0 またはそれ以降、 可視化サポート付き
- Qiskit Runtime v0.40 またはそれ以降 (
pip install qiskit-ibm-runtime) - Qiskit Experiments v0.12 またはそれ以降 (
pip install qiskit-experiments) - Rustworkx グラフ・ライブラリ (
pip install rustworkx)
セットアップ
from qiskit_ibm_runtime import SamplerV2
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import hellinger_fidelity
from qiskit.transpiler import InstructionProperties
from qiskit_experiments.library import (
T1,
T2Hahn,
LocalReadoutError,
StandardRB,
)
from qiskit_experiments.framework import BatchExperiment, ParallelExperiment
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Session
from datetime import datetime
from collections import defaultdict
import numpy as np
import rustworkx
import matplotlib.pyplot as plt
import copyステップ1:古典的な入力を量子問題にマッピングする
性能の違いをベンチマークするために、長さの異なる線形チェーン全体でベル状態を準備する回路を考える。 連鎖の末端におけるベル状態の忠実度が測定される。
from qiskit import QuantumCircuit
ideal_dist = {"00": 0.5, "11": 0.5}
num_qubits_list = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 127]
circuits = []
for num_qubits in num_qubits_list:
circuit = QuantumCircuit(num_qubits, 2)
circuit.h(0)
for i in range(num_qubits - 1):
circuit.cx(i, i + 1)
circuit.barrier()
circuit.measure(0, 0)
circuit.measure(num_qubits - 1, 1)
circuits.append(circuit)
circuits[-1].draw(output="mpl", style="clifford", fold=-1)Output:
バックエンドと結合マップを設定する
まず、バックエンドを選択します
# To run on hardware, select the backend with the fewest number of jobs in the queue
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
qubits = list(range(backend.num_qubits))そして、その結合写像を求める
coupling_graph = backend.coupling_map.graph.to_undirected(multigraph=False)
# Get unidirectional coupling map
one_dir_coupling_map = coupling_graph.edge_list()できるだけ多くの2量子ビットゲートを同時にベンチマークするために、カップリング・マップを layered_coupling_map。 このオブジェクトはレイヤーのリストを含み、各レイヤーは2量子ビットゲートを同時に実行できるエッジのリストである。 これはカップリング・マップのエッジ・カラーリングとも呼ばれる。
# Get layered coupling map
edge_coloring = rustworkx.graph_bipartite_edge_color(coupling_graph)
layered_coupling_map = defaultdict(list)
for edge_idx, color in edge_coloring.items():
layered_coupling_map[color].append(
coupling_graph.get_edge_endpoints_by_index(edge_idx)
)
layered_coupling_map = [
sorted(layered_coupling_map[i])
for i in sorted(layered_coupling_map.keys())
]特性評価実験
QPUにおける量子ビットの主な特性を明らかにするために、一連の実験が行われる。 これらは、 、 、読み出しエラー、1量子ビットと2量子ビットのゲートエラーである。 ここでは、これらの特性がどのようなものかを簡単に要約し、その特性を明らかにするために使われたパッケージ内の実験を紹介する。 qiskit-experiments パッケージの実験を参照する。
T1
は、振幅減衰デコヒーレンス過程により、励起された量子ビットが基底状態に落ちるまでの特徴的な時間である。 実験では、励起された量子ビットを遅延後に測定する。 遅延時間が大きいほど 基底状態に落ちる可能性が高くなる。 実験の目的は 実験の目的は、量子ビットの基底状態への減衰率を評価することである。 基底状態への
T2
は、1つの量子ビットのブロッホベクトルのXY平面上への投影が ベクトル投影がXY平面上で初期振幅の約37%( )まで減少するのに必要な時間を表しています。 まで低下するのに必要な時間を表しています。 ハーンエコー実験では、この減衰の速度を見積もることができる。
状態準備と測定(SPAM)誤差の特性評価
SPAMエラー特性実験では、量子ビットを特定の状態( または )に準備し、測定する。 そして、準備された状態とは異なる状態を測定する確率が、誤差の確率となる。
単一量子ビットおよび二量子ビットのランダム化ベンチマーキング
ランダム化ベンチマーク(RB )は、量子プロセッサのエラーレートを評価するための一般的なプロトコルである。 量子プロセッサの RB実験とは、与えられた量子ビット上にランダムなクリフォード回路を生成する実験である。 回路によって計算されるユニタリーが恒等式となるような、与えられた量子ビット上のランダムなクリフォード回路を生成することである。 である。 回路を走らせた後、エラー(つまり、基底状態とは異なる出力)が生じたショットの数をカウントし、このデータからError Per Cliffordを計算することで、量子デバイスのエラー推定値を推測することができる。
# Create T1 experiments on all qubit in parallel
t1_exp = ParallelExperiment(
[
T1(
physical_qubits=[qubit],
delays=[1e-6, 20e-6, 40e-6, 80e-6, 200e-6, 400e-6],
)
for qubit in qubits
],
backend,
analysis=None,
)
# Create T2-Hahn experiments on all qubit in parallel
t2_exp = ParallelExperiment(
[
T2Hahn(
physical_qubits=[qubit],
delays=[1e-6, 20e-6, 40e-6, 80e-6, 200e-6, 400e-6],
)
for qubit in qubits
],
backend,
analysis=None,
)
# Create readout experiments on all qubit in parallel
readout_exp = LocalReadoutError(qubits)
# Create single-qubit RB experiments on all qubit in parallel
singleq_rb_exp = ParallelExperiment(
[
StandardRB(
physical_qubits=[qubit], lengths=[10, 100, 500], num_samples=10
)
for qubit in qubits
],
backend,
analysis=None,
)
# Create two-qubit RB experiments on the three layers of disjoint edges of the heavy-hex
twoq_rb_exp_batched = BatchExperiment(
[
ParallelExperiment(
[
StandardRB(
physical_qubits=pair,
lengths=[10, 50, 100],
num_samples=10,
)
for pair in layer
],
backend,
analysis=None,
)
for layer in layered_coupling_map
],
backend,
flatten_results=True,
analysis=None,
)QPUの経時特性
報告されたQPUのプロパティを時系列で見てみると(以下、1週間を考えてみる)、1日単位でどのように変動するかがわかる。 小さな変動は1日の中でも起こりうる。 このシナリオでは、報告されたプロパティ(1日1回更新)では、QPUの現在の状態を正確に把握することはできない。 さらに、ジョブがローカルで(現在報告されているプロパティを使用して)トランスパイルされ、サブミットされたが、後の時間(数分または数日)にのみ実行された場合、トランスパイルステップで量子ビットの選択に古いプロパティが使用された危険性がある。 これは、実行時にQPUに関する最新情報を持つことの重要性を浮き彫りにしている。 まず、ある時間範囲のプロパティを検索してみよう。
instruction_2q_name = "cz" # set the name of the default 2q of the device
errors_list = []
for day_idx in range(10, 17):
calibrations_time = datetime(
year=2025, month=8, day=day_idx, hour=0, minute=0, second=0
)
targer_hist = backend.target_history(datetime=calibrations_time)
t1_dict, t2_dict = {}, {}
for qubit in range(targer_hist.num_qubits):
t1_dict[qubit] = targer_hist.qubit_properties[qubit].t1
t2_dict[qubit] = targer_hist.qubit_properties[qubit].t2
errors_dict = {
"1q": targer_hist["sx"],
"2q": targer_hist[f"{instruction_2q_name}"],
"spam": targer_hist["measure"],
"t1": t1_dict,
"t2": t2_dict,
}
errors_list.append(errors_dict)次に、値をプロットしてみよう
fig, axs = plt.subplots(5, 1, figsize=(10, 20), sharex=False)
# Plot for T1 values
for qubit in range(targer_hist.num_qubits):
t1s = []
for errors_dict in errors_list:
t1_dict = errors_dict["t1"]
try:
t1s.append(t1_dict[qubit] / 1e-6)
except:
print(f"missing t1 data for qubit {qubit}")
axs[0].plot(t1s)
axs[0].set_title("T1")
axs[0].set_ylabel(r"Time ($\mu s$)")
axs[0].set_xlabel("Days")
# Plot for T2 values
for qubit in range(targer_hist.num_qubits):
t2s = []
for errors_dict in errors_list:
t2_dict = errors_dict["t2"]
try:
t2s.append(t2_dict[qubit] / 1e-6)
except:
print(f"missing t2 data for qubit {qubit}")
axs[1].plot(t2s)
axs[1].set_title("T2")
axs[1].set_ylabel(r"Time ($\mu s$)")
axs[1].set_xlabel("Days")
# Plot SPAM values
for qubit in range(targer_hist.num_qubits):
spams = []
for errors_dict in errors_list:
spam_dict = errors_dict["spam"]
spams.append(spam_dict[tuple([qubit])].error)
axs[2].plot(spams)
axs[2].set_title("SPAM Errors")
axs[2].set_ylabel("Error Rate")
axs[2].set_xlabel("Days")
# Plot 1Q Gate Errors
for qubit in range(targer_hist.num_qubits):
oneq_gates = []
for errors_dict in errors_list:
oneq_gate_dict = errors_dict["1q"]
oneq_gates.append(oneq_gate_dict[tuple([qubit])].error)
axs[3].plot(oneq_gates)
axs[3].set_title("1Q Gate Errors")
axs[3].set_ylabel("Error Rate")
axs[3].set_xlabel("Days")
# Plot 2Q Gate Errors
for pair in one_dir_coupling_map:
twoq_gates = []
for errors_dict in errors_list:
twoq_gate_dict = errors_dict["2q"]
twoq_gates.append(twoq_gate_dict[pair].error)
axs[4].plot(twoq_gates)
axs[4].set_title("2Q Gate Errors")
axs[4].set_ylabel("Error Rate")
axs[4].set_xlabel("Days")
plt.subplots_adjust(hspace=0.5)
plt.show()Output:
数日間で、量子ビットの特性のいくつかが大きく変化することがわかるだろう。 このことは、実験に最適な性能の量子ビットを選択できるようにするために、QPUの状態に関する新鮮な情報を持つことの重要性を浮き彫りにしている。
ステップ2:量子ハードウェア実行に向けた問題の最適化
このチュートリアルでは、回路や演算子の最適化は行わない。
ステップ3: Qiskit primitivesを使用して実行する
デフォルトの量子ビット選択で量子回路を実行する
パフォーマンスの参考結果として、要求されたバックエンドのプロパティで選択された量子ビットであるデフォルトの量子ビットを使用して、QPU上で量子回路を実行します。 optimization_level = 3 を使用する。 この設定には、最も高度なトランスパイル・オプティマイゼーションが含まれており、ターゲット・プロパティ(オペレーション・エラーなど)を使用して、実行に最も適した性能の量子ビットを選択します。
pm = generate_preset_pass_manager(target=backend.target, optimization_level=3)
isa_circuits = pm.run(circuits)
initial_qubits = [
[
idx
for idx, qb in circuit.layout.initial_layout.get_physical_bits().items()
if qb._register.name != "ancilla"
]
for circuit in isa_circuits
]リアルタイム量子ビット選択による量子回路の実行
このセクションでは、最適な結果を得るために、QPUの量子ビット特性に関する最新の情報を持つことの重要性を調査する。 まず、QPUの特性評価実験一式( 、 、SPAM、1量子ビットRB、2量子ビットRB)を実施し、これを用いてバックエンドの特性を更新する。 これにより、パス・マネージャーはQPUに関する新鮮な情報に基づいて実行用の量子ビットを選択できるようになり、実行性能が向上する可能性がある。 次に、ベルペア回路を実行し、更新QPU特性を用いて量子ビットを選択した後に得られた忠実度を、デフォルトで報告されている特性を用いて量子ビットを選択した場合に得られた忠実度と比較します。
フィッティング・ルーチンが測定データに曲線をフィットさせることができない場合、特性評価実験の一部が失敗する可能性があることに注意。 これらの実験で警告が表示された場合は、どの量子ビットでどのキャラクタリゼーションが失敗したかを理解するためにそれらを検査し、実験のパラメータ( 、 、またはRB実験の数の長さのような)を調整してみてください。
# Prepare characterization experiments
batches = [t1_exp, t2_exp, readout_exp, singleq_rb_exp, twoq_rb_exp_batched]
batches_exp = BatchExperiment(batches, backend) # , analysis=None)
run_options = {"shots": 1e3, "dynamic": False}
with Session(backend=backend) as session:
sampler = SamplerV2(mode=session)
# Run characterization experiments
batches_exp_data = batches_exp.run(
sampler=sampler, **run_options
).block_for_results()
EPG_sx_result_list = batches_exp_data.analysis_results("EPG_sx")
EPG_sx_result_q_indices = [
result.device_components.index for result in EPG_sx_result_list
]
EPG_x_result_list = batches_exp_data.analysis_results("EPG_x")
EPG_x_result_q_indices = [
result.device_components.index for result in EPG_x_result_list
]
T1_result_list = batches_exp_data.analysis_results("T1")
T1_result_q_indices = [
result.device_components.index for result in T1_result_list
]
T2_result_list = batches_exp_data.analysis_results("T2")
T2_result_q_indices = [
result.device_components.index for result in T2_result_list
]
Readout_result_list = batches_exp_data.analysis_results(
"Local Readout Mitigator"
)
EPG_2q_result_list = batches_exp_data.analysis_results(
f"EPG_{instruction_2q_name}"
)
# Update target properties
target = copy.deepcopy(backend.target)
for i in range(target.num_qubits - 1):
qarg = (i,)
if qarg in EPG_sx_result_q_indices:
target.update_instruction_properties(
instruction="sx",
qargs=qarg,
properties=InstructionProperties(
error=EPG_sx_result_list[i].value.nominal_value
),
)
if qarg in EPG_x_result_q_indices:
target.update_instruction_properties(
instruction="x",
qargs=qarg,
properties=InstructionProperties(
error=EPG_x_result_list[i].value.nominal_value
),
)
err_mat = Readout_result_list.value.assignment_matrix(i)
readout_assignment_error = (
err_mat[0, 1] + err_mat[1, 0]
) / 2 # average readout error
target.update_instruction_properties(
instruction="measure",
qargs=qarg,
properties=InstructionProperties(error=readout_assignment_error),
)
if qarg in T1_result_q_indices:
target.qubit_properties[i].t1 = T1_result_list[
i
].value.nominal_value
if qarg in T2_result_q_indices:
target.qubit_properties[i].t2 = T2_result_list[
i
].value.nominal_value
for pair_idx, pair in enumerate(one_dir_coupling_map):
qarg = tuple(pair)
try:
target.update_instruction_properties(
instruction=instruction_2q_name,
qargs=qarg,
properties=InstructionProperties(
error=EPG_2q_result_list[pair_idx].value.nominal_value
),
)
except:
target.update_instruction_properties(
instruction=instruction_2q_name,
qargs=qarg[::-1],
properties=InstructionProperties(
error=EPG_2q_result_list[pair_idx].value.nominal_value
),
)
# transpile circuits to updated target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
isa_circuit_updated = pm.run(circuits)
updated_qubits = [
[
idx
for idx, qb in circuit.layout.initial_layout.get_physical_bits().items()
if qb._register.name != "ancilla"
]
for circuit in isa_circuit_updated
]
n_trials = 3 # run multiple trials to see variations
# interleave circuits
interleaved_circuits = []
for original_circuit, updated_circuit in zip(
isa_circuits, isa_circuit_updated
):
interleaved_circuits.append(original_circuit)
interleaved_circuits.append(updated_circuit)
# Run circuits
# Set simple error suppression/mitigation options
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XY4"
job_interleaved = sampler.run(interleaved_circuits * n_trials)ステップ4:後処理を行い、結果を希望の古典形式で返す
最後に、2つの異なる設定で得られたベル状態の忠実度を比較してみよう:
originalつまり、バックエンドの報告された特性に基づいてトランスパイラが選択したデフォルトの量子ビットを使用する。updatedつまり、特性実験が実行された後、バックエンドの更新された特性に基づいて選択された量子ビットである。
results = job_interleaved.result()
all_fidelity_list, all_fidelity_updated_list = [], []
for exp_idx in range(n_trials):
fidelity_list, fidelity_updated_list = [], []
for idx, num_qubits in enumerate(num_qubits_list):
pub_result_original = results[
2 * exp_idx * len(num_qubits_list) + 2 * idx
]
pub_result_updated = results[
2 * exp_idx * len(num_qubits_list) + 2 * idx + 1
]
fid = hellinger_fidelity(
ideal_dist, pub_result_original.data.c.get_counts()
)
fidelity_list.append(fid)
fid_up = hellinger_fidelity(
ideal_dist, pub_result_updated.data.c.get_counts()
)
fidelity_updated_list.append(fid_up)
all_fidelity_list.append(fidelity_list)
all_fidelity_updated_list.append(fidelity_updated_list)plt.figure(figsize=(8, 6))
plt.errorbar(
num_qubits_list,
np.mean(all_fidelity_list, axis=0),
yerr=np.std(all_fidelity_list, axis=0),
fmt="o-.",
label="original",
color="b",
)
# plt.plot(num_qubits_list, fidelity_list, '-.')
plt.errorbar(
num_qubits_list,
np.mean(all_fidelity_updated_list, axis=0),
yerr=np.std(all_fidelity_updated_list, axis=0),
fmt="o-.",
label="updated",
color="r",
)
# plt.plot(num_qubits_list, fidelity_updated_list, '-.')
plt.xlabel("Chain length")
plt.xticks(num_qubits_list)
plt.ylabel("Fidelity")
plt.title("Bell pair fidelity at the edge of N-qubits chain")
plt.legend()
plt.grid(
alpha=0.2,
linestyle="-.",
)
plt.show()Output:
また、チェーンの長さが長くなり、物理量子ビットを選択する自由度が減ると、更新されたデバイス情報の重要性は低下する。 しかし、その性能を理解するために、デバイスの特性に関する新鮮なデータを収集することは良い習慣である。 時折、過渡的な2レベルシステムが一部の量子ビットの性能に影響を与えることがある。 リアルタイムのデータは、そのような事象がいつ起こっているかを知らせてくれ、そのような場合に実験的失敗を避けるのに役立つ。
この方法を実行に移し、どれだけの効果が得られるか試してみよう! また、異なるバックエンドからどれだけの改善が得られるか試してみることもできる。
チュートリアル調査
このチュートリアルに関するご意見・ご感想をお寄せください。 あなたの洞察は、私たちのコンテンツの提供とユーザーエクスペリエンスを向上させるのに役立ちます。