conspire 0.7.2

The Rust interface to conspire.
Documentation
use super::{CscMatrix, SparseError, Vector};
use crate::math::assert::Assert;
use crate::math::assert::AssertionError;

fn symmetric_matrix(n: usize, scale: f64) -> CscMatrix {
    let mut pattern: Vec<(usize, usize)> = (0..n).map(|i| (i, i)).collect();
    (0..n - 7).for_each(|i| {
        pattern.push((i, i + 7));
        pattern.push((i + 7, i));
    });
    (0..n - 3).for_each(|i| {
        pattern.push((i, i + 3));
        pattern.push((i + 3, i));
    });
    let mut matrix = CscMatrix::from_pattern(n, n, pattern);
    matrix.fill(|i, j| {
        if i == j {
            16.0 + (i % 5) as f64
        } else {
            scale * (((i.min(j) * 7 + i.max(j) * 3) % 5) as f64 - 2.0)
        }
    });
    matrix
}

#[test]
fn solve_matches_lu() -> Result<(), AssertionError> {
    let n = 100;
    let matrix = symmetric_matrix(n, 1.0);
    let b: Vector = (0..n).map(|i| (i % 13) as f64 - 6.0).collect();
    let mut ldl = matrix.ldl_symbolic()?;
    ldl.refactor(&matrix).expect("Refactorization failed.");
    Assert::default().eq_within_tols(
        ldl.solve(&b),
        &matrix.lu_amd().expect("Factorization failed.").solve(&b),
    )?;
    Assert::default().eq_within_tols(&(&matrix * &ldl.solve(&b)), &b)
}

#[test]
fn refactor_new_values() -> Result<(), AssertionError> {
    let n = 100;
    let matrix = symmetric_matrix(n, 1.0);
    let mut ldl = matrix.ldl_symbolic()?;
    ldl.refactor(&matrix).expect("Refactorization failed.");
    let matrix = symmetric_matrix(n, -2.0);
    ldl.refactor(&matrix).expect("Refactorization failed.");
    let b: Vector = (0..n).map(|i| (i % 17) as f64 - 8.0).collect();
    Assert::default().eq_within_tols(&(&matrix * &ldl.solve(&b)), &b)
}

#[test]
fn indefinite() -> Result<(), AssertionError> {
    let n = 100;
    let mut matrix = symmetric_matrix(n, 1.0);
    matrix.fill(|i, j| {
        if i == j {
            (16.0 + (i % 5) as f64) * if i % 2 == 0 { 1.0 } else { -1.0 }
        } else {
            ((i.min(j) * 7 + i.max(j) * 3) % 5) as f64 - 2.0
        }
    });
    let mut ldl = matrix.ldl_symbolic()?;
    ldl.refactor(&matrix).expect("Refactorization failed.");
    let b: Vector = (0..n).map(|i| (i % 13) as f64 - 6.0).collect();
    Assert::default().eq_within_tols(&(&matrix * &ldl.solve(&b)), &b)
}

#[test]
fn missing_diagonal() -> Result<(), AssertionError> {
    let pattern = vec![
        (0, 0),
        (0, 1),
        (1, 0),
        (1, 1),
        (0, 2),
        (2, 0),
        (1, 2),
        (2, 1),
    ];
    let mut matrix = CscMatrix::from_pattern(3, 3, pattern);
    matrix.fill(|i, j| if i == j { 4.0 } else { (i + j) as f64 });
    let mut ldl = matrix.ldl_symbolic()?;
    ldl.refactor(&matrix).expect("Refactorization failed.");
    let b = Vector::from([1.0, 2.0, 3.0]);
    Assert::default().eq_within_tols(&(&matrix * &ldl.solve(&b)), &b)
}

#[test]
fn singular() {
    let mut matrix = CscMatrix::from_pattern(2, 2, vec![(0, 0), (0, 1), (1, 0), (1, 1)]);
    matrix.fill(|_, _| 1.0);
    let mut ldl = matrix.ldl_symbolic().expect("Symbolic failed.");
    assert!(
        ldl.refactor(&matrix)
            .is_err_and(|error| error == SparseError::Singular)
    )
}

fn saddle_point_matrix(n: usize, num_constraints: usize, scale: f64) -> CscMatrix {
    let total = n + num_constraints;
    let mut pattern: Vec<(usize, usize)> = (0..n).map(|i| (i, i)).collect();
    (0..n - 3).for_each(|i| {
        pattern.push((i, i + 3));
        pattern.push((i + 3, i));
    });
    (0..num_constraints).for_each(|c| {
        let dof = (5 * c) % n;
        let other = (5 * c + 2) % n;
        pattern.push((n + c, dof));
        pattern.push((dof, n + c));
        pattern.push((n + c, other));
        pattern.push((other, n + c));
    });
    let mut matrix = CscMatrix::from_pattern(total, total, pattern);
    matrix.fill(|i, j| {
        if i == j {
            16.0 + (i % 5) as f64
        } else if i >= n || j >= n {
            1.0 + (i.min(j) % 3) as f64
        } else {
            scale * (((i.min(j) * 7 + i.max(j) * 3) % 5) as f64 - 2.0)
        }
    });
    matrix
}

#[test]
fn saddle_point_matches_lu() -> Result<(), AssertionError> {
    let matrix = saddle_point_matrix(40, 6, 1.0);
    let b: Vector = (0..46).map(|i| (i % 13) as f64 - 6.0).collect();
    let mut ldl = matrix.ldl_symbolic()?;
    ldl.refactor(&matrix).expect("Refactorization failed.");
    Assert::default().eq_within_tols(
        ldl.solve(&b),
        &matrix.lu_amd().expect("Factorization failed.").solve(&b),
    )?;
    Assert::default().eq_within_tols(&(&matrix * &ldl.solve(&b)), &b)
}

#[test]
fn saddle_point_refactor_new_values() -> Result<(), AssertionError> {
    let matrix = saddle_point_matrix(40, 6, 1.0);
    let mut ldl = matrix.ldl_symbolic()?;
    ldl.refactor(&matrix).expect("Refactorization failed.");
    let matrix = saddle_point_matrix(40, 6, -2.0);
    ldl.refactor(&matrix).expect("Refactorization failed.");
    let b: Vector = (0..46).map(|i| (i % 17) as f64 - 8.0).collect();
    Assert::default().eq_within_tols(&(&matrix * &ldl.solve(&b)), &b)
}

#[test]
fn saddle_point_larger() -> Result<(), AssertionError> {
    let matrix = saddle_point_matrix(200, 31, 1.0);
    let b: Vector = (0..231).map(|i| (i % 11) as f64 - 5.0).collect();
    let mut ldl = matrix.ldl_symbolic()?;
    ldl.refactor(&matrix).expect("Refactorization failed.");
    Assert::default().eq_within_tols(&(&matrix * &ldl.solve(&b)), &b)
}