use sprs::CsMat;
use crate::matrix::incidence::diagonal;
use crate::matrix::triplet::CooBuilder;
pub fn build_weighted_laplacian(a: &CsMat<f64>, w: &[f64]) -> CsMat<f64> {
let d = diagonal(w);
let at = a.transpose_view().to_csr();
a * &(&d * &at)
}
pub fn ground_at(matrix: &CsMat<f64>, r: usize) -> CsMat<f64> {
ground_at_each(matrix, &[r])
}
pub fn ground_at_each(matrix: &CsMat<f64>, refs: &[usize]) -> CsMat<f64> {
ground_with(matrix, &Grounding::new(refs))
}
pub(crate) struct Grounding {
grounds: Vec<usize>,
}
impl Grounding {
pub(crate) fn new(refs: &[usize]) -> Self {
let mut grounds = refs.to_vec();
grounds.sort_unstable();
grounds.dedup();
Self { grounds }
}
pub(crate) fn len(&self) -> usize {
self.grounds.len()
}
pub(crate) fn max(&self) -> Option<usize> {
self.grounds.last().copied()
}
pub(crate) fn reduced(&self, i: usize) -> Option<usize> {
if self.grounds.binary_search(&i).is_ok() {
None
} else {
Some(i - self.grounds.partition_point(|&g| g < i))
}
}
}
pub(crate) fn ground_with(matrix: &CsMat<f64>, g: &Grounding) -> CsMat<f64> {
let n = matrix.rows();
debug_assert_eq!(n, matrix.cols(), "ground_with expects a square matrix");
if let Some(last) = g.max() {
assert!(
last < n,
"ground_with: index {last} out of range for {n}x{n} matrix"
);
}
let mut out = CooBuilder::new(n.saturating_sub(g.len()));
for (&v, (i, j)) in matrix {
if let (Some(ri), Some(rj)) = (g.reduced(i), g.reduced(j)) {
out.add(ri, rj, v);
}
}
out.finish_csr()
}
#[derive(Debug, Clone, Copy)]
pub struct GroundedIndexMap {
pub n: usize,
pub r: usize,
}
impl GroundedIndexMap {
#[inline]
pub fn new(n: usize, r: usize) -> Self {
Self { n, r }
}
#[inline]
pub fn full_to_reduced(&self, i: usize) -> Option<usize> {
match i {
_ if i == self.r => None,
_ if i > self.r => Some(i - 1),
_ => Some(i),
}
}
#[inline]
pub fn reduced_to_full(&self, i: usize) -> usize {
if i >= self.r { i + 1 } else { i }
}
}
pub fn unit_vector(n: usize, r: usize) -> Vec<f64> {
let mut e = vec![0.0; n];
if r < n {
e[r] = 1.0;
}
e
}
pub fn reference_indicator(n: usize, refs: &[usize]) -> Vec<f64> {
let mut e = vec![0.0; n];
for &r in refs {
if r < n {
e[r] = 1.0;
}
}
e
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grounding_reduced_shifts_survivors() {
let g = Grounding::new(&[1, 3]);
assert_eq!(g.len(), 2);
assert_eq!(g.reduced(0), Some(0));
assert_eq!(g.reduced(1), None);
assert_eq!(g.reduced(2), Some(1));
assert_eq!(g.reduced(3), None);
assert_eq!(g.reduced(4), Some(2));
}
#[test]
fn grounding_sorts_and_dedups() {
let g = Grounding::new(&[3, 1, 3]);
assert_eq!(g.len(), 2);
assert_eq!(g.max(), Some(3));
assert_eq!(g.reduced(2), Some(1));
assert_eq!(g.reduced(4), Some(2));
}
fn diag_matrix(vals: &[f64]) -> CsMat<f64> {
let mut b = CooBuilder::new(vals.len());
for (i, &v) in vals.iter().enumerate() {
b.add(i, i, v);
}
b.finish_csr()
}
#[test]
fn ground_at_each_removes_rows_and_cols() {
let m = diag_matrix(&[10.0, 20.0, 30.0, 40.0]);
let g1 = ground_at_each(&m, &[1]);
assert_eq!((g1.rows(), g1.cols()), (3, 3));
assert_eq!(g1.get(0, 0), Some(&10.0));
assert_eq!(g1.get(1, 1), Some(&30.0));
assert_eq!(g1.get(2, 2), Some(&40.0));
let g2 = ground_at_each(&m, &[2, 0]);
assert_eq!((g2.rows(), g2.cols()), (2, 2));
assert_eq!(g2.get(0, 0), Some(&20.0));
assert_eq!(g2.get(1, 1), Some(&40.0));
}
#[test]
fn reference_indicator_marks_each_ref() {
assert_eq!(reference_indicator(4, &[0, 2]), vec![1.0, 0.0, 1.0, 0.0]);
assert_eq!(reference_indicator(3, &[5]), vec![0.0, 0.0, 0.0]);
assert_eq!(reference_indicator(3, &[1]), unit_vector(3, 1));
}
}