#![forbid(unsafe_code)]
use approx::assert_abs_diff_eq;
use pastey::paste;
use proptest::{array, prelude::*};
use la_stack::prelude::*;
#[path = "common/proptest_config.rs"]
mod proptest_config;
use proptest_config::with_default_cases;
fn small_f64() -> impl Strategy<Value = f64> {
(-1000i16..=1000i16).prop_map(|x| f64::from(x) / 10.0)
}
fn small_nonzero_f64() -> impl Strategy<Value = f64> {
prop_oneof![(-1000i16..=-1i16), (1i16..=1000i16)].prop_map(|x| f64::from(x) / 10.0)
}
macro_rules! gen_matrix_proptests {
($d:literal) => {
paste! {
proptest! {
#![proptest_config(with_default_cases(64))]
#[test]
fn [<matrix_try_from_rows_views_and_get_roundtrip_ $d d>](
rows in array::[<uniform $d>](
array::[<uniform $d>](small_f64()),
),
) {
let m = Matrix::<$d>::try_from_rows(rows).unwrap();
prop_assert_eq!(m.as_rows(), &rows);
for r in 0..$d {
for c in 0..$d {
assert_abs_diff_eq!(m.get(r, c).unwrap(), rows[r][c], epsilon = 0.0);
assert_abs_diff_eq!(m.try_get(r, c).unwrap(), rows[r][c], epsilon = 0.0);
}
}
prop_assert_eq!(m.get($d, 0), None);
prop_assert_eq!(m.get(0, $d), None);
let row_out_of_bounds = matches!(
m.try_get($d, 0),
Err(LaError::IndexOutOfBounds {
row,
col,
dim,
..
})
if row == $d && col == 0 && dim == $d
);
prop_assert!(row_out_of_bounds);
let col_out_of_bounds = matches!(
m.try_get(0, $d),
Err(LaError::IndexOutOfBounds {
row,
col,
dim,
..
})
if row == 0 && col == $d && dim == $d
);
prop_assert!(col_out_of_bounds);
prop_assert_eq!(m.into_rows(), rows);
}
#[test]
fn [<matrix_set_get_in_bounds_ $d d>](
rows in array::[<uniform $d>](array::[<uniform $d>](small_f64())),
r in 0usize..$d,
c in 0usize..$d,
v in small_f64(),
) {
let mut m = Matrix::<$d>::try_from_rows(rows).unwrap();
let mut expected = rows;
prop_assert_eq!(m.set(r, c, v), Ok(()));
expected[r][c] = v;
prop_assert_eq!(m.as_rows(), &expected);
assert_abs_diff_eq!(m.get(r, c).unwrap(), v, epsilon = 0.0);
prop_assert_eq!(m.set(r, c, -v), Ok(()));
expected[r][c] = -v;
prop_assert_eq!(m.as_rows(), &expected);
assert_abs_diff_eq!(m.try_get(r, c).unwrap(), -v, epsilon = 0.0);
}
#[test]
fn [<matrix_set_out_of_bounds_preserves_matrix_ $d d>](
rows in array::[<uniform $d>](
array::[<uniform $d>](small_f64()),
),
v in small_f64(),
) {
let mut m = Matrix::<$d>::try_from_rows(rows).unwrap();
let original = m;
let row_out_of_bounds = matches!(
m.set($d, 0, v),
Err(LaError::IndexOutOfBounds {
row,
col,
dim,
..
})
if row == $d && col == 0 && dim == $d
);
prop_assert!(row_out_of_bounds);
prop_assert_eq!(m, original);
let col_out_of_bounds = matches!(
m.set(0, $d, v),
Err(LaError::IndexOutOfBounds {
row,
col,
dim,
..
})
if row == 0 && col == $d && dim == $d
);
prop_assert!(col_out_of_bounds);
prop_assert_eq!(m, original);
}
#[test]
fn [<matrix_norm_inf_matches_max_abs_row_sum_ $d d>](
rows in array::[<uniform $d>](
array::[<uniform $d>](small_f64()),
),
) {
let m = Matrix::<$d>::try_from_rows(rows).unwrap();
let expected = rows
.iter()
.map(|row| row.iter().map(|&x| x.abs()).sum::<f64>())
.fold(0.0f64, f64::max);
let actual = m.norm_inf().unwrap();
assert_abs_diff_eq!(actual, expected, epsilon = 0.0);
prop_assert!(actual >= 0.0);
}
#[test]
fn [<matrix_det_and_solve_for_diagonal_ $d d>](
diag in array::[<uniform $d>](small_nonzero_f64()),
b_arr in array::[<uniform $d>](small_f64()),
) {
let mut rows = [[0.0f64; $d]; $d];
for i in 0..$d {
rows[i][i] = diag[i];
}
let a = Matrix::<$d>::try_from_rows(rows).unwrap();
let det = a.det().unwrap();
let expected_det = {
let mut acc = 1.0;
for i in 0..$d {
acc *= diag[i];
}
acc
};
let eps = expected_det.abs().mul_add(1e-12, 1e-12);
assert_abs_diff_eq!(det, expected_det, epsilon = eps);
let lu = a.lu(DEFAULT_SINGULAR_TOL).unwrap();
let b = Vector::<$d>::try_new(b_arr).unwrap();
let x = lu.solve(b).unwrap().into_array();
for i in 0..$d {
let expected_x = b_arr[i] / diag[i];
assert_abs_diff_eq!(x[i], expected_x, epsilon = 1e-12);
}
}
}
}
};
}
gen_matrix_proptests!(1);
gen_matrix_proptests!(2);
gen_matrix_proptests!(3);
gen_matrix_proptests!(4);
gen_matrix_proptests!(5);
#[test]
fn zero_dimension_matrix_obeys_empty_product_and_bounds_contracts() {
let mut matrix = Matrix::<0>::try_from_rows([]).unwrap();
assert!(matrix.as_rows().is_empty());
assert_eq!(matrix.get(0, 0), None);
assert!(matches!(
matrix.try_get(0, 0),
Err(LaError::IndexOutOfBounds {
row: 0,
col: 0,
dim: 0,
..
})
));
assert!(matches!(
matrix.set(0, 0, 1.0),
Err(LaError::IndexOutOfBounds {
row: 0,
col: 0,
dim: 0,
..
})
));
assert_eq!(matrix.norm_inf(), Ok(0.0));
assert_eq!(matrix.det(), Ok(1.0));
assert!(
matrix
.lu(DEFAULT_SINGULAR_TOL)
.unwrap()
.solve(Vector::<0>::zero())
.unwrap()
.into_array()
.is_empty()
);
}