use crate::matrix::SparseMatrixGF2; use std::collections::VecDeque; // Graphe de Tanner #[derive(Debug, Clone)] pub struct TannerGraph { pub n_var: usize, pub n_chk: usize, var_to_chk: Vec>, chk_to_var: Vec>, } impl TannerGraph { pub fn from_matrix(h: &SparseMatrixGF2) -> Self { let n_var = h.cols; let n_chk = h.rows; let chk_to_var: Vec> = (0..n_chk).map(|c| h.row_neighbors(c).to_vec()).collect(); let mut var_to_chk = vec![vec![]; n_var]; for c in 0..n_chk { for &v in &chk_to_var[c] { var_to_chk[v].push(c); } } Self { n_var, n_chk, var_to_chk, chk_to_var, } } pub fn var_neighbors(&self, v: usize) -> &[usize] { &self.var_to_chk[v] } pub fn chk_neighbors(&self, c: usize) -> &[usize] { &self.chk_to_var[c] } pub fn var_degree(&self, v: usize) -> usize { self.var_to_chk[v].len() } pub fn chk_degree(&self, c: usize) -> usize { self.chk_to_var[c].len() } // Calcule le girth par BFS depuis chaque noeud de variable // O(n * (n + m)) pub fn girth(&self) -> usize { let mut min_girth = usize::MAX; for start in 0..self.n_var { if let Some(g) = self.bfs_girth_from_var(start) { min_girth = min_girth.min(g); if min_girth == 4 { return 4; } // minimum possible } } min_girth } // Détection rapide de cycles-4, deux var-nodes partagent >= check-nodes pub fn has_4_cycle(&self) -> bool { for v1 in 0..self.n_var { for v2 in (v1 + 1)..self.n_var { let common = self.var_to_chk[v1] .iter() .filter(|c| self.var_to_chk[v2].contains(c)) .count(); if common >= 2 { return true; } } } false } // Girth local depuis un noeud de variable v (pour PEG). pub fn local_girth_from_var(&self, v: usize) -> usize { self.bfs_girth_from_var(v).unwrap_or(usize::MAX) } // BFS depuis le noeud de variable start, // retourne la longueur du court cycle passant par ce noeud (None si aucun cycle) fn bfs_girth_from_var(&self, start: usize) -> Option { // dist_var[v] = distance depuis start jusqu'au var-node v // dist_chk[c] = distance depuis start jusqu'au check-node c let mut dist_var = vec![usize::MAX; self.n_var]; let mut dist_chk = vec![usize::MAX; self.n_chk]; dist_var[start] = 0; // File : (is_var: bool, index, distance) let mut queue: VecDeque<(bool, usize, usize)> = VecDeque::new(); queue.push_back((true, start, 0)); let mut shortest = None; while let Some((is_var, node, dist)) = queue.pop_front() { if is_var { for &c in self.var_neighbors(node) { if dist_chk[c] == usize::MAX { dist_chk[c] = dist + 1; queue.push_back((false, c, dist + 1)); } else { // Cycle trouvé let cycle_len = dist + 1 + dist_chk[c]; shortest = Some(shortest.map_or(cycle_len, |s: usize| s.min(cycle_len))); } } } else { for &v in self.chk_neighbors(node) { if v == start && dist + 1 >= 2 { let cycle_len = dist + 1; shortest = Some(shortest.map_or(cycle_len, |s: usize| s.min(cycle_len))); continue; } if dist_var[v] == usize::MAX { dist_var[v] = dist + 1; queue.push_back((true, v, dist + 1)); } } } } shortest } pub fn var_degree_distribution(&self) -> Vec { let max_deg = self.var_to_chk.iter().map(|v| v.len()).max().unwrap_or(0); let mut counts = vec![0usize; max_deg + 1]; for v in 0..self.n_var { counts[self.var_degree(v)] += 1; } counts .iter() .map(|&c| c as f64 / self.n_var as f64) .collect() } pub fn is_regular(&self) -> bool { let d0 = self.var_degree(0); let c0 = self.chk_degree(0); self.var_to_chk.iter().all(|v| v.len() == d0) && self.chk_to_var.iter().all(|c| c.len() == c0) } } // #[cfg(test)] // mod tests { // use super::*; // use crate::matrix::SparseMatrixGF2; // // fn simple_h() -> SparseMatrixGF2 { // SparseMatrixGF2::from_dense(&vec![ // vec![1u8, 1, 0, 1, 0], // vec![0, 1, 1, 0, 1], // vec![1, 0, 1, 0, 1], // ]) // } // // #[test] // fn test_construction_from_matrix() { // let h = simple_h(); // let g = TannerGraph::from_matrix(&h); // assert_eq!(g.n_var, 5); // assert_eq!(g.n_chk, 3); // } // // #[test] // fn test_var_degree() { // let g = TannerGraph::from_matrix(&simple_h()); // assert_eq!(g.var_degree(0), 2); // } // }