Skip to main content

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