Expert Chapitre 30-31 / 16

Codes stabilisateurs & Codes de surface

Comprendre les codes stabilisateurs et les codes de surface — sans prérequis mathématiques, avec du code Q# et Qiskit.

Codes stabilisateurs

Le groupe de Pauli — un système d’étiquettes pour les erreurs

En jour 28-29, on a vu que le bruit quantique se manifeste par des erreurs de type bit-flip (X), phase-flip (Z), ou les deux combinés (Y = iXZ). Ces trois opérateurs, plus l’identité I, forment le groupe de Pauli sur un qubit. Sur n qubits, le groupe de Pauli contient tous les produits tensoriels possibles de I, X, Y, Z — par exemple X⊗Z⊗I sur 3 qubits.

Analogie C# : Pense au groupe de Pauli comme un enum d’exceptions. Dans un système distribué, tu as quelques catégories d’erreur fondamentales : TimeoutException, NullReferenceException, InvalidOperationException. Toute erreur réelle est une combinaison de ces catégories de base. De la même manière, toute erreur quantique peut s’exprimer comme une combinaison d’opérateurs de Pauli. C’est ce qui rend le formalisme si puissant : il suffit de savoir corriger I, X, Y et Z pour corriger n’importe quelle erreur.

Propriétés clés du groupe de Pauli :

  • Chaque opérateur est son propre inverse : X² = I, Z² = I, Y² = I
  • Deux opérateurs de Pauli soit commutent (AB = BA), soit anticommutent (AB = −BA)
  • Exemple : X et Z anticommutent (XZ = −ZX), mais X⊗I et I⊗Z commutent

Générateurs stabilisateurs — des « tests de santé » compacts

Un code stabilisateur est défini par un ensemble de générateurs — des opérateurs de Pauli multi-qubits qui « stabilisent » les états valides du code. Concrètement, si |ψ⟩ est un mot de code valide et S est un générateur stabilisateur, alors :

S|ψ⟩ = +1 × |ψ⟩

L’état est un vecteur propre de S avec valeur propre +1. Si une erreur E frappe le qubit et que E anticommute avec S, alors :

S(E|ψ⟩) = −1 × (E|ψ⟩)

La valeur propre bascule à −1. En mesurant S, on obtient le syndrome : +1 signifie « ce check passe », −1 signifie « erreur détectée ».

Analogie : Les générateurs stabilisateurs sont comme des tests unitaires dans une suite CI/CD. Chaque test vérifie une propriété du système sans exposer l’état interne. Si un test échoue (syndrome = −1), tu sais que quelque chose a cassé, mais le test lui-même ne révèle pas les données internes de ton application. De même, mesurer un stabilisateur détecte une erreur sans révéler l’état quantique logique encodé.

Le code à 3 qubits revisité : En jour 28-29, on a vu ce code avec des mesures de parité. En langage stabilisateur, ses deux générateurs sont :

  • S₁ = Z⊗Z⊗I (parité des qubits 0 et 1)
  • S₂ = I⊗Z⊗Z (parité des qubits 1 et 2)

Pour l’état |000⟩ + |111⟩, les deux donnent +1. Si le qubit 1 subit un bit-flip X, alors S₁ donne −1 et S₂ donne −1 — syndrome (−1, −1), ce qui pointe exactement vers le qubit 1. C’est exactement ce qu’on avait vu, mais exprimé de manière plus compacte et systématique.

Distance d’un code — combien d’erreurs avant l’invisibilité ?

La distance d d’un code quantique est le poids minimal (nombre de qubits non-triviaux) d’un opérateur de Pauli qui commute avec tous les stabilisateurs mais qui n’est pas lui-même un stabilisateur. En termes concrets :

  • Un code de distance d peut détecter jusqu’à d−1 erreurs
  • Il peut corriger jusqu’à ⌊(d−1)/2⌋ erreurs

On note un code quantique [[n, k, d]] où n = qubits physiques, k = qubits logiques, d = distance.

Analogie : La distance, c’est l’épaisseur d’un blindage. Un blindage d’épaisseur 3 stoppe 1 projectile (corrige 1 erreur) et détecte que 2 projectiles l’ont touché (sans pouvoir les corriger). Si 3 projectiles frappent au même endroit, ils traversent sans être détectés — c’est une erreur logique silencieuse, le pire scénario.

Exemples de codes :

