use nalgebra::DMatrix;
use nalgebra_block_triangularization::{
upper_block_triangular_structure, upper_triangular_permutations,
};
use proptest::prelude::*;
fn arbitrary_matrix(
max_rows: usize,
max_cols: usize,
) -> impl Strategy<Value = (usize, usize, DMatrix<u8>)> {
(1..=max_rows, 1..=max_cols).prop_flat_map(|(nrows, ncols)| {
let total = nrows * ncols;
(
Just(nrows),
Just(ncols),
prop::collection::vec(any::<u8>(), total).prop_map(move |bits| {
let data: Vec<u8> = bits.into_iter().map(|b| b % 2).collect();
DMatrix::from_row_slice(nrows, ncols, &data)
}),
)
})
}
proptest! {
#[test]
fn output_orders_are_permutations(
bits in prop::collection::vec(any::<u8>(), 1..400)
) {
let n = (bits.len() as f64).sqrt() as usize;
if n < 1 {
return Ok(());
}
let bits: Vec<u8> = bits.into_iter().take(n * n).map(|b| b % 2).collect();
let m = DMatrix::from_row_slice(n, n, &bits);
let structure = upper_block_triangular_structure(&m);
let mut sorted_rows = structure.row_order.clone();
sorted_rows.sort_unstable();
prop_assert_eq!(sorted_rows, (0..n).collect::<Vec<_>>());
let mut sorted_cols = structure.col_order.clone();
sorted_cols.sort_unstable();
prop_assert_eq!(sorted_cols, (0..n).collect::<Vec<_>>());
}
#[test]
fn block_sizes_sum_correctly(
bits in prop::collection::vec(any::<u8>(), 1..400)
) {
let n = (bits.len() as f64).sqrt() as usize;
if n < 1 {
return Ok(());
}
let bits: Vec<u8> = bits.into_iter().take(n * n).map(|b| b % 2).collect();
let m = DMatrix::from_row_slice(n, n, &bits);
let structure = upper_block_triangular_structure(&m);
let sum: usize = structure.block_sizes.iter().sum();
prop_assert_eq!(sum, n);
}
#[test]
fn matching_size_bounded_in_btf(
bits in prop::collection::vec(any::<u8>(), 1..600)
) {
let total = bits.len();
let nrows = ((total as f64) / 1.5).sqrt() as usize;
let ncols = if nrows > 0 { total / nrows } else { 0 };
if nrows < 1 || ncols < 1 {
return Ok(());
}
let bits: Vec<u8> = bits.into_iter().take(nrows * ncols).map(|b| b % 2).collect();
let m = DMatrix::from_row_slice(nrows, ncols, &bits);
let structure = upper_block_triangular_structure(&m);
prop_assert!(structure.matching_size <= nrows.min(ncols));
}
#[test]
fn permutations_valid_rectangular((nrows, ncols, m) in arbitrary_matrix(20, 20)) {
let structure = upper_block_triangular_structure(&m);
prop_assert_eq!(structure.row_order.len(), nrows, "Row order has wrong length");
prop_assert_eq!(structure.col_order.len(), ncols, "Column order has wrong length");
let (pr, pc) = upper_triangular_permutations(&m);
let mut u = m.clone();
pr.permute_rows(&mut u);
pc.permute_columns(&mut u);
prop_assert_eq!(u.nrows(), nrows);
prop_assert_eq!(u.ncols(), ncols);
}
#[test]
fn permutations_preserve_dimensions((nrows, ncols, m) in arbitrary_matrix(20, 20)) {
let (pr, pc) = upper_triangular_permutations(&m);
let mut u = m.clone();
pr.permute_rows(&mut u);
pc.permute_columns(&mut u);
prop_assert_eq!(u.nrows(), nrows, "Permuted matrix has wrong row count");
prop_assert_eq!(u.ncols(), ncols, "Permuted matrix has wrong column count");
}
#[test]
fn block_structure_reasonable(
bits in prop::collection::vec(any::<u8>(), 1..400)
) {
let n = (bits.len() as f64).sqrt() as usize;
if n < 1 {
return Ok(());
}
let bits: Vec<u8> = bits.into_iter().take(n * n).map(|b| b % 2).collect();
let m = DMatrix::from_row_slice(n, n, &bits);
let structure = upper_block_triangular_structure(&m);
for &size in &structure.block_sizes {
prop_assert!(size > 0, "Block has zero size");
}
prop_assert!(
structure.block_sizes.len() <= n,
"More blocks than rows"
);
}
#[test]
fn structure_is_deterministic((_nrows, _ncols, m) in arbitrary_matrix(15, 15)) {
let structure1 = upper_block_triangular_structure(&m);
let structure2 = upper_block_triangular_structure(&m);
prop_assert_eq!(structure1.row_order, structure2.row_order, "Row order not deterministic");
prop_assert_eq!(structure1.col_order, structure2.col_order, "Column order not deterministic");
prop_assert_eq!(structure1.block_sizes, structure2.block_sizes, "Block sizes not deterministic");
prop_assert_eq!(structure1.matching_size, structure2.matching_size, "Matching size not deterministic");
}
#[test]
fn handles_zero_matrix(n in 1..20usize) {
let m = DMatrix::<u8>::zeros(n, n);
let structure = upper_block_triangular_structure(&m);
prop_assert_eq!(structure.row_order.len(), n);
prop_assert_eq!(structure.col_order.len(), n);
let mut sorted_rows = structure.row_order.clone();
sorted_rows.sort_unstable();
prop_assert_eq!(sorted_rows, (0..n).collect::<Vec<_>>());
prop_assert_eq!(structure.matching_size, 0, "Zero matrix should have zero matching");
}
#[test]
fn identity_matrix_single_block(n in 1..20usize) {
let m = DMatrix::<u8>::identity(n, n);
let structure = upper_block_triangular_structure(&m);
prop_assert_eq!(structure.matching_size, n, "Identity should have perfect matching");
let sum: usize = structure.block_sizes.iter().sum();
prop_assert_eq!(sum, n);
}
#[test]
fn matching_quality((nrows, ncols, m) in arbitrary_matrix(20, 20)) {
let structure = upper_block_triangular_structure(&m);
prop_assert!(structure.matching_size <= nrows.min(ncols));
if structure.matching_size > 0 {
prop_assert!(!structure.block_sizes.is_empty(), "Positive matching but no blocks");
}
}
}