use crate::{Gf2, Result}; // Matrice creuse GF(2) — format CSR + CSC dual #[derive(Debug, Clone)] 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, col_idx: Vec, // CSC — accès col j : row_idx[ col_ptr[j] .. col_ptr[j+1] ] col_ptr: Vec, row_idx: Vec, } 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]) -> 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 (voisins check→var) 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 (voisins var→check) 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 (calcul du syndrome : s = H * c^T) pub fn multiply_vec(&self, x: &[Gf2]) -> Vec { (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> { 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 GF(2) // Utilisée pour la matrice génératrice G et les calculs de Gauss-Jordan // G = [I | P], P est souvent dense #[derive(Debug, Clone)] pub struct DenseMatrixGF2 { pub rows: usize, pub cols: usize, data: Vec>, } 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; } // Addition de deux lignes dans GF(2) 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 multiply_vec(&self, x: &[Gf2]) -> Vec { 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) } // Retourne la permutation de colonnes appliquée et le rang // self est sous forme échelonnée réduite pub fn gauss_jordan_gf2(&mut self) -> (Vec, usize) { let mut perm: Vec = (0..self.cols).collect(); let mut pivot_row = 0; for col in 0..self.cols { // Chercher un pivot dans la colonne courante let pivot = (pivot_row..self.rows).find(|&r| self.data[r][col] == 1); let Some(p) = pivot else { continue }; self.row_swap(pivot_row, p); // Éliminer dans toutes les autres lignes for r in 0..self.rows { if r != pivot_row && self.data[r][col] == 1 { self.row_add(r, pivot_row); } } pivot_row += 1; if pivot_row == self.rows { break; } } (perm, pivot_row) } } // #[cfg(test)] // mod tests { // use super::*; // // #[test] // fn test_from_dense_roundtrip() { // let dense = vec![vec![1u8, 0, 1, 0], vec![0, 1, 0, 1], vec![1, 1, 0, 0]]; // let sparse = SparseMatrixGF2::from_dense(&dense); // assert_eq!(sparse.to_dense(), dense); // } // // #[test] // fn test_multiply_vec_gf2() { // let dense = vec![vec![1u8, 1, 0], vec![0, 1, 1]]; // let h = SparseMatrixGF2::from_dense(&dense); // let x = vec![1u8, 1, 1]; // let s = h.multiply_vec(&x); // // ligne 0 : 1^1^0 = 0, ligne 1 : 0^1^1 = 0 // assert_eq!(s, vec![0u8, 0]); // } // // #[test] // fn test_transpose_double_is_identity() { // let dense = vec![vec![1u8, 0, 1], vec![0, 1, 0]]; // let h = SparseMatrixGF2::from_dense(&dense); // assert_eq!(h.transpose().transpose().to_dense(), dense); // } // // #[test] // fn test_columns_share_two_ones() { // // Colonnes 0 et 1 partagent les lignes 0 et 1 → cycle-4 // let dense = vec![vec![1u8, 1, 0], vec![1, 1, 0], vec![0, 0, 1]]; // let h = SparseMatrixGF2::from_dense(&dense); // assert!(h.columns_share_two_ones(0, 1)); // assert!(!h.columns_share_two_ones(0, 2)); // } // }