CodeNotationQubits physiquesQubits logiquesDistance
Bit-flip 3 qubits[[3, 1, 1]]311 (ne corrige que les X)
Shor[[9, 1, 3]]913
Steane[[7, 1, 3]]713
Code de surface d=3[[17, 1, 3]]1713
Code de surface d=5[[49, 1, 5]]4915

Mesure de stabilisateur avec Qiskit — code à 3 qubits en langage stabilisateur

from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator

def code_stabilisateur_3qubits():
    """Mesure les deux stabilisateurs Z⊗Z⊗I et I⊗Z⊗Z."""
    # 3 qubits de données (q0, q1, q2) + 2 ancillas (q3, q4)
    qc = QuantumCircuit(5, 2)
    
    # Préparer l'état logique |0_L⟩ = |000⟩ + |111⟩ (non normalisé)
    qc.h(0)
    qc.cx(0, 1)
    qc.cx(0, 2)
    qc.barrier()
    
    # Injecter une erreur X sur le qubit 1
    qc.x(1)  # ← Bit-flip simulé
    qc.barrier()
    
    # --- Mesure du stabilisateur S1 = Z⊗Z⊗I ---
    # Ancilla q3 en superposition
    qc.h(3)
    # CNOT contrôlé par l'ancilla vers les qubits concernés
    qc.cx(3, 0)  # Parité avec q0
    qc.cx(3, 1)  # Parité avec q1
    qc.h(3)
    qc.measure(3, 0)  # Syndrome s1
    qc.barrier()
    
    # --- Mesure du stabilisateur S2 = I⊗Z⊗Z ---
    qc.h(4)
    qc.cx(4, 1)
    qc.cx(4, 2)
    qc.h(4)
    qc.measure(4, 1)  # Syndrome s2
    
    return qc

circuit = code_stabilisateur_3qubits()
sim = AerSimulator()
result = sim.run(transpile(circuit, sim), shots=1024).result()
print(result.get_counts())
# → Le syndrome (1, 1) domine : erreur détectée sur le qubit 1

Encodage d’un qubit logique en Q#

// Q# — Encoder un qubit logique dans un code stabilisateur à 3 qubits
operation EncoderQubitLogique(donnee : Qubit, ancillas : Qubit[]) : Unit {
    // donnee contient l'état α|0⟩ + β|1⟩ à protéger
    // ancillas contient 2 qubits initialisés à |0⟩
    
    // Encoder : α|0⟩ + β|1⟩  →  α|000⟩ + β|111⟩
    CNOT(donnee, ancillas[0]);
    CNOT(donnee, ancillas[1]);
    
    // Maintenant les 3 qubits forment un état intriqué
    // Les stabilisateurs Z⊗Z⊗I et I⊗Z⊗Z valent tous +1
    Message("Qubit logique encodé dans le code [[3,1,1]]");
}

operation MesureStabilisateur(qubits : Qubit[]) : (Result, Result) {
    // Mesurer S1 = Z⊗Z⊗I et S2 = I⊗Z⊗Z
    use a0 = Qubit();
    use a1 = Qubit();
    
    // S1 : parité de qubit[0] et qubit[1]
    H(a0);
    CNOT(a0, qubits[0]);
    CNOT(a0, qubits[1]);
    H(a0);
    let s1 = M(a0);
    Reset(a0);
    
    // S2 : parité de qubit[1] et qubit[2]
    H(a1);
    CNOT(a1, qubits[1]);
    CNOT(a1, qubits[2]);
    H(a1);
    let s2 = M(a1);
    Reset(a1);
    
    return (s1, s2);
}

Attention : Les stabilisateurs te disent quel type de syndrome s’est produit, pas directement quelle erreur l’a causé. Plusieurs erreurs physiques différentes peuvent produire le même syndrome. C’est le rôle du décodeur (un algorithme classique) d’interpréter le syndrome et de choisir la correction la plus probable. Un mauvais décodeur peut aggraver les choses en appliquant la mauvaise correction — c’est comme un catch qui fait un throw du mauvais type d’exception.


Codes de surface

Un échiquier quantique

Le code de surface est le candidat numéro un pour la correction d’erreur quantique à grande échelle. Son principe : disposer les qubits sur une grille 2D — comme un échiquier — et mesurer des stabilisateurs qui ne concernent que des voisins immédiats.

Analogie : Imagine un échiquier où chaque case contient un qubit. Les cases blanches sont les qubits de données (ils portent l’information logique). Les cases noires sont les qubits de syndrome (ils surveillent leurs voisins). Chaque qubit de syndrome ne parle qu’aux 4 qubits de données adjacents — comme un vigile qui surveille uniquement les 4 portes autour de lui.

