use crate::scalar::Scalar;
use super::{CscMatrix, CsrMatrix};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidateError {
LengthMismatch,
PtrLengthMismatch,
PtrInvalid,
IndexOutOfBounds,
UnsortedIndices,
DuplicateIndex,
ExplicitZero,
}
pub fn validate_csr<T: Scalar>(m: &CsrMatrix<T>) -> Result<(), ValidateError> {
validate_compressed(m.rows(), m.cols(), m.row_ptr(), m.col_indices(), m.values())
}
pub fn validate_csc<T: Scalar>(m: &CscMatrix<T>) -> Result<(), ValidateError> {
validate_compressed(m.cols(), m.rows(), m.col_ptr(), m.row_indices(), m.values())
}
fn validate_compressed<T: Scalar>(
outer: usize,
inner: usize,
ptr: &[u32],
indices: &[u32],
values: &[T],
) -> Result<(), ValidateError> {
if indices.len() != values.len() {
return Err(ValidateError::LengthMismatch);
}
if ptr.len() != outer + 1 {
return Err(ValidateError::PtrLengthMismatch);
}
let nnz = indices.len();
if ptr[0] != 0 || ptr[outer] as usize != nnz {
return Err(ValidateError::PtrInvalid);
}
for i in 1..=outer {
if ptr[i] < ptr[i - 1] {
return Err(ValidateError::PtrInvalid);
}
}
for &idx in indices {
if idx as usize >= inner {
return Err(ValidateError::IndexOutOfBounds);
}
}
for s in 0..outer {
for k in ptr[s] as usize + 1..ptr[s + 1] as usize {
if indices[k] == indices[k - 1] {
return Err(ValidateError::DuplicateIndex);
}
if indices[k] < indices[k - 1] {
return Err(ValidateError::UnsortedIndices);
}
}
}
for v in values {
if *v == T::zero() {
return Err(ValidateError::ExplicitZero);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{ValidateError, validate_csr};
use crate::sparse::CsrMatrix;
use alloc::vec;
#[test]
fn canonical_matrix_passes() {
let m =
CsrMatrix::new(2, 3, vec![0, 2, 3], vec![0, 2, 1], vec![1.0_f64, 2.0, 3.0]).unwrap();
assert_eq!(validate_csr(&m), Ok(()));
}
#[test]
fn empty_matrix_passes() {
let m = CsrMatrix::<f64>::new(3, 4, vec![0, 0, 0, 0], vec![], vec![]).unwrap();
assert_eq!(validate_csr(&m), Ok(()));
}
#[test]
fn length_mismatch_detected() {
let m = CsrMatrix::new_raw(1, 2, vec![0, 2], vec![0, 1], vec![1.0_f64]);
assert_eq!(validate_csr(&m), Err(ValidateError::LengthMismatch));
}
#[test]
fn ptr_length_mismatch_detected() {
let m = CsrMatrix::new_raw(2, 2, vec![0, 1], vec![1], vec![1.0_f64]);
assert_eq!(validate_csr(&m), Err(ValidateError::PtrLengthMismatch));
}
#[test]
fn ptr_not_starting_at_zero_detected() {
let m = CsrMatrix::new_raw(1, 2, vec![1, 1], vec![0], vec![1.0_f64]);
assert_eq!(validate_csr(&m), Err(ValidateError::PtrInvalid));
}
#[test]
fn ptr_not_monotone_detected() {
let m = CsrMatrix::new_raw(2, 3, vec![0, 2, 1], vec![0, 1], vec![1.0_f64, 2.0]);
assert_eq!(validate_csr(&m), Err(ValidateError::PtrInvalid));
}
#[test]
fn ptr_last_entry_mismatch_detected() {
let m = CsrMatrix::new_raw(1, 2, vec![0, 5], vec![0, 1], vec![1.0_f64, 2.0]);
assert_eq!(validate_csr(&m), Err(ValidateError::PtrInvalid));
}
#[test]
fn index_out_of_bounds_detected() {
let m = CsrMatrix::new_raw(1, 2, vec![0, 2], vec![0, 3], vec![1.0_f64, 2.0]);
assert_eq!(validate_csr(&m), Err(ValidateError::IndexOutOfBounds));
}
#[test]
fn unsorted_indices_detected() {
let m = CsrMatrix::new(1, 3, vec![0, 2], vec![2, 0], vec![1.0_f64, 2.0]).unwrap();
assert_eq!(validate_csr(&m), Err(ValidateError::UnsortedIndices));
}
#[test]
fn duplicate_index_detected() {
let m = CsrMatrix::new(1, 3, vec![0, 2], vec![1, 1], vec![1.0_f64, 2.0]).unwrap();
assert_eq!(validate_csr(&m), Err(ValidateError::DuplicateIndex));
}
#[test]
fn duplicates_do_not_leak_across_row_boundaries() {
let m =
CsrMatrix::new(2, 2, vec![0, 2, 3], vec![0, 1, 1], vec![1.0_f64, 2.0, 3.0]).unwrap();
assert_eq!(validate_csr(&m), Ok(()));
}
#[test]
fn explicit_zero_detected() {
let m = CsrMatrix::new(1, 2, vec![0, 2], vec![0, 1], vec![1.0_f64, 0.0]).unwrap();
assert_eq!(validate_csr(&m), Err(ValidateError::ExplicitZero));
}
#[test]
fn nan_entry_is_not_reported_as_zero() {
let m = CsrMatrix::new(1, 1, vec![0, 1], vec![0], vec![f64::NAN]).unwrap();
assert_eq!(validate_csr(&m), Ok(()));
}
}
#[cfg(test)]
mod tests_csc {
use super::{ValidateError, validate_csc};
use crate::sparse::CscMatrix;
use alloc::vec;
#[test]
fn csc_canonical_matrix_passes() {
let m =
CscMatrix::new(3, 2, vec![0, 2, 3], vec![0, 2, 1], vec![1.0_f64, 2.0, 3.0]).unwrap();
assert_eq!(validate_csc(&m), Ok(()));
}
#[test]
fn csc_length_mismatch_detected() {
let m = CscMatrix::new_raw(2, 1, vec![0, 2], vec![0, 1], vec![1.0_f64]);
assert_eq!(validate_csc(&m), Err(ValidateError::LengthMismatch));
}
#[test]
fn csc_ptr_length_mismatch_detected() {
let m = CscMatrix::new_raw(2, 2, vec![0, 1], vec![1], vec![1.0_f64]);
assert_eq!(validate_csc(&m), Err(ValidateError::PtrLengthMismatch));
}
#[test]
fn csc_ptr_invalid_detected() {
let m = CscMatrix::new_raw(3, 2, vec![0, 2, 1], vec![0, 1], vec![1.0_f64, 2.0]);
assert_eq!(validate_csc(&m), Err(ValidateError::PtrInvalid));
}
#[test]
fn csc_index_out_of_bounds_detected() {
let m = CscMatrix::new_raw(2, 1, vec![0, 2], vec![0, 3], vec![1.0_f64, 2.0]);
assert_eq!(validate_csc(&m), Err(ValidateError::IndexOutOfBounds));
}
#[test]
fn csc_unsorted_indices_detected() {
let m = CscMatrix::new(3, 1, vec![0, 2], vec![2, 0], vec![1.0_f64, 2.0]).unwrap();
assert_eq!(validate_csc(&m), Err(ValidateError::UnsortedIndices));
}
#[test]
fn csc_duplicate_index_detected() {
let m = CscMatrix::new(3, 1, vec![0, 2], vec![1, 1], vec![1.0_f64, 2.0]).unwrap();
assert_eq!(validate_csc(&m), Err(ValidateError::DuplicateIndex));
}
#[test]
fn csc_explicit_zero_detected() {
let m = CscMatrix::new(2, 1, vec![0, 2], vec![0, 1], vec![1.0_f64, 0.0]).unwrap();
assert_eq!(validate_csc(&m), Err(ValidateError::ExplicitZero));
}
}