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