/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 | | } |