This commit is contained in:
2026-05-07 18:32:36 +02:00
parent 2fcd368e34
commit b7faade0c8
25 changed files with 4209 additions and 4 deletions

295
srctmp/matrix.rs Normal file
View File

@ -0,0 +1,295 @@
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<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 (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<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 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<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;
}
// 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<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)
}
// 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>, usize) {
let mut perm: Vec<usize> = (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));
// }
// }