annexes
This commit is contained in:
70
code_slides.typ
Normal file
70
code_slides.typ
Normal file
@ -0,0 +1,70 @@
|
||||
// ============================================================
|
||||
// code_slides.typ — Module de génération de slides de code
|
||||
// Thème clair, sobre, lisible
|
||||
// ============================================================
|
||||
|
||||
// Couleurs
|
||||
#let couleur-orange = rgb("#E85D04")
|
||||
#let fond-code = rgb("#F4F4F4") // gris très clair
|
||||
#let texte-code = rgb("#1A1A1A") // quasi-noir, très lisible
|
||||
#let bordure-code = rgb("#CCCCCC") // gris moyen pour le contour
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// STYLE DU BLOC DE CODE — fond clair, texte foncé, sobre
|
||||
// ----------------------------------------------------------
|
||||
#let style-bloc-code(contenu) = block(
|
||||
width: 100%,
|
||||
fill: fond-code,
|
||||
radius: 4pt,
|
||||
inset: (x: 1.2em, y: 1em),
|
||||
breakable: true,
|
||||
stroke: (paint: bordure-code, thickness: 1pt),
|
||||
)[
|
||||
#set text(fill: texte-code, size: 14pt) // taille augmentée
|
||||
#contenu
|
||||
]
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// SLIDE DE TRANSITION — texte simple, pas d'effet
|
||||
// ----------------------------------------------------------
|
||||
#let slide-titre-fichier(nom-fichier, myslide, graphe_tanner_fond) = myslide(
|
||||
// Header : juste le texte de section
|
||||
[Code Source],
|
||||
// Contenu : nom du fichier centré, sobre
|
||||
align(center + horizon)[
|
||||
// Graphe de Tanner en fond décoratif (discret)
|
||||
#place(center + horizon, dy: 8.2cm)[
|
||||
#graphe_tanner_fond(0.9cm, 1.75)
|
||||
]
|
||||
|
||||
#place(center + horizon, dy: 7.5cm)[#text(size: 50pt, weight: "bold")[#nom-fichier]]
|
||||
],
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// SLIDE DE CODE — header texte seul, sans icône
|
||||
// ----------------------------------------------------------
|
||||
#let slide-code-fichier(nom-fichier, contenu-code, myslide) = myslide(
|
||||
// Header : juste le nom du fichier, texte brut
|
||||
[#nom-fichier],
|
||||
// Contenu
|
||||
style-bloc-code[
|
||||
#raw(contenu-code, lang: "rust", block: true)
|
||||
],
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// FONCTION PRINCIPALE
|
||||
// ----------------------------------------------------------
|
||||
#let generer_slides_code(fichiers, myslide, graphe_tanner_fond) = {
|
||||
for chemin in fichiers {
|
||||
let parties = chemin.split("/")
|
||||
let nom = parties.last()
|
||||
|
||||
slide-titre-fichier(nom, myslide, graphe_tanner_fond)
|
||||
|
||||
let source = read(chemin)
|
||||
|
||||
slide-code-fichier(nom, source, myslide)
|
||||
}
|
||||
}
|
||||
75
ldpc-theme.tmTheme
Normal file
75
ldpc-theme.tmTheme
Normal file
@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Thème de coloration syntaxique LDPC — charte Orange/Vert/Rouge/Bleu -->
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>name</key> <string>LDPC Theme</string>
|
||||
<key>settings</key>
|
||||
<array>
|
||||
<!-- Base : fond sombre, texte clair -->
|
||||
<dict>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key> <string>#0F1117</string>
|
||||
<key>foreground</key> <string>#E8EAF6</string>
|
||||
<key>caret</key> <string>#E85D04</string>
|
||||
<key>selection</key> <string>#1A73E840</string>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<!-- ORANGE : mots-clés (fn, let, mut, pub, use, mod…) -->
|
||||
<dict>
|
||||
<key>name</key> <string>Keywords</string>
|
||||
<key>scope</key> <string>keyword, keyword.control, keyword.other,
|
||||
storage.type, storage.modifier</string>
|
||||
<key>settings</key>
|
||||
<dict><key>foreground</key><string>#E85D04</string></dict>
|
||||
</dict>
|
||||
|
||||
<!-- VERT : commentaires et chaînes de caractères -->
|
||||
<dict>
|
||||
<key>name</key> <string>Strings and Comments</string>
|
||||
<key>scope</key> <string>string, string.quoted, comment,
|
||||
comment.line, comment.block</string>
|
||||
<key>settings</key>
|
||||
<dict><key>foreground</key><string>#2D9A4E</string></dict>
|
||||
</dict>
|
||||
|
||||
<!-- ROUGE : types, traits, structs, enums -->
|
||||
<dict>
|
||||
<key>name</key> <string>Types and Traits</string>
|
||||
<key>scope</key> <string>entity.name.type, support.type,
|
||||
entity.name.struct, entity.name.enum,
|
||||
entity.name.trait</string>
|
||||
<key>settings</key>
|
||||
<dict><key>foreground</key><string>#D62828</string></dict>
|
||||
</dict>
|
||||
|
||||
<!-- BLEU : fonctions, macros, variables liées -->
|
||||
<dict>
|
||||
<key>name</key> <string>Functions and Macros</string>
|
||||
<key>scope</key> <string>entity.name.function, meta.macro,
|
||||
support.function, variable.other.member</string>
|
||||
<key>settings</key>
|
||||
<dict><key>foreground</key><string>#1A73E8</string></dict>
|
||||
</dict>
|
||||
|
||||
<!-- Nombres : orange clair -->
|
||||
<dict>
|
||||
<key>name</key> <string>Numbers</string>
|
||||
<key>scope</key> <string>constant.numeric</string>
|
||||
<key>settings</key>
|
||||
<dict><key>foreground</key><string>#FF8C42</string></dict>
|
||||
</dict>
|
||||
|
||||
<!-- Constantes booléennes/lifetime -->
|
||||
<dict>
|
||||
<key>name</key> <string>Constants</string>
|
||||
<key>scope</key> <string>constant.language, storage.lifetime</string>
|
||||
<key>settings</key>
|
||||
<dict><key>foreground</key><string>#1A73E8</string></dict>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
467
main.typ
467
main.typ
@ -1,4 +1,6 @@
|
||||
#import "composants.typ": *
|
||||
#import "@preview/touying:0.5.2": *
|
||||
#import "code_slides.typ": generer_slides_code
|
||||
|
||||
#set page(
|
||||
paper: "presentation-4-3",
|
||||
@ -1460,12 +1462,15 @@
|
||||
#[]<fin>
|
||||
|
||||
#myslide("Annexe")[
|
||||
#v(6.5cm)
|
||||
#align(center + horizon)[
|
||||
#text(size: 100pt)[
|
||||
Annexe
|
||||
]
|
||||
#align(center + horizon)[
|
||||
#image("src/construction.jpg", width: 80%)
|
||||
]
|
||||
// #align(center + horizon)[
|
||||
// #image("src/construction.jpg", width: 80%)
|
||||
// ]
|
||||
]
|
||||
|
||||
// #myslide("Définition : Matrice Génératrice")[
|
||||
@ -1545,20 +1550,371 @@
|
||||
Poser les notations algebriques etc...
|
||||
]
|
||||
|
||||
#myslide("Définition du BER et SFR")[
|
||||
#myslide("Métriques : BER et FER")[
|
||||
#set text(size: 18pt)
|
||||
|
||||
#definition(titre: "Bit Error Rate (BER)", accent: blue)[
|
||||
$
|
||||
"BER" = frac("Nombre de bits incorrects reçus", "Nombre total de bits transmis")
|
||||
$
|
||||
]
|
||||
|
||||
#myslide("Decodage par maximum de vraisemblance")[
|
||||
Expliquer, quelle distance ? etc
|
||||
#v(0.6em)
|
||||
|
||||
#definition(titre: "Frame Error Rate (FER)", accent: orange)[
|
||||
$
|
||||
"FER" = frac("Nombre de trames avec au moins 1 bit incorrect", "Nombre total de trames")
|
||||
$
|
||||
]
|
||||
|
||||
#myslide("Code LDPC non régulier")[
|
||||
#v(0.6em)
|
||||
|
||||
#grid(
|
||||
columns: (1fr, 1fr),
|
||||
gutter: 0.8em,
|
||||
block(fill: blue.lighten(90%), stroke: 0.5pt + blue.lighten(30%), inset: 10pt, radius: 4pt)[
|
||||
*Courbe Waterfall* : BER en fonction de $E_b \/ N_0$ (dB) — représente les performances
|
||||
],
|
||||
block(fill: orange.lighten(90%), stroke: 0.5pt + orange.lighten(30%), inset: 10pt, radius: 4pt)[
|
||||
FER $>=$ BER \
|
||||
Une seule erreur de bit invalide toute la trame
|
||||
],
|
||||
)
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
// - *Monte-Carlo* : simuler un grand nombre de transmissions, collecter ~1000 erreurs par point
|
||||
- $E_b\/N_0$ en dB $= 10 log_10 (E_b\/N_0)$
|
||||
]
|
||||
|
||||
#myslide("Richardson-Urbanke")[
|
||||
#myslide("Décodage par Maximum de Vraisemblance (ML)")[
|
||||
#set text(size: 17pt)
|
||||
|
||||
#definition(titre: "Décodeur ML — Canal AWGN + BPSK")[
|
||||
$
|
||||
hat(bold(c)) = arg max_(bold(c) in cal(C)) P(bold(r) | bold(c)) = arg min_(bold(c) in cal(C)) d_H (bold(r), bold(c))
|
||||
$
|
||||
Minimiser la distance de Hamming au mot de code le plus proche.
|
||||
]
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
#definition(titre: "Décodage par Syndrome (Cosets)")[
|
||||
- Calculer $bold(s) = bold(H) bold(r)^top$
|
||||
- Chercher $bold(e)$ de poids minimal tel que $bold(H) bold(e)^top = bold(s)$
|
||||
- Table préalculée de $2^(n-k)$ entrées $-->$ faisable seulement si $n - k$ petit
|
||||
]
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
#definition(titre: "Complexité — NP-Difficile (Berlekamp, 1978)", accent: red)[
|
||||
Pour $bold(H)$ quelconque : $cal(O)(2^k)$ candidats à tester.
|
||||
|
||||
- $k = 648$ : $2^(648) approx 10^(195)$ — hors de portée
|
||||
- Solution : exploiter la *structure creuse* de $bold(H)$ $-->$ BP itératif
|
||||
]
|
||||
]
|
||||
|
||||
#myslide("Codes LDPC Irréguliers")[
|
||||
#set text(size: 17pt)
|
||||
|
||||
#definition(titre: "Distribution de Degrés (polynômes sur les arêtes)")[
|
||||
$
|
||||
lambda(x) = sum_i lambda_i x^(i-1), quad rho(x) = sum_j rho_j x^(j-1)
|
||||
$
|
||||
$lambda_i$ = fraction d'*arêtes* reliées à des VN de degré $i$, idem pour $rho_j$ et les CN.
|
||||
]
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
#grid(
|
||||
columns: (1fr, 1fr),
|
||||
gutter: 0.8em,
|
||||
definition(titre: "Avantages", accent: green.darken(10%))[
|
||||
- Approchent la limite de Shannon de plus près
|
||||
- VN de degré 2 : accélèrent la convergence initiale du BP
|
||||
],
|
||||
definition(titre: "Optimisation", accent: orange)[
|
||||
*Density Evolution* : calcule analytiquement le seuil de décodage en fonction de $(lambda, rho)$ $->$ optimiser numériquement
|
||||
],
|
||||
)
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
- Contrainte : $n sum_i lambda_i \/ i = |E| = m sum_j rho_j \/ j$
|
||||
- DVB-S2 ($n = 64800$) : degrés variables entre 2 et 13 selon le rendement cible
|
||||
]
|
||||
|
||||
#myslide("Encodage Efficace : Richardson-Urbanke")[
|
||||
#set text(size: 17pt)
|
||||
|
||||
*Problème :* Forme systématique $->$ $bold(G)$ dense $->$ $cal(O)(n^2)$ opérations.
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
#definition(titre: "Forme ALT : Approximate Lower Triangular")[
|
||||
Par permutation des lignes/colonnes de $bold(H)$ :
|
||||
$
|
||||
bold(H) = mat(A, B, T; C, D, E)
|
||||
$
|
||||
où $T$ est *triangulaire inférieure* de taille $(m-g) times (m-g)$, $g$ = gap (très petit).
|
||||
]
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
#definition(titre: [Encodage en $cal(O)(n + g^2)$])[
|
||||
Résolution en 2 phases :
|
||||
1. Calculer $bold(p)_1 in FF_2^g$ par élimination sur système de taille $g$
|
||||
2. Calculer $bold(p)_2 in FF_2^(m-g)$ par back-substitution sur $T$ (triangulaire)
|
||||
]
|
||||
|
||||
#v(0.3em)
|
||||
|
||||
- En pratique (QC-LDPC / 5G / DVB-S2) : encodage par *registres à décalage* $->$ $cal(O)(n)$
|
||||
]
|
||||
|
||||
#myslide("Canal AWGN")[
|
||||
#v(-0.8em)
|
||||
#set text(size: 18pt)
|
||||
|
||||
#definition(titre: "Modèle du Canal — BPSK sur AWGN")[
|
||||
$
|
||||
y_i = x_i + n_i, quad x_i in {+1, -1}, quad n_i ~ cal(N)(0, sigma^2)
|
||||
$
|
||||
Mapping BPSK : bit $0 arrow.bar +1$, bit $1 arrow.bar -1$
|
||||
]
|
||||
|
||||
#v(-0.5em)
|
||||
|
||||
#definition(titre: "Rapport Signal sur Bruit")[
|
||||
$
|
||||
frac(E_b, N_0) = frac(1, 2 R sigma^2)
|
||||
$
|
||||
$R = k\/n$ : rendement du code. Exprimé en dB : $10 log_10(E_b\/N_0)$.
|
||||
]
|
||||
|
||||
#v(-0.5em)
|
||||
|
||||
#definition(titre: "LLR Initial sur Canal AWGN")[
|
||||
$
|
||||
L_"canal"(y_i) = ln frac(P(v_i = 0 | y_i), P(v_i = 1 | y_i)) = frac(2 y_i, sigma^2)
|
||||
$
|
||||
Le LLR est *proportionnel* à la valeur reçue $y_i$ : signe = décision, valeur absolue = confiance.
|
||||
]
|
||||
]
|
||||
|
||||
#myslide("Construction de Gallager (1962)")[
|
||||
#set text(size: 17pt)
|
||||
|
||||
#definition(titre: "Principe")[
|
||||
Empiler $w_c$ sous-matrices de taille $(m \/ w_c) times n$ :
|
||||
$
|
||||
bold(H) = mat(H_1; H_2; dots.v; H_(w_c))
|
||||
$
|
||||
]
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
#set text(size: 21pt)
|
||||
*Algorithme :*
|
||||
- $H_1$ : blocs réguliers de $w_r$ uns consécutifs (colonnes disjointes)
|
||||
- $H_2, dots, H_(w_c)$ : copies de $H_1$ avec colonnes *permutées aléatoirement*
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
#set text(size: 17pt)
|
||||
#grid(
|
||||
columns: (1fr, 1fr),
|
||||
gutter: 0.8em,
|
||||
definition(titre: "Avantage", accent: green.darken(10%))[
|
||||
Simple à construire.\
|
||||
Garantit $w_c$ et $w_r$ exacts.
|
||||
],
|
||||
definition(titre: "Limite", accent: red)[
|
||||
Cycles de longueur 4 fréquents.\
|
||||
Aucun contrôle du girth.
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
#myslide("Construction de MacKay-Neal (1996)")[
|
||||
#v(1.6cm)
|
||||
#set text(size: 19pt)
|
||||
|
||||
#definition(titre: "Principe")[
|
||||
Construction *aléatoire* de $bold(H)$ avec évitement actif des 4-cycles.
|
||||
]
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
#set text(size: 21pt)
|
||||
*Algorithme* — pour chaque arête à placer $(v_j, c_i)$ :
|
||||
1. Vérifier l'absence de 4-cycle : $exists.not (j', i')$ tel que $H_(i,j') = H_(i',j) = H_(i',j') = 1$
|
||||
2. Si conflit : *rejeter* $c_i$ et tirer un autre nœud de contrôle
|
||||
3. Sinon : ajouter l'arête
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
#set text(size: 18pt)
|
||||
#definition(titre: "Résultat")[
|
||||
- Garanti *sans 4-cycles* par construction $->$ girth $>= 6$
|
||||
// - Non garanti globalement (dépend de la densité)
|
||||
// - Fondement théorique du BP (article MacKay & Neal, 1996)
|
||||
- Performances nettement supérieures à Gallager
|
||||
]
|
||||
]
|
||||
|
||||
#myslide("Progressive Edge-Growth (Hu et al., 2005)")[
|
||||
#set text(size: 18pt)
|
||||
|
||||
#definition(titre: "Idée")[
|
||||
Construire les arêtes *une par une* en maximisant le *girth local* à chaque étape.
|
||||
]
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
#set text(size: 21pt)
|
||||
*Algorithme* — pour relier $v_j$ à un nouveau CN :
|
||||
1. BFS depuis $v_j$ dans le graphe courant
|
||||
2. S'arrêter quand tous les CN ne sont plus accessibles à un nouveau niveau
|
||||
3. Relier $v_j$ au CN de plus faible degré *non encore atteint* par le BFS
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
#set text(size: 18pt)
|
||||
#grid(
|
||||
columns: (1fr, 1fr),
|
||||
gutter: 0.8em,
|
||||
definition(titre: "Avantage", accent: green.darken(10%))[
|
||||
Maximise le girth global.\
|
||||
Surpasse Gallager et MacKay.\
|
||||
Standard de référence.
|
||||
],
|
||||
definition(titre: "Complexité", accent: orange)[
|
||||
$cal(O)(n dot w_c dot m)$\
|
||||
Coût en construction seulement, pas en décodage.
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
#myslide("Limite de Shannon : Canal AWGN")[
|
||||
#set text(size: 18pt)
|
||||
|
||||
#definition(titre: "Capacité du Canal AWGN (Shannon, 1948)")[
|
||||
$
|
||||
C = 1/2 log_2(1 + "SNR") quad ["bit / utilisation"]
|
||||
$
|
||||
Il *existe* un code de rendement $R < C$ avec BER $-> 0$ quand $n -> infinity$.
|
||||
]
|
||||
|
||||
#v(0.6em)
|
||||
|
||||
- Pour $R = 1/2$ : limite à $E_b\/N_0 approx 0.19$ dB
|
||||
- *Bit-Flipping* : ~5–6 dB de la limite
|
||||
- *Sum-Product* : ~1–1.5 dB de la limite
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
#definition(titre: "Pourquoi de grands blocs ?", accent: orange)[
|
||||
Loi des grands nombres : pour $n$ grand, le bruit moyen par bit converge vers $sigma^2$.
|
||||
Plus $n$ est grand, plus on s'approche de la limite — au prix de la latence.
|
||||
]
|
||||
|
||||
#v(0.3em)
|
||||
|
||||
#align(center)[
|
||||
#block(fill: gray.lighten(90%), stroke: 0.5pt + gray, inset: 10pt, radius: 4pt)[
|
||||
DVB-S2 : $n = 64800$ → à 0.5 dB de Shannon pour $R = 1/2$.
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
#myslide("Girth : Impact sur la Convergence du BP")[
|
||||
#v(0.7cm)
|
||||
#set text(size: 17pt)
|
||||
|
||||
#definition(titre: [BP Exact $->$ Graphe = Arbre])[
|
||||
Sans cycle, le BP est *exact* et converge en au plus diamètre(graphe) itérations.
|
||||
]
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
#definition(titre: "Problème des Cycles Courts", accent: red)[
|
||||
Un cycle de longueur $2l$ : après $l$ itérations, un message revient à son point de départ.
|
||||
$->$ *Violation de l'indépendance* des messages $->$ BP sous-optimal.
|
||||
]
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
- *Girth = 4* : deux VN partagent deux CN $->$ corrélation immédiate, convergence vers solution incorrecte
|
||||
- *Girth = 6* : premier retour après 3 itérations $->$ stable pour quelques itérations
|
||||
// - PEG garantit girth $>= 6$ voire $8$ pour $n = 1296$, $w_c = 3$
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
#align(center)[
|
||||
#block(fill: gray.lighten(90%), stroke: 0.5pt + gray, inset: 10pt, radius: 4pt)[
|
||||
girth $>= 2 I_"max"$ pour que les cycles n'affectent pas les $I_"max"$ premières itérations.
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
#myslide("QC-LDPC : Codes Quasi-Cycliques")[
|
||||
#v(1cm)
|
||||
#set text(size: 18pt)
|
||||
|
||||
#definition(titre: [Structure de $bold(H)$ — Blocs Circulants])[
|
||||
$
|
||||
bold(H) = mat(
|
||||
bold(Pi)^(p_(0,0)), dots, bold(Pi)^(p_(0,n_b-1));
|
||||
dots.v, dots.down, dots.v;
|
||||
bold(Pi)^(p_(m_b-1,0)), dots, bold(Pi)^(p_(m_b-1,n_b-1))
|
||||
)
|
||||
$
|
||||
$bold(Pi)^p$ = identité décalée de $p$ lignes ($p = -1$ $->$ matrice nulle)
|
||||
]
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
#definition(titre: "Avantages")[
|
||||
- *Stockage* : seulement la matrice des exposants $p_(i,j)$ (taille $m_b times n_b$)
|
||||
- *Encodage* : registres à décalage $->$ $cal(O)(n)$ simple en FPGA
|
||||
- *Décodage* : $Z$ noeuds traités en parallèle par bloc circulant
|
||||
]
|
||||
|
||||
#v(0.3em)
|
||||
|
||||
- DVB-S2 : $Z = 360$, $m_b times n_b = 45 times 90$ $->$ $n = 64800$
|
||||
]
|
||||
|
||||
#myslide("Convergence du BP : Comportement et Critères")[
|
||||
#set text(size: 17pt)
|
||||
|
||||
#definition(titre: "Hypothèse du BP")[
|
||||
Messages entrants en un nœud supposés *indépendants*.
|
||||
$->$ Exact sur arbre, *approché* sur graphe à grand girth.
|
||||
]
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
#definition(titre: "Critères d'Arrêt")[
|
||||
- *Syndrome nul* : $bold(H) hat(bold(c))^top = bold(0)$ $->$ succès, on s'arrête
|
||||
- *$I_"max"$ itérations* atteint $->$ échec $->$ paquet perdu (compte dans le FER)
|
||||
]
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
- En pratique : $I_"max" = 50$–$200$ selon le code et le SNR
|
||||
- Oscillations possibles autour d'un *pseudo-mot de code* (trapping set)
|
||||
- Plus le SNR est élevé, moins d'itérations nécessaires
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
#align(center)[
|
||||
#block(fill: gray.lighten(90%), stroke: 0.5pt + gray, inset: 10pt, radius: 4pt)[
|
||||
Analyse théorique : *Density Evolution* — calcule le seuil exact de convergence en fonction de $E_b\/N_0$.
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
#myslide("CN Update : Formalisme probabiliste")[
|
||||
@ -1708,19 +2064,106 @@
|
||||
]
|
||||
]
|
||||
|
||||
#myslide("AWGN")[
|
||||
#myslide("Bit-Flipping : Choix du Seuil")[
|
||||
#set text(size: 18pt)
|
||||
|
||||
- Chaque $v_j$ reçoit de ses $w_c$ voisins $c_i$ un verdict $f_i in {0, 1}$.
|
||||
- Il retourne son bit si *trop d'équations échouent*.
|
||||
|
||||
#v(0.3em)
|
||||
|
||||
#definition(titre: "Stratégies de Seuil")[
|
||||
#table(
|
||||
columns: (1.2fr, 1.4fr, 1.4fr),
|
||||
inset: 8pt,
|
||||
stroke: 0.5pt + gray,
|
||||
align: center + horizon,
|
||||
[*Seuil*], [*Règle*], [*Remarque*],
|
||||
[Majorité stricte], [$k_j > w_c \/ 2$], [Standard Gallager-A],
|
||||
[Seuil fixe $b$], [$k_j >= b$ (ex. $b = w_c$)], [Gallager-B : plus conservateur],
|
||||
[Tous insatisfaits], [$k_j = w_c$], [Très conservateur, peu de faux retournements],
|
||||
)
|
||||
]
|
||||
|
||||
#myslide("Gallager")[
|
||||
#v(0.5em)
|
||||
|
||||
- *Gallager-A* : retourner si *toutes* les contraintes sont violées ($k_j = w_c$) — évite les oscillations
|
||||
- *Gallager-B* : retourner si *au moins* $b < w_c$ contraintes violées — plus agressif
|
||||
]
|
||||
|
||||
#myslide("Mackay-Neal")[
|
||||
#myslide("Que Se Passe-t-il en Cas d'Échec ?")[
|
||||
#set text(size: 16pt)
|
||||
|
||||
#definition(titre: "Échec du Décodeur")[
|
||||
Après $I_"max"$ itérations : syndrome $bold(s) != bold(0)$ $->$ *décodage échoué*. \
|
||||
Deux scénarios :
|
||||
- La trame est *perdue* (FPV, diffusion) $->$ acceptable
|
||||
- La trame doit *arriver* (fichier, protocole) $->$ retransmission nécessaire
|
||||
]
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
#myslide("Progressive Edge-growth")[
|
||||
|
||||
#definition(titre: "ARQ — Automatic Repeat reQuest")[
|
||||
Le récepteur envoie un *NACK* (Not Acknowledged) : l'émetteur retransmet.
|
||||
- *Stop-and-Wait* : simple mais inefficace
|
||||
- *Hybrid ARQ (HARQ)* : combine ARQ + codage — standard LTE/5G
|
||||
]
|
||||
|
||||
#v(0.4em)
|
||||
|
||||
#definition(titre: "HARQ Type II — Chase Combining", accent: blue)[
|
||||
Le récepteur *combine* les LLR des deux transmissions avant de redécoder :
|
||||
$
|
||||
L_"total"(y_i) = L_"canal"^1(y_i) + L_"canal"^2(y_i)
|
||||
$
|
||||
// $->$ gain de $3$ dB sans retransmission supplémentaire effective.
|
||||
]
|
||||
]
|
||||
|
||||
#myslide("Limites du Modèle AWGN")[
|
||||
#v(-0.7cm)
|
||||
#set text(size: 17pt)
|
||||
|
||||
#definition(titre: "Ce que AWGN suppose")[
|
||||
- Bruit *blanc* : indépendant d'un symbole à l'autre
|
||||
- Distribution *gaussienne* stationnaire
|
||||
- Canal *sans mémoire* : chaque bit perturbé indépendamment
|
||||
]
|
||||
|
||||
#v(-0.2cm)
|
||||
|
||||
#definition(titre: "Canaux Réels — Ce qu'AWGN ne capture pas", accent: red)[
|
||||
#table(
|
||||
columns: (1.2fr, 1.8fr),
|
||||
inset: 8pt,
|
||||
stroke: 0.5pt + gray,
|
||||
[*Phénomène*], [*Impact*],
|
||||
[Évanouissements (fading)], [SNR varie dans le temps],
|
||||
[Burst d'erreurs], [Bits consécutifs corrompus $->$ entrelacement nécessaire],
|
||||
[Bruit impulsionnel], [Pics de bruit ponctuels (moteurs, orages)],
|
||||
[Erreurs de phase], [Synchronisation imparfaite en BPSK],
|
||||
)
|
||||
]
|
||||
|
||||
#v(-0.2cm)
|
||||
|
||||
#definition(titre: "Solution : Entrelacement", accent: blue)[
|
||||
Permuter les bits *avant* l'envoi : les erreurs en rafale deviennent des erreurs isolées pour le décodeur LDPC.
|
||||
]
|
||||
]
|
||||
|
||||
#set raw(theme: "ldpc-theme.tmTheme")
|
||||
|
||||
#generer_slides_code(
|
||||
(
|
||||
"src/rs/code.rs",
|
||||
"src/rs/encoder.rs",
|
||||
"src/rs/decoder.rs",
|
||||
"src/rs/generator.rs",
|
||||
"src/rs/channel.rs",
|
||||
"src/rs/matrix.rs",
|
||||
"src/rs/lib.rs",
|
||||
),
|
||||
myslide,
|
||||
graphe_tanner_fond,
|
||||
)
|
||||
|
||||
74
src/rs/channel.rs
Normal file
74
src/rs/channel.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use crate::{Gf2, LdpcError, Llr, Result};
|
||||
|
||||
// Trait Channel
|
||||
pub trait Channel: Send + Sync {
|
||||
fn transmit(&self, codeword: &[Gf2], rng: &mut impl rand::Rng) -> Vec<Llr>;
|
||||
fn capacity(&self) -> f64;
|
||||
}
|
||||
|
||||
// Canal AWGN
|
||||
// BPSK mod 0 -> +1.0, 1 -> -1.0
|
||||
// Signal recu y = x + n, n ~ N(0, sig^2)
|
||||
// LLR optimal L(y) = 2y/sig^2
|
||||
// sig^2 = N_0/2 = 1/(2 * R * SNR_lin)
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AwgnChannel {
|
||||
pub snr_db: f64,
|
||||
sigma: f64,
|
||||
}
|
||||
|
||||
impl AwgnChannel {
|
||||
pub fn new(snr_db: f64, code_rate: f64) -> Result<Self> {
|
||||
if !(0.0..1.0).contains(&code_rate) {
|
||||
return Err(LdpcError::OutOfRange("code_rate ∈ ]0, 1[".into()));
|
||||
}
|
||||
let snr_lin = 10.0_f64.powf(snr_db / 10.0);
|
||||
let sigma = (1.0 / (2.0 * code_rate * snr_lin)).sqrt();
|
||||
Ok(Self { snr_db, sigma })
|
||||
}
|
||||
|
||||
pub fn sigma(&self) -> f64 {
|
||||
self.sigma
|
||||
}
|
||||
pub fn snr_linear(&self) -> f64 {
|
||||
10.0_f64.powf(self.snr_db / 10.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn llr_from_received(y: f64, sigma: f64) -> Llr {
|
||||
2.0 * y / (sigma * sigma)
|
||||
}
|
||||
}
|
||||
|
||||
impl Channel for AwgnChannel {
|
||||
fn transmit(&self, codeword: &[Gf2], rng: &mut impl rand::Rng) -> Vec<Llr> {
|
||||
use rand_distr::{Distribution, Normal};
|
||||
let normal = Normal::new(0.0, self.sigma).unwrap();
|
||||
codeword
|
||||
.iter()
|
||||
.map(|&b| {
|
||||
let x = if b == 0 { 1.0_f64 } else { -1.0_f64 };
|
||||
let y = x + normal.sample(rng);
|
||||
Self::llr_from_received(y, self.sigma)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn capacity(&self) -> f64 {
|
||||
// Capacité BPSK-AWGN Monte-Carlo
|
||||
use rand_distr::{Distribution, Normal};
|
||||
let mut rng = rand::thread_rng();
|
||||
let normal = Normal::new(0.0, self.sigma).unwrap();
|
||||
let n_samples = 10_000usize;
|
||||
let mut sum = 0.0f64;
|
||||
for _ in 0..n_samples {
|
||||
let n: f64 = normal.sample(&mut rng);
|
||||
let y = 1.0 + n; // bit 0 transmis (x=+1)
|
||||
let llr = Self::llr_from_received(y, self.sigma);
|
||||
// I = 1 - E[log2(1 + exp(-2y/sig^2))]
|
||||
sum += (1.0 + (-llr).exp()).log2();
|
||||
}
|
||||
1.0 - sum / n_samples as f64
|
||||
}
|
||||
}
|
||||
329
src/rs/code.rs
Normal file
329
src/rs/code.rs
Normal file
@ -0,0 +1,329 @@
|
||||
use crate::graph::TannerGraph;
|
||||
use crate::matrix::{DenseMatrixGF2, SparseMatrixGF2};
|
||||
use crate::{Gf2, LdpcError, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LdpcParams {
|
||||
pub n: usize,
|
||||
pub k: usize,
|
||||
pub topology: CodeTopology,
|
||||
pub generation: GenerationMethod,
|
||||
pub seed: Option<u64>,
|
||||
}
|
||||
|
||||
impl LdpcParams {
|
||||
pub fn rate(&self) -> f64 {
|
||||
self.k as f64 / self.n as f64
|
||||
}
|
||||
pub fn m(&self) -> usize {
|
||||
self.n - self.k
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
if self.k >= self.n {
|
||||
return Err(LdpcError::InvalidParameters("k doit être < n".into()));
|
||||
}
|
||||
if self.n < 4 {
|
||||
return Err(LdpcError::InvalidParameters("n trop petit".into()));
|
||||
}
|
||||
self.topology.validate(self)
|
||||
}
|
||||
}
|
||||
|
||||
// Topologie
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum CodeTopology {
|
||||
// Chaque varnode a degré wc, chaque checknode a degré wr
|
||||
// Condition nécessaire n * wc == m * wr
|
||||
Regular { wc: usize, wr: usize },
|
||||
// Potentielle implémentation Irregular (plus tard !)
|
||||
}
|
||||
|
||||
impl CodeTopology {
|
||||
fn validate(&self, params: &LdpcParams) -> Result<()> {
|
||||
match self {
|
||||
CodeTopology::Regular { wc, wr } => {
|
||||
if params.n * wc != params.m() * wr {
|
||||
return Err(LdpcError::InvalidParameters(format!(
|
||||
"n*wc ({}) != m*wr ({}) pour LDPC régulier",
|
||||
params.n * wc,
|
||||
params.m() * wr
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode de génération
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum GenerationMethod {
|
||||
// H = [H1 | H2 | ... | Hwc]^T, H1 régulière, Hi = permutation de H1
|
||||
Gallager,
|
||||
// Ajout de colonnes de poids fixe, rejet si cycle4 créé
|
||||
MacKayNeal { max_attempts: usize },
|
||||
}
|
||||
|
||||
// Forme systématique
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SystematicForm {
|
||||
// G = [I_k | P], dense
|
||||
pub g: DenseMatrixGF2,
|
||||
// Permutation de colonnes appliquée à H => [A | I_m]
|
||||
pub col_perm: Vec<usize>,
|
||||
// Permutation inverse => reformer le mot de code
|
||||
pub col_perm_inv: Vec<usize>,
|
||||
}
|
||||
|
||||
// Structure principale
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LdpcCode {
|
||||
pub params: LdpcParams,
|
||||
pub h: SparseMatrixGF2,
|
||||
#[serde(skip, default = "default_graph")]
|
||||
pub graph: TannerGraph,
|
||||
pub systematic_form: Option<SystematicForm>,
|
||||
}
|
||||
|
||||
impl LdpcCode {
|
||||
pub fn new(params: LdpcParams) -> Result<Self> {
|
||||
params.validate()?;
|
||||
let mut rng = build_rng(params.seed);
|
||||
let h = match ¶ms.generation {
|
||||
GenerationMethod::Gallager => generate_gallager(¶ms, &mut rng)?,
|
||||
GenerationMethod::MacKayNeal { max_attempts } => {
|
||||
generate_mackay_neal(¶ms, *max_attempts, &mut rng)?
|
||||
}
|
||||
};
|
||||
let graph = TannerGraph::from_matrix(&h);
|
||||
Ok(Self {
|
||||
params,
|
||||
h,
|
||||
graph,
|
||||
systematic_form: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_matrix(h: SparseMatrixGF2, k: usize) -> Result<Self> {
|
||||
let n = h.cols;
|
||||
let params = LdpcParams {
|
||||
n,
|
||||
k,
|
||||
topology: CodeTopology::Regular { wc: 0, wr: 0 }, // inconnu
|
||||
generation: GenerationMethod::Gallager,
|
||||
seed: None,
|
||||
};
|
||||
let graph = TannerGraph::from_matrix(&h);
|
||||
Ok(Self {
|
||||
params,
|
||||
h,
|
||||
graph,
|
||||
systematic_form: None,
|
||||
})
|
||||
}
|
||||
|
||||
// Calcule G par Gauss-Jordan sur H (to cache)
|
||||
pub fn compute_systematic_form(&mut self) -> Result<()> {
|
||||
if self.systematic_form.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut dense = DenseMatrixGF2::zeros(self.m(), self.n());
|
||||
let h_dense = self.h.to_dense();
|
||||
for r in 0..self.m() {
|
||||
for c in 0..self.n() {
|
||||
dense.set(r, c, h_dense[r][c]);
|
||||
}
|
||||
}
|
||||
|
||||
let (col_perm, rank) = dense.systematize(self.k());
|
||||
|
||||
// Possibilité d'avoir rg < m donc juste rejeter et recommencer
|
||||
if rank < self.m() {
|
||||
return Err(LdpcError::SingularMatrix);
|
||||
}
|
||||
|
||||
// Création de G^T (taille n,k) pour encodage c = G^T * m
|
||||
let mut g_t = DenseMatrixGF2::zeros(self.n(), self.k());
|
||||
|
||||
// I_k (haut)
|
||||
for i in 0..self.k() {
|
||||
g_t.set(i, i, 1);
|
||||
}
|
||||
|
||||
// (bas) Matrice A (extraite des k premières colonnes de la matrice dense pivotée)
|
||||
for r in 0..self.m() {
|
||||
for c in 0..self.k() {
|
||||
g_t.set(self.k() + r, c, dense.get(r, c));
|
||||
}
|
||||
}
|
||||
|
||||
// Création de la permutation inverse pour réordonner le mot de code
|
||||
let mut col_perm_inv = vec![0usize; self.n()];
|
||||
for (i, &p) in col_perm.iter().enumerate() {
|
||||
col_perm_inv[p] = i;
|
||||
}
|
||||
|
||||
self.systematic_form = Some(SystematicForm {
|
||||
g: g_t,
|
||||
col_perm,
|
||||
col_perm_inv,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rate(&self) -> f64 {
|
||||
self.params.rate()
|
||||
}
|
||||
pub fn n(&self) -> usize {
|
||||
self.params.n
|
||||
}
|
||||
pub fn k(&self) -> usize {
|
||||
self.params.k
|
||||
}
|
||||
pub fn m(&self) -> usize {
|
||||
self.params.m()
|
||||
}
|
||||
pub fn girth(&self) -> usize {
|
||||
self.graph.girth()
|
||||
}
|
||||
|
||||
pub fn is_codeword(&self, c: &[Gf2]) -> bool {
|
||||
self.h.multiply_vec(c).iter().all(|&s| s == 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn build_rng(seed: Option<u64>) -> impl rand::Rng {
|
||||
use rand::SeedableRng;
|
||||
rand::rngs::StdRng::seed_from_u64(seed.unwrap_or_else(rand::random))
|
||||
}
|
||||
|
||||
// Gallager
|
||||
// H divisée en wc sous-matrices de taille (m / wc) * n
|
||||
// H1 = matrice régulière (ligne contient wr 1)
|
||||
// H2..Hwc = permutations aléatoires de colonnes de H1
|
||||
|
||||
fn generate_gallager(params: &LdpcParams, rng: &mut impl rand::Rng) -> Result<SparseMatrixGF2> {
|
||||
let CodeTopology::Regular { wc, wr } = params.topology else {
|
||||
return Err(LdpcError::InvalidParameters(
|
||||
"Gallager nécessite un code régulier".into(),
|
||||
));
|
||||
};
|
||||
let n = params.n;
|
||||
let m = params.m();
|
||||
if m % wc != 0 {
|
||||
return Err(LdpcError::InvalidParameters(
|
||||
"m doit être divisible par wc".into(),
|
||||
));
|
||||
}
|
||||
let rows_per_block = m / wc;
|
||||
let mut ones: Vec<(usize, usize)> = Vec::new();
|
||||
|
||||
for r in 0..rows_per_block {
|
||||
for j in 0..wr {
|
||||
ones.push((r, r * wr + j));
|
||||
}
|
||||
}
|
||||
use rand::seq::SliceRandom;
|
||||
for block in 1..wc {
|
||||
let mut perm: Vec<usize> = (0..n).collect();
|
||||
perm.shuffle(rng);
|
||||
for r in 0..rows_per_block {
|
||||
for j in 0..wr {
|
||||
ones.push((block * rows_per_block + r, perm[r * wr + j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for b in 1..wc {
|
||||
let r_source = b * rows_per_block;
|
||||
|
||||
let row_0_cols: Vec<usize> = ones
|
||||
.iter()
|
||||
.filter(|&&(r, _)| r == 0)
|
||||
.map(|&(_, c)| c)
|
||||
.collect();
|
||||
|
||||
if let Some(idx) = ones
|
||||
.iter()
|
||||
.position(|&(r, c)| r == r_source && !row_0_cols.contains(&c))
|
||||
{
|
||||
ones[idx].0 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SparseMatrixGF2::from_positions(m, n, ones))
|
||||
}
|
||||
|
||||
// MacKay-Neal
|
||||
// Ajoute les colonnes une à une avec poids wc
|
||||
// Rejette colonne créant cycle4 (2 colonnes n'ont qu'un 1 en commun)
|
||||
fn generate_mackay_neal(
|
||||
params: &LdpcParams,
|
||||
max_attempts: usize,
|
||||
rng: &mut impl rand::Rng,
|
||||
) -> Result<SparseMatrixGF2> {
|
||||
let CodeTopology::Regular { wc, wr } = params.topology else {
|
||||
return Err(LdpcError::InvalidParameters(
|
||||
"MacKayNeal nécessite régulier".into(),
|
||||
));
|
||||
};
|
||||
let n = params.n;
|
||||
let m = params.m();
|
||||
let mut ones: Vec<(usize, usize)> = Vec::new();
|
||||
|
||||
// Suivi du poids de chaque ligne
|
||||
let mut row_weights = vec![0usize; m];
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
for col in 0..n {
|
||||
let mut placed = false;
|
||||
for _attempt in 0..max_attempts {
|
||||
let mut available_rows: Vec<usize> = (0..m).filter(|&r| row_weights[r] < wr).collect();
|
||||
|
||||
if available_rows.len() < wc {
|
||||
break; // Plus de lignes dispo
|
||||
}
|
||||
|
||||
available_rows.shuffle(rng);
|
||||
let candidate = available_rows[..wc].to_vec();
|
||||
|
||||
// Vérifier cycle4
|
||||
let mut ok = true;
|
||||
let mut c2 = 0;
|
||||
while c2 < col && ok {
|
||||
let existing: Vec<usize> = ones
|
||||
.iter()
|
||||
.filter(|&&(_, c)| c == c2)
|
||||
.map(|&(r, _)| r)
|
||||
.collect();
|
||||
let shared = candidate.iter().filter(|r| existing.contains(r)).count();
|
||||
if shared >= 2 {
|
||||
ok = false;
|
||||
}
|
||||
c2 += 1;
|
||||
}
|
||||
|
||||
if ok {
|
||||
for &r in &candidate {
|
||||
ones.push((r, col));
|
||||
row_weights[r] += 1;
|
||||
}
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !placed {
|
||||
return Err(LdpcError::GenerationFailed {
|
||||
attempts: max_attempts,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(SparseMatrixGF2::from_positions(m, n, ones))
|
||||
}
|
||||
|
||||
fn default_graph() -> TannerGraph {
|
||||
TannerGraph::from_matrix(&SparseMatrixGF2::zeros(1, 1))
|
||||
}
|
||||
410
src/rs/decoder.rs
Normal file
410
src/rs/decoder.rs
Normal file
@ -0,0 +1,410 @@
|
||||
use crate::code::LdpcCode;
|
||||
use crate::graph::TannerGraph;
|
||||
use crate::matrix::SparseMatrixGF2;
|
||||
use crate::{BitVec, Gf2, Llr};
|
||||
|
||||
// Résultat
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DecoderResult {
|
||||
Converged(BitVec),
|
||||
MaxIterationsReached(BitVec),
|
||||
Failure,
|
||||
}
|
||||
|
||||
impl DecoderResult {
|
||||
pub fn codeword(&self) -> Option<&BitVec> {
|
||||
match self {
|
||||
DecoderResult::Converged(c) | DecoderResult::MaxIterationsReached(c) => Some(c),
|
||||
DecoderResult::Failure => None,
|
||||
}
|
||||
}
|
||||
pub fn is_success(&self) -> bool {
|
||||
matches!(self, DecoderResult::Converged(_))
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DecoderConfig {
|
||||
pub max_iterations: usize,
|
||||
pub early_stopping: bool,
|
||||
}
|
||||
|
||||
impl Default for DecoderConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_iterations: 50,
|
||||
early_stopping: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trait Decoder
|
||||
pub trait Decoder: Send + Sync {
|
||||
fn decode(&self, channel_llr: &[Llr]) -> DecoderResult;
|
||||
|
||||
fn decode_hard(&self, received: &[Gf2]) -> DecoderResult {
|
||||
let llr: Vec<Llr> = received
|
||||
.iter()
|
||||
.map(|&b| if b == 0 { 1.0 } else { -1.0 })
|
||||
.collect();
|
||||
self.decode(&llr)
|
||||
}
|
||||
}
|
||||
|
||||
// Primitives GF(2) et LLR
|
||||
#[inline]
|
||||
pub fn hard_decision(llr: Llr) -> Gf2 {
|
||||
if llr >= 0.0 {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_syndrome(h: &SparseMatrixGF2, c: &[Gf2]) -> Vec<Gf2> {
|
||||
h.multiply_vec(c)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn phi(x: Llr) -> Llr {
|
||||
let ax = x.abs().max(1e-10);
|
||||
-((ax / 2.0).tanh().ln())
|
||||
}
|
||||
|
||||
// Mises à jour des noeuds
|
||||
|
||||
// Mise à jour Sum-Product du noeud de contrôle
|
||||
fn check_node_update_sp(incoming: &[Llr], out: &mut [Llr]) {
|
||||
let phi_sum: Llr = incoming.iter().map(|&l| phi(l.abs())).sum();
|
||||
let sign_prod: Llr = incoming.iter().map(|&l| l.signum()).product();
|
||||
for (_j, (&l, r)) in incoming.iter().zip(out.iter_mut()).enumerate() {
|
||||
let phi_excl = phi_sum - phi(l.abs());
|
||||
let sign_excl = sign_prod * l.signum();
|
||||
*r = sign_excl * phi(phi_excl);
|
||||
}
|
||||
}
|
||||
|
||||
// Mise à jour Min-Sum avec facteur de normalisation a
|
||||
// alpha in [0.75, 0.875] compense le biais de Min-Sum brut
|
||||
fn check_node_update_ms(incoming: &[Llr], out: &mut [Llr], alpha: Llr) {
|
||||
let sign_prod: Llr = incoming.iter().map(|&l| l.signum()).product();
|
||||
let mut min1 = Llr::INFINITY;
|
||||
let mut min2 = Llr::INFINITY;
|
||||
let mut min1_idx = 0;
|
||||
for (j, &l) in incoming.iter().enumerate() {
|
||||
let al = l.abs();
|
||||
if al < min1 {
|
||||
min2 = min1;
|
||||
min1 = al;
|
||||
min1_idx = j;
|
||||
} else if al < min2 {
|
||||
min2 = al;
|
||||
}
|
||||
}
|
||||
for (j, (&l, r)) in incoming.iter().zip(out.iter_mut()).enumerate() {
|
||||
let min_excl = if j == min1_idx { min2 } else { min1 };
|
||||
let sign_excl = sign_prod * l.signum();
|
||||
*r = alpha * sign_excl * min_excl;
|
||||
}
|
||||
}
|
||||
|
||||
// Mise à jour du noeud de variable
|
||||
fn variable_node_update(ch_llr: Llr, incoming_c2v: &[Llr], out: &mut [Llr]) {
|
||||
let total: Llr = ch_llr + incoming_c2v.iter().sum::<Llr>();
|
||||
for (&r, o) in incoming_c2v.iter().zip(out.iter_mut()) {
|
||||
*o = total - r;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn posterior_llr(ch_llr: Llr, c2v_msgs: &[Llr]) -> Llr {
|
||||
ch_llr + c2v_msgs.iter().sum::<Llr>()
|
||||
}
|
||||
|
||||
// Messages internes
|
||||
// Indexés par (check_id, position_dans_liste_voisins) (O(1))
|
||||
struct Messages {
|
||||
v2c: Vec<Vec<Llr>>,
|
||||
c2v: Vec<Vec<Llr>>,
|
||||
}
|
||||
|
||||
impl Messages {
|
||||
fn new(graph: &TannerGraph) -> Self {
|
||||
let v2c = (0..graph.n_chk)
|
||||
.map(|c| vec![0.0; graph.chk_degree(c)])
|
||||
.collect();
|
||||
let c2v = (0..graph.n_chk)
|
||||
.map(|c| vec![0.0; graph.chk_degree(c)])
|
||||
.collect();
|
||||
Self { v2c, c2v }
|
||||
}
|
||||
}
|
||||
|
||||
// Table de correspondance, pour chaque (var, check), index dans la liste de voisins
|
||||
// Précalculé une fois à construction du décodeur
|
||||
struct EdgeIndex {
|
||||
var_pos_in_chk: Vec<Vec<usize>>,
|
||||
chk_pos_in_var: Vec<Vec<usize>>,
|
||||
}
|
||||
|
||||
impl EdgeIndex {
|
||||
fn build(graph: &TannerGraph) -> Self {
|
||||
let var_pos_in_chk = (0..graph.n_chk)
|
||||
.map(|c| {
|
||||
graph
|
||||
.chk_neighbors(c)
|
||||
.iter()
|
||||
.map(|&v| graph.var_neighbors(v).iter().position(|&x| x == c).unwrap())
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let chk_pos_in_var = (0..graph.n_var)
|
||||
.map(|v| {
|
||||
graph
|
||||
.var_neighbors(v)
|
||||
.iter()
|
||||
.map(|&c| graph.chk_neighbors(c).iter().position(|&x| x == v).unwrap())
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
var_pos_in_chk,
|
||||
chk_pos_in_var,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bit-Flipping
|
||||
pub struct BitFlippingDecoder {
|
||||
graph: TannerGraph,
|
||||
h: SparseMatrixGF2,
|
||||
config: DecoderConfig,
|
||||
}
|
||||
|
||||
impl BitFlippingDecoder {
|
||||
pub fn new(code: &LdpcCode, config: DecoderConfig) -> Self {
|
||||
Self {
|
||||
graph: code.graph.clone(),
|
||||
h: code.h.clone(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for BitFlippingDecoder {
|
||||
fn decode(&self, channel_llr: &[Llr]) -> DecoderResult {
|
||||
let mut bits: Vec<Gf2> = channel_llr.iter().map(|&l| hard_decision(l)).collect();
|
||||
|
||||
for _iter in 0..self.config.max_iterations {
|
||||
let syndrome = compute_syndrome(&self.h, &bits);
|
||||
if self.config.early_stopping && syndrome.iter().all(|&s| s == 0) {
|
||||
return DecoderResult::Converged(bits);
|
||||
}
|
||||
let mut unsatisfied = vec![0usize; self.graph.n_var];
|
||||
for c in 0..self.graph.n_chk {
|
||||
if syndrome[c] == 1 {
|
||||
for &v in self.graph.chk_neighbors(c) {
|
||||
unsatisfied[v] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut flipped = false;
|
||||
for v in 0..self.graph.n_var {
|
||||
if unsatisfied[v] > self.graph.var_degree(v) / 2 {
|
||||
bits[v] ^= 1;
|
||||
flipped = true;
|
||||
}
|
||||
}
|
||||
if !flipped {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let synd = compute_syndrome(&self.h, &bits);
|
||||
if synd.iter().all(|&s| s == 0) {
|
||||
DecoderResult::Converged(bits)
|
||||
} else {
|
||||
DecoderResult::MaxIterationsReached(bits)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BP
|
||||
fn bp_decode<F>(
|
||||
graph: &TannerGraph,
|
||||
h: &SparseMatrixGF2,
|
||||
config: &DecoderConfig,
|
||||
channel_llr: &[Llr],
|
||||
edge_idx: &EdgeIndex,
|
||||
check_update: F,
|
||||
) -> DecoderResult
|
||||
where
|
||||
F: Fn(&[Llr], &mut [Llr]),
|
||||
{
|
||||
let mut msgs = Messages::new(graph);
|
||||
|
||||
// Init
|
||||
for c in 0..graph.n_chk {
|
||||
for (j, &v) in graph.chk_neighbors(c).iter().enumerate() {
|
||||
msgs.v2c[c][j] = channel_llr[v];
|
||||
}
|
||||
}
|
||||
|
||||
for _iter in 0..config.max_iterations {
|
||||
// Maj des checknodes
|
||||
for c in 0..graph.n_chk {
|
||||
let v2c = msgs.v2c[c].clone();
|
||||
check_update(&v2c, &mut msgs.c2v[c]);
|
||||
}
|
||||
|
||||
// Maj des varnodes
|
||||
for v in 0..graph.n_var {
|
||||
let neighbors = graph.var_neighbors(v);
|
||||
// Rassembler les c2v entrants sur ce varnode
|
||||
let incoming: Vec<Llr> = neighbors
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &c)| {
|
||||
let j = edge_idx.chk_pos_in_var[v][i];
|
||||
msgs.c2v[c][j]
|
||||
})
|
||||
.collect();
|
||||
let mut new_v2c = vec![0.0; neighbors.len()];
|
||||
variable_node_update(channel_llr[v], &incoming, &mut new_v2c);
|
||||
for (i, &c) in neighbors.iter().enumerate() {
|
||||
let j = edge_idx.chk_pos_in_var[v][i];
|
||||
msgs.v2c[c][j] = new_v2c[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Hard décision + arrêt
|
||||
if config.early_stopping {
|
||||
let bits = make_decision(graph, &msgs, channel_llr, edge_idx);
|
||||
if compute_syndrome(h, &bits).iter().all(|&s| s == 0) {
|
||||
return DecoderResult::Converged(bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let bits = make_decision(graph, &msgs, channel_llr, edge_idx);
|
||||
let synd = compute_syndrome(h, &bits);
|
||||
if synd.iter().all(|&s| s == 0) {
|
||||
DecoderResult::Converged(bits)
|
||||
} else {
|
||||
DecoderResult::MaxIterationsReached(bits)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_decision(
|
||||
graph: &TannerGraph,
|
||||
msgs: &Messages,
|
||||
channel_llr: &[Llr],
|
||||
edge_idx: &EdgeIndex,
|
||||
) -> Vec<Gf2> {
|
||||
(0..graph.n_var)
|
||||
.map(|v| {
|
||||
let incoming: Vec<Llr> = graph
|
||||
.var_neighbors(v)
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &c)| {
|
||||
let j = edge_idx.chk_pos_in_var[v][i];
|
||||
msgs.c2v[c][j]
|
||||
})
|
||||
.collect();
|
||||
hard_decision(posterior_llr(channel_llr[v], &incoming))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Sum-Product
|
||||
pub struct SumProductDecoder {
|
||||
graph: TannerGraph,
|
||||
h: SparseMatrixGF2,
|
||||
config: DecoderConfig,
|
||||
edge_idx: EdgeIndex,
|
||||
}
|
||||
|
||||
impl SumProductDecoder {
|
||||
pub fn new(code: &LdpcCode, config: DecoderConfig) -> Self {
|
||||
let edge_idx = EdgeIndex::build(&code.graph);
|
||||
Self {
|
||||
graph: code.graph.clone(),
|
||||
h: code.h.clone(),
|
||||
config,
|
||||
edge_idx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for SumProductDecoder {
|
||||
fn decode(&self, channel_llr: &[Llr]) -> DecoderResult {
|
||||
bp_decode(
|
||||
&self.graph,
|
||||
&self.h,
|
||||
&self.config,
|
||||
channel_llr,
|
||||
&self.edge_idx,
|
||||
|incoming, out| check_node_update_sp(incoming, out),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Min-Sum
|
||||
pub struct MinSumDecoder {
|
||||
graph: TannerGraph,
|
||||
h: SparseMatrixGF2,
|
||||
config: DecoderConfig,
|
||||
scaling_factor: Llr,
|
||||
edge_idx: EdgeIndex,
|
||||
}
|
||||
|
||||
impl MinSumDecoder {
|
||||
pub fn new(code: &LdpcCode, config: DecoderConfig, scaling_factor: Llr) -> Self {
|
||||
let edge_idx = EdgeIndex::build(&code.graph);
|
||||
Self {
|
||||
graph: code.graph.clone(),
|
||||
h: code.h.clone(),
|
||||
config,
|
||||
scaling_factor,
|
||||
edge_idx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for MinSumDecoder {
|
||||
fn decode(&self, channel_llr: &[Llr]) -> DecoderResult {
|
||||
let alpha = self.scaling_factor;
|
||||
bp_decode(
|
||||
&self.graph,
|
||||
&self.h,
|
||||
&self.config,
|
||||
channel_llr,
|
||||
&self.edge_idx,
|
||||
move |incoming, out| check_node_update_ms(incoming, out, alpha),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Factory
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DecoderMethod {
|
||||
BitFlipping,
|
||||
SumProduct,
|
||||
MinSum { scaling_factor: Llr },
|
||||
}
|
||||
|
||||
pub fn build_decoder(
|
||||
code: &LdpcCode,
|
||||
method: DecoderMethod,
|
||||
config: DecoderConfig,
|
||||
) -> Box<dyn Decoder> {
|
||||
match method {
|
||||
DecoderMethod::BitFlipping => Box::new(BitFlippingDecoder::new(code, config)),
|
||||
DecoderMethod::SumProduct => Box::new(SumProductDecoder::new(code, config)),
|
||||
DecoderMethod::MinSum { scaling_factor } => {
|
||||
Box::new(MinSumDecoder::new(code, config, scaling_factor))
|
||||
}
|
||||
}
|
||||
}
|
||||
134
src/rs/encoder.rs
Normal file
134
src/rs/encoder.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use crate::code::LdpcCode;
|
||||
use crate::{BitVec, Gf2, LdpcError, Result};
|
||||
|
||||
pub trait Encoder: Send + Sync {
|
||||
fn encode(&self, message: &[Gf2]) -> Result<BitVec>;
|
||||
fn message_len(&self) -> usize;
|
||||
fn codeword_len(&self) -> usize;
|
||||
fn extract_message(&self, codeword: &[Gf2]) -> Vec<Gf2>;
|
||||
|
||||
fn check_input(&self, msg: &[Gf2]) -> Result<()> {
|
||||
if msg.len() != self.message_len() {
|
||||
return Err(LdpcError::DimensionMismatch {
|
||||
expected: self.message_len(),
|
||||
got: msg.len(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EncodingMethod {
|
||||
Systematic,
|
||||
}
|
||||
|
||||
pub struct SystematicEncoder {
|
||||
k: usize,
|
||||
n: usize,
|
||||
// g_t: DenseMatrixGF2,
|
||||
packed_g_cols: Vec<Vec<u64>>,
|
||||
perm_inv: Vec<usize>,
|
||||
col_perm: Vec<usize>,
|
||||
}
|
||||
|
||||
impl SystematicEncoder {
|
||||
pub fn new(code: &mut LdpcCode) -> Result<Self> {
|
||||
code.compute_systematic_form()?;
|
||||
let sf = code.systematic_form.as_ref().unwrap();
|
||||
|
||||
let k = code.k();
|
||||
let n = code.n();
|
||||
let g_t = &sf.g;
|
||||
|
||||
let num_blocks = (n + 63) / 64;
|
||||
|
||||
// Bitpacking
|
||||
let mut packed_g_cols = vec![vec![0u64; num_blocks]; k];
|
||||
|
||||
for j in 0..k {
|
||||
for i in 0..n {
|
||||
if g_t.get(i, j) == 1 {
|
||||
let block_idx = i / 64;
|
||||
let bit_idx = i % 64;
|
||||
packed_g_cols[j][block_idx] |= 1 << bit_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
k,
|
||||
n,
|
||||
packed_g_cols,
|
||||
perm_inv: sf.col_perm_inv.clone(),
|
||||
col_perm: sf.col_perm.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for SystematicEncoder {
|
||||
// fn encode(&self, message: &[Gf2]) -> Result<BitVec> {
|
||||
// self.check_input(message)?;
|
||||
//
|
||||
// let c_perm = self.g_t.multiply_vec(message);
|
||||
//
|
||||
// // Retablir l'ordre initial des bits selon la permutation de H
|
||||
// let mut c = vec![0u8; self.n];
|
||||
// // for (i, &ci) in c_perm.iter().enumerate() {
|
||||
// // c[self.perm_inv[i]] = ci;
|
||||
// // }
|
||||
//
|
||||
// for i in 0..self.n {
|
||||
// c[i] = c_perm[self.perm_inv[i]];
|
||||
// }
|
||||
//
|
||||
// Ok(c)
|
||||
// }
|
||||
fn encode(&self, message: &[Gf2]) -> Result<BitVec> {
|
||||
self.check_input(message)?;
|
||||
|
||||
let num_blocks = (self.n + 63) / 64;
|
||||
let mut accum = vec![0u64; num_blocks];
|
||||
|
||||
for (j, &bit) in message.iter().enumerate() {
|
||||
if bit == 1 {
|
||||
for b in 0..num_blocks {
|
||||
accum[b] ^= self.packed_g_cols[j][b];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut c = vec![0u8; self.n];
|
||||
for i in 0..self.n {
|
||||
let block_idx = i / 64;
|
||||
let bit_idx = i % 64;
|
||||
let val = ((accum[block_idx] >> bit_idx) & 1) as u8;
|
||||
|
||||
c[self.col_perm[i]] = val;
|
||||
}
|
||||
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
fn extract_message(&self, codeword: &[Gf2]) -> Vec<Gf2> {
|
||||
let mut msg = vec![0u8; self.k];
|
||||
for j in 0..self.k {
|
||||
msg[j] = codeword[self.col_perm[j]];
|
||||
}
|
||||
msg
|
||||
}
|
||||
|
||||
fn message_len(&self) -> usize {
|
||||
self.k
|
||||
}
|
||||
|
||||
fn codeword_len(&self) -> usize {
|
||||
self.n
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_encoder(code: &mut LdpcCode, method: EncodingMethod) -> Result<Box<dyn Encoder>> {
|
||||
match method {
|
||||
EncodingMethod::Systematic => Ok(Box::new(SystematicEncoder::new(code)?)),
|
||||
}
|
||||
}
|
||||
118
src/rs/generator.rs
Normal file
118
src/rs/generator.rs
Normal file
@ -0,0 +1,118 @@
|
||||
use clap::Parser;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use ldpc::code::{CodeTopology, GenerationMethod, LdpcCode, LdpcParams};
|
||||
use rayon::prelude::*;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about = "Generateur LDPC Haute Performance")]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
n: usize,
|
||||
#[arg(short, long)]
|
||||
k: usize,
|
||||
#[arg(long, default_value_t = 3)]
|
||||
wc: usize,
|
||||
#[arg(long, default_value_t = 6)]
|
||||
wr: usize,
|
||||
#[arg(short, long, default_value = "mackay")]
|
||||
method: String,
|
||||
}
|
||||
|
||||
fn main() -> ldpc::Result<()> {
|
||||
let args = Args::parse();
|
||||
let m = args.n - args.k;
|
||||
|
||||
if (args.n * args.wc) % m != 0 || (args.n * args.wc) / m != args.wr {
|
||||
println!("Erreur : Parametres impossibles (n*wc != m*wr)");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let found = Arc::new(AtomicBool::new(false));
|
||||
let attempts = Arc::new(AtomicU64::new(0));
|
||||
let start_global = Instant::now();
|
||||
|
||||
let pb = ProgressBar::new_spinner();
|
||||
pb.enable_steady_tick(Duration::from_millis(80));
|
||||
pb.set_style(
|
||||
ProgressStyle::default_spinner()
|
||||
.template(
|
||||
"{spinner:.cyan} [{elapsed_precise}] {msg} | Tentatives: {pos} | {per_sec} mats/s",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
pb.set_message("Recherche d'une matrice valide...");
|
||||
|
||||
let method = if args.method == "mackay" {
|
||||
GenerationMethod::MacKayNeal { max_attempts: 1000 }
|
||||
} else {
|
||||
GenerationMethod::Gallager
|
||||
};
|
||||
|
||||
let pb_clone = pb.clone();
|
||||
let found_clone = Arc::clone(&found);
|
||||
let attempts_clone = Arc::clone(&attempts);
|
||||
|
||||
let result = (0..num_cpus::get())
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
while !found_clone.load(Ordering::Relaxed) {
|
||||
let current_attempt = attempts_clone.fetch_add(1, Ordering::SeqCst);
|
||||
pb_clone.set_position(current_attempt);
|
||||
|
||||
let params = LdpcParams {
|
||||
n: args.n,
|
||||
k: args.k,
|
||||
topology: CodeTopology::Regular {
|
||||
wc: args.wc,
|
||||
wr: args.wr,
|
||||
},
|
||||
generation: method.clone(),
|
||||
seed: Some(rand::random()),
|
||||
};
|
||||
|
||||
if let Ok(mut code) = LdpcCode::new(params) {
|
||||
// Cette etape est la plus longue (Gauss-Jordan)
|
||||
if code.compute_systematic_form().is_ok() {
|
||||
if !found_clone.swap(true, Ordering::SeqCst) {
|
||||
return Some(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.find_any(|res| res.is_some())
|
||||
.flatten();
|
||||
|
||||
if let Some(code) = result {
|
||||
pb.finish_with_message(format!("Succes en {:.2?}", start_global.elapsed()));
|
||||
|
||||
let filename = format!(
|
||||
"cache_ldpc_{}_n{}_k{}.bin",
|
||||
args.method.to_uppercase(),
|
||||
args.n,
|
||||
args.k
|
||||
);
|
||||
let encoded = bincode::serialize(&code).expect("Erreur serialisation");
|
||||
let mut file = File::create(&filename).expect("Erreur creation");
|
||||
file.write_all(&encoded).expect("Erreur ecriture");
|
||||
|
||||
println!("\nFichier genere : {}", filename);
|
||||
println!(
|
||||
"Girth : {} | Densite : {:.2}%",
|
||||
code.girth(),
|
||||
code.h.density() * 100.0
|
||||
);
|
||||
} else {
|
||||
pb.abandon_with_message("Recherche arretee.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
42
src/rs/lib.rs
Normal file
42
src/rs/lib.rs
Normal file
@ -0,0 +1,42 @@
|
||||
pub mod benchmark;
|
||||
pub mod benchmark2;
|
||||
pub mod benchmark3;
|
||||
pub mod channel;
|
||||
pub mod code;
|
||||
pub mod decoder;
|
||||
pub mod encoder;
|
||||
pub mod graph;
|
||||
pub mod image_sim;
|
||||
pub mod matrix;
|
||||
|
||||
pub type Gf2 = u8;
|
||||
pub type Llr = f64;
|
||||
pub type BitVec = Vec<Gf2>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LdpcError {
|
||||
#[error("Paramètres invalides : {0}")]
|
||||
InvalidParameters(String),
|
||||
|
||||
#[error("Matrice singulière : impossible d'inverser")]
|
||||
SingularMatrix,
|
||||
|
||||
#[error("Génération échouée après {attempts} tentatives")]
|
||||
GenerationFailed { attempts: usize },
|
||||
|
||||
#[error("Dimension incorrecte : attendu {expected}, reçu {got}")]
|
||||
DimensionMismatch { expected: usize, got: usize },
|
||||
|
||||
#[error("Le vecteur fourni n'est pas un mot de code valide")]
|
||||
InvalidCodeword,
|
||||
|
||||
#[error("Paramètre hors plage : {0}")]
|
||||
OutOfRange(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, LdpcError>;
|
||||
|
||||
pub use channel::Channel;
|
||||
pub use code::{CodeTopology, GenerationMethod, LdpcCode, LdpcParams};
|
||||
pub use decoder::{Decoder, DecoderConfig, DecoderMethod, DecoderResult};
|
||||
pub use encoder::{Encoder, EncodingMethod};
|
||||
284
src/rs/matrix.rs
Normal file
284
src/rs/matrix.rs
Normal file
@ -0,0 +1,284 @@
|
||||
use crate::Gf2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Matrice creuse format CSR + CSC
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SparseMatrixGF2 {
|
||||
pub rows: usize,
|
||||
pub cols: usize,
|
||||
// CSR accès ligne i : col_idx[row_ptr[i]..row_ptr[i+1]]
|
||||
row_ptr: Vec<usize>,
|
||||
col_idx: Vec<usize>,
|
||||
// CSC accès col j : row_idx[col_ptr[j]..col_ptr[j+1]]
|
||||
col_ptr: Vec<usize>,
|
||||
row_idx: Vec<usize>,
|
||||
}
|
||||
|
||||
impl SparseMatrixGF2 {
|
||||
pub fn zeros(rows: usize, cols: usize) -> Self {
|
||||
Self {
|
||||
rows,
|
||||
cols,
|
||||
row_ptr: vec![0; rows + 1],
|
||||
col_idx: vec![],
|
||||
col_ptr: vec![0; cols + 1],
|
||||
row_idx: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
// Depuis une liste de (row, col) indiquant les positions des 1s
|
||||
// Trie les entrées et construit CSR + CSC en un seul passage
|
||||
pub fn from_positions(rows: usize, cols: usize, mut ones: Vec<(usize, usize)>) -> Self {
|
||||
// Construction CSR
|
||||
ones.sort_unstable();
|
||||
let mut row_ptr = vec![0usize; rows + 1];
|
||||
let mut col_idx = Vec::with_capacity(ones.len());
|
||||
for &(r, c) in &ones {
|
||||
row_ptr[r + 1] += 1;
|
||||
col_idx.push(c);
|
||||
}
|
||||
for i in 0..rows {
|
||||
row_ptr[i + 1] += row_ptr[i];
|
||||
}
|
||||
// Construction CSC
|
||||
let mut col_sorted = ones.clone();
|
||||
col_sorted.sort_unstable_by_key(|&(r, c)| (c, r));
|
||||
let mut col_ptr = vec![0usize; cols + 1];
|
||||
let mut row_idx = Vec::with_capacity(ones.len());
|
||||
for &(r, c) in &col_sorted {
|
||||
col_ptr[c + 1] += 1;
|
||||
row_idx.push(r);
|
||||
}
|
||||
for j in 0..cols {
|
||||
col_ptr[j + 1] += col_ptr[j];
|
||||
}
|
||||
Self {
|
||||
rows,
|
||||
cols,
|
||||
row_ptr,
|
||||
col_idx,
|
||||
col_ptr,
|
||||
row_idx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_dense(dense: &[Vec<Gf2>]) -> Self {
|
||||
let rows = dense.len();
|
||||
let cols = if rows > 0 { dense[0].len() } else { 0 };
|
||||
let ones: Vec<(usize, usize)> = dense
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(r, row)| {
|
||||
row.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, &v)| v == 1)
|
||||
.map(move |(c, _)| (r, c))
|
||||
})
|
||||
.collect();
|
||||
Self::from_positions(rows, cols, ones)
|
||||
}
|
||||
|
||||
pub fn get(&self, row: usize, col: usize) -> Gf2 {
|
||||
let slice = self.row_neighbors(row);
|
||||
if slice.binary_search(&col).is_ok() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// Indices des colonnes où la ligne row vaut 1
|
||||
pub fn row_neighbors(&self, row: usize) -> &[usize] {
|
||||
&self.col_idx[self.row_ptr[row]..self.row_ptr[row + 1]]
|
||||
}
|
||||
|
||||
// Indices des lignes où la colonne col vaut 1
|
||||
pub fn col_neighbors(&self, col: usize) -> &[usize] {
|
||||
&self.row_idx[self.col_ptr[col]..self.col_ptr[col + 1]]
|
||||
}
|
||||
|
||||
pub fn row_weight(&self, row: usize) -> usize {
|
||||
self.row_ptr[row + 1] - self.row_ptr[row]
|
||||
}
|
||||
|
||||
pub fn col_weight(&self, col: usize) -> usize {
|
||||
self.col_ptr[col + 1] - self.col_ptr[col]
|
||||
}
|
||||
|
||||
pub fn nnz(&self) -> usize {
|
||||
self.col_idx.len()
|
||||
}
|
||||
|
||||
pub fn density(&self) -> f64 {
|
||||
self.nnz() as f64 / (self.rows * self.cols) as f64
|
||||
}
|
||||
|
||||
// Produit H * x mod 2 (syndrome : s = H * c^T)
|
||||
pub fn multiply_vec(&self, x: &[Gf2]) -> Vec<Gf2> {
|
||||
(0..self.rows)
|
||||
.map(|r| {
|
||||
self.row_neighbors(r)
|
||||
.iter()
|
||||
.map(|&c| x[c])
|
||||
.fold(0u8, |acc, b| acc ^ b)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn transpose(&self) -> Self {
|
||||
Self {
|
||||
rows: self.cols,
|
||||
cols: self.rows,
|
||||
row_ptr: self.col_ptr.clone(),
|
||||
col_idx: self.row_idx.clone(),
|
||||
col_ptr: self.row_ptr.clone(),
|
||||
row_idx: self.col_idx.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifie si deux colonnes partagent >= 2 positions de 1 -> cycle-4 détecté
|
||||
pub fn columns_share_two_ones(&self, c1: usize, c2: usize) -> bool {
|
||||
let n1 = self.col_neighbors(c1);
|
||||
let n2 = self.col_neighbors(c2);
|
||||
let mut common = 0usize;
|
||||
let (mut i, mut j) = (0, 0);
|
||||
while i < n1.len() && j < n2.len() {
|
||||
match n1[i].cmp(&n2[j]) {
|
||||
std::cmp::Ordering::Less => i += 1,
|
||||
std::cmp::Ordering::Greater => j += 1,
|
||||
std::cmp::Ordering::Equal => {
|
||||
common += 1;
|
||||
if common >= 2 {
|
||||
return true;
|
||||
}
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn to_dense(&self) -> Vec<Vec<Gf2>> {
|
||||
let mut out = vec![vec![0u8; self.cols]; self.rows];
|
||||
for r in 0..self.rows {
|
||||
for &c in self.row_neighbors(r) {
|
||||
out[r][c] = 1;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
// Matrice dense
|
||||
// Utilisée pour G et Gauss-Jordan
|
||||
// G = [I | P], P dense
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DenseMatrixGF2 {
|
||||
pub rows: usize,
|
||||
pub cols: usize,
|
||||
data: Vec<Vec<Gf2>>,
|
||||
}
|
||||
|
||||
impl DenseMatrixGF2 {
|
||||
pub fn zeros(rows: usize, cols: usize) -> Self {
|
||||
Self {
|
||||
rows,
|
||||
cols,
|
||||
data: vec![vec![0u8; cols]; rows],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity(n: usize) -> Self {
|
||||
let mut m = Self::zeros(n, n);
|
||||
for i in 0..n {
|
||||
m.data[i][i] = 1;
|
||||
}
|
||||
m
|
||||
}
|
||||
|
||||
pub fn get(&self, row: usize, col: usize) -> Gf2 {
|
||||
self.data[row][col]
|
||||
}
|
||||
pub fn set(&mut self, row: usize, col: usize, val: Gf2) {
|
||||
self.data[row][col] = val;
|
||||
}
|
||||
|
||||
pub fn row_add(&mut self, dst: usize, src: usize) {
|
||||
for j in 0..self.cols {
|
||||
self.data[dst][j] ^= self.data[src][j];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_swap(&mut self, r1: usize, r2: usize) {
|
||||
self.data.swap(r1, r2);
|
||||
}
|
||||
|
||||
pub fn col_swap(&mut self, c1: usize, c2: usize) {
|
||||
for r in 0..self.rows {
|
||||
let tmp = self.data[r][c1];
|
||||
self.data[r][c1] = self.data[r][c2];
|
||||
self.data[r][c2] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn multiply_vec(&self, x: &[Gf2]) -> Vec<Gf2> {
|
||||
self.data
|
||||
.iter()
|
||||
.map(|row| {
|
||||
row.iter()
|
||||
.zip(x.iter())
|
||||
.fold(0u8, |acc, (&a, &b)| acc ^ (a & b))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn into_sparse(self) -> SparseMatrixGF2 {
|
||||
SparseMatrixGF2::from_dense(&self.data)
|
||||
}
|
||||
|
||||
pub fn systematize(&mut self, k: usize) -> (Vec<usize>, usize) {
|
||||
let m = self.rows;
|
||||
let n = self.cols;
|
||||
let mut col_perm: Vec<usize> = (0..n).collect();
|
||||
let mut rank = 0;
|
||||
|
||||
for i in 0..m {
|
||||
// Placer le pivot ligne i à la colonne target_c (former I_m à droite)
|
||||
let target_c = k + i;
|
||||
let mut pivot_found = false;
|
||||
|
||||
// pivot, chercher en premier dans les colonnes cibles restantes
|
||||
// et après dans les colonnes de données (0..k)
|
||||
for c_search in (target_c..n).chain(0..k) {
|
||||
for r_search in i..m {
|
||||
if self.data[r_search][c_search] == 1 {
|
||||
// pivot à la position (i, target_c)
|
||||
self.row_swap(i, r_search);
|
||||
if c_search != target_c {
|
||||
self.col_swap(target_c, c_search);
|
||||
col_perm.swap(target_c, c_search);
|
||||
}
|
||||
pivot_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if pivot_found {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if pivot_found {
|
||||
rank += 1;
|
||||
// Elimination dans toutes les autres lignes => forcer la colonne à 0 (sauf le pivot à 1)
|
||||
for r in 0..m {
|
||||
if r != i && self.data[r][target_c] == 1 {
|
||||
self.row_add(r, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(col_perm, rank)
|
||||
}
|
||||
}
|
||||
2
target/rust-analyzer/flycheck0/stderr
Normal file
2
target/rust-analyzer/flycheck0/stderr
Normal file
@ -0,0 +1,2 @@
|
||||
warning: rklip v0.1.0 (/home/zefad/Documents/Code/Rust/rklip) ignoring invalid dependency `rklipd` which is missing a lib target
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.70s
|
||||
269
target/rust-analyzer/flycheck0/stdout
Normal file
269
target/rust-analyzer/flycheck0/stdout
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user