Structure concrète :

  • Qubits de données : placés sur les arêtes de la grille
  • Stabilisateurs de type X (face) : produit de X sur les 4 qubits de données autour d’une face → détectent les erreurs Z (phase-flip)
  • Stabilisateurs de type Z (sommet) : produit de Z sur les 4 qubits de données autour d’un sommet → détectent les erreurs X (bit-flip)

Sur une grille d × d :

  • Nombre de qubits de données : environ 2d² − 2d + 1
  • Nombre de qubits de syndrome : environ d² − 1 (type X) + (d−1)² (type Z)
  • Pour d = 3 : 17 qubits de données, 4 stabilisateurs X, 4 stabilisateurs Z

Distance = taille de la grille

C’est la propriété la plus élégante du code de surface : la distance du code est égale à d, la dimension de la grille. Pour qu’une erreur logique passe inaperçue, il faudrait une chaîne d’erreurs qui traverse toute la grille d’un bord à l’autre — soit au minimum d erreurs.

Analogie : Pense à un champ de mines. Pour traverser sans être détecté, il faut un chemin complet d’un bord à l’autre. Plus le champ est large (d grand), plus il faut d’étapes pour le traverser. C’est pour ça qu’augmenter d rend le code exponentiellement plus fiable — tant que le taux d’erreur physique reste sous le seuil.

Augmenter la distance :

Distance dQubits physiques (≈)Erreurs corrigeablesTaux d’erreur logique (pour p = 0.1%)
3171~10⁻⁴
5492~10⁻⁷
7973~10⁻¹⁰
112215~10⁻¹⁶

Le seuil de tolérance aux erreurs — la ligne magique à ~1%

Le code de surface a un seuil d’erreur d’environ 1% (plus précisément ~0.7% à ~1.1% selon le modèle de bruit). Cela signifie :

  • Si le taux d’erreur physique par porte est en dessous du seuil : augmenter d réduit exponentiellement le taux d’erreur logique. Plus ta grille est grande, plus ton qubit logique est fiable.
  • Si le taux d’erreur physique est au-dessus du seuil : augmenter d empire les choses. Les erreurs se propagent plus vite que le code ne peut les corriger.

Analogie : C’est comme le point de bascule dans un système de monitoring. Si ton taux d’erreur par requête est sous un certain seuil, ajouter des replicas améliore la fiabilité globale (redondance utile). Mais si le taux est trop élevé, plus de replicas signifie plus de messages de synchronisation, plus de conflits — le système empire. Le seuil, c’est cette ligne de démarcation.

État actuel (2025-2026) : Les meilleurs processeurs supraconducteurs (Google Willow, IBM Heron) atteignent des taux d’erreur par porte à 2 qubits de 0.1% à 0.5% — clairement sous le seuil de 1%. C’est pourquoi la communauté est optimiste : on est dans la zone où le code de surface fonctionne.

Pourquoi le matériel supraconducteur adore le code de surface

Le code de surface a un avantage décisif pour le hardware supraconducteur : il ne nécessite que des interactions entre voisins immédiats sur la grille 2D. Aucune porte CNOT longue distance n’est requise.

Pourquoi c’est crucial :

  • Les qubits supraconducteurs sont fabriqués sur une puce planaire (2D)
  • Les portes à deux qubits (CNOT, CZ) ne fonctionnent bien qu’entre qubits physiquement adjacents
  • Le code de surface s’aligne parfaitement avec cette contrainte physique
  • D’autres codes (Steane, codes LDPC) nécessitent des connexions longue distance, coûteuses et bruyantes

Analogie : C’est comme la différence entre une architecture microservices avec communication locale uniquement (chaque service ne parle qu’à ses voisins directs via un bus local) et une architecture qui exige des appels réseau entre data centers distants. Le premier est plus simple, plus rapide, et plus fiable. Le code de surface, c’est l’architecture « communication locale uniquement ».

Code de surface — vérification des stabilisateurs en Qiskit

from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator

