use num_complex::{Complex32, Complex64};
use super::*;
#[test]
fn checked_convert_follows_dtype_promotion_lattice() {
assert!(can_convert_dtype(DType::F32, DType::F64));
assert!(can_convert_dtype(DType::F64, DType::C64));
assert!(can_convert_dtype(DType::Bool, DType::I64));
assert!(!can_convert_dtype(DType::F64, DType::F32));
assert!(!can_convert_dtype(DType::F64, DType::I32));
assert!(!can_convert_dtype(DType::C64, DType::F64));
assert!(!can_convert_dtype(DType::I32, DType::Bool));
}
#[test]
fn validate_convert_dtype_reports_typed_error() {
let err = validate_convert_dtype("convert", DType::C64, DType::I32).unwrap_err();
assert!(matches!(
err,
Error::UnsupportedDTypeConversion {
op: "convert",
from: DType::C64,
to: DType::I32,
..
}
));
}
macro_rules! float_singular_tests {
($mod_name:ident, $t:ty) => {
mod $mod_name {
use super::*;
fn tensor(shape: Vec<usize>, data: Vec<$t>) -> TypedTensor<$t> {
TypedTensor::<$t>::from_vec_col_major(shape, data).unwrap()
}
#[test]
fn nonsquare_tall_nonsingular() {
let t = tensor(
vec![3, 2],
vec![
2.0 as $t, 1.0 as $t, 0.0 as $t, 0.0 as $t, 5.0 as $t, 4.0 as $t,
],
);
assert!(check_singular_diagonal(&t).is_ok());
}
#[test]
fn nonsquare_tall_singular() {
let t = tensor(
vec![3, 2],
vec![
0.0 as $t, 1.0 as $t, 0.0 as $t, 0.0 as $t, 0.0 as $t, 4.0 as $t,
],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn nonsquare_wide_nonsingular() {
let t = tensor(
vec![2, 3],
vec![
2.0 as $t, 0.0 as $t, 1.0 as $t, 3.0 as $t, 0.0 as $t, 4.0 as $t,
],
);
assert!(check_singular_diagonal(&t).is_ok());
}
#[test]
fn nonsquare_wide_singular() {
let t = tensor(
vec![2, 3],
vec![
0.0 as $t, 0.0 as $t, 1.0 as $t, 3.0 as $t, 0.0 as $t, 4.0 as $t,
],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn zero_diagonal() {
let t = tensor(vec![2, 2], vec![0.0 as $t, 1.0 as $t, 1.0 as $t, 0.0 as $t]);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn nan_diagonal() {
let t = tensor(vec![2, 2], vec![<$t>::NAN, 1.0 as $t, 0.0 as $t, 1.0 as $t]);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn inf_diagonal() {
let t = tensor(
vec![2, 2],
vec![<$t>::INFINITY, 1.0 as $t, 0.0 as $t, 1.0 as $t],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn neg_inf_diagonal() {
let t = tensor(
vec![2, 2],
vec![<$t>::NEG_INFINITY, 1.0 as $t, 0.0 as $t, 1.0 as $t],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn single_element_singular() {
let t = tensor(vec![1, 1], vec![0.0 as $t]);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn single_element_nonsingular() {
let t = tensor(vec![1, 1], vec![5.0 as $t]);
assert!(check_singular_diagonal(&t).is_ok());
}
#[test]
fn batched_singular() {
let t = tensor(
vec![2, 2, 2],
vec![
1.0 as $t, 0.0 as $t, 0.0 as $t, 2.0 as $t, 0.0 as $t, 0.0 as $t,
0.0 as $t, 4.0 as $t,
],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn batched_nonsingular() {
let t = tensor(
vec![2, 2, 2],
vec![
1.0 as $t, 0.0 as $t, 0.0 as $t, 2.0 as $t, 3.0 as $t, 0.0 as $t,
0.0 as $t, 4.0 as $t,
],
);
assert!(check_singular_diagonal(&t).is_ok());
}
}
};
}
macro_rules! complex_singular_tests {
($mod_name:ident, $t:ty, $float:ty) => {
mod $mod_name {
use super::*;
fn tensor(shape: Vec<usize>, data: Vec<$t>) -> TypedTensor<$t> {
TypedTensor::<$t>::from_vec_col_major(shape, data).unwrap()
}
#[test]
fn nonsquare_tall_nonsingular() {
let t = tensor(
vec![3, 2],
vec![
<$t>::new(2.0 as $float, 0.0 as $float),
<$t>::new(1.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(5.0 as $float, 0.0 as $float),
<$t>::new(4.0 as $float, 0.0 as $float),
],
);
assert!(check_singular_diagonal(&t).is_ok());
}
#[test]
fn nonsquare_tall_singular() {
let t = tensor(
vec![3, 2],
vec![
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(1.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(4.0 as $float, 0.0 as $float),
],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn nonsquare_wide_nonsingular() {
let t = tensor(
vec![2, 3],
vec![
<$t>::new(2.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(1.0 as $float, 0.0 as $float),
<$t>::new(3.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(4.0 as $float, 0.0 as $float),
],
);
assert!(check_singular_diagonal(&t).is_ok());
}
#[test]
fn nonsquare_wide_singular() {
let t = tensor(
vec![2, 3],
vec![
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(1.0 as $float, 0.0 as $float),
<$t>::new(3.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(4.0 as $float, 0.0 as $float),
],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn nonsingular() {
let t = tensor(
vec![2, 2],
vec![
<$t>::new(2.0 as $float, 0.0 as $float),
<$t>::new(1.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(3.0 as $float, 0.0 as $float),
],
);
assert!(check_singular_diagonal(&t).is_ok());
}
#[test]
fn zero_diagonal() {
let t = tensor(
vec![2, 2],
vec![
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(1.0 as $float, 0.0 as $float),
<$t>::new(1.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn nan_diagonal() {
let t = tensor(
vec![2, 2],
vec![
<$t>::new(<$float>::NAN, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(1.0 as $float, 0.0 as $float),
],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn inf_diagonal() {
let t = tensor(
vec![2, 2],
vec![
<$t>::new(1.0 as $float, <$float>::INFINITY),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(1.0 as $float, 0.0 as $float),
],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn neg_inf_diagonal() {
let t = tensor(
vec![2, 2],
vec![
<$t>::new(<$float>::NEG_INFINITY, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(1.0 as $float, 0.0 as $float),
],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn single_element_singular() {
let t = tensor(vec![1, 1], vec![<$t>::new(0.0 as $float, 0.0 as $float)]);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn single_element_nonsingular() {
let t = tensor(vec![1, 1], vec![<$t>::new(5.0 as $float, 0.0 as $float)]);
assert!(check_singular_diagonal(&t).is_ok());
}
#[test]
fn batched_singular() {
let t = tensor(
vec![2, 2, 2],
vec![
<$t>::new(1.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(2.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(4.0 as $float, 0.0 as $float),
],
);
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn batched_nonsingular() {
let t = tensor(
vec![2, 2, 2],
vec![
<$t>::new(1.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(2.0 as $float, 0.0 as $float),
<$t>::new(3.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(0.0 as $float, 0.0 as $float),
<$t>::new(4.0 as $float, 0.0 as $float),
],
);
assert!(check_singular_diagonal(&t).is_ok());
}
}
};
}
float_singular_tests!(f32_tests, f32);
float_singular_tests!(f64_tests, f64);
complex_singular_tests!(c32_tests, Complex32, f32);
complex_singular_tests!(c64_tests, Complex64, f64);
#[test]
fn rank_less_than_two_returns_error_instead_of_panicking() {
for shape in [Vec::new(), vec![3]] {
let data = if shape.is_empty() {
vec![1.0]
} else {
vec![1.0, 2.0, 3.0]
};
let t = TypedTensor::<f64>::from_vec_col_major(shape.clone(), data).unwrap();
let err = check_singular_diagonal(&t).unwrap_err();
assert!(matches!(
err,
Error::RankMismatch {
op: "solve",
expected: 2,
actual
} if actual == shape.len()
));
}
}
#[test]
fn f64_batched_error_includes_batch_index_and_position() {
let t = TypedTensor::<f64>::from_vec_col_major(
vec![2, 2, 2],
vec![1.0, 0.0, 0.0, 2.0, 3.0, 0.0, 0.0, 0.0],
)
.unwrap();
let err = check_singular_diagonal(&t).unwrap_err();
let msg = match &err {
Error::BackendFailure { message, .. } => message.clone(),
_ => unreachable!(),
};
assert!(
msg.contains("batch 1"),
"expected batch index in error message, got: {msg}"
);
assert!(
msg.contains("position [1,1]"),
"expected exact diagonal position in error message, got: {msg}"
);
}
#[test]
fn f64_unbatched_error_omits_batch_index_and_includes_position() {
let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![0.0, 1.0, 1.0, 0.0]).unwrap();
let err = check_singular_diagonal(&t).unwrap_err();
let msg = match &err {
Error::BackendFailure { message, .. } => message.clone(),
_ => unreachable!(),
};
assert!(
!msg.contains("batch"),
"unbatched error should not mention batch, got: {msg}"
);
assert!(
msg.contains("position [0,0]"),
"expected exact diagonal position in error message, got: {msg}"
);
}
#[test]
fn f64_unbatched_error_second_diagonal_reports_correct_position() {
let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![3.0, 1.0, 1.0, 0.0]).unwrap();
let err = check_singular_diagonal(&t).unwrap_err();
let msg = match &err {
Error::BackendFailure { message, .. } => message.clone(),
_ => unreachable!(),
};
assert!(
msg.contains("position [1,1]"),
"expected second diagonal position in error message, got: {msg}"
);
}
#[test]
fn f64_batched_error_first_batch_reports_correct_position() {
let t = TypedTensor::<f64>::from_vec_col_major(
vec![2, 2, 2],
vec![0.0, 1.0, 1.0, 2.0, 3.0, 0.0, 0.0, 4.0],
)
.unwrap();
let err = check_singular_diagonal(&t).unwrap_err();
let msg = match &err {
Error::BackendFailure { message, .. } => message.clone(),
_ => unreachable!(),
};
assert!(
msg.contains("batch 0"),
"expected batch 0 in error message, got: {msg}"
);
assert!(
msg.contains("position [0,0]"),
"expected exact diagonal position in error message, got: {msg}"
);
}
#[test]
fn f32_unbatched_error_includes_exact_position() {
let t =
TypedTensor::<f32>::from_vec_col_major(vec![2, 2], vec![0.0f32, 1.0, 1.0, 0.0]).unwrap();
let err = check_singular_diagonal(&t).unwrap_err();
let msg = match &err {
Error::BackendFailure { message, .. } => message.clone(),
_ => unreachable!(),
};
assert!(
msg.contains("position [0,0]"),
"expected exact diagonal position in error message, got: {msg}"
);
assert!(
!msg.contains("batch"),
"unbatched f32 error should not mention batch, got: {msg}"
);
}
#[test]
fn c64_unbatched_error_includes_exact_position() {
let t = TypedTensor::<Complex64>::from_vec_col_major(
vec![2, 2],
vec![
Complex64::new(0.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
],
)
.unwrap();
let err = check_singular_diagonal(&t).unwrap_err();
let msg = match &err {
Error::BackendFailure { message, .. } => message.clone(),
_ => unreachable!(),
};
assert!(
msg.contains("position [0,0]"),
"expected exact position in c64 error, got: {msg}"
);
assert!(
!msg.contains("batch"),
"unbatched c64 error should not mention batch, got: {msg}"
);
}
#[test]
fn c32_unbatched_error_includes_exact_position() {
let t = TypedTensor::<Complex32>::from_vec_col_major(
vec![2, 2],
vec![
Complex32::new(0.0, 0.0),
Complex32::new(1.0, 0.0),
Complex32::new(1.0, 0.0),
Complex32::new(0.0, 0.0),
],
)
.unwrap();
let err = check_singular_diagonal(&t).unwrap_err();
let msg = match &err {
Error::BackendFailure { message, .. } => message.clone(),
_ => unreachable!(),
};
assert!(
msg.contains("position [0,0]"),
"expected exact position in c32 error, got: {msg}"
);
assert!(
!msg.contains("batch"),
"unbatched c32 error should not mention batch, got: {msg}"
);
}
macro_rules! validate_nonsingular_u_test {
($mod_name:ident, $variant:ident, $inner:ty) => {
mod $mod_name {
use num_traits::{One, Zero};
use super::*;
#[test]
fn singular() {
let t = Tensor::$variant(
TypedTensor::<$inner>::from_vec_col_major(
vec![2, 2],
vec![
<$inner>::zero(),
<$inner>::one(),
<$inner>::one(),
<$inner>::zero(),
],
)
.unwrap(),
);
let err = validate_nonsingular_u(&t).unwrap_err();
assert!(matches!(err, Error::BackendFailure { op: "solve", .. }));
}
#[test]
fn nonsingular() {
let t = Tensor::$variant(
TypedTensor::<$inner>::from_vec_col_major(
vec![2, 2],
vec![
<$inner>::one(),
<$inner>::zero(),
<$inner>::zero(),
<$inner>::one() + <$inner>::one(),
],
)
.unwrap(),
);
assert!(validate_nonsingular_u(&t).is_ok());
}
}
};
}
validate_nonsingular_u_test!(validate_f32, F32, f32);
validate_nonsingular_u_test!(validate_f64, F64, f64);
validate_nonsingular_u_test!(validate_c32, C32, Complex32);
validate_nonsingular_u_test!(validate_c64, C64, Complex64);