Códigos de repetição
Estimativa de uso: menos de 10 segundos em um processador Heron (NOTA: Esta é apenas uma estimativa. Seu tempo de execução pode variar.)
Resultados de aprendizagem
-
Como implementar um código de correção de erro de bit-flip usando circuitos dinâmicos
-
Como medir estabilizadores para detectar erros quânticos sem destruir a informação codificada
-
Como avaliar o desempenho da correção de erro quântico comparando resultados corrigidos e não corrigidos
Pré-requisitos
Contexto
Para habilitar a correção de erro quântico (QEC) em tempo real, você precisa ser capaz de controlar dinamicamente o fluxo do programa quântico durante a execução para que as portas quânticas possam ser condicionadas aos resultados de medição. Este tutorial executa o código de bit-flip, que é uma forma muito simples de QEC. Ele demonstra um circuito quântico dinâmico que pode proteger um qubit codificado de um único erro de bit-flip e, em seguida, avalia o desempenho do código de bit-flip.
Você pode explorar qubits ancilla adicionais e emaranhamento para medir estabilizadores que não transformam informações quânticas codificadas, enquanto ainda informam sobre algumas classes de erros que podem ter ocorrido. Um código estabilizador quântico codifica qubits lógicos em qubits físicos. Os códigos estabilizadores têm como foco central corrigir um conjunto discreto de erros com suporte no grupo de Pauli .
Neste tutorial, demonstramos o código de bit-flip usando um experimento simples de memória quântica. Vamos preparar o qubit codificado no estado lógico e então implementar múltiplos ciclos de tempo ocioso (para acumular erros) seguidos de detecção e correção de erros. Em seguida, quantificamos a probabilidade de erro lógico em função do número de ciclos (tempo), onde a probabilidade de erro lógico é a probabilidade de encontrar os qubits em um estado que não recupera o após a medição final (concretamente, os estados correspondentes a um erro são , , e ).
Vamos comparar a taxa de erro versus o tempo com as taxas de erro de qubits individuais não codificados, e também com o uso do código de repetição em que apenas detectamos e corrigimos erros após a medição final, mas não dinamicamente durante o circuito.
Observação: o código de repetição permite apenas a correção de erros de bit-flip e, portanto, não é um código de correção de erro completo. No entanto, devido à sua simplicidade, é um bom ponto de partida para ilustrar como implementar correção de erro em um computador quântico. O experimento de memória abaixo testa apenas um único tipo de erro (um decaindo para ) e, tecnicamente, demonstra apenas a proteção de informação clássica.
Requisitos
Antes de iniciar este tutorial, certifique-se de que você tem o seguinte instalado:
-
Qiskit SDK v2.0 ou posterior, com suporte para visualização
-
Qiskit Runtime v0.40 ou posterior (
pip install qiskit-ibm-runtime)
Configuração
# 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()
Exemplo de simulador em pequena escala
Vamos dispensar esta etapa, já que o objetivo deste experimento é medir o tempo de vida de um qubit lógico sob ruído real de hardware, como o amortecimento de amplitude durante os atrasos ociosos; um simulador sem ruído não mostraria erros a detectar e corrigir.
Exemplo de hardware em grande escala
Passo 1. Mapear entradas clássicas para um problema quântico
Escolha um backend
Para detectar erros durante o circuito, precisamos escolher um backend que tenha acesso ao método MidCircuitMeasure (consulte a documentação).
# 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
Construa uma sequência de circuitos estabilizadores de bit-flip implementando múltiplas rodadas de detecção e correção de erros
O código de bit-flip está entre os exemplos mais simples de um código estabilizador. Ele protege o estado contra um único erro de bit-flip (X) em qualquer um dos qubits de codificação. Considere a ação do erro de bit-flip , que mapeia e em qualquer um de nossos qubits, então temos . O código requer cinco qubits: três são usados para codificar o estado protegido (os "qubits de dados"), e os dois restantes são usados como ancillas de medição de estabilizador.
Abaixo você construirá circuitos que (1) preparam os qubits de dados no estado lógico , depois (2) executam múltiplos ciclos de um atraso de seguido de correção de erro (incluindo o reset dos qubits de síndrome), e (3) leem o estado dos qubits de dados.
Também testaremos o tempo de vida do estado sem usar correção de erro, incluindo três qubits de referência que preparamos no estado , deixamos ociosos e depois lemos.
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)

Passo 2: Otimizar o problema para execução em hardware quântico
Para reduzir o tempo total de execução do trabalho, as primitivas Qiskit aceitam apenas circuitos e observáveis que estejam em conformidade com as instruções e conectividade suportadas pelo sistema alvo (referidos como circuitos e observáveis de arquitetura de conjunto de instruções (ISA)). Saiba mais sobre transpilação.
Gerar circuitos ISA
Começaremos encontrando um layout inicial (ou seja, uma seleção de qubits físicos para mapear nossos circuitos) transpilando o mais longo de nossos circuitos usando o gerenciador de passes predefinido com nível de otimização 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 os qubits de referência com os quais compararemos nossa memória quântica lógica, escolheremos os melhores qubits disponíveis em termos de tempo de coerência de amortecimento de amplitude ().
# 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)

Passo 3. Executar usando primitivas 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'
Passo 4: Pós-processar e retornar resultado em formato clássico desejado
Agora vamos comparar as taxas de erro vs. tempo entre a memória lógica usando o código de repetição de 3 qubits, de um lado, e os qubits de referência individuais não codificados, de outro.
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 um tempo de vida melhorado do estado , mesmo em comparação com os melhores qubits físicos (não codificados). No entanto, tenha em mente que este experimento corrige apenas um tipo de erro: os bit-flips. Você pode melhorar o tempo de vida do qubit lógico? Talvez você queira explorar a otimização do tempo de atraso, aumentar o tamanho do código de repetição além de , etc.
Próximos passos
Se você achou este trabalho interessante, talvez se interesse pelo seguinte material:
-
Curso Fundamentos da correção de erro quântico - saiba mais sobre correção de erro quântico
-
Tutorial Detecção de erro de baixo overhead com códigos de espaço-tempo - aprenda a usar verificações de Pauli para detectar erros e pós-selecionar amostras