Sari la conținutul principal

Benchmarking în timp real pentru selecția qubiților

Estimare de utilizare: 4 minute pe un procesor Eagle r2 (NOTĂ: Aceasta este doar o estimare. Timpul tău de execuție poate varia.)

# 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

Fundal

Acest tutorial arată cum să rulezi experimente de caracterizare în timp real și să actualizezi proprietățile backend-ului pentru a îmbunătăți selecția qubiților la maparea unui circuit pe qubiții fizici ai unui QPU. Vei învăța experimentele de bază de caracterizare folosite pentru a determina proprietățile QPU-ului, cum să le realizezi în Qiskit și cum să actualizezi proprietățile salvate în obiectul backend care reprezintă QPU-ul pe baza acestor experimente.

Proprietățile raportate de QPU sunt actualizate o dată pe zi, dar sistemul poate deriva mai rapid decât intervalul dintre actualizări. Acest lucru poate afecta fiabilitatea rutinelor de selecție a qubiților din etapa Layout a pass manager-ului, deoarece acestea ar folosi proprietăți raportate care nu reflectă starea curentă a QPU-ului. Din acest motiv, poate merita să aloci timp QPU pentru experimente de caracterizare, care pot fi ulterior folosite pentru a actualiza proprietățile QPU-ului utilizate de rutina Layout.

Cerințe

Înainte de a începe acest tutorial, asigură-te că ai instalate următoarele:

  • Qiskit SDK v2.0 sau o versiune mai recentă, cu suport pentru vizualizare
  • Qiskit Runtime v0.40 sau mai recent ( pip install qiskit-ibm-runtime )
  • Qiskit Experiments v0.12 sau mai recent ( pip install qiskit-experiments )
  • Biblioteca de grafuri Rustworkx (pip install rustworkx)

Configurare

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

Pasul 1: Maparea intrărilor clasice pe o problemă cuantică

Pentru a măsura diferența de performanță, considerăm un circuit care pregătește o stare Bell de-a lungul unui lanț liniar de lungime variabilă. Se măsoară fidelitatea stării Bell la capetele lanțului.

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 of the previous code cell

Output of the previous code cell

Configurarea backend-ului și a hărții de cuplaj

Mai întâi, selectează un 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))

Apoi obține harta sa de cuplaj

coupling_graph = backend.coupling_map.graph.to_undirected(multigraph=False)

# Get unidirectional coupling map
one_dir_coupling_map = coupling_graph.edge_list()

Pentru a putea efectua benchmark pe cât mai multe porți cu doi qubiți simultan, separăm harta de cuplaj într-o layered_coupling_map. Acest obiect conține o listă de straturi, unde fiecare strat este o listă de muchii pe care porțile cu doi qubiți pot fi executate în același timp. Aceasta se mai numește și o colorare a muchiilor hărții de cuplaj.

# 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())
]

Experimente de caracterizare

O serie de experimente este utilizată pentru a caracteriza principalele proprietăți ale qubiților dintr-un QPU. Acestea sunt T1T_1, T2T_2, eroarea de citire și eroarea de poartă single-qubit și two-qubit. Vom rezuma pe scurt ce reprezintă aceste proprietăți și vom face referire la experimentele din pachetul qiskit-experiments folosite pentru a le caracteriza.

T1

T1T_1 este timpul caracteristic în care un Qubit excitat revine la starea de bază datorită proceselor de decoerență prin amortizare de amplitudine. Într-un experiment T1T_1, măsurăm un Qubit excitat după o întârziere. Cu cât întârzierea este mai mare, cu atât este mai probabil ca Qubit-ul să revină la starea de bază. Scopul experimentului este de a caracteriza rata de decădere a Qubit-ului spre starea de bază.

T2

T2T_2 reprezintă timpul necesar pentru ca proiecția vectorului Bloch al unui singur Qubit pe planul XY să scadă la aproximativ 37% (1e\frac{1}{e}) din amplitudinea sa inițială, datorită proceselor de decoerență prin defazare. Într-un experiment T2 Hahn Echo, putem estima rata acestei decăderi.

Caracterizarea erorii de pregătire și măsurare a stării (SPAM)

Într-un experiment de caracterizare a erorii SPAM, qubiții sunt pregătiți într-o anumită stare (0\vert 0 \rangle sau 1\vert 1 \rangle) și măsurați. Probabilitatea de a măsura o stare diferită de cea pregătită oferă astfel probabilitatea erorii.

Benchmarking aleatoriu single-qubit și two-qubit

Benchmarking-ul aleatoriu (RB) este un protocol popular pentru caracterizarea ratei de eroare a procesoarelor cuantice. Un experiment RB constă în generarea unor circuite Clifford aleatorii pe qubiții dați, astfel încât unitara calculată de circuite să fie identitatea. După rularea circuitelor, se numără numărul de shot-uri care produc o eroare (adică o ieșire diferită de starea de bază), iar din aceste date se pot deduce estimări ale erorilor pentru dispozitivul cuantic, calculând Eroarea 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,
)

