use ruvector_solver::types::CsrMatrix;
pub struct Lcg {
state: u64,
}
impl Lcg {
pub fn new(seed: u64) -> Self {
Self { state: seed }
}
pub fn next_u64(&mut self) -> u64 {
self.state = self
.state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.state
}
pub fn next_f64(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
}
pub fn next_f64_range(&mut self, lo: f64, hi: f64) -> f64 {
lo + (hi - lo) * self.next_f64()
}
}
pub fn random_diag_dominant_csr(n: usize, density: f64, seed: u64) -> CsrMatrix<f64> {
let mut rng = Lcg::new(seed);
let mut entries: Vec<(usize, usize, f64)> = Vec::new();
for i in 0..n {
let mut off_diag_sum = 0.0f64;
for j in 0..n {
if i == j {
continue;
}
if rng.next_f64() < density {
let val = rng.next_f64_range(-1.0, 1.0);
entries.push((i, j, val));
off_diag_sum += val.abs();
}
}
if off_diag_sum == 0.0 && n > 1 {
let j = (i + 1) % n;
let val = rng.next_f64_range(0.1, 0.5);
entries.push((i, j, val));
off_diag_sum = val;
}
let diag_val = off_diag_sum + 1.0 + rng.next_f64();
entries.push((i, i, diag_val));
}
CsrMatrix::<f64>::from_coo(n, n, entries)
}
pub fn random_laplacian_csr(n: usize, density: f64, seed: u64) -> CsrMatrix<f64> {
let mut rng = Lcg::new(seed);
let mut adj = vec![vec![0.0f64; n]; n];
for i in 0..n {
for j in (i + 1)..n {
if rng.next_f64() < density {
let weight = rng.next_f64_range(0.1, 2.0);
adj[i][j] = weight;
adj[j][i] = weight;
}
}
}
for i in 0..n.saturating_sub(1) {
if adj[i][i + 1] == 0.0 {
let weight = rng.next_f64_range(0.1, 1.0);
adj[i][i + 1] = weight;
adj[i + 1][i] = weight;
}
}
let mut entries: Vec<(usize, usize, f64)> = Vec::new();
for i in 0..n {
let mut degree = 0.0f64;
for j in 0..n {
if i != j && adj[i][j] != 0.0 {
entries.push((i, j, -adj[i][j]));
degree += adj[i][j];
}
}
entries.push((i, i, degree));
}
CsrMatrix::<f64>::from_coo(n, n, entries)
}
pub fn random_spd_csr(n: usize, density: f64, seed: u64) -> CsrMatrix<f64> {
let mut rng = Lcg::new(seed);
let mut dense = vec![vec![0.0f64; n]; n];
for i in 0..n {
for j in i..n {
if i == j || rng.next_f64() < density {
let val = rng.next_f64_range(-1.0, 1.0);
dense[i][j] += val;
if i != j {
dense[j][i] += val;
}
}
}
}
let mut a = vec![vec![0.0f64; n]; n];
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += dense[k][i] * dense[k][j];
}
a[i][j] = sum;
}
}
for i in 0..n {
a[i][i] += 1.0;
}
let mut entries: Vec<(usize, usize, f64)> = Vec::new();
for i in 0..n {
for j in 0..n {
if a[i][j].abs() > 1e-15 {
entries.push((i, j, a[i][j]));
}
}
}
CsrMatrix::<f64>::from_coo(n, n, entries)
}
pub fn random_vector(n: usize, seed: u64) -> Vec<f64> {
let mut rng = Lcg::new(seed);
(0..n).map(|_| rng.next_f64_range(-1.0, 1.0)).collect()
}
pub fn adjacency_from_edges(n: usize, edges: &[(usize, usize)]) -> CsrMatrix<f64> {
let mut entries: Vec<(usize, usize, f64)> = Vec::new();
for &(u, v) in edges {
entries.push((u, v, 1.0));
if u != v {
entries.push((v, u, 1.0));
}
}
CsrMatrix::<f64>::from_coo(n, n, entries)
}
pub fn dense_solve(matrix: &CsrMatrix<f64>, rhs: &[f64]) -> Vec<f64> {
let n = matrix.rows;
assert_eq!(n, matrix.cols, "dense_solve requires a square matrix");
assert_eq!(rhs.len(), n, "rhs length must match matrix dimension");
let mut aug = vec![vec![0.0f64; n + 1]; n];
for i in 0..n {
aug[i][n] = rhs[i];
let start = matrix.row_ptr[i];
let end = matrix.row_ptr[i + 1];
for idx in start..end {
let j = matrix.col_indices[idx];
aug[i][j] = matrix.values[idx];
}
}
for col in 0..n {
let mut max_row = col;
let mut max_val = aug[col][col].abs();
for row in (col + 1)..n {
if aug[row][col].abs() > max_val {
max_val = aug[row][col].abs();
max_row = row;
}
}
assert!(max_val > 1e-15, "matrix is singular or near-singular");
aug.swap(col, max_row);
let pivot = aug[col][col];
for row in (col + 1)..n {
let factor = aug[row][col] / pivot;
for j in col..=n {
aug[row][j] -= factor * aug[col][j];
}
}
}
let mut x = vec![0.0f64; n];
for i in (0..n).rev() {
let mut sum = aug[i][n];
for j in (i + 1)..n {
sum -= aug[i][j] * x[j];
}
x[i] = sum / aug[i][i];
}
x
}
pub fn l2_norm(v: &[f64]) -> f64 {
v.iter().map(|&x| x * x).sum::<f64>().sqrt()
}
pub fn l2_distance(a: &[f64], b: &[f64]) -> f64 {
assert_eq!(a.len(), b.len(), "vectors must have same length");
a.iter()
.zip(b.iter())
.map(|(&ai, &bi)| (ai - bi) * (ai - bi))
.sum::<f64>()
.sqrt()
}
pub fn relative_error(approx: &[f64], exact: &[f64]) -> f64 {
let exact_norm = l2_norm(exact);
let error = l2_distance(approx, exact);
if exact_norm > 1e-15 {
error / exact_norm
} else {
error
}
}
pub fn compute_residual(matrix: &CsrMatrix<f64>, x: &[f64], rhs: &[f64]) -> Vec<f64> {
let n = matrix.rows;
let mut ax = vec![0.0f64; n];
matrix.spmv(x, &mut ax);
(0..n).map(|i| rhs[i] - ax[i]).collect()
}
pub fn f32_to_f64(v: &[f32]) -> Vec<f64> {
v.iter().map(|&x| x as f64).collect()
}