use sprs::{CsMat, TriMat};
pub struct SparseSystem {
tri: TriMat<f64>,
dim: usize,
}
impl SparseSystem {
pub fn new(dim: usize) -> Self {
Self {
tri: TriMat::new((dim, dim)),
dim,
}
}
pub fn add(&mut self, i: usize, j: usize, val: f64) {
if i < self.dim && j < self.dim {
self.tri.add_triplet(i, j, val);
if i != j {
self.tri.add_triplet(j, i, val);
}
}
}
pub fn add_diag(&mut self, i: usize, val: f64) {
if i < self.dim {
self.tri.add_triplet(i, i, val);
}
}
pub fn finish(self) -> CsMat<f64> {
self.tri.to_csr()
}
pub fn dim(&self) -> usize {
self.dim
}
}
pub fn conjugate_gradient(
a: &CsMat<f64>,
b: &[f64],
max_iter: usize,
tol: f64,
) -> Option<Vec<f64>> {
let n = a.rows();
assert_eq!(n, b.len(), "A 与 b 维度不匹配");
if n == 0 {
return Some(Vec::new());
}
let b_norm = norm2(b);
if b_norm < 1e-30 {
return Some(vec![0.0; n]);
}
let mut x = vec![0.0; n];
let mut r = b.to_vec();
let mut p = r.clone();
let mut rsold = dot(&r, &r);
for _iter in 0..max_iter {
let ap = sparse_matvec(a, &p);
let alpha = rsold / dot(&p, &ap);
for i in 0..n {
x[i] += alpha * p[i];
}
for i in 0..n {
r[i] -= alpha * ap[i];
}
let rsnew = dot(&r, &r);
let residual_rel = rsnew.sqrt() / b_norm;
if residual_rel < tol {
return Some(x);
}
let beta = rsnew / rsold;
for i in 0..n {
p[i] = r[i] + beta * p[i];
}
rsold = rsnew;
}
None
}
pub fn regularize_diagonal(a: &mut CsMat<f64>, lambda: f64) {
let n = a.rows();
for i in 0..n {
if let Some(val) = a.get_mut(i, i) {
*val += lambda;
}
}
}
fn sparse_matvec(a: &CsMat<f64>, x: &[f64]) -> Vec<f64> {
let mut y = vec![0.0; a.rows()];
for (row_idx, row) in a.outer_iterator().enumerate() {
let mut sum = 0.0;
for (col_idx, &val) in row.iter() {
sum += val * x[col_idx];
}
y[row_idx] = sum;
}
y
}
fn dot(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
fn norm2(v: &[f64]) -> f64 {
dot(v, v).sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cg_identity() {
let mut tri = TriMat::new((3, 3));
tri.add_triplet(0, 0, 1.0);
tri.add_triplet(1, 1, 1.0);
tri.add_triplet(2, 2, 1.0);
let a = tri.to_csr();
let b = vec![1.0, 2.0, 3.0];
let x = conjugate_gradient(&a, &b, 50, 1e-12).unwrap();
for i in 0..3 {
assert!((x[i] - b[i]).abs() < 1e-10);
}
}
#[test]
fn test_cg_small_spd() {
let mut tri = TriMat::new((3, 3));
tri.add_triplet(0, 0, 2.0);
tri.add_triplet(1, 1, 3.0);
tri.add_triplet(2, 2, 2.0);
tri.add_triplet(0, 1, 1.0);
tri.add_triplet(1, 0, 1.0);
tri.add_triplet(1, 2, -1.0);
tri.add_triplet(2, 1, -1.0);
let a = tri.to_csr();
let b = vec![1.0, 0.0, 1.0];
let x = conjugate_gradient(&a, &b, 50, 1e-12).unwrap();
let ax = sparse_matvec(&a, &x);
for i in 0..3 {
assert!(
(ax[i] - b[i]).abs() < 1e-10,
"component {i}: Ax={} b={}",
ax[i],
b[i]
);
}
}
#[test]
fn test_regularize_diagonal() {
let mut tri = TriMat::new((2, 2));
tri.add_triplet(0, 0, 1.0);
tri.add_triplet(1, 1, 2.0);
let mut a = tri.to_csr();
regularize_diagonal(&mut a, 0.5);
assert!((a.get(0, 0).unwrap() - 1.5).abs() < 1e-14);
assert!((a.get(1, 1).unwrap() - 2.5).abs() < 1e-14);
}
#[test]
fn test_sparse_system_builder() {
let mut sys = SparseSystem::new(3);
sys.add(0, 0, 4.0);
sys.add(0, 1, -1.0);
sys.add(1, 2, -2.0);
sys.add_diag(2, 3.0);
let a = sys.finish();
assert_eq!(a.rows(), 3);
assert!((a.get(0, 0).unwrap() - 4.0).abs() < 1e-14);
assert!((a.get(0, 1).unwrap() + 1.0).abs() < 1e-14);
assert!((a.get(1, 0).unwrap() + 1.0).abs() < 1e-14);
assert!((a.get(1, 2).unwrap() + 2.0).abs() < 1e-14);
assert!((a.get(2, 1).unwrap() + 2.0).abs() < 1e-14);
assert!((a.get(2, 2).unwrap() - 3.0).abs() < 1e-14);
}
}