## Coverage Report

#### Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/trueno/src/vector/ops/norms.rs

    Line
    Count
    Source
    1
    //! Vector norm operations
    2
    //!
    3
    //! This module provides vector norm calculations:
    4
    //! - `norm_l1()` - L1 norm (Manhattan norm)
    5
    //! - `norm_l2()` - L2 norm (Euclidean norm)
    6
    //! - `norm_linf()` - L∞ norm (infinity norm / max norm)
    7
    8
    #[cfg(target_arch = "x86_64")]
    9
    use crate::backends::avx2::Avx2Backend;
    10
    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
    11
    use crate::backends::neon::NeonBackend;
    12
    use crate::backends::scalar::ScalarBackend;
    13
    #[cfg(target_arch = "x86_64")]
    14
    use crate::backends::sse2::Sse2Backend;
    15
    #[cfg(target_arch = "wasm32")]
    16
    use crate::backends::wasm::WasmBackend;
    17
    use crate::backends::VectorBackend;
    18
    use crate::{Backend, Result, Vector};
    19
    20
    impl Vector<f32> {
    21
        /// L2 norm (Euclidean norm)
    22
        ///
    23
        /// Computes the Euclidean length of the vector: sqrt(sum(a\[i\]^2)).
    24
        /// This is mathematically equivalent to sqrt(dot(self, self)).
    25
        ///
    26
        /// # Performance
    27
        ///
    28
        /// Uses optimized SIMD implementations via the dot product operation.
    29
        ///
    30
        /// # Examples
    31
        ///
    32
        /// ```
    33
        /// use trueno::Vector;
    34
        ///
    35
        /// let v = Vector::from_slice(&[3.0, 4.0]);
    36
        /// let norm = v.norm_l2().unwrap();
    37
        /// assert!((norm - 5.0).abs() < 1e-5); // sqrt(3^2 + 4^2) = 5
    38
        /// ```
    39
        ///
    40
        /// # Empty vectors
    41
        ///
    42
        /// Returns 0.0 for empty vectors (consistent with the mathematical definition).
    43
        ///
    44
        /// ```
    45
        /// use trueno::Vector;
    46
        ///
    47
        /// let v: Vector<f32> = Vector::from_slice(&[]);
    48
        /// assert_eq!(v.norm_l2().unwrap(), 0.0);
    49
        /// ```
    50
    0
        pub fn norm_l2(&self) -> Result<f32> {
    51
    0
            if self.as_slice().is_empty() {
    52
    0
                return Ok(0.0);
    53
    0
            }
    54
    55
            // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
    56
    0
            let result = unsafe {
    57
    0
                match self.backend() {
    58
    0
                    Backend::Scalar => ScalarBackend::norm_l2(self.as_slice()),
    59
                    #[cfg(target_arch = "x86_64")]
    60
    0
                    Backend::SSE2 | Backend::AVX => Sse2Backend::norm_l2(self.as_slice()),
    61
                    #[cfg(target_arch = "x86_64")]
    62
    0
                    Backend::AVX2 | Backend::AVX512 => Avx2Backend::norm_l2(self.as_slice()),
    63
                    #[cfg(not(target_arch = "x86_64"))]
    64
                    Backend::SSE2 | Backend::AVX | Backend::AVX2 | Backend::AVX512 => {
    65
                        ScalarBackend::norm_l2(self.as_slice())
    66
                    }
    67
                    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
    68
                    Backend::NEON => NeonBackend::norm_l2(self.as_slice()),
    69
                    #[cfg(not(any(target_arch = "aarch64", target_arch = "arm")))]
    70
    0
                    Backend::NEON => ScalarBackend::norm_l2(self.as_slice()),
    71
                    #[cfg(target_arch = "wasm32")]
    72
                    Backend::WasmSIMD => WasmBackend::norm_l2(self.as_slice()),
    73
                    #[cfg(not(target_arch = "wasm32"))]
    74
    0
                    Backend::WasmSIMD => ScalarBackend::norm_l2(self.as_slice()),
    75
    0
                    Backend::GPU | Backend::Auto => ScalarBackend::norm_l2(self.as_slice()),
    76
                }
    77
            };
    78
    79
    0
            Ok(result)
    80
    0
        }
    81
    82
        /// Compute the L1 norm (Manhattan norm) of the vector
    83
        ///
    84
        /// Returns the sum of absolute values: ||v||₁ = sum(|v\[i\]|)
    85
        ///
    86
        /// The L1 norm is used in:
    87
        /// - Machine learning (L1 regularization, Lasso regression)
    88
        /// - Distance metrics (Manhattan distance)
    89
        /// - Sparse modeling and feature selection
    90
        /// - Signal processing
    91
        ///
    92
        /// # Examples
    93
        ///
    94
        /// ```
    95
        /// use trueno::Vector;
    96
        ///
    97
        /// let v = Vector::from_slice(&[3.0, -4.0, 5.0]);
    98
        /// let norm = v.norm_l1().unwrap();
    99
        ///
    100
        /// // |3| + |-4| + |5| = 12
    101
        /// assert!((norm - 12.0).abs() < 1e-5);
    102
        /// ```
    103
        ///
    104
        /// # Empty Vector
    105
        ///
    106
        /// ```
    107
        /// use trueno::Vector;
    108
        ///
    109
        /// let v: Vector<f32> = Vector::from_slice(&[]);
    110
        /// assert_eq!(v.norm_l1().unwrap(), 0.0);
    111
        /// ```
    112
    0
        pub fn norm_l1(&self) -> Result<f32> {
    113
    0
            if self.as_slice().is_empty() {
    114
    0
                return Ok(0.0);
    115
    0
            }
    116
    117
            // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
    118
    0
            let result = unsafe {
    119
    0
                match self.backend() {
    120
    0
                    Backend::Scalar => ScalarBackend::norm_l1(self.as_slice()),
    121
                    #[cfg(target_arch = "x86_64")]
    122
    0
                    Backend::SSE2 | Backend::AVX => Sse2Backend::norm_l1(self.as_slice()),
    123
                    #[cfg(target_arch = "x86_64")]
    124
    0
                    Backend::AVX2 | Backend::AVX512 => Avx2Backend::norm_l1(self.as_slice()),
    125
                    #[cfg(not(target_arch = "x86_64"))]
    126
                    Backend::SSE2 | Backend::AVX | Backend::AVX2 | Backend::AVX512 => {
    127
                        ScalarBackend::norm_l1(self.as_slice())
    128
                    }
    129
                    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
    130
                    Backend::NEON => NeonBackend::norm_l1(self.as_slice()),
    131
                    #[cfg(not(any(target_arch = "aarch64", target_arch = "arm")))]
    132
    0
                    Backend::NEON => ScalarBackend::norm_l1(self.as_slice()),
    133
                    #[cfg(target_arch = "wasm32")]
    134
                    Backend::WasmSIMD => WasmBackend::norm_l1(self.as_slice()),
    135
                    #[cfg(not(target_arch = "wasm32"))]
    136
    0
                    Backend::WasmSIMD => ScalarBackend::norm_l1(self.as_slice()),
    137
    0
                    Backend::GPU | Backend::Auto => ScalarBackend::norm_l1(self.as_slice()),
    138
                }
    139
            };
    140
    141
    0
            Ok(result)
    142
    0
        }
    143
    144
        /// Compute the L∞ norm (infinity norm / max norm) of the vector
    145
        ///
    146
        /// Returns the maximum absolute value: ||v||∞ = max(|v\[i\]|)
    147
        ///
    148
        /// The L∞ norm is used in:
    149
        /// - Numerical analysis (error bounds, stability analysis)
    150
        /// - Optimization (Chebyshev approximation)
    151
        /// - Signal processing (peak detection)
    152
        /// - Distance metrics (Chebyshev distance)
    153
        ///
    154
        /// # Examples
    155
        ///
    156
        /// ```
    157
        /// use trueno::Vector;
    158
        ///
    159
        /// let v = Vector::from_slice(&[3.0, -7.0, 5.0, -2.0]);
    160
        /// let norm = v.norm_linf().unwrap();
    161
        ///
    162
        /// // max(|3|, |-7|, |5|, |-2|) = 7
    163
        /// assert!((norm - 7.0).abs() < 1e-5);
    164
        /// ```
    165
        ///
    166
        /// # Empty Vector
    167
        ///
    168
        /// ```
    169
        /// use trueno::Vector;
    170
        ///
    171
        /// let v: Vector<f32> = Vector::from_slice(&[]);
    172
        /// assert_eq!(v.norm_linf().unwrap(), 0.0);
    173
        /// ```
    174
    0
        pub fn norm_linf(&self) -> Result<f32> {
    175
    0
            if self.as_slice().is_empty() {
    176
    0
                return Ok(0.0);
    177
    0
            }
    178
    179
            // Use optimized SIMD backend for single-pass abs+max
    180
            // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
    181
    0
            let max_abs = unsafe {
    182
    0
                match self.backend() {
    183
    0
                    Backend::Scalar => ScalarBackend::norm_linf(self.as_slice()),
    184
                    #[cfg(target_arch = "x86_64")]
    185
    0
                    Backend::SSE2 | Backend::AVX => Sse2Backend::norm_linf(self.as_slice()),
    186
                    #[cfg(target_arch = "x86_64")]
    187
    0
                    Backend::AVX2 | Backend::AVX512 => Avx2Backend::norm_linf(self.as_slice()),
    188
                    #[cfg(not(target_arch = "x86_64"))]
    189
                    Backend::SSE2 | Backend::AVX | Backend::AVX2 | Backend::AVX512 => {
    190
                        ScalarBackend::norm_linf(self.as_slice())
    191
                    }
    192
                    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
    193
                    Backend::NEON => ScalarBackend::norm_linf(self.as_slice()), // NEON fallback
    194
                    #[cfg(not(any(target_arch = "aarch64", target_arch = "arm")))]
    195
    0
                    Backend::NEON => ScalarBackend::norm_linf(self.as_slice()),
    196
                    #[cfg(target_arch = "wasm32")]
    197
                    Backend::WasmSIMD => ScalarBackend::norm_linf(self.as_slice()), // WASM fallback
    198
                    #[cfg(not(target_arch = "wasm32"))]
    199
    0
                    Backend::WasmSIMD => ScalarBackend::norm_linf(self.as_slice()),
    200
    0
                    Backend::GPU | Backend::Auto => ScalarBackend::norm_linf(self.as_slice()),
    201
                }
    202
            };
    203
    204
    0
            Ok(max_abs)
    205
    0
        }
    206
    }
    207
    208
    #[cfg(test)]
    209
    mod tests {
    210
        use super::*;
    211
    212
        #[test]
    213
        fn test_norm_l2_pythagorean() {
    214
            let v = Vector::from_slice(&[3.0, 4.0]);
    215
            let norm = v.norm_l2().unwrap();
    216
            assert!((norm - 5.0).abs() < 1e-5); // 3-4-5 triangle
    217
        }
    218
    219
        #[test]
    220
        fn test_norm_l2_empty() {
    221
            let v: Vector<f32> = Vector::from_slice(&[]);
    222
            assert_eq!(v.norm_l2().unwrap(), 0.0);
    223
        }
    224
    225
        #[test]
    226
        fn test_norm_l2_unit() {
    227
            let v = Vector::from_slice(&[1.0, 0.0, 0.0]);
    228
            assert!((v.norm_l2().unwrap() - 1.0).abs() < 1e-5);
    229
        }
    230
    231
        #[test]
    232
        fn test_norm_l1_basic() {
    233
            let v = Vector::from_slice(&[3.0, -4.0, 5.0]);
    234
            let norm = v.norm_l1().unwrap();
    235
            assert!((norm - 12.0).abs() < 1e-5);
    236
        }
    237
    238
        #[test]
    239
        fn test_norm_l1_empty() {
    240
            let v: Vector<f32> = Vector::from_slice(&[]);
    241
            assert_eq!(v.norm_l1().unwrap(), 0.0);
    242
        }
    243
    244
        #[test]
    245
        fn test_norm_linf_basic() {
    246
            let v = Vector::from_slice(&[3.0, -7.0, 5.0, -2.0]);
    247
            let norm = v.norm_linf().unwrap();
    248
            assert!((norm - 7.0).abs() < 1e-5);
    249
        }
    250
    251
        #[test]
    252
        fn test_norm_linf_empty() {
    253
            let v: Vector<f32> = Vector::from_slice(&[]);
    254
            assert_eq!(v.norm_linf().unwrap(), 0.0);
    255
        }
    256
    257
        #[test]
    258
        fn test_norm_linf_all_negative() {
    259
            let v = Vector::from_slice(&[-1.0, -5.0, -3.0]);
    260
            let norm = v.norm_linf().unwrap();
    261
            assert!((norm - 5.0).abs() < 1e-5);
    262
        }
    263
    }