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
}