#![deny(rustdoc::broken_intra_doc_links)]
use crate::{
FpScalar, RealScalar,
algorithms::accumulators::{
Accumulator, MaxAbsValueAccumulator, NaiveSum, NeumaierSum, SumAccumulator,
},
functions::{Pow, Sqrt},
scalars::NonNegativeRealScalar,
};
use num_traits::{One, Zero};
use rayon::prelude::*;
use std::marker::PhantomData;
use try_create::TryNew;
struct ScaledSumSqAccumulator<ScalarType, SumSqAcc>
where
ScalarType: FpScalar,
SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
{
scale: ScalarType::RealType,
sumsq_accumulator: SumSqAcc,
zero: ScalarType::RealType,
}
impl<ScalarType, SumSqAcc> ScaledSumSqAccumulator<ScalarType, SumSqAcc>
where
ScalarType: FpScalar,
SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
{
fn add_value_and_update_scale(&mut self, value: ScalarType::RealType) {
if value > self.zero {
let r_sq = if self.scale < value {
if self.scale > self.zero {
let r = self.scale.clone() / &value;
self.sumsq_accumulator.rescale_by(&r.pow(2));
}
self.scale = value;
ScalarType::RealType::one()
} else {
(value / &self.scale).pow(2)
};
self.sumsq_accumulator.push(r_sq);
}
}
fn result_sqrt(self) -> NonNegativeRealScalar<ScalarType::RealType> {
let sqrt_sum_sq = if self.scale == self.zero {
self.zero
} else {
self.sumsq_accumulator.result().sqrt() * self.scale
};
NonNegativeRealScalar::try_new(sqrt_sum_sq).expect(
"ScaledSumSqAccumulator::result_sqrt: computed value is negative or infinite (bug)",
)
}
}
impl<ScalarType, SumSqAcc> Accumulator for ScaledSumSqAccumulator<ScalarType, SumSqAcc>
where
ScalarType: FpScalar,
SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
{
type Input = ScalarType;
type Output = NonNegativeRealScalar<ScalarType::RealType>;
fn new() -> Self {
Self {
scale: ScalarType::RealType::zero(),
sumsq_accumulator: SumSqAcc::new(),
zero: ScalarType::RealType::zero(),
}
}
fn push(&mut self, value: Self::Input) {
self.add_value_and_update_scale(value.abs());
}
fn combine(&mut self, mut other: Self) {
if other.scale > self.scale {
if self.scale > self.zero {
let r = self.scale.clone() / &other.scale;
self.sumsq_accumulator.rescale_by(&r.pow(2));
}
self.scale = other.scale;
self.sumsq_accumulator.combine(other.sumsq_accumulator);
} else if other.scale > self.zero {
if other.scale < self.scale {
let r = other.scale / &self.scale;
other.sumsq_accumulator.rescale_by(&r.pow(2));
}
self.sumsq_accumulator.combine(other.sumsq_accumulator);
}
}
fn result(self) -> Self::Output {
let sum_sq = if self.scale == self.zero {
self.zero
} else {
self.sumsq_accumulator.result() * self.scale.pow(2)
};
NonNegativeRealScalar::try_new(sum_sq)
.expect("ScaledSumSqAccumulator::result: computed value is negative or infinite (bug)")
}
}
pub struct L1Norm<Acc>(PhantomData<Acc>);
pub type L1NormNaiveSum<ScalarType> = L1Norm<NaiveSum<<ScalarType as FpScalar>::RealType>>;
pub type L1NormNeumaierSum<ScalarType> = L1Norm<NeumaierSum<<ScalarType as FpScalar>::RealType>>;
pub struct L2Norm<Acc>(PhantomData<Acc>);
pub type L2NormNaiveSum<ScalarType> = L2Norm<NaiveSum<<ScalarType as FpScalar>::RealType>>;
pub type L2NormNeumaierSum<ScalarType> = L2Norm<NeumaierSum<<ScalarType as FpScalar>::RealType>>;
impl<Acc: SumAccumulator<Input: RealScalar>> L2Norm<Acc> {
pub fn compute_vector_norm_sq<T>(v: &[T]) -> NonNegativeRealScalar<Acc::Output>
where
T: FpScalar<RealType = Acc::Output>,
{
Self::compute_vector_norm_sq_iter(v.iter().cloned())
}
pub fn compute_vector_norm_sq_iter<I>(v: I) -> NonNegativeRealScalar<Acc::Output>
where
I: IntoIterator<Item: FpScalar<RealType = Acc::Output>>,
{
ScaledSumSqAccumulator::<I::Item, Acc>::new_sequential(v).result()
}
}
impl<Acc> L2Norm<Acc>
where
Acc: SumAccumulator<Input: RealScalar> + Send,
{
pub fn compute_vector_norm_sq_par<T>(v: &[T]) -> NonNegativeRealScalar<Acc::Output>
where
T: FpScalar<RealType = Acc::Output>,
{
Self::compute_vector_norm_sq_par_iter(v.par_iter().cloned())
}
pub fn compute_vector_norm_sq_par_iter<I>(v: I) -> NonNegativeRealScalar<Acc::Output>
where
I: IntoParallelIterator<Item: FpScalar<RealType = Acc::Output>>,
{
ScaledSumSqAccumulator::<I::Item, Acc>::new_parallel(v).result()
}
}
pub struct LinfNorm<Acc = ()>(PhantomData<Acc>);
pub trait VectorNorm<ScalarType: FpScalar> {
fn compute_vector_norm(v: &[ScalarType]) -> NonNegativeRealScalar<ScalarType::RealType> {
Self::compute_vector_norm_iter(v.iter().cloned())
}
fn compute_vector_norm_iter<I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
where
I: IntoIterator<Item = ScalarType>;
}
pub trait ParallelVectorNorm<T: FpScalar + Send + Sync>: VectorNorm<T> {
fn compute_vector_norm_par(v: &[T]) -> NonNegativeRealScalar<T::RealType> {
Self::compute_vector_norm_par_iter(v.par_iter().cloned())
}
fn compute_vector_norm_par_iter<I: IntoParallelIterator<Item = T>>(
iter: I,
) -> NonNegativeRealScalar<T::RealType>;
}
impl<ScalarType, SumAcc> VectorNorm<ScalarType> for L2Norm<SumAcc>
where
ScalarType: FpScalar,
SumAcc: SumAccumulator<Input = ScalarType::RealType>,
{
fn compute_vector_norm_iter<I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
where
I: IntoIterator<Item = ScalarType>,
{
ScaledSumSqAccumulator::<ScalarType, SumAcc>::new_sequential(v).result_sqrt()
}
}
impl<T, SumAcc> ParallelVectorNorm<T> for L2Norm<SumAcc>
where
T: FpScalar,
SumAcc: SumAccumulator<Input = T::RealType> + Send,
{
fn compute_vector_norm_par_iter<I: IntoParallelIterator<Item = T>>(
iter: I,
) -> NonNegativeRealScalar<T::RealType> {
ScaledSumSqAccumulator::<T, SumAcc>::new_parallel(iter).result_sqrt()
}
}
impl<ScalarType, SumAcc> VectorNorm<ScalarType> for L1Norm<SumAcc>
where
ScalarType: FpScalar,
SumAcc: SumAccumulator<Input = ScalarType::RealType>,
{
fn compute_vector_norm_iter<I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
where
I: IntoIterator<Item = ScalarType>,
{
NonNegativeRealScalar::try_new(SumAcc::new_sequential(v.into_iter().map(|x| x.abs())).result())
.expect("<L1Norm as VectorNorm>::compute_vector_norm_iter: computed value is negative or infinite (bug)")
}
}
impl<T, SumAcc> ParallelVectorNorm<T> for L1Norm<SumAcc>
where
T: FpScalar,
SumAcc: SumAccumulator<Input = T::RealType> + Send,
{
fn compute_vector_norm_par_iter<I: IntoParallelIterator<Item = T>>(
iter: I,
) -> NonNegativeRealScalar<T::RealType> {
NonNegativeRealScalar::try_new(SumAcc::new_parallel(iter.into_par_iter().map(|x| x.abs())).result())
.expect("<L1Norm as ParallelVectorNorm>::compute_vector_norm_par_iter: computed value is negative or infinite (bug)")
}
}
impl<ScalarType> VectorNorm<ScalarType> for LinfNorm
where
ScalarType: FpScalar,
{
fn compute_vector_norm_iter<I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
where
I: IntoIterator<Item = ScalarType>,
{
MaxAbsValueAccumulator::new_sequential(v).result()
}
}
impl<ScalarType> ParallelVectorNorm<ScalarType> for LinfNorm
where
ScalarType: FpScalar,
{
fn compute_vector_norm_par_iter<I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
where
I: IntoParallelIterator<Item = ScalarType>,
{
MaxAbsValueAccumulator::new_parallel(v).result()
}
}
#[inline(always)]
pub fn vector_norm_l2_with_accumulator<ScalarType, SumSqAcc>(
v: &[ScalarType],
) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
{
L2Norm::<SumSqAcc>::compute_vector_norm(v)
}
#[inline(always)]
pub fn vector_norm_l2_iter_with_accumulator<ScalarType, SumSqAcc, I>(
v: I,
) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
I: IntoIterator<Item = ScalarType>,
{
L2Norm::<SumSqAcc>::compute_vector_norm_iter(v)
}
#[inline(always)]
pub fn vector_norm_l2_sq_with_accumulator<ScalarType, SumSqAcc>(
v: &[ScalarType],
) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
{
L2Norm::<SumSqAcc>::compute_vector_norm_sq(v)
}
#[inline(always)]
pub fn vector_norm_l2_sq_iter_with_accumulator<ScalarType, SumSqAcc, I>(
v: I,
) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
I: IntoIterator<Item = ScalarType>,
{
L2Norm::<SumSqAcc>::compute_vector_norm_sq_iter(v)
}
#[inline(always)]
pub fn vector_norm_l1_with_accumulator<ScalarType, SumAcc>(
a: &[ScalarType],
) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
SumAcc: SumAccumulator<Input = ScalarType::RealType>,
{
L1Norm::<SumAcc>::compute_vector_norm(a)
}
#[inline(always)]
pub fn vector_norm_l1_iter_with_accumulator<ScalarType, SumAcc, I>(
a: I,
) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
SumAcc: SumAccumulator<Input = ScalarType::RealType>,
I: IntoIterator<Item = ScalarType>,
{
L1Norm::<SumAcc>::compute_vector_norm_iter(a)
}
#[inline(always)]
pub fn vector_norm_l2<ScalarType>(v: &[ScalarType]) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
{
L2NormNaiveSum::<ScalarType>::compute_vector_norm(v)
}
#[inline(always)]
pub fn vector_norm_l2_iter<ScalarType, I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
I: IntoIterator<Item = ScalarType>,
{
L2NormNaiveSum::<ScalarType>::compute_vector_norm_iter(v)
}
#[inline(always)]
pub fn vector_norm_l2_sq<ScalarType: FpScalar>(
v: &[ScalarType],
) -> NonNegativeRealScalar<ScalarType::RealType> {
L2NormNaiveSum::<ScalarType>::compute_vector_norm_sq(v)
}
#[inline(always)]
pub fn vector_norm_l2_sq_iter<ScalarType, I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
I: IntoIterator<Item = ScalarType>,
{
L2NormNaiveSum::<ScalarType>::compute_vector_norm_sq_iter(v)
}
#[inline(always)]
pub fn vector_norm_l2_sq_neumaier<ScalarType: FpScalar>(
v: &[ScalarType],
) -> NonNegativeRealScalar<ScalarType::RealType> {
L2NormNeumaierSum::<ScalarType>::compute_vector_norm_sq(v)
}
#[inline(always)]
pub fn vector_norm_l2_sq_neumaier_iter<ScalarType, I>(
v: I,
) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
I: IntoIterator<Item = ScalarType>,
{
L2NormNeumaierSum::<ScalarType>::compute_vector_norm_sq_iter(v)
}
#[inline(always)]
pub fn vector_norm_l2_neumaier<ScalarType: FpScalar>(
v: &[ScalarType],
) -> NonNegativeRealScalar<ScalarType::RealType> {
L2NormNeumaierSum::<ScalarType>::compute_vector_norm(v)
}
#[inline(always)]
pub fn vector_norm_l2_neumaier_iter<ScalarType, I>(
v: I,
) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
I: IntoIterator<Item = ScalarType>,
{
L2NormNeumaierSum::<ScalarType>::compute_vector_norm_iter(v)
}
#[inline(always)]
pub fn vector_norm_l1<ScalarType: FpScalar>(
a: &[ScalarType],
) -> NonNegativeRealScalar<ScalarType::RealType> {
L1NormNaiveSum::<ScalarType>::compute_vector_norm(a)
}
#[inline(always)]
pub fn vector_norm_l1_iter<ScalarType, I>(a: I) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
I: IntoIterator<Item = ScalarType>,
{
L1NormNaiveSum::<ScalarType>::compute_vector_norm_iter(a)
}
#[inline(always)]
pub fn vector_norm_l1_neumaier<ScalarType: FpScalar>(
a: &[ScalarType],
) -> NonNegativeRealScalar<ScalarType::RealType> {
L1NormNeumaierSum::<ScalarType>::compute_vector_norm(a)
}
#[inline(always)]
pub fn vector_norm_l1_neumaier_iter<ScalarType, I>(
a: I,
) -> NonNegativeRealScalar<ScalarType::RealType>
where
ScalarType: FpScalar,
I: IntoIterator<Item = ScalarType>,
{
L1NormNeumaierSum::<ScalarType>::compute_vector_norm_iter(a)
}
#[inline(always)]
pub fn vector_norm_linf<ScalarType: FpScalar>(
a: &[ScalarType],
) -> NonNegativeRealScalar<ScalarType::RealType> {
LinfNorm::<()>::compute_vector_norm(a)
}
#[inline(always)]
pub fn vector_norm_linf_par<ScalarType: FpScalar>(
a: &[ScalarType],
) -> NonNegativeRealScalar<ScalarType::RealType> {
LinfNorm::<()>::compute_vector_norm_par(a)
}
#[inline(always)]
pub fn vector_norm_linf_iter<ScalarType: FpScalar, I>(
a: I,
) -> NonNegativeRealScalar<ScalarType::RealType>
where
I: IntoIterator<Item = ScalarType>,
{
LinfNorm::<()>::compute_vector_norm_iter(a)
}
#[inline(always)]
pub fn vector_norm_linf_par_iter<ScalarType: FpScalar, I>(
a: I,
) -> NonNegativeRealScalar<ScalarType::RealType>
where
I: IntoParallelIterator<Item = ScalarType>,
{
LinfNorm::<()>::compute_vector_norm_par_iter(a)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::RealScalar;
mod vector_norm_l2_tests {
use super::*;
const EPS: f64 = 1e-12;
#[test]
fn three_four_five() {
let v = [3.0_f64, 4.0];
assert!((*vector_norm_l2(&v).as_ref() - 5.0).abs() < EPS);
}
#[test]
fn zero_vector() {
let v = [0.0_f64; 4];
assert_eq!(*vector_norm_l2(&v).as_ref(), 0.0);
}
#[test]
fn one_dimension_positive() {
let v = [5.0_f64];
assert!((*vector_norm_l2(&v).as_ref() - 5.0).abs() < EPS);
}
#[test]
fn one_dimension_negative() {
let v = [-7.0_f64];
assert!((*vector_norm_l2(&v).as_ref() - 7.0).abs() < EPS);
}
#[test]
fn unit_axis_vectors_3d() {
for axis in 0..3 {
let mut e = [0.0_f64; 3];
e[axis] = 1.0;
assert!((*vector_norm_l2(&e).as_ref() - 1.0).abs() < EPS);
}
}
#[test]
fn known_3d_norm() {
let v = [1.0_f64, 2.0, 2.0];
assert!((*vector_norm_l2(&v).as_ref() - 3.0).abs() < EPS);
}
#[test]
fn negation_preserves_norm() {
let v = [3.0_f64, -4.0, 5.0];
let neg_v = [-3.0_f64, 4.0, -5.0];
assert_eq!(vector_norm_l2(&v), vector_norm_l2(&neg_v));
}
#[test]
fn numerical_stability_large_values() {
let huge = f64::MAX / 2.0;
let v = [huge, 0.0_f64];
let n = *vector_norm_l2(&v).as_ref();
assert!(n.is_finite(), "norm should be finite, got {n}");
assert!((n / huge - 1.0).abs() < 1e-10);
}
#[test]
fn numerical_stability_tiny_values() {
let tiny = f64::MIN_POSITIVE;
let v = [tiny, 0.0_f64];
let n = *vector_norm_l2(&v).as_ref();
assert!(n.is_finite());
assert!(n > 0.0);
}
}
mod vector_norm_l2_sq_tests {
use super::*;
use crate::RealNative64StrictFinite;
const EPS: f64 = 1e-16;
#[test]
fn three_four_five() {
let v = [3.0_f64, 4.0];
assert_eq!(*vector_norm_l2_sq(&v).as_ref(), 25.0);
}
#[test]
fn zero_vector() {
let v = [0.0_f64; 4];
assert_eq!(*vector_norm_l2_sq(&v).as_ref(), 0.0);
}
#[test]
fn one_dimension_positive() {
let v = [5.0_f64];
assert_eq!(*vector_norm_l2_sq(&v).as_ref(), 25.0);
}
#[test]
fn one_dimension_negative() {
let v = [-7.0_f64];
assert_eq!(*vector_norm_l2_sq(&v).as_ref(), 49.0);
}
#[test]
fn unit_axis_vectors_3d() {
for axis in 0..3 {
let mut e = [0.0_f64; 3];
e[axis] = 1.0;
assert_eq!(*vector_norm_l2_sq(&e).as_ref(), 1.0);
}
}
#[test]
fn known_3d_vector_norm_sq() {
let v = [1.0_f64, 2.0, 2.0];
assert_eq!(*vector_norm_l2_sq(&v).as_ref(), 9.0);
}
#[test]
fn negation_preserves_vector_norm_sq() {
let v = [3.0_f64, -4.0, 5.0];
let neg_v = [-3.0_f64, 4.0, -5.0];
assert_eq!(vector_norm_l2_sq(&v), vector_norm_l2_sq(&neg_v));
}
#[test]
fn consistent_with_vector_norm_l2() {
let v = [2.0_f64, 3.0, 6.0];
let sq = *vector_norm_l2_sq(&v).as_ref();
let n = *vector_norm_l2(&v).as_ref();
assert_eq!(sq, n * n);
}
#[test]
fn numerical_stability_large_values() {
let large = 1.0e150_f64;
let v = [large, 0.0_f64];
let sq = *vector_norm_l2_sq(&v).as_ref();
assert!(sq.is_finite(), "squared norm should be finite, got {sq}");
assert_eq!(sq / (large * large), 1.0);
}
#[test]
fn numerical_stability_tiny_values() {
let small = 1.0e-100_f64;
let v = [small, 0.0_f64];
let sq = *vector_norm_l2_sq(&v).as_ref();
assert!(sq.is_finite());
assert!(sq > 0.0);
}
#[test]
#[should_panic]
fn overflow_panics_slice_api() {
let v = [RealNative64StrictFinite::from_f64(f64::MAX)];
let _ = vector_norm_l2_sq(&v);
}
#[test]
#[should_panic]
fn overflow_panics_iter_api() {
let v = vec![RealNative64StrictFinite::from_f64(f64::MAX)];
let _ = vector_norm_l2_sq_iter(v);
}
#[test]
#[should_panic]
fn overflow_panics_accumulator_api() {
let v = [RealNative64StrictFinite::from_f64(f64::MAX)];
let _ = vector_norm_l2_sq_with_accumulator::<_, NaiveSum<_>>(&v);
}
#[test]
fn geq_vector_norm_linf_squared() {
let v = [3.0_f64, -4.0, 5.0];
let sq = *vector_norm_l2_sq(&v).as_ref();
let linf = *vector_norm_linf(&v).as_ref();
assert!(sq >= linf * linf - EPS);
}
#[test]
fn naive_accumulation_loses_small_squared_terms() {
const N: usize = 10_000;
let mut v = [1e-9_f64; N + 1];
v[0] = 1.0;
let result = *vector_norm_l2_sq(&v).as_ref();
let expected = 1.0_f64 + N as f64 * 1e-18;
assert_eq!(result, 1.0_f64);
let relative_error = (result - expected).abs() / expected;
assert!(
relative_error > 1e-15,
"relative error {relative_error:.2e} should be significant \
(would be ~ε ≈ 2.2e-16 with Neumaier-sum)"
);
}
}
mod vector_norm_l2_sq_neumaier_tests {
use super::*;
const EPS: f64 = f64::EPSILON;
#[test]
fn three_four_five() {
let v = [3.0_f64, 4.0];
assert_eq!(*vector_norm_l2_sq_neumaier(&v).as_ref(), 25.0);
}
#[test]
fn zero_vector() {
let v = [0.0_f64; 4];
assert_eq!(*vector_norm_l2_sq_neumaier(&v).as_ref(), 0.0);
}
#[test]
fn one_dimension() {
let v = [7.0_f64];
assert_eq!(*vector_norm_l2_sq_neumaier(&v).as_ref(), 49.0);
}
#[test]
fn matches_naive_for_normal_values() {
let v = [1.0_f64, 2.0, 3.0];
let neumaier = *vector_norm_l2_sq_neumaier(&v).as_ref();
let naive = *vector_norm_l2_sq(&v).as_ref();
assert!((neumaier - naive).abs() < EPS);
}
#[test]
fn recovers_small_squared_terms() {
const N: usize = 10_000;
let mut v = [1e-9_f64; N + 1];
v[0] = 1.0;
let result = *vector_norm_l2_sq_neumaier(&v).as_ref();
let expected = 1.0_f64 + N as f64 * 1e-18;
let relative_error = (result - expected).abs() / expected;
assert!(
relative_error < EPS,
"relative error {relative_error:.2e} should be tiny with Neumaier-sum"
);
let naive = *vector_norm_l2_sq(&v).as_ref();
assert_eq!(
naive, 1.0_f64,
"naive should collapse to 1.0 (known limitation)"
);
assert!(
result > naive,
"Neumaier result {result} should exceed naive {naive}"
);
}
#[test]
fn numerical_stability_large_values() {
let v = [1e150_f64, 1e150, 1e150];
let result = *vector_norm_l2_sq_neumaier(&v).as_ref();
let expected = 3.0 * 1e300_f64;
let relative_error = (result - expected).abs() / expected;
assert!(relative_error < EPS, "large values: {relative_error:.2e}");
}
#[test]
fn numerical_stability_tiny_values() {
let v = [1e-100_f64, 1e-100, 1e-100];
let result = *vector_norm_l2_sq_neumaier(&v).as_ref();
let expected = 3.0e-200_f64;
let relative_error = (result - expected).abs() / expected;
assert!(relative_error < EPS, "tiny values: {relative_error:.2e}");
}
#[test]
fn consistent_with_vector_norm_l2() {
let v = [1.5_f64, 2.5, 3.5, 4.5];
let sq = *vector_norm_l2_sq_neumaier(&v).as_ref();
let l2 = *vector_norm_l2(&v).as_ref();
let err = (sq - l2 * l2).abs();
println!("sq - l2² = {}", err);
assert_eq!(err, 0.7105427357601002e-14);
}
}
mod vector_norm_l2_neumaier_tests {
use super::*;
const EPS: f64 = f64::EPSILON;
#[test]
fn three_four_five() {
let v = [3.0_f64, 4.0];
assert_eq!(*vector_norm_l2_neumaier(&v).as_ref(), 5.0);
}
#[test]
fn zero_vector() {
let v = [0.0_f64; 4];
assert_eq!(*vector_norm_l2_neumaier(&v).as_ref(), 0.0);
}
#[test]
fn one_dimension() {
let v = [7.0_f64];
assert_eq!(*vector_norm_l2_neumaier(&v).as_ref(), 7.0);
}
#[test]
fn matches_naive_for_normal_values() {
let v = [1.0_f64, 2.0, 3.0];
let neumaier = *vector_norm_l2_neumaier(&v).as_ref();
let naive = *vector_norm_l2(&v).as_ref();
assert!((neumaier - naive).abs() < EPS);
}
#[test]
fn recovers_small_squared_terms() {
const N: usize = 10_000;
let mut v = [1e-9_f64; N + 1];
v[0] = 1.0;
let result = *vector_norm_l2_neumaier(&v).as_ref();
let expected = (1.0_f64 + N as f64 * 1e-18).sqrt();
let relative_error = (result - expected).abs() / expected;
assert!(
relative_error < 1e-12,
"relative error {relative_error:.2e} should be tiny with Neumaier-sum"
);
let naive = *vector_norm_l2(&v).as_ref();
assert_eq!(
naive, 1.0_f64,
"naive should collapse to 1.0 (known limitation)"
);
assert!(
result > naive,
"Neumaier result {result} should exceed naive {naive}"
);
}
#[test]
fn numerical_stability_large_values() {
let v = [1e150_f64, 1e150, 1e150];
let result = *vector_norm_l2_neumaier(&v).as_ref();
let expected = (3.0_f64).sqrt() * 1e150;
let relative_error = (result - expected).abs() / expected;
assert!(relative_error < EPS, "large values: {relative_error:.2e}");
}
#[test]
fn numerical_stability_tiny_values() {
let v = [1e-150_f64, 1e-150, 1e-150];
let result = *vector_norm_l2_neumaier(&v).as_ref();
let expected = (3.0_f64).sqrt() * 1e-150;
let relative_error = (result - expected).abs() / expected;
assert!(relative_error < EPS, "tiny values: {relative_error:.2e}");
}
#[test]
fn consistent_with_vector_norm_l2_sq_neumaier() {
let v = [1.5_f64, 2.5, 3.5, 4.5];
let norm = *vector_norm_l2_neumaier(&v).as_ref();
let sq = *vector_norm_l2_sq_neumaier(&v).as_ref();
assert!(
(norm * norm - sq).abs() < 100. * EPS,
"norm²={} sq={}",
norm * norm,
sq
);
}
#[test]
fn unit_axis_vectors() {
let e1 = [1.0_f64, 0.0, 0.0];
let e2 = [0.0_f64, 1.0, 0.0];
let e3 = [0.0_f64, 0.0, 1.0];
for e in [&e1, &e2, &e3] {
let n = *vector_norm_l2_neumaier(e).as_ref();
assert!((n - 1.0).abs() < EPS, "unit axis: {n}");
}
}
}
mod vector_norm_l1_tests {
use super::*;
const EPS: f64 = 1e-12;
#[test]
fn three_plus_four() {
let v = [3.0_f64, 4.0];
assert_eq!(*vector_norm_l1(&v).as_ref(), 7.0);
}
#[test]
fn zero_vector() {
let v = [0.0_f64; 4];
assert_eq!(*vector_norm_l1(&v).as_ref(), 0.0);
}
#[test]
fn one_dimension_positive() {
let v = [5.0_f64];
assert!((*vector_norm_l1(&v).as_ref() - 5.0).abs() < EPS);
}
#[test]
fn one_dimension_negative() {
let v = [-7.0_f64];
assert!((*vector_norm_l1(&v).as_ref() - 7.0).abs() < EPS);
}
#[test]
fn unit_axis_vectors_3d() {
for axis in 0..3 {
let mut e = [0.0_f64; 3];
e[axis] = 1.0;
assert_eq!(*vector_norm_l1(&e).as_ref(), 1.0);
}
}
#[test]
fn known_3d_norm() {
let v = [1.0_f64, 2.0, 2.0];
assert!((*vector_norm_l1(&v).as_ref() - 5.0).abs() < EPS);
}
#[test]
fn negation_preserves_norm() {
let v = [3.0_f64, -4.0, 5.0];
let neg_v = [-3.0_f64, 4.0, -5.0];
assert_eq!(vector_norm_l1(&v), vector_norm_l1(&neg_v));
}
#[test]
fn mixed_signs() {
let v = [-1.0_f64, 2.0, -3.0, 4.0];
assert!((*vector_norm_l1(&v).as_ref() - 10.0).abs() < EPS);
}
#[test]
fn l1_geq_l2() {
let v = [3.0_f64, -4.0, 5.0];
assert!(vector_norm_l1(&v).as_ref() >= vector_norm_l2(&v).as_ref());
}
#[test]
fn numerical_stability_large_values() {
let huge = f64::MAX / 4.0;
let v = [huge, huge];
let n = *vector_norm_l1(&v).as_ref();
assert!(n.is_finite(), "norm should be finite, got {n}");
assert!((n / (2.0 * huge) - 1.0).abs() < 1e-10);
}
#[test]
fn numerical_stability_tiny_values() {
let tiny = f64::MIN_POSITIVE;
let v = [tiny, 0.0_f64];
let n = *vector_norm_l1(&v).as_ref();
assert!(n.is_finite());
assert!(n > 0.0);
}
}
mod vector_norm_l1_neumaier_tests {
use super::*;
const EPS: f64 = 1e-12;
#[test]
fn three_plus_four() {
let v = [3.0_f64, 4.0];
assert_eq!(*vector_norm_l1_neumaier(&v).as_ref(), 7.0);
}
#[test]
fn zero_vector() {
let v = [0.0_f64; 4];
assert_eq!(*vector_norm_l1_neumaier(&v).as_ref(), 0.0);
}
#[test]
fn one_dimension_positive() {
let v = [5.0_f64];
assert!((*vector_norm_l1_neumaier(&v).as_ref() - 5.0).abs() < EPS);
}
#[test]
fn one_dimension_negative() {
let v = [-7.0_f64];
assert!((*vector_norm_l1_neumaier(&v).as_ref() - 7.0).abs() < EPS);
}
#[test]
fn unit_axis_vectors_3d() {
for axis in 0..3 {
let mut e = [0.0_f64; 3];
e[axis] = 1.0;
assert_eq!(*vector_norm_l1_neumaier(&e).as_ref(), 1.0);
}
}
#[test]
fn known_3d_norm() {
let v = [1.0_f64, 2.0, 2.0];
assert!((*vector_norm_l1_neumaier(&v).as_ref() - 5.0).abs() < EPS);
}
#[test]
fn negation_preserves_norm() {
let v = [3.0_f64, -4.0, 5.0];
let neg_v = [-3.0_f64, 4.0, -5.0];
assert_eq!(vector_norm_l1_neumaier(&v), vector_norm_l1_neumaier(&neg_v));
}
#[test]
fn mixed_signs() {
let v = [-1.0_f64, 2.0, -3.0, 4.0];
assert!((*vector_norm_l1_neumaier(&v).as_ref() - 10.0).abs() < EPS);
}
#[test]
fn l1_geq_l2() {
let v = [3.0_f64, -4.0, 5.0];
assert!(vector_norm_l1_neumaier(&v).as_ref() >= vector_norm_l2(&v).as_ref());
}
#[test]
fn numerical_stability_large_values() {
let huge = f64::MAX / 4.0;
let v = [huge, huge];
let n = *vector_norm_l1_neumaier(&v).as_ref();
assert!(n.is_finite(), "norm should be finite, got {n}");
assert!((n / (2.0 * huge) - 1.0).abs() < 1e-10);
}
#[test]
fn numerical_stability_tiny_values() {
let tiny = f64::MIN_POSITIVE;
let v = [tiny, 0.0_f64];
let n = *vector_norm_l1_neumaier(&v).as_ref();
assert!(n.is_finite());
assert!(n > 0.0);
}
}
mod vector_norm_linf_tests {
use super::*;
const EPS: f64 = 1e-12;
#[test]
fn parallel_slice_matches_sequential() {
let v = [3.0_f64, -4.0, 5.0, -2.5, 1.0];
assert_eq!(vector_norm_linf_par(&v), vector_norm_linf(&v));
}
#[test]
fn parallel_iter_matches_sequential_iter() {
let v = vec![3.0_f64, -4.0, 5.0, -2.5, 1.0];
let par = vector_norm_linf_par_iter(v.clone());
let seq = vector_norm_linf_iter(v);
assert_eq!(par, seq);
}
#[test]
fn max_of_three_four() {
let v = [3.0_f64, 4.0];
assert_eq!(*vector_norm_linf(&v).as_ref(), 4.0);
}
#[test]
fn zero_vector() {
let v = [0.0_f64; 4];
assert_eq!(*vector_norm_linf(&v).as_ref(), 0.0);
}
#[test]
fn one_dimension_positive() {
let v = [5.0_f64];
assert!((*vector_norm_linf(&v).as_ref() - 5.0).abs() < EPS);
}
#[test]
fn one_dimension_negative() {
let v = [-7.0_f64];
assert!((*vector_norm_linf(&v).as_ref() - 7.0).abs() < EPS);
}
#[test]
fn unit_axis_vectors_3d() {
for axis in 0..3 {
let mut e = [0.0_f64; 3];
e[axis] = 1.0;
assert_eq!(*vector_norm_linf(&e).as_ref(), 1.0);
}
}
#[test]
fn known_3d_norm() {
let v = [1.0_f64, -2.0, 3.0];
assert!((*vector_norm_linf(&v).as_ref() - 3.0).abs() < EPS);
}
#[test]
fn negation_preserves_norm() {
let v = [3.0_f64, -4.0, 5.0];
let neg_v = [-3.0_f64, 4.0, -5.0];
assert_eq!(vector_norm_linf(&v), vector_norm_linf(&neg_v));
}
#[test]
fn dominant_component() {
let v = [1.0_f64, 0.001, 100.0, 0.5];
assert!((*vector_norm_linf(&v).as_ref() - 100.0).abs() < EPS);
}
#[test]
fn linf_leq_l2() {
let v = [3.0_f64, -4.0, 5.0];
assert!(vector_norm_linf(&v).as_ref() <= vector_norm_l2(&v).as_ref());
}
#[test]
fn linf_leq_l1() {
let v = [3.0_f64, -4.0, 5.0];
assert!(vector_norm_linf(&v).as_ref() <= vector_norm_l1(&v).as_ref());
}
#[test]
fn numerical_stability_large_values() {
let huge = f64::MAX / 2.0;
let v = [1.0_f64, huge];
let n = *vector_norm_linf(&v).as_ref();
assert!(n.is_finite(), "norm should be finite, got {n}");
assert!((n / huge - 1.0).abs() < 1e-10);
}
#[test]
fn numerical_stability_tiny_values() {
let tiny = f64::MIN_POSITIVE;
let v = [tiny, 0.0_f64];
let n = *vector_norm_linf(&v).as_ref();
assert!(n.is_finite());
assert!(n > 0.0);
}
}
mod other_vector_norm_l2_tests {
use super::*;
use crate::RealNative64StrictFinite;
use approx::assert_relative_eq;
fn vec_f64(vals: &[f64]) -> Vec<RealNative64StrictFinite> {
vals.iter()
.map(|&v| RealNative64StrictFinite::from_f64(v))
.collect()
}
mod basic_cases {
use super::*;
#[test]
fn pythagorean_3_4_5() {
let data = vec_f64(&[3.0, 4.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 5.0, epsilon = 1e-15);
}
#[test]
fn pythagorean_reverse_order() {
let data = vec_f64(&[4.0, 3.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 5.0, epsilon = 1e-15);
}
#[test]
fn unit_vector_x() {
let data = vec_f64(&[1.0, 0.0, 0.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 1.0, epsilon = 1e-15);
}
#[test]
fn unit_vector_y() {
let data = vec_f64(&[0.0, 1.0, 0.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 1.0, epsilon = 1e-15);
}
#[test]
fn three_equal_values() {
let data = vec_f64(&[1.0, 1.0, 1.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 3.0_f64.sqrt(), epsilon = 1e-15);
}
#[test]
fn larger_vector() {
let data = vec_f64(&[1.0, 2.0, 3.0, 4.0, 5.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 55.0_f64.sqrt(), epsilon = 1e-14);
}
}
mod edge_cases {
use super::*;
#[test]
fn empty_vector() {
let data: Vec<RealNative64StrictFinite> = vec![];
let norm = vector_norm_l2(&data).into_inner();
assert_eq!(*norm.as_ref(), 0.0);
}
#[test]
fn single_positive_element() {
let data = vec_f64(&[7.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 7.0, epsilon = 1e-15);
}
#[test]
fn single_negative_element() {
let data = vec_f64(&[-7.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 7.0, epsilon = 1e-15);
}
#[test]
fn all_zeros() {
let data = vec_f64(&[0.0, 0.0, 0.0, 0.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_eq!(*norm.as_ref(), 0.0);
}
#[test]
fn zeros_interspersed() {
let data = vec_f64(&[0.0, 3.0, 0.0, 4.0, 0.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 5.0, epsilon = 1e-15);
}
#[test]
fn negative_values() {
let data = vec_f64(&[-3.0, -4.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 5.0, epsilon = 1e-15);
}
#[test]
fn mixed_signs() {
let data = vec_f64(&[-3.0, 4.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), 5.0, epsilon = 1e-15);
}
#[test]
fn single_zero() {
let data = vec_f64(&[0.0]);
let norm = vector_norm_l2(&data).into_inner();
assert_eq!(*norm.as_ref(), 0.0);
}
}
mod numerical_stability {
use super::*;
#[test]
fn large_values_no_overflow() {
let scale = 1e154;
let data = vec_f64(&[3.0 * scale, 4.0 * scale]);
let norm = vector_norm_l2(&data).into_inner();
let expected = 5.0 * scale;
let rel_err = (*norm.as_ref() - expected).abs() / expected;
assert!(
rel_err < 1e-14,
"Large values: expected {}, got {}, rel_err {}",
expected,
*norm.as_ref(),
rel_err
);
}
#[test]
fn very_large_values() {
let scale = 1e300;
let data = vec_f64(&[scale, scale]);
let norm = vector_norm_l2(&data).into_inner();
let expected = scale * 2.0_f64.sqrt();
let rel_err = (*norm.as_ref() - expected).abs() / expected;
assert!(
rel_err < 1e-14,
"Very large values: expected {}, got {}, rel_err {}",
expected,
*norm.as_ref(),
rel_err
);
}
#[test]
fn small_values_no_underflow() {
let scale = 1e-154;
let data = vec_f64(&[3.0 * scale, 4.0 * scale]);
let norm = vector_norm_l2(&data).into_inner();
let expected = 5.0 * scale;
let rel_err = (*norm.as_ref() - expected).abs() / expected;
assert!(
rel_err < 1e-14,
"Small values: expected {}, got {}, rel_err {}",
expected,
*norm.as_ref(),
rel_err
);
}
#[test]
fn very_small_values() {
let scale = 1e-300;
let data = vec_f64(&[scale, scale]);
let norm = vector_norm_l2(&data).into_inner();
let expected = scale * 2.0_f64.sqrt();
let rel_err = (*norm.as_ref() - expected).abs() / expected;
assert!(
rel_err < 1e-14,
"Very small values: expected {}, got {}, rel_err {}",
expected,
*norm.as_ref(),
rel_err
);
}
#[test]
fn mixed_large_and_small() {
let data = vec_f64(&[1e150, 1.0, 1e-150]);
let norm = vector_norm_l2(&data).into_inner();
let rel_err = (*norm.as_ref() - 1e150).abs() / 1e150;
assert!(
rel_err < 1e-14,
"Mixed magnitudes: expected ~1e150, got {}, rel_err {}",
*norm.as_ref(),
rel_err
);
}
#[test]
fn all_same_large_values() {
let x = 1e154;
let n = 100;
let data: Vec<RealNative64StrictFinite> = (0..n)
.map(|_| RealNative64StrictFinite::from_f64(x))
.collect();
let norm = vector_norm_l2(&data).into_inner();
let expected = x * (n as f64).sqrt();
let rel_err = (*norm.as_ref() - expected).abs() / expected;
assert!(
rel_err < 1e-13,
"100 large values: expected {}, got {}, rel_err {}",
expected,
*norm.as_ref(),
rel_err
);
}
#[test]
fn all_same_small_values() {
let x = 1e-154;
let n = 100;
let data: Vec<RealNative64StrictFinite> = (0..n)
.map(|_| RealNative64StrictFinite::from_f64(x))
.collect();
let norm = vector_norm_l2(&data).into_inner();
let expected = x * (n as f64).sqrt();
let rel_err = (*norm.as_ref() - expected).abs() / expected;
assert!(
rel_err < 1e-13,
"100 small values: expected {}, got {}, rel_err {}",
expected,
*norm.as_ref(),
rel_err
);
}
#[test]
fn rescaling_triggered_multiple_times() {
let data = vec_f64(&[1.0, 2.0, 4.0, 8.0, 16.0]);
let expected = (1.0 + 4.0 + 16.0 + 64.0 + 256.0_f64).sqrt(); let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), expected, epsilon = 1e-14);
}
#[test]
fn descending_order_no_rescaling() {
let data = vec_f64(&[16.0, 8.0, 4.0, 2.0, 1.0]);
let expected = (1.0 + 4.0 + 16.0 + 64.0 + 256.0_f64).sqrt();
let norm = vector_norm_l2(&data).into_inner();
assert_relative_eq!(*norm.as_ref(), expected, epsilon = 1e-14);
}
}
mod special_values {
use super::*;
#[test]
fn max_finite_value() {
let data = vec_f64(&[f64::MAX]);
let norm = vector_norm_l2(&data).into_inner();
assert_eq!(*norm.as_ref(), f64::MAX);
}
#[test]
fn min_positive_value() {
let data = vec_f64(&[f64::MIN_POSITIVE]);
let norm = vector_norm_l2(&data).into_inner();
assert_eq!(*norm.as_ref(), f64::MIN_POSITIVE);
}
#[test]
fn epsilon() {
let data = vec_f64(&[f64::EPSILON]);
let norm = vector_norm_l2(&data).into_inner();
assert_eq!(*norm.as_ref(), f64::EPSILON);
}
}
#[cfg(feature = "rug")]
mod rug_backend {
use super::*;
use crate::functions::{Abs, Sqrt};
use crate::{Constants, RealRugStrictFinite};
use num::Zero;
use try_create::TryNew;
const PRECISION: u32 = 200;
type R = RealRugStrictFinite<PRECISION>;
fn rug_f64(v: f64) -> R {
R::try_from_f64(v).unwrap()
}
fn rug_str(s: &str) -> R {
R::try_new(rug::Float::with_val(
PRECISION,
rug::Float::parse(s).unwrap(),
))
.unwrap()
}
#[test]
fn basic_3_4_5() {
let data: Vec<R> = vec![rug_f64(3.0), rug_f64(4.0)];
let norm = vector_norm_l2(&data).into_inner();
let five = rug_f64(5.0);
let diff = (norm.clone() - &five).abs();
assert!(
diff < R::epsilon(),
"3-4-5: expected 5, got {}, diff {}",
norm,
diff
);
}
#[test]
fn empty_vector() {
let data: Vec<R> = vec![];
let norm = vector_norm_l2(&data).into_inner();
assert_eq!(norm, R::zero());
}
#[test]
fn single_element() {
let data = vec![rug_f64(7.0)];
let norm = vector_norm_l2(&data).into_inner();
let seven = rug_f64(7.0);
assert_eq!(norm, seven);
}
#[test]
fn single_negative() {
let data = vec![rug_f64(-7.0)];
let norm = vector_norm_l2(&data).into_inner();
let seven = rug_f64(7.0);
assert_eq!(norm, seven);
}
#[test]
fn all_zeros() {
let data: Vec<R> = vec![R::zero(), R::zero(), R::zero()];
let norm = vector_norm_l2(&data).into_inner();
assert_eq!(norm, R::zero());
}
#[test]
fn high_precision_values() {
let data: Vec<R> = vec![rug_str("1e-100"), rug_str("1e-100")];
let norm = vector_norm_l2(&data).into_inner();
let expected = rug_str("1e-100") * rug_f64(2.0).sqrt();
let diff = (norm.clone() - &expected).abs();
assert!(
diff < R::epsilon() * &expected,
"High precision: expected {}, got {}, diff {}",
expected,
norm,
diff
);
}
#[test]
fn very_large_exponents() {
let large = rug_str("1e1000");
let data: Vec<R> = vec![large.clone(), large.clone()];
let norm = vector_norm_l2(&data).into_inner();
let sqrt2 = rug_f64(2.0).sqrt();
let expected = large * sqrt2;
let rel_diff = ((norm.clone() - &expected) / &expected).abs();
assert!(
rel_diff < R::epsilon(),
"Very large exponents: rel_diff = {}",
rel_diff
);
}
#[test]
fn very_small_exponents() {
let small = rug_str("1e-1000");
let data: Vec<R> = vec![small.clone(), small.clone()];
let norm = vector_norm_l2(&data).into_inner();
let sqrt2 = rug_f64(2.0).sqrt();
let expected = small * sqrt2;
let rel_diff = ((norm.clone() - &expected) / &expected).abs();
assert!(
rel_diff < R::epsilon(),
"Very small exponents: rel_diff = {}",
rel_diff
);
}
#[test]
fn mixed_signs_rug() {
let data: Vec<R> = vec![rug_f64(-3.0), rug_f64(4.0)];
let norm = vector_norm_l2(&data).into_inner();
let five = rug_f64(5.0);
let diff = (norm.clone() - &five).abs();
assert!(diff < R::epsilon());
}
#[test]
fn zeros_interspersed_rug() {
let data: Vec<R> =
vec![R::zero(), rug_f64(3.0), R::zero(), rug_f64(4.0), R::zero()];
let norm = vector_norm_l2(&data).into_inner();
let five = rug_f64(5.0);
let diff = (norm.clone() - &five).abs();
assert!(diff < R::epsilon());
}
#[test]
fn ascending_order_triggers_rescaling() {
let data: Vec<R> = vec![
rug_f64(1.0),
rug_f64(2.0),
rug_f64(4.0),
rug_f64(8.0),
rug_f64(16.0),
];
let expected = rug_f64(341.0).sqrt();
let norm = vector_norm_l2(&data).into_inner();
let diff = (norm.clone() - &expected).abs();
assert!(
diff < R::epsilon() * &expected,
"Ascending: expected {}, got {}, diff {}",
expected,
norm,
diff
);
}
#[test]
fn higher_precision() {
const HIGH_PREC: u32 = 500;
type HP = RealRugStrictFinite<HIGH_PREC>;
let hp_f64 = |v: f64| HP::try_from_f64(v).unwrap();
let data: Vec<HP> = vec![hp_f64(3.0), hp_f64(4.0)];
let norm = vector_norm_l2(&data).into_inner();
let five = hp_f64(5.0);
let diff = (norm.clone() - &five).abs();
assert!(diff < HP::epsilon());
}
}
}
mod complex_types {
use super::*;
use crate::{ComplexNative64StrictFinite, RealNative64StrictFinite};
use num::Complex;
use try_create::TryNew;
fn c(re: f64, im: f64) -> ComplexNative64StrictFinite {
ComplexNative64StrictFinite::try_new(Complex::new(re, im)).unwrap()
}
fn inner_f64(x: crate::scalars::NonNegativeRealScalar<RealNative64StrictFinite>) -> f64 {
*x.into_inner().as_ref()
}
mod vector_norm_l2_complex {
use super::*;
#[test]
fn components_on_axes() {
let v = [c(3.0, 0.0), c(0.0, 4.0)];
assert!((inner_f64(vector_norm_l2(&v)) - 5.0).abs() < 1e-12);
}
#[test]
fn single_complex() {
let v = [c(3.0, 4.0)];
assert!((inner_f64(vector_norm_l2(&v)) - 5.0).abs() < 1e-12);
}
#[test]
fn zero_vector() {
let v = [c(0.0, 0.0), c(0.0, 0.0)];
assert_eq!(inner_f64(vector_norm_l2(&v)), 0.0);
}
#[test]
fn conjugate_preserves_norm() {
let v = [c(1.0, 2.0), c(3.0, -4.0)];
let vc = [c(1.0, -2.0), c(3.0, 4.0)];
assert!(
(inner_f64(vector_norm_l2(&v)) - inner_f64(vector_norm_l2(&vc))).abs() < 1e-14
);
}
#[test]
fn matches_neumaier() {
let v = [c(1.0, 2.0), c(3.0, -1.0), c(-1.0, 4.0)];
let diff =
(inner_f64(vector_norm_l2(&v)) - inner_f64(vector_norm_l2_neumaier(&v))).abs();
assert!(diff < 1e-14);
}
}
mod vector_norm_l2_sq_complex {
use super::*;
#[test]
fn components_on_axes() {
let v = [c(3.0, 0.0), c(0.0, 4.0)];
assert!((inner_f64(vector_norm_l2_sq(&v)) - 25.0).abs() < 1e-12);
}
#[test]
fn single_complex() {
let v = [c(3.0, 4.0)];
assert!((inner_f64(vector_norm_l2_sq(&v)) - 25.0).abs() < 1e-12);
}
#[test]
fn consistent_with_vector_norm_l2() {
let v = [c(1.0, 2.0), c(3.0, 4.0)];
let l2 = inner_f64(vector_norm_l2(&v));
let sq = inner_f64(vector_norm_l2_sq(&v));
assert!((sq - l2 * l2).abs() < 1e-12);
}
#[test]
fn matches_neumaier() {
let v = [c(3.0, 0.0), c(0.0, 4.0)];
let diff = (inner_f64(vector_norm_l2_sq(&v))
- inner_f64(vector_norm_l2_sq_neumaier(&v)))
.abs();
assert!(diff < 1e-14);
}
}
mod vector_norm_l2_sq_neumaier_complex {
use super::*;
#[test]
fn zero_vector() {
let v = [c(0.0, 0.0), c(0.0, 0.0)];
assert_eq!(inner_f64(vector_norm_l2_sq_neumaier(&v)), 0.0);
}
#[test]
fn matches_naive() {
let v = [c(2.0, 1.0), c(0.0, 3.0), c(-1.0, -1.0)];
let diff = (inner_f64(vector_norm_l2_sq_neumaier(&v))
- inner_f64(vector_norm_l2_sq(&v)))
.abs();
assert!(diff < 1e-14);
}
}
mod vector_norm_l2_neumaier_complex {
use super::*;
#[test]
fn zero_vector() {
let v = [c(0.0, 0.0)];
assert_eq!(inner_f64(vector_norm_l2_neumaier(&v)), 0.0);
}
#[test]
fn matches_naive() {
let v = [c(2.0, 1.0), c(0.0, 3.0), c(-1.0, -1.0)];
let diff =
(inner_f64(vector_norm_l2_neumaier(&v)) - inner_f64(vector_norm_l2(&v))).abs();
assert!(diff < 1e-14);
}
}
mod vector_norm_l1_complex {
use super::*;
#[test]
fn components_on_axes() {
let v = [c(3.0, 0.0), c(0.0, 4.0)];
assert!((inner_f64(vector_norm_l1(&v)) - 7.0).abs() < 1e-12);
}
#[test]
fn single_complex() {
let v = [c(3.0, 4.0)];
assert!((inner_f64(vector_norm_l1(&v)) - 5.0).abs() < 1e-12);
}
#[test]
fn l1_geq_l2() {
let v = [c(3.0, 0.0), c(0.0, 4.0)];
assert!(inner_f64(vector_norm_l1(&v)) >= inner_f64(vector_norm_l2(&v)) - 1e-14);
}
#[test]
fn zero_vector() {
let v = [c(0.0, 0.0), c(0.0, 0.0)];
assert_eq!(inner_f64(vector_norm_l1(&v)), 0.0);
}
#[test]
fn matches_neumaier() {
let v = [c(1.0, 2.0), c(3.0, 4.0), c(5.0, 6.0)];
let diff =
(inner_f64(vector_norm_l1(&v)) - inner_f64(vector_norm_l1_neumaier(&v))).abs();
assert!(diff < 1e-14);
}
}
mod vector_norm_l1_neumaier_complex {
use super::*;
#[test]
fn components_on_axes() {
let v = [c(3.0, 0.0), c(0.0, 4.0)];
assert!((inner_f64(vector_norm_l1_neumaier(&v)) - 7.0).abs() < 1e-12);
}
#[test]
fn zero_vector() {
let v = [c(0.0, 0.0)];
assert_eq!(inner_f64(vector_norm_l1_neumaier(&v)), 0.0);
}
}
mod vector_norm_linf_complex {
use super::*;
#[test]
fn maximum_modulus() {
let v = [c(3.0, 0.0), c(0.0, 4.0)];
assert!((inner_f64(vector_norm_linf(&v)) - 4.0).abs() < 1e-12);
}
#[test]
fn equal_moduli() {
let v = [c(3.0, 4.0), c(0.0, 5.0)];
assert!((inner_f64(vector_norm_linf(&v)) - 5.0).abs() < 1e-12);
}
#[test]
fn linf_leq_l1() {
let v = [c(1.0, 2.0), c(3.0, 4.0)];
assert!(inner_f64(vector_norm_linf(&v)) <= inner_f64(vector_norm_l1(&v)) + 1e-14);
}
#[test]
fn linf_leq_l2() {
let v = [c(1.0, 2.0), c(3.0, 4.0)];
let linf = inner_f64(vector_norm_linf(&v));
let l2 = inner_f64(vector_norm_l2(&v));
let n = v.len() as f64;
assert!(linf <= l2 + 1e-14);
assert!(l2 <= n.sqrt() * linf + 1e-14);
}
#[test]
fn zero_vector() {
let v = [c(0.0, 0.0), c(0.0, 0.0)];
assert_eq!(inner_f64(vector_norm_linf(&v)), 0.0);
}
#[test]
fn parallel_matches_sequential() {
let v = [c(1.0, 2.0), c(3.0, 4.0), c(-2.0, -1.0)];
let seq = inner_f64(vector_norm_linf(&v));
let par = inner_f64(vector_norm_linf_par(&v));
assert!((seq - par).abs() < 1e-14);
}
}
}
}