Evaluar circuitos dinámicos con pares de Bell cortados
Estimación de uso: 22 segundos en un procesador Heron r2 (NOTA: Esto es solo una estimación. Su tiempo de ejecución puede variar.)
Antecedentes
El hardware cuántico está típicamente limitado a interacciones locales, pero muchos algoritmos requieren entrelazar qubits distantes o incluso qubits en procesadores separados. Los circuitos dinámicos, es decir, circuitos con medición a mitad de circuito y realimentación, proporcionan una forma de superar estas limitaciones mediante el uso de comunicación clásica en tiempo real para implementar efectivamente operaciones cuánticas no locales. En este enfoque, los resultados de medición de una parte de un circuito (o de una QPU) pueden activar condicionalmente compuertas en otra, lo que nos permite teleportar el entrelazamiento a grandes distancias. Esto constituye la base de los esquemas de operaciones locales y comunicación clásica (LOCC), donde consumimos estados de recurso entrelazados (pares de Bell) y comunicamos los resultados de medición de forma clásica para vincular qubits distantes.
Un uso prometedor de LOCC es realizar compuertas CNOT virtuales de largo alcance mediante teleportación, como se muestra en el tutorial de entrelazamiento de largo alcance. En lugar de una CNOT directa de largo alcance (que la conectividad del hardware podría no permitir), creamos pares de Bell y realizamos una implementación de compuerta basada en teleportación. Sin embargo, la fidelidad de dichas operaciones depende de las características del hardware. La decoherencia de los qubits durante el retardo necesario (mientras se esperan los resultados de medición) y la latencia de la comunicación clásica pueden degradar el estado entrelazado. Además, los errores en las mediciones a mitad de circuito son más difíciles de corregir que los errores en las mediciones finales, ya que se propagan al resto del circuito a través de las compuertas condicionales.
En el experimento de referencia, los autores introducen una evaluación de fidelidad de pares de Bell para identificar qué partes de un dispositivo son más adecuadas para el entrelazamiento basado en LOCC. La idea es ejecutar un pequeño circuito dinámico en cada grupo de cuatro qubits conectados del procesador. Este circuito de cuatro qubits primero crea un par de Bell en los dos qubits centrales, y luego utiliza esos qubits como recurso para entrelazar los dos qubits de los extremos mediante LOCC. Concretamente, los qubits 1 y 2 se preparan en un par de Bell no cortado de forma local (usando una compuerta Hadamard y una CNOT), y luego una rutina de teleportación consume ese par de Bell para entrelazar los qubits 0 y 3. Los qubits 1 y 2 se miden durante la ejecución del circuito, y en función de esos resultados, se aplican correcciones de Pauli (una en el qubit 3 y una en el qubit 0). Los qubits 0 y 3 quedan entonces en un estado de Bell al final del circuito.
Para cuantificar la calidad de este par entrelazado final, medimos sus estabilizadores: específicamente, la paridad en la base () y en la base (). Para un par de Bell perfecto, ambos valores esperados son iguales a +1. En la práctica, el ruido del hardware reducirá estos valores. Por lo tanto, repetimos el circuito dos veces para cada par de qubits: un circuito mide los qubits 0 y 3 en la base , y otro los mide en la base . A partir de los resultados, obtenemos una estimación de y para ese par de qubits. Usamos el error cuadrático medio (MSE) de estos estabilizadores con respecto al valor ideal (1) como una métrica simple de fidelidad de entrelazamiento. Un MSE más bajo significa que los dos qubits lograron un estado de Bell más cercano al ideal (mayor fidelidad), mientras que un MSE más alto indica más error. Al escanear este experimento a lo largo del dispositivo, podemos evaluar la capacidad de medición y realimentación de diferentes grupos de qubits e identificar los mejores pares de qubits para operaciones LOCC.
Este tutorial demuestra el experimento en un dispositivo IBM Quantum® para ilustrar cómo los circuitos dinámicos pueden usarse para generar y evaluar el entrelazamiento entre qubits distantes. Mapearemos todas las cadenas lineales de cuatro qubits en el dispositivo, ejecutaremos el circuito de teleportación en cada una, y luego visualizaremos la distribución de los valores de MSE. Este procedimiento de extremo a extremo muestra cómo aprovechar Qiskit Runtime y las características de circuitos dinámicos para tomar decisiones informadas sobre el hardware al cortar circuitos o distribuir algoritmos cuánticos a través de un sistema modular.
Requisitos
Antes de comenzar este tutorial, asegúrese 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)
Configuración
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
from qiskit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler import generate_preset_pass_manager
import numpy as np
import matplotlib.pyplot as plt
def create_bell_stab(initial_layouts):
"""
Create a circuit for a 1D chain of qubits (number of qubits must be a multiple of 4),
where a middle Bell pair is consumed to create a Bell at the edge.
Takes as input a list of lists, where each element of the list is a
1D chain of physical qubits that is used as the initial_layout for the transpiled circuit.
Returns a list of length-2 tuples, each tuple contains a circuit to measure the ZZ stabilizer and
a circuit to measure the XX stabilizer of the edge Bell state.
"""
bell_circuits = []
for (
initial_layout
) in initial_layouts: # Iterate over chains of physical qubits
assert (
len(initial_layout) % 4 == 0
), f"The length of the chain must be a multiple of 4, len(inital_layout)={len(initial_layout)}"
num_pairs = len(initial_layout) // 4
bell_parallel = QuantumCircuit(4 * num_pairs, 4 * num_pairs)
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(c0, c1) = pair_idx * 4, pair_idx * 4 + 3 # edge qubits
(ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2 # middle qubits
bell_parallel.h(q0)
bell_parallel.h(q1)
bell_parallel.cx(q1, q2)
bell_parallel.cx(q0, q1)
bell_parallel.cx(q2, q3)
bell_parallel.h(q2)
# add barrier BEFORE measurements and add id in conditional
bell_parallel.barrier()
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2 # middle qubits
bell_parallel.measure(q1, ca0)
bell_parallel.measure(q2, ca1)
# bell_parallel.barrier() #remove barrier after measurement
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2 # middle qubits
with bell_parallel.if_test((ca0, 1)):
bell_parallel.x(q3)
with bell_parallel.if_test((ca1, 1)):
bell_parallel.z(q0)
bell_parallel.id(q0) # add id here for correct alignment
bell_zz = bell_parallel.copy()
bell_zz.barrier()
bell_xx = bell_parallel.copy()
bell_xx.barrier()
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
bell_xx.h(q0)
bell_xx.h(q3)
bell_xx.barrier()
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(c0, c1) = pair_idx * 4, pair_idx * 4 + 3 # edge qubits
bell_zz.measure(q0, c0)
bell_zz.measure(q3, c1)
bell_xx.measure(q0, c0)
bell_xx.measure(q3, c1)
bell_circuits.append(bell_zz)
bell_circuits.append(bell_xx)
return bell_circuits
def get_mse(result, initial_layouts):
"""
given a result object and the initial layouts, returns a dict of layouts and their mse
"""
layout_mse = {}
for layout_idx, initial_layout in enumerate(initial_layouts):
layout_mse[tuple(initial_layout)] = {}
num_pairs = len(initial_layout) // 4
counts_zz = result[2 * layout_idx].data.c.get_counts()
total_shots = sum(counts_zz.values())
# Get ZZ expectation value
exp_zz_list = []
for pair_idx in range(num_pairs):
exp_zz = 0
for bitstr, shots in counts_zz.items():
bitstr = bitstr[::-1] # reverse order to big endian
b1, b0 = (
bitstr[pair_idx * 4],
bitstr[pair_idx * 4 + 3],
) # parse bitstring to get edge measurements for each 4-q chain
z_val0 = 1 if b0 == "0" else -1
z_val1 = 1 if b1 == "0" else -1
exp_zz += z_val0 * z_val1 * shots
exp_zz /= total_shots
exp_zz_list.append(exp_zz)
counts_xx = result[2 * layout_idx + 1].data.c.get_counts()
total_shots = sum(counts_xx.values())
# Get XX expectation value
exp_xx_list = []
for pair_idx in range(num_pairs):
exp_xx = 0
for bitstr, shots in counts_xx.items():
bitstr = bitstr[::-1] # reverse order to big endian
b1, b0 = (
bitstr[pair_idx * 4],
bitstr[pair_idx * 4 + 3],
) # parse bitstring to get edge measurements for each 4-q chain
x_val0 = 1 if b0 == "0" else -1
x_val1 = 1 if b1 == "0" else -1
exp_xx += x_val0 * x_val1 * shots
exp_xx /= total_shots
exp_xx_list.append(exp_xx)
mse_list = [
((exp_zz - 1) ** 2 + (exp_xx - 1) ** 2) / 2
for exp_zz, exp_xx in zip(exp_zz_list, exp_xx_list)
]
print(f"layout {initial_layout}")
for idx in range(num_pairs):
layout_mse[tuple(initial_layout)][
tuple(initial_layout[4 * idx : 4 * idx + 4])
] = mse_list[idx]
print(
f"qubits: {initial_layout[4*idx:4*idx+4]}, mse:, {round(mse_list[idx],4)}"
)
# print(f'exp_zz: {round(exp_zz_list[idx],4)}, exp_xx: {round(exp_xx_list[idx],4)}')
print(" ")
return layout_mse
def plot_mse_ecdfs(layouts_mse, combine_layouts=False):
"""
Plot CDF of MSE data for multiple layouts. Optionally combine all data in a single CDF
"""
if not combine_layouts:
for initial_layout, layouts in layouts_mse.items():
sorted_layouts = dict(
sorted(layouts.items(), key=lambda item: item[1])
) # sort layouts by mse
# get layouts and mses
layout_list = list(sorted_layouts.keys())
mse_list = np.asarray(list(sorted_layouts.values()))
# convert to numpy
x = np.array(mse_list)
y = np.arange(1, len(x) + 1) / len(x)
# Prepend (x[0], 0) to start CDF at zero
x = np.insert(x, 0, x[0])
y = np.insert(y, 0, 0)
# Create the plot
plt.plot(
x,
y,
marker="x",
linestyle="-",
label=f"qubits: {initial_layout}",
)
# add qubits labels for the edge pairs
for xi, yi, q in zip(x[1:], y[1:], layout_list):
plt.annotate(
[q[0], q[3]],
(xi, yi),
textcoords="offset points",
xytext=(5, -10),
ha="left",
fontsize=8,
)
elif combine_layouts:
all_layouts = {}
all_initial_layout = []
for (
initial_layout,
layouts,
) in layouts_mse.items(): # puts together all layout information
all_layouts.update(layouts)
all_initial_layout += initial_layout
sorted_layouts = dict(
sorted(all_layouts.items(), key=lambda item: item[1])
) # sort layouts by mse
# get layouts and mses
layout_list = list(sorted_layouts.keys())
mse_list = np.asarray(list(sorted_layouts.values()))
# convert to numpy
x = np.array(mse_list)
y = np.arange(1, len(x) + 1) / len(x)
# Prepend (x[0], 0) to start CDF at zero
x = np.insert(x, 0, x[0])
y = np.insert(y, 0, 0)
# Create the plot
plt.plot(
x,
y,
marker="x",
linestyle="-",
label=f"qubits: {sorted(list(set(all_initial_layout)))}",
)
# add qubit labels for the edge pairs
for xi, yi, q in zip(x[1:], y[1:], layout_list):
plt.annotate(
[q[0], q[3]],
(xi, yi),
textcoords="offset points",
xytext=(5, -10),
ha="left",
fontsize=8,
)
plt.xscale("log")
plt.xlabel("Mean squared error of ⟨ZZ⟩ and ⟨XX⟩")
plt.ylabel("Cumulative distribution function")
plt.title("CDF for different initial layouts")
plt.grid(alpha=0.3)
plt.show()
Paso 1: Mapear las entradas clásicas a un problema cuántico
El primer paso es crear un conjunto de circuitos cuánticos para evaluar todos los enlaces candidatos de pares de Bell adaptados a la topología del dispositivo. Buscamos programáticamente en el mapa de acoplamiento del dispositivo todas las cadenas de cuatro qubits conectados linealmente. Cada una de estas cadenas (etiquetada por los índices de qubit ) sirve como caso de prueba para el circuito de intercambio de entrelazamiento. Al identificar todos los caminos posibles de longitud 4, aseguramos la máxima cobertura de las posibles agrupaciones de qubits que podrían implementar el protocolo.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True)
Generamos estas cadenas utilizando una función auxiliar que realiza una búsqueda voraz en el grafo del dispositivo. Esta función devuelve "franjas" de cuatro cadenas de cuatro qubits agrupadas en conjuntos de 16 qubits (los circuitos dinámicos actualmente restringen el tamaño del registro de medición a 16 qubits). La agrupación nos permite ejecutar múltiples experimentos de cuatro qubits en paralelo en distintas partes del chip, y hacer un uso eficiente de todo el dispositivo. Cada franja de 16 qubits contiene cuatro cadenas disjuntas, lo que significa que ningún qubit se reutiliza dentro de ese grupo. Por ejemplo, una franja podría consistir en las cadenas , , y empaquetadas juntas. Cualquier qubit que no fue incluido en una franja se devuelve en la variable leftover.
from itertools import chain
from collections import defaultdict
def stripes16_from_backend(backend):
"""
Creates stripes of 16 qubits, four non-overlapping four-qubit chains, that cover as much of
the coupling map as possible. Returns any unused qubits as leftovers.
"""
# get the undirected adjacency list
edges = backend.coupling_map.get_edges()
graph = defaultdict(set)
for u, v in edges:
graph[u].add(v)
graph[v].add(u)
qubits = sorted(graph) # all qubit indices that appear
# greedy search for 4-long linear chains (blocks) ────────────
used = set() # qubits already placed in a block
blocks = [] # each block is a four-qubit list
for q in qubits: # deterministic order for reproducibility
if q in used:
continue # already consumed by earlier block
# depth-first "straight" walk of length 3 without revisiting nodes
def extend(path):
if len(path) == 4:
return path
tip = path[-1]
for nbr in sorted(graph[tip]): # deterministic
if nbr not in path and nbr not in used:
maybe = extend(path + [nbr])
if maybe:
return maybe
return None
block = extend([q])
if block: # found a 4-node path
blocks.append(block)
used.update(block)
# bundle four four-qubit blocks into one 16-qubit stripe (max number of measurement compatible with if-else)
stripes = [
list(chain.from_iterable(blocks[i : i + 4]))
for i in range(0, len(blocks) // 4 * 4, 4) # full groups of four
]
leftovers = set(qubits) - set(chain.from_iterable(stripes))
return stripes, leftovers
initial_layouts, leftover = stripes16_from_backend(backend)
A continuación, construimos el circuito para cada franja de 16 qubits. La rutina realiza lo siguiente para cada cadena:
- Preparar un par de Bell central: Aplicar una compuerta Hadamard en el qubit 1 y una CNOT del qubit 1 al qubit 2. Esto entrelaza los qubits 1 y 2 (creando un estado de Bell ).
- Entrelazar los qubits de los extremos: Aplicar una CNOT del qubit 0 al qubit 1, y una CNOT del qubit 2 al qubit 3. Esto vincula los pares inicialmente separados de modo que los qubits 0 y 3 quedarán entrelazados después de los pasos siguientes. También se aplica una compuerta Hadamard en el qubit 2 (esto, combinado con las CNOT anteriores, forma parte de una medición de Bell en los qubits 1 y 2). En este punto, los qubits 0 y 3 aún no están entrelazados, pero los qubits 1 y 2 están entrelazados con ellos en un estado más amplio de cuatro qubits.
- Mediciones a mitad de circuito y realimentación: Los qubits 1 y 2 (los qubits centrales) se miden en la base computacional, produciendo dos bits clásicos. En función de esos resultados de medición, aplicamos operaciones condicionales: si la medición del qubit 1 (llamemos a este bit ) es 1, aplicamos una compuerta en el qubit 3; si la medición del qubit 2 () es 1, aplicamos una compuerta en el qubit 0. Estas compuertas condicionales (implementadas usando el constructor
if_test/if_elsede Qiskit) realizan las correcciones de teleportación estándar. "Deshacen" las inversiones aleatorias de Pauli que ocurren al proyectar los qubits 1 y 2, asegurando que los qubits 0 y 3 terminen en un estado de Bell conocido, independientemente de los resultados de medición. Después de este paso, los qubits 0 y 3 deberían estar idealmente entrelazados en el estado de Bell . - Medir los estabilizadores del par de Bell: Luego dividimos en dos versiones del circuito. En la primera versión, medimos el estabilizador en los qubits 0 y 3. En la segunda versión, medimos el estabilizador en estos qubits.
Para cada disposición inicial de cuatro qubits, la función anterior devuelve dos circuitos (uno para la medición del estabilizador , otro para ). Al final de este paso, tenemos una lista de circuitos que cubren cada cadena de cuatro qubits en el dispositivo. Estos circuitos incluyen mediciones a mitad de circuito y operaciones condicionales (if/else), que son las instrucciones clave del circuito dinámico.
circuits = create_bell_stab(initial_layouts)
circuits[-1].draw("mpl", fold=-1)

Paso 2: Optimizar el problema para la ejecución en hardware cuántico
Antes de ejecutar nuestros circuitos en hardware real, necesitamos transpilarlos para que coincidan con las restricciones físicas del dispositivo. La transpilación mapeará el circuito abstracto sobre los qubits físicos y el conjunto de compuertas del dispositivo elegido. Dado que ya hemos elegido qubits físicos específicos para cada cadena (proporcionando un initial_layout al generador de circuitos), utilizamos el nivel de optimización del transpilador optimization_level=0 con ese diseño fijo. Esto le indica a Qiskit que no reasigne qubits ni realice optimizaciones pesadas que puedan alterar la estructura del circuito. Queremos mantener la secuencia de operaciones (especialmente las compuertas condicionales) exactamente como se especificó.
isa_circuits = []
for ind, init_layout in enumerate(initial_layouts):
pm = generate_preset_pass_manager(
optimization_level=0, backend=backend, initial_layout=init_layout
)
isa_circ = pm.run(circuits[ind * 2 : ind * 2 + 2])
isa_circuits.extend(isa_circ)
isa_circuits[1].draw("mpl", fold=-1, idle_wires=False)

Paso 3: Ejecutar utilizando las primitivas de Qiskit
Ahora podemos ejecutar el experimento en el dispositivo cuántico. Utilizamos Qiskit Runtime y su primitiva Sampler para ejecutar el lote de circuitos de manera eficiente.
sampler = Sampler(mode=backend)
sampler.options.environment.job_tags = ["cut-bell-pair-test"]
job = sampler.run(isa_circuits)
Paso 4: Post-procesar y devolver el resultado en el formato clásico deseado
El paso final es calcular la métrica de error cuadrático medio (MSE) para cada grupo de qubits probado y resumir los resultados. Para cada cadena, ahora tenemos los valores medidos y . Si los qubits 0 y 3 estuvieran perfectamente entrelazados en un estado de Bell , esperaríamos que ambos fueran +1. Cuantificamos la desviación utilizando el MSE:
Este valor es 0 para un par de Bell perfecto, y aumenta a medida que el estado entrelazado se vuelve más ruidoso (con resultados aleatorios que dan una expectativa cercana a 0, el MSE se aproximaría a 1). El código calcula este MSE para cada grupo de cuatro qubits.
Los resultados revelan una amplia gama de calidad de entrelazamiento a lo largo del dispositivo. Esto confirma el hallazgo del artículo de que puede haber más de un orden de magnitud de variación en la fidelidad del estado de Bell dependiendo de qué qubits físicos se utilicen. En términos prácticos, esto significa que ciertas regiones o enlaces en el chip son mucho mejores para realizar operaciones de medición a mitad de circuito y retroalimentación que otras. Factores como el error de lectura de los qubits, el tiempo de vida de los qubits y la diafonía probablemente contribuyen a estas diferencias. Por ejemplo, si una cadena incluye un qubit de lectura particularmente ruidoso, la medición a mitad de circuito podría ser poco confiable, lo que llevaría a una fidelidad deficiente para ese par entrelazado (MSE alto).
layouts_mse = get_mse(job.result(), initial_layouts)
layout [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
qubits: [0, 1, 2, 3], mse:, 0.0312
qubits: [4, 5, 6, 7], mse:, 0.0491
qubits: [8, 9, 10, 11], mse:, 0.0711
qubits: [12, 13, 14, 15], mse:, 0.0436
layout [16, 23, 22, 21, 17, 27, 26, 25, 18, 31, 30, 29, 19, 35, 34, 33]
qubits: [16, 23, 22, 21], mse:, 0.0197
qubits: [17, 27, 26, 25], mse:, 0.113
qubits: [18, 31, 30, 29], mse:, 0.0287
qubits: [19, 35, 34, 33], mse:, 0.0433
layout [36, 41, 42, 43, 37, 45, 46, 47, 38, 49, 50, 51, 39, 53, 54, 55]
qubits: [36, 41, 42, 43], mse:, 0.1645
qubits: [37, 45, 46, 47], mse:, 0.0409
qubits: [38, 49, 50, 51], mse:, 0.0519
qubits: [39, 53, 54, 55], mse:, 0.0829
layout [56, 63, 62, 61, 57, 67, 66, 65, 58, 71, 70, 69, 59, 75, 74, 73]
qubits: [56, 63, 62, 61], mse:, 0.8663
qubits: [57, 67, 66, 65], mse:, 0.0375
qubits: [58, 71, 70, 69], mse:, 0.0664
qubits: [59, 75, 74, 73], mse:, 0.0291
layout [76, 81, 82, 83, 77, 85, 86, 87, 78, 89, 90, 91, 79, 93, 94, 95]
qubits: [76, 81, 82, 83], mse:, 0.0598
qubits: [77, 85, 86, 87], mse:, 0.313
qubits: [78, 89, 90, 91], mse:, 0.0679
qubits: [79, 93, 94, 95], mse:, 0.0505
layout [96, 103, 102, 101, 97, 107, 106, 105, 98, 111, 110, 109, 99, 115, 114, 113]
qubits: [96, 103, 102, 101], mse:, 0.0302
qubits: [97, 107, 106, 105], mse:, 0.0384
qubits: [98, 111, 110, 109], mse:, 0.0375
qubits: [99, 115, 114, 113], mse:, 0.1051
layout [116, 121, 122, 123, 117, 125, 126, 127, 118, 129, 130, 131, 119, 133, 134, 135]
qubits: [116, 121, 122, 123], mse:, 0.1624
qubits: [117, 125, 126, 127], mse:, 0.7246
qubits: [118, 129, 130, 131], mse:, 0.5919
qubits: [119, 133, 134, 135], mse:, 0.5277
layout [136, 143, 142, 141, 137, 147, 146, 145, 138, 151, 150, 149, 139, 155, 154, 153]
qubits: [136, 143, 142, 141], mse:, 0.0383
qubits: [137, 147, 146, 145], mse:, 1.0187
qubits: [138, 151, 150, 149], mse:, 0.1531
qubits: [139, 155, 154, 153], mse:, 0.0471
Finalmente, visualizamos el rendimiento general trazando la función de distribución acumulada (CDF) de los valores de MSE para todas las cadenas. El gráfico de la CDF muestra el umbral de MSE en el eje x y la fracción de pares de qubits que tienen como máximo ese MSE en el eje y. Esta curva comienza en cero y se aproxima a uno a medida que el umbral crece para abarcar todos los puntos de datos. Un ascenso pronunciado cerca de un MSE bajo indicaría que muchos pares son de alta fidelidad; un ascenso lento significa que muchos pares tienen errores mayores. Anotamos la CDF con las identidades de los mejores pares. En el gráfico, cada punto de la CDF corresponde al MSE de una cadena de cuatro qubits, y etiquetamos el punto con el par de índices de qubits que fueron entrelazados en ese experimento. Esto facilita identificar qué pares de qubits físicos tienen el mejor rendimiento (los puntos más a la izquierda en la CDF).
plot_mse_ecdfs(layouts_mse, combine_layouts=True)