Evaluación comparativa en tiempo real para la selección de qubits
Estimación de uso: 4 minutos en un procesador Eagle r2 (NOTA: Esto es sólo una estimación. Su tiempo de ejecución puede variar)
En segundo plano
Este tutorial muestra cómo ejecutar experimentos de caracterización en tiempo real y actualizar las propiedades del backend para mejorar la selección de qubits cuando se mapea un circuito a los qubits físicos en una QPU. Aprenderás los experimentos básicos de caracterización que se utilizan para determinar las propiedades de la QPU, cómo realizarlos en Qiskit y cómo actualizar las propiedades guardadas en el objeto backend que representa a la QPU en función de estos experimentos.
Las propiedades notificadas por QPU se actualizan una vez al día, pero el sistema puede desviarse más rápido que el tiempo entre actualizaciones. Esto puede afectar a la fiabilidad de las rutinas de selección de qubits en la etapa Layout del gestor de pases, ya que estarían utilizando propiedades notificadas que no representan el estado actual de la QPU. Por esta razón, puede merecer la pena dedicar algo de tiempo de la QPU a experimentos de caracterización, que luego pueden utilizarse para actualizar las propiedades de la QPU utilizadas por la rutina Layout .
Requisitos
Antes de empezar este tutorial, asegúrate de tener instalado lo siguiente:
- Qiskit SDK v2.0 o posterior, con soporte de visualización
- Qiskit Runtime v0.40 o posterior (
pip install qiskit-ibm-runtime) - Experimentos Qiskit v0.12 o posterior (
pip install qiskit-experiments) - Biblioteca gráfica Rustworkx (
pip install rustworkx)
Configuración
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 copyPaso 1: Asignar entradas clásicas a un problema cuántico
Para evaluar la diferencia de rendimiento, consideramos un circuito que prepara un estado Bell a través de una cadena lineal de longitud variable. Se mide la fidelidad del estado Bell en los extremos de la cadena.
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:
Configurar el backend y el mapa de acoplamiento
En primer lugar, seleccione 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))A continuación, obtenga su mapa de acoplamiento
coupling_graph = backend.coupling_map.graph.to_undirected(multigraph=False)
# Get unidirectional coupling map
one_dir_coupling_map = coupling_graph.edge_list()Para poder comparar simultáneamente tantas puertas de dos qubits como sea posible, separamos el mapa de acoplamiento en layered_coupling_map. Este objeto contiene una lista de capas donde cada capa es una lista de aristas en las que se pueden ejecutar puertas de dos qubits al mismo tiempo. También se denomina coloración de los bordes del mapa de acoplamiento.
# 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())
]Experimentos de caracterización
Se utiliza una serie de experimentos para caracterizar las principales propiedades de los qubits en una QPU. Se trata de , , error de lectura y error de puerta de uno y dos qubits. Resumiremos brevemente cuáles son estas propiedades y haremos referencia a los experimentos del paquete qiskit-experiments que se utilizan para caracterizarlas.
T1
es el tiempo característico que tarda un qubit excitado en caer al estado de reposo debido a los procesos de decoherencia de amortiguación de amplitud. En un experimento , medimos un qubit excitado tras un retardo. Cuanto mayor sea el tiempo de retardo, más probable es que el qubit caiga al estado de masa. El objetivo del experimento es caracterizar la tasa de decaimiento del qubit hacia el hacia el estado básico.
T2
representa el tiempo necesario para que la proyección del vector Bloch de un qubit en el plano XY caiga hasta aproximadamente el 37% ( ) de su amplitud inicial debido a los procesos de decoherencia de desfase de su amplitud inicial debido a procesos de decoherencia. En un experimento de Eco Hahn , podemos estimar la velocidad de esta desintegración.
Caracterización del error de preparación y medición del estado (SPAM)
En un experimento de caracterización de errores SPAM, los qubit se preparan en un estado determinado ( o ) y se miden. La probabilidad de medir un estado distinto del preparado da entonces la probabilidad del error.
Evaluación comparativa aleatoria de un solo qubit y dos qubits
La evaluación comparativa aleatoria (RB ) es un protocolo popular para caracterizar la tasa de error de los procesadores cuánticos procesadores cuánticos. Un experimento RB consiste en la generación de circuitos Clifford en los qubits dados de forma que el unitario calculado por los circuitos sea la identidad. Después de ejecutar los circuitos, se cuenta el número de disparos que dan lugar a un error (es decir, una salida diferente del estado fundamental), y a partir de estos datos se pueden inferir estimaciones de error para el dispositivo cuántico, calculando el Error Por 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,
)Propiedades de la QPU a lo largo del tiempo
Si observamos las propiedades QPU notificadas a lo largo del tiempo (a continuación consideraremos una sola semana), veremos cómo pueden fluctuar en la escala de un solo día. Pueden producirse pequeñas fluctuaciones incluso en un mismo día. En este caso, las propiedades notificadas (actualizadas una vez al día) no reflejarán con exactitud el estado actual de la QPU. Además, si un trabajo se transpila localmente (utilizando las propiedades actuales notificadas) y se envía, pero no se ejecuta hasta más tarde (minutos o días), puede correr el riesgo de haber utilizado propiedades obsoletas para la selección de qubits en el paso de transpilación. Esto pone de relieve la importancia de disponer de información actualizada sobre la QPU en el momento de la ejecución. En primer lugar, recuperemos las propiedades en un intervalo de tiempo determinado.
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)A continuación, vamos a trazar los valores
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:
Se puede ver que a lo largo de varios días algunas de las propiedades de los qubits pueden cambiar considerablemente. Esto subraya la importancia de disponer de información actualizada sobre el estado de la QPU para poder seleccionar los qubits con mejor rendimiento para un experimento.
Paso 2: Optimizar el problema para la ejecución en hardware cuántico
En este tutorial no se realiza ninguna optimización de los circuitos ni de los operadores.
Paso 3: Ejecutar utilizando Qiskit primitives
Ejecutar un circuito cuántico con selección predeterminada de qubits
Como resultado de referencia del rendimiento, ejecutaremos un circuito cuántico en una QPU utilizando los qubits por defecto, que son los qubits seleccionados con las propiedades backend solicitadas. Utilizaremos optimization_level = 3. Esta configuración incluye la optimización de transpilación más avanzada y utiliza las propiedades del objetivo (como los errores de operación) para seleccionar los qubits de mejor rendimiento para la ejecució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
]Ejecutar un circuito cuántico con selección de qubits en tiempo real
En esta sección, investigaremos la importancia de disponer de información actualizada sobre las propiedades de los qubits de la QPU para obtener resultados óptimos. En primer lugar, llevaremos a cabo un conjunto completo de experimentos de caracterización de QPU ( , , SPAM, RB de un qubit y RB de dos qubits), que luego podremos utilizar para actualizar las propiedades del backend. Esto permite al gestor de pases seleccionar qubits para la ejecución basándose en información reciente sobre la QPU, lo que posiblemente mejore el rendimiento de la ejecución. En segundo lugar, ejecutamos el circuito de pares de Bell y comparamos la fidelidad obtenida después de seleccionar los qubits con las propiedades QPU actualizadas con la fidelidad que obtuvimos antes cuando utilizamos las propiedades informadas por defecto para la selección de qubits.
Tenga en cuenta que algunos de los experimentos de caracterización pueden fallar cuando la rutina de ajuste no puede ajustar una curva a los datos medidos. Si ves advertencias procedentes de estos experimentos, inspecciónalos para entender qué caracterización falló en qué qubits, e intenta ajustar los parámetros del experimento (como los tiempos para , , o la longitud del número de experimentos 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)Paso 4: Procesamiento posterior y devolución del resultado en el formato clásico deseado
Por último, comparemos la fidelidad del estado de Bell obtenido en las dos configuraciones diferentes:
originales decir, con los qubits por defecto elegidos por el transpilador basándose en las propiedades notificadas del backend.updatedes decir, con los qubits elegidos en función de las propiedades actualizadas del backend una vez realizados los experimentos de caracterización.
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:
No todas las ejecuciones mostrarán una mejora en el rendimiento debido a la caracterización en tiempo real, y con el aumento de la longitud de la cadena, y por lo tanto menos libertad para elegir qubits físicos, la importancia de la información actualizada del dispositivo se vuelve menos sustancial. Sin embargo, es una buena práctica recopilar nuevos datos sobre las propiedades del dispositivo para comprender su rendimiento. Ocasionalmente, los sistemas transitorios de dos niveles pueden afectar al rendimiento de algunos de los qubits. Los datos en tiempo real pueden informarnos de cuándo se producen tales acontecimientos y ayudarnos a evitar fallos experimentales en tales casos.
Intente aplicar este método a sus ejecuciones y determine el beneficio que obtiene También puedes probar y ver cuántas mejoras obtienes con diferentes backends.
Encuesta tutorial
Responda a esta breve encuesta para darnos su opinión sobre este tutorial. Su opinión nos ayudará a mejorar nuestra oferta de contenidos y la experiencia de los usuarios.