Benchmarking qubitů v reálném čase pro výběr qubitů
Odhad využití: 4 minuty na procesoru Eagle r2 (POZNÁMKA: Jde pouze o odhad. Skutečný čas běhu se může lišit.)
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-experiments qiskit-ibm-runtime rustworkx
# This cell is hidden from users – it disables some lint rules
# ruff: noqa: E722
Pozadí
Tento tutoriál ukazuje, jak spustit experimenty charakterizace v reálném čase a aktualizovat vlastnosti backendu pro lepší výběr qubitů při mapování Circuit na fyzické qubity QPU. Naučíš se základní charakterizační experimenty, které slouží ke zjištění vlastností QPU, jak je provést v Qiskitu a jak aktualizovat vlastnosti uložené v objektu backendu reprezentujícím QPU na základě těchto experimentů.
Vlastnosti hlášené QPU jsou aktualizovány jednou denně, ale systém se může měnit rychleji než je interval mezi aktualizacemi. To může ovlivnit spolehlivost rutin výběru qubitů ve fázi Layout správce průchodů, protože by používaly hlášené vlastnosti, které neodpovídají aktuálnímu stavu QPU. Z tohoto důvodu může být vhodné věnovat část času QPU charakterizačním experimentům, které pak lze použít k aktualizaci vlastností QPU využívaných rutinou Layout.
Požadavky
Před zahájením tohoto tutoriálu se ujisti, že máš nainstalováno:
- Qiskit SDK v2.0 nebo novější, s podporou vizualizace
- Qiskit Runtime v0.40 nebo novější (
pip install qiskit-ibm-runtime) - Qiskit Experiments v0.12 nebo novější (
pip install qiskit-experiments) - Grafová knihovna Rustworkx (
pip install rustworkx)
Nastavení
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
Krok 1: Mapování klasických vstupů na kvantový problém
Pro porovnání rozdílů ve výkonu uvažujeme Circuit, který připravuje Bellův stav na lineárním řetězci proměnné délky. Měří se věrnost Bellova stavu na koncích řetězce.
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)


Nastavení backendu a coupling map
Nejprve vyber Backend
# 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))
Pak získej jeho coupling map
coupling_graph = backend.coupling_map.graph.to_undirected(multigraph=False)
# Get unidirectional coupling map
one_dir_coupling_map = coupling_graph.edge_list()
Abychom mohli současně benchmarkovat co nejvíce dvouqubitových Gate, rozdělíme coupling map na layered_coupling_map. Tento objekt obsahuje seznam vrstev, přičemž každá vrstva je seznam hran, na nichž lze současně provádět dvouqubitové Gate. Říká se tomu také hranové barvení coupling map.
# 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())
]
Charakterizační experimenty
K charakterizaci hlavních vlastností qubitů v QPU slouží řada experimentů. Jsou to , , chyba čtení a chyba jednoqubitového a dvouqubitového Gate. Stručně si shrneme, co tyto vlastnosti jsou, a odkážeme na experimenty z balíčku qiskit-experiments, které se k jejich charakterizaci používají.
T1
je charakteristická doba, za kterou excitovaný Qubit klesne do základního stavu vlivem dekoherenčních procesů tlumení amplitudy. V experimentu měříme excitovaný Qubit po uplynutí prodlevy. Čím větší je doba prodlevy, tím pravděpodobnější je, že Qubit klesne do základního stavu. Cílem experimentu je charakterizovat rychlost rozpadání Qubitu do základního stavu.
T2
představuje dobu potřebnou k tomu, aby projekce Blochova vektoru jednoho Qubitu na rovinu XY klesla na přibližně 37 % () své počáteční amplitudy vlivem dekoherenčních procesů dephazingu. V experimentu Hahn Echo můžeme odhadnout rychlost tohoto rozpadu.
Charakterizace chyby přípravy stavu a měření (SPAM)
V experimentu charakterizace chyby SPAM jsou qubity připraveny do určitého stavu ( nebo ) a změřeny. Pravděpodobnost naměření stavu odlišného od připraveného pak udává pravděpodobnost chyby.
Randomizované benchmarking jednoqubitových a dvouqubitových Gate
Randomizované benchmarking (RB) je oblíbený protokol pro charakterizaci chybovosti kvantových procesorů. Experiment RB spočívá ve generování náhodných Cliffordových Circuit na daných qubitech tak, aby unitární operace vypočítaná Circuit byla identita. Po spuštění Circuit se spočítá počet shotů, které skončily chybou (tj. výstupem odlišným od základního stavu), a z těchto dat lze odvodit odhady chybovosti kvantového zařízení výpočtem chyby na 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,
)
Vlastnosti QPU v průběhu času
Při pohledu na hlášené vlastnosti QPU v průběhu času (níže uvažujeme jeden týden) vidíme, jak mohou kolísat v rozsahu jediného dne. Malé výkyvy mohou nastat i během dne. V takovém scénáři hlášené vlastnosti (aktualizované jednou denně) nepřesně zachycují aktuální stav QPU. Navíc pokud je úloha transpilována lokálně (s použitím aktuálně hlášených vlastností) a odeslána, ale spuštěna až později (za minuty nebo dny), hrozí, že při výběru qubitů v transpilačním kroku byly použity zastaralé vlastnosti. To zdůrazňuje důležitost mít aktuální informace o QPU v době spuštění. Nejprve si načteme vlastnosti za určitý časový rozsah.
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)
Pak si hodnoty vykreslíme
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()

