Códigos de repetición
Estimación de uso: menos de 10 segundos en un procesador Heron (NOTA: Esta es solo una estimación. Tu tiempo de ejecución puede variar.)
Resultados de aprendizaje
-
Cómo implementar un código de corrección de errores de bit-flip usando circuitos dinámicos
-
Cómo medir estabilizadores para detectar errores cuánticos sin destruir la información codificada
-
Cómo evaluar el rendimiento de la corrección de errores cuánticos comparando resultados corregidos y sin corregir
Prerrequisitos
Antecedentes
Para habilitar la corrección de errores cuánticos (QEC) en tiempo real, necesitas poder controlar dinámicamente el flujo del programa cuántico durante la ejecución para que los gates cuánticos puedan estar condicionados a los resultados de medición. Este tutorial ejecuta el código de bit-flip, que es una forma muy simple de QEC. Demuestra un circuito cuántico dinámico que puede proteger un qubit codificado de un solo error de bit-flip, y luego evalúa el rendimiento del código de bit-flip.
Puedes explotar qubits ancilla adicionales y entrelazamiento para medir estabilizadores que no transforman información cuántica codificada, mientras aún te informan sobre algunas clases de errores que podrían haber ocurrido. Un código estabilizador cuántico codifica qubits lógicos en qubits físicos. Los códigos estabilizadores se centran fundamentalmente en corregir un conjunto discreto de errores con soporte en el grupo de Pauli .
En este tutorial, demostramos el código de bit-flip usando un experimento simple de memoria cuántica. Prepararemos el qubit codificado en el estado lógico y luego implementaremos múltiples ciclos de tiempo inactivo (para acumular errores) seguidos de detección y corrección de errores. Luego cuantificamos la probabilidad de un error lógico en función del número de ciclos (tiempo), donde la probabilidad de error lógico es la probabilidad de encontrar los qubits en un estado que no recupera después de la medición final (concretamente, los estados correspondientes a un error son , , , y ).
Compararemos la tasa de error frente al tiempo con las tasas de error de qubits individuales sin codificar, y también con el uso del código de repetición donde solo detectamos y corregimos errores después de la medición final, pero no dinámicamente durante el circuito.
Nota: el código de repetición solo permite la corrección de errores de bit-flip y, por lo tanto, no es un código de corrección de errores completo. Sin embargo, debido a su simplicidad, es un buen punto de partida para ilustrar cómo implementar la corrección de errores en una computadora cuántica. El experimento de memoria a continuación solo prueba un único tipo de error (un decayendo a ) y técnicamente solo demuestra la protección de información clásica.
Requisitos
Antes de comenzar 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)
Configuración
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "scipy": "scipy", "seaborn": "seaborn"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
sns.set()
plt.rc("xtick", labelsize=20)
plt.rc("ytick", labelsize=20)
plt.rc("lines", linewidth=3)
plt.rc("font", size=20)
plt.rc("legend", fontsize="large")
plt.rc("axes", labelsize=20)
plt.rcParams["figure.figsize"] = 15, 6
plt.rcParams["legend.title_fontsize"] = 25
# Qiskit imports
from qiskit import (
QuantumCircuit,
QuantumRegister,
ClassicalRegister,
)
# qiskit-ibm-runtime
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit_ibm_runtime.circuit import MidCircuitMeasure
service = QiskitRuntimeService()
Ejemplo de simulador a pequeña escala
Renunciaremos a este paso ya que el objetivo de este experimento es medir el tiempo de vida de un qubit lógico bajo el ruido real del hardware, como el amortiguamiento de amplitud durante los retrasos de inactividad; un simulador sin ruido no mostraría errores que detectar y corregir.
Ejemplo de hardware a gran escala
Paso 1: Mapear entradas clásicas a un problema cuántico
Elegir un backend
Para detectar errores durante el circuito, necesitamos elegir un backend que tenga acceso al método MidCircuitMeasure (consulta la documentación).
# You can see all backends that support mid-circuit measurements by running the following code.
service.backends(filters=lambda b: "measure_2" in b.supported_instructions)
[<IBMBackend('ibm_pittsburgh')>,
<IBMBackend('ibm_boston')>,
<IBMBackend('ibm_kingston')>]
# Choose the least busy backend that supports mid-circuit measurements (`measure_2`).
backend = service.least_busy(
filters=lambda b: "measure_2" in b.supported_instructions,
operational=True,
simulator=False,
dynamic_circuits=True,
)
# backend = service.backend(backend_name) # alternatively, you could choose a specific backend
print(backend.name)
ibm_boston
Construir una secuencia de circuitos estabilizadores de bit-flip que implementen múltiples rondas de detección y corrección de errores
El código de bit-flip está entre los ejemplos más simples de un código estabilizador. Protege el estado contra un solo error de bit-flip (X) en cualquiera de los qubits de codificación. Considera la acción del error de bit-flip , que mapea y en cualquiera de nuestros qubits, entonces tenemos . El código requiere cinco qubits: tres se usan para codificar el estado protegido (los "qubits de datos"), y los dos restantes se usan como ancillas de medición de estabilizador.
A continuación construirás circuitos que (1) preparan los qubits de datos en el estado lógico , luego (2) ejecutan múltiples ciclos de un retraso de seguido de corrección de errores (incluyendo el reinicio de los qubits de síndrome), y (3) leen el estado de los qubits de datos.
También probaremos el tiempo de vida del estado sin usar corrección de errores incluyendo tres qubits de referencia que preparamos en el estado , dejamos inactivos, y luego leemos.
def build_qc(
qreg_data,
qreg_syndrome,
creg_data,
creg_syndrome,
qreg_ref=None,
creg_ref=None,
):
"""Build a typical error correction circuit"""
if qreg_ref:
return QuantumCircuit(
qreg_data,
qreg_syndrome,
creg_data,
creg_syndrome,
qreg_ref,
creg_ref,
)
else:
return QuantumCircuit(
qreg_data, qreg_syndrome, creg_data, creg_syndrome
)
def encode_bit_flip(circuit, qreg_data, qreg_ref=None) -> QuantumCircuit:
"""Encode bit-flip. This is done by simply adding a cx"""
for q in qreg_data:
circuit.x(q)
if qreg_ref:
for q in qreg_ref:
circuit.x(q)
circuit.barrier()
return circuit
def measure_syndrome_bit(
circuit, qreg_data, qreg_syndrome, creg_syndrome, qreg_ref=None
):
"""
Measure the syndrome by measuring the parity.
We reset our ancilla qubits after measuring the stabilizer
so we can reuse them for repeated stabilizer measurements.
Because we have already observed the state of the qubit,
we can write the conditional reset protocol directly to
avoid another round of qubit measurement if we used
the `reset` instruction.
"""
circuit.cx(qreg_data[0], qreg_syndrome[0])
circuit.cx(qreg_data[1], qreg_syndrome[0])
circuit.cx(qreg_data[0], qreg_syndrome[1])
circuit.cx(qreg_data[2], qreg_syndrome[1])
circuit.barrier()
for q_measure, c_measure in zip(qreg_syndrome, creg_syndrome):
circuit.append(MidCircuitMeasure(), [q_measure], [c_measure])
with circuit.if_test((creg_syndrome[0], 1)):
circuit.x(qreg_syndrome[0])
with circuit.if_test((creg_syndrome[1], 1)):
circuit.x(qreg_syndrome[1])
circuit.barrier()
return circuit
def apply_correction_bit(circuit, qreg_data, creg_syndrome):
"""We can detect where an error occurred and correct our state"""
with circuit.if_test((creg_syndrome, 3)):
circuit.x(qreg_data[0])
with circuit.if_test((creg_syndrome, 1)):
circuit.x(qreg_data[1])
with circuit.if_test((creg_syndrome, 2)):
circuit.x(qreg_data[2])
circuit.barrier()
return circuit
def apply_final_readout(
circuit, qreg_data, creg_data, qreg_ref=None, creg_ref=None
):
"""Read out the final measurements"""
circuit.barrier()
if qreg_ref:
circuit.measure(qreg_ref, creg_ref)
circuit.measure(qreg_data, creg_data)
return circuit
def build_error_correction_sequence(
num_cycles, cycles_per_circuit, nq_ref=3, delay=None
) -> QuantumCircuit:
circuits = []
reps = []
qreg_data = QuantumRegister(3, name="qdata")
qreg_syndrome = QuantumRegister(2, name="qsyndrome")
creg_data = ClassicalRegister(3, name="cdata")
creg_syndrome = ClassicalRegister(2, name="csyndrome")
qreg_ref = QuantumRegister(nq_ref, name="qreference")
creg_ref = ClassicalRegister(nq_ref, name="creference")
circuit = build_qc(
qreg_data,
qreg_syndrome,
creg_data,
creg_syndrome,
qreg_ref=qreg_ref,
creg_ref=creg_ref,
)
circuit = encode_bit_flip(circuit, qreg_data, qreg_ref=qreg_ref)
circuit_n = circuit.copy()
circuit_n = apply_final_readout(
circuit_n, qreg_data, creg_data, qreg_ref=qreg_ref, creg_ref=creg_ref
)
circuits.append(circuit_n)
reps.append(0)
for i in range(1, num_cycles + 1):
if delay:
circuit.delay(delay, unit="us")
circuit.barrier()
circuit = measure_syndrome_bit(
circuit,
qreg_data,
qreg_syndrome,
creg_syndrome,
qreg_ref=qreg_ref,
)
circuit = apply_correction_bit(circuit, qreg_data, creg_syndrome)
circuit_n = circuit.copy()
if i % cycles_per_circuit == 0:
circuit_n = apply_final_readout(
circuit_n,
qreg_data,
creg_data,
qreg_ref=qreg_ref,
creg_ref=creg_ref,
)
circuits.append(circuit_n)
reps.append(i)
return circuits, np.array(reps)
num_cycles = 40
cycles_per_circuit = 4
nq_ref = 3
circuits, rep_array = build_error_correction_sequence(
num_cycles, cycles_per_circuit, nq_ref=3, delay=5
)
circuits[1].draw(output="mpl", cregbundle=False, fold=50)

