#![forbid(unsafe_code)]
#![deny(missing_docs)]
pub mod quotient_graph;
use core::fmt;
pub const CONTRACT_VERSION: u32 = 1;
#[derive(Debug, Clone, Copy)]
pub struct CscPattern<'a> {
pub n: usize,
pub col_ptr: &'a [i32],
pub row_idx: &'a [i32],
}
impl<'a> CscPattern<'a> {
pub fn new(n: usize, col_ptr: &'a [i32], row_idx: &'a [i32]) -> Option<Self> {
if col_ptr.len() != n + 1 {
return None;
}
if col_ptr[0] != 0 {
return None;
}
let nnz_i32 = *col_ptr.last()?;
if nnz_i32 < 0 {
return None;
}
let nnz = nnz_i32 as usize;
if row_idx.len() != nnz {
return None;
}
for w in col_ptr.windows(2) {
if w[1] < w[0] {
return None;
}
}
let n_i32: i32 = match i32::try_from(n) {
Ok(v) => v,
Err(_) => return None,
};
for &r in row_idx {
if r < 0 || r >= n_i32 {
return None;
}
}
for w in col_ptr.windows(2) {
let lo = w[0] as usize;
let hi = w[1] as usize;
if row_idx[lo..hi].windows(2).any(|p| p[1] < p[0]) {
return None;
}
}
Some(Self {
n,
col_ptr,
row_idx,
})
}
pub fn nnz(&self) -> usize {
self.row_idx.len()
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct OrderingStats {
pub time_us: u64,
pub fill_estimate: Option<u64>,
pub flop_estimate: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OrderingError {
MalformedInput,
NonSymmetric,
IndexOverflow,
DisconnectedGraph,
Internal(&'static str),
}
impl fmt::Display for OrderingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MalformedInput => f.write_str("ordering input failed structural validation"),
Self::NonSymmetric => {
f.write_str("ordering input pattern was not structurally symmetric")
}
Self::IndexOverflow => f.write_str("ordering workspace exceeded i32::MAX"),
Self::DisconnectedGraph => f.write_str("ordering input graph is disconnected"),
Self::Internal(msg) => write!(f, "ordering internal error: {msg}"),
}
}
}
impl std::error::Error for OrderingError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn contract_version_is_one() {
assert_eq!(CONTRACT_VERSION, 1);
}
#[test]
fn empty_pattern_ok() {
let cp = [0i32];
let ri: [i32; 0] = [];
let p = CscPattern::new(0, &cp, &ri).expect("n=0 pattern");
assert_eq!(p.n, 0);
assert_eq!(p.nnz(), 0);
}
#[test]
fn diagonal_2x2_ok() {
let cp = [0i32, 1, 2];
let ri = [0i32, 1];
let p = CscPattern::new(2, &cp, &ri).unwrap();
assert_eq!(p.nnz(), 2);
}
#[test]
fn rejects_bad_col_ptr_length() {
let cp = [0i32, 1];
let ri = [0i32];
assert!(CscPattern::new(2, &cp, &ri).is_none());
}
#[test]
fn rejects_oob_row_index() {
let cp = [0i32, 1];
let ri = [5i32];
assert!(CscPattern::new(1, &cp, &ri).is_none());
}
#[test]
fn rejects_negative_row_index() {
let cp = [0i32, 1];
let ri = [-1i32];
assert!(CscPattern::new(1, &cp, &ri).is_none());
}
#[test]
fn rejects_nonzero_first_col_ptr() {
let cp = [1i32, 2];
let ri = [0i32];
assert!(CscPattern::new(1, &cp, &ri).is_none());
}
#[test]
fn rejects_nonmonotone_col_ptr() {
let cp = [0i32, 2, 1];
let ri = [0i32, 0];
assert!(CscPattern::new(2, &cp, &ri).is_none());
}
#[test]
fn rejects_row_idx_length_mismatch() {
let cp = [0i32, 1, 2];
let ri = [0i32];
assert!(CscPattern::new(2, &cp, &ri).is_none());
}
#[test]
fn rejects_unsorted_rows_within_column() {
let cp = [0i32, 2, 2];
let ri = [1i32, 0];
assert!(CscPattern::new(2, &cp, &ri).is_none());
}
#[test]
fn accepts_sorted_rows_with_adjacent_duplicate() {
let cp = [0i32, 3, 3];
let ri = [0i32, 0, 1];
assert!(CscPattern::new(2, &cp, &ri).is_some());
}
#[test]
fn rejects_negative_col_ptr_tail() {
let cp = [0i32, -1];
let ri: [i32; 0] = [];
assert!(CscPattern::new(1, &cp, &ri).is_none());
}
#[test]
fn ordering_error_display_is_non_empty() {
for e in [
OrderingError::MalformedInput,
OrderingError::NonSymmetric,
OrderingError::IndexOverflow,
OrderingError::DisconnectedGraph,
OrderingError::Internal("boom"),
] {
assert!(!format!("{e}").is_empty());
}
}
#[test]
fn ordering_stats_default_is_none_fields() {
let s = OrderingStats::default();
assert_eq!(s.time_us, 0);
assert!(s.fill_estimate.is_none());
assert!(s.flop_estimate.is_none());
}
}