Desenvolver e validar um protocolo QEC

Use qodec e o qdk.ec módulo do Microsoft Quantum Development Kit (QDK) para definir um código de correção quântica de erros (QEC) e transformá-lo num protocolo executável. Neste artigo, defines um código de erro, constróis uma baseline qodec, validas as suas declarações, calculas a distância do gadget e guardas o protocolo.

Os exemplos neste artigo usam o código $[[4,2,2]]$ C4 , que armazena dois qubits lógicos em quatro qubits físicos e deteta qualquer erro físico Pauli individual.

Prerequisites

Conclua estas tarefas antes de começar:

  • Instale QDK para a correção de erros.
  • Usa um ambiente com Python a versão 3.11 ou posterior.
  • Confirma que o qodecStim parser está disponível. Em LinuxArm64 ou WindowsArm64, instale Stim a partir da fonte e depois reinicie Python ou o kernel Jupyter antes de chamar build_qodec.

Defina o código

Crie um qodec.Code objeto com os estabilizadores do código e um representante lógico X e Z para cada qubit lógico.

import qodec

c4 = qodec.Code(
    "C4",
    stabilizers=["X_0 X_1 X_2 X_3", "Z_0 Z_1 Z_2 Z_3"],
    x=["X_0 X_1", "X_0 X_2"],
    z=["Z_0 Z_2", "Z_0 Z_1"],
)

O pacote qodec utiliza notação esparsa para cadeias Pauli. Cada termo tem uma Pauli letra e um rótulo físico de qubit. Por exemplo, X_0 X_2 aplica X aos qubits físicos 0 e 2.

A definição do código é um objeto matemático estático. Não inclui circuitos que preparam, preservam ou medem estados codificados.

Analise o código

Para inspecionar um snapshot dos operadores de código, crie um CodeProfile.

from qdk import ec

profile = ec.CodeProfile(c4)

print("Physical support:", sorted(profile.support))
print("Physical qubits:", profile.length)
print("Logical qubits:", profile.logical_qubit_count)
print("Stabilizers:", profile.stabilizers)
print("Logical X operators:", profile.x)
print("Logical Z operators:", profile.z)

O perfil não muda se depois mudares o objeto original qodec.Code .

Calcular a distância do código

Para encontrar o número mínimo de erros físicos permitidos que causam um efeito lógico não trivial com síndrome zero, chame distance.

distance = profile.distance()
print("Code distance:", distance)

error = distance.witness.product
syndrome = profile.syndrome_of(error)
logical_effect = profile.logical_effect_of(error)

print("Witness:", error)
print("Syndrome:", sorted(syndrome))
print("Logical effect:", logical_effect)

assert distance.value == 2
assert profile.is_logical(error)

Por defeito, a procura atribui custo um a cada erro X, Y ou Z de qubit único. O retornado Distance tem limites certificados e um testemunho para qualquer limite superior finito.

Quando uma pesquisa exata completa custa demasiado, use distance_bounds.

bounds = profile.distance_bounds(upper_bound=4)
print("Distance bounds:", bounds)
print("Lower bound:", bounds.lower_bound)
print("Upper bound:", bounds.upper_bound)

O solver predefinido é HiGHS. Para usar um solucionador diferente, defina solver="enumeration", ou instale MWPF e defina solver="mwpf".

Construa uma baseline qodec

A qodec descreve como executar instruções codificadas. Tem conjuntos de instruções, códigos, camadas, gadgets, circuitos, verificações e equações de saída.

Utilize build_qodec para criar um protocolo de duas camadas a partir do código C4.

protocol = ec.build_qodec(
    c4,
    strategy="bare-css/v1",
    strict=False,
)

Este exemplo utiliza bare-css/v1 para lhe permitir inspecionar um protocolo simples sem qubits de bandeira.

Caution

A estratégia bare-css/v1 não promete circuitos tolerantes a falhas. Calcule a distância de cada gadget antes de usar o protocolo para experiências ruidosas.

O construtor tenta criar estas instruções lógicas:

  • prepare_z_all
  • prepare_x_all
  • syndrome
  • measure_z_all
  • measure_x_all
  • h_all
  • cx_all

O sufixo _all significa que uma instrução atua sobre cada qubit lógico num bloco. Por exemplo, cx_all aplica CNOT entre qubits lógicos correspondentes em dois blocos.

Nem todos os códigos suportam todas as instruções candidatas. Com strict=True, que é o padrão, o construtor gera um erro quando não consegue construir e verificar um candidato. Com strict=False, o construtor mantém as instruções suportadas e regista as omissões em protocol.metadata["qdk.ec"]["build"]["omitted"].

Inspeciona a camada lógica e os seus gadgets.

logical_layer = protocol.layers[0]

print("Instructions:")
for mnemonic in logical_layer.instruction_set.instructions:
    print(" -", mnemonic)

print("Gadgets:")
for mnemonic in logical_layer.gadgets:
    print(" -", mnemonic)

A segunda camada contém o conjunto físico Stim de instruções que os circuitos gerados utilizam.

Inspecionar um gadget

Uma instrução lógica declara um contrato. O seu dispositivo fornece o circuito que implementa o contrato.

Inspecione o prepare_z_all aparelho.

gadget = logical_layer.gadgets["prepare_z_all"]
gadget_profile = ec.GadgetProfile(gadget)

print("Declared objective:")
print(gadget_profile.objective)

print("Computed action:")
print(gadget_profile.action)

Compare a ação calculada a partir do circuito com o objetivo declarado do comando.

objective = gadget_profile.objective
assert objective is not None

