use core::f64::consts::{E, PI};
use multicalc::linear_algebra::{Matrix, Matrix2D, Matrix3D, Vector, Vector2D, Vector3D};
use multicalc_testkit::tol::{Tol, assert_scalar_close, assert_vector_close};
use proptest::prelude::*;
use proptest::test_runner::TestCaseError;
fn vector_strategy<const N: usize, S>(num_strategy: S) -> impl Strategy<Value = Vector<N>>
where
S: Strategy<Value = f64>,
{
prop::array::uniform::<_, N>(num_strategy).prop_map(Vector::new)
}
#[test]
fn construct_and_access() {
let vector = Vector::new([1.0, 2.0, 3.0]);
assert_eq!(vector.get(0), Some(&1.0));
assert_eq!(vector.into_array(), [1.0, 2.0, 3.0]);
let mut mutable = Vector::from([4.0, 5.0]);
if let Some(entry) = mutable.get_mut(1) {
*entry = 9.0;
}
assert_eq!(mutable, Vector::new([4.0, 9.0]));
let zeros: Vector3D = Vector::zeros();
assert_eq!(zeros, Vector::new([0.0, 0.0, 0.0]));
assert_eq!(
Vector::<4>::from_fn(|index| index as f64),
Vector::new([0.0, 1.0, 2.0, 3.0])
);
let mut matrix = Matrix::new([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(matrix.get(1, 0), Some(&3.0));
if let Some(entry) = matrix.get_mut(0, 1) {
*entry = 7.0;
}
assert_eq!(matrix.get(0, 1), Some(&7.0));
let identity: Matrix3D = Matrix::identity();
assert_eq!(
identity.into_array(),
[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
);
assert_eq!(
Matrix2D::from_fn(|row, column| (row * 2 + column) as f64),
Matrix::new([[0.0, 1.0], [2.0, 3.0]])
);
}
#[test]
fn try_from_slice_length() {
assert_eq!(
Vector3D::try_from_slice(&[1.0, 2.0, 3.0]),
Some(Vector::new([1.0, 2.0, 3.0]))
);
assert!(Vector3D::try_from_slice(&[1.0, 2.0]).is_none());
assert_eq!(
Matrix2D::try_from_row_slice(&[1.0, 2.0, 3.0, 4.0]),
Some(Matrix::new([[1.0, 2.0], [3.0, 4.0]]))
);
assert!(Matrix2D::try_from_row_slice(&[1.0, 2.0, 3.0]).is_none());
}
#[test]
fn get_checked_access() {
let vector = Vector::new([1.0, 2.0, 3.0]);
assert_eq!(vector.get(0), Some(&1.0));
assert_eq!(vector.get(3), None);
let mut mutable = Vector::new([4.0, 5.0]);
if let Some(entry) = mutable.get_mut(1) {
*entry = 9.0;
}
assert_eq!(mutable.get(1), Some(&9.0));
let mut matrix = Matrix::new([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(matrix.get(1, 0), Some(&3.0));
assert_eq!(matrix.get(2, 0), None);
assert_eq!(matrix.get(0, 2), None);
if let Some(entry) = matrix.get_mut(0, 1) {
*entry = 7.0;
}
assert_eq!(matrix.get(0, 1), Some(&7.0));
matrix.as_mut_slice_rows()[1][1] = 8.0;
assert_eq!(matrix.get(1, 1), Some(&8.0));
}
fn check_vector_map<const N: usize, F: Fn(f64) -> f64>(v: Vector<N>, f: F) {
let u = v.map(&f);
for (a, b) in u.as_slice().iter().zip(v.as_slice()) {
assert_eq!(*a, f(*b));
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn vector_map_1(v in vector_strategy::<1, _>(prop::num::f64::NORMAL)) {
check_vector_map(v, |x| x + PI);
}
#[test]
fn vector_map_2(v in vector_strategy::<2, _>(prop::num::f64::NORMAL)) {
check_vector_map(v, |x| 2.0 * x);
}
#[test]
fn vector_map_3(v in vector_strategy::<3, _>(prop::num::f64::NORMAL)) {
check_vector_map(v, |x| x - E);
}
#[test]
fn vector_map_4(v in vector_strategy::<4, _>(prop::num::f64::NORMAL)) {
check_vector_map(v, |x| x * x);
}
}
#[test]
fn vector_arithmetic() {
let left = Vector::new([1.0, 2.0, 3.0]);
let right = Vector::new([4.0, 5.0, 6.0]);
assert_eq!(left + right, Vector::new([5.0, 7.0, 9.0]));
assert_eq!(right - left, Vector::new([3.0, 3.0, 3.0]));
assert_eq!(-left, Vector::new([-1.0, -2.0, -3.0]));
assert_eq!(left * 2.0, left.scale(2.0));
assert_eq!(left.scale(2.0), Vector::new([2.0, 4.0, 6.0]));
assert_eq!(left / 2.0, left.scale(0.5));
let mut accumulated = left;
accumulated += right;
assert_eq!(accumulated, left + right);
accumulated -= right;
assert_eq!(accumulated, left);
let a = Vector::new([
1.0,
-1.0,
0.0,
-0.0,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NAN,
]);
let div_zero = a / 0.0;
let expected = Vector::new([
f64::INFINITY,
f64::NEG_INFINITY,
f64::NAN,
f64::NAN,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NAN,
]);
div_zero
.into_array()
.into_iter()
.zip(expected.as_array())
.for_each(|(got, want)| {
assert_eq!(
got.total_cmp(want),
core::cmp::Ordering::Equal,
"{got} != {want}"
);
})
}
#[test]
fn vector_dot_and_norm() {
let left: Vector3D = Vector::new([1.0, 2.0, 3.0]);
let right: Vector3D = Vector::new([4.0, 5.0, 6.0]);
assert_eq!(left.dot(right), 32.0);
assert!((left.dot(right) - right.dot(left)).abs() < 1e-12); assert_eq!(Vector::new([1.0, 0.0]).dot(Vector::new([0.0, 1.0])), 0.0);
let empty: Vector<0> = Vector::zeros();
assert_eq!(empty.dot(empty), 0.0);
let vector: Vector2D = Vector::new([3.0, 4.0]);
assert_eq!(vector.norm(), 5.0);
assert!((vector.norm_squared() - vector.norm() * vector.norm()).abs() < 1e-12);
let zeros: Vector3D = Vector::zeros();
assert_eq!(zeros.norm(), 0.0);
assert!(Vector::new([f64::INFINITY, 0.0]).norm().is_infinite());
}
#[test]
fn vector_is_finite() {
assert!(Vector::new([1.0, -2.0, 3.0]).is_finite());
assert!(Vector::<0>::zeros().is_finite()); assert!(!Vector::new([1.0, f64::NAN]).is_finite());
assert!(!Vector::new([f64::INFINITY, 0.0]).is_finite());
assert!(!Vector::new([0.0, f64::NEG_INFINITY]).is_finite());
}
fn check_vector_normalized<const N: usize>(mut v: Vector<N>) -> Result<(), TestCaseError> {
let norm = v.norm();
prop_assume!(norm.is_finite());
prop_assume!(norm > 1e-16);
let tol = Tol {
abs: <f64 as multicalc::Numeric>::EPSILON_X30,
rel: 1e-8,
};
let normalized = v.normalized();
assert_scalar_close(normalized.norm(), 1.0, tol);
assert_vector_close(&v, &normalized.scale(norm), tol);
assert_eq!(Some(normalized), v.try_normalized());
let mut v_copy = v;
v.normalize();
assert_eq!(v, normalized);
assert!(v_copy.try_normalize().is_some());
assert_eq!(v_copy, normalized);
Ok(())
}
#[test]
fn zero_vector_normalize() {
let mut near_zero = Vector::new([<f64 as multicalc::Numeric>::EPSILON, 0.0, 0.0]);
assert_eq!(near_zero.normalized(), Vector::new([1.0f64, 0.0, 0.0]));
near_zero.normalize();
assert_eq!(near_zero, Vector::new([1.0f64, 0.0, 0.0]));
let mut zero = Vector::new([0.0f64, 0.0, 0.0]);
assert_eq!(zero.try_normalized(), None);
assert!(zero.try_normalize().is_none());
assert_eq!(zero, Vector::new([0.0f64, 0.0, 0.0]));
assert!(
zero.normalized()
.into_array()
.into_iter()
.all(|x| x.total_cmp(&f64::NAN).is_eq())
);
zero.normalize();
assert!(
zero.into_array()
.into_iter()
.all(|x| x.total_cmp(&f64::NAN).is_eq())
);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn vector_normalize_1(v in vector_strategy::<1, _>(prop::num::f64::NORMAL)) {
check_vector_normalized(v)?;
}
#[test]
fn vector_normalize_2(v in vector_strategy::<2, _>(prop::num::f64::NORMAL)) {
check_vector_normalized(v)?;
}
#[test]
fn vector_normalize_3(v in vector_strategy::<3, _>(prop::num::f64::NORMAL)) {
check_vector_normalized(v)?;
}
#[test]
fn vector_normalize_4(v in vector_strategy::<4, _>(prop::num::f64::NORMAL)) {
check_vector_normalized(v)?;
}
}
#[test]
fn vector_cross_3d() {
let unit_x = Vector::new([1.0, 0.0, 0.0]);
let unit_y = Vector::new([0.0, 1.0, 0.0]);
let unit_z = Vector::new([0.0, 0.0, 1.0]);
assert_eq!(unit_x.cross(unit_y), unit_z);
assert_eq!(unit_y.cross(unit_z), unit_x);
assert_eq!(unit_z.cross(unit_x), unit_y);
assert_eq!(unit_x.cross(unit_y), -(unit_y.cross(unit_x)));
let left = Vector::new([1.0, 2.0, 3.0]);
let right = Vector::new([4.0, 5.0, 6.0]);
let cross_product = left.cross(right);
assert_eq!(left.dot(cross_product), 0.0); assert_eq!(right.dot(cross_product), 0.0);
}
#[test]
fn vector_cross_2d_and_scalar_triple() {
assert_eq!(Vector::new([1.0, 0.0]).cross(Vector::new([0.0, 1.0])), 1.0);
let left: Vector2D = Vector::new([2.0, 3.0]);
let right: Vector2D = Vector::new([5.0, 7.0]);
assert!((left.cross(right) + right.cross(left)).abs() < 1e-12); assert_eq!(left.cross(left), 0.0);
let unit_x = Vector::new([1.0, 0.0, 0.0]);
let unit_y = Vector::new([0.0, 1.0, 0.0]);
let unit_z = Vector::new([0.0, 0.0, 1.0]);
assert_eq!(unit_x.scalar_triple(unit_y, unit_z), 1.0);
let first: Vector3D = Vector::new([1.0, 2.0, 3.0]);
let second: Vector3D = Vector::new([0.0, 1.0, 4.0]);
let third: Vector3D = Vector::new([5.0, 6.0, 0.0]);
assert!(
(first.scalar_triple(second, third) - second.scalar_triple(third, first)).abs() < 1e-12
);
let rows_as_matrix = Matrix::new([first.into_array(), second.into_array(), third.into_array()]);
assert!((first.scalar_triple(second, third) - rows_as_matrix.determinant()).abs() < 1e-12);
}