Paso 2: Optimizar el problema para la ejecución en hardware cuántico
Para reducir el tiempo total de ejecución del trabajo, las primitivas de Qiskit solo aceptan circuitos y observables que se ajustan a las instrucciones y conectividad compatibles con el sistema objetivo (denominados circuitos y observables de arquitectura de conjunto de instrucciones (ISA)). Aprende más sobre transpilación.
Generar circuitos ISA
Comenzaremos encontrando un diseño inicial (es decir, una selección de qubits físicos a los que mapear nuestros circuitos) transpilando el más largo de nuestros circuitos usando el gestor de pases preestablecido con nivel de optimización 3.
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
isa_circuit_ref = pm.run(circuits[-1])
init_layout = isa_circuit_ref.layout.initial_index_layout(
filter_ancillas=True
)
print(init_layout)
[56, 44, 62, 43, 63, 22, 48, 67]
Para los qubits de referencia con los que compararemos nuestra memoria cuántica lógica, los elegiremos como los mejores qubits disponibles en términos de tiempo de coherencia de amortiguamiento de amplitud ().
# get all qubits ordered by T1
t1_data = []
for i in range(backend.num_qubits):
try:
t1_us = backend.properties().t1(i) * 1e6
except Exception:
t1_us = 0.0
t1_data.append((i, t1_us))
t1_data_sorted = sorted(t1_data, key=lambda x: x[1], reverse=True)
# exclude the qubits we have already mapped the error correcting code to
t1_data_sorted = [
t1_data for t1_data in t1_data_sorted if t1_data[0] not in init_layout[:5]
]
# use the best qubits in terms of T1 for the reference qubits
init_layout = init_layout[:5] + [t1_data[0] for t1_data in t1_data_sorted[:3]]
print(init_layout)
[56, 44, 62, 43, 63, 143, 131, 31]
# These are the resulting T1 times
properties = backend.properties()
print("Amplitude damping decoherence times for code data qubits:")
for q in init_layout[:3]:
t1 = properties.t1(q)
print(f"qubit {q}: T1 = {t1 * 1e6:.0f} mus")
print("\nAmplitude damping decoherence times for reference qubits:")
for q in init_layout[-3:]:
t1 = properties.t1(q)
print(f"qubit {q}: T1 = {t1 * 1e6:.0f} mus")
Amplitude damping decoherence times code data qubits:
qubit 56: T1 = 322 mus
qubit 44: T1 = 263 mus
qubit 62: T1 = 290 mus
Amplitude damping decoherence times reference qubits:
qubit 143: T1 = 442 mus
qubit 131: T1 = 410 mus
qubit 31: T1 = 401 mus
# now we transpile all circuits to this initial layout; this way each circuit is run on the same qubits and we can make a fair comparison
pm = generate_preset_pass_manager(
backend=backend,
optimization_level=3,
initial_layout=init_layout,
)
isa_circuits = pm.run(circuits)
isa_circuits[1].draw("mpl", cregbundle=False, fold=50)