if gadget_profile.action.is_equivalent_to(objective):
    print("The gadget implements its declared instruction.")
else:
    print(objective.why_not_equivalent_to(gadget_profile.action))

A GadgetProfile captura um instantâneo da sua entrada, por isso não reflete alterações posteriores. Depois de editar um gadget, crie um novo perfil.

Auditar o protocolo

Use audit para verificar o qodec todo.

report = ec.audit(protocol)
print(report)

Se a auditoria não encontrar erros ou avisos, imprime:

audit: ok

A auditoria verifica as seguintes áreas:

  • Code algebra
  • Instrução e estrutura de camadas
  • Chamadas de circuito e associações de parâmetros
  • Ações do gadget
  • Verificações e equações de indicação
  • Assinalar equações
  • Relações de tramas de entrada e saída
  • Limites de referência e completude do protocolo

Para agir sobre severidades específicas, utilize as coleções de relatórios.

if report.errors:
    for diagnostic in report.errors:
        print(diagnostic)
    raise ValueError("The qodec has audit errors.")

if report.warnings:
    for diagnostic in report.warnings:
        print(diagnostic)

O report.ok valor significa que o relatório não contém erros. Os avisos não alteram essa propriedade.

Determinar as equações em falta

Utilize filled quando um dispositivo de rascunho ou qodec omite verificações ou equações lógicas que a simulação exata pode derivar.

O exemplo seguinte cria uma cópia incompleta do protocolo.

draft = qodec.Qodec.loads(protocol.dumps())
draft_gadgets = draft.layers[0].gadgets

draft_gadgets["measure_x_all"].readouts = []
draft_gadgets["cx_all"].checks = []

print(ec.audit(draft))

Preencha as equações suportadas e depois audite o resultado.

completed = ec.filled(draft)
print(ec.audit(completed))

A filled função devolve um novo gadget ou qodec e não altera a sua entrada. Deriva controlos, equações de leitura observáveis e relações de referencial. Preserva as ligações de flag porque a simulação sem ruído não consegue identificar quais as falhas que uma flag deve detetar.

Se o protocolo original já tiver equações diretas verificadas, filled mantém-nas sempre que possível.

Analisar falhas de dispositivos

Uma auditoria sem erros prova consistência sem ruído. Não prova tolerância a falhas.

Chame GadgetProfile.distance para encontrar o número mínimo de falhas de circuito permitidas que causam uma falha lógica enquanto todas as verificações e flags declarados permanecem zero. A falha combinada deve também deixar cada saída dentro do seu espaço de código.

protocol = completed

for mnemonic, gadget in sorted(protocol.layers[0].gadgets.items()):
    result = ec.GadgetProfile(gadget).distance()
    witness = result.witness if result.upper_bound is not None else "none"
    print(f"{mnemonic}: distance={result}, witness={witness}")

O modelo de avarias por defeito inclui estes eventos após cada chamada de circuito:

  • Um Pauli erro nos qubits afetados pela chamada
  • Uma inversão de um bit de saída que a chamada produz
  • Uma combinação correlacionada desses erros

Cada evento numa chamada custa 1, mesmo quando tem um erro multiqubit correlacionado.

Um código pode ter distância dois, enquanto um dos seus dispositivos tem distância um. Por exemplo, uma única falha de circuito pode espalhar-se por um ancilla e criar um erro lógico não detetado. Adiciona verificações ou circuitos assinalados, depois cria um novo GadgetProfile e calcula novamente a distância.

Inspecionar uma falha específica

Utilize FaultEvent.after para definir uma falha após um índice de chamada de circuito baseado em zero.

gadget = protocol.layers[0].gadgets["syndrome"]
profile = ec.GadgetProfile(gadget)

fault = ec.FaultEvent.after(0, ec.Pauli("X_0"))
(effect,) = profile.effects_of([fault])

print("Fault:", fault)
print("Changed quantities:", effect)

A FaultEffect pode incluir verificações modificadas, indicações lógicas e indicadores da trama de saída. Utilize conjuntos de falhas explícitas quando o modelo padrão ao nível do circuito não corresponde às suas suposições de hardware.

result = profile.distance(faults=[fault])
print(result)

Pesquisas exatas de gadgets podem demorar muito tempo porque o conjunto de falhas padrão cresce exponencialmente com a abrangência de cada chamada. Utilize distance_bounds, um conjunto explícito de falhas, ou um upper_bound quando precisar de limitar a pesquisa.

Guardar o protocolo

Verifica a versão final qodec antes de a guardares.

report = ec.audit(protocol)
if report.errors or report.warnings:
    raise ValueError(str(report))

Guarda o protocolo como um único YAML bundle e carrega-o novamente para verificar se corresponde ao original.

from pathlib import Path

output_directory = Path("qodec-output")
output_directory.mkdir(exist_ok=True)

saved_path = protocol.save(str(output_directory), single_file=True)
loaded = qodec.Qodec.load(saved_path)

assert loaded == protocol
print("Saved qodec:", saved_path)

Um pacote de ficheiro único armazena o manifesto, definições de código, conjuntos de instruções, gadgets e fontes de circuito num único YAML ficheiro, o que facilita a partilha e o recarregamento.

Execute o protocolo

Depois de o qodec passar na sua auditoria e cumprir os requisitos de distância, compila um programa lógico para Quantum Intermediate Representation (QIR) e executa-o através do protocolo. Veja Execução e descodificação de programas lógicos.

Para um exemplo completo que adiciona circuitos flag e compara taxas de erro lógicas, veja o guia do protocolo QEC no QDK repositório GitHub.