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