use faer::Col;
use faer::prelude::{Reborrow, Solve};
use faer::sparse::linalg::solvers::{Lu, SymbolicLu};
use faer::sparse::{Argsort, Pair, SparseColMat, SymbolicSparseColMat, Triplet};
use num_complex::Complex;
pub trait LinearSolver: Sized {
fn new(n: usize, entries: &[(usize, usize, f64)]) -> Option<Self>;
fn factor_and_solve(
&mut self,
entries: &[(usize, usize, f64)],
rhs: &[f64],
) -> Option<Vec<f64>>;
fn factor_and_solve_values(&mut self, values: &[f64], rhs: &[f64]) -> Option<Vec<f64>>;
}
#[derive(Clone)]
pub struct SparseMatrix {
n: usize,
mat: SparseColMat<usize, Complex<f64>>,
}
impl SparseMatrix {
pub fn build(n: usize, entries: &[(usize, usize, Complex<f64>)]) -> Option<Self> {
let triplets: Vec<Triplet<usize, usize, Complex<f64>>> = entries
.iter()
.map(|&(r, c, v)| Triplet::new(r, c, v))
.collect();
let mat =
SparseColMat::<usize, Complex<f64>>::try_new_from_triplets(n, n, &triplets).ok()?;
Some(Self { n, mat })
}
pub fn mul_vec(&self, v: &[Complex<f64>]) -> Vec<Complex<f64>> {
let vcol = Col::<Complex<f64>>::from_fn(self.n, |i| v[i]);
let result = &self.mat * &vcol;
(0..self.n).map(|i| result[i]).collect()
}
}
pub fn solve_complex(
n: usize,
entries: &[(usize, usize, Complex<f64>)],
rhs: &[Complex<f64>],
) -> Option<Vec<Complex<f64>>> {
let triplets: Vec<Triplet<usize, usize, Complex<f64>>> = entries
.iter()
.map(|&(r, c, v)| Triplet::new(r, c, v))
.collect();
let mat = SparseColMat::<usize, Complex<f64>>::try_new_from_triplets(n, n, &triplets).ok()?;
let lu = mat.sp_lu().ok()?;
let b = Col::<Complex<f64>>::from_fn(n, |i| rhs[i]);
let x = lu.solve(&b);
if (0..n).any(|i| !x[i].re.is_finite() || !x[i].im.is_finite()) {
return None;
}
Some((0..n).map(|i| x[i]).collect())
}
pub struct RealSparseSystem {
n: usize,
n_values: usize,
symbolic_mat: SymbolicSparseColMat<usize>,
argsort: Argsort<usize>,
symbolic_lu: SymbolicLu<usize>,
}
impl RealSparseSystem {
pub fn new(n: usize, entries: &[(usize, usize, f64)]) -> Option<Self> {
let pairs: Vec<Pair<usize, usize>> = entries
.iter()
.map(|&(r, c, _)| Pair { row: r, col: c })
.collect();
let (symbolic_mat, argsort) =
SymbolicSparseColMat::try_new_from_indices(n, n, &pairs).ok()?;
let symbolic_lu = SymbolicLu::try_new(symbolic_mat.rb()).ok()?;
Some(Self {
n,
n_values: entries.len(),
symbolic_mat,
argsort,
symbolic_lu,
})
}
pub fn factor_and_solve(
&self,
entries: &[(usize, usize, f64)],
rhs: &[f64],
) -> Option<Vec<f64>> {
let vals: Vec<f64> = entries.iter().map(|&(_, _, v)| v).collect();
self.solve_values(&vals, rhs)
}
pub fn solve_values(&self, vals: &[f64], rhs: &[f64]) -> Option<Vec<f64>> {
if vals.len() != self.n_values || rhs.len() < self.n {
return None;
}
let mat =
SparseColMat::new_from_argsort(self.symbolic_mat.clone(), &self.argsort, vals).ok()?;
let lu = Lu::try_new_with_symbolic(self.symbolic_lu.clone(), mat.as_ref()).ok()?;
let b = Col::<f64>::from_fn(self.n, |i| rhs[i]);
let x = lu.solve(&b);
if (0..self.n).any(|i| !x[i].is_finite()) {
return None;
}
Some((0..self.n).map(|i| x[i]).collect())
}
}
impl LinearSolver for RealSparseSystem {
fn new(n: usize, entries: &[(usize, usize, f64)]) -> Option<Self> {
RealSparseSystem::new(n, entries)
}
fn factor_and_solve(
&mut self,
entries: &[(usize, usize, f64)],
rhs: &[f64],
) -> Option<Vec<f64>> {
RealSparseSystem::factor_and_solve(self, entries, rhs)
}
fn factor_and_solve_values(&mut self, values: &[f64], rhs: &[f64]) -> Option<Vec<f64>> {
self.solve_values(values, rhs)
}
}
pub struct ComplexSparseSystem {
n: usize,
lu: Lu<usize, Complex<f64>>,
}
impl ComplexSparseSystem {
pub fn new(n: usize, entries: &[(usize, usize, Complex<f64>)]) -> Option<Self> {
let triplets: Vec<Triplet<usize, usize, Complex<f64>>> = entries
.iter()
.map(|&(r, c, v)| Triplet::new(r, c, v))
.collect();
let mat =
SparseColMat::<usize, Complex<f64>>::try_new_from_triplets(n, n, &triplets).ok()?;
Some(Self {
n,
lu: mat.sp_lu().ok()?,
})
}
pub fn solve(&self, rhs: &[Complex<f64>]) -> Option<Vec<Complex<f64>>> {
let b = Col::<Complex<f64>>::from_fn(self.n, |i| rhs[i]);
let x = self.lu.solve(&b);
if (0..self.n).any(|i| !x[i].re.is_finite() || !x[i].im.is_finite()) {
return None;
}
Some((0..self.n).map(|i| x[i]).collect())
}
pub fn solve_adjoint(&self, rhs: &[Complex<f64>]) -> Option<Vec<Complex<f64>>> {
debug_assert_eq!(rhs.len(), self.n);
let mut b = Col::<Complex<f64>>::from_fn(self.n, |i| rhs[i]);
self.lu.solve_adjoint_in_place(b.as_mut());
if (0..self.n).any(|i| !b[i].re.is_finite() || !b[i].im.is_finite()) {
return None;
}
Some((0..self.n).map(|i| b[i]).collect())
}
}
pub struct RealFactorization {
n: usize,
lu: Lu<usize, f64>,
}
impl RealFactorization {
pub fn new(n: usize, entries: &[(usize, usize, f64)]) -> Option<Self> {
let triplets: Vec<Triplet<usize, usize, f64>> = entries
.iter()
.map(|&(r, c, v)| Triplet::new(r, c, v))
.collect();
let mat = SparseColMat::<usize, f64>::try_new_from_triplets(n, n, &triplets).ok()?;
Some(Self {
n,
lu: mat.sp_lu().ok()?,
})
}
pub fn n(&self) -> usize {
self.n
}
pub fn solve(&self, rhs: &[f64]) -> Option<Vec<f64>> {
let mut out = vec![0.0; self.n];
self.solve_into(rhs, &mut out).then_some(out)
}
pub fn solve_transpose(&self, rhs: &[f64]) -> Option<Vec<f64>> {
debug_assert_eq!(rhs.len(), self.n);
let mut b = Col::<f64>::from_fn(self.n, |i| rhs[i]);
self.lu.solve_transpose_in_place(b.as_mut());
if (0..self.n).any(|i| !b[i].is_finite()) {
return None;
}
Some((0..self.n).map(|i| b[i]).collect())
}
fn solve_into(&self, rhs: &[f64], out: &mut [f64]) -> bool {
debug_assert_eq!(rhs.len(), self.n);
debug_assert_eq!(out.len(), self.n);
let b = Col::<f64>::from_fn(self.n, |i| rhs[i]);
let x = self.lu.solve(&b);
if (0..self.n).any(|i| !x[i].is_finite()) {
return false;
}
for i in 0..self.n {
out[i] = x[i];
}
true
}
}
pub fn solve_dense(mut a: Vec<Vec<f64>>, mut b: Vec<f64>) -> Option<Vec<f64>> {
let n = b.len();
for col in 0..n {
let pivot_row = (col..n).max_by(|&i, &j| a[i][col].abs().total_cmp(&a[j][col].abs()))?;
if a[pivot_row][col].abs() < 1e-12 {
return None;
}
a.swap(col, pivot_row);
b.swap(col, pivot_row);
for row in (col + 1)..n {
let factor = a[row][col] / a[col][col];
if factor == 0.0 {
continue;
}
for k in col..n {
a[row][k] -= factor * a[col][k];
}
b[row] -= factor * b[col];
}
}
let mut x = vec![0.0; n];
for row in (0..n).rev() {
let sum: f64 = (row + 1..n).map(|k| a[row][k] * x[k]).sum();
x[row] = (b[row] - sum) / a[row][row];
}
Some(x)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn solve_dense_matches_a_hand_solution() {
let x = solve_dense(vec![vec![2.0, 1.0], vec![1.0, 3.0]], vec![5.0, 10.0]).unwrap();
assert!(
(x[0] - 1.0).abs() < 1e-12 && (x[1] - 3.0).abs() < 1e-12,
"{x:?}"
);
}
#[test]
fn solve_dense_pivots_past_a_zero_diagonal() {
let x = solve_dense(vec![vec![0.0, 2.0], vec![1.0, 1.0]], vec![4.0, 3.0]).unwrap();
assert!(
(x[0] - 1.0).abs() < 1e-12 && (x[1] - 2.0).abs() < 1e-12,
"{x:?}"
);
}
#[test]
fn solve_dense_singular_returns_none() {
assert!(solve_dense(vec![vec![1.0, 1.0], vec![2.0, 2.0]], vec![1.0, 2.0]).is_none());
}
#[test]
fn solve_complex_simple_system() {
let entries = vec![
(0, 0, Complex::new(2.0, 0.0)),
(0, 1, Complex::new(1.0, 0.0)),
(1, 0, Complex::new(1.0, 0.0)),
(1, 1, Complex::new(3.0, 0.0)),
];
let rhs = vec![Complex::new(5.0, 0.0), Complex::new(10.0, 0.0)];
let x = solve_complex(2, &entries, &rhs).unwrap();
assert!((x[0] - Complex::new(1.0, 0.0)).norm() < 1e-10);
assert!((x[1] - Complex::new(3.0, 0.0)).norm() < 1e-10);
}
#[test]
fn solve_complex_duplicate_entries_sum() {
let entries = vec![
(0, 0, Complex::new(1.0, 0.0)),
(0, 0, Complex::new(1.0, 0.0)),
(0, 1, Complex::new(1.0, 0.0)),
(1, 0, Complex::new(1.0, 0.0)),
(1, 1, Complex::new(3.0, 0.0)),
];
let rhs = vec![Complex::new(5.0, 0.0), Complex::new(10.0, 0.0)];
let x = solve_complex(2, &entries, &rhs).unwrap();
assert!((x[0] - Complex::new(1.0, 0.0)).norm() < 1e-10);
assert!((x[1] - Complex::new(3.0, 0.0)).norm() < 1e-10);
}
#[test]
fn real_sparse_system_refactor_reuses_symbolic() {
let entries_a = vec![(0, 0, 2.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 3.0)];
let sys = RealSparseSystem::new(2, &entries_a).unwrap();
let x = sys.factor_and_solve(&entries_a, &[5.0, 10.0]).unwrap();
assert!((x[0] - 1.0).abs() < 1e-10);
assert!((x[1] - 3.0).abs() < 1e-10);
let entries_b = vec![(0, 0, 4.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 2.0)];
let x2 = sys.factor_and_solve(&entries_b, &[6.0, 5.0]).unwrap();
assert!((x2[0] - 1.0).abs() < 1e-10);
assert!((x2[1] - 2.0).abs() < 1e-10);
}
#[test]
fn singular_matrix_returns_none() {
let entries = vec![(0, 0, 1.0), (0, 1, 1.0), (1, 0, 2.0), (1, 1, 2.0)];
let sys = RealSparseSystem::new(2, &entries);
if let Some(sys) = sys {
assert!(sys.factor_and_solve(&entries, &[1.0, 2.0]).is_none());
}
}
#[test]
fn real_factorization_reuses_one_factorization_across_right_hand_sides() {
let entries = vec![(0, 0, 2.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 3.0)];
let sys = RealFactorization::new(2, &entries).unwrap();
assert_eq!(sys.n(), 2);
let x = sys.solve(&[5.0, 10.0]).unwrap();
assert!((x[0] - 1.0).abs() < 1e-12, "got {}", x[0]);
assert!((x[1] - 3.0).abs() < 1e-12, "got {}", x[1]);
let c0 = sys.solve(&[1.0, 0.0]).unwrap();
assert!(
(c0[0] - 0.6).abs() < 1e-12 && (c0[1] + 0.2).abs() < 1e-12,
"{c0:?}"
);
let c1 = sys.solve(&[0.0, 1.0]).unwrap();
assert!(
(c1[0] + 0.2).abs() < 1e-12 && (c1[1] - 0.4).abs() < 1e-12,
"{c1:?}"
);
}
#[test]
fn real_factorization_solve_into_matches_solve() {
let entries = vec![(0, 0, 2.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 3.0)];
let sys = RealFactorization::new(2, &entries).unwrap();
let mut out = vec![0.0; 2];
assert!(sys.solve_into(&[5.0, 10.0], &mut out));
assert_eq!(out, sys.solve(&[5.0, 10.0]).unwrap());
}
#[test]
fn real_factorization_duplicate_entries_sum() {
let entries = vec![
(0, 0, 1.0),
(0, 0, 1.0),
(0, 1, 1.0),
(1, 0, 1.0),
(1, 1, 3.0),
];
let sys = RealFactorization::new(2, &entries).unwrap();
let x = sys.solve(&[5.0, 10.0]).unwrap();
assert!(
(x[0] - 1.0).abs() < 1e-12 && (x[1] - 3.0).abs() < 1e-12,
"{x:?}"
);
}
#[test]
fn real_factorization_solve_transpose_matches_an_explicit_transpose() {
let entries = vec![(0, 0, 2.0), (0, 1, 1.0), (1, 0, 5.0), (1, 1, 3.0)];
let transposed: Vec<(usize, usize, f64)> =
entries.iter().map(|&(r, c, v)| (c, r, v)).collect();
let a = RealFactorization::new(2, &entries).unwrap();
let at = RealFactorization::new(2, &transposed).unwrap();
for rhs in [[1.0, 0.0], [0.0, 1.0], [3.0, -7.0]] {
let via_transpose = a.solve_transpose(&rhs).unwrap();
let via_explicit = at.solve(&rhs).unwrap();
for i in 0..2 {
assert!(
(via_transpose[i] - via_explicit[i]).abs() < 1e-12,
"rhs {rhs:?}: {via_transpose:?} vs {via_explicit:?}"
);
}
}
let forward = a.solve(&[1.0, 0.0]).unwrap();
let adjoint = a.solve_transpose(&[1.0, 0.0]).unwrap();
assert!(
(forward[1] - adjoint[1]).abs() > 1e-6,
"{forward:?} vs {adjoint:?}"
);
}
#[test]
fn complex_system_solve_adjoint_matches_an_explicit_conjugate_transpose() {
let c = |re: f64, im: f64| Complex::new(re, im);
let entries = vec![
(0, 0, c(2.0, 1.0)),
(0, 1, c(1.0, -3.0)),
(1, 0, c(5.0, 2.0)),
(1, 1, c(3.0, 0.5)),
];
let adjoint_entries: Vec<(usize, usize, Complex<f64>)> = entries
.iter()
.map(|&(r, c_, v)| (c_, r, v.conj()))
.collect();
let a = ComplexSparseSystem::new(2, &entries).unwrap();
let ah = ComplexSparseSystem::new(2, &adjoint_entries).unwrap();
for rhs in [
[c(1.0, 0.0), c(0.0, 0.0)],
[c(0.0, 0.0), c(1.0, 0.0)],
[c(3.0, -1.0), c(-7.0, 2.0)],
] {
let via_adjoint = a.solve_adjoint(&rhs).unwrap();
let via_explicit = ah.solve(&rhs).unwrap();
for i in 0..2 {
assert!(
(via_adjoint[i] - via_explicit[i]).norm() < 1e-12,
"rhs {rhs:?}: {via_adjoint:?} vs {via_explicit:?}"
);
}
}
let forward = a.solve(&[c(1.0, 0.0), c(0.0, 0.0)]).unwrap();
let adjoint = a.solve_adjoint(&[c(1.0, 0.0), c(0.0, 0.0)]).unwrap();
assert!(
(forward[1] - adjoint[1]).norm() > 1e-6,
"{forward:?} vs {adjoint:?}"
);
}
#[test]
fn real_factorization_singular_returns_none() {
let entries = vec![(0, 0, 1.0), (0, 1, 1.0), (1, 0, 2.0), (1, 1, 2.0)];
if let Some(sys) = RealFactorization::new(2, &entries) {
assert!(sys.solve(&[1.0, 2.0]).is_none());
}
}
}