use crate::csr_array::CsrArray;
use crate::error::{SparseError, SparseResult};
use crate::sparray::SparseArray;
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use scirs2_core::SparseElement;
use std::collections::HashMap;
use std::fmt::Debug;
use std::ops::{Add, Div, Mul, Sub};
#[derive(Debug, Clone)]
pub struct LUResult<T>
where
T: Float + SparseElement + Debug + Copy + 'static,
{
pub l: CsrArray<T>,
pub u: CsrArray<T>,
pub p: Array1<usize>,
pub success: bool,
}
#[derive(Debug, Clone)]
pub struct QRResult<T>
where
T: Float + SparseElement + Debug + Copy + 'static,
{
pub q: CsrArray<T>,
pub r: CsrArray<T>,
pub success: bool,
}
#[derive(Debug, Clone)]
pub struct CholeskyResult<T>
where
T: Float + SparseElement + Debug + Copy + 'static,
{
pub l: CsrArray<T>,
pub success: bool,
}
#[derive(Debug, Clone)]
pub struct PivotedCholeskyResult<T>
where
T: Float + SparseElement + Debug + Copy + 'static,
{
pub l: CsrArray<T>,
pub p: Array1<usize>,
pub rank: usize,
pub success: bool,
}
#[derive(Debug, Clone, Default)]
pub enum PivotingStrategy {
None,
#[default]
Partial,
Threshold(f64),
ScaledPartial,
Complete,
Rook,
}
#[derive(Debug, Clone)]
pub struct LUOptions {
pub pivoting: PivotingStrategy,
pub zero_threshold: f64,
pub check_singular: bool,
}
impl Default for LUOptions {
fn default() -> Self {
Self {
pivoting: PivotingStrategy::default(),
zero_threshold: 1e-14,
check_singular: true,
}
}
}
#[derive(Debug, Clone)]
pub struct ILUOptions {
pub drop_tol: f64,
pub fill_factor: f64,
pub max_fill_per_row: usize,
pub pivoting: PivotingStrategy,
}
impl Default for ILUOptions {
fn default() -> Self {
Self {
drop_tol: 1e-4,
fill_factor: 2.0,
max_fill_per_row: 20,
pivoting: PivotingStrategy::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct ICOptions {
pub drop_tol: f64,
pub fill_factor: f64,
pub max_fill_per_row: usize,
}
impl Default for ICOptions {
fn default() -> Self {
Self {
drop_tol: 1e-4,
fill_factor: 2.0,
max_fill_per_row: 20,
}
}
}
#[allow(dead_code)]
pub fn lu_decomposition<T, S>(_matrix: &S, pivotthreshold: f64) -> SparseResult<LUResult<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>,
S: SparseArray<T>,
{
let options = LUOptions {
pivoting: PivotingStrategy::Threshold(pivotthreshold),
zero_threshold: 1e-14,
check_singular: true,
};
lu_decomposition_with_options(_matrix, Some(options))
}
#[allow(dead_code)]
pub fn lu_decomposition_with_options<T, S>(
matrix: &S,
options: Option<LUOptions>,
) -> SparseResult<LUResult<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>,
S: SparseArray<T>,
{
let opts = options.unwrap_or_default();
let (n, m) = matrix.shape();
if n != m {
return Err(SparseError::ValueError(
"Matrix must be square for LU decomposition".to_string(),
));
}
let (row_indices, col_indices, values) = matrix.find();
let mut working_matrix = SparseWorkingMatrix::from_triplets(
row_indices.as_slice().expect("Operation failed"),
col_indices.as_slice().expect("Operation failed"),
values.as_slice().expect("Operation failed"),
n,
);
let mut row_perm: Vec<usize> = (0..n).collect();
let mut col_perm: Vec<usize> = (0..n).collect();
let mut row_scales = vec![T::sparse_one(); n];
if matches!(opts.pivoting, PivotingStrategy::ScaledPartial) {
for (i, scale) in row_scales.iter_mut().enumerate().take(n) {
let row_data = working_matrix.get_row(i);
let max_val = row_data
.values()
.map(|&v| v.abs())
.fold(T::sparse_zero(), |a, b| if a > b { a } else { b });
if max_val > T::sparse_zero() {
*scale = max_val;
}
}
}
for k in 0..n - 1 {
let (pivot_row, pivot_col) =
find_enhanced_pivot(&working_matrix, k, &row_perm, &col_perm, &row_scales, &opts)?;
if pivot_row != k {
row_perm.swap(k, pivot_row);
}
if pivot_col != k
&& matches!(
opts.pivoting,
PivotingStrategy::Complete | PivotingStrategy::Rook
)
{
col_perm.swap(k, pivot_col);
for &row_idx in row_perm.iter().take(n) {
let temp = working_matrix.get(row_idx, k);
working_matrix.set(row_idx, k, working_matrix.get(row_idx, pivot_col));
working_matrix.set(row_idx, pivot_col, temp);
}
}
let actual_pivot_row = row_perm[k];
let actual_pivot_col = col_perm[k];
let pivot_value = working_matrix.get(actual_pivot_row, actual_pivot_col);
if opts.check_singular
&& pivot_value.abs() < T::from(opts.zero_threshold).expect("Operation failed")
{
return Ok(LUResult {
l: CsrArray::from_triplets(&[], &[], &[], (n, n), false)?,
u: CsrArray::from_triplets(&[], &[], &[], (n, n), false)?,
p: Array1::from_vec(row_perm),
success: false,
});
}
for &actual_row_i in row_perm.iter().take(n).skip(k + 1) {
let factor = working_matrix.get(actual_row_i, actual_pivot_col) / pivot_value;
if !SparseElement::is_zero(&factor) {
working_matrix.set(actual_row_i, actual_pivot_col, factor);
let pivot_row_data = working_matrix.get_row(actual_pivot_row);
for (col, &value) in &pivot_row_data {
if *col > k {
let old_val = working_matrix.get(actual_row_i, *col);
working_matrix.set(actual_row_i, *col, old_val - factor * value);
}
}
}
}
}
let (l_rows, l_cols, l_vals, u_rows, u_cols, u_vals) =
extract_lu_factors(&working_matrix, &row_perm, n);
let l = CsrArray::from_triplets(&l_rows, &l_cols, &l_vals, (n, n), false)?;
let u = CsrArray::from_triplets(&u_rows, &u_cols, &u_vals, (n, n), false)?;
Ok(LUResult {
l,
u,
p: Array1::from_vec(row_perm),
success: true,
})
}
#[allow(dead_code)]
pub fn qr_decomposition<T, S>(matrix: &S) -> SparseResult<QRResult<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>,
S: SparseArray<T>,
{
let (m, n) = matrix.shape();
let dense_matrix = matrix.to_array();
let mut q = Array2::zeros((m, n));
let mut r = Array2::zeros((n, n));
for j in 0..n {
for i in 0..m {
q[[i, j]] = dense_matrix[[i, j]];
}
for k in 0..j {
let mut dot = T::sparse_zero();
for i in 0..m {
dot = dot + q[[i, k]] * dense_matrix[[i, j]];
}
r[[k, j]] = dot;
for i in 0..m {
q[[i, j]] = q[[i, j]] - dot * q[[i, k]];
}
}
let mut norm = T::sparse_zero();
for i in 0..m {
norm = norm + q[[i, j]] * q[[i, j]];
}
norm = norm.sqrt();
r[[j, j]] = norm;
if !SparseElement::is_zero(&norm) {
for i in 0..m {
q[[i, j]] = q[[i, j]] / norm;
}
}
}
let q_sparse = dense_to_sparse(&q)?;
let r_sparse = dense_to_sparse(&r)?;
Ok(QRResult {
q: q_sparse,
r: r_sparse,
success: true,
})
}
#[allow(dead_code)]
pub fn cholesky_decomposition<T, S>(matrix: &S) -> SparseResult<CholeskyResult<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>,
S: SparseArray<T>,
{
let (n, m) = matrix.shape();
if n != m {
return Err(SparseError::ValueError(
"Matrix must be square for Cholesky decomposition".to_string(),
));
}
let (row_indices, col_indices, values) = matrix.find();
let mut working_matrix = SparseWorkingMatrix::from_triplets(
row_indices.as_slice().expect("Operation failed"),
col_indices.as_slice().expect("Operation failed"),
values.as_slice().expect("Operation failed"),
n,
);
for k in 0..n {
let mut sum = T::sparse_zero();
for j in 0..k {
let l_kj = working_matrix.get(k, j);
sum = sum + l_kj * l_kj;
}
let a_kk = working_matrix.get(k, k);
let diag_val = a_kk - sum;
if diag_val <= T::sparse_zero() {
return Ok(CholeskyResult {
l: CsrArray::from_triplets(&[], &[], &[], (n, n), false)?,
success: false,
});
}
let l_kk = diag_val.sqrt();
working_matrix.set(k, k, l_kk);
for i in (k + 1)..n {
let mut sum = T::sparse_zero();
for j in 0..k {
sum = sum + working_matrix.get(i, j) * working_matrix.get(k, j);
}
let a_ik = working_matrix.get(i, k);
let l_ik = (a_ik - sum) / l_kk;
working_matrix.set(i, k, l_ik);
}
}
let (l_rows, l_cols, l_vals) = extract_lower_triangular(&working_matrix, n);
let l = CsrArray::from_triplets(&l_rows, &l_cols, &l_vals, (n, n), false)?;
Ok(CholeskyResult { l, success: true })
}
#[allow(dead_code)]
pub fn pivoted_cholesky_decomposition<T, S>(
matrix: &S,
threshold: Option<T>,
) -> SparseResult<PivotedCholeskyResult<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>,
S: SparseArray<T>,
{
let (n, m) = matrix.shape();
if n != m {
return Err(SparseError::ValueError(
"Matrix must be square for Cholesky decomposition".to_string(),
));
}
let threshold = threshold.unwrap_or_else(|| T::from(1e-12).expect("Operation failed"));
let (row_indices, col_indices, values) = matrix.find();
let mut working_matrix = SparseWorkingMatrix::from_triplets(
row_indices.as_slice().expect("Operation failed"),
col_indices.as_slice().expect("Operation failed"),
values.as_slice().expect("Operation failed"),
n,
);
let mut perm: Vec<usize> = (0..n).collect();
let mut rank = 0;
for k in 0..n {
let mut max_diag = T::sparse_zero();
let mut pivot_idx = k;
for i in k..n {
let mut diag_val = working_matrix.get(perm[i], perm[i]);
for j in 0..k {
let l_ij = working_matrix.get(perm[i], perm[j]);
diag_val = diag_val - l_ij * l_ij;
}
if diag_val > max_diag {
max_diag = diag_val;
pivot_idx = i;
}
}
if max_diag <= threshold {
break;
}
if pivot_idx != k {
perm.swap(k, pivot_idx);
}
let l_kk = max_diag.sqrt();
working_matrix.set(perm[k], perm[k], l_kk);
rank += 1;
for i in (k + 1)..n {
let mut sum = T::sparse_zero();
for j in 0..k {
sum = sum
+ working_matrix.get(perm[i], perm[j]) * working_matrix.get(perm[k], perm[j]);
}
let a_ik = working_matrix.get(perm[i], perm[k]);
let l_ik = (a_ik - sum) / l_kk;
working_matrix.set(perm[i], perm[k], l_ik);
}
}
let mut l_rows = Vec::new();
let mut l_cols = Vec::new();
let mut l_vals = Vec::new();
for i in 0..rank {
for j in 0..=i {
let val = working_matrix.get(perm[i], perm[j]);
if val != T::sparse_zero() {
l_rows.push(i);
l_cols.push(j);
l_vals.push(val);
}
}
}
let l = CsrArray::from_triplets(&l_rows, &l_cols, &l_vals, (n, rank), false)?;
let p = Array1::from_vec(perm);
Ok(PivotedCholeskyResult {
l,
p,
rank,
success: true,
})
}
#[derive(Debug, Clone)]
pub struct LDLTResult<T>
where
T: Float + SparseElement + Debug + Copy + 'static,
{
pub l: CsrArray<T>,
pub d: Array1<T>,
pub p: Array1<usize>,
pub success: bool,
}
#[allow(dead_code)]
pub fn ldlt_decomposition<T, S>(
matrix: &S,
pivoting: Option<bool>,
threshold: Option<T>,
) -> SparseResult<LDLTResult<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>,
S: SparseArray<T>,
{
let (n, m) = matrix.shape();
if n != m {
return Err(SparseError::ValueError(
"Matrix must be square for LDLT decomposition".to_string(),
));
}
let use_pivoting = pivoting.unwrap_or(true);
let threshold = threshold.unwrap_or_else(|| T::from(1e-12).expect("Operation failed"));
let (row_indices, col_indices, values) = matrix.find();
let mut working_matrix = SparseWorkingMatrix::from_triplets(
row_indices.as_slice().expect("Operation failed"),
col_indices.as_slice().expect("Operation failed"),
values.as_slice().expect("Operation failed"),
n,
);
let mut perm: Vec<usize> = (0..n).collect();
let mut d_values = vec![T::sparse_zero(); n];
for k in 0..n {
if use_pivoting {
let pivot_idx = find_ldlt_pivot(&working_matrix, k, &perm, threshold);
if pivot_idx != k {
perm.swap(k, pivot_idx);
}
}
let actual_k = perm[k];
let mut diag_val = working_matrix.get(actual_k, actual_k);
for j in 0..k {
let l_kj = working_matrix.get(actual_k, perm[j]);
diag_val = diag_val - l_kj * l_kj * d_values[j];
}
d_values[k] = diag_val;
if diag_val.abs() < threshold {
return Ok(LDLTResult {
l: CsrArray::from_triplets(&[], &[], &[], (n, n), false)?,
d: Array1::from_vec(d_values),
p: Array1::from_vec(perm),
success: false,
});
}
for i in (k + 1)..n {
let actual_i = perm[i];
let mut l_ik = working_matrix.get(actual_i, actual_k);
for j in 0..k {
l_ik = l_ik
- working_matrix.get(actual_i, perm[j])
* working_matrix.get(actual_k, perm[j])
* d_values[j];
}
l_ik = l_ik / diag_val;
working_matrix.set(actual_i, actual_k, l_ik);
}
working_matrix.set(actual_k, actual_k, T::sparse_one());
}
let (l_rows, l_cols, l_vals) = extract_unit_lower_triangular(&working_matrix, &perm, n);
let l = CsrArray::from_triplets(&l_rows, &l_cols, &l_vals, (n, n), false)?;
Ok(LDLTResult {
l,
d: Array1::from_vec(d_values),
p: Array1::from_vec(perm),
success: true,
})
}
#[allow(dead_code)]
fn find_ldlt_pivot<T>(
matrix: &SparseWorkingMatrix<T>,
k: usize,
perm: &[usize],
threshold: T,
) -> usize
where
T: Float + SparseElement + Debug + Copy,
{
let n = matrix.n;
let mut max_val = T::sparse_zero();
let mut pivot_idx = k;
for (i, &actual_i) in perm.iter().enumerate().take(n).skip(k) {
let diag_val = matrix.get(actual_i, actual_i).abs();
if diag_val > max_val {
max_val = diag_val;
pivot_idx = i;
}
}
if max_val >= threshold {
pivot_idx
} else {
k }
}
#[allow(dead_code)]
fn extract_unit_lower_triangular<T>(
matrix: &SparseWorkingMatrix<T>,
perm: &[usize],
n: usize,
) -> (Vec<usize>, Vec<usize>, Vec<T>)
where
T: Float + SparseElement + Debug + Copy,
{
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for i in 0..n {
let actual_i = perm[i];
rows.push(i);
cols.push(i);
vals.push(T::sparse_one());
for (j, &perm_j) in perm.iter().enumerate().take(i) {
let val = matrix.get(actual_i, perm_j);
if val != T::sparse_zero() {
rows.push(i);
cols.push(j);
vals.push(val);
}
}
}
(rows, cols, vals)
}
#[allow(dead_code)]
pub fn incomplete_lu<T, S>(matrix: &S, options: Option<ILUOptions>) -> SparseResult<LUResult<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>,
S: SparseArray<T>,
{
let opts = options.unwrap_or_default();
let (n, m) = matrix.shape();
if n != m {
return Err(SparseError::ValueError(
"Matrix must be square for ILU decomposition".to_string(),
));
}
let (row_indices, col_indices, values) = matrix.find();
let mut working_matrix = SparseWorkingMatrix::from_triplets(
row_indices.as_slice().expect("Operation failed"),
col_indices.as_slice().expect("Operation failed"),
values.as_slice().expect("Operation failed"),
n,
);
for k in 0..n - 1 {
let pivot_val = working_matrix.get(k, k);
if pivot_val.abs() < T::from(1e-14).expect("Operation failed") {
continue; }
let col_k_entries = working_matrix.get_column_below_diagonal(k);
for &row_i in &col_k_entries {
let factor = working_matrix.get(row_i, k) / pivot_val;
if factor.abs() < T::from(opts.drop_tol).expect("Operation failed") {
working_matrix.set(row_i, k, T::sparse_zero());
continue;
}
working_matrix.set(row_i, k, factor);
let row_k_entries = working_matrix.get_row_after_column(k, k);
for (col_j, &val_kj) in &row_k_entries {
if working_matrix.has_entry(row_i, *col_j) {
let old_val = working_matrix.get(row_i, *col_j);
let new_val = old_val - factor * val_kj;
if new_val.abs() < T::from(opts.drop_tol).expect("Operation failed") {
working_matrix.set(row_i, *col_j, T::sparse_zero());
} else {
working_matrix.set(row_i, *col_j, new_val);
}
}
}
}
}
let identity_p: Vec<usize> = (0..n).collect();
let (l_rows, l_cols, l_vals, u_rows, u_cols, u_vals) =
extract_lu_factors(&working_matrix, &identity_p, n);
let l = CsrArray::from_triplets(&l_rows, &l_cols, &l_vals, (n, n), false)?;
let u = CsrArray::from_triplets(&u_rows, &u_cols, &u_vals, (n, n), false)?;
Ok(LUResult {
l,
u,
p: Array1::from_vec(identity_p),
success: true,
})
}
#[allow(dead_code)]
pub fn incomplete_cholesky<T, S>(
matrix: &S,
options: Option<ICOptions>,
) -> SparseResult<CholeskyResult<T>>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>,
S: SparseArray<T>,
{
let opts = options.unwrap_or_default();
let (n, m) = matrix.shape();
if n != m {
return Err(SparseError::ValueError(
"Matrix must be square for IC decomposition".to_string(),
));
}
let (row_indices, col_indices, values) = matrix.find();
let mut working_matrix = SparseWorkingMatrix::from_triplets(
row_indices.as_slice().expect("Operation failed"),
col_indices.as_slice().expect("Operation failed"),
values.as_slice().expect("Operation failed"),
n,
);
for k in 0..n {
let mut sum = T::sparse_zero();
let row_k_before_k = working_matrix.get_row_before_column(k, k);
for &val_kj in row_k_before_k.values() {
sum = sum + val_kj * val_kj;
}
let a_kk = working_matrix.get(k, k);
let diag_val = a_kk - sum;
if diag_val <= T::sparse_zero() {
return Ok(CholeskyResult {
l: CsrArray::from_triplets(&[], &[], &[], (n, n), false)?,
success: false,
});
}
let l_kk = diag_val.sqrt();
working_matrix.set(k, k, l_kk);
let col_k_below = working_matrix.get_column_below_diagonal(k);
for &row_i in &col_k_below {
let mut sum = T::sparse_zero();
let row_i_before_k = working_matrix.get_row_before_column(row_i, k);
let row_k_before_k = working_matrix.get_row_before_column(k, k);
for (col_j, &val_ij) in &row_i_before_k {
if let Some(&val_kj) = row_k_before_k.get(col_j) {
sum = sum + val_ij * val_kj;
}
}
let a_ik = working_matrix.get(row_i, k);
let l_ik = (a_ik - sum) / l_kk;
if l_ik.abs() < T::from(opts.drop_tol).expect("Operation failed") {
working_matrix.set(row_i, k, T::sparse_zero());
} else {
working_matrix.set(row_i, k, l_ik);
}
}
}
let (l_rows, l_cols, l_vals) = extract_lower_triangular(&working_matrix, n);
let l = CsrArray::from_triplets(&l_rows, &l_cols, &l_vals, (n, n), false)?;
Ok(CholeskyResult { l, success: true })
}
struct SparseWorkingMatrix<T>
where
T: Float + SparseElement + Debug + Copy,
{
data: HashMap<(usize, usize), T>,
n: usize,
}
impl<T> SparseWorkingMatrix<T>
where
T: Float
+ SparseElement
+ Debug
+ Copy
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>,
{
fn from_triplets(rows: &[usize], cols: &[usize], values: &[T], n: usize) -> Self {
let mut data = HashMap::new();
for (i, (&row, &col)) in rows.iter().zip(cols.iter()).enumerate() {
data.insert((row, col), values[i]);
}
Self { data, n }
}
fn get(&self, row: usize, col: usize) -> T {
self.data
.get(&(row, col))
.copied()
.unwrap_or(T::sparse_zero())
}
fn set(&mut self, row: usize, col: usize, value: T) {
if SparseElement::is_zero(&value) {
self.data.remove(&(row, col));
} else {
self.data.insert((row, col), value);
}
}
fn has_entry(&self, row: usize, col: usize) -> bool {
self.data.contains_key(&(row, col))
}
fn get_row(&self, row: usize) -> HashMap<usize, T> {
let mut result = HashMap::new();
for (&(r, c), &value) in &self.data {
if r == row {
result.insert(c, value);
}
}
result
}
fn get_row_after_column(&self, row: usize, col: usize) -> HashMap<usize, T> {
let mut result = HashMap::new();
for (&(r, c), &value) in &self.data {
if r == row && c > col {
result.insert(c, value);
}
}
result
}
fn get_row_before_column(&self, row: usize, col: usize) -> HashMap<usize, T> {
let mut result = HashMap::new();
for (&(r, c), &value) in &self.data {
if r == row && c < col {
result.insert(c, value);
}
}
result
}
fn get_column_below_diagonal(&self, col: usize) -> Vec<usize> {
let mut result = Vec::new();
for &(r, c) in self.data.keys() {
if c == col && r > col {
result.push(r);
}
}
result.sort();
result
}
}
#[allow(dead_code)]
fn find_pivot<T>(
matrix: &SparseWorkingMatrix<T>,
k: usize,
p: &[usize],
threshold: f64,
) -> SparseResult<usize>
where
T: Float + SparseElement + Debug + Copy,
{
let opts = LUOptions {
pivoting: PivotingStrategy::Threshold(threshold),
zero_threshold: 1e-14,
check_singular: true,
};
let row_scales = vec![T::sparse_one(); matrix.n];
let col_perm: Vec<usize> = (0..matrix.n).collect();
let (pivot_row, pivot_col) = find_enhanced_pivot(matrix, k, p, &col_perm, &row_scales, &opts)?;
Ok(pivot_row)
}
#[allow(dead_code)]
fn find_enhanced_pivot<T>(
matrix: &SparseWorkingMatrix<T>,
k: usize,
row_perm: &[usize],
col_perm: &[usize],
row_scales: &[T],
opts: &LUOptions,
) -> SparseResult<(usize, usize)>
where
T: Float + SparseElement + Debug + Copy,
{
let n = matrix.n;
match &opts.pivoting {
PivotingStrategy::None => {
Ok((k, k))
}
PivotingStrategy::Partial => {
let mut max_val = T::sparse_zero();
let mut pivot_row = k;
for (idx, &actual_row) in row_perm.iter().enumerate().skip(k).take(n - k) {
let i = k + idx;
let val = matrix.get(actual_row, col_perm[k]).abs();
if val > max_val {
max_val = val;
pivot_row = i;
}
}
Ok((pivot_row, k))
}
PivotingStrategy::Threshold(threshold) => {
let threshold_val = T::from(*threshold).expect("Operation failed");
let mut max_val = T::sparse_zero();
let mut pivot_row = k;
for (idx, &actual_row) in row_perm.iter().enumerate().skip(k).take(n - k) {
let i = k + idx;
let val = matrix.get(actual_row, col_perm[k]).abs();
if val > max_val {
max_val = val;
pivot_row = i;
}
if val >= threshold_val {
pivot_row = i;
break;
}
}
Ok((pivot_row, k))
}
PivotingStrategy::ScaledPartial => {
let mut max_ratio = T::sparse_zero();
let mut pivot_row = k;
for (idx, &actual_row) in row_perm.iter().enumerate().skip(k).take(n - k) {
let i = k + idx;
let val = matrix.get(actual_row, col_perm[k]).abs();
let scale = row_scales[actual_row];
let ratio = if scale > T::sparse_zero() {
val / scale
} else {
val
};
if ratio > max_ratio {
max_ratio = ratio;
pivot_row = i;
}
}
Ok((pivot_row, k))
}
PivotingStrategy::Complete => {
let mut max_val = T::sparse_zero();
let mut pivot_row = k;
let mut pivot_col = k;
for (i_idx, &actual_row) in row_perm.iter().enumerate().skip(k).take(n - k) {
let i = k + i_idx;
for (j_idx, &actual_col) in col_perm.iter().enumerate().skip(k).take(n - k) {
let j = k + j_idx;
let val = matrix.get(actual_row, actual_col).abs();
if val > max_val {
max_val = val;
pivot_row = i;
pivot_col = j;
}
}
}
Ok((pivot_row, pivot_col))
}
PivotingStrategy::Rook => {
let mut best_row = k;
let mut best_col = k;
let mut max_val = T::sparse_zero();
for (idx, &actual_row) in row_perm.iter().enumerate().skip(k).take(n - k) {
let i = k + idx;
let val = matrix.get(actual_row, col_perm[k]).abs();
if val > max_val {
max_val = val;
best_row = i;
}
}
if max_val > T::from(opts.zero_threshold).expect("Operation failed") {
let actual_best_row = row_perm[best_row];
let mut col_max = T::sparse_zero();
for (idx, &actual_col) in col_perm.iter().enumerate().skip(k).take(n - k) {
let j = k + idx;
let val = matrix.get(actual_best_row, actual_col).abs();
if val > col_max {
col_max = val;
best_col = j;
}
}
let improvement_threshold = T::from(1.5).expect("Operation failed");
if col_max > max_val * improvement_threshold {
max_val = T::sparse_zero();
for (idx, &actual_row) in row_perm.iter().enumerate().skip(k).take(n - k) {
let i = k + idx;
let val = matrix.get(actual_row, col_perm[best_col]).abs();
if val > max_val {
max_val = val;
best_row = i;
}
}
}
}
Ok((best_row, best_col))
}
}
}
type LuFactors<T> = (
Vec<usize>, Vec<usize>, Vec<T>, Vec<usize>, Vec<usize>, Vec<T>, );
#[allow(dead_code)]
fn extract_lu_factors<T>(matrix: &SparseWorkingMatrix<T>, p: &[usize], n: usize) -> LuFactors<T>
where
T: Float + SparseElement + Debug + Copy,
{
let mut l_rows = Vec::new();
let mut l_cols = Vec::new();
let mut l_vals = Vec::new();
let mut u_rows = Vec::new();
let mut u_cols = Vec::new();
let mut u_vals = Vec::new();
#[allow(clippy::needless_range_loop)]
for i in 0..n {
let actual_row = p[i];
l_rows.push(i);
l_cols.push(i);
l_vals.push(T::sparse_one());
for j in 0..n {
let val = matrix.get(actual_row, j);
if !SparseElement::is_zero(&val) {
if j < i {
l_rows.push(i);
l_cols.push(j);
l_vals.push(val);
} else {
u_rows.push(i);
u_cols.push(j);
u_vals.push(val);
}
}
}
}
(l_rows, l_cols, l_vals, u_rows, u_cols, u_vals)
}
#[allow(dead_code)]
fn extract_lower_triangular<T>(
matrix: &SparseWorkingMatrix<T>,
n: usize,
) -> (Vec<usize>, Vec<usize>, Vec<T>)
where
T: Float + SparseElement + Debug + Copy,
{
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for i in 0..n {
for j in 0..=i {
let val = matrix.get(i, j);
if !SparseElement::is_zero(&val) {
rows.push(i);
cols.push(j);
vals.push(val);
}
}
}
(rows, cols, vals)
}
#[allow(dead_code)]
fn dense_to_sparse<T>(matrix: &Array2<T>) -> SparseResult<CsrArray<T>>
where
T: Float + SparseElement + Debug + Copy,
{
let (m, n) = matrix.dim();
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for i in 0..m {
for j in 0..n {
let val = matrix[[i, j]];
if !SparseElement::is_zero(&val) {
rows.push(i);
cols.push(j);
vals.push(val);
}
}
}
CsrArray::from_triplets(&rows, &cols, &vals, (m, n), false)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::csr_array::CsrArray;
fn create_test_matrix() -> CsrArray<f64> {
let rows = vec![0, 0, 1, 1, 2, 2];
let cols = vec![0, 1, 0, 1, 1, 2];
let data = vec![2.0, 1.0, 1.0, 3.0, 2.0, 4.0];
CsrArray::from_triplets(&rows, &cols, &data, (3, 3), false).expect("Operation failed")
}
fn create_spd_matrix() -> CsrArray<f64> {
let rows = vec![0, 1, 1, 2, 2, 2];
let cols = vec![0, 0, 1, 0, 1, 2];
let data = vec![4.0, 2.0, 5.0, 1.0, 3.0, 6.0];
CsrArray::from_triplets(&rows, &cols, &data, (3, 3), false).expect("Operation failed")
}
#[test]
fn test_lu_decomposition() {
let matrix = create_test_matrix();
let lu_result = lu_decomposition(&matrix, 0.1).expect("Operation failed");
assert!(lu_result.success);
assert_eq!(lu_result.l.shape(), (3, 3));
assert_eq!(lu_result.u.shape(), (3, 3));
assert_eq!(lu_result.p.len(), 3);
}
#[test]
fn test_qr_decomposition() {
let rows = vec![0, 1, 2];
let cols = vec![0, 0, 1];
let data = vec![1.0, 2.0, 3.0];
let matrix =
CsrArray::from_triplets(&rows, &cols, &data, (3, 2), false).expect("Operation failed");
let qr_result = qr_decomposition(&matrix).expect("Operation failed");
assert!(qr_result.success);
assert_eq!(qr_result.q.shape(), (3, 2));
assert_eq!(qr_result.r.shape(), (2, 2));
}
#[test]
fn test_cholesky_decomposition() {
let matrix = create_spd_matrix();
let chol_result = cholesky_decomposition(&matrix).expect("Operation failed");
assert!(chol_result.success);
assert_eq!(chol_result.l.shape(), (3, 3));
}
#[test]
fn test_incomplete_lu() {
let matrix = create_test_matrix();
let options = ILUOptions {
drop_tol: 1e-6,
..Default::default()
};
let ilu_result = incomplete_lu(&matrix, Some(options)).expect("Operation failed");
assert!(ilu_result.success);
assert_eq!(ilu_result.l.shape(), (3, 3));
assert_eq!(ilu_result.u.shape(), (3, 3));
}
#[test]
fn test_incomplete_cholesky() {
let matrix = create_spd_matrix();
let options = ICOptions {
drop_tol: 1e-6,
..Default::default()
};
let ic_result = incomplete_cholesky(&matrix, Some(options)).expect("Operation failed");
assert!(ic_result.success);
assert_eq!(ic_result.l.shape(), (3, 3));
}
#[test]
fn test_sparse_working_matrix() {
let rows = vec![0, 1, 2];
let cols = vec![0, 1, 2];
let vals = vec![1.0, 2.0, 3.0];
let mut matrix = SparseWorkingMatrix::from_triplets(&rows, &cols, &vals, 3);
assert_eq!(matrix.get(0, 0), 1.0);
assert_eq!(matrix.get(1, 1), 2.0);
assert_eq!(matrix.get(2, 2), 3.0);
assert_eq!(matrix.get(0, 1), 0.0);
matrix.set(0, 1, 5.0);
assert_eq!(matrix.get(0, 1), 5.0);
matrix.set(0, 1, 0.0);
assert_eq!(matrix.get(0, 1), 0.0);
assert!(!matrix.has_entry(0, 1));
}
#[test]
fn test_dense_to_sparse_conversion() {
let dense =
Array2::from_shape_vec((2, 2), vec![1.0, 0.0, 2.0, 3.0]).expect("Operation failed");
let sparse = dense_to_sparse(&dense).expect("Operation failed");
assert_eq!(sparse.nnz(), 3);
assert_eq!(sparse.get(0, 0), 1.0);
assert_eq!(sparse.get(0, 1), 0.0);
assert_eq!(sparse.get(1, 0), 2.0);
assert_eq!(sparse.get(1, 1), 3.0);
}
}