Développer et valider un protocole QEC

Utilisez qodec et le qdk.ec module de l’Microsoft Quantum Development Kit (QDK) pour définir un code de correction d’erreur quantique (QEC) et le transformer en protocole exécutable. Dans cet article, vous définissez un code d’erreur, créez un qodec de référence, validez ses déclarations, calculez la distance du gadget et enregistrez le protocole.

Les exemples de cet article utilisent le code $[[4,2,2]]$ C4 , qui stocke deux qubits logiques dans quatre qubits physiques et détecte toute erreur physique Pauli unique.

Prerequisites

Effectuez ces tâches avant de commencer :

  • Installer QDK pour la correction des erreurs.
  • Utilisez un environnement avec Python 3.11 ou une version ultérieure.
  • Vérifiez que l’analyseur qodecStim est disponible. Sur LinuxArm64 ou WindowsArm64, installez Stim à partir de la source, puis redémarrez Python ou le Jupyter noyau avant d’appeler build_qodec.

Définir le code

Créez un qodec.Code objet avec les stabilisateurs de code et un représentant X et Z logique pour chaque qubit logique.

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"],
)

Le package qodec utilise la notation clairsemée pour les chaînes Pauli. Chaque terme a une lettre Pauli et une étiquette de qubit physique. Par exemple, X_0 X_2 applique X aux qubits physiques 0 et 2.

La définition du code est un objet mathématique statique. Il n’inclut pas de circuits qui préparent, conservent ou mesurent des états codés.

Analyser le code

Pour inspecter une capture des opérateurs de code, créez un 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)

Le profil ne change pas si vous modifiez ultérieurement l’objet d’origine qodec.Code .

Calculer la distance du code

Pour trouver le nombre minimal d’erreurs physiques autorisées qui provoquent un effet logique non trivial avec zéro syndrome, appelez 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)

Par défaut, la recherche affecte un coût de un à chaque erreur X, Y ou Z à qubit unique. La valeur renvoyée Distance a des limites certifiées et un témoin pour toute limite supérieure finie.

Quand une recherche exacte complète coûte trop cher, utilisez 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)

Le solveur par défaut est HiGHS. Pour utiliser un autre solveur, définir solver="enumeration"ou installer MWPF et définir solver="mwpf".

Créer une base de référence qodec

Un qodec décrit comment exécuter des instructions encodées. Il comporte des ensembles d’instructions, des codes, des couches, des gadgets, des circuits, des vérifications et des équations de lecture.

Utilisez build_qodec pour créer un protocole à deux couches à partir du code C4.

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

Cet exemple utilise bare-css/v1 pour vous permettre d’inspecter un protocole simple sans qubits d’indicateur.

Caution

La bare-css/v1 stratégie ne promet pas de circuits tolérants aux pannes. Calculez la distance de chaque gadget avant d’utiliser le protocole pour les expériences bruyantes.

Le builder tente de créer ces instructions logiques :

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

Le _all suffixe signifie qu’une instruction agit sur chaque qubit logique dans un bloc. Par exemple, cx_all applique CNOT entre les qubits logiques correspondants dans deux blocs.

Tous les codes ne prennent pas nécessairement en charge chaque instruction candidate. Avec strict=True, qui est la valeur par défaut, le builder signale une erreur lorsqu’il ne peut pas générer et vérifier un candidat. Avec strict=False, le générateur conserve les instructions prises en charge et enregistre les omissions dans protocol.metadata["qdk.ec"]["build"]["omitted"].

Inspectez la couche logique et ses 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)

La deuxième couche contient le jeu d’instructions physiques Stim que les circuits générés utilisent.

Inspectez un gadget

Une instruction logique déclare un contrat. Ce gadget fournit le circuit qui implémente le contrat.

Inspectez le prepare_z_all gadget.

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)

Comparez l’action calculée à partir du circuit avec l’objectif d’instruction déclaré.

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))

Un GadgetProfile prend une capture instantanée de son entrée, de sorte qu’il ne reflète pas les modifications ultérieures. Après avoir modifié un gadget, créez un profil.

Auditer le protocole

