Codici di ripetizione
Stima del tempo di esecuzione: meno di 10 secondi su un processore Heron (NOTA: si tratta solo di una stima. (La durata potrebbe variare.)
Risultati di apprendimento
- Come implementare un codice di correzione degli errori di inversione di bit utilizzando circuiti dinamici
- Come misurare gli stabilizzatori per rilevare errori quantistici senza distruggere le informazioni codificate
- Come valutare le prestazioni della correzione quantistica degli errori confrontando i risultati corretti con quelli non corretti
Prerequisiti
Sfondo
Per consentire la correzione degli errori quantistici (QEC) in tempo reale, è necessario poter controllare dinamicamente il flusso del programma quantistico durante l'esecuzione, in modo che le porte quantistiche possano essere condizionate dai risultati delle misure. Questa esercitazione esegue il codice bit-flip, che è una forma molto semplice di QEC. Dimostra un circuito quantistico dinamico in grado di proteggere un qubit codificato da un singolo errore di bit-flip e valuta le prestazioni del codice bit-flip.
È possibile sfruttare qubit ancilla aggiuntivi e l'entanglement per misurare stabilizzatori che non trasformano le informazioni quantistiche codificate, informando comunque su alcune classi di errori che potrebbero essersi verificati. Un codice stabilizzatore quantistico codifica qubit logici in qubit fisici. I codici stabilizzatori si concentrano criticamente sulla correzione di un insieme di errori discreti con il supporto del gruppo di Pauli .
In questo tutorial illustriamo il codice “bit-flip” attraverso un semplice esperimento sulla memoria quantistica. Prepareremo il qubit codificato nello stato logico e quindi eseguiremo più cicli di inattività (per generare errori), seguiti dal rilevamento e dalla correzione degli errori. Quantifichiamo quindi la probabilità di un errore logico in funzione del numero di cicli (tempo), dove la probabilità di errore logico è la probabilità di trovare i qubit in uno stato che non ripristini l’ e dopo la misurazione finale (in particolare, gli stati corrispondenti a un errore sono , , e ).
Confronteremo il tasso di errore in funzione del tempo con i tassi di errore dei singoli qubit non codificati, nonché con l’uso del codice di ripetizione, in cui gli errori vengono rilevati e corretti solo dopo la misurazione finale, ma non dinamicamente durante il funzionamento del circuito.
Nota: il codice di ripetizione consente solo la correzione degli errori di inversione di bit e, pertanto, non è un codice di correzione degli errori completo. Tuttavia, proprio grazie alla sua semplicità, rappresenta un buon punto di partenza per illustrare come implementare la correzione degli errori su un computer quantistico. L'esperimento sulla memoria riportato di seguito verifica solo un unico tipo di errore (un " " che si riduce a " ") e, tecnicamente, dimostra solo la protezione delle informazioni classiche.
Requisiti
Prima di iniziare questa esercitazione, assicuratevi di aver installato quanto segue:
- Qiskit SDK v2.0 o versioni successive, con supporto alla visualizzazione
- Qiskit Runtime v0.40 o più tardi (
pip install qiskit-ibm-runtime)
Configura
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()Esempio di simulatore su piccola scala
Tralasceremo questo passaggio poiché l'obiettivo di questo esperimento è misurare la durata di vita di un qubit logico in presenza di rumore hardware reale, come lo smorzamento di ampiezza durante i ritardi di inattività; un simulatore privo di rumore non mostrerebbe errori da rilevare e correggere.
Esempio di hardware su larga scala
Fase 1: Mappare gli input classici su un problema quantistico
Scegli un backend
Per individuare eventuali errori durante l'esecuzione del circuito, è necessario scegliere un backend che abbia accesso al metodo MidCircuitMeasure (vedere la documentazione ).
# 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)Output:
[<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)Output:
ibm_boston
Realizzare una sequenza di circuiti stabilizzatori con inversione di bit che implementino più cicli di rilevamento e correzione degli errori
Il codice bit-flip è uno degli esempi più semplici di codice stabilizzatore. Protegge lo stato da un singolo errore di inversione di bit (X) su uno qualsiasi dei qubit di codifica. Consideriamo l’azione dell’errore di inversione di bit , che mappa e su uno qualsiasi dei nostri qubit; in tal caso, si ottiene . Il codice richiede cinque qubit: tre vengono utilizzati per codificare lo stato protetto (i “qubit di dati”), mentre i restanti due fungono da ancilla per la misurazione degli stabilizzatori.
Di seguito realizzerete dei circuiti che (1) preparano i qubit di dati nello stato logico , poi (2) eseguono più cicli di un ritardo seguiti dalla correzione degli errori (compreso il reset dei qubit di sindrome) e (3) leggono lo stato dei qubit di dati.
Verificheremo inoltre la durata dello stato “ ” senza ricorrere alla correzione degli errori, includendo tre qubit di riferimento che prepareremo nello stato “ ”, lasceremo inattivi e poi leggeremo.
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 circuitdef 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)Output:
Fase 2: Ottimizzare il problema per l'esecuzione su hardware quantistico
Per ridurre il tempo totale di esecuzione del processo, l’ Qiskit primitives e accetta solo circuiti e osservabili conformi alle istruzioni e alla connettività supportate dal sistema di destinazione (denominati “circuiti e osservabili dell’architettura del set di istruzioni” (ISA)). Scopri di più sulla transpilazione.
Generare circuiti ISA
Inizieremo individuando una configurazione iniziale (ovvero una selezione di qubit fisici a cui mappare i nostri circuiti) mediante la transpilazione del circuito più lungo tra quelli a nostra disposizione, utilizzando il gestore di passaggi preimpostato con livello di ottimizzazione 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)Output:
[56, 44, 62, 43, 63, 22, 48, 67]
Come qubit di riferimento con cui confronteremo la nostra memoria quantistica logica, sceglieremo i migliori qubit disponibili in termini di tempo di coerenza con smorzamento di ampiezza ( ).
# 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)Output:
[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")Output:
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)Output:
Passaggio 3: Eseguire il comando utilizzando Qiskit primitives
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()Output:
'DONE'
Fase 4: Elaborazione successiva e restituzione del risultato nel formato classico desiderato
Confronteremo ora i tassi di errore in funzione del tempo tra la memoria logica che utilizza il codice di ripetizione a 3 qubit, da un lato, e i singoli qubit di riferimento non codificati, dall’altro.
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);Output:
Best-fit effective T1 = 1397 us
Si osserva chiaramente un aumento della durata dello stato " ", anche rispetto ai migliori qubit fisici (non codificati). Tuttavia, tieni presente che questo esperimento corregge solo un tipo di errore, ovvero i bit-flip. È possibile aumentare la durata di vita del qubit logico? Potresti valutare la possibilità di ottimizzare il tempo di ritardo, aumentare la dimensione del codice di ripetizione oltre , ecc.
Passi successivi
Se questo lavoro ti è sembrato interessante, potrebbero interessarti anche i seguenti materiali:
- Corso sui fondamenti della correzione degli errori quantistici - scopri di più sulla correzione degli errori quantistici
- Tutorial sul rilevamento degli errori a basso overhead con codici spazio-temporali : scopri come utilizzare i controlli di Pauli per rilevare gli errori e post-selezionare i campioni