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/arithmetic.rs
Line
Count
Source
1
//! Arithmetic operations for Vector<f32>
2
//!
3
//! This module provides element-wise arithmetic operations:
4
//! - Basic: `add`, `sub`, `mul`, `div`
5
//! - Scalar: `scale`
6
//! - Fused: `fma` (fused multiply-add)
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::vector::Vector;
19
use crate::{dispatch_binary_op, Backend, Result, TruenoError};
20
21
impl Vector<f32> {
22
    /// Element-wise addition
23
    ///
24
    /// # Performance
25
    ///
26
    /// Auto-selects the best available backend:
27
    /// - **AVX2**: ~4x faster than scalar for 1K+ elements
28
    /// - **GPU**: ~50x faster than scalar for 10M+ elements
29
    ///
30
    /// # Examples
31
    ///
32
    /// ```
33
    /// use trueno::Vector;
34
    ///
35
    /// let a = Vector::from_slice(&[1.0, 2.0, 3.0]);
36
    /// let b = Vector::from_slice(&[4.0, 5.0, 6.0]);
37
    /// let result = a.add(&b)?;
38
    ///
39
    /// assert_eq!(result.as_slice(), &[5.0, 7.0, 9.0]);
40
    /// # Ok::<(), trueno::TruenoError>(())
41
    /// ```
42
    ///
43
    /// # Errors
44
    ///
45
    /// Returns [`TruenoError::SizeMismatch`] if vectors have different lengths.
46
15
    pub fn add(&self, other: &Self) -> Result<Self> {
47
15
        if self.len() != other.len() {
48
0
            return Err(TruenoError::SizeMismatch {
49
0
                expected: self.len(),
50
0
                actual: other.len(),
51
0
            });
52
15
        }
53
54
15
        let mut result = vec![0.0; self.len()];
55
56
        // Use parallel processing for large arrays
57
        #[cfg(feature = "parallel")]
58
        {
59
            const PARALLEL_THRESHOLD: usize = 100_000; // Threshold for element-wise ops
60
            const CHUNK_SIZE: usize = 65536; // 64K elements = 256KB, cache-friendly
61
62
            if self.len() >= PARALLEL_THRESHOLD {
63
                use rayon::prelude::*;
64
65
                self.data
66
                    .par_chunks(CHUNK_SIZE)
67
                    .zip(other.data.par_chunks(CHUNK_SIZE))
68
                    .zip(result.par_chunks_mut(CHUNK_SIZE))
69
                    .for_each(|((chunk_a, chunk_b), chunk_out)| {
70
                        dispatch_binary_op!(self.backend, add, chunk_a, chunk_b, chunk_out);
71
                    });
72
73
                return Ok(Self {
74
                    data: result,
75
                    backend: self.backend,
76
                });
77
            }
78
        }
79
80
15
        
dispatch_binary_op!0
(self.backend, add,
&self.data0
,
&other.data0
,
&mut result0
);
81
82
15
        Ok(Self {
83
15
            data: result,
84
15
            backend: self.backend,
85
15
        })
86
15
    }
87
88
    /// Element-wise subtraction
89
    ///
90
    /// # Performance
91
    ///
92
    /// Auto-selects the best available backend:
93
    /// - **AVX2**: ~4x faster than scalar for 1K+ elements
94
    /// - **GPU**: ~50x faster than scalar for 10M+ elements
95
    ///
96
    /// # Examples
97
    ///
98
    /// ```
99
    /// use trueno::Vector;
100
    ///
101
    /// let a = Vector::from_slice(&[5.0, 7.0, 9.0]);
102
    /// let b = Vector::from_slice(&[1.0, 2.0, 3.0]);
103
    /// let result = a.sub(&b)?;
104
    ///
105
    /// assert_eq!(result.as_slice(), &[4.0, 5.0, 6.0]);
106
    /// # Ok::<(), trueno::TruenoError>(())
107
    /// ```
108
    ///
109
    /// # Errors
110
    ///
111
    /// Returns [`TruenoError::SizeMismatch`] if vectors have different lengths.
112
15
    pub fn sub(&self, other: &Self) -> Result<Self> {
113
15
        if self.len() != other.len() {
114
0
            return Err(TruenoError::SizeMismatch {
115
0
                expected: self.len(),
116
0
                actual: other.len(),
117
0
            });
118
15
        }
119
120
15
        let mut result = vec![0.0; self.len()];
121
122
        // Use parallel processing for large arrays
123
        #[cfg(feature = "parallel")]
124
        {
125
            const PARALLEL_THRESHOLD: usize = 100_000;
126
            const CHUNK_SIZE: usize = 65536;
127
128
            if self.len() >= PARALLEL_THRESHOLD {
129
                use rayon::prelude::*;
130
131
                self.data
132
                    .par_chunks(CHUNK_SIZE)
133
                    .zip(other.data.par_chunks(CHUNK_SIZE))
134
                    .zip(result.par_chunks_mut(CHUNK_SIZE))
135
                    .for_each(|((chunk_a, chunk_b), chunk_out)| {
136
                        dispatch_binary_op!(self.backend, sub, chunk_a, chunk_b, chunk_out);
137
                    });
138
139
                return Ok(Self {
140
                    data: result,
141
                    backend: self.backend,
142
                });
143
            }
144
        }
145
146
15
        
dispatch_binary_op!0
(self.backend, sub,
&self.data0
,
&other.data0
,
&mut result0
);
147
148
15
        Ok(Self {
149
15
            data: result,
150
15
            backend: self.backend,
151
15
        })
152
15
    }
153
154
    /// Element-wise multiplication
155
    ///
156
    /// # Examples
157
    ///
158
    /// ```
159
    /// use trueno::Vector;
160
    ///
161
    /// let a = Vector::from_slice(&[2.0, 3.0, 4.0]);
162
    /// let b = Vector::from_slice(&[5.0, 6.0, 7.0]);
163
    /// let result = a.mul(&b)?;
164
    ///
165
    /// assert_eq!(result.as_slice(), &[10.0, 18.0, 28.0]);
166
    /// # Ok::<(), trueno::TruenoError>(())
167
    /// ```
168
61
    pub fn mul(&self, other: &Self) -> Result<Self> {
169
61
        if self.len() != other.len() {
170
0
            return Err(TruenoError::SizeMismatch {
171
0
                expected: self.len(),
172
0
                actual: other.len(),
173
0
            });
174
61
        }
175
176
61
        let mut result = vec![0.0; self.len()];
177
178
        // Use parallel processing for large arrays
179
        #[cfg(feature = "parallel")]
180
        {
181
            const PARALLEL_THRESHOLD: usize = 100_000;
182
            const CHUNK_SIZE: usize = 65536;
183
184
            if self.len() >= PARALLEL_THRESHOLD {
185
                use rayon::prelude::*;
186
187
                self.data
188
                    .par_chunks(CHUNK_SIZE)
189
                    .zip(other.data.par_chunks(CHUNK_SIZE))
190
                    .zip(result.par_chunks_mut(CHUNK_SIZE))
191
                    .for_each(|((chunk_a, chunk_b), chunk_out)| {
192
                        dispatch_binary_op!(self.backend, mul, chunk_a, chunk_b, chunk_out);
193
                    });
194
195
                return Ok(Self {
196
                    data: result,
197
                    backend: self.backend,
198
                });
199
            }
200
        }
201
202
61
        
dispatch_binary_op!0
(self.backend, mul,
&self.data0
,
&other.data0
,
&mut result0
);
203
204
61
        Ok(Self {
205
61
            data: result,
206
61
            backend: self.backend,
207
61
        })
208
61
    }
209
210
    /// Element-wise division
211
    ///
212
    /// # Examples
213
    ///
214
    /// ```
215
    /// use trueno::Vector;
216
    ///
217
    /// let a = Vector::from_slice(&[10.0, 20.0, 30.0]);
218
    /// let b = Vector::from_slice(&[2.0, 4.0, 5.0]);
219
    /// let result = a.div(&b)?;
220
    ///
221
    /// assert_eq!(result.as_slice(), &[5.0, 5.0, 6.0]);
222
    /// # Ok::<(), trueno::TruenoError>(())
223
    /// ```
224
0
    pub fn div(&self, other: &Self) -> Result<Self> {
225
0
        if self.len() != other.len() {
226
0
            return Err(TruenoError::SizeMismatch {
227
0
                expected: self.len(),
228
0
                actual: other.len(),
229
0
            });
230
0
        }
231
232
0
        let mut result = vec![0.0; self.len()];
233
234
        // Use parallel processing for large arrays
235
        #[cfg(feature = "parallel")]
236
        {
237
            const PARALLEL_THRESHOLD: usize = 100_000;
238
            const CHUNK_SIZE: usize = 65536;
239
240
            if self.len() >= PARALLEL_THRESHOLD {
241
                use rayon::prelude::*;
242
243
                self.data
244
                    .par_chunks(CHUNK_SIZE)
245
                    .zip(other.data.par_chunks(CHUNK_SIZE))
246
                    .zip(result.par_chunks_mut(CHUNK_SIZE))
247
                    .for_each(|((chunk_a, chunk_b), chunk_out)| {
248
                        dispatch_binary_op!(self.backend, div, chunk_a, chunk_b, chunk_out);
249
                    });
250
251
                return Ok(Self {
252
                    data: result,
253
                    backend: self.backend,
254
                });
255
            }
256
        }
257
258
0
        dispatch_binary_op!(self.backend, div, &self.data, &other.data, &mut result);
259
260
0
        Ok(Self {
261
0
            data: result,
262
0
            backend: self.backend,
263
0
        })
264
0
    }
265
266
    /// Scalar multiplication (scale all elements by a scalar value)
267
    ///
268
    /// Returns a new vector where each element is multiplied by the scalar.
269
    ///
270
    /// # Examples
271
    ///
272
    /// ```
273
    /// use trueno::Vector;
274
    ///
275
    /// let v = Vector::from_slice(&[1.0, 2.0, 3.0, 4.0]);
276
    /// let result = v.scale(2.0)?;
277
    ///
278
    /// assert_eq!(result.as_slice(), &[2.0, 4.0, 6.0, 8.0]);
279
    /// # Ok::<(), trueno::TruenoError>(())
280
    /// ```
281
    ///
282
    /// # Scaling by Zero
283
    ///
284
    /// ```
285
    /// use trueno::Vector;
286
    ///
287
    /// let v = Vector::from_slice(&[1.0, 2.0, 3.0]);
288
    /// let result = v.scale(0.0)?;
289
    /// assert_eq!(result.as_slice(), &[0.0, 0.0, 0.0]);
290
    /// # Ok::<(), trueno::TruenoError>(())
291
    /// ```
292
    ///
293
    /// # Negative Scaling
294
    ///
295
    /// ```
296
    /// use trueno::Vector;
297
    ///
298
    /// let v = Vector::from_slice(&[1.0, -2.0, 3.0]);
299
    /// let result = v.scale(-2.0)?;
300
    /// assert_eq!(result.as_slice(), &[-2.0, 4.0, -6.0]);
301
    /// # Ok::<(), trueno::TruenoError>(())
302
    /// ```
303
1
    pub fn scale(&self, scalar: f32) -> Result<Vector<f32>> {
304
1
        let mut result_data = vec![0.0; self.len()];
305
306
1
        if !self.data.is_empty() {
307
            // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
308
            unsafe {
309
1
                match self.backend {
310
0
                    Backend::Scalar => ScalarBackend::scale(&self.data, scalar, &mut result_data),
311
                    #[cfg(target_arch = "x86_64")]
312
                    Backend::SSE2 | Backend::AVX => {
313
0
                        Sse2Backend::scale(&self.data, scalar, &mut result_data)
314
                    }
315
                    #[cfg(target_arch = "x86_64")]
316
                    Backend::AVX2 | Backend::AVX512 => {
317
1
                        Avx2Backend::scale(&self.data, scalar, &mut result_data)
318
                    }
319
                    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
320
                    Backend::NEON => NeonBackend::scale(&self.data, scalar, &mut result_data),
321
                    #[cfg(target_arch = "wasm32")]
322
                    Backend::WasmSIMD => WasmBackend::scale(&self.data, scalar, &mut result_data),
323
0
                    Backend::GPU => return Err(TruenoError::UnsupportedBackend(Backend::GPU)),
324
                    Backend::Auto => {
325
                        // Auto should have been resolved at creation time
326
0
                        return Err(TruenoError::UnsupportedBackend(Backend::Auto));
327
                    }
328
                    #[allow(unreachable_patterns)]
329
0
                    _ => ScalarBackend::scale(&self.data, scalar, &mut result_data),
330
                }
331
            }
332
0
        }
333
334
1
        Ok(Vector {
335
1
            data: result_data,
336
1
            backend: self.backend,
337
1
        })
338
1
    }
339
340
    /// Fused multiply-add: result\[i\] = self\[i\] * b\[i\] + c\[i\]
341
    ///
342
    /// Computes element-wise fused multiply-add operation. On hardware with FMA support
343
    /// (AVX2, NEON), this is a single instruction with better performance and numerical
344
    /// accuracy (no intermediate rounding). On platforms without FMA (SSE2, WASM), uses
345
    /// separate multiply and add operations.
346
    ///
347
    /// # Arguments
348
    ///
349
    /// * `b` - The second vector to multiply with
350
    /// * `c` - The vector to add to the product
351
    ///
352
    /// # Returns
353
    ///
354
    /// A new vector where each element is `self\[i\] * b\[i\] + c\[i\]`
355
    ///
356
    /// # Errors
357
    ///
358
    /// Returns `SizeMismatch` if vector lengths don't match
359
    ///
360
    /// # Examples
361
    ///
362
    /// ```
363
    /// use trueno::Vector;
364
    ///
365
    /// let a = Vector::from_slice(&[2.0, 3.0, 4.0]);
366
    /// let b = Vector::from_slice(&[5.0, 6.0, 7.0]);
367
    /// let c = Vector::from_slice(&[1.0, 2.0, 3.0]);
368
    /// let result = a.fma(&b, &c)?;
369
    /// assert_eq!(result.as_slice(), &[11.0, 20.0, 31.0]);  // [2*5+1, 3*6+2, 4*7+3]
370
    /// # Ok::<(), trueno::TruenoError>(())
371
    /// ```
372
    ///
373
    /// # Use Cases
374
    ///
375
    /// - Neural networks: matrix multiplication, backpropagation
376
    /// - Scientific computing: polynomial evaluation, numerical integration
377
    /// - Graphics: transformation matrices, shader computations
378
    /// - Physics simulations: force calculations, particle systems
379
0
    pub fn fma(&self, b: &Vector<f32>, c: &Vector<f32>) -> Result<Vector<f32>> {
380
0
        if self.len() != b.len() {
381
0
            return Err(TruenoError::SizeMismatch {
382
0
                expected: self.len(),
383
0
                actual: b.len(),
384
0
            });
385
0
        }
386
0
        if self.len() != c.len() {
387
0
            return Err(TruenoError::SizeMismatch {
388
0
                expected: self.len(),
389
0
                actual: c.len(),
390
0
            });
391
0
        }
392
393
0
        let mut result_data = vec![0.0; self.len()];
394
395
0
        if !self.data.is_empty() {
396
            // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
397
            unsafe {
398
0
                match self.backend {
399
                    Backend::Scalar => {
400
0
                        ScalarBackend::fma(&self.data, &b.data, &c.data, &mut result_data)
401
                    }
402
                    #[cfg(target_arch = "x86_64")]
403
                    Backend::SSE2 | Backend::AVX => {
404
0
                        Sse2Backend::fma(&self.data, &b.data, &c.data, &mut result_data)
405
                    }
406
                    #[cfg(target_arch = "x86_64")]
407
                    Backend::AVX2 | Backend::AVX512 => {
408
0
                        Avx2Backend::fma(&self.data, &b.data, &c.data, &mut result_data)
409
                    }
410
                    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
411
                    Backend::NEON => {
412
                        NeonBackend::fma(&self.data, &b.data, &c.data, &mut result_data)
413
                    }
414
                    #[cfg(target_arch = "wasm32")]
415
                    Backend::WasmSIMD => {
416
                        WasmBackend::fma(&self.data, &b.data, &c.data, &mut result_data)
417
                    }
418
0
                    Backend::GPU => return Err(TruenoError::UnsupportedBackend(Backend::GPU)),
419
                    Backend::Auto => {
420
0
                        return Err(TruenoError::UnsupportedBackend(Backend::Auto));
421
                    }
422
                    #[allow(unreachable_patterns)]
423
0
                    _ => ScalarBackend::fma(&self.data, &b.data, &c.data, &mut result_data),
424
                }
425
            }
426
0
        }
427
428
0
        Ok(Vector {
429
0
            data: result_data,
430
0
            backend: self.backend,
431
0
        })
432
0
    }
433
}
434
435
#[cfg(test)]
436
mod tests {
437
    use super::*;
438
439
    // ===== Add Tests =====
440
441
    #[test]
442
    fn test_add_basic() {
443
        let a = Vector::from_slice(&[1.0, 2.0, 3.0]);
444
        let b = Vector::from_slice(&[4.0, 5.0, 6.0]);
445
        let result = a.add(&b).unwrap();
446
        assert_eq!(result.as_slice(), &[5.0, 7.0, 9.0]);
447
    }
448
449
    #[test]
450
    fn test_add_size_mismatch() {
451
        let a = Vector::from_slice(&[1.0, 2.0]);
452
        let b = Vector::from_slice(&[1.0, 2.0, 3.0]);
453
        let result = a.add(&b);
454
        assert!(result.is_err());
455
        match result {
456
            Err(TruenoError::SizeMismatch { expected, actual }) => {
457
                assert_eq!(expected, 2);
458
                assert_eq!(actual, 3);
459
            }
460
            _ => panic!("Expected SizeMismatch error"),
461
        }
462
    }
463
464
    #[test]
465
    fn test_add_empty() {
466
        let a = Vector::from_slice(&[]);
467
        let b = Vector::from_slice(&[]);
468
        let result = a.add(&b).unwrap();
469
        assert!(result.as_slice().is_empty());
470
    }
471
472
    #[test]
473
    fn test_add_single_element() {
474
        let a = Vector::from_slice(&[1.5]);
475
        let b = Vector::from_slice(&[2.5]);
476
        let result = a.add(&b).unwrap();
477
        assert!((result.as_slice()[0] - 4.0).abs() < 1e-6);
478
    }
479
480
    #[test]
481
    fn test_add_negatives() {
482
        let a = Vector::from_slice(&[-1.0, -2.0, -3.0]);
483
        let b = Vector::from_slice(&[1.0, 2.0, 3.0]);
484
        let result = a.add(&b).unwrap();
485
        assert_eq!(result.as_slice(), &[0.0, 0.0, 0.0]);
486
    }
487
488
    #[test]
489
    fn test_add_large_array() {
490
        let n = 10000;
491
        let a = Vector::from_slice(&vec![1.0; n]);
492
        let b = Vector::from_slice(&vec![2.0; n]);
493
        let result = a.add(&b).unwrap();
494
        for val in result.as_slice() {
495
            assert!((val - 3.0).abs() < 1e-6);
496
        }
497
    }
498
499
    // ===== Sub Tests =====
500
501
    #[test]
502
    fn test_sub_basic() {
503
        let a = Vector::from_slice(&[5.0, 7.0, 9.0]);
504
        let b = Vector::from_slice(&[1.0, 2.0, 3.0]);
505
        let result = a.sub(&b).unwrap();
506
        assert_eq!(result.as_slice(), &[4.0, 5.0, 6.0]);
507
    }
508
509
    #[test]
510
    fn test_sub_size_mismatch() {
511
        let a = Vector::from_slice(&[1.0, 2.0, 3.0]);
512
        let b = Vector::from_slice(&[1.0]);
513
        let result = a.sub(&b);
514
        assert!(result.is_err());
515
    }
516
517
    #[test]
518
    fn test_sub_empty() {
519
        let a = Vector::from_slice(&[]);
520
        let b = Vector::from_slice(&[]);
521
        let result = a.sub(&b).unwrap();
522
        assert!(result.as_slice().is_empty());
523
    }
524
525
    #[test]
526
    fn test_sub_self() {
527
        let a = Vector::from_slice(&[1.0, 2.0, 3.0, 4.0]);
528
        let result = a.sub(&a).unwrap();
529
        for val in result.as_slice() {
530
            assert!((val - 0.0).abs() < 1e-6);
531
        }
532
    }
533
534
    // ===== Mul Tests =====
535
536
    #[test]
537
    fn test_mul_basic() {
538
        let a = Vector::from_slice(&[1.0, 2.0, 3.0]);
539
        let b = Vector::from_slice(&[2.0, 3.0, 4.0]);
540
        let result = a.mul(&b).unwrap();
541
        assert_eq!(result.as_slice(), &[2.0, 6.0, 12.0]);
542
    }
543
544
    #[test]
545
    fn test_mul_size_mismatch() {
546
        let a = Vector::from_slice(&[1.0]);
547
        let b = Vector::from_slice(&[1.0, 2.0]);
548
        let result = a.mul(&b);
549
        assert!(result.is_err());
550
    }
551
552
    #[test]
553
    fn test_mul_empty() {
554
        let a = Vector::from_slice(&[]);
555
        let b = Vector::from_slice(&[]);
556
        let result = a.mul(&b).unwrap();
557
        assert!(result.as_slice().is_empty());
558
    }
559
560
    #[test]
561
    fn test_mul_by_zero() {
562
        let a = Vector::from_slice(&[1.0, 2.0, 3.0]);
563
        let b = Vector::from_slice(&[0.0, 0.0, 0.0]);
564
        let result = a.mul(&b).unwrap();
565
        assert_eq!(result.as_slice(), &[0.0, 0.0, 0.0]);
566
    }
567
568
    #[test]
569
    fn test_mul_by_one() {
570
        let a = Vector::from_slice(&[5.0, 10.0, 15.0]);
571
        let b = Vector::from_slice(&[1.0, 1.0, 1.0]);
572
        let result = a.mul(&b).unwrap();
573
        assert_eq!(result.as_slice(), &[5.0, 10.0, 15.0]);
574
    }
575
576
    // ===== Div Tests =====
577
578
    #[test]
579
    fn test_div_basic() {
580
        let a = Vector::from_slice(&[4.0, 6.0, 8.0]);
581
        let b = Vector::from_slice(&[2.0, 2.0, 2.0]);
582
        let result = a.div(&b).unwrap();
583
        assert_eq!(result.as_slice(), &[2.0, 3.0, 4.0]);
584
    }
585
586
    #[test]
587
    fn test_div_size_mismatch() {
588
        let a = Vector::from_slice(&[1.0, 2.0]);
589
        let b = Vector::from_slice(&[1.0, 2.0, 3.0, 4.0]);
590
        let result = a.div(&b);
591
        assert!(result.is_err());
592
    }
593
594
    #[test]
595
    fn test_div_empty() {
596
        let a = Vector::from_slice(&[]);
597
        let b = Vector::from_slice(&[]);
598
        let result = a.div(&b).unwrap();
599
        assert!(result.as_slice().is_empty());
600
    }
601
602
    #[test]
603
    fn test_div_by_one() {
604
        let a = Vector::from_slice(&[5.0, 10.0, 15.0]);
605
        let b = Vector::from_slice(&[1.0, 1.0, 1.0]);
606
        let result = a.div(&b).unwrap();
607
        assert_eq!(result.as_slice(), &[5.0, 10.0, 15.0]);
608
    }
609
610
    #[test]
611
    fn test_div_by_zero_produces_inf() {
612
        let a = Vector::from_slice(&[1.0, 2.0]);
613
        let b = Vector::from_slice(&[0.0, 0.0]);
614
        let result = a.div(&b).unwrap();
615
        assert!(result.as_slice()[0].is_infinite());
616
        assert!(result.as_slice()[1].is_infinite());
617
    }
618
619
    // ===== Scale Tests =====
620
621
    #[test]
622
    fn test_scale_basic() {
623
        let a = Vector::from_slice(&[1.0, 2.0, 3.0]);
624
        let result = a.scale(2.0).unwrap();
625
        assert_eq!(result.as_slice(), &[2.0, 4.0, 6.0]);
626
    }
627
628
    #[test]
629
    fn test_scale_empty() {
630
        let a = Vector::from_slice(&[]);
631
        let result = a.scale(5.0).unwrap();
632
        assert!(result.as_slice().is_empty());
633
    }
634
635
    #[test]
636
    fn test_scale_by_zero() {
637
        let a = Vector::from_slice(&[1.0, 2.0, 3.0]);
638
        let result = a.scale(0.0).unwrap();
639
        assert_eq!(result.as_slice(), &[0.0, 0.0, 0.0]);
640
    }
641
642
    #[test]
643
    fn test_scale_by_one() {
644
        let a = Vector::from_slice(&[5.0, 10.0, 15.0]);
645
        let result = a.scale(1.0).unwrap();
646
        assert_eq!(result.as_slice(), &[5.0, 10.0, 15.0]);
647
    }
648
649
    #[test]
650
    fn test_scale_negative() {
651
        let a = Vector::from_slice(&[1.0, -2.0, 3.0]);
652
        let result = a.scale(-1.0).unwrap();
653
        assert_eq!(result.as_slice(), &[-1.0, 2.0, -3.0]);
654
    }
655
656
    // ===== FMA Tests =====
657
658
    #[test]
659
    fn test_fma_basic() {
660
        let a = Vector::from_slice(&[1.0, 2.0, 3.0]);
661
        let b = Vector::from_slice(&[2.0, 2.0, 2.0]);
662
        let c = Vector::from_slice(&[1.0, 1.0, 1.0]);
663
        // a * b + c = [2+1, 4+1, 6+1] = [3, 5, 7]
664
        let result = a.fma(&b, &c).unwrap();
665
        assert_eq!(result.as_slice(), &[3.0, 5.0, 7.0]);
666
    }
667
668
    #[test]
669
    fn test_fma_size_mismatch_b() {
670
        let a = Vector::from_slice(&[1.0, 2.0, 3.0]);
671
        let b = Vector::from_slice(&[2.0]);
672
        let c = Vector::from_slice(&[1.0, 1.0, 1.0]);
673
        let result = a.fma(&b, &c);
674
        assert!(result.is_err());
675
    }
676
677
    #[test]
678
    fn test_fma_size_mismatch_c() {
679
        let a = Vector::from_slice(&[1.0, 2.0, 3.0]);
680
        let b = Vector::from_slice(&[2.0, 2.0, 2.0]);
681
        let c = Vector::from_slice(&[1.0]);
682
        let result = a.fma(&b, &c);
683
        assert!(result.is_err());
684
    }
685
686
    #[test]
687
    fn test_fma_empty() {
688
        let a = Vector::from_slice(&[]);
689
        let b = Vector::from_slice(&[]);
690
        let c = Vector::from_slice(&[]);
691
        let result = a.fma(&b, &c).unwrap();
692
        assert!(result.as_slice().is_empty());
693
    }
694
695
    #[test]
696
    fn test_fma_multiply_by_zero() {
697
        let a = Vector::from_slice(&[5.0, 10.0, 15.0]);
698
        let b = Vector::from_slice(&[0.0, 0.0, 0.0]);
699
        let c = Vector::from_slice(&[1.0, 2.0, 3.0]);
700
        // a * 0 + c = c
701
        let result = a.fma(&b, &c).unwrap();
702
        assert_eq!(result.as_slice(), &[1.0, 2.0, 3.0]);
703
    }
704
705
    #[test]
706
    fn test_fma_add_zero() {
707
        let a = Vector::from_slice(&[2.0, 3.0, 4.0]);
708
        let b = Vector::from_slice(&[3.0, 2.0, 1.0]);
709
        let c = Vector::from_slice(&[0.0, 0.0, 0.0]);
710
        // a * b + 0 = a * b
711
        let result = a.fma(&b, &c).unwrap();
712
        assert_eq!(result.as_slice(), &[6.0, 6.0, 4.0]);
713
    }
714
715
    // ===== Backend Tests =====
716
717
    #[test]
718
    fn test_add_scalar_backend() {
719
        let a = Vector::from_slice_with_backend(&[1.0, 2.0, 3.0], Backend::Scalar);
720
        let b = Vector::from_slice_with_backend(&[4.0, 5.0, 6.0], Backend::Scalar);
721
        let result = a.add(&b).unwrap();
722
        assert_eq!(result.as_slice(), &[5.0, 7.0, 9.0]);
723
    }
724
725
    #[test]
726
    #[cfg(target_arch = "x86_64")]
727
    fn test_add_sse2_backend() {
728
        let a = Vector::from_slice_with_backend(&[1.0, 2.0, 3.0, 4.0], Backend::SSE2);
729
        let b = Vector::from_slice_with_backend(&[4.0, 5.0, 6.0, 7.0], Backend::SSE2);
730
        let result = a.add(&b).unwrap();
731
        assert_eq!(result.as_slice(), &[5.0, 7.0, 9.0, 11.0]);
732
    }
733
734
    #[test]
735
    #[cfg(target_arch = "x86_64")]
736
    fn test_add_avx2_backend() {
737
        if !std::arch::is_x86_feature_detected!("avx2") {
738
            return; // Skip if AVX2 not available
739
        }
740
        let data: Vec<f32> = vec![1.0; 16];
741
        let a = Vector::from_slice_with_backend(&data, Backend::AVX2);
742
        let b_data: Vec<f32> = vec![2.0; 16];
743
        let b = Vector::from_slice_with_backend(&b_data, Backend::AVX2);
744
        let result = a.add(&b).unwrap();
745
        for &val in result.as_slice() {
746
            assert!((val - 3.0).abs() < 1e-6);
747
        }
748
    }
749
750
    // ===== Edge Cases =====
751
752
    #[test]
753
    fn test_add_non_aligned_size() {
754
        // Test with sizes that don't align to SIMD register widths
755
        let a = Vector::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]); // 7 elements
756
        let b = Vector::from_slice(&[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]);
757
        let result = a.add(&b).unwrap();
758
        assert_eq!(result.as_slice(), &[2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
759
    }
760
761
    #[test]
762
    fn test_mul_preserves_sign() {
763
        let a = Vector::from_slice(&[2.0, -2.0, 2.0, -2.0]);
764
        let b = Vector::from_slice(&[3.0, 3.0, -3.0, -3.0]);
765
        let result = a.mul(&b).unwrap();
766
        assert_eq!(result.as_slice(), &[6.0, -6.0, -6.0, 6.0]);
767
    }
768
769
    #[test]
770
    fn test_operations_with_special_floats() {
771
        let a = Vector::from_slice(&[f32::INFINITY, f32::NEG_INFINITY, 0.0]);
772
        let b = Vector::from_slice(&[1.0, 1.0, 1.0]);
773
        let result = a.add(&b).unwrap();
774
        assert!(result.as_slice()[0].is_infinite());
775
        assert!(result.as_slice()[1].is_infinite());
776
        assert!((result.as_slice()[2] - 1.0).abs() < 1e-6);
777
    }
778
}