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/transforms.rs
Line
Count
Source
1
//! Vector transformation operations
2
//!
3
//! This module provides element-wise transformation methods:
4
//! - `abs()` - Element-wise absolute value
5
//! - `clamp()` / `clip()` - Clamp values to a range
6
//! - `lerp()` - Linear interpolation between two vectors
7
//! - `sqrt()` - Element-wise square root
8
//! - `recip()` - Element-wise reciprocal (1/x)
9
//! - `pow()` - Element-wise power
10
11
#[cfg(target_arch = "x86_64")]
12
use crate::backends::avx2::Avx2Backend;
13
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
14
use crate::backends::neon::NeonBackend;
15
use crate::backends::scalar::ScalarBackend;
16
#[cfg(target_arch = "x86_64")]
17
use crate::backends::sse2::Sse2Backend;
18
#[cfg(target_arch = "wasm32")]
19
use crate::backends::wasm::WasmBackend;
20
use crate::backends::VectorBackend;
21
use crate::dispatch_unary_op;
22
use crate::{Backend, Result, TruenoError, Vector};
23
24
impl Vector<f32> {
25
    /// Compute element-wise absolute value
26
    ///
27
    /// Returns a new vector where each element is the absolute value of the corresponding input element.
28
    ///
29
    /// # Examples
30
    ///
31
    /// ```
32
    /// use trueno::Vector;
33
    ///
34
    /// let v = Vector::from_slice(&[3.0, -4.0, 5.0, -2.0]);
35
    /// let result = v.abs().unwrap();
36
    ///
37
    /// assert_eq!(result.as_slice(), &[3.0, 4.0, 5.0, 2.0]);
38
    /// ```
39
    ///
40
    /// # Empty Vector
41
    ///
42
    /// ```
43
    /// use trueno::Vector;
44
    ///
45
    /// let v: Vector<f32> = Vector::from_slice(&[]);
46
    /// let result = v.abs().unwrap();
47
    /// assert_eq!(result.len(), 0);
48
    /// ```
49
0
    pub fn abs(&self) -> Result<Vector<f32>> {
50
0
        let mut result_data = vec![0.0; self.len()];
51
52
0
        if !self.as_slice().is_empty() {
53
            // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
54
            unsafe {
55
0
                match self.backend() {
56
0
                    Backend::Scalar => ScalarBackend::abs(self.as_slice(), &mut result_data),
57
                    #[cfg(target_arch = "x86_64")]
58
                    Backend::SSE2 | Backend::AVX => {
59
0
                        Sse2Backend::abs(self.as_slice(), &mut result_data)
60
                    }
61
                    #[cfg(target_arch = "x86_64")]
62
                    Backend::AVX2 | Backend::AVX512 => {
63
0
                        Avx2Backend::abs(self.as_slice(), &mut result_data)
64
                    }
65
                    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
66
                    Backend::NEON => NeonBackend::abs(self.as_slice(), &mut result_data),
67
                    #[cfg(target_arch = "wasm32")]
68
                    Backend::WasmSIMD => WasmBackend::abs(self.as_slice(), &mut result_data),
69
0
                    Backend::GPU => return Err(TruenoError::UnsupportedBackend(Backend::GPU)),
70
                    Backend::Auto => {
71
0
                        return Err(TruenoError::UnsupportedBackend(Backend::Auto));
72
                    }
73
                    #[allow(unreachable_patterns)]
74
0
                    _ => ScalarBackend::abs(self.as_slice(), &mut result_data),
75
                }
76
            }
77
0
        }
78
79
0
        Ok(Vector::from_slice_with_backend(&result_data, self.backend()))
80
0
    }
81
82
    /// Clip values to a specified range [min_val, max_val]
83
    ///
84
    /// Constrains each element to be within the specified range:
85
    /// - Values below min_val become min_val
86
    /// - Values above max_val become max_val
87
    /// - Values within range stay unchanged
88
    ///
89
    /// This is useful for outlier handling, gradient clipping in neural networks,
90
    /// and ensuring values stay within valid bounds.
91
    ///
92
    /// # Examples
93
    ///
94
    /// ```
95
    /// use trueno::Vector;
96
    ///
97
    /// let v = Vector::from_slice(&[-5.0, 0.0, 5.0, 10.0, 15.0]);
98
    /// let clipped = v.clip(0.0, 10.0).unwrap();
99
    ///
100
    /// // Values: [-5, 0, 5, 10, 15] → [0, 0, 5, 10, 10]
101
    /// assert_eq!(clipped.as_slice(), &[0.0, 0.0, 5.0, 10.0, 10.0]);
102
    /// ```
103
    ///
104
    /// # Invalid range
105
    ///
106
    /// Returns InvalidInput error if min_val > max_val.
107
    ///
108
    /// ```
109
    /// use trueno::{Vector, TruenoError};
110
    ///
111
    /// let v = Vector::from_slice(&[1.0, 2.0, 3.0]);
112
    /// let result = v.clip(10.0, 5.0); // min > max
113
    /// assert!(matches!(result, Err(TruenoError::InvalidInput(_))));
114
    /// ```
115
0
    pub fn clip(&self, min_val: f32, max_val: f32) -> Result<Self> {
116
0
        if min_val > max_val {
117
0
            return Err(TruenoError::InvalidInput(format!(
118
0
                "min_val ({}) must be <= max_val ({})",
119
0
                min_val, max_val
120
0
            )));
121
0
        }
122
123
        // Scalar fallback: Element-wise clamp
124
0
        let data: Vec<f32> = self
125
0
            .as_slice()
126
0
            .iter()
127
0
            .map(|&x| x.max(min_val).min(max_val))
128
0
            .collect();
129
130
0
        Ok(Vector::from_vec(data))
131
0
    }
132
133
    /// Clamp elements to range [min_val, max_val]
134
    ///
135
    /// Returns a new vector where each element is constrained to the specified range.
136
    /// Elements below min_val become min_val, elements above max_val become max_val.
137
    ///
138
    /// # Examples
139
    ///
140
    /// ```
141
    /// use trueno::Vector;
142
    ///
143
    /// let v = Vector::from_slice(&[-5.0, 0.0, 5.0, 10.0, 15.0]);
144
    /// let result = v.clamp(0.0, 10.0).unwrap();
145
    ///
146
    /// assert_eq!(result.as_slice(), &[0.0, 0.0, 5.0, 10.0, 10.0]);
147
    /// ```
148
    ///
149
    /// # Negative Range
150
    ///
151
    /// ```
152
    /// use trueno::Vector;
153
    ///
154
    /// let v = Vector::from_slice(&[-10.0, -5.0, 0.0, 5.0]);
155
    /// let result = v.clamp(-8.0, -2.0).unwrap();
156
    /// assert_eq!(result.as_slice(), &[-8.0, -5.0, -2.0, -2.0]);
157
    /// ```
158
    ///
159
    /// # Errors
160
    ///
161
    /// Returns `InvalidInput` if min_val > max_val.
162
0
    pub fn clamp(&self, min_val: f32, max_val: f32) -> Result<Vector<f32>> {
163
        // Validate range
164
0
        if min_val > max_val {
165
0
            return Err(TruenoError::InvalidInput(format!(
166
0
                "Invalid clamp range: min ({}) > max ({})",
167
0
                min_val, max_val
168
0
            )));
169
0
        }
170
171
0
        let mut result_data = vec![0.0; self.len()];
172
173
0
        if !self.as_slice().is_empty() {
174
            // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
175
            unsafe {
176
0
                match self.backend() {
177
                    Backend::Scalar => {
178
0
                        ScalarBackend::clamp(self.as_slice(), min_val, max_val, &mut result_data)
179
                    }
180
                    #[cfg(target_arch = "x86_64")]
181
                    Backend::SSE2 | Backend::AVX => {
182
0
                        Sse2Backend::clamp(self.as_slice(), min_val, max_val, &mut result_data)
183
                    }
184
                    #[cfg(target_arch = "x86_64")]
185
                    Backend::AVX2 | Backend::AVX512 => {
186
0
                        Avx2Backend::clamp(self.as_slice(), min_val, max_val, &mut result_data)
187
                    }
188
                    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
189
                    Backend::NEON => {
190
                        NeonBackend::clamp(self.as_slice(), min_val, max_val, &mut result_data)
191
                    }
192
                    #[cfg(target_arch = "wasm32")]
193
                    Backend::WasmSIMD => {
194
                        WasmBackend::clamp(self.as_slice(), min_val, max_val, &mut result_data)
195
                    }
196
0
                    Backend::GPU => return Err(TruenoError::UnsupportedBackend(Backend::GPU)),
197
                    Backend::Auto => {
198
0
                        return Err(TruenoError::UnsupportedBackend(Backend::Auto));
199
                    }
200
                    #[allow(unreachable_patterns)]
201
                    _ => {
202
0
                        ScalarBackend::clamp(self.as_slice(), min_val, max_val, &mut result_data)
203
                    }
204
                }
205
            }
206
0
        }
207
208
0
        Ok(Vector::from_slice_with_backend(&result_data, self.backend()))
209
0
    }
210
211
    /// Linear interpolation between two vectors
212
    ///
213
    /// Computes element-wise linear interpolation: `result\[i\] = a\[i\] + t * (b\[i\] - a\[i\])`
214
    ///
215
    /// - When `t = 0.0`, returns `self`
216
    /// - When `t = 1.0`, returns `other`
217
    /// - Values outside `[0, 1]` perform extrapolation
218
    ///
219
    /// # Examples
220
    ///
221
    /// ```
222
    /// use trueno::Vector;
223
    ///
224
    /// let a = Vector::from_slice(&[0.0, 10.0, 20.0]);
225
    /// let b = Vector::from_slice(&[100.0, 110.0, 120.0]);
226
    /// let result = a.lerp(&b, 0.5).unwrap();
227
    ///
228
    /// assert_eq!(result.as_slice(), &[50.0, 60.0, 70.0]);
229
    /// ```
230
    ///
231
    /// # Extrapolation
232
    ///
233
    /// ```
234
    /// use trueno::Vector;
235
    ///
236
    /// let a = Vector::from_slice(&[0.0, 10.0]);
237
    /// let b = Vector::from_slice(&[10.0, 20.0]);
238
    ///
239
    /// // t > 1.0 extrapolates beyond b
240
    /// let result = a.lerp(&b, 2.0).unwrap();
241
    /// assert_eq!(result.as_slice(), &[20.0, 30.0]);
242
    /// ```
243
    ///
244
    /// # Errors
245
    ///
246
    /// Returns `SizeMismatch` if vectors have different lengths.
247
0
    pub fn lerp(&self, other: &Vector<f32>, t: f32) -> Result<Vector<f32>> {
248
0
        if self.len() != other.len() {
249
0
            return Err(TruenoError::SizeMismatch {
250
0
                expected: self.len(),
251
0
                actual: other.len(),
252
0
            });
253
0
        }
254
255
0
        let mut result_data = vec![0.0; self.len()];
256
257
0
        if !self.as_slice().is_empty() {
258
            // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
259
            unsafe {
260
0
                match self.backend() {
261
                    Backend::Scalar => {
262
0
                        ScalarBackend::lerp(self.as_slice(), other.as_slice(), t, &mut result_data)
263
                    }
264
                    #[cfg(target_arch = "x86_64")]
265
                    Backend::SSE2 | Backend::AVX => {
266
0
                        Sse2Backend::lerp(self.as_slice(), other.as_slice(), t, &mut result_data)
267
                    }
268
                    #[cfg(target_arch = "x86_64")]
269
                    Backend::AVX2 | Backend::AVX512 => {
270
0
                        Avx2Backend::lerp(self.as_slice(), other.as_slice(), t, &mut result_data)
271
                    }
272
                    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
273
                    Backend::NEON => {
274
                        NeonBackend::lerp(self.as_slice(), other.as_slice(), t, &mut result_data)
275
                    }
276
                    #[cfg(target_arch = "wasm32")]
277
                    Backend::WasmSIMD => {
278
                        WasmBackend::lerp(self.as_slice(), other.as_slice(), t, &mut result_data)
279
                    }
280
0
                    Backend::GPU => return Err(TruenoError::UnsupportedBackend(Backend::GPU)),
281
                    Backend::Auto => {
282
0
                        return Err(TruenoError::UnsupportedBackend(Backend::Auto));
283
                    }
284
                    #[allow(unreachable_patterns)]
285
                    _ => {
286
0
                        ScalarBackend::lerp(self.as_slice(), other.as_slice(), t, &mut result_data)
287
                    }
288
                }
289
            }
290
0
        }
291
292
0
        Ok(Vector::from_slice_with_backend(&result_data, self.backend()))
293
0
    }
294
295
    /// Element-wise square root: result\[i\] = sqrt(self\[i\])
296
    ///
297
    /// Computes the square root of each element. For negative values, returns NaN
298
    /// following IEEE 754 floating-point semantics.
299
    ///
300
    /// # Returns
301
    ///
302
    /// A new vector where each element is the square root of the corresponding input element
303
    ///
304
    /// # Examples
305
    ///
306
    /// ```
307
    /// use trueno::Vector;
308
    ///
309
    /// let a = Vector::from_slice(&[4.0, 9.0, 16.0, 25.0]);
310
    /// let result = a.sqrt().unwrap();
311
    /// assert_eq!(result.as_slice(), &[2.0, 3.0, 4.0, 5.0]);
312
    /// ```
313
    ///
314
    /// Negative values produce NaN:
315
    /// ```
316
    /// use trueno::Vector;
317
    ///
318
    /// let a = Vector::from_slice(&[-1.0, 4.0]);
319
    /// let result = a.sqrt().unwrap();
320
    /// assert!(result.as_slice()[0].is_nan());
321
    /// assert_eq!(result.as_slice()[1], 2.0);
322
    /// ```
323
    ///
324
    /// # Use Cases
325
    ///
326
    /// - Distance calculations: Euclidean distance computation
327
    /// - Statistics: Standard deviation, RMS (root mean square)
328
    /// - Machine learning: Normalization, gradient descent with adaptive learning rates
329
    /// - Signal processing: Amplitude calculations, power spectrum analysis
330
    /// - Physics simulations: Velocity from kinetic energy, wave propagation
331
0
    pub fn sqrt(&self) -> Result<Vector<f32>> {
332
0
        let mut result_data = vec![0.0; self.len()];
333
334
0
        if !self.as_slice().is_empty() {
335
            // Use parallel processing for large arrays
336
            #[cfg(feature = "parallel")]
337
            {
338
                const PARALLEL_THRESHOLD: usize = 100_000;
339
                const CHUNK_SIZE: usize = 65536;
340
341
                if self.len() >= PARALLEL_THRESHOLD {
342
                    use rayon::prelude::*;
343
344
                    self.as_slice()
345
                        .par_chunks(CHUNK_SIZE)
346
                        .zip(result_data.par_chunks_mut(CHUNK_SIZE))
347
                        .for_each(|(chunk_in, chunk_out)| {
348
                            dispatch_unary_op!(self.backend(), sqrt, chunk_in, chunk_out);
349
                        });
350
351
                    return Ok(Vector::from_slice_with_backend(&result_data, self.backend()));
352
                }
353
            }
354
355
0
            dispatch_unary_op!(self.backend(), sqrt, self.as_slice(), &mut result_data);
356
0
        }
357
358
0
        Ok(Vector::from_slice_with_backend(&result_data, self.backend()))
359
0
    }
360
361
    /// Element-wise reciprocal: result\[i\] = 1 / self\[i\]
362
    ///
363
    /// Computes the reciprocal (multiplicative inverse) of each element.
364
    /// For zero values, returns infinity following IEEE 754 floating-point semantics.
365
    ///
366
    /// # Returns
367
    ///
368
    /// A new vector where each element is the reciprocal of the corresponding input element
369
    ///
370
    /// # Examples
371
    ///
372
    /// ```
373
    /// use trueno::Vector;
374
    ///
375
    /// let a = Vector::from_slice(&[2.0, 4.0, 5.0, 10.0]);
376
    /// let result = a.recip().unwrap();
377
    /// assert_eq!(result.as_slice(), &[0.5, 0.25, 0.2, 0.1]);
378
    /// ```
379
    ///
380
    /// Zero values produce infinity:
381
    /// ```
382
    /// use trueno::Vector;
383
    ///
384
    /// let a = Vector::from_slice(&[0.0, 2.0]);
385
    /// let result = a.recip().unwrap();
386
    /// assert!(result.as_slice()[0].is_infinite());
387
    /// assert_eq!(result.as_slice()[1], 0.5);
388
    /// ```
389
    ///
390
    /// # Use Cases
391
    ///
392
    /// - Division optimization: `a / b` → `a * recip(b)` (multiplication is faster)
393
    /// - Neural networks: Learning rate schedules, weight normalization
394
    /// - Statistics: Harmonic mean calculations, inverse transformations
395
    /// - Physics: Resistance (R = 1/G), optical power (P = 1/f)
396
    /// - Signal processing: Frequency to period conversion, filter design
397
0
    pub fn recip(&self) -> Result<Vector<f32>> {
398
0
        let mut result_data = vec![0.0; self.len()];
399
400
0
        if !self.as_slice().is_empty() {
401
0
            dispatch_unary_op!(self.backend(), recip, self.as_slice(), &mut result_data);
402
0
        }
403
404
0
        Ok(Vector::from_slice_with_backend(&result_data, self.backend()))
405
0
    }
406
407
    /// Element-wise power: result\[i\] = base\[i\]^n
408
    ///
409
    /// Raises each element to the given power `n`.
410
    /// Uses Rust's optimized f32::powf() method.
411
    ///
412
    /// # Examples
413
    ///
414
    /// ```
415
    /// use trueno::Vector;
416
    ///
417
    /// let v = Vector::from_slice(&[2.0, 3.0, 4.0]);
418
    /// let squared = v.pow(2.0).unwrap();
419
    /// assert_eq!(squared.as_slice(), &[4.0, 9.0, 16.0]);
420
    ///
421
    /// let sqrt = v.pow(0.5).unwrap();  // Fractional power = root
422
    /// ```
423
    ///
424
    /// # Special Cases
425
    ///
426
    /// - `x.pow(0.0)` returns 1.0 for all x (even x=0)
427
    /// - `x.pow(1.0)` returns x (identity)
428
    /// - `x.pow(-1.0)` returns 1/x (reciprocal)
429
    /// - `x.pow(0.5)` returns sqrt(x) (square root)
430
    ///
431
    /// # Applications
432
    ///
433
    /// - Statistics: Power transformations (Box-Cox, Yeo-Johnson)
434
    /// - Machine learning: Polynomial features, activation functions
435
    /// - Physics: Inverse square law (1/r²), power laws
436
    /// - Signal processing: Power spectral density, root mean square
437
0
    pub fn pow(&self, n: f32) -> Result<Vector<f32>> {
438
0
        let pow_data: Vec<f32> = self.as_slice().iter().map(|x| x.powf(n)).collect();
439
0
        Ok(Vector::from_vec(pow_data))
440
0
    }
441
}
442
443
#[cfg(test)]
444
mod tests {
445
    use super::*;
446
447
    #[test]
448
    fn test_abs_basic() {
449
        let v = Vector::from_slice(&[3.0, -4.0, 5.0, -2.0]);
450
        let result = v.abs().unwrap();
451
        assert_eq!(result.as_slice(), &[3.0, 4.0, 5.0, 2.0]);
452
    }
453
454
    #[test]
455
    fn test_abs_empty() {
456
        let v: Vector<f32> = Vector::from_slice(&[]);
457
        let result = v.abs().unwrap();
458
        assert_eq!(result.len(), 0);
459
    }
460
461
    #[test]
462
    fn test_clip_basic() {
463
        let v = Vector::from_slice(&[-5.0, 0.0, 5.0, 10.0, 15.0]);
464
        let clipped = v.clip(0.0, 10.0).unwrap();
465
        assert_eq!(clipped.as_slice(), &[0.0, 0.0, 5.0, 10.0, 10.0]);
466
    }
467
468
    #[test]
469
    fn test_clip_invalid_range() {
470
        let v = Vector::from_slice(&[1.0, 2.0, 3.0]);
471
        let result = v.clip(10.0, 5.0);
472
        assert!(matches!(result, Err(TruenoError::InvalidInput(_))));
473
    }
474
475
    #[test]
476
    fn test_clamp_basic() {
477
        let v = Vector::from_slice(&[-5.0, 0.0, 5.0, 10.0, 15.0]);
478
        let result = v.clamp(0.0, 10.0).unwrap();
479
        assert_eq!(result.as_slice(), &[0.0, 0.0, 5.0, 10.0, 10.0]);
480
    }
481
482
    #[test]
483
    fn test_clamp_negative_range() {
484
        let v = Vector::from_slice(&[-10.0, -5.0, 0.0, 5.0]);
485
        let result = v.clamp(-8.0, -2.0).unwrap();
486
        assert_eq!(result.as_slice(), &[-8.0, -5.0, -2.0, -2.0]);
487
    }
488
489
    #[test]
490
    fn test_lerp_midpoint() {
491
        let a = Vector::from_slice(&[0.0, 10.0, 20.0]);
492
        let b = Vector::from_slice(&[100.0, 110.0, 120.0]);
493
        let result = a.lerp(&b, 0.5).unwrap();
494
        assert_eq!(result.as_slice(), &[50.0, 60.0, 70.0]);
495
    }
496
497
    #[test]
498
    fn test_lerp_extrapolation() {
499
        let a = Vector::from_slice(&[0.0, 10.0]);
500
        let b = Vector::from_slice(&[10.0, 20.0]);
501
        let result = a.lerp(&b, 2.0).unwrap();
502
        assert_eq!(result.as_slice(), &[20.0, 30.0]);
503
    }
504
505
    #[test]
506
    fn test_lerp_size_mismatch() {
507
        let a = Vector::from_slice(&[0.0, 10.0]);
508
        let b = Vector::from_slice(&[10.0, 20.0, 30.0]);
509
        let result = a.lerp(&b, 0.5);
510
        assert!(matches!(result, Err(TruenoError::SizeMismatch { .. })));
511
    }
512
513
    #[test]
514
    fn test_sqrt_basic() {
515
        let a = Vector::from_slice(&[4.0, 9.0, 16.0, 25.0]);
516
        let result = a.sqrt().unwrap();
517
        assert_eq!(result.as_slice(), &[2.0, 3.0, 4.0, 5.0]);
518
    }
519
520
    #[test]
521
    fn test_sqrt_negative() {
522
        let a = Vector::from_slice(&[-1.0, 4.0]);
523
        let result = a.sqrt().unwrap();
524
        assert!(result.as_slice()[0].is_nan());
525
        assert_eq!(result.as_slice()[1], 2.0);
526
    }
527
528
    #[test]
529
    fn test_recip_basic() {
530
        let a = Vector::from_slice(&[2.0, 4.0, 5.0, 10.0]);
531
        let result = a.recip().unwrap();
532
        assert_eq!(result.as_slice(), &[0.5, 0.25, 0.2, 0.1]);
533
    }
534
535
    #[test]
536
    fn test_recip_zero() {
537
        let a = Vector::from_slice(&[0.0, 2.0]);
538
        let result = a.recip().unwrap();
539
        assert!(result.as_slice()[0].is_infinite());
540
        assert_eq!(result.as_slice()[1], 0.5);
541
    }
542
543
    #[test]
544
    fn test_pow_squared() {
545
        let v = Vector::from_slice(&[2.0, 3.0, 4.0]);
546
        let squared = v.pow(2.0).unwrap();
547
        assert_eq!(squared.as_slice(), &[4.0, 9.0, 16.0]);
548
    }
549
550
    #[test]
551
    fn test_pow_square_root() {
552
        let v = Vector::from_slice(&[4.0, 9.0, 16.0]);
553
        let sqrt = v.pow(0.5).unwrap();
554
        assert!((sqrt.as_slice()[0] - 2.0).abs() < 1e-5);
555
        assert!((sqrt.as_slice()[1] - 3.0).abs() < 1e-5);
556
        assert!((sqrt.as_slice()[2] - 4.0).abs() < 1e-5);
557
    }
558
}