#![deny(rustdoc::broken_intra_doc_links)]
use crate::RealScalar;
pub fn l2_norm<T: RealScalar>(x: &[T]) -> T {
let mut scale = T::zero();
let mut sumsq = T::zero();
let zero = T::zero();
for xi in x.iter().cloned() {
let abs_xi = xi.abs();
if abs_xi == zero {
continue;
}
if scale < abs_xi {
if scale > zero {
let r = scale.clone() / &abs_xi;
sumsq *= r.pow(2);
}
scale = abs_xi.clone();
}
let r = abs_xi.clone() / &scale;
sumsq += r.pow(2);
}
if scale == zero {
zero
} else {
scale * sumsq.sqrt()
}
}
#[cfg(test)]
mod 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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
assert_eq!(*norm.as_ref(), 0.0);
}
#[test]
fn single_positive_element() {
let data = vec_f64(&[7.0]);
let norm = l2_norm(&data);
assert_relative_eq!(*norm.as_ref(), 7.0, epsilon = 1e-15);
}
#[test]
fn single_negative_element() {
let data = vec_f64(&[-7.0]);
let norm = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
assert_relative_eq!(*norm.as_ref(), 5.0, epsilon = 1e-15);
}
#[test]
fn single_zero() {
let data = vec_f64(&[0.0]);
let norm = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
assert_eq!(*norm.as_ref(), f64::MAX);
}
#[test]
fn min_positive_value() {
let data = vec_f64(&[f64::MIN_POSITIVE]);
let norm = l2_norm(&data);
assert_eq!(*norm.as_ref(), f64::MIN_POSITIVE);
}
#[test]
fn epsilon() {
let data = vec_f64(&[f64::EPSILON]);
let norm = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
assert_eq!(norm, R::zero());
}
#[test]
fn single_element() {
let data = vec![rug_f64(7.0)];
let norm = l2_norm(&data);
let seven = rug_f64(7.0);
assert_eq!(norm, seven);
}
#[test]
fn single_negative() {
let data = vec![rug_f64(-7.0)];
let norm = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
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 = l2_norm(&data);
let five = hp_f64(5.0);
let diff = (norm.clone() - &five).abs();
assert!(diff < HP::epsilon());
}
}
}