num_valid/algorithms/vector_norms.rs
1//! Functions and utilities for computing vector norms.
2//!
3//! This module provides public utilities for vector-norm computation on validated
4//! scalars (`ScalarType: FpScalar`), supporting both real
5//! ([`RealScalar`]) and complex
6//! ([`ComplexScalar`](crate::ComplexScalar)) scalars.
7//!
8//! # Norm type structs
9//!
10//! Three zero-sized structs identify norm kinds and implement the [`VectorNorm`]
11//! and [`ParallelVectorNorm`] traits:
12//!
13//! | Struct | Formula | Accumulator |
14//! |--------|---------|-------------|
15//! | [`L1Norm<Acc>`] | `‖v‖₁ = Σᵢ \|vᵢ\|` | [`SumAccumulator`] |
16//! | [`L2Norm<Acc>`] | `‖v‖₂ = √(Σᵢ \|vᵢ\|²)` | [`SumAccumulator`] |
17//! | [`LinfNorm<Acc>`] | `‖v‖∞ = maxᵢ \|vᵢ\|` | [`MaxAbsValueAccumulator`] |
18//!
19//! The type parameter `Acc` selects the summation strategy:
20//! - `NaiveSum<R>` — fast plain `+=` (default for most functions)
21//! - `NeumaierSum<R>` — compensated summation for higher accuracy
22//!
23//! For the two most common combinations, convenience type aliases are provided:
24//!
25//! | Alias | Expands to |
26//! |-------|------------|
27//! | [`L1NormNaiveSum<T>`] | `L1Norm<NaiveSum<T::RealType>>` |
28//! | [`L1NormNeumaierSum<T>`] | `L1Norm<NeumaierSum<T::RealType>>` |
29//! | [`L2NormNaiveSum<T>`] | `L2Norm<NaiveSum<T::RealType>>` |
30//! | [`L2NormNeumaierSum<T>`] | `L2Norm<NeumaierSum<T::RealType>>` |
31//!
32//! ## Direct usage
33//!
34//! The structs can be used directly via their static methods — useful when you
35//! need a specific norm+strategy combination without going through the free
36//! functions. The type aliases make the type annotations shorter:
37//!
38//! ```
39//! use num_valid::{*, algorithms::vector_norms::*};
40//! use num_valid::algorithms::accumulators::{NaiveSum, NeumaierSum};
41//!
42//! let v = [3.0_f64, -4.0, 5.0];
43//!
44//! // Using a type alias — equivalent to L1Norm::<NaiveSum<f64>>::compute_vector_norm(&v)
45//! let l1 = L1NormNaiveSum::<f64>::compute_vector_norm(&v);
46//! assert_eq!(*l1.as_raw_scalar_ref(), 12.0);
47//!
48//! // L2 norm with Neumaier compensated summation (higher accuracy)
49//! let l2 = L2NormNeumaierSum::<f64>::compute_vector_norm(&v);
50//! assert!((*l2.as_raw_scalar_ref() - 50.0_f64.sqrt()).abs() < 1e-12);
51//!
52//! // L∞ norm (default marker type `Acc = ()`)
53//! let linf = LinfNorm::<()>::compute_vector_norm(&v);
54//! assert_eq!(linf.as_raw_scalar_ref(), &5.0);
55//!
56//! // L∞ norm in parallel
57//! let linf_par = LinfNorm::<()>::compute_vector_norm_par(&v);
58//! assert_eq!(linf_par, linf);
59//!
60//! // L2 squared norm (not a norm, but avoids the sqrt)
61//! let l2_sq = L2NormNaiveSum::<f64>::compute_vector_norm_sq(&v);
62//! assert_eq!(*l2_sq.as_raw_scalar_ref(), 50.0);
63//! ```
64//!
65//! # Trait-based API
66//!
67//! [`VectorNorm<ScalarType>`] and [`ParallelVectorNorm<T>`] provide a generic
68//! interface that lets algorithms be parameterised over the norm type:
69//!
70//! ```
71//! use num_valid::{
72//! *,
73//! algorithms::{accumulators::NaiveSum, vector_norms::{VectorNorm, L2Norm}},
74//! scalars::NonNegativeRealScalar,
75//! };
76//!
77//! fn compute<T: FpScalar, N: VectorNorm<T>>(data: &[T]) -> NonNegativeRealScalar<T::RealType> {
78//! N::compute_vector_norm(data)
79//! }
80//!
81//! let v = [3.0_f64, 4.0];
82//! let norm = compute::<_, L2Norm<NaiveSum<f64>>>(&v);
83//! assert!((*norm.as_raw_scalar_ref() - 5.0).abs() < 1e-12);
84//! ```
85//!
86//! # Free functions
87//!
88//! Ergonomic wrappers for the most common use cases. All variants accept both
89//! slice (`&[ScalarType]`) and owned-iterator (`_iter`-suffixed) inputs.
90//! All return [`NonNegativeRealScalar<ScalarType::RealType>`].
91//!
92//! For real scalars `RealType = ScalarType`; for complex scalars `RealType` is
93//! the underlying real component type — the norm is always real-valued because
94//! each element contributes via its modulus `|·|`.
95//!
96//! When the input is borrowed (e.g. `slice.iter()`), chain `.cloned()` before
97//! passing to an `_iter`-suffixed function.
98//!
99//! ## L2 (Euclidean) norms — `‖v‖₂ = √(Σᵢ |vᵢ|²)`
100//!
101//! | Function | Formula | Summation |
102//! |----------|---------|-----------|
103//! | [`vector_norm_l2`] | `‖v‖₂` | naive |
104//! | [`vector_norm_l2_iter`] | `‖v‖₂` | naive |
105//! | [`vector_norm_l2_neumaier`] | `‖v‖₂` | Neumaier compensated |
106//! | [`vector_norm_l2_neumaier_iter`] | `‖v‖₂` | Neumaier compensated |
107//! | [`vector_norm_l2_sq`] | `‖v‖₂²` | naive |
108//! | [`vector_norm_l2_sq_iter`] | `‖v‖₂²` | naive |
109//! | [`vector_norm_l2_sq_neumaier`] | `‖v‖₂²` | Neumaier compensated |
110//! | [`vector_norm_l2_sq_neumaier_iter`] | `‖v‖₂²` | Neumaier compensated |
111//!
112//! All L2 variants use a **scaled sum-of-squares** algorithm (equivalent to
113//! LAPACK `dnrm2`): the running `scale = max |vᵢ|` ensures that no intermediate
114//! value overflows or underflows. The `_neumaier` variants additionally apply
115//! compensated summation to recover low-order bits lost when components span
116//! many orders of magnitude.
117//!
118//! Note: `‖v‖₂²` is not a norm (it violates the triangle inequality) and is
119//! therefore intentionally absent from the [`VectorNorm`] trait. It is exposed
120//! as a computational convenience and as a method on [`L2Norm`].
121//!
122//! ## L1 (Manhattan) norm — `‖v‖₁ = Σᵢ |vᵢ|`
123//!
124//! | Function | Formula | Summation |
125//! |----------|---------|-----------|
126//! | [`vector_norm_l1`] | `‖v‖₁` | naive |
127//! | [`vector_norm_l1_iter`] | `‖v‖₁` | naive |
128//! | [`vector_norm_l1_neumaier`] | `‖v‖₁` | Neumaier compensated |
129//! | [`vector_norm_l1_neumaier_iter`] | `‖v‖₁` | Neumaier compensated |
130//!
131//! ## L∞ (Chebyshev) norm — `‖v‖∞ = maxᵢ |vᵢ|`
132//!
133//! | Function | Execution |
134//! |----------|-----------|
135//! | [`vector_norm_linf`] | sequential, slice |
136//! | [`vector_norm_linf_iter`] | sequential, iterator |
137//! | [`vector_norm_linf_par`] | parallel, slice |
138//! | [`vector_norm_linf_par_iter`] | parallel, iterator |
139//!
140//! ## Advanced API — custom accumulator
141//!
142//! For full control over the summation strategy use the accumulator-generic
143//! functions. These accept any [`SumAccumulator`] as a type parameter:
144//! - [`vector_norm_l1_with_accumulator`] / [`vector_norm_l1_iter_with_accumulator`]
145//! - [`vector_norm_l2_with_accumulator`] / [`vector_norm_l2_iter_with_accumulator`]
146//! - [`vector_norm_l2_sq_with_accumulator`] / [`vector_norm_l2_sq_iter_with_accumulator`]
147
148#![deny(rustdoc::broken_intra_doc_links)]
149
150use crate::{
151 FpScalar, RealScalar,
152 algorithms::accumulators::{
153 Accumulator, MaxAbsValueAccumulator, NaiveSum, NeumaierSum, ScaledSumSqAccumulator,
154 SumAccumulator,
155 },
156 scalars::NonNegativeRealScalar,
157};
158use rayon::prelude::*;
159use std::marker::PhantomData;
160
161//---------------------------------------------------------
162/// Identifies the Manhattan (L1) norm, `‖v‖₁ = Σᵢ |vᵢ|`.
163///
164/// The type parameter `Acc` selects the summation strategy for absolute values.
165/// Typical choices:
166/// - `NaiveSum<R>` — plain `+=`, fastest option.
167/// - `NeumaierSum<R>` — compensated summation, more accurate when components
168/// span many orders of magnitude.
169///
170/// `L1Norm<Acc>` implements [`VectorNorm`] and [`ParallelVectorNorm`] for any
171/// [`FpScalar`] whose `RealType` matches `Acc::Input`.
172pub struct L1Norm<Acc>(PhantomData<Acc>);
173
174/// Type alias for [`L1Norm`] using [`NaiveSum`] as summation strategy.
175///
176/// `L1NormNaiveSum<T>` expands to `L1Norm<NaiveSum<NonNegativeRealScalar<T::RealType>>>`. Use this
177/// when performance is the priority and the data is well-scaled.
178pub type L1NormNaiveSum<ScalarType> =
179 L1Norm<NaiveSum<NonNegativeRealScalar<<ScalarType as FpScalar>::RealType>>>;
180
181/// Type alias for [`L1Norm`] using [`NeumaierSum`] as summation strategy.
182///
183/// `L1NormNeumaierSum<T>` expands to `L1Norm<NeumaierSum<NonNegativeRealScalar<T::RealType>>>`. Use
184/// this when components span many orders of magnitude and accuracy matters.
185pub type L1NormNeumaierSum<ScalarType> =
186 L1Norm<NeumaierSum<NonNegativeRealScalar<<ScalarType as FpScalar>::RealType>>>;
187
188//---------------------------------------------------------
189
190//---------------------------------------------------------
191/// Identifies the Euclidean (L2) norm, `‖v‖₂ = √(Σᵢ |vᵢ|²)`.
192///
193/// The type parameter `Acc` selects the summation strategy for the scaled
194/// sum-of-squares loop. Typical choices:
195/// - `NaiveSum<R>` — plain `+=`, fastest option.
196/// - `NeumaierSum<R>` — compensated summation for higher accuracy when
197/// components span many orders of magnitude.
198///
199/// `L2Norm<Acc>` implements [`VectorNorm`] and [`ParallelVectorNorm`] for any
200/// [`FpScalar`] whose `RealType` matches `Acc::Input`.
201///
202/// In addition to the standard norm, `L2Norm` exposes the squared Euclidean
203/// norm `‖v‖₂² = Σᵢ |vᵢ|²` as a separate operation (avoiding the square root)
204/// via [`L2Norm::compute_vector_norm_sq`] and its iterator/parallel variants.
205/// Note that `‖v‖₂²` is not a norm — it violates the triangle inequality — and
206/// is therefore not part of the [`VectorNorm`] trait.
207pub struct L2Norm<Acc>(PhantomData<Acc>);
208
209/// Type alias for [`L2Norm`] using [`NaiveSum`] as summation strategy.
210///
211/// `L2NormNaiveSum<T>` expands to `L2Norm<NaiveSum<T::RealType>>`. Use this
212/// when performance is the priority and the data is well-scaled.
213pub type L2NormNaiveSum<ScalarType> = L2Norm<NaiveSum<<ScalarType as FpScalar>::RealType>>;
214
215/// Type alias for [`L2Norm`] using [`NeumaierSum`] as summation strategy.
216///
217/// `L2NormNeumaierSum<T>` expands to `L2Norm<NeumaierSum<T::RealType>>`. Use
218/// this when components span many orders of magnitude and accuracy matters.
219pub type L2NormNeumaierSum<ScalarType> = L2Norm<NeumaierSum<<ScalarType as FpScalar>::RealType>>;
220
221impl<Acc: SumAccumulator<Input: RealScalar>> L2Norm<Acc> {
222 /// Computes the squared Euclidean (L2) norm `‖v‖₂²` of a slice.
223 ///
224 /// Equivalent to [`compute_vector_norm_sq_iter`](Self::compute_vector_norm_sq_iter)
225 /// called with `v.iter().cloned()`.
226 ///
227 /// This avoids the square root present in [`VectorNorm::compute_vector_norm`]
228 /// and is useful when only relative comparisons of norms are needed.
229 pub fn compute_vector_norm_sq<T>(v: &[T]) -> NonNegativeRealScalar<Acc::Output>
230 where
231 T: FpScalar<RealType = Acc::Output>,
232 {
233 Self::compute_vector_norm_sq_iter(v.iter().cloned())
234 }
235
236 /// Computes the squared Euclidean (L2) norm `‖v‖₂²` from an owned iterator.
237 ///
238 /// Uses the same scaled sum-of-squares algorithm as [`VectorNorm`] but
239 /// returns `scale² × sumsq` directly without taking the square root.
240 pub fn compute_vector_norm_sq_iter<I>(v: I) -> NonNegativeRealScalar<Acc::Output>
241 where
242 I: IntoIterator<Item: FpScalar<RealType = Acc::Output>>,
243 {
244 ScaledSumSqAccumulator::<Acc>::new_sequential_from_scalars(v).result()
245 }
246}
247
248impl<Acc> L2Norm<Acc>
249where
250 Acc: SumAccumulator<Input: RealScalar> + Send,
251{
252 /// Computes the squared Euclidean (L2) norm `‖v‖₂²` of a slice in parallel.
253 ///
254 /// Equivalent to [`compute_vector_norm_sq_par_iter`](Self::compute_vector_norm_sq_par_iter)
255 /// called with `v.par_iter().cloned()`.
256 pub fn compute_vector_norm_sq_par<T>(v: &[T]) -> NonNegativeRealScalar<Acc::Output>
257 where
258 T: FpScalar<RealType = Acc::Output>,
259 {
260 Self::compute_vector_norm_sq_par_iter(v.par_iter().cloned())
261 }
262
263 /// Computes the squared Euclidean (L2) norm `‖v‖₂²` from a parallel iterator.
264 ///
265 /// Chunks are processed in parallel and merged via
266 /// [`Accumulator::combine`],
267 /// preserving the numerical properties of the chosen `Acc` strategy across chunks.
268 pub fn compute_vector_norm_sq_par_iter<I>(v: I) -> NonNegativeRealScalar<Acc::Output>
269 where
270 I: IntoParallelIterator<Item: FpScalar<RealType = Acc::Output>>,
271 {
272 ScaledSumSqAccumulator::<Acc>::new_parallel_from_scalars(v).result()
273 }
274}
275
276//---------------------------------------------------------
277
278//pub struct L2SqNorm<Strategy>(PhantomData<Strategy>);
279
280//---------------------------------------------------------
281/// Identifies the Chebyshev (L∞) norm, `‖v‖∞ = maxᵢ |vᵢ|`.
282///
283/// Unlike [`L1Norm`] and [`L2Norm`], `LinfNorm` does not use a summation
284/// strategy: it is a single linear scan for the maximum absolute value.
285///
286/// The `Acc` parameter is currently a marker type (default `()`) and is not
287/// used by the vector implementation itself. It exists to keep the API
288/// extensible and to align type-level dispatch with norm families where a
289/// dedicated accumulator may be useful in related contexts (for example,
290/// matrix norms).
291///
292/// `LinfNorm` implements both [`VectorNorm`] and [`ParallelVectorNorm`] for any
293/// [`FpScalar`]. The parallel variant uses
294/// [`MaxAbsValueAccumulator`]
295/// with rayon `fold` + `reduce`, which is numerically exact (no rounding).
296pub struct LinfNorm<Acc = ()>(PhantomData<Acc>);
297//---------------------------------------------------------
298
299//---------------------------------------------------------
300/// Sequential vector norm computation.
301///
302/// Implemented by [`L1Norm`], [`L2Norm`], and [`LinfNorm`]. Generic code that
303/// accepts any norm should bound `N: VectorNorm<T>`.
304///
305/// All implementations accept both real and complex scalars: for complex types
306/// each element contributes via its modulus `|zᵢ|`, so the result is always
307/// a real-valued [`NonNegativeRealScalar`].
308///
309/// # Methods
310///
311/// - [`compute_vector_norm`](VectorNorm::compute_vector_norm) — slice input
312/// (delegates to `compute_vector_norm_iter` by default).
313/// - [`compute_vector_norm_iter`](VectorNorm::compute_vector_norm_iter) — owned
314/// iterator input; **must** be implemented by each norm type.
315pub trait VectorNorm<ScalarType: FpScalar> {
316 /// Computes the norm of a slice of scalars.
317 ///
318 /// The default implementation calls [`compute_vector_norm_iter`](Self::compute_vector_norm_iter)
319 /// with `v.iter().cloned()`.
320 fn compute_vector_norm(v: &[ScalarType]) -> NonNegativeRealScalar<ScalarType::RealType> {
321 Self::compute_vector_norm_iter(v.iter().cloned())
322 }
323
324 /// Computes the norm from an owned-item iterator.
325 ///
326 /// Iterator-based APIs require **owned** items. When the input is borrowed
327 /// (e.g. from a slice), chain `.iter().cloned()` before calling this method.
328 fn compute_vector_norm_iter<I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
329 where
330 I: IntoIterator<Item = ScalarType>;
331}
332
333/// Parallel vector norm computation.
334///
335/// Extends [`VectorNorm`] with parallel variants that distribute work across
336/// threads using rayon. Chunks are accumulated independently and merged via
337/// [`Accumulator::combine`].
338///
339/// Requires `T: Send + Sync`.
340///
341/// # Methods
342///
343/// - [`compute_vector_norm_par`](ParallelVectorNorm::compute_vector_norm_par) — parallel slice.
344/// - [`compute_vector_norm_par_iter`](ParallelVectorNorm::compute_vector_norm_par_iter) —
345/// parallel iterator; **must** be implemented by each norm type.
346pub trait ParallelVectorNorm<T: FpScalar + Send + Sync>: VectorNorm<T> {
347 /// Computes the norm of a slice in parallel.
348 ///
349 /// The default implementation calls
350 /// [`compute_vector_norm_par_iter`](Self::compute_vector_norm_par_iter)
351 /// with `v.par_iter().cloned()`.
352 fn compute_vector_norm_par(v: &[T]) -> NonNegativeRealScalar<T::RealType> {
353 Self::compute_vector_norm_par_iter(v.par_iter().cloned())
354 }
355
356 /// Computes the norm from an owned parallel iterator.
357 fn compute_vector_norm_par_iter<I: IntoParallelIterator<Item = T>>(
358 iter: I,
359 ) -> NonNegativeRealScalar<T::RealType>;
360}
361//---------------------------------------------------------
362
363//---------------------------------------------------------
364impl<ScalarType, SumAcc> VectorNorm<ScalarType> for L2Norm<SumAcc>
365where
366 ScalarType: FpScalar,
367 SumAcc: SumAccumulator<Input = ScalarType::RealType>,
368{
369 fn compute_vector_norm_iter<I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
370 where
371 I: IntoIterator<Item = ScalarType>,
372 {
373 ScaledSumSqAccumulator::<SumAcc>::new_sequential_from_scalars(v).result_sqrt()
374 }
375}
376
377impl<T, SumAcc> ParallelVectorNorm<T> for L2Norm<SumAcc>
378where
379 T: FpScalar,
380 SumAcc: SumAccumulator<Input = T::RealType> + Send,
381{
382 fn compute_vector_norm_par_iter<I: IntoParallelIterator<Item = T>>(
383 iter: I,
384 ) -> NonNegativeRealScalar<T::RealType> {
385 ScaledSumSqAccumulator::<SumAcc>::new_parallel_from_scalars(iter).result_sqrt()
386 }
387}
388//---------------------------------------------------------
389
390//---------------------------------------------------------
391impl<ScalarType, SumAcc> VectorNorm<ScalarType> for L1Norm<SumAcc>
392where
393 ScalarType: FpScalar,
394 SumAcc: SumAccumulator<Input = NonNegativeRealScalar<ScalarType::RealType>>,
395{
396 fn compute_vector_norm_iter<I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
397 where
398 I: IntoIterator<Item = ScalarType>,
399 {
400 SumAcc::new_sequential(v.into_iter().map(|x| x.abs())).result()
401 }
402}
403
404impl<T, SumAcc> ParallelVectorNorm<T> for L1Norm<SumAcc>
405where
406 T: FpScalar,
407 SumAcc: SumAccumulator<Input = NonNegativeRealScalar<T::RealType>> + Send,
408{
409 fn compute_vector_norm_par_iter<I: IntoParallelIterator<Item = T>>(
410 iter: I,
411 ) -> NonNegativeRealScalar<T::RealType> {
412 SumAcc::new_parallel(iter.into_par_iter().map(|x| x.abs())).result()
413 }
414}
415
416//---------------------------------------------------------
417
418//---------------------------------------------------------
419impl<ScalarType> VectorNorm<ScalarType> for LinfNorm
420where
421 ScalarType: FpScalar,
422{
423 fn compute_vector_norm_iter<I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
424 where
425 I: IntoIterator<Item = ScalarType>,
426 {
427 MaxAbsValueAccumulator::new_sequential(v).result()
428 }
429}
430
431impl<ScalarType> ParallelVectorNorm<ScalarType> for LinfNorm
432where
433 ScalarType: FpScalar,
434{
435 fn compute_vector_norm_par_iter<I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
436 where
437 I: IntoParallelIterator<Item = ScalarType>,
438 {
439 MaxAbsValueAccumulator::new_parallel(v).result()
440 }
441}
442//---------------------------------------------------------
443
444// ── Public L2 norm functions ─────────────────────────────────────────────────
445
446/// Accumulator-driven implementation of the Euclidean (L2) norm.
447///
448/// This helper computes `vector_norm_l2` using the provided accumulation strategy
449/// `SumSqAcc` for the scaled sum-of-squares term.
450///
451/// The final value is guaranteed to be non-negative by construction and is
452/// wrapped into [`NonNegativeRealScalar`].
453///
454/// # Panics
455///
456/// Panics only if the computed value is negative, which indicates an internal
457/// invariant violation.
458#[inline(always)]
459pub fn vector_norm_l2_with_accumulator<ScalarType, SumSqAcc>(
460 v: &[ScalarType],
461) -> NonNegativeRealScalar<ScalarType::RealType>
462where
463 ScalarType: FpScalar,
464 SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
465{
466 L2Norm::<SumSqAcc>::compute_vector_norm(v)
467}
468
469/// Iterator-based accumulator-driven implementation of the Euclidean (L2) norm.
470///
471/// This is the iterator counterpart of [`vector_norm_l2_with_accumulator`] and accepts
472/// any `IntoIterator<Item = ScalarType>`.
473///
474/// Use this when your inputs are already produced as an iterator chain.
475#[inline(always)]
476pub fn vector_norm_l2_iter_with_accumulator<ScalarType, SumSqAcc, I>(
477 v: I,
478) -> NonNegativeRealScalar<ScalarType::RealType>
479where
480 ScalarType: FpScalar,
481 SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
482 I: IntoIterator<Item = ScalarType>,
483{
484 L2Norm::<SumSqAcc>::compute_vector_norm_iter(v)
485}
486
487/// Accumulator-driven implementation of the squared Euclidean (L2) norm.
488///
489/// This function computes `vector_norm_l2_sq` using the provided accumulation
490/// strategy `SumSqAcc` for the scaled sum-of-squares term.
491///
492/// Use this API when you need explicit control over how terms are accumulated,
493/// for example to compare [`NaiveSum`] with [`NeumaierSum`] or a custom
494/// accumulator implementation.
495///
496/// The final value is guaranteed to be non-negative by construction and is
497/// wrapped into [`NonNegativeRealScalar`].
498///
499/// # Examples
500///
501/// ```
502/// use num_valid::{
503/// *,
504/// algorithms::{
505/// accumulators::{NaiveSum, NeumaierSum},
506/// vector_norms::vector_norm_l2_sq_with_accumulator,
507/// },
508/// };
509///
510/// let v = [3.0_f64, 4.0];
511/// let naive = vector_norm_l2_sq_with_accumulator::<_, NaiveSum<_>>(&v);
512/// let compensated = vector_norm_l2_sq_with_accumulator::<_, NeumaierSum<_>>(&v);
513/// assert_eq!(naive.as_raw_scalar_ref(), &25.0);
514/// assert_eq!(naive, compensated);
515/// ```
516///
517/// # Panics
518///
519/// Panics only if the computed value is negative, which indicates an internal
520/// invariant violation.
521#[inline(always)]
522pub fn vector_norm_l2_sq_with_accumulator<ScalarType, SumSqAcc>(
523 v: &[ScalarType],
524) -> NonNegativeRealScalar<ScalarType::RealType>
525where
526 ScalarType: FpScalar,
527 SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
528{
529 L2Norm::<SumSqAcc>::compute_vector_norm_sq(v)
530}
531
532/// Iterator-based accumulator-driven implementation of the squared Euclidean
533/// (L2) norm.
534///
535/// This is the iterator counterpart of [`vector_norm_l2_sq_with_accumulator`] and
536/// accepts any `IntoIterator<Item = ScalarType>`.
537#[inline(always)]
538pub fn vector_norm_l2_sq_iter_with_accumulator<ScalarType, SumSqAcc, I>(
539 v: I,
540) -> NonNegativeRealScalar<ScalarType::RealType>
541where
542 ScalarType: FpScalar,
543 SumSqAcc: SumAccumulator<Input = ScalarType::RealType>,
544 I: IntoIterator<Item = ScalarType>,
545{
546 L2Norm::<SumSqAcc>::compute_vector_norm_sq_iter(v)
547}
548
549/// Accumulator-driven implementation of the Manhattan (L1) norm.
550///
551/// This function computes `vector_norm_l1` by summing absolute values with the
552/// accumulation strategy `SumAbsAcc`.
553///
554/// Use this API when you need explicit control over how absolute values are
555/// accumulated, for example to compare [`NaiveSum`] with [`NeumaierSum`] or a
556/// custom accumulator implementation.
557///
558/// The final value is guaranteed to be non-negative by construction and is
559/// wrapped into [`NonNegativeRealScalar`].
560///
561/// # Examples
562///
563/// ```
564/// use num_valid::{
565/// *,
566/// algorithms::{
567/// vector_norms::vector_norm_l1_with_accumulator,
568/// accumulators::{NaiveSum, NeumaierSum},
569/// },
570/// };
571///
572/// let v = [3.0_f64, 4.0];
573/// let naive = vector_norm_l1_with_accumulator::<_, NaiveSum<_>>(&v);
574/// let compensated = vector_norm_l1_with_accumulator::<_, NeumaierSum<_>>(&v);
575/// assert_eq!(naive.as_raw_scalar_ref(), &7.0);
576/// assert_eq!(naive, compensated);
577/// ```
578///
579/// # Panics
580///
581/// Panics only if the computed value is negative, which indicates an internal
582/// invariant violation.
583#[inline(always)]
584pub fn vector_norm_l1_with_accumulator<ScalarType, SumAcc>(
585 a: &[ScalarType],
586) -> NonNegativeRealScalar<ScalarType::RealType>
587where
588 ScalarType: FpScalar,
589 SumAcc: SumAccumulator<Input = NonNegativeRealScalar<ScalarType::RealType>>,
590{
591 L1Norm::<SumAcc>::compute_vector_norm(a)
592}
593
594/// Iterator-based accumulator-driven implementation of the Manhattan (L1) norm.
595///
596/// This is the iterator counterpart of [`vector_norm_l1_with_accumulator`] and accepts
597/// any `IntoIterator<Item = ScalarType>`.
598#[inline(always)]
599pub fn vector_norm_l1_iter_with_accumulator<ScalarType, SumAcc, I>(
600 a: I,
601) -> NonNegativeRealScalar<ScalarType::RealType>
602where
603 ScalarType: FpScalar,
604 SumAcc: SumAccumulator<Input = NonNegativeRealScalar<ScalarType::RealType>>,
605 I: IntoIterator<Item = ScalarType>,
606{
607 L1Norm::<SumAcc>::compute_vector_norm_iter(a)
608}
609
610/// Computes the Euclidean (L2) norm of a slice of scalars.
611///
612/// Returns ‖**v**‖₂ = √(Σᵢ |vᵢ|²), where |·| denotes the modulus (absolute
613/// value for reals, complex modulus for complex scalars).
614///
615/// # Numerical Stability
616///
617/// The result is computed using a **scaled sum-of-squares** algorithm: the
618/// running `scale` equals the largest |vᵢ| seen so far and `sumsq`
619/// accumulates (|vᵢ| / scale)². The final result is `scale × √sumsq`. This
620/// avoids overflow and underflow of intermediate values.
621///
622/// The algorithm is equivalent to the one used in LAPACK's `dnrm2` routine.
623/// Note: scaling alone does not prevent the loss of small addends when they are
624/// absorbed into a larger running total (absorption error); use [`vector_norm_l2_neumaier`]
625/// when that precision is required.
626///
627/// Equivalent to calling the internal accumulator-driven implementation with
628/// [`NaiveSum`].
629///
630/// # Examples
631///
632/// ```
633/// use num_valid::{*, algorithms::vector_norms::vector_norm_l2};
634///
635/// // 3-4-5 right triangle: norm of (3, 4) is 5
636/// let v = [3.0_f64, 4.0];
637/// assert!((*vector_norm_l2(&v).as_raw_scalar_ref() - 5.0).abs() < 1e-12);
638///
639/// // Zero vector
640/// let z = [0.0_f64; 3];
641/// assert_eq!(vector_norm_l2(&z).as_raw_scalar_ref(), &0.0);
642///
643/// // Unit axis vector has norm 1
644/// let e1 = [1.0_f64, 0.0, 0.0];
645/// assert!((*vector_norm_l2(&e1).as_raw_scalar_ref() - 1.0).abs() < 1e-15);
646/// ```
647///
648/// Complex scalars are also supported; `|zᵢ|` is the complex modulus:
649///
650/// ```
651/// use num_valid::{*, algorithms::vector_norms::vector_norm_l2};
652/// use num::Complex;
653/// use try_create::TryNew;
654///
655/// // ‖[3+0i, 0+4i]‖₂ = √(3² + 4²) = 5
656/// let v = [
657/// ComplexNative64StrictFinite::try_new(Complex::new(3.0_f64, 0.0)).unwrap(),
658/// ComplexNative64StrictFinite::try_new(Complex::new(0.0_f64, 4.0)).unwrap(),
659/// ];
660/// let result = vector_norm_l2(&v).into_inner();
661/// assert!((*result.as_raw_scalar_ref() - 5.0_f64).abs() < 1e-12);
662/// ```
663#[inline(always)]
664pub fn vector_norm_l2<ScalarType>(v: &[ScalarType]) -> NonNegativeRealScalar<ScalarType::RealType>
665where
666 ScalarType: FpScalar,
667{
668 L2NormNaiveSum::<ScalarType>::compute_vector_norm(v)
669}
670
671/// Computes the Euclidean (L2) norm from an owned-item iterator.
672///
673/// This is the iterator counterpart of [`vector_norm_l2`].
674///
675/// # Examples
676///
677/// ```
678/// use num_valid::{*, algorithms::vector_norms::vector_norm_l2_iter};
679///
680/// let v = vec![3.0_f64, 4.0];
681/// assert!((*vector_norm_l2_iter(v).as_raw_scalar_ref() - 5.0).abs() < 1e-12);
682/// ```
683#[inline(always)]
684pub fn vector_norm_l2_iter<ScalarType, I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
685where
686 ScalarType: FpScalar,
687 I: IntoIterator<Item = ScalarType>,
688{
689 L2NormNaiveSum::<ScalarType>::compute_vector_norm_iter(v)
690}
691
692/// Computes the squared Euclidean (L2) norm of a slice of scalars.
693///
694/// Returns ‖**v**‖₂² = Σᵢ |vᵢ|², where |·| denotes the modulus.
695///
696/// # Numerical Stability
697///
698/// The result is computed using a **scaled sum-of-squares** algorithm: the
699/// running `scale` equals the largest |vᵢ| seen so far and `sumsq`
700/// accumulates (|vᵢ| / scale)². The final result is `scale² × sumsq`. This
701/// avoids overflow and underflow of intermediate values.
702///
703/// Note: `scale²` must not overflow the maximum representable value of the
704/// underlying real type. For vectors where accuracy of small squared terms
705/// matters, prefer [`vector_norm_l2_sq_neumaier`].
706///
707/// Equivalent to calling [`vector_norm_l2_sq_with_accumulator`] with [`NaiveSum`].
708///
709/// # Examples
710///
711/// ```
712/// use num_valid::{*, algorithms::vector_norms::vector_norm_l2_sq};
713///
714/// // 3-4-5 right triangle: ‖(3, 4)‖₂² == 9 + 16 == 25
715/// let v = [3.0_f64, 4.0];
716/// assert_eq!(vector_norm_l2_sq(&v).as_raw_scalar_ref(), &25.0);
717///
718/// // Zero vector
719/// let z = [0.0_f64; 3];
720/// assert_eq!(vector_norm_l2_sq(&z).as_raw_scalar_ref(), &0.0);
721///
722/// // Unit axis vector has squared norm 1
723/// let e1 = [1.0_f64, 0.0, 0.0];
724/// assert_eq!(vector_norm_l2_sq(&e1).as_raw_scalar_ref(), &1.0);
725/// ```
726#[inline(always)]
727pub fn vector_norm_l2_sq<ScalarType: FpScalar>(
728 v: &[ScalarType],
729) -> NonNegativeRealScalar<ScalarType::RealType> {
730 L2NormNaiveSum::<ScalarType>::compute_vector_norm_sq(v)
731}
732
733/// Computes the squared Euclidean (L2) norm from an owned-item iterator.
734///
735/// This is the iterator counterpart of [`vector_norm_l2_sq`].
736#[inline(always)]
737pub fn vector_norm_l2_sq_iter<ScalarType, I>(v: I) -> NonNegativeRealScalar<ScalarType::RealType>
738where
739 ScalarType: FpScalar,
740 I: IntoIterator<Item = ScalarType>,
741{
742 L2NormNaiveSum::<ScalarType>::compute_vector_norm_sq_iter(v)
743}
744
745/// Computes the squared Euclidean (L2) norm of a slice of scalars using
746/// **scaling + Neumaier compensated summation**.
747///
748/// Returns ‖**v**‖₂² = Σᵢ |vᵢ|², where |·| denotes the modulus.
749///
750/// # Numerical Accuracy
751///
752/// This is an improved variant of [`vector_norm_l2_sq`] that combines two complementary
753/// techniques:
754///
755/// 1. **Scaling** (same as [`vector_norm_l2_sq`]): tracks `scale = max |vᵢ|` and
756/// accumulates `Σ (|vᵢ|/scale)²`, so that no individual term overflows or
757/// underflows before being squared.
758///
759/// 2. **Neumaier compensated summation**: replaces the plain `sumsq += r²` with
760/// Kahan/Neumaier compensation, capturing low-order bits that are silently
761/// discarded in the naive loop. This is particularly beneficial when a large
762/// dominant component (near `scale`) causes the unit-in-last-place of `sumsq`
763/// to exceed many small squared terms.
764///
765/// **When rescaling occurs** (a new maximum is found mid-iteration), the
766/// accumulated Neumaier sum is multiplied by `r²` and the compensation term is
767/// reset to zero. Rescaling is rare in practice (occurs at most log(N) times for
768/// random data), so the accuracy benefit of Neumaier is preserved for the bulk
769/// of the summation.
770///
771/// # Special Cases
772///
773/// | Input | Result |
774/// |-------|--------|
775/// | all-zero vector | `0.0` |
776/// | single dominant component | full Neumaier accuracy for the remaining terms |
777///
778/// # Examples
779///
780/// ```
781/// use num_valid::{*, algorithms::vector_norms::vector_norm_l2_sq_neumaier};
782///
783/// // 3-4-5 right triangle: ‖(3, 4)‖₂² == 25
784/// let v = [3.0_f64, 4.0];
785/// assert_eq!(vector_norm_l2_sq_neumaier(&v).as_raw_scalar_ref(), &25.0);
786///
787/// // Zero vector
788/// let z = [0.0_f64; 3];
789/// assert_eq!(vector_norm_l2_sq_neumaier(&z).as_raw_scalar_ref(), &0.0);
790///
791/// // Recovers small squared terms lost by naive accumulation:
792/// // v = [1.0, 1e-9 × 10_000]; true ‖v‖₂² = 1.0 + 1e-14
793/// const N: usize = 10_000;
794/// let mut v_large: [f64; N + 1] = [1e-9; N + 1];
795/// v_large[0] = 1.0;
796/// let result = *vector_norm_l2_sq_neumaier(&v_large).as_raw_scalar_ref();
797/// let expected = 1.0_f64 + N as f64 * 1e-18;
798/// assert!((result - expected).abs() < 1e-15 * expected);
799/// ```
800#[inline(always)]
801pub fn vector_norm_l2_sq_neumaier<ScalarType: FpScalar>(
802 v: &[ScalarType],
803) -> NonNegativeRealScalar<ScalarType::RealType> {
804 L2NormNeumaierSum::<ScalarType>::compute_vector_norm_sq(v)
805}
806
807/// Computes the squared Euclidean (L2) norm from an owned-item iterator using
808/// scaling plus Neumaier compensated summation.
809///
810/// This is the iterator counterpart of [`vector_norm_l2_sq_neumaier`].
811#[inline(always)]
812pub fn vector_norm_l2_sq_neumaier_iter<ScalarType, I>(
813 v: I,
814) -> NonNegativeRealScalar<ScalarType::RealType>
815where
816 ScalarType: FpScalar,
817 I: IntoIterator<Item = ScalarType>,
818{
819 L2NormNeumaierSum::<ScalarType>::compute_vector_norm_sq_iter(v)
820}
821
822/// Computes the Euclidean (L2) norm of a slice of scalars using
823/// **scaling + Neumaier compensated summation**.
824///
825/// Returns ‖**v**‖₂ = √(Σᵢ |vᵢ|²), where |·| denotes the modulus.
826///
827/// # Numerical Accuracy
828///
829/// This is an improved variant of [`vector_norm_l2`] that combines two complementary
830/// techniques:
831///
832/// 1. **Scaling** (same as [`vector_norm_l2`]): tracks `scale = max |vᵢ|` and
833/// accumulates `Σ (|vᵢ|/scale)²`, so that no individual term overflows or
834/// underflows before being squared.
835///
836/// 2. **Neumaier compensated summation**: replaces the plain `sumsq += r²` with
837/// Kahan/Neumaier compensation, capturing low-order bits that are silently
838/// discarded in the naive loop. The final result is `scale × √(neumaier.result())`.
839///
840/// **When rescaling occurs** (a new maximum is found mid-iteration), the
841/// accumulated Neumaier sum is multiplied by `r²` and the compensation term is
842/// reset to zero. Rescaling is rare in practice, so the Neumaier accuracy benefit
843/// is preserved for the bulk of the summation.
844///
845/// # Special Cases
846///
847/// | Input | Result |
848/// |-------|--------|
849/// | all-zero vector | `0.0` |
850/// | single dominant component | full Neumaier accuracy for the remaining terms |
851///
852/// # Examples
853///
854/// ```
855/// use num_valid::{*, algorithms::vector_norms::vector_norm_l2_neumaier};
856///
857/// // 3-4-5 right triangle: ‖(3, 4)‖₂ == 5
858/// let v = [3.0_f64, 4.0];
859/// assert!((*vector_norm_l2_neumaier(&v).as_raw_scalar_ref() - 5.0).abs() < 1e-12);
860///
861/// // Zero vector
862/// let z = [0.0_f64; 3];
863/// assert_eq!(vector_norm_l2_neumaier(&z).as_raw_scalar_ref(), &0.0);
864///
865/// // Recovers small squared terms lost by naive accumulation:
866/// // v = [1.0, 1e-9 × 10_000]; true ‖v‖₂ = sqrt(1.0 + 1e-14) ≈ 1.0 + 5e-15
867/// const N: usize = 10_000;
868/// let mut v_large: [f64; N + 1] = [1e-9; N + 1];
869/// v_large[0] = 1.0;
870/// let result = *vector_norm_l2_neumaier(&v_large).as_raw_scalar_ref();
871/// let expected = (1.0_f64 + N as f64 * 1e-18).sqrt();
872/// assert!((result - expected).abs() < 1e-15 * expected);
873/// ```
874#[inline(always)]
875pub fn vector_norm_l2_neumaier<ScalarType: FpScalar>(
876 v: &[ScalarType],
877) -> NonNegativeRealScalar<ScalarType::RealType> {
878 L2NormNeumaierSum::<ScalarType>::compute_vector_norm(v)
879}
880
881/// Computes the Euclidean (L2) norm from an owned-item iterator using scaling
882/// plus Neumaier compensated summation.
883///
884/// This is the iterator counterpart of [`vector_norm_l2_neumaier`].
885#[inline(always)]
886pub fn vector_norm_l2_neumaier_iter<ScalarType, I>(
887 v: I,
888) -> NonNegativeRealScalar<ScalarType::RealType>
889where
890 ScalarType: FpScalar,
891 I: IntoIterator<Item = ScalarType>,
892{
893 L2NormNeumaierSum::<ScalarType>::compute_vector_norm_iter(v)
894}
895
896/// Computes the Manhattan (L1) norm of a slice of scalars.
897///
898/// Returns ‖**v**‖₁ = Σᵢ |vᵢ|, where |·| denotes the modulus.
899///
900/// # Numerical Accuracy
901///
902/// The absolute values are summed with a **naive `+=` loop**.
903/// For typical inputs this is exact up to the rounding of each individual
904/// `abs()` call. When components span many orders of magnitude a large
905/// running total may absorb small addends (absorption error), silently
906/// discarding their low-order bits. Use [`vector_norm_l1_neumaier`] when that
907/// extra precision matters.
908///
909/// Equivalent to calling [`vector_norm_l1_with_accumulator`] with [`NaiveSum`].
910///
911/// # Examples
912///
913/// ```
914/// use num_valid::{*, algorithms::vector_norms::vector_norm_l1};
915///
916/// // L1 norm of (3, 4) is 3 + 4 = 7
917/// let v = [3.0_f64, 4.0];
918/// assert_eq!(vector_norm_l1(&v).as_raw_scalar_ref(), &7.0);
919///
920/// // Zero vector
921/// let z = [0.0_f64; 3];
922/// assert_eq!(vector_norm_l1(&z).as_raw_scalar_ref(), &0.0);
923///
924/// // Unit axis vector has L1 norm 1
925/// let e1 = [1.0_f64, 0.0, 0.0];
926/// assert_eq!(vector_norm_l1(&e1).as_raw_scalar_ref(), &1.0);
927/// ```
928///
929/// For complex scalars the L1 norm sums the complex moduli:
930///
931/// ```
932/// use num_valid::{*, algorithms::vector_norms::vector_norm_l1};
933/// use num::Complex;
934/// use try_create::TryNew;
935///
936/// // ‖[3+0i, 0+4i]‖₁ = |3+0i| + |0+4i| = 3 + 4 = 7
937/// let v = [
938/// ComplexNative64StrictFinite::try_new(Complex::new(3.0_f64, 0.0)).unwrap(),
939/// ComplexNative64StrictFinite::try_new(Complex::new(0.0_f64, 4.0)).unwrap(),
940/// ];
941/// let result = vector_norm_l1(&v);
942/// assert!((*result.as_raw_scalar_ref() - 7.0_f64).abs() < 1e-12);
943/// ```
944#[inline(always)]
945pub fn vector_norm_l1<ScalarType: FpScalar>(
946 a: &[ScalarType],
947) -> NonNegativeRealScalar<ScalarType::RealType> {
948 L1NormNaiveSum::<ScalarType>::compute_vector_norm(a)
949}
950
951/// Computes the Manhattan (L1) norm from an owned-item iterator.
952///
953/// This is the iterator counterpart of [`vector_norm_l1`].
954#[inline(always)]
955pub fn vector_norm_l1_iter<ScalarType, I>(a: I) -> NonNegativeRealScalar<ScalarType::RealType>
956where
957 ScalarType: FpScalar,
958 I: IntoIterator<Item = ScalarType>,
959{
960 L1NormNaiveSum::<ScalarType>::compute_vector_norm_iter(a)
961}
962
963/// Computes the Manhattan (L1) norm of a slice of scalars using
964/// **Neumaier compensated summation**.
965///
966/// Returns ‖**v**‖₁ = Σᵢ |vᵢ|, where |·| denotes the modulus.
967///
968/// # Numerical Accuracy
969///
970/// The absolute values are accumulated using the **Neumaier compensated
971/// summation** algorithm, which corrects for round-off errors that can
972/// accumulate in a naïve loop. For typical coordinate values the improvement
973/// is small, but for vectors whose components span many orders of magnitude
974/// the compensation captures the round-off lost when a small addend is absorbed
975/// by a much larger running total (absorption error). Catastrophic cancellation
976/// — the loss of significant bits by subtracting nearly-equal numbers — cannot
977/// occur in a sum of absolute values, so the benefit here is specifically the
978/// recovery of those absorbed low-order bits.
979///
980/// # Examples
981///
982/// ```
983/// use num_valid::{*, algorithms::vector_norms::vector_norm_l1_neumaier};
984///
985/// // L1 norm of (3, 4) is 3 + 4 = 7
986/// let v = [3.0_f64, 4.0];
987/// assert_eq!(vector_norm_l1_neumaier(&v).as_raw_scalar_ref(), &7.0);
988///
989/// // Zero vector
990/// let z = [0.0_f64; 3];
991/// assert_eq!(vector_norm_l1_neumaier(&z).as_raw_scalar_ref(), &0.0);
992///
993/// // Unit axis vector has L1 norm 1
994/// let e1 = [1.0_f64, 0.0, 0.0];
995/// assert_eq!(vector_norm_l1_neumaier(&e1).as_raw_scalar_ref(), &1.0);
996/// ```
997#[inline(always)]
998pub fn vector_norm_l1_neumaier<ScalarType: FpScalar>(
999 a: &[ScalarType],
1000) -> NonNegativeRealScalar<ScalarType::RealType> {
1001 L1NormNeumaierSum::<ScalarType>::compute_vector_norm(a)
1002}
1003
1004/// Computes the Manhattan (L1) norm from an owned-item iterator using
1005/// Neumaier compensated summation.
1006///
1007/// This is the iterator counterpart of [`vector_norm_l1_neumaier`].
1008#[inline(always)]
1009pub fn vector_norm_l1_neumaier_iter<ScalarType, I>(
1010 a: I,
1011) -> NonNegativeRealScalar<ScalarType::RealType>
1012where
1013 ScalarType: FpScalar,
1014 I: IntoIterator<Item = ScalarType>,
1015{
1016 L1NormNeumaierSum::<ScalarType>::compute_vector_norm_iter(a)
1017}
1018
1019/// Computes the Chebyshev (L∞) norm of a slice of scalars.
1020///
1021/// Returns ‖**v**‖∞ = maxᵢ |vᵢ|, where |·| denotes the modulus.
1022///
1023/// # Algorithm
1024///
1025/// A single linear scan keeps the running maximum absolute value. The result
1026/// requires no square root and no compensation — it is numerically exact up to
1027/// a single call to `abs()` on each component.
1028///
1029/// # Examples
1030///
1031/// ```
1032/// use num_valid::{*, algorithms::vector_norms::vector_norm_linf};
1033///
1034/// // L∞ norm of (3, 4) = max(3, 4) = 4
1035/// let v = [3.0_f64, 4.0];
1036/// assert_eq!(vector_norm_linf(&v).as_raw_scalar_ref(), &4.0);
1037///
1038/// // Zero vector
1039/// let z = [0.0_f64; 3];
1040/// assert_eq!(vector_norm_linf(&z).as_raw_scalar_ref(), &0.0);
1041///
1042/// // Unit axis vector has L∞ norm 1
1043/// let e1 = [1.0_f64, 0.0, 0.0];
1044/// assert_eq!(vector_norm_linf(&e1).as_raw_scalar_ref(), &1.0);
1045/// ```
1046///
1047/// For complex scalars `|·|` is the complex modulus:
1048///
1049/// ```
1050/// use num_valid::{*, algorithms::vector_norms::vector_norm_linf};
1051/// use num::Complex;
1052/// use try_create::TryNew;
1053///
1054/// // ‖[3+0i, 0+4i]‖∞ = max(|3+0i|, |0+4i|) = max(3, 4) = 4
1055/// let v = [
1056/// ComplexNative64StrictFinite::try_new(Complex::new(3.0_f64, 0.0)).unwrap(),
1057/// ComplexNative64StrictFinite::try_new(Complex::new(0.0_f64, 4.0)).unwrap(),
1058/// ];
1059/// let result = vector_norm_linf(&v).into_inner();
1060/// assert!((*result.as_raw_scalar_ref() - 4.0_f64).abs() < 1e-12);
1061/// ```
1062#[inline(always)]
1063pub fn vector_norm_linf<ScalarType: FpScalar>(
1064 a: &[ScalarType],
1065) -> NonNegativeRealScalar<ScalarType::RealType> {
1066 LinfNorm::<()>::compute_vector_norm(a)
1067}
1068
1069/// Computes the Chebyshev (L∞) norm of a slice of scalars in parallel.
1070///
1071/// Returns ‖**v**‖∞ = maxᵢ |vᵢ|, where |·| denotes the modulus.
1072///
1073/// This is the parallel counterpart of [`vector_norm_linf`].
1074///
1075/// # Examples
1076///
1077/// ```
1078/// use num_valid::{*, algorithms::vector_norms::vector_norm_linf_par};
1079///
1080/// let v = [3.0_f64, 4.0, -2.0];
1081/// assert_eq!(vector_norm_linf_par(&v).as_raw_scalar_ref(), &4.0);
1082/// ```
1083#[inline(always)]
1084pub fn vector_norm_linf_par<ScalarType: FpScalar>(
1085 a: &[ScalarType],
1086) -> NonNegativeRealScalar<ScalarType::RealType> {
1087 LinfNorm::<()>::compute_vector_norm_par(a)
1088}
1089
1090/// Computes the Chebyshev (L∞) norm from an owned-item iterator.
1091///
1092/// This is the iterator counterpart of [`vector_norm_linf`].
1093#[inline(always)]
1094pub fn vector_norm_linf_iter<ScalarType: FpScalar, I>(
1095 a: I,
1096) -> NonNegativeRealScalar<ScalarType::RealType>
1097where
1098 I: IntoIterator<Item = ScalarType>,
1099{
1100 LinfNorm::<()>::compute_vector_norm_iter(a)
1101}
1102
1103/// Computes the infinity norm (maximum absolute value) of a parallel iterator of values.
1104///
1105/// This function computes the infinity norm, which is the maximum absolute value of the elements
1106/// in the given parallel iterator. It uses the `Abs` trait to compute the absolute value of each
1107/// element and the `Max` trait to find the maximum value.
1108///
1109/// This is the parallel iterator counterpart of [`vector_norm_linf_iter`].
1110///
1111/// # Examples
1112///
1113/// ```
1114/// use num_valid::{*, algorithms::vector_norms::vector_norm_linf_par_iter};
1115///
1116/// let v = vec![3.0_f64, 4.0, -2.0];
1117/// assert_eq!(vector_norm_linf_par_iter(v).as_raw_scalar_ref(), &4.0);
1118/// ```
1119#[inline(always)]
1120pub fn vector_norm_linf_par_iter<ScalarType: FpScalar, I>(
1121 a: I,
1122) -> NonNegativeRealScalar<ScalarType::RealType>
1123where
1124 I: IntoParallelIterator<Item = ScalarType>,
1125{
1126 LinfNorm::<()>::compute_vector_norm_par_iter(a)
1127}
1128
1129// ================================================================================================
1130// Tests
1131// ================================================================================================
1132
1133#[cfg(test)]
1134mod tests {
1135 use super::*;
1136 use crate::{RealScalar, ScalarCore};
1137
1138 mod vector_norm_l2_tests {
1139 use super::*;
1140
1141 const EPS: f64 = 1e-12;
1142
1143 /// 3-4-5: norm of (3, 4) is 5.
1144 #[test]
1145 fn three_four_five() {
1146 let v = [3.0_f64, 4.0];
1147 assert!((*vector_norm_l2(&v).as_raw_scalar_ref() - 5.0).abs() < EPS);
1148 }
1149
1150 /// Zero vector has norm 0.
1151 #[test]
1152 fn zero_vector() {
1153 let v = [0.0_f64; 4];
1154 assert_eq!(*vector_norm_l2(&v).as_raw_scalar_ref(), 0.0);
1155 }
1156
1157 /// 1-D: norm is |v[0]|.
1158 #[test]
1159 fn one_dimension_positive() {
1160 let v = [5.0_f64];
1161 assert!((*vector_norm_l2(&v).as_raw_scalar_ref() - 5.0).abs() < EPS);
1162 }
1163
1164 /// 1-D: norm of negative scalar is its absolute value.
1165 #[test]
1166 fn one_dimension_negative() {
1167 let v = [-7.0_f64];
1168 assert!((*vector_norm_l2(&v).as_raw_scalar_ref() - 7.0).abs() < EPS);
1169 }
1170
1171 /// Unit axis vectors always have norm 1.
1172 #[test]
1173 fn unit_axis_vectors_3d() {
1174 for axis in 0..3 {
1175 let mut e = [0.0_f64; 3];
1176 e[axis] = 1.0;
1177 assert!((*vector_norm_l2(&e).as_raw_scalar_ref() - 1.0).abs() < EPS);
1178 }
1179 }
1180
1181 /// Known 3-D norm: sqrt(1² + 2² + 2²) = 3.
1182 #[test]
1183 fn known_3d_norm() {
1184 let v = [1.0_f64, 2.0, 2.0];
1185 assert!((*vector_norm_l2(&v).as_raw_scalar_ref() - 3.0).abs() < EPS);
1186 }
1187
1188 /// Negating a vector preserves its norm.
1189 #[test]
1190 fn negation_preserves_norm() {
1191 let v = [3.0_f64, -4.0, 5.0];
1192 let neg_v = [-3.0_f64, 4.0, -5.0];
1193 assert_eq!(vector_norm_l2(&v), vector_norm_l2(&neg_v));
1194 }
1195
1196 /// Numerical stability: component near f64::MAX must not overflow.
1197 #[test]
1198 fn numerical_stability_large_values() {
1199 let huge = f64::MAX / 2.0;
1200 let v = [huge, 0.0_f64];
1201 let n = *vector_norm_l2(&v).as_raw_scalar_ref();
1202 assert!(n.is_finite(), "norm should be finite, got {n}");
1203 assert!((n / huge - 1.0).abs() < 1e-10);
1204 }
1205
1206 /// Numerical stability: subnormal component must not underflow to zero.
1207 #[test]
1208 fn numerical_stability_tiny_values() {
1209 let tiny = f64::MIN_POSITIVE;
1210 let v = [tiny, 0.0_f64];
1211 let n = *vector_norm_l2(&v).as_raw_scalar_ref();
1212 assert!(n.is_finite());
1213 assert!(n > 0.0);
1214 }
1215 }
1216
1217 mod vector_norm_l2_sq_tests {
1218 use super::*;
1219 use crate::RealNative64StrictFinite;
1220
1221 const EPS: f64 = 1e-16;
1222
1223 /// 3-4-5: vector_norm_l2_sq([3, 4]) == 3² + 4² == 25.
1224 #[test]
1225 fn three_four_five() {
1226 let v = [3.0_f64, 4.0];
1227 assert_eq!(*vector_norm_l2_sq(&v).as_raw_scalar_ref(), 25.0);
1228 }
1229
1230 /// Zero vector: squared norm is 0.
1231 #[test]
1232 fn zero_vector() {
1233 let v = [0.0_f64; 4];
1234 assert_eq!(*vector_norm_l2_sq(&v).as_raw_scalar_ref(), 0.0);
1235 }
1236
1237 /// 1-D positive: vector_norm_l2_sq([x]) == x².
1238 #[test]
1239 fn one_dimension_positive() {
1240 let v = [5.0_f64];
1241 assert_eq!(*vector_norm_l2_sq(&v).as_raw_scalar_ref(), 25.0);
1242 }
1243
1244 /// 1-D negative: vector_norm_l2_sq([x]) == x² (sign disappears).
1245 #[test]
1246 fn one_dimension_negative() {
1247 let v = [-7.0_f64];
1248 assert_eq!(*vector_norm_l2_sq(&v).as_raw_scalar_ref(), 49.0);
1249 }
1250
1251 /// Unit axis vectors always have squared norm 1.
1252 #[test]
1253 fn unit_axis_vectors_3d() {
1254 for axis in 0..3 {
1255 let mut e = [0.0_f64; 3];
1256 e[axis] = 1.0;
1257 assert_eq!(*vector_norm_l2_sq(&e).as_raw_scalar_ref(), 1.0);
1258 }
1259 }
1260
1261 /// Known 3-D: 1² + 2² + 2² = 1 + 4 + 4 = 9.
1262 #[test]
1263 fn known_3d_vector_norm_sq() {
1264 let v = [1.0_f64, 2.0, 2.0];
1265 assert_eq!(*vector_norm_l2_sq(&v).as_raw_scalar_ref(), 9.0);
1266 }
1267
1268 /// Negating a vector preserves its squared norm.
1269 #[test]
1270 fn negation_preserves_vector_norm_sq() {
1271 let v = [3.0_f64, -4.0, 5.0];
1272 let neg_v = [-3.0_f64, 4.0, -5.0];
1273 assert_eq!(vector_norm_l2_sq(&v), vector_norm_l2_sq(&neg_v));
1274 }
1275
1276 /// vector_norm_l2_sq == vector_norm_l2² (consistency with vector_norm_l2).
1277 #[test]
1278 fn consistent_with_vector_norm_l2() {
1279 let v = [2.0_f64, 3.0, 6.0];
1280 let sq = *vector_norm_l2_sq(&v).as_raw_scalar_ref();
1281 let n = *vector_norm_l2(&v).as_raw_scalar_ref();
1282 assert_eq!(sq, n * n);
1283 }
1284
1285 /// Numerical stability: component below sqrt(f64::MAX) ≈ 1.34e154 must not overflow.
1286 /// Note: vector_norm_l2_sq computes scale² internally; values above sqrt(f64::MAX) will overflow.
1287 #[test]
1288 fn numerical_stability_large_values() {
1289 // Safe upper bound: scale must satisfy scale² < f64::MAX
1290 let large = 1.0e150_f64;
1291 let v = [large, 0.0_f64];
1292 let sq = *vector_norm_l2_sq(&v).as_raw_scalar_ref();
1293 assert!(sq.is_finite(), "squared norm should be finite, got {sq}");
1294 assert_eq!(sq / (large * large), 1.0);
1295 }
1296
1297 /// Numerical stability: component above sqrt(f64::MIN_POSITIVE) ≈ 1.05e-154 must not underflow.
1298 /// Note: vector_norm_l2_sq computes scale² internally; values below sqrt(f64::MIN_POSITIVE) will underflow to 0.
1299 #[test]
1300 fn numerical_stability_tiny_values() {
1301 // Safe lower bound: scale must satisfy scale² > 0 (no underflow)
1302 let small = 1.0e-100_f64;
1303 let v = [small, 0.0_f64];
1304 let sq = *vector_norm_l2_sq(&v).as_raw_scalar_ref();
1305 assert!(sq.is_finite());
1306 assert!(sq > 0.0);
1307 }
1308
1309 /// Overflow is expected when the mathematically correct squared norm is
1310 /// not representable (e.g. f64::MAX squared).
1311 ///
1312 /// We use strict validated scalars so overflow is rejected in both debug
1313 /// and release builds.
1314 #[test]
1315 #[should_panic]
1316 fn overflow_panics_slice_api() {
1317 let v = [RealNative64StrictFinite::from_f64(f64::MAX)];
1318 let _ = vector_norm_l2_sq(&v);
1319 }
1320
1321 /// Iterator API must fail in the same way when final scale² overflows.
1322 #[test]
1323 #[should_panic]
1324 fn overflow_panics_iter_api() {
1325 let v = vec![RealNative64StrictFinite::from_f64(f64::MAX)];
1326 let _ = vector_norm_l2_sq_iter(v);
1327 }
1328
1329 /// Accumulator-driven API must also panic on non-finite final output.
1330 #[test]
1331 #[should_panic]
1332 fn overflow_panics_accumulator_api() {
1333 let v = [RealNative64StrictFinite::from_f64(f64::MAX)];
1334 let _ = vector_norm_l2_sq_with_accumulator::<_, NaiveSum<_>>(&v);
1335 }
1336
1337 /// vector_norm_l2_sq is always ≥ vector_norm_linf² (since ‖v‖∞ ≤ ‖v‖₂).
1338 #[test]
1339 fn geq_vector_norm_linf_squared() {
1340 let v = [3.0_f64, -4.0, 5.0];
1341 let sq = *vector_norm_l2_sq(&v).as_raw_scalar_ref();
1342 let linf = *vector_norm_linf(&v).as_raw_scalar_ref();
1343 assert!(sq >= linf * linf - EPS);
1344 }
1345
1346 /// Documents the inaccuracy of naive accumulation for small squared terms.
1347 ///
1348 /// v = [1.0, δ, δ, …, δ] with δ = 1e-9, N = 10_000.
1349 /// True ‖v‖₂² = 1.0 + N·δ² = 1.0 + 1e-14.
1350 ///
1351 /// The scaled algorithm keeps `sumsq ≈ 1.0` throughout; each δ² = 1e-18
1352 /// is well below the floating-point precision of 1.0 (ε ≈ 2.2e-16), so
1353 /// `sumsq += δ²` rounds back to `sumsq` unchanged and every small term is
1354 /// silently discarded. The final result equals exactly 1.0, an error of
1355 /// 1e-14 — four orders of magnitude above machine epsilon.
1356 ///
1357 /// With Neumaier compensated summation the δ² corrections would be captured
1358 /// in the running compensation term and the result would satisfy
1359 /// `(result - expected).abs() < ε·expected ≈ 2.2e-16`.
1360 #[test]
1361 fn naive_accumulation_loses_small_squared_terms() {
1362 const N: usize = 10_000;
1363 let mut v = [1e-9_f64; N + 1];
1364 v[0] = 1.0;
1365
1366 let result = *vector_norm_l2_sq(&v).as_raw_scalar_ref();
1367 let expected = 1.0_f64 + N as f64 * 1e-18; // = 1.0 + 1e-14
1368
1369 // Current: every δ² = 1e-18 < ε is lost → result collapses to 1.0.
1370 assert_eq!(result, 1.0_f64);
1371
1372 // The relative error equals the entire small-terms contribution (~1e-14),
1373 // far larger than machine epsilon (~2.2e-16).
1374 let relative_error = (result - expected).abs() / expected;
1375 assert!(
1376 relative_error > 1e-15,
1377 "relative error {relative_error:.2e} should be significant \
1378 (would be ~ε ≈ 2.2e-16 with Neumaier-sum)"
1379 );
1380
1381 // With Neumaier the following assertion would hold instead:
1382 // assert!((result - expected).abs() < 1e-15 * expected);
1383 }
1384 }
1385
1386 mod vector_norm_l2_sq_neumaier_tests {
1387 use super::*;
1388
1389 const EPS: f64 = f64::EPSILON;
1390
1391 /// 3-4-5: vector_norm_l2_sq_neumaier([3, 4]) == 3² + 4² == 25.
1392 #[test]
1393 fn three_four_five() {
1394 let v = [3.0_f64, 4.0];
1395 assert_eq!(*vector_norm_l2_sq_neumaier(&v).as_raw_scalar_ref(), 25.0);
1396 }
1397
1398 /// Zero vector → 0.
1399 #[test]
1400 fn zero_vector() {
1401 let v = [0.0_f64; 4];
1402 assert_eq!(*vector_norm_l2_sq_neumaier(&v).as_raw_scalar_ref(), 0.0);
1403 }
1404
1405 /// 1-D: vector_norm_l2_sq_neumaier([x]) == x².
1406 #[test]
1407 fn one_dimension() {
1408 let v = [7.0_f64];
1409 assert_eq!(*vector_norm_l2_sq_neumaier(&v).as_raw_scalar_ref(), 49.0);
1410 }
1411
1412 /// Matches vector_norm_l2_sq on a generic 3-D vector.
1413 #[test]
1414 fn matches_naive_for_normal_values() {
1415 let v = [1.0_f64, 2.0, 3.0];
1416 let neumaier = *vector_norm_l2_sq_neumaier(&v).as_raw_scalar_ref();
1417 let naive = *vector_norm_l2_sq(&v).as_raw_scalar_ref();
1418 assert!((neumaier - naive).abs() < EPS);
1419 }
1420
1421 /// Neumaier version recovers small squared terms that naive accumulation loses.
1422 ///
1423 /// v = [1.0, δ × N] where δ = 1e-9. True ‖v‖₂² = 1.0 + N·δ² = 1.0 + 1e-14.
1424 /// Naive: every δ² = 1e-18 < ε is silently dropped → result = 1.0.
1425 /// Neumaier: compensation accumulates all N·δ² → result ≈ 1.0 + 1e-14.
1426 #[test]
1427 fn recovers_small_squared_terms() {
1428 const N: usize = 10_000;
1429 let mut v = [1e-9_f64; N + 1];
1430 v[0] = 1.0;
1431
1432 let result = *vector_norm_l2_sq_neumaier(&v).as_raw_scalar_ref();
1433 let expected = 1.0_f64 + N as f64 * 1e-18; // = 1.0 + 1e-14
1434
1435 // The Neumaier-compensated result must be within machine-epsilon of expected.
1436 let relative_error = (result - expected).abs() / expected;
1437 assert!(
1438 relative_error < EPS,
1439 "relative error {relative_error:.2e} should be tiny with Neumaier-sum"
1440 );
1441
1442 // Demonstrate the contrast with naive accumulation.
1443 let naive = *vector_norm_l2_sq(&v).as_raw_scalar_ref();
1444 assert_eq!(
1445 naive, 1.0_f64,
1446 "naive should collapse to 1.0 (known limitation)"
1447 );
1448 assert!(
1449 result > naive,
1450 "Neumaier result {result} should exceed naive {naive}"
1451 );
1452 }
1453
1454 /// Numerical stability: large-valued input does not overflow.
1455 #[test]
1456 fn numerical_stability_large_values() {
1457 let v = [1e150_f64, 1e150, 1e150];
1458 let result = *vector_norm_l2_sq_neumaier(&v).as_raw_scalar_ref();
1459 let expected = 3.0 * 1e300_f64;
1460 let relative_error = (result - expected).abs() / expected;
1461 assert!(relative_error < EPS, "large values: {relative_error:.2e}");
1462 }
1463
1464 /// Numerical stability: tiny-valued input does not underflow to zero.
1465 #[test]
1466 fn numerical_stability_tiny_values() {
1467 let v = [1e-100_f64, 1e-100, 1e-100];
1468 let result = *vector_norm_l2_sq_neumaier(&v).as_raw_scalar_ref();
1469 let expected = 3.0e-200_f64;
1470 let relative_error = (result - expected).abs() / expected;
1471 assert!(relative_error < EPS, "tiny values: {relative_error:.2e}");
1472 }
1473
1474 /// Consistent with vector_norm_l2: vector_norm_l2_sq_neumaier(v) ≈ vector_norm_l2(v)².
1475 #[test]
1476 fn consistent_with_vector_norm_l2() {
1477 let v = [1.5_f64, 2.5, 3.5, 4.5];
1478 let sq = *vector_norm_l2_sq_neumaier(&v).as_raw_scalar_ref();
1479 let l2 = *vector_norm_l2(&v).as_raw_scalar_ref();
1480 let err = (sq - l2 * l2).abs();
1481 println!("sq - l2² = {}", err);
1482 assert_eq!(err, 0.7105427357601002e-14);
1483 }
1484 }
1485
1486 mod vector_norm_l2_neumaier_tests {
1487 use super::*;
1488
1489 const EPS: f64 = f64::EPSILON;
1490
1491 /// 3-4-5 right triangle: ‖(3, 4)‖₂ == 5.
1492 #[test]
1493 fn three_four_five() {
1494 let v = [3.0_f64, 4.0];
1495 assert_eq!(*vector_norm_l2_neumaier(&v).as_raw_scalar_ref(), 5.0);
1496 }
1497
1498 /// Zero vector → 0.
1499 #[test]
1500 fn zero_vector() {
1501 let v = [0.0_f64; 4];
1502 assert_eq!(*vector_norm_l2_neumaier(&v).as_raw_scalar_ref(), 0.0);
1503 }
1504
1505 /// 1-D: ‖[x]‖₂ == |x|.
1506 #[test]
1507 fn one_dimension() {
1508 let v = [7.0_f64];
1509 assert_eq!(*vector_norm_l2_neumaier(&v).as_raw_scalar_ref(), 7.0);
1510 }
1511
1512 /// Matches vector_norm_l2 on ordinary values (no dominant small-term cancellation).
1513 #[test]
1514 fn matches_naive_for_normal_values() {
1515 let v = [1.0_f64, 2.0, 3.0];
1516 let neumaier = *vector_norm_l2_neumaier(&v).as_raw_scalar_ref();
1517 let naive = *vector_norm_l2(&v).as_raw_scalar_ref();
1518 assert!((neumaier - naive).abs() < EPS);
1519 }
1520
1521 /// Neumaier version recovers small squared terms that naive accumulation loses.
1522 ///
1523 /// v = [1.0, δ × N] where δ = 1e-9. True ‖v‖₂ = √(1.0 + N·δ²) = √(1.0 + 1e-14).
1524 /// Naive: sumsq collapses to 1.0 → norm = 1.0 (all small terms lost).
1525 /// Neumaier: sumsq ≈ 1.0 + 1e-14 → norm ≈ √(1.0 + 1e-14) ≈ 1.0 + 5e-15.
1526 #[test]
1527 fn recovers_small_squared_terms() {
1528 const N: usize = 10_000;
1529 let mut v = [1e-9_f64; N + 1];
1530 v[0] = 1.0;
1531
1532 let result = *vector_norm_l2_neumaier(&v).as_raw_scalar_ref();
1533 let expected = (1.0_f64 + N as f64 * 1e-18).sqrt();
1534
1535 let relative_error = (result - expected).abs() / expected;
1536 assert!(
1537 relative_error < 1e-12,
1538 "relative error {relative_error:.2e} should be tiny with Neumaier-sum"
1539 );
1540
1541 // Contrast with naive: vector_norm_l2 loses the small terms, giving exactly 1.0.
1542 let naive = *vector_norm_l2(&v).as_raw_scalar_ref();
1543 assert_eq!(
1544 naive, 1.0_f64,
1545 "naive should collapse to 1.0 (known limitation)"
1546 );
1547 assert!(
1548 result > naive,
1549 "Neumaier result {result} should exceed naive {naive}"
1550 );
1551 }
1552
1553 /// Numerical stability: large-valued input does not overflow.
1554 #[test]
1555 fn numerical_stability_large_values() {
1556 let v = [1e150_f64, 1e150, 1e150];
1557 let result = *vector_norm_l2_neumaier(&v).as_raw_scalar_ref();
1558 let expected = (3.0_f64).sqrt() * 1e150;
1559 let relative_error = (result - expected).abs() / expected;
1560 assert!(relative_error < EPS, "large values: {relative_error:.2e}");
1561 }
1562
1563 /// Numerical stability: tiny-valued input does not underflow to zero.
1564 #[test]
1565 fn numerical_stability_tiny_values() {
1566 let v = [1e-150_f64, 1e-150, 1e-150];
1567 let result = *vector_norm_l2_neumaier(&v).as_raw_scalar_ref();
1568 let expected = (3.0_f64).sqrt() * 1e-150;
1569 let relative_error = (result - expected).abs() / expected;
1570 assert!(relative_error < EPS, "tiny values: {relative_error:.2e}");
1571 }
1572
1573 /// Consistent with vector_norm_l2_sq_neumaier: vector_norm_l2_neumaier(v)² ≈ vector_norm_l2_sq_neumaier(v).
1574 #[test]
1575 fn consistent_with_vector_norm_l2_sq_neumaier() {
1576 let v = [1.5_f64, 2.5, 3.5, 4.5];
1577 let norm = *vector_norm_l2_neumaier(&v).as_raw_scalar_ref();
1578 let sq = *vector_norm_l2_sq_neumaier(&v).as_raw_scalar_ref();
1579 assert!(
1580 (norm * norm - sq).abs() < 100. * EPS,
1581 "norm²={} sq={}",
1582 norm * norm,
1583 sq
1584 );
1585 }
1586
1587 /// Unit-axis vectors have norm 1.
1588 #[test]
1589 fn unit_axis_vectors() {
1590 let e1 = [1.0_f64, 0.0, 0.0];
1591 let e2 = [0.0_f64, 1.0, 0.0];
1592 let e3 = [0.0_f64, 0.0, 1.0];
1593 for e in [&e1, &e2, &e3] {
1594 let n = *vector_norm_l2_neumaier(e).as_raw_scalar_ref();
1595 assert!((n - 1.0).abs() < EPS, "unit axis: {n}");
1596 }
1597 }
1598 }
1599
1600 mod vector_norm_l1_tests {
1601 use super::*;
1602
1603 const EPS: f64 = 1e-12;
1604
1605 /// L1 norm of (3, 4) is 3 + 4 = 7.
1606 #[test]
1607 fn three_plus_four() {
1608 let v = [3.0_f64, 4.0];
1609 assert_eq!(*vector_norm_l1(&v).as_raw_scalar_ref(), 7.0);
1610 }
1611
1612 /// Zero vector has L1 norm 0.
1613 #[test]
1614 fn zero_vector() {
1615 let v = [0.0_f64; 4];
1616 assert_eq!(*vector_norm_l1(&v).as_raw_scalar_ref(), 0.0);
1617 }
1618
1619 /// 1-D: norm is |v[0]|.
1620 #[test]
1621 fn one_dimension_positive() {
1622 let v = [5.0_f64];
1623 assert!((*vector_norm_l1(&v).as_raw_scalar_ref() - 5.0).abs() < EPS);
1624 }
1625
1626 /// 1-D: norm of negative scalar is its absolute value.
1627 #[test]
1628 fn one_dimension_negative() {
1629 let v = [-7.0_f64];
1630 assert!((*vector_norm_l1(&v).as_raw_scalar_ref() - 7.0).abs() < EPS);
1631 }
1632
1633 /// Unit axis vectors always have L1 norm 1.
1634 #[test]
1635 fn unit_axis_vectors_3d() {
1636 for axis in 0..3 {
1637 let mut e = [0.0_f64; 3];
1638 e[axis] = 1.0;
1639 assert_eq!(*vector_norm_l1(&e).as_raw_scalar_ref(), 1.0);
1640 }
1641 }
1642
1643 /// Known 3-D: |1| + |2| + |2| = 5.
1644 #[test]
1645 fn known_3d_norm() {
1646 let v = [1.0_f64, 2.0, 2.0];
1647 assert!((*vector_norm_l1(&v).as_raw_scalar_ref() - 5.0).abs() < EPS);
1648 }
1649
1650 /// Negating a vector preserves its L1 norm.
1651 #[test]
1652 fn negation_preserves_norm() {
1653 let v = [3.0_f64, -4.0, 5.0];
1654 let neg_v = [-3.0_f64, 4.0, -5.0];
1655 assert_eq!(vector_norm_l1(&v), vector_norm_l1(&neg_v));
1656 }
1657
1658 /// L1 norm is the sum of absolute values: mixed signs.
1659 #[test]
1660 fn mixed_signs() {
1661 let v = [-1.0_f64, 2.0, -3.0, 4.0];
1662 assert!((*vector_norm_l1(&v).as_raw_scalar_ref() - 10.0).abs() < EPS);
1663 }
1664
1665 /// L1 ≥ L2 for any vector.
1666 #[test]
1667 fn l1_geq_l2() {
1668 let v = [3.0_f64, -4.0, 5.0];
1669 assert!(
1670 vector_norm_l1(&v).as_raw_scalar_ref() >= vector_norm_l2(&v).as_raw_scalar_ref()
1671 );
1672 }
1673
1674 /// Numerical stability: large components must not overflow.
1675 #[test]
1676 fn numerical_stability_large_values() {
1677 let huge = f64::MAX / 4.0;
1678 // Two equal components: L1 norm = 2 × huge
1679 let v = [huge, huge];
1680 let n = *vector_norm_l1(&v).as_raw_scalar_ref();
1681 assert!(n.is_finite(), "norm should be finite, got {n}");
1682 assert!((n / (2.0 * huge) - 1.0).abs() < 1e-10);
1683 }
1684
1685 /// Numerical stability: subnormal component must not underflow to zero.
1686 #[test]
1687 fn numerical_stability_tiny_values() {
1688 let tiny = f64::MIN_POSITIVE;
1689 let v = [tiny, 0.0_f64];
1690 let n = *vector_norm_l1(&v).as_raw_scalar_ref();
1691 assert!(n.is_finite());
1692 assert!(n > 0.0);
1693 }
1694 }
1695
1696 mod vector_norm_l1_neumaier_tests {
1697 use super::*;
1698
1699 const EPS: f64 = 1e-12;
1700
1701 /// L1 norm of (3, 4) is 3 + 4 = 7.
1702 #[test]
1703 fn three_plus_four() {
1704 let v = [3.0_f64, 4.0];
1705 assert_eq!(*vector_norm_l1_neumaier(&v).as_raw_scalar_ref(), 7.0);
1706 }
1707
1708 /// Zero vector has L1 norm 0.
1709 #[test]
1710 fn zero_vector() {
1711 let v = [0.0_f64; 4];
1712 assert_eq!(*vector_norm_l1_neumaier(&v).as_raw_scalar_ref(), 0.0);
1713 }
1714
1715 /// 1-D: norm is |v[0]|.
1716 #[test]
1717 fn one_dimension_positive() {
1718 let v = [5.0_f64];
1719 assert!((*vector_norm_l1_neumaier(&v).as_raw_scalar_ref() - 5.0).abs() < EPS);
1720 }
1721
1722 /// 1-D: norm of negative scalar is its absolute value.
1723 #[test]
1724 fn one_dimension_negative() {
1725 let v = [-7.0_f64];
1726 assert!((*vector_norm_l1_neumaier(&v).as_raw_scalar_ref() - 7.0).abs() < EPS);
1727 }
1728
1729 /// Unit axis vectors always have L1 norm 1.
1730 #[test]
1731 fn unit_axis_vectors_3d() {
1732 for axis in 0..3 {
1733 let mut e = [0.0_f64; 3];
1734 e[axis] = 1.0;
1735 assert_eq!(*vector_norm_l1_neumaier(&e).as_raw_scalar_ref(), 1.0);
1736 }
1737 }
1738
1739 /// Known 3-D: |1| + |2| + |2| = 5.
1740 #[test]
1741 fn known_3d_norm() {
1742 let v = [1.0_f64, 2.0, 2.0];
1743 assert!((*vector_norm_l1_neumaier(&v).as_raw_scalar_ref() - 5.0).abs() < EPS);
1744 }
1745
1746 /// Negating a vector preserves its L1 norm.
1747 #[test]
1748 fn negation_preserves_norm() {
1749 let v = [3.0_f64, -4.0, 5.0];
1750 let neg_v = [-3.0_f64, 4.0, -5.0];
1751 assert_eq!(vector_norm_l1_neumaier(&v), vector_norm_l1_neumaier(&neg_v));
1752 }
1753
1754 /// L1 norm is the sum of absolute values: mixed signs.
1755 #[test]
1756 fn mixed_signs() {
1757 let v = [-1.0_f64, 2.0, -3.0, 4.0];
1758 assert!((*vector_norm_l1_neumaier(&v).as_raw_scalar_ref() - 10.0).abs() < EPS);
1759 }
1760
1761 /// L1 ≥ L2 for any vector.
1762 #[test]
1763 fn l1_geq_l2() {
1764 let v = [3.0_f64, -4.0, 5.0];
1765 assert!(
1766 vector_norm_l1_neumaier(&v).as_raw_scalar_ref()
1767 >= vector_norm_l2(&v).as_raw_scalar_ref()
1768 );
1769 }
1770
1771 /// Numerical stability: large components must not overflow.
1772 #[test]
1773 fn numerical_stability_large_values() {
1774 let huge = f64::MAX / 4.0;
1775 // Two equal components: L1 norm = 2 × huge
1776 let v = [huge, huge];
1777 let n = *vector_norm_l1_neumaier(&v).as_raw_scalar_ref();
1778 assert!(n.is_finite(), "norm should be finite, got {n}");
1779 assert!((n / (2.0 * huge) - 1.0).abs() < 1e-10);
1780 }
1781
1782 /// Numerical stability: subnormal component must not underflow to zero.
1783 #[test]
1784 fn numerical_stability_tiny_values() {
1785 let tiny = f64::MIN_POSITIVE;
1786 let v = [tiny, 0.0_f64];
1787 let n = *vector_norm_l1_neumaier(&v).as_raw_scalar_ref();
1788 assert!(n.is_finite());
1789 assert!(n > 0.0);
1790 }
1791 }
1792
1793 mod vector_norm_linf_tests {
1794 use super::*;
1795
1796 const EPS: f64 = 1e-12;
1797
1798 #[test]
1799 fn parallel_slice_matches_sequential() {
1800 let v = [3.0_f64, -4.0, 5.0, -2.5, 1.0];
1801 assert_eq!(vector_norm_linf_par(&v), vector_norm_linf(&v));
1802 }
1803
1804 #[test]
1805 fn parallel_iter_matches_sequential_iter() {
1806 let v = vec![3.0_f64, -4.0, 5.0, -2.5, 1.0];
1807 let par = vector_norm_linf_par_iter(v.clone());
1808 let seq = vector_norm_linf_iter(v);
1809 assert_eq!(par, seq);
1810 }
1811
1812 /// L∞ norm of (3, 4) = max(3, 4) = 4.
1813 #[test]
1814 fn max_of_three_four() {
1815 let v = [3.0_f64, 4.0];
1816 assert_eq!(*vector_norm_linf(&v).as_raw_scalar_ref(), 4.0);
1817 }
1818
1819 /// Zero vector has L∞ norm 0.
1820 #[test]
1821 fn zero_vector() {
1822 let v = [0.0_f64; 4];
1823 assert_eq!(*vector_norm_linf(&v).as_raw_scalar_ref(), 0.0);
1824 }
1825
1826 /// 1-D: norm is |v[0]|.
1827 #[test]
1828 fn one_dimension_positive() {
1829 let v = [5.0_f64];
1830 assert!((*vector_norm_linf(&v).as_raw_scalar_ref() - 5.0).abs() < EPS);
1831 }
1832
1833 /// 1-D: norm of negative scalar is its absolute value.
1834 #[test]
1835 fn one_dimension_negative() {
1836 let v = [-7.0_f64];
1837 assert!((*vector_norm_linf(&v).as_raw_scalar_ref() - 7.0).abs() < EPS);
1838 }
1839
1840 /// Unit axis vectors always have L∞ norm 1.
1841 #[test]
1842 fn unit_axis_vectors_3d() {
1843 for axis in 0..3 {
1844 let mut e = [0.0_f64; 3];
1845 e[axis] = 1.0;
1846 assert_eq!(*vector_norm_linf(&e).as_raw_scalar_ref(), 1.0);
1847 }
1848 }
1849
1850 /// Known 3-D: max(|1|, |-2|, |3|) = 3.
1851 #[test]
1852 fn known_3d_norm() {
1853 let v = [1.0_f64, -2.0, 3.0];
1854 assert!((*vector_norm_linf(&v).as_raw_scalar_ref() - 3.0).abs() < EPS);
1855 }
1856
1857 /// Negating a vector preserves its L∞ norm.
1858 #[test]
1859 fn negation_preserves_norm() {
1860 let v = [3.0_f64, -4.0, 5.0];
1861 let neg_v = [-3.0_f64, 4.0, -5.0];
1862 assert_eq!(vector_norm_linf(&v), vector_norm_linf(&neg_v));
1863 }
1864
1865 /// The dominant component determines the result.
1866 #[test]
1867 fn dominant_component() {
1868 let v = [1.0_f64, 0.001, 100.0, 0.5];
1869 assert!((*vector_norm_linf(&v).as_raw_scalar_ref() - 100.0).abs() < EPS);
1870 }
1871
1872 /// L∞ ≤ L2 for any vector.
1873 #[test]
1874 fn linf_leq_l2() {
1875 let v = [3.0_f64, -4.0, 5.0];
1876 assert!(
1877 vector_norm_linf(&v).as_raw_scalar_ref() <= vector_norm_l2(&v).as_raw_scalar_ref()
1878 );
1879 }
1880
1881 /// L∞ ≤ L1 for any vector.
1882 #[test]
1883 fn linf_leq_l1() {
1884 let v = [3.0_f64, -4.0, 5.0];
1885 assert!(
1886 vector_norm_linf(&v).as_raw_scalar_ref() <= vector_norm_l1(&v).as_raw_scalar_ref()
1887 );
1888 }
1889
1890 /// Numerical stability: large component must remain finite.
1891 #[test]
1892 fn numerical_stability_large_values() {
1893 let huge = f64::MAX / 2.0;
1894 let v = [1.0_f64, huge];
1895 let n = *vector_norm_linf(&v).as_raw_scalar_ref();
1896 assert!(n.is_finite(), "norm should be finite, got {n}");
1897 assert!((n / huge - 1.0).abs() < 1e-10);
1898 }
1899
1900 /// Numerical stability: subnormal component must not underflow to zero.
1901 #[test]
1902 fn numerical_stability_tiny_values() {
1903 let tiny = f64::MIN_POSITIVE;
1904 let v = [tiny, 0.0_f64];
1905 let n = *vector_norm_linf(&v).as_raw_scalar_ref();
1906 assert!(n.is_finite());
1907 assert!(n > 0.0);
1908 }
1909 }
1910
1911 mod other_vector_norm_l2_tests {
1912 use super::*;
1913 use crate::RealNative64StrictFinite;
1914 use approx::assert_relative_eq;
1915
1916 /// Helper to create a vector of validated reals from f64 values
1917 fn vec_f64(vals: &[f64]) -> Vec<RealNative64StrictFinite> {
1918 vals.iter()
1919 .map(|&v| RealNative64StrictFinite::from_f64(v))
1920 .collect()
1921 }
1922
1923 mod basic_cases {
1924 use super::*;
1925
1926 #[test]
1927 fn pythagorean_3_4_5() {
1928 let data = vec_f64(&[3.0, 4.0]);
1929 let norm = vector_norm_l2(&data).into_inner();
1930 assert_relative_eq!(*norm.as_raw_scalar_ref(), 5.0, epsilon = 1e-15);
1931 }
1932
1933 #[test]
1934 fn pythagorean_reverse_order() {
1935 // Same result regardless of order
1936 let data = vec_f64(&[4.0, 3.0]);
1937 let norm = vector_norm_l2(&data).into_inner();
1938 assert_relative_eq!(*norm.as_raw_scalar_ref(), 5.0, epsilon = 1e-15);
1939 }
1940
1941 #[test]
1942 fn unit_vector_x() {
1943 let data = vec_f64(&[1.0, 0.0, 0.0]);
1944 let norm = vector_norm_l2(&data).into_inner();
1945 assert_relative_eq!(*norm.as_raw_scalar_ref(), 1.0, epsilon = 1e-15);
1946 }
1947
1948 #[test]
1949 fn unit_vector_y() {
1950 let data = vec_f64(&[0.0, 1.0, 0.0]);
1951 let norm = vector_norm_l2(&data).into_inner();
1952 assert_relative_eq!(*norm.as_raw_scalar_ref(), 1.0, epsilon = 1e-15);
1953 }
1954
1955 #[test]
1956 fn three_equal_values() {
1957 // ||[1, 1, 1]|| = sqrt(3)
1958 let data = vec_f64(&[1.0, 1.0, 1.0]);
1959 let norm = vector_norm_l2(&data).into_inner();
1960 assert_relative_eq!(*norm.as_raw_scalar_ref(), 3.0_f64.sqrt(), epsilon = 1e-15);
1961 }
1962
1963 #[test]
1964 fn larger_vector() {
1965 // ||[1, 2, 3, 4, 5]|| = sqrt(1 + 4 + 9 + 16 + 25) = sqrt(55)
1966 let data = vec_f64(&[1.0, 2.0, 3.0, 4.0, 5.0]);
1967 let norm = vector_norm_l2(&data).into_inner();
1968 assert_relative_eq!(*norm.as_raw_scalar_ref(), 55.0_f64.sqrt(), epsilon = 1e-14);
1969 }
1970 }
1971
1972 mod edge_cases {
1973 use super::*;
1974
1975 #[test]
1976 fn empty_vector() {
1977 let data: Vec<RealNative64StrictFinite> = vec![];
1978 let norm = vector_norm_l2(&data).into_inner();
1979 assert_eq!(*norm.as_raw_scalar_ref(), 0.0);
1980 }
1981
1982 #[test]
1983 fn single_positive_element() {
1984 let data = vec_f64(&[7.0]);
1985 let norm = vector_norm_l2(&data).into_inner();
1986 assert_relative_eq!(*norm.as_raw_scalar_ref(), 7.0, epsilon = 1e-15);
1987 }
1988
1989 #[test]
1990 fn single_negative_element() {
1991 let data = vec_f64(&[-7.0]);
1992 let norm = vector_norm_l2(&data).into_inner();
1993 assert_relative_eq!(*norm.as_raw_scalar_ref(), 7.0, epsilon = 1e-15);
1994 }
1995
1996 #[test]
1997 fn all_zeros() {
1998 let data = vec_f64(&[0.0, 0.0, 0.0, 0.0]);
1999 let norm = vector_norm_l2(&data).into_inner();
2000 assert_eq!(*norm.as_raw_scalar_ref(), 0.0);
2001 }
2002
2003 #[test]
2004 fn zeros_interspersed() {
2005 let data = vec_f64(&[0.0, 3.0, 0.0, 4.0, 0.0]);
2006 let norm = vector_norm_l2(&data).into_inner();
2007 assert_relative_eq!(*norm.as_raw_scalar_ref(), 5.0, epsilon = 1e-15);
2008 }
2009
2010 #[test]
2011 fn negative_values() {
2012 let data = vec_f64(&[-3.0, -4.0]);
2013 let norm = vector_norm_l2(&data).into_inner();
2014 assert_relative_eq!(*norm.as_raw_scalar_ref(), 5.0, epsilon = 1e-15);
2015 }
2016
2017 #[test]
2018 fn mixed_signs() {
2019 let data = vec_f64(&[-3.0, 4.0]);
2020 let norm = vector_norm_l2(&data).into_inner();
2021 assert_relative_eq!(*norm.as_raw_scalar_ref(), 5.0, epsilon = 1e-15);
2022 }
2023
2024 #[test]
2025 fn single_zero() {
2026 let data = vec_f64(&[0.0]);
2027 let norm = vector_norm_l2(&data).into_inner();
2028 assert_eq!(*norm.as_raw_scalar_ref(), 0.0);
2029 }
2030 }
2031
2032 mod numerical_stability {
2033 use super::*;
2034
2035 #[test]
2036 fn large_values_no_overflow() {
2037 // Values that would overflow with naive x^2 approach
2038 let scale = 1e154;
2039 let data = vec_f64(&[3.0 * scale, 4.0 * scale]);
2040 let norm = vector_norm_l2(&data).into_inner();
2041 let expected = 5.0 * scale;
2042 let rel_err = (*norm.as_raw_scalar_ref() - expected).abs() / expected;
2043 assert!(
2044 rel_err < 1e-14,
2045 "Large values: expected {}, got {}, rel_err {}",
2046 expected,
2047 *norm.as_raw_scalar_ref(),
2048 rel_err
2049 );
2050 }
2051
2052 #[test]
2053 fn very_large_values() {
2054 // Even closer to overflow
2055 let scale = 1e300;
2056 let data = vec_f64(&[scale, scale]);
2057 let norm = vector_norm_l2(&data).into_inner();
2058 let expected = scale * 2.0_f64.sqrt();
2059 let rel_err = (*norm.as_raw_scalar_ref() - expected).abs() / expected;
2060 assert!(
2061 rel_err < 1e-14,
2062 "Very large values: expected {}, got {}, rel_err {}",
2063 expected,
2064 *norm.as_raw_scalar_ref(),
2065 rel_err
2066 );
2067 }
2068
2069 #[test]
2070 fn small_values_no_underflow() {
2071 // Values that would underflow with naive x^2 approach
2072 let scale = 1e-154;
2073 let data = vec_f64(&[3.0 * scale, 4.0 * scale]);
2074 let norm = vector_norm_l2(&data).into_inner();
2075 let expected = 5.0 * scale;
2076 let rel_err = (*norm.as_raw_scalar_ref() - expected).abs() / expected;
2077 assert!(
2078 rel_err < 1e-14,
2079 "Small values: expected {}, got {}, rel_err {}",
2080 expected,
2081 *norm.as_raw_scalar_ref(),
2082 rel_err
2083 );
2084 }
2085
2086 #[test]
2087 fn very_small_values() {
2088 // Even closer to underflow
2089 let scale = 1e-300;
2090 let data = vec_f64(&[scale, scale]);
2091 let norm = vector_norm_l2(&data).into_inner();
2092 let expected = scale * 2.0_f64.sqrt();
2093 let rel_err = (*norm.as_raw_scalar_ref() - expected).abs() / expected;
2094 assert!(
2095 rel_err < 1e-14,
2096 "Very small values: expected {}, got {}, rel_err {}",
2097 expected,
2098 *norm.as_raw_scalar_ref(),
2099 rel_err
2100 );
2101 }
2102
2103 #[test]
2104 fn mixed_large_and_small() {
2105 // Large value dominates
2106 let data = vec_f64(&[1e150, 1.0, 1e-150]);
2107 let norm = vector_norm_l2(&data).into_inner();
2108 // Norm is approximately 1e150 (small values negligible)
2109 let rel_err = (*norm.as_raw_scalar_ref() - 1e150).abs() / 1e150;
2110 assert!(
2111 rel_err < 1e-14,
2112 "Mixed magnitudes: expected ~1e150, got {}, rel_err {}",
2113 *norm.as_raw_scalar_ref(),
2114 rel_err
2115 );
2116 }
2117
2118 #[test]
2119 fn all_same_large_values() {
2120 // n values of x: ||[x, x, ..., x]|| = |x| * sqrt(n)
2121 let x = 1e154;
2122 let n = 100;
2123 let data: Vec<RealNative64StrictFinite> = (0..n)
2124 .map(|_| RealNative64StrictFinite::from_f64(x))
2125 .collect();
2126 let norm = vector_norm_l2(&data).into_inner();
2127 let expected = x * (n as f64).sqrt();
2128 let rel_err = (*norm.as_raw_scalar_ref() - expected).abs() / expected;
2129 assert!(
2130 rel_err < 1e-13,
2131 "100 large values: expected {}, got {}, rel_err {}",
2132 expected,
2133 *norm.as_raw_scalar_ref(),
2134 rel_err
2135 );
2136 }
2137
2138 #[test]
2139 fn all_same_small_values() {
2140 let x = 1e-154;
2141 let n = 100;
2142 let data: Vec<RealNative64StrictFinite> = (0..n)
2143 .map(|_| RealNative64StrictFinite::from_f64(x))
2144 .collect();
2145 let norm = vector_norm_l2(&data).into_inner();
2146 let expected = x * (n as f64).sqrt();
2147 let rel_err = (*norm.as_raw_scalar_ref() - expected).abs() / expected;
2148 assert!(
2149 rel_err < 1e-13,
2150 "100 small values: expected {}, got {}, rel_err {}",
2151 expected,
2152 *norm.as_raw_scalar_ref(),
2153 rel_err
2154 );
2155 }
2156
2157 #[test]
2158 fn rescaling_triggered_multiple_times() {
2159 // Ascending order triggers rescaling at each step
2160 let data = vec_f64(&[1.0, 2.0, 4.0, 8.0, 16.0]);
2161 let expected = (1.0 + 4.0 + 16.0 + 64.0 + 256.0_f64).sqrt(); // sqrt(341)
2162 let norm = vector_norm_l2(&data).into_inner();
2163 assert_relative_eq!(*norm.as_raw_scalar_ref(), expected, epsilon = 1e-14);
2164 }
2165
2166 #[test]
2167 fn descending_order_no_rescaling() {
2168 // Descending order: max is found first, no rescaling needed
2169 let data = vec_f64(&[16.0, 8.0, 4.0, 2.0, 1.0]);
2170 let expected = (1.0 + 4.0 + 16.0 + 64.0 + 256.0_f64).sqrt();
2171 let norm = vector_norm_l2(&data).into_inner();
2172 assert_relative_eq!(*norm.as_raw_scalar_ref(), expected, epsilon = 1e-14);
2173 }
2174 }
2175
2176 mod special_values {
2177 use super::*;
2178
2179 #[test]
2180 fn max_finite_value() {
2181 // Single f64::MAX should give f64::MAX
2182 let data = vec_f64(&[f64::MAX]);
2183 let norm = vector_norm_l2(&data).into_inner();
2184 assert_eq!(*norm.as_raw_scalar_ref(), f64::MAX);
2185 }
2186
2187 #[test]
2188 fn min_positive_value() {
2189 // Single f64::MIN_POSITIVE should give f64::MIN_POSITIVE
2190 let data = vec_f64(&[f64::MIN_POSITIVE]);
2191 let norm = vector_norm_l2(&data).into_inner();
2192 assert_eq!(*norm.as_raw_scalar_ref(), f64::MIN_POSITIVE);
2193 }
2194
2195 #[test]
2196 fn epsilon() {
2197 let data = vec_f64(&[f64::EPSILON]);
2198 let norm = vector_norm_l2(&data).into_inner();
2199 assert_eq!(*norm.as_raw_scalar_ref(), f64::EPSILON);
2200 }
2201 }
2202
2203 #[cfg(feature = "rug")]
2204 mod rug_backend {
2205 use super::*;
2206 use crate::functions::{Abs, Sqrt};
2207 use crate::{Constants, RealRugStrictFinite};
2208 use num::Zero;
2209 use try_create::TryNew;
2210
2211 const PRECISION: u32 = 200;
2212 type R = RealRugStrictFinite<PRECISION>;
2213
2214 /// Helper to create a validated rug real from f64
2215 fn rug_f64(v: f64) -> R {
2216 R::try_from_f64(v).unwrap()
2217 }
2218
2219 /// Helper to create a validated rug real from string (for exact values)
2220 fn rug_str(s: &str) -> R {
2221 R::try_new(rug::Float::with_val(
2222 PRECISION,
2223 rug::Float::parse(s).unwrap(),
2224 ))
2225 .unwrap()
2226 }
2227
2228 #[test]
2229 fn basic_3_4_5() {
2230 let data: Vec<R> = vec![rug_f64(3.0), rug_f64(4.0)];
2231 let norm = vector_norm_l2(&data).into_inner();
2232 let five = rug_f64(5.0);
2233
2234 let diff = (norm.clone() - &five).abs().into_inner();
2235 assert!(
2236 diff < R::epsilon(),
2237 "3-4-5: expected 5, got {}, diff {}",
2238 norm,
2239 diff
2240 );
2241 }
2242
2243 #[test]
2244 fn empty_vector() {
2245 let data: Vec<R> = vec![];
2246 let norm = vector_norm_l2(&data).into_inner();
2247 assert_eq!(norm, R::zero());
2248 }
2249
2250 #[test]
2251 fn single_element() {
2252 let data = vec![rug_f64(7.0)];
2253 let norm = vector_norm_l2(&data).into_inner();
2254 let seven = rug_f64(7.0);
2255 assert_eq!(norm, seven);
2256 }
2257
2258 #[test]
2259 fn single_negative() {
2260 let data = vec![rug_f64(-7.0)];
2261 let norm = vector_norm_l2(&data).into_inner();
2262 let seven = rug_f64(7.0);
2263 assert_eq!(norm, seven);
2264 }
2265
2266 #[test]
2267 fn all_zeros() {
2268 let data: Vec<R> = vec![R::zero(), R::zero(), R::zero()];
2269 let norm = vector_norm_l2(&data).into_inner();
2270 assert_eq!(norm, R::zero());
2271 }
2272
2273 #[test]
2274 fn high_precision_values() {
2275 // Values that cannot be exactly represented in f64
2276 let data: Vec<R> = vec![rug_str("1e-100"), rug_str("1e-100")];
2277 let norm = vector_norm_l2(&data).into_inner();
2278
2279 // Expected: sqrt(2) * 1e-100
2280 let expected = rug_str("1e-100") * rug_f64(2.0).sqrt();
2281 let diff = (norm.clone() - &expected).abs().into_inner();
2282
2283 assert!(
2284 diff < R::epsilon() * &expected,
2285 "High precision: expected {}, got {}, diff {}",
2286 expected,
2287 norm,
2288 diff
2289 );
2290 }
2291
2292 #[test]
2293 fn very_large_exponents() {
2294 // Rug can handle much larger exponents than f64
2295 // These values would be infinity in f64
2296 let large = rug_str("1e1000");
2297 let data: Vec<R> = vec![large.clone(), large.clone()];
2298 let norm = vector_norm_l2(&data).into_inner();
2299
2300 // Expected: sqrt(2) * 1e1000
2301 let sqrt2 = rug_f64(2.0).sqrt();
2302 let expected = large * sqrt2;
2303
2304 let rel_diff = ((norm.clone() - &expected) / &expected).abs().into_inner();
2305 assert!(
2306 rel_diff < R::epsilon(),
2307 "Very large exponents: rel_diff = {}",
2308 rel_diff
2309 );
2310 }
2311
2312 #[test]
2313 fn very_small_exponents() {
2314 // Rug can handle much smaller exponents than f64
2315 // These values would be zero in f64
2316 let small = rug_str("1e-1000");
2317 let data: Vec<R> = vec![small.clone(), small.clone()];
2318 let norm = vector_norm_l2(&data).into_inner();
2319
2320 // Expected: sqrt(2) * 1e-1000
2321 let sqrt2 = rug_f64(2.0).sqrt();
2322 let expected = small * sqrt2;
2323
2324 let rel_diff = ((norm.clone() - &expected) / &expected).abs().into_inner();
2325 assert!(
2326 rel_diff < R::epsilon(),
2327 "Very small exponents: rel_diff = {}",
2328 rel_diff
2329 );
2330 }
2331
2332 #[test]
2333 fn mixed_signs_rug() {
2334 let data: Vec<R> = vec![rug_f64(-3.0), rug_f64(4.0)];
2335 let norm = vector_norm_l2(&data).into_inner();
2336 let five = rug_f64(5.0);
2337
2338 let diff = (norm.clone() - &five).abs().into_inner();
2339 assert!(diff < R::epsilon());
2340 }
2341
2342 #[test]
2343 fn zeros_interspersed_rug() {
2344 let data: Vec<R> =
2345 vec![R::zero(), rug_f64(3.0), R::zero(), rug_f64(4.0), R::zero()];
2346 let norm = vector_norm_l2(&data).into_inner();
2347 let five = rug_f64(5.0);
2348
2349 let diff = (norm.clone() - &five).abs().into_inner();
2350 assert!(diff < R::epsilon());
2351 }
2352
2353 #[test]
2354 fn ascending_order_triggers_rescaling() {
2355 let data: Vec<R> = vec![
2356 rug_f64(1.0),
2357 rug_f64(2.0),
2358 rug_f64(4.0),
2359 rug_f64(8.0),
2360 rug_f64(16.0),
2361 ];
2362 // sqrt(1 + 4 + 16 + 64 + 256) = sqrt(341)
2363 let expected = rug_f64(341.0).sqrt();
2364 let norm = vector_norm_l2(&data).into_inner();
2365
2366 let diff = (norm.clone() - &expected).abs().into_inner();
2367 assert!(
2368 diff < R::epsilon() * &expected,
2369 "Ascending: expected {}, got {}, diff {}",
2370 expected,
2371 norm,
2372 diff
2373 );
2374 }
2375
2376 #[test]
2377 fn higher_precision() {
2378 // Test with even higher precision
2379 const HIGH_PREC: u32 = 500;
2380 type HP = RealRugStrictFinite<HIGH_PREC>;
2381
2382 let hp_f64 = |v: f64| HP::try_from_f64(v).unwrap();
2383
2384 let data: Vec<HP> = vec![hp_f64(3.0), hp_f64(4.0)];
2385 let norm = vector_norm_l2(&data).into_inner();
2386 let five = hp_f64(5.0);
2387
2388 let diff = (norm.clone() - &five).abs().into_inner();
2389 assert!(diff < HP::epsilon());
2390 }
2391 }
2392 }
2393
2394 // ── Complex scalar tests ─────────────────────────────────────────────────
2395
2396 mod complex_types {
2397 use super::*;
2398 use crate::{ComplexNative64StrictFinite, RealNative64StrictFinite};
2399 use num::Complex;
2400 use try_create::TryNew;
2401
2402 /// Helper: build a `ComplexNative64StrictFinite` from two f64 components.
2403 fn c(re: f64, im: f64) -> ComplexNative64StrictFinite {
2404 ComplexNative64StrictFinite::try_new(Complex::new(re, im)).unwrap()
2405 }
2406
2407 /// Helper: extract the inner f64 from a `NonNegativeRealScalar<RealNative64StrictFinite>`.
2408 fn inner_f64(x: crate::scalars::NonNegativeRealScalar<RealNative64StrictFinite>) -> f64 {
2409 *x.into_inner().as_raw_scalar_ref()
2410 }
2411
2412 mod vector_norm_l2_complex {
2413 use super::*;
2414
2415 /// ‖[3+0i, 0+4i]‖₂ = √(3² + 4²) = 5
2416 #[test]
2417 fn components_on_axes() {
2418 let v = [c(3.0, 0.0), c(0.0, 4.0)];
2419 assert!((inner_f64(vector_norm_l2(&v)) - 5.0).abs() < 1e-12);
2420 }
2421
2422 /// ‖[3+4i]‖₂ = |3+4i| = 5
2423 #[test]
2424 fn single_complex() {
2425 let v = [c(3.0, 4.0)];
2426 assert!((inner_f64(vector_norm_l2(&v)) - 5.0).abs() < 1e-12);
2427 }
2428
2429 /// ‖[0+0i, 0+0i]‖₂ = 0
2430 #[test]
2431 fn zero_vector() {
2432 let v = [c(0.0, 0.0), c(0.0, 0.0)];
2433 assert_eq!(inner_f64(vector_norm_l2(&v)), 0.0);
2434 }
2435
2436 /// Negating imaginary part does not change the norm.
2437 #[test]
2438 fn conjugate_preserves_norm() {
2439 let v = [c(1.0, 2.0), c(3.0, -4.0)];
2440 let vc = [c(1.0, -2.0), c(3.0, 4.0)];
2441 assert!(
2442 (inner_f64(vector_norm_l2(&v)) - inner_f64(vector_norm_l2(&vc))).abs() < 1e-14
2443 );
2444 }
2445
2446 /// vector_norm_l2 and vector_norm_l2_neumaier agree on ordinary values.
2447 #[test]
2448 fn matches_neumaier() {
2449 let v = [c(1.0, 2.0), c(3.0, -1.0), c(-1.0, 4.0)];
2450 let diff =
2451 (inner_f64(vector_norm_l2(&v)) - inner_f64(vector_norm_l2_neumaier(&v))).abs();
2452 assert!(diff < 1e-14);
2453 }
2454 }
2455
2456 mod vector_norm_l2_sq_complex {
2457 use super::*;
2458
2459 /// ‖[3+0i, 0+4i]‖₂² = 9 + 16 = 25
2460 #[test]
2461 fn components_on_axes() {
2462 let v = [c(3.0, 0.0), c(0.0, 4.0)];
2463 assert!((inner_f64(vector_norm_l2_sq(&v)) - 25.0).abs() < 1e-12);
2464 }
2465
2466 /// ‖[3+4i]‖₂² = |3+4i|² = 25
2467 #[test]
2468 fn single_complex() {
2469 let v = [c(3.0, 4.0)];
2470 assert!((inner_f64(vector_norm_l2_sq(&v)) - 25.0).abs() < 1e-12);
2471 }
2472
2473 /// vector_norm_l2_sq(v) ≈ vector_norm_l2(v)²
2474 #[test]
2475 fn consistent_with_vector_norm_l2() {
2476 let v = [c(1.0, 2.0), c(3.0, 4.0)];
2477 let l2 = inner_f64(vector_norm_l2(&v));
2478 let sq = inner_f64(vector_norm_l2_sq(&v));
2479 assert!((sq - l2 * l2).abs() < 1e-12);
2480 }
2481
2482 /// vector_norm_l2_sq and vector_norm_l2_sq_neumaier agree on ordinary values.
2483 #[test]
2484 fn matches_neumaier() {
2485 let v = [c(3.0, 0.0), c(0.0, 4.0)];
2486 let diff = (inner_f64(vector_norm_l2_sq(&v))
2487 - inner_f64(vector_norm_l2_sq_neumaier(&v)))
2488 .abs();
2489 assert!(diff < 1e-14);
2490 }
2491 }
2492
2493 mod vector_norm_l2_sq_neumaier_complex {
2494 use super::*;
2495
2496 #[test]
2497 fn zero_vector() {
2498 let v = [c(0.0, 0.0), c(0.0, 0.0)];
2499 assert_eq!(inner_f64(vector_norm_l2_sq_neumaier(&v)), 0.0);
2500 }
2501
2502 #[test]
2503 fn matches_naive() {
2504 let v = [c(2.0, 1.0), c(0.0, 3.0), c(-1.0, -1.0)];
2505 let diff = (inner_f64(vector_norm_l2_sq_neumaier(&v))
2506 - inner_f64(vector_norm_l2_sq(&v)))
2507 .abs();
2508 assert!(diff < 1e-14);
2509 }
2510 }
2511
2512 mod vector_norm_l2_neumaier_complex {
2513 use super::*;
2514
2515 #[test]
2516 fn zero_vector() {
2517 let v = [c(0.0, 0.0)];
2518 assert_eq!(inner_f64(vector_norm_l2_neumaier(&v)), 0.0);
2519 }
2520
2521 #[test]
2522 fn matches_naive() {
2523 let v = [c(2.0, 1.0), c(0.0, 3.0), c(-1.0, -1.0)];
2524 let diff =
2525 (inner_f64(vector_norm_l2_neumaier(&v)) - inner_f64(vector_norm_l2(&v))).abs();
2526 assert!(diff < 1e-14);
2527 }
2528 }
2529
2530 mod vector_norm_l1_complex {
2531 use super::*;
2532
2533 /// ‖[3+0i, 0+4i]‖₁ = |3+0i| + |0+4i| = 3 + 4 = 7
2534 #[test]
2535 fn components_on_axes() {
2536 let v = [c(3.0, 0.0), c(0.0, 4.0)];
2537 assert!((inner_f64(vector_norm_l1(&v)) - 7.0).abs() < 1e-12);
2538 }
2539
2540 /// ‖[3+4i]‖₁ = |3+4i| = 5
2541 #[test]
2542 fn single_complex() {
2543 let v = [c(3.0, 4.0)];
2544 assert!((inner_f64(vector_norm_l1(&v)) - 5.0).abs() < 1e-12);
2545 }
2546
2547 /// ‖v‖₁ ≥ ‖v‖₂ for multi-element vectors (triangle inequality corollary)
2548 #[test]
2549 fn l1_geq_l2() {
2550 let v = [c(3.0, 0.0), c(0.0, 4.0)];
2551 assert!(inner_f64(vector_norm_l1(&v)) >= inner_f64(vector_norm_l2(&v)) - 1e-14);
2552 }
2553
2554 #[test]
2555 fn zero_vector() {
2556 let v = [c(0.0, 0.0), c(0.0, 0.0)];
2557 assert_eq!(inner_f64(vector_norm_l1(&v)), 0.0);
2558 }
2559
2560 /// vector_norm_l1 and vector_norm_l1_neumaier agree on ordinary values.
2561 #[test]
2562 fn matches_neumaier() {
2563 let v = [c(1.0, 2.0), c(3.0, 4.0), c(5.0, 6.0)];
2564 let diff =
2565 (inner_f64(vector_norm_l1(&v)) - inner_f64(vector_norm_l1_neumaier(&v))).abs();
2566 assert!(diff < 1e-14);
2567 }
2568 }
2569
2570 mod vector_norm_l1_neumaier_complex {
2571 use super::*;
2572
2573 #[test]
2574 fn components_on_axes() {
2575 let v = [c(3.0, 0.0), c(0.0, 4.0)];
2576 assert!((inner_f64(vector_norm_l1_neumaier(&v)) - 7.0).abs() < 1e-12);
2577 }
2578
2579 #[test]
2580 fn zero_vector() {
2581 let v = [c(0.0, 0.0)];
2582 assert_eq!(inner_f64(vector_norm_l1_neumaier(&v)), 0.0);
2583 }
2584 }
2585
2586 mod vector_norm_linf_complex {
2587 use super::*;
2588
2589 /// ‖[3+0i, 0+4i]‖∞ = max(3, 4) = 4
2590 #[test]
2591 fn maximum_modulus() {
2592 let v = [c(3.0, 0.0), c(0.0, 4.0)];
2593 assert!((inner_f64(vector_norm_linf(&v)) - 4.0).abs() < 1e-12);
2594 }
2595
2596 /// ‖[3+4i, 0+5i]‖∞ = max(5, 5) = 5 (equal moduli)
2597 #[test]
2598 fn equal_moduli() {
2599 let v = [c(3.0, 4.0), c(0.0, 5.0)];
2600 assert!((inner_f64(vector_norm_linf(&v)) - 5.0).abs() < 1e-12);
2601 }
2602
2603 /// ‖v‖∞ ≤ ‖v‖₁ always holds
2604 #[test]
2605 fn linf_leq_l1() {
2606 let v = [c(1.0, 2.0), c(3.0, 4.0)];
2607 assert!(inner_f64(vector_norm_linf(&v)) <= inner_f64(vector_norm_l1(&v)) + 1e-14);
2608 }
2609
2610 /// ‖v‖∞ ≤ ‖v‖₂ ≤ √n · ‖v‖∞ (standard norm inequality)
2611 #[test]
2612 fn linf_leq_l2() {
2613 let v = [c(1.0, 2.0), c(3.0, 4.0)];
2614 let linf = inner_f64(vector_norm_linf(&v));
2615 let l2 = inner_f64(vector_norm_l2(&v));
2616 let n = v.len() as f64;
2617 assert!(linf <= l2 + 1e-14);
2618 assert!(l2 <= n.sqrt() * linf + 1e-14);
2619 }
2620
2621 #[test]
2622 fn zero_vector() {
2623 let v = [c(0.0, 0.0), c(0.0, 0.0)];
2624 assert_eq!(inner_f64(vector_norm_linf(&v)), 0.0);
2625 }
2626
2627 #[test]
2628 fn parallel_matches_sequential() {
2629 let v = [c(1.0, 2.0), c(3.0, 4.0), c(-2.0, -1.0)];
2630 let seq = inner_f64(vector_norm_linf(&v));
2631 let par = inner_f64(vector_norm_linf_par(&v));
2632 assert!((seq - par).abs() < 1e-14);
2633 }
2634 }
2635
2636 mod api_variants {
2637 use super::*;
2638
2639 type L1Naive = NaiveSum<NonNegativeRealScalar<f64>>;
2640 type L2Naive = NaiveSum<f64>;
2641
2642 #[test]
2643 fn exercises_iterator_and_parallel_variants() {
2644 let values = [3.0_f64, -4.0, 5.0];
2645
2646 assert_eq!(
2647 *L2Norm::<L2Naive>::compute_vector_norm_sq_par(&values).as_raw_scalar_ref(),
2648 50.0
2649 );
2650 assert_eq!(
2651 *L2Norm::<L2Naive>::compute_vector_norm_sq_par_iter(values.par_iter().cloned())
2652 .as_raw_scalar_ref(),
2653 50.0
2654 );
2655
2656 assert_eq!(
2657 *L2Norm::<L2Naive>::compute_vector_norm_iter(values.iter().cloned())
2658 .as_raw_scalar_ref(),
2659 50.0_f64.sqrt()
2660 );
2661 assert_eq!(
2662 *L2Norm::<L2Naive>::compute_vector_norm_par(&values).as_raw_scalar_ref(),
2663 50.0_f64.sqrt()
2664 );
2665 assert_eq!(
2666 *L2Norm::<L2Naive>::compute_vector_norm_par_iter(values.par_iter().cloned())
2667 .as_raw_scalar_ref(),
2668 50.0_f64.sqrt()
2669 );
2670
2671 assert_eq!(
2672 *L1Norm::<L1Naive>::compute_vector_norm_iter(values.iter().cloned())
2673 .as_raw_scalar_ref(),
2674 12.0
2675 );
2676 assert_eq!(
2677 *L1Norm::<L1Naive>::compute_vector_norm_par(&values).as_raw_scalar_ref(),
2678 12.0
2679 );
2680 assert_eq!(
2681 *L1Norm::<L1Naive>::compute_vector_norm_par_iter(values.par_iter().cloned())
2682 .as_raw_scalar_ref(),
2683 12.0
2684 );
2685
2686 assert_eq!(
2687 *LinfNorm::<()>::compute_vector_norm_iter(values.iter().cloned())
2688 .as_raw_scalar_ref(),
2689 5.0
2690 );
2691 assert_eq!(
2692 *LinfNorm::<()>::compute_vector_norm_par(&values).as_raw_scalar_ref(),
2693 5.0
2694 );
2695 assert_eq!(
2696 *LinfNorm::<()>::compute_vector_norm_par_iter(values.par_iter().cloned())
2697 .as_raw_scalar_ref(),
2698 5.0
2699 );
2700 }
2701
2702 #[test]
2703 fn exercises_free_iterator_and_accumulator_helpers() {
2704 let values = [3.0_f64, -4.0, 5.0];
2705
2706 assert_eq!(
2707 *vector_norm_l2_with_accumulator::<_, L2Naive>(&values).as_raw_scalar_ref(),
2708 50.0_f64.sqrt()
2709 );
2710 assert_eq!(
2711 *vector_norm_l2_iter_with_accumulator::<_, L2Naive, _>(values.iter().cloned())
2712 .as_raw_scalar_ref(),
2713 50.0_f64.sqrt()
2714 );
2715 assert_eq!(
2716 *vector_norm_l2_sq_with_accumulator::<_, L2Naive>(&values).as_raw_scalar_ref(),
2717 50.0
2718 );
2719 assert_eq!(
2720 *vector_norm_l2_sq_iter_with_accumulator::<_, L2Naive, _>(
2721 values.iter().cloned()
2722 )
2723 .as_raw_scalar_ref(),
2724 50.0
2725 );
2726 assert_eq!(
2727 *vector_norm_l1_with_accumulator::<_, L1Naive>(&values).as_raw_scalar_ref(),
2728 12.0
2729 );
2730 assert_eq!(
2731 *vector_norm_l1_iter_with_accumulator::<_, L1Naive, _>(values.iter().cloned())
2732 .as_raw_scalar_ref(),
2733 12.0
2734 );
2735
2736 assert_eq!(
2737 *vector_norm_l2_iter(values.iter().cloned()).as_raw_scalar_ref(),
2738 50.0_f64.sqrt()
2739 );
2740 assert_eq!(
2741 *vector_norm_l2_sq_iter(values.iter().cloned()).as_raw_scalar_ref(),
2742 50.0
2743 );
2744 assert_eq!(
2745 *vector_norm_l2_sq_neumaier_iter(values.iter().cloned()).as_raw_scalar_ref(),
2746 50.0
2747 );
2748 assert_eq!(
2749 *vector_norm_l2_neumaier_iter(values.iter().cloned()).as_raw_scalar_ref(),
2750 50.0_f64.sqrt()
2751 );
2752 assert_eq!(
2753 *vector_norm_l1_iter(values.iter().cloned()).as_raw_scalar_ref(),
2754 12.0
2755 );
2756 assert_eq!(
2757 *vector_norm_l1_neumaier_iter(values.iter().cloned()).as_raw_scalar_ref(),
2758 12.0
2759 );
2760 assert_eq!(
2761 *vector_norm_linf_iter(values.iter().cloned()).as_raw_scalar_ref(),
2762 5.0
2763 );
2764 assert_eq!(
2765 *vector_norm_linf_par_iter(values.par_iter().cloned()).as_raw_scalar_ref(),
2766 5.0
2767 );
2768 }
2769 }
2770 }
2771}