use std::time::Duration;
#[derive(Debug, Clone)]
pub struct CsrMatrix<T> {
pub row_ptr: Vec<usize>,
pub col_indices: Vec<usize>,
pub values: Vec<T>,
pub rows: usize,
pub cols: usize,
}
impl<T: Copy + Default + std::ops::Mul<Output = T> + std::ops::AddAssign> CsrMatrix<T> {
#[inline]
pub fn spmv(&self, x: &[T], y: &mut [T]) {
debug_assert!(
x.len() >= self.cols,
"spmv: x.len()={} < cols={}",
x.len(),
self.cols,
);
debug_assert!(
y.len() >= self.rows,
"spmv: y.len()={} < rows={}",
y.len(),
self.rows,
);
for i in 0..self.rows {
let mut sum = T::default();
let start = self.row_ptr[i];
let end = self.row_ptr[i + 1];
for idx in start..end {
sum += self.values[idx] * x[self.col_indices[idx]];
}
y[i] = sum;
}
}
}
impl CsrMatrix<f32> {
#[inline]
pub fn spmv_unchecked(&self, x: &[f32], y: &mut [f32]) {
debug_assert!(x.len() >= self.cols);
debug_assert!(y.len() >= self.rows);
let vals = self.values.as_ptr();
let cols = self.col_indices.as_ptr();
let rp = self.row_ptr.as_ptr();
for i in 0..self.rows {
let start = unsafe { *rp.add(i) };
let end = unsafe { *rp.add(i + 1) };
let mut sum = 0.0f32;
for idx in start..end {
unsafe {
let v = *vals.add(idx);
let c = *cols.add(idx);
sum += v * *x.get_unchecked(c);
}
}
unsafe { *y.get_unchecked_mut(i) = sum };
}
}
#[inline]
pub fn fused_residual_norm_sq(&self, x: &[f32], rhs: &[f32], residual: &mut [f32]) -> f64 {
debug_assert!(x.len() >= self.cols);
debug_assert!(rhs.len() >= self.rows);
debug_assert!(residual.len() >= self.rows);
let vals = self.values.as_ptr();
let cols = self.col_indices.as_ptr();
let rp = self.row_ptr.as_ptr();
let mut norm_sq = 0.0f64;
for i in 0..self.rows {
let start = unsafe { *rp.add(i) };
let end = unsafe { *rp.add(i + 1) };
let mut ax_i = 0.0f32;
for idx in start..end {
unsafe {
let v = *vals.add(idx);
let c = *cols.add(idx);
ax_i += v * *x.get_unchecked(c);
}
}
let r_i = rhs[i] - ax_i;
residual[i] = r_i;
norm_sq += (r_i as f64) * (r_i as f64);
}
norm_sq
}
}
impl CsrMatrix<f64> {
#[inline]
pub fn spmv_unchecked(&self, x: &[f64], y: &mut [f64]) {
debug_assert!(x.len() >= self.cols);
debug_assert!(y.len() >= self.rows);
let vals = self.values.as_ptr();
let cols = self.col_indices.as_ptr();
let rp = self.row_ptr.as_ptr();
for i in 0..self.rows {
let start = unsafe { *rp.add(i) };
let end = unsafe { *rp.add(i + 1) };
let mut sum = 0.0f64;
for idx in start..end {
unsafe {
let v = *vals.add(idx);
let c = *cols.add(idx);
sum += v * *x.get_unchecked(c);
}
}
unsafe { *y.get_unchecked_mut(i) = sum };
}
}
}
impl<T> CsrMatrix<T> {
#[inline]
pub fn nnz(&self) -> usize {
self.values.len()
}
#[inline]
pub fn row_degree(&self, row: usize) -> usize {
self.row_ptr[row + 1] - self.row_ptr[row]
}
#[inline]
pub fn row_entries(&self, row: usize) -> impl Iterator<Item = (usize, &T)> {
let start = self.row_ptr[row];
let end = self.row_ptr[row + 1];
self.col_indices[start..end]
.iter()
.copied()
.zip(self.values[start..end].iter())
}
}
impl<T: Copy + Default> CsrMatrix<T> {
pub fn transpose(&self) -> CsrMatrix<T> {
let nnz = self.nnz();
let t_rows = self.cols;
let t_cols = self.rows;
let mut row_ptr = vec![0usize; t_rows + 1];
for &c in &self.col_indices {
row_ptr[c + 1] += 1;
}
for i in 1..=t_rows {
row_ptr[i] += row_ptr[i - 1];
}
let mut col_indices = vec![0usize; nnz];
let mut values = vec![T::default(); nnz];
let mut cursor = row_ptr.clone();
for row in 0..self.rows {
let start = self.row_ptr[row];
let end = self.row_ptr[row + 1];
for idx in start..end {
let c = self.col_indices[idx];
let dest = cursor[c];
col_indices[dest] = row;
values[dest] = self.values[idx];
cursor[c] += 1;
}
}
CsrMatrix {
row_ptr,
col_indices,
values,
rows: t_rows,
cols: t_cols,
}
}
}
impl<T: Copy + Default + std::ops::AddAssign> CsrMatrix<T> {
pub fn from_coo_generic(
rows: usize,
cols: usize,
entries: impl IntoIterator<Item = (usize, usize, T)>,
) -> Self {
let mut sorted: Vec<_> = entries.into_iter().collect();
sorted.sort_unstable_by_key(|(r, c, _)| (*r, *c));
let nnz = sorted.len();
let mut row_ptr = vec![0usize; rows + 1];
let mut col_indices = Vec::with_capacity(nnz);
let mut values = Vec::with_capacity(nnz);
for &(r, _, _) in &sorted {
assert!(r < rows, "row index {} out of bounds (rows={})", r, rows);
row_ptr[r + 1] += 1;
}
for i in 1..=rows {
row_ptr[i] += row_ptr[i - 1];
}
for (_, c, v) in sorted {
assert!(c < cols, "col index {} out of bounds (cols={})", c, cols);
col_indices.push(c);
values.push(v);
}
Self {
row_ptr,
col_indices,
values,
rows,
cols,
}
}
}
impl CsrMatrix<f32> {
pub fn from_coo(
rows: usize,
cols: usize,
entries: impl IntoIterator<Item = (usize, usize, f32)>,
) -> Self {
Self::from_coo_generic(rows, cols, entries)
}
pub fn identity(n: usize) -> Self {
let row_ptr: Vec<usize> = (0..=n).collect();
let col_indices: Vec<usize> = (0..n).collect();
let values = vec![1.0f32; n];
Self {
row_ptr,
col_indices,
values,
rows: n,
cols: n,
}
}
}
impl CsrMatrix<f64> {
pub fn from_coo(
rows: usize,
cols: usize,
entries: impl IntoIterator<Item = (usize, usize, f64)>,
) -> Self {
Self::from_coo_generic(rows, cols, entries)
}
pub fn identity(n: usize) -> Self {
let row_ptr: Vec<usize> = (0..=n).collect();
let col_indices: Vec<usize> = (0..n).collect();
let values = vec![1.0f64; n];
Self {
row_ptr,
col_indices,
values,
rows: n,
cols: n,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum Algorithm {
Neumann,
Jacobi,
GaussSeidel,
ForwardPush,
BackwardPush,
CG,
HybridRandomWalk,
TRUE,
BMSSP,
Dense,
}
impl std::fmt::Display for Algorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Algorithm::Neumann => write!(f, "neumann"),
Algorithm::Jacobi => write!(f, "jacobi"),
Algorithm::GaussSeidel => write!(f, "gauss-seidel"),
Algorithm::ForwardPush => write!(f, "forward-push"),
Algorithm::BackwardPush => write!(f, "backward-push"),
Algorithm::CG => write!(f, "cg"),
Algorithm::HybridRandomWalk => write!(f, "hybrid-random-walk"),
Algorithm::TRUE => write!(f, "true-solver"),
Algorithm::BMSSP => write!(f, "bmssp"),
Algorithm::Dense => write!(f, "dense"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QueryType {
LinearSystem,
PageRankSingle {
source: usize,
},
PageRankPairwise {
source: usize,
target: usize,
},
SpectralFilter {
polynomial_degree: usize,
},
BatchLinearSystem {
batch_size: usize,
},
}
#[derive(Debug, Clone)]
pub struct SparsityProfile {
pub rows: usize,
pub cols: usize,
pub nnz: usize,
pub density: f64,
pub is_diag_dominant: bool,
pub estimated_spectral_radius: f64,
pub estimated_condition: f64,
pub is_symmetric_structure: bool,
pub avg_nnz_per_row: f64,
pub max_nnz_per_row: usize,
}
#[derive(Debug, Clone)]
pub struct ComplexityEstimate {
pub algorithm: Algorithm,
pub estimated_flops: u64,
pub estimated_iterations: usize,
pub estimated_memory_bytes: usize,
pub complexity_class: ComplexityClass,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ComplexityClass {
SublinearNnz,
SqrtCondition,
Linear,
Quadratic,
Cubic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ComputeLane {
Fast,
Normal,
Batch,
}
#[derive(Debug, Clone)]
pub struct ComputeBudget {
pub max_time: Duration,
pub max_iterations: usize,
pub tolerance: f64,
}
impl Default for ComputeBudget {
fn default() -> Self {
Self {
max_time: Duration::from_secs(30),
max_iterations: 1000,
tolerance: 1e-6,
}
}
}
#[derive(Debug, Clone)]
pub struct ConvergenceInfo {
pub iteration: usize,
pub residual_norm: f64,
}
#[derive(Debug, Clone)]
pub struct SolverResult {
pub solution: Vec<f32>,
pub iterations: usize,
pub residual_norm: f64,
pub wall_time: Duration,
pub convergence_history: Vec<ConvergenceInfo>,
pub algorithm: Algorithm,
}