use num_bigint::{BigInt, Sign};
use sprs::{CsMat, TriMat};
fn bigint_to_mod_i64(c: &BigInt, prime: &BigInt) -> i64 {
let r = c % prime;
let (sign, digits) = r.to_u64_digits();
if digits.is_empty() {
return 0;
}
let v = digits[0] as i64;
if sign == Sign::Minus {
let p = prime.to_u64_digits().1[0] as i64;
v + p
} else {
v
}
}
pub struct SprsMacaulayMatrix {
pub matrix: CsMat<i64>,
pub nrows: usize,
pub ncols: usize,
}
impl SprsMacaulayMatrix {
pub fn from_rows(rows: &[Vec<(usize, BigInt)>], ncols: usize, prime: &BigInt) -> Self {
let nrows = rows.len();
let mut triplet = TriMat::new((nrows, ncols));
for (i, row) in rows.iter().enumerate() {
for &(col, ref coeff) in row {
let c_val = bigint_to_mod_i64(coeff, prime);
if c_val != 0 {
triplet.add_triplet(i, col, c_val);
}
}
}
let matrix = triplet.to_csr();
Self {
matrix,
nrows,
ncols,
}
}
pub fn nnz(&self) -> usize {
self.matrix.nnz()
}
pub fn density(&self) -> f64 {
if self.nrows == 0 || self.ncols == 0 {
return 0.0;
}
self.nnz() as f64 / (self.nrows as f64 * self.ncols as f64)
}
pub fn row_count(&self) -> usize {
self.nrows
}
pub fn spmv(&self, x: &[i64], prime: i64) -> Option<Vec<i64>> {
if x.len() != self.ncols {
return None;
}
let mut y = vec![0i64; self.nrows];
for (row_idx, row_vec) in self.matrix.outer_iterator().enumerate() {
for (col, &val) in row_vec.iter() {
y[row_idx] = (y[row_idx] + val * x[col]) % prime;
if y[row_idx] < 0 {
y[row_idx] += prime;
}
}
}
Some(y)
}
}
impl std::fmt::Debug for SprsMacaulayMatrix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SprsMacaulayMatrix")
.field("nrows", &self.nrows)
.field("ncols", &self.ncols)
.field("nnz", &self.nnz())
.field("density", &self.density())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn macaulay_from_rows_identity() {
let rows = vec![
vec![(0, BigInt::from(1))],
vec![(1, BigInt::from(1))],
vec![(2, BigInt::from(1))],
];
let m = SprsMacaulayMatrix::from_rows(&rows, 3, &BigInt::from(5));
assert_eq!(m.nrows, 3);
assert_eq!(m.ncols, 3);
assert_eq!(m.nnz(), 3);
assert!((m.density() - 1.0 / 3.0).abs() < 1e-10);
}
#[test]
fn macaulay_modular_reduction() {
let rows = vec![vec![(0, BigInt::from(7)), (1, BigInt::from(3))]];
let m = SprsMacaulayMatrix::from_rows(&rows, 2, &BigInt::from(5));
assert_eq!(m.nnz(), 2);
let x = vec![1i64, 0];
let y = m.spmv(&x, 5).unwrap();
assert_eq!(y[0], 2); }
#[test]
fn macaulay_spmv() {
let rows = vec![
vec![(0, BigInt::from(1)), (1, BigInt::from(2))],
vec![(0, BigInt::from(3)), (1, BigInt::from(4))],
];
let m = SprsMacaulayMatrix::from_rows(&rows, 2, &BigInt::from(100));
let x = vec![1i64, 1];
let y = m.spmv(&x, 100).unwrap();
assert_eq!(y[0], 3);
assert_eq!(y[1], 7);
}
#[test]
fn macaulay_sparse() {
let rows: Vec<Vec<(usize, BigInt)>> = (0..10)
.map(|i| vec![(i, BigInt::from(1)), ((i + 5) % 10, BigInt::from(2))])
.collect();
let m = SprsMacaulayMatrix::from_rows(&rows, 10, &BigInt::from(7));
assert_eq!(m.nrows, 10);
assert_eq!(m.ncols, 10);
assert!((m.density() - 0.2).abs() < 1e-10);
}
#[test]
fn macaulay_spmv_dimension_mismatch() {
let rows = vec![vec![(0, BigInt::from(1))]];
let m = SprsMacaulayMatrix::from_rows(&rows, 1, &BigInt::from(7));
assert!(m.spmv(&[1, 2], 7).is_none());
}
}