vecmat 0.7.8

Low-dimensional vector algebra with min_const_generics support
Documentation
use crate::{distr::*, traits::Dot, Quaternion};
use approx::*;
use num_traits::One;
use rand_::prelude::*;
use rand_xorshift::XorShiftRng;

#[cfg(feature = "std")]
use std::boxed::Box;

const SAMPLE_ATTEMPTS: usize = 256;
type Qf = Quaternion<f64>;

#[test]
fn imaginary_units() {
    type Qi = Quaternion<i32>;
    assert_eq!(Qi::i() * Qi::i(), -Qi::one());
    assert_eq!(Qi::j() * Qi::j(), -Qi::one());
    assert_eq!(Qi::k() * Qi::k(), -Qi::one());
    assert_eq!(Qi::i() * Qi::j() * Qi::k(), -Qi::one());

    assert_eq!(Qi::i() * Qi::j(), Qi::k());
    assert_eq!(Qi::j() * Qi::k(), Qi::i());
    assert_eq!(Qi::k() * Qi::i(), Qi::j());

    assert_eq!(Qi::j() * Qi::i(), -Qi::k());
    assert_eq!(Qi::k() * Qi::j(), -Qi::i());
    assert_eq!(Qi::i() * Qi::k(), -Qi::j());
}

#[test]
#[allow(clippy::eq_op)]
fn inversion() {
    let mut rng = XorShiftRng::seed_from_u64(0xFEED0);
    for _ in 0..SAMPLE_ATTEMPTS {
        let a: Qf = rng.sample(&NonZero);
        assert_abs_diff_eq!(a / a, Quaternion::one(), epsilon = 1e-14);
    }
}

#[test]
fn law_of_cosines() {
    for _ in 0..SAMPLE_ATTEMPTS {
        let mut rng = XorShiftRng::seed_from_u64(0xFEED1);
        let a: Qf = rng.sample(&Normal);
        let b: Qf = rng.sample(&Normal);
        assert_abs_diff_eq!(
            a.norm_sqr() + b.norm_sqr() + 2.0 * a.dot(b),
            (a + b).norm_sqr(),
            epsilon = 1e-14,
        );
    }
}

#[test]
fn conjugation() {
    for _ in 0..SAMPLE_ATTEMPTS {
        let mut rng = XorShiftRng::seed_from_u64(0xFEED2);
        let a: Qf = rng.sample(&Normal);
        assert_abs_diff_eq!(a * a.conj(), Quaternion::<f64>::one() * a.norm_sqr());
        assert_abs_diff_eq!(a.conj() * a, Quaternion::<f64>::one() * a.norm_sqr());
    }
}

#[cfg(feature = "std")]
#[test]
fn derivation() {
    let cases = [
        (
            Box::new(|p: Qf| p) as Box<dyn Fn(_) -> _>,
            Box::new(|_: Qf, v: Qf| v) as Box<dyn Fn(_, _) -> _>,
        ),
        (
            Box::new(|p: Qf| p * p),
            Box::new(|p: Qf, v: Qf| p * v + v * p),
        ),
        (
            Box::new(|p: Qf| p.inv()),
            Box::new(|p: Qf, v: Qf| {
                let p2 = p.norm_sqr();
                (v.conj() - (2.0 * p.dot(v) / p2) * p.conj()) / p2
            }),
        ),
    ];

    const EPS: f64 = 1e-8;
    let mut rng = XorShiftRng::seed_from_u64(0xFEED3);

    for (f, dfdv) in cases.iter() {
        for _ in 0..SAMPLE_ATTEMPTS {
            let p = rng.sample(&Normal);
            let v = rng.sample(&Unit);
            let deriv = dfdv(p, v);
            let dabs = deriv.norm();
            assert_abs_diff_eq!(deriv, (f(p + EPS * v) - f(p)) / EPS, epsilon = 1e-6 * dabs,);
        }
    }
}

#[test]
fn into_matrix() {
    let mut rng = XorShiftRng::seed_from_u64(0xFEED0);
    for _ in 0..SAMPLE_ATTEMPTS {
        let a: Qf = rng.sample(&NonZero);
        let b: Qf = rng.sample(&NonZero);
        assert_abs_diff_eq!(
            (a * b).into_matrix(),
            a.into_matrix().dot(b.into_matrix()),
            epsilon = 1e-14,
        );
    }
}