Proprietățile QPU-ului în timp

Privind proprietățile raportate ale QPU-ului în timp (vom considera mai jos o perioadă de o săptămână), observăm cum acestea pot fluctua la scara unei singure zile. Fluctuații mici pot apărea chiar și în cursul unei zile. În acest scenariu, proprietățile raportate (actualizate o dată pe zi) nu vor surprinde cu precizie starea curentă a QPU-ului. Mai mult, dacă un job este transpilat local (folosind proprietățile raportate la momentul respectiv) și trimis, dar executat mai târziu (minute sau zile), există riscul de a fi folosit proprietăți depășite pentru selecția qubiților în etapa de transpilare. Aceasta subliniază importanța de a dispune de informații actualizate despre QPU la momentul execuției. Mai întâi, să recuperăm proprietățile pe un anumit interval de timp.

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)

Apoi să reprezentăm grafic valorile

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 of the previous code cell

Se poate observa că pe parcursul mai multor zile, unele proprietăți ale qubiților se pot schimba considerabil. Aceasta subliniază importanța de a dispune de informații proaspete despre starea QPU-ului, pentru a putea selecta qubiții cu cea mai bună performanță pentru un experiment.

Pasul 2: Optimizează problema pentru execuția pe hardware cuantic

Nu se efectuează nicio optimizare a circuitelor sau operatorilor în acest tutorial.

Pasul 3: Execută folosind primitivele Qiskit

Execută un Circuit cuantic cu selecția implicită a Qubiților

Ca referință pentru performanță, vom executa un Circuit cuantic pe un QPU folosind Qubiții impliciți, adică cei selectați pe baza proprietăților raportate de Backend. Vom folosi optimization_level = 3. Această setare include cele mai avansate optimizări de transpilare și utilizează proprietățile țintă (cum ar fi erorile de operație) pentru a selecta Qubiții cu cele mai bune performanțe pentru execuție.

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
]

Execută un Circuit cuantic cu selecția în timp real a Qubiților

În această secțiune, vom analiza importanța de a deține informații actualizate despre proprietățile Qubiților QPU pentru rezultate optime. În primul rând, vom efectua o suită completă de experimente de caracterizare a QPU (T1T_1, T2T_2, SPAM, RB pe un singur Qubit și RB pe doi Qubiți), pe care le putem folosi ulterior pentru a actualiza proprietățile Backend-ului. Aceasta permite pass manager-ului să selecteze Qubiții pentru execuție pe baza unor informații proaspete despre QPU, îmbunătățind potențial performanțele execuției. În al doilea rând, executăm Circuitul de perechi Bell și comparăm fidelitatea obținută după selectarea Qubiților cu proprietățile QPU actualizate față de fidelitatea obținută anterior când am folosit proprietățile raportate implicit pentru selecția Qubiților.

atenție

Reține că unele dintre experimentele de caracterizare pot eșua atunci când rutina de ajustare nu poate potrivi o curbă la datele măsurate. Dacă observi avertismente provenite din aceste experimente, inspectează-le pentru a înțelege care caracterizare a eșuat pe care Qubiți și încearcă să ajustezi parametrii experimentului (cum ar fi timpii pentru T1T_1, T2T_2, sau lungimile numărate ale experimentelor 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)

Pasul 4: Post-procesează și returnează rezultatul în formatul clasic dorit

În final, să comparăm fidelitatea stării Bell obținute în cele două scenarii diferite:

  • original, adică folosind Qubiții impliciți aleși de Transpiler pe baza proprietăților raportate ale Backend-ului.
  • updated, adică folosind Qubiții aleși pe baza proprietăților actualizate ale Backend-ului după ce experimentele de caracterizare au rulat.
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 of the previous code cell

Nu toate rulările vor arăta o îmbunătățire a performanței datorită caracterizării în timp real — iar pe măsură ce lungimea lanțului crește și, astfel, libertatea de a alege Qubiți fizici scade, importanța informațiilor actualizate despre dispozitiv devine mai puțin semnificativă. Cu toate acestea, este o bună practică să colectezi date proaspete despre proprietățile dispozitivului pentru a-i înțelege performanța. Ocazional, sisteme tranzitorii cu două niveluri pot afecta performanța unor Qubiți. Datele în timp real ne pot informa când se produc astfel de evenimente și ne pot ajuta să evităm eșecurile experimentale în astfel de situații.

Îndemn la acțiune

Încearcă să aplici această metodă la execuțiile tale și determină cât de mult beneficiezi! Poți de asemenea să experimentezi și să vezi ce îmbunătățiri obții de la diferite Backend-uri.

Sondaj tutorial

Te rog să completezi acest scurt sondaj pentru a oferi feedback despre acest tutorial. Părerile tale ne vor ajuta să îmbunătățim conținutul și experiența utilizatorilor.

Link către sondaj

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.