Vidíš, že během několika dní se některé vlastnosti qubitů mohou výrazně změnit. To zdůrazňuje důležitost mít čerstvé informace o stavu QPU, abychom mohli pro experiment vybrat nejlépe fungující qubity.
Krok 2: Optimalizace problému pro spuštění na kvantovém hardwaru
V tomto tutoriálu neprovádíme žádnou optimalizaci Circuit nebo operátorů.
Krok 3: Spuštění pomocí primitiv Qiskit
Spuštění kvantového Circuit s výchozím výběrem Qubitů
Jako referenční výsledek výkonu spustíme kvantový Circuit na QPU s použitím výchozích Qubitů, tedy Qubitů vybraných na základě požadovaných vlastností Backendu. Použijeme optimization_level = 3. Toto nastavení zahrnuje nejpokročilejší optimalizaci transpilace a využívá vlastnosti cíle (jako jsou chyby operací) k výběru nejlépe fungujících Qubitů pro spuštění.
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
]
Spuštění kvantového Circuit s výběrem Qubitů v reálném čase
V této části prozkoumáme, jak důležité je mít aktuální informace o vlastnostech Qubitů QPU pro dosažení optimálních výsledků. Nejprve provedeme úplnou sadu experimentů pro charakterizaci QPU (, , SPAM, jednoqubitové RB a dvouqubitové RB), které pak použijeme k aktualizaci vlastností Backendu. To umožní pass manageru vybrat Qubitové pro spuštění na základě čerstvých informací o QPU, čímž se případně zlepší výkon spuštění. Za druhé spustíme Circuit s Bell párem a porovnáme věrnost získanou po výběru Qubitů s aktualizovanými vlastnostmi QPU s věrností, které jsme dosáhli při použití výchozích nahlášených vlastností pro výběr Qubitů.
Všimni si, že některé charakterizační experimenty mohou selhat, pokud rutina fitování nedokáže proložit křivku naměřenými daty. Pokud vidíš varování pocházející z těchto experimentů, prozkoumej je a zjisti, která charakterizace selhala na kterých Qubitech, a zkus upravit parametry experimentu (například časy pro , nebo počty délek RB experimentů).
# 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)
Krok 4: Post-processing a vrácení výsledku v požadovaném klasickém formátu
Nakonec porovnáme věrnost Bell stavu dosaženou ve dvou různých nastaveních:
original, tedy s výchozími Qubitami vybranými Transpilerem na základě nahlášených vlastností Backendu.updated, tedy s Qubitami vybranými na základě aktualizovaných vlastností Backendu po provedení charakterizačních experimentů.
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()

Ne každý běh ukáže zlepšení výkonu díky charakterizaci v reálném čase — a s rostoucí délkou řetězce, a tedy menší volností při výběru fyzických Qubitů, se důležitost aktualizovaných informací o zařízení snižuje. Je však dobrou praxí sbírat čerstvá data o vlastnostech zařízení, abychom porozuměli jeho výkonu. Občas mohou přechodné dvouúrovňové systémy ovlivnit výkon některých Qubitů. Data v reálném čase nás mohou informovat, když k takovým událostem dochází, a pomoci nám vyhnout se experimentálním selháním v takových případech.
Zkus tuto metodu aplikovat na svá vlastní spuštění a zjisti, jaký přínos ti přináší! Můžeš také vyzkoušet, jak velká zlepšení získáš z různých Backendů.
Průzkum k tutoriálu
Vyplň prosím tento krátký průzkum a poskytni nám zpětnou vazbu k tomuto tutoriálu. Tvoje postřehy nám pomohou zlepšit naši nabídku obsahu a uživatelský zážitek.
Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.