def surface_code_stabilisateurs_d3():
    """
    Surface code simplifié (distance 3) — mesure d'un stabilisateur X
    et d'un stabilisateur Z sur une portion de la grille.
    
    Grille 3x3 simplifiée :
        d0 -- d1 -- d2
        |     |     |
        d3 -- d4 -- d5
        |     |     |
        d6 -- d7 -- d8
    
    On mesure :
    - Stabilisateur X (face centrale) : X sur d0, d1, d3, d4
    - Stabilisateur Z (sommet central) : Z sur d1, d2, d4, d5
    """
    # 9 qubits de données + 2 ancillas de syndrome
    qc = QuantumCircuit(11, 2)
    
    # Initialiser les qubits de données dans |0⟩ (état trivial du code)
    # (déjà fait par défaut)
    
    # Injecter une erreur Z sur le qubit d1 (index 1)
    qc.z(1)  # ← Erreur de phase simulée
    qc.barrier()
    
    # --- Mesure du stabilisateur X (face) : X₀X₁X₃X₄ ---
    # L'ancilla est le qubit 9
    qc.h(9)       # Ancilla en superposition
    qc.cx(9, 0)   # CNOT vers d0
    qc.cx(9, 1)   # CNOT vers d1
    qc.cx(9, 3)   # CNOT vers d3
    qc.cx(9, 4)   # CNOT vers d4
    qc.h(9)
    qc.measure(9, 0)  # Syndrome face
    qc.barrier()
    
    # --- Mesure du stabilisateur Z (sommet) : Z₁Z₂Z₄Z₅ ---
    # L'ancilla est le qubit 10
    qc.cx(1, 10)  # CNOT depuis d1
    qc.cx(2, 10)  # CNOT depuis d2
    qc.cx(4, 10)  # CNOT depuis d4
    qc.cx(5, 10)  # CNOT depuis d5
    qc.measure(10, 1)  # Syndrome sommet
    
    return qc

circuit = surface_code_stabilisateurs_d3()
sim = AerSimulator()
result = sim.run(transpile(circuit, sim), shots=1024).result()
print(result.get_counts())
# → Le stabilisateur X (face) détecte l'erreur Z sur d1
# Le syndrome face = 1, syndrome sommet dépend de la position de l'erreur

Stabilisateurs de surface en Q#

// Q# — Vérification de stabilisateurs sur une grille 2D
operation MesureStabilisateurX(
    donnees : Qubit[],
    indices : Int[]
) : Result {
    // Mesure un stabilisateur de type X (face)
    // Produit de X sur les qubits aux indices donnés
    use ancilla = Qubit();
    
    H(ancilla);
    for idx in indices {
        CNOT(ancilla, donnees[idx]);
    }
    H(ancilla);
    
    let syndrome = M(ancilla);
    Reset(ancilla);
    return syndrome;
}

operation MesureStabilisateurZ(
    donnees : Qubit[],
    indices : Int[]
) : Result {
    // Mesure un stabilisateur de type Z (sommet)
    // Produit de Z sur les qubits aux indices donnés
    use ancilla = Qubit();
    
    for idx in indices {
        CNOT(donnees[idx], ancilla);
    }
    
    let syndrome = M(ancilla);
    Reset(ancilla);
    return syndrome;
}

operation VerifierGrilleSurface() : (Result, Result) {
    // Grille simplifiée : 4 qubits de données
    // Stabilisateur X sur tous les 4, stabilisateur Z sur tous les 4
    use donnees = Qubit[4];
    
    // Injecter une erreur Z sur le qubit 0
    Z(donnees[0]);
    
    // Mesurer les stabilisateurs
    let sx = MesureStabilisateurX(donnees, [0, 1, 2, 3]);
    let sz = MesureStabilisateurZ(donnees, [0, 1, 2, 3]);
    
    Message($"Syndrome X (face) : {sx}");
    Message($"Syndrome Z (sommet) : {sz}");
    
    // Nettoyer
    ResetAll(donnees);
    return (sx, sz);
}

Attention — Le coût en qubits physiques est énorme : Pour exécuter l’algorithme de Shor et casser RSA-2048, les estimations actuelles prévoient environ 20 millions de qubits physiques avec un code de surface de distance ~23. Les meilleurs processeurs actuels ont ~1 000 à 1 500 qubits. Le code de surface fonctionne en principe, mais le passage à l’échelle reste un défi industriel majeur. C’est comme avoir un algorithme O(n log n) parfait… mais n = 20 millions et chaque opération coûte un composant physique sur une puce.


Quiz — teste tes connaissances
Expert 7 questions Objectif : 5/7 minimum
0/7
bonnes reponses
Objectif non atteint (minimum 5/7 requis).
Remonte relire la fiche memo ci-dessus en pretant attention aux points rates, puis clique sur « Recommencer » pour retenter.