Utilisez audit pour vérifier l’ensemble de qodec.

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

Si l’audit ne trouve pas d’erreurs ou d’avertissements, il imprime :

audit: ok

L’audit vérifie ces domaines :

  • Algèbre du code
  • Instruction et structure de couche
  • Appels de circuit et affectations de paramètres
  • Actions du gadget
  • Vérifications et équations de lecture
  • Équations de signalement
  • Relations de trames d’entrée et de sortie
  • Limites de référence et complétivité du protocole

Pour agir sur des niveaux de gravité spécifiques, utilisez les collections de rapports.

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)

La report.ok valeur signifie que le rapport n’a aucune erreur. Les avertissements ne modifient pas cette propriété.

Déduire les équations manquantes

Utilisez filled lorsqu’un brouillon de gadget ou un qodec omet des vérifications ou des équations de lecture logique que la simulation exacte permet de déduire.

L’exemple suivant crée une copie incomplète du protocole.

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))

Renseignez les équations prises en charge, puis auditez le résultat.

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

La filled fonction retourne un nouveau gadget ou qodec et ne modifie pas son entrée. Il dérive les contrôles, les équations de lecture observables et les relations de cadres. Il conserve les associations d’indicateurs, car la simulation sans bruit ne peut pas identifier les défaillances qu’un indicateur doit détecter.

Si le protocole d’origine comporte déjà des équations directes vérifiées, filled les conserve si possible.

Analyser les défaillances de gadget

Un audit sans erreur prouve la cohérence parfaite. Il ne prouve pas la tolérance aux pannes.

Appelez GadgetProfile.distance pour trouver le nombre minimal d’erreurs de circuit autorisées provoquant une défaillance logique alors que toutes les vérifications et indicateurs déclarés restent à zéro. Le défaut combiné doit également laisser chaque sortie à l’intérieur de son espace de code.

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}")

Le modèle de défaut par défaut inclut les événements suivants après chaque appel de circuit :

  • Erreur Pauli sur les qubits que l’appel affecte
  • Un retournement d’un bit de lecture produit par l’appel
  • Une combinaison corrélée de ces erreurs

Chaque événement sur un appel coûte un, même lorsqu’il est associé à une erreur multi-qubit.

Un code peut avoir la distance deux alors qu’un de ses gadgets a une distance 1. Par exemple, une erreur de circuit unique peut se propager à travers une ancilla et créer une erreur logique non détectée. Ajoutez des vérifications ou des circuits de signalisation, puis créez un nouveau GadgetProfile et calculez à nouveau la distance.

Inspectez un défaut spécifique

Utilisez FaultEvent.after pour définir une défaillance après un index d’appel de circuit commençant à zéro.

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)

Un FaultEffect peut inclure des vérifications modifiées, des affichages logiques et des indicateurs de cadre de sortie. Utilisez des jeux d’erreurs explicites lorsque le modèle au niveau du circuit par défaut ne correspond pas à vos hypothèses matérielles.

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

Les recherches précises de gadgets peuvent prendre beaucoup de temps, car l’ensemble de défauts par défaut s’accroît de manière exponentielle à chaque appel. Utilisez distance_bounds, un jeu d’erreurs explicite ou un upper_bound lorsque vous devez limiter la recherche.

Enregistrer le protocole

Auditez la version finale de qodec avant de l’enregistrer.

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

Enregistrez le protocole en tant qu’ensemble unique YAML , puis chargez-le à nouveau pour vérifier qu’il correspond à l’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)

Un bundle monofichier stocke le manifeste, les définitions de code, les jeux d’instructions, les gadgets et les sources de circuit dans un seul fichier YAML, ce qui facilite le partage et le rechargement.

Exécuter le protocole

Après que le qodec a passé son audit et répond à vos exigences de distance, compilez un programme logique en représentation intermédiaire quantique (QIR) et exécutez-le via le protocole. Consultez Exécuter et décoder des programmes logiques.

Pour obtenir un exemple complet qui ajoute des circuits d’indicateur et compare les taux d’erreur logiques, consultez la procédure pas à pas du protocole QEC sur le QDK référentiel GitHub.