Paso 3. Ejecutar usando primitivas de Qiskit
sampler = Sampler(mode=backend)
sampler.options.environment.job_tags = ["TUT-REPCODE"]
sampler.options.max_execution_time = 600 # this workload is expected to be under 10s, but it is generally a good habit to set a max execution time (here 600s = 10m)
job = sampler.run(isa_circuits, shots=1000)
print(job.job_id())
job.status()
'DONE'
Paso 4: Post-procesar y devolver el resultado en el formato clásico deseado
Ahora compararemos las tasas de error frente al tiempo entre la memoria lógica usando el código de repetición de 3 qubits por un lado, y los qubits de referencia individuales sin codificar por otro lado.
results = job.result()
def correct_counts(counts_dict):
"""
Corrects the measured logical qubit encoded in the repetition code using majority vote
"""
result = {"000": 0, "111": 0}
for bitstring, count in counts_dict.items():
key = "111" if bitstring.count("1") > 1 else "000"
result[key] += count
return result
accuracy = [] # logical qubit
accuracies_ref = np.zeros(
(len(results), nq_ref)
) # accuracies on individual reference qubits
for n, pub_result in enumerate(results):
# logical accuracy (one minus error probability) for active error correction with repetition code
counts = pub_result.data.cdata.get_counts()
shots = sum(counts.values())
counts_corrected = correct_counts(counts)
accuracy.append(counts_corrected.get("111", 0) / shots)
# accuracy for individual physical reference qubits without any error correction
for i in range(nq_ref):
counts = pub_result.data.creference.slice_bits(i).get_counts()
accuracies_ref[n, i] = counts.get("1", 0) / shots
accuracy = np.array(accuracy)
def error_proba_t1(N, t1):
"""
Exponential fitting function for amplitude damping vs. number of cycles
"""
t_cycle = 7.3e-6 # approximate time per cycle = 5 mus delay + 2.3 mus for error correction
return 1 - np.exp(-t_cycle * N / t1)
fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(
rep_array, (1.0 - accuracy) * 100.0, "ko-", linewidth=3, label="rep code"
)
for i in range(nq_ref):
accuracy_1q = accuracies_ref[:, i]
if i == 0:
ax.plot(
rep_array,
(1.0 - accuracy_1q) * 100.0,
"go-",
linewidth=1,
label="1q reference",
)
else:
ax.plot(rep_array, (1.0 - accuracy_1q) * 100.0, "go-", linewidth=1)
params_bf, pcov = curve_fit(
error_proba_t1, rep_array, 1.0 - accuracy, bounds=([0, 5e-3])
)
t1_bf = params_bf[0]
print(f"Best-fit effective T1 = {t1_bf * 1e6:.0f} us")
error_prob_bf = error_proba_t1(
rep_array, t1_bf
) # np.array([1 - np.exp(-t_cycle * nt/t1) for nt in rep_array])
ax.plot(rep_array, error_prob_bf * 100.0, "k--", linewidth=1)
ax.set_xlabel("error correction rounds")
ax.set_ylabel("error [%]")
ax.set_ylim(bottom=0)
ax.legend(fontsize=15);
Best-fit effective T1 = 1397 us

Vemos claramente un tiempo de vida mejorado del estado , incluso en comparación con los mejores qubits físicos (sin codificar). Sin embargo, ten en cuenta que este experimento solo corrige un tipo de error, a saber, bit-flips. ¿Puedes mejorar el tiempo de vida del qubit lógico? Podrías explorar la optimización del tiempo de retraso, escalar el tamaño del código de repetición más allá de , etc.
Próximos pasos
Si este trabajo te pareció interesante, podrían interesarte los siguientes materiales:
-
Curso de fundamentos de corrección de errores cuánticos - aprende más sobre la corrección de errores cuánticos
-
Tutorial de detección de errores de bajo overhead con códigos spacetime - aprende a usar comprobaciones de Pauli para detectar errores y post-seleccionar muestras