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/transcendental.rs
Line
Count
Source
1
//! Transcendental mathematical functions for Vector<f32>
2
//!
3
//! This module provides element-wise transcendental functions including:
4
//! - Exponentials: `exp`, `ln`, `log2`, `log10`
5
//! - Trigonometric: `sin`, `cos`, `tan`, `asin`, `acos`, `atan`
6
//! - Hyperbolic: `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`
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_unary_op, Backend, Result, TruenoError};
20
21
impl Vector<f32> {
22
    /// Element-wise exponential: result\[i\] = e^x\[i\]
23
    ///
24
    /// Computes the natural exponential (e^x) for each element.
25
    /// Uses Rust's optimized f32::exp() method.
26
    ///
27
    /// # Examples
28
    ///
29
    /// ```
30
    /// use trueno::Vector;
31
    ///
32
    /// let v = Vector::from_slice(&[0.0, 1.0, 2.0]);
33
    /// let result = v.exp().unwrap();
34
    /// // result ≈ [1.0, 2.718, 7.389]
35
    /// ```
36
    ///
37
    /// # Special Cases
38
    ///
39
    /// - `exp(0.0)` returns 1.0
40
    /// - `exp(1.0)` returns e ≈ 2.71828
41
    /// - `exp(-∞)` returns 0.0
42
    /// - `exp(+∞)` returns +∞
43
    ///
44
    /// # Applications
45
    ///
46
    /// - Machine learning: Softmax activation, sigmoid, exponential loss
47
    /// - Statistics: Exponential distribution, log-normal distribution
48
    /// - Physics: Radioactive decay, population growth models
49
    /// - Signal processing: Exponential smoothing, envelope detection
50
    /// - Numerical methods: Solving differential equations
51
0
    pub fn exp(&self) -> Result<Vector<f32>> {
52
0
        let mut result_data = vec![0.0; self.len()];
53
54
0
        if !self.data.is_empty() {
55
            // Use parallel processing for large arrays
56
            #[cfg(feature = "parallel")]
57
            {
58
                const PARALLEL_THRESHOLD: usize = 100_000;
59
                const CHUNK_SIZE: usize = 65536;
60
61
                if self.len() >= PARALLEL_THRESHOLD {
62
                    use rayon::prelude::*;
63
64
                    self.data
65
                        .par_chunks(CHUNK_SIZE)
66
                        .zip(result_data.par_chunks_mut(CHUNK_SIZE))
67
                        .for_each(|(chunk_in, chunk_out)| {
68
                            // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
69
                            unsafe {
70
                                match self.backend {
71
                                    Backend::Scalar => ScalarBackend::exp(chunk_in, chunk_out),
72
                                    #[cfg(target_arch = "x86_64")]
73
                                    Backend::SSE2 | Backend::AVX => {
74
                                        Sse2Backend::exp(chunk_in, chunk_out)
75
                                    }
76
                                    #[cfg(target_arch = "x86_64")]
77
                                    Backend::AVX2 | Backend::AVX512 => {
78
                                        Avx2Backend::exp(chunk_in, chunk_out)
79
                                    }
80
                                    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
81
                                    Backend::NEON => NeonBackend::exp(chunk_in, chunk_out),
82
                                    #[cfg(target_arch = "wasm32")]
83
                                    Backend::WasmSIMD => WasmBackend::exp(chunk_in, chunk_out),
84
                                    Backend::GPU => ScalarBackend::exp(chunk_in, chunk_out),
85
                                    Backend::Auto => ScalarBackend::exp(chunk_in, chunk_out),
86
                                    #[allow(unreachable_patterns)]
87
                                    _ => ScalarBackend::exp(chunk_in, chunk_out),
88
                                }
89
                            }
90
                        });
91
92
                    return Ok(Vector {
93
                        data: result_data,
94
                        backend: self.backend,
95
                    });
96
                }
97
            }
98
99
            // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
100
            unsafe {
101
0
                match self.backend {
102
0
                    Backend::Scalar => ScalarBackend::exp(&self.data, &mut result_data),
103
                    #[cfg(target_arch = "x86_64")]
104
0
                    Backend::SSE2 | Backend::AVX => Sse2Backend::exp(&self.data, &mut result_data),
105
                    #[cfg(target_arch = "x86_64")]
106
                    Backend::AVX2 | Backend::AVX512 => {
107
0
                        Avx2Backend::exp(&self.data, &mut result_data)
108
                    }
109
                    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
110
                    Backend::NEON => NeonBackend::exp(&self.data, &mut result_data),
111
                    #[cfg(target_arch = "wasm32")]
112
                    Backend::WasmSIMD => WasmBackend::exp(&self.data, &mut result_data),
113
0
                    Backend::GPU => return Err(TruenoError::UnsupportedBackend(Backend::GPU)),
114
                    Backend::Auto => {
115
                        // Auto should have been resolved at creation time
116
0
                        return Err(TruenoError::UnsupportedBackend(Backend::Auto));
117
                    }
118
                    #[allow(unreachable_patterns)]
119
0
                    _ => ScalarBackend::exp(&self.data, &mut result_data),
120
                }
121
            }
122
0
        }
123
124
0
        Ok(Vector {
125
0
            data: result_data,
126
0
            backend: self.backend,
127
0
        })
128
0
    }
129
130
    /// Element-wise natural logarithm: result\[i\] = ln(x\[i\])
131
    ///
132
    /// Computes the natural logarithm (base e) for each element.
133
    /// Uses Rust's optimized f32::ln() method.
134
    ///
135
    /// # Examples
136
    ///
137
    /// ```
138
    /// use trueno::Vector;
139
    ///
140
    /// let v = Vector::from_slice(&[1.0, std::f32::consts::E, std::f32::consts::E.powi(2)]);
141
    /// let result = v.ln().unwrap();
142
    /// // result ≈ [0.0, 1.0, 2.0]
143
    /// ```
144
    ///
145
    /// # Special Cases
146
    ///
147
    /// - `ln(1.0)` returns 0.0
148
    /// - `ln(e)` returns 1.0
149
    /// - `ln(x)` for x ≤ 0 returns NaN
150
    /// - `ln(0.0)` returns -∞
151
    /// - `ln(+∞)` returns +∞
152
    ///
153
    /// # Applications
154
    ///
155
    /// - Machine learning: Log loss, log-likelihood, softmax normalization
156
    /// - Statistics: Log-normal distribution, log transformation for skewed data
157
    /// - Information theory: Entropy calculation, mutual information
158
    /// - Economics: Log returns, elasticity calculations
159
    /// - Signal processing: Decibel conversion, log-frequency analysis
160
0
    pub fn ln(&self) -> Result<Vector<f32>> {
161
0
        let mut result_data = vec![0.0; self.len()];
162
163
0
        if !self.data.is_empty() {
164
0
            dispatch_unary_op!(self.backend, ln, &self.data, &mut result_data);
165
0
        }
166
167
0
        Ok(Vector {
168
0
            data: result_data,
169
0
            backend: self.backend,
170
0
        })
171
0
    }
172
173
    /// Element-wise base-2 logarithm: result\[i\] = log₂(x\[i\])
174
    ///
175
    /// Computes the base-2 logarithm for each element.
176
    /// Uses Rust's optimized f32::log2() method.
177
    ///
178
    /// # Examples
179
    ///
180
    /// ```
181
    /// use trueno::Vector;
182
    ///
183
    /// let v = Vector::from_slice(&[1.0, 2.0, 4.0, 8.0]);
184
    /// let result = v.log2().unwrap();
185
    /// // result ≈ [0.0, 1.0, 2.0, 3.0]
186
    /// ```
187
    ///
188
    /// # Special Cases
189
    ///
190
    /// - `log2(1.0)` returns 0.0
191
    /// - `log2(2.0)` returns 1.0
192
    /// - `log2(x)` for x ≤ 0 returns NaN
193
    /// - `log2(0.0)` returns -∞
194
    /// - `log2(+∞)` returns +∞
195
    ///
196
    /// # Applications
197
    ///
198
    /// - Information theory: Entropy in bits, mutual information
199
    /// - Computer science: Bit manipulation, binary search complexity
200
    /// - Audio: Octave calculations, pitch detection
201
    /// - Data compression: Huffman coding, arithmetic coding
202
0
    pub fn log2(&self) -> Result<Vector<f32>> {
203
0
        let mut result_data = vec![0.0; self.len()];
204
205
0
        if !self.data.is_empty() {
206
0
            dispatch_unary_op!(self.backend, log2, &self.data, &mut result_data);
207
0
        }
208
209
0
        Ok(Vector {
210
0
            data: result_data,
211
0
            backend: self.backend,
212
0
        })
213
0
    }
214
215
    /// Element-wise base-10 logarithm: result\[i\] = log₁₀(x\[i\])
216
    ///
217
    /// Computes the base-10 (common) logarithm for each element.
218
    /// Uses Rust's optimized f32::log10() method.
219
    ///
220
    /// # Examples
221
    ///
222
    /// ```
223
    /// use trueno::Vector;
224
    ///
225
    /// let v = Vector::from_slice(&[1.0, 10.0, 100.0, 1000.0]);
226
    /// let result = v.log10().unwrap();
227
    /// // result ≈ [0.0, 1.0, 2.0, 3.0]
228
    /// ```
229
    ///
230
    /// # Special Cases
231
    ///
232
    /// - `log10(1.0)` returns 0.0
233
    /// - `log10(10.0)` returns 1.0
234
    /// - `log10(x)` for x ≤ 0 returns NaN
235
    /// - `log10(0.0)` returns -∞
236
    /// - `log10(+∞)` returns +∞
237
    ///
238
    /// # Applications
239
    ///
240
    /// - Audio: Decibel calculations (dB = 20 * log10(amplitude))
241
    /// - Chemistry: pH calculations (-log10(H+ concentration))
242
    /// - Seismology: Richter scale
243
    /// - Scientific notation: Order of magnitude calculations
244
0
    pub fn log10(&self) -> Result<Vector<f32>> {
245
0
        let mut result_data = vec![0.0; self.len()];
246
247
0
        if !self.data.is_empty() {
248
0
            dispatch_unary_op!(self.backend, log10, &self.data, &mut result_data);
249
0
        }
250
251
0
        Ok(Vector {
252
0
            data: result_data,
253
0
            backend: self.backend,
254
0
        })
255
0
    }
256
257
    /// Element-wise sine: result\[i\] = sin(x\[i\])
258
    ///
259
    /// Computes the sine for each element (input in radians).
260
    /// Uses Rust's optimized f32::sin() method.
261
    ///
262
    /// # Examples
263
    ///
264
    /// ```
265
    /// use trueno::Vector;
266
    /// use std::f32::consts::PI;
267
    ///
268
    /// let v = Vector::from_slice(&[0.0, PI / 2.0, PI]);
269
    /// let result = v.sin().unwrap();
270
    /// // result ≈ [0.0, 1.0, 0.0]
271
    /// ```
272
    ///
273
    /// # Special Cases
274
    ///
275
    /// - `sin(0)` returns 0.0
276
    /// - `sin(π/2)` returns 1.0
277
    /// - `sin(π)` returns 0.0 (approximately)
278
    /// - `sin(-x)` returns -sin(x) (odd function)
279
    /// - Periodic with period 2π: sin(x + 2π) = sin(x)
280
    ///
281
    /// # Applications
282
    ///
283
    /// - Signal processing: Waveform generation, oscillators, modulation
284
    /// - Physics: Harmonic motion, wave propagation, pendulums
285
    /// - Audio: Synthesizers, tone generation, effects processing
286
    /// - Graphics: Animation, rotation transformations, procedural generation
287
    /// - Fourier analysis: Frequency decomposition, spectral analysis
288
0
    pub fn sin(&self) -> Result<Vector<f32>> {
289
0
        let mut result_data = vec![0.0; self.len()];
290
291
0
        if !self.data.is_empty() {
292
0
            dispatch_unary_op!(self.backend, sin, &self.data, &mut result_data);
293
0
        }
294
295
0
        Ok(Vector {
296
0
            data: result_data,
297
0
            backend: self.backend,
298
0
        })
299
0
    }
300
301
    /// Element-wise cosine: result\[i\] = cos(x\[i\])
302
    ///
303
    /// Computes the cosine for each element (input in radians).
304
    /// Uses Rust's optimized f32::cos() method.
305
    ///
306
    /// # Examples
307
    ///
308
    /// ```
309
    /// use trueno::Vector;
310
    /// use std::f32::consts::PI;
311
    ///
312
    /// let v = Vector::from_slice(&[0.0, PI / 2.0, PI]);
313
    /// let result = v.cos().unwrap();
314
    /// // result ≈ [1.0, 0.0, -1.0]
315
    /// ```
316
    ///
317
    /// # Special Cases
318
    ///
319
    /// - `cos(0)` returns 1.0
320
    /// - `cos(π/2)` returns 0.0 (approximately)
321
    /// - `cos(π)` returns -1.0
322
    /// - `cos(-x)` returns cos(x) (even function)
323
    /// - Periodic with period 2π: cos(x + 2π) = cos(x)
324
    /// - Relation to sine: cos(x) = sin(x + π/2)
325
    ///
326
    /// # Applications
327
    ///
328
    /// - Signal processing: Phase-shifted waveforms, I/Q modulation, quadrature signals
329
    /// - Physics: Projectile motion, wave interference, damped oscillations
330
    /// - Graphics: Rotation matrices, camera transforms, circular motion
331
    /// - Audio: Stereo panning, spatial audio, frequency synthesis
332
    /// - Engineering: Control systems, frequency response, AC circuits
333
0
    pub fn cos(&self) -> Result<Vector<f32>> {
334
0
        let mut result_data = vec![0.0; self.len()];
335
336
0
        if !self.data.is_empty() {
337
0
            dispatch_unary_op!(self.backend, cos, &self.data, &mut result_data);
338
0
        }
339
340
0
        Ok(Vector {
341
0
            data: result_data,
342
0
            backend: self.backend,
343
0
        })
344
0
    }
345
346
    /// Computes element-wise tangent (tan) of the vector.
347
    ///
348
    /// Returns a new vector where each element is the tangent of the corresponding input element.
349
    /// tan(x) = sin(x) / cos(x)
350
    ///
351
    /// # Returns
352
    /// - `Ok(Vector<f32>)`: New vector with tan(x) for each element
353
    ///
354
    /// # Properties
355
    /// - Odd function: tan(-x) = -tan(x)
356
    /// - Period: 2π (not π, despite common misconception)
357
    /// - Undefined at x = π/2 + nπ (where n is any integer)
358
    /// - tan(x) = sin(x) / cos(x)
359
    /// - Range: (-∞, +∞)
360
    ///
361
    /// # Performance
362
    /// - Iterator map pattern for cache efficiency
363
    /// - Leverages Rust's optimized f32::tan()
364
    /// - Auto-vectorized by LLVM on supporting platforms
365
    ///
366
    /// # Examples
367
    /// ```
368
    /// use trueno::Vector;
369
    /// use std::f32::consts::PI;
370
    ///
371
    /// let angles = Vector::from_slice(&[0.0, PI / 4.0, -PI / 4.0]);
372
    /// let result = angles.tan().unwrap();
373
    /// // Result: [0.0, 1.0, -1.0] (approximately)
374
    /// ```
375
    ///
376
    /// # Use Cases
377
    /// - Trigonometry: Slope calculations, angle relationships
378
    /// - Signal processing: Phase analysis, modulation
379
    /// - Physics: Projectile trajectories, optics (Snell's law angles)
380
    /// - Graphics: Perspective projection, field of view calculations
381
    /// - Engineering: Slope gradients, tangent lines to curves
382
0
    pub fn tan(&self) -> Result<Vector<f32>> {
383
0
        let mut result_data = vec![0.0; self.len()];
384
385
0
        if !self.data.is_empty() {
386
0
            dispatch_unary_op!(self.backend, tan, &self.data, &mut result_data);
387
0
        }
388
389
0
        Ok(Vector {
390
0
            data: result_data,
391
0
            backend: self.backend,
392
0
        })
393
0
    }
394
395
    /// Computes element-wise arcsine (asin/sin⁻¹) of the vector.
396
    ///
397
    /// Returns a new vector where each element is the inverse sine of the corresponding input element.
398
    /// This is the inverse function of sin: if y = sin(x), then x = asin(y).
399
    ///
400
    /// # Returns
401
    /// - `Ok(Vector<f32>)`: New vector with asin(x) for each element
402
    ///
403
    /// # Properties
404
    /// - Domain: [-1, 1] (inputs outside this range produce NaN)
405
    /// - Range: [-π/2, π/2]
406
    /// - Odd function: asin(-x) = -asin(x)
407
    /// - Inverse relation: asin(sin(x)) = x for x ∈ [-π/2, π/2]
408
    /// - asin(0) = 0
409
    /// - asin(1) = π/2
410
    /// - asin(-1) = -π/2
411
    ///
412
    /// # Performance
413
    /// - Iterator map pattern for cache efficiency
414
    /// - Leverages Rust's optimized f32::asin()
415
    /// - Auto-vectorized by LLVM on supporting platforms
416
    ///
417
    /// # Examples
418
    /// ```
419
    /// use trueno::Vector;
420
    /// use std::f32::consts::PI;
421
    ///
422
    /// let values = Vector::from_slice(&[0.0, 0.5, 1.0]);
423
    /// let result = values.asin().unwrap();
424
    /// // Result: [0.0, π/6, π/2] (approximately)
425
    /// ```
426
    ///
427
    /// # Use Cases
428
    /// - Physics: Calculating angles from sine values in mechanics, optics
429
    /// - Signal processing: Phase recovery, demodulation
430
    /// - Graphics: Inverse transformations, angle calculations
431
    /// - Navigation: GPS calculations, spherical trigonometry
432
    /// - Control systems: Inverse kinematics, servo positioning
433
0
    pub fn asin(&self) -> Result<Vector<f32>> {
434
0
        let asin_data: Vec<f32> = self.data.iter().map(|x| x.asin()).collect();
435
0
        Ok(Vector {
436
0
            data: asin_data,
437
0
            backend: self.backend,
438
0
        })
439
0
    }
440
441
    /// Computes element-wise arccosine (acos/cos⁻¹) of the vector.
442
    ///
443
    /// Returns a new vector where each element is the inverse cosine of the corresponding input element.
444
    /// This is the inverse function of cos: if y = cos(x), then x = acos(y).
445
    ///
446
    /// # Returns
447
    /// - `Ok(Vector<f32>)`: New vector with acos(x) for each element
448
    ///
449
    /// # Properties
450
    /// - Domain: [-1, 1] (inputs outside this range produce NaN)
451
    /// - Range: [0, π]
452
    /// - Symmetry: acos(-x) = π - acos(x)
453
    /// - Inverse relation: acos(cos(x)) = x for x ∈ [0, π]
454
    /// - acos(0) = π/2
455
    /// - acos(1) = 0
456
    /// - acos(-1) = π
457
    ///
458
    /// # Performance
459
    /// - Iterator map pattern for cache efficiency
460
    /// - Leverages Rust's optimized f32::acos()
461
    /// - Auto-vectorized by LLVM on supporting platforms
462
    ///
463
    /// # Examples
464
    /// ```
465
    /// use trueno::Vector;
466
    /// use std::f32::consts::PI;
467
    ///
468
    /// let values = Vector::from_slice(&[0.0, 0.5, 1.0]);
469
    /// let result = values.acos().unwrap();
470
    /// // Result: [π/2, π/3, 0.0] (approximately)
471
    /// ```
472
    ///
473
    /// # Use Cases
474
    /// - Physics: Angle calculations in mechanics, optics, reflections
475
    /// - Signal processing: Phase analysis, correlation functions
476
    /// - Graphics: View angle calculations, lighting models
477
    /// - Navigation: Bearing calculations, great circle distances
478
    /// - Robotics: Joint angle solving, orientation calculations
479
0
    pub fn acos(&self) -> Result<Vector<f32>> {
480
0
        let acos_data: Vec<f32> = self.data.iter().map(|x| x.acos()).collect();
481
0
        Ok(Vector {
482
0
            data: acos_data,
483
0
            backend: self.backend,
484
0
        })
485
0
    }
486
487
    /// Computes element-wise arctangent (atan/tan⁻¹) of the vector.
488
    ///
489
    /// Returns a new vector where each element is the inverse tangent of the corresponding input element.
490
    /// This is the inverse function of tan: if y = tan(x), then x = atan(y).
491
    ///
492
    /// # Returns
493
    /// - `Ok(Vector<f32>)`: New vector with atan(x) for each element
494
    ///
495
    /// # Properties
496
    /// - Domain: All real numbers (-∞, +∞)
497
    /// - Range: (-π/2, π/2)
498
    /// - Odd function: atan(-x) = -atan(x)
499
    /// - Inverse relation: atan(tan(x)) = x for x ∈ (-π/2, π/2)
500
    /// - atan(0) = 0
501
    /// - atan(1) = π/4
502
    /// - atan(-1) = -π/4
503
    /// - lim(x→∞) atan(x) = π/2
504
    /// - lim(x→-∞) atan(x) = -π/2
505
    ///
506
    /// # Performance
507
    /// - Iterator map pattern for cache efficiency
508
    /// - Leverages Rust's optimized f32::atan()
509
    /// - Auto-vectorized by LLVM on supporting platforms
510
    ///
511
    /// # Examples
512
    /// ```
513
    /// use trueno::Vector;
514
    /// use std::f32::consts::PI;
515
    ///
516
    /// let values = Vector::from_slice(&[0.0, 1.0, -1.0]);
517
    /// let result = values.atan().unwrap();
518
    /// // Result: [0.0, π/4, -π/4] (approximately)
519
    /// ```
520
    ///
521
    /// # Use Cases
522
    /// - Physics: Angle calculations from slopes, velocity components
523
    /// - Signal processing: Phase unwrapping, FM demodulation
524
    /// - Graphics: Rotation calculations, camera orientation
525
    /// - Robotics: Inverse kinematics, steering angles
526
    /// - Navigation: Heading calculations from coordinates
527
0
    pub fn atan(&self) -> Result<Vector<f32>> {
528
0
        let atan_data: Vec<f32> = self.data.iter().map(|x| x.atan()).collect();
529
0
        Ok(Vector {
530
0
            data: atan_data,
531
0
            backend: self.backend,
532
0
        })
533
0
    }
534
535
    /// Computes the hyperbolic sine (sinh) of each element.
536
    ///
537
    /// # Mathematical Definition
538
    ///
539
    /// sinh(x) = (e^x - e^(-x)) / 2
540
    ///
541
    /// # Properties
542
    ///
543
    /// - Domain: (-∞, +∞)
544
    /// - Range: (-∞, +∞)
545
    /// - Odd function: sinh(-x) = -sinh(x)
546
    /// - sinh(0) = 0
547
    ///
548
    /// # Examples
549
    ///
550
    /// ```
551
    /// use trueno::Vector;
552
    ///
553
    /// let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
554
    /// let result = v.sinh().unwrap();
555
    /// assert!((result.as_slice()[0] - 0.0).abs() < 1e-5);
556
    /// ```
557
0
    pub fn sinh(&self) -> Result<Vector<f32>> {
558
0
        let sinh_data: Vec<f32> = self.data.iter().map(|x| x.sinh()).collect();
559
0
        Ok(Vector {
560
0
            data: sinh_data,
561
0
            backend: self.backend,
562
0
        })
563
0
    }
564
565
    /// Computes the hyperbolic cosine (cosh) of each element.
566
    ///
567
    /// # Mathematical Definition
568
    ///
569
    /// cosh(x) = (e^x + e^(-x)) / 2
570
    ///
571
    /// # Properties
572
    ///
573
    /// - Domain: (-∞, +∞)
574
    /// - Range: [1, +∞)
575
    /// - Even function: cosh(-x) = cosh(x)
576
    /// - cosh(0) = 1
577
    /// - Always positive: cosh(x) ≥ 1 for all x
578
    ///
579
    /// # Examples
580
    ///
581
    /// ```
582
    /// use trueno::Vector;
583
    ///
584
    /// let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
585
    /// let result = v.cosh().unwrap();
586
    /// assert!((result.as_slice()[0] - 1.0).abs() < 1e-5);
587
    /// ```
588
0
    pub fn cosh(&self) -> Result<Vector<f32>> {
589
0
        let cosh_data: Vec<f32> = self.data.iter().map(|x| x.cosh()).collect();
590
0
        Ok(Vector {
591
0
            data: cosh_data,
592
0
            backend: self.backend,
593
0
        })
594
0
    }
595
596
    /// Computes the hyperbolic tangent (tanh) of each element.
597
    ///
598
    /// # Mathematical Definition
599
    ///
600
    /// tanh(x) = sinh(x) / cosh(x) = (e^x - e^(-x)) / (e^x + e^(-x))
601
    ///
602
    /// # Properties
603
    ///
604
    /// - Domain: (-∞, +∞)
605
    /// - Range: (-1, 1)
606
    /// - Odd function: tanh(-x) = -tanh(x)
607
    /// - tanh(0) = 0
608
    /// - Bounded: -1 < tanh(x) < 1 for all x
609
    /// - Commonly used as activation function in neural networks
610
    ///
611
    /// # Examples
612
    ///
613
    /// ```
614
    /// use trueno::Vector;
615
    ///
616
    /// let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
617
    /// let result = v.tanh().unwrap();
618
    /// assert!((result.as_slice()[0] - 0.0).abs() < 1e-5);
619
    /// // All values are in range (-1, 1)
620
    /// assert!(result.as_slice().iter().all(|&x| x > -1.0 && x < 1.0));
621
    /// ```
622
0
    pub fn tanh(&self) -> Result<Vector<f32>> {
623
0
        if self.data.is_empty() {
624
0
            return Err(TruenoError::EmptyVector);
625
0
        }
626
627
        // OpComplexity::Low - GPU threshold: >100K elements
628
        #[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
629
        const GPU_THRESHOLD: usize = usize::MAX; // GPU DISABLED - 2-800x slower, see docs/performance-analysis.md
630
631
        // Try GPU first for large vectors
632
        #[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
633
        {
634
0
            if self.data.len() >= GPU_THRESHOLD {
635
                use crate::backends::gpu::GpuDevice;
636
0
                if GpuDevice::is_available() {
637
0
                    let gpu = GpuDevice::new().map_err(TruenoError::InvalidInput)?;
638
0
                    let mut result = vec![0.0; self.data.len()];
639
0
                    if gpu.tanh(&self.data, &mut result).is_ok() {
640
0
                        return Ok(Vector::from_vec(result));
641
0
                    }
642
0
                }
643
0
            }
644
        }
645
646
0
        let mut result = vec![0.0; self.len()];
647
648
        // Dispatch to appropriate SIMD backend
649
        // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
650
        unsafe {
651
0
            match self.backend {
652
0
                Backend::Scalar => {
653
0
                    ScalarBackend::tanh(&self.data, &mut result);
654
0
                }
655
                #[cfg(target_arch = "x86_64")]
656
0
                Backend::SSE2 | Backend::AVX => {
657
0
                    Sse2Backend::tanh(&self.data, &mut result);
658
0
                }
659
                #[cfg(target_arch = "x86_64")]
660
0
                Backend::AVX2 | Backend::AVX512 => {
661
0
                    Avx2Backend::tanh(&self.data, &mut result);
662
0
                }
663
                #[cfg(not(target_arch = "x86_64"))]
664
                Backend::SSE2 | Backend::AVX | Backend::AVX2 | Backend::AVX512 => {
665
                    ScalarBackend::tanh(&self.data, &mut result);
666
                }
667
                #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
668
                Backend::NEON => {
669
                    NeonBackend::tanh(&self.data, &mut result);
670
                }
671
                #[cfg(not(any(target_arch = "aarch64", target_arch = "arm")))]
672
0
                Backend::NEON => {
673
0
                    ScalarBackend::tanh(&self.data, &mut result);
674
0
                }
675
                #[cfg(target_arch = "wasm32")]
676
                Backend::WasmSIMD => {
677
                    WasmBackend::tanh(&self.data, &mut result);
678
                }
679
                #[cfg(not(target_arch = "wasm32"))]
680
0
                Backend::WasmSIMD => {
681
0
                    ScalarBackend::tanh(&self.data, &mut result);
682
0
                }
683
                Backend::GPU | Backend::Auto => {
684
                    // Auto should have been resolved at Vector creation
685
                    // GPU falls back to best available SIMD
686
                    #[cfg(target_arch = "x86_64")]
687
                    {
688
0
                        if is_x86_feature_detected!("avx2") {
689
0
                            Avx2Backend::tanh(&self.data, &mut result);
690
0
                        } else {
691
0
                            Sse2Backend::tanh(&self.data, &mut result);
692
0
                        }
693
                    }
694
                    #[cfg(not(target_arch = "x86_64"))]
695
                    {
696
                        ScalarBackend::tanh(&self.data, &mut result);
697
                    }
698
                }
699
            }
700
        }
701
702
0
        Ok(Vector {
703
0
            data: result,
704
0
            backend: self.backend,
705
0
        })
706
0
    }
707
708
    /// Computes the inverse hyperbolic sine (asinh) of each element.
709
    ///
710
    /// # Mathematical Definition
711
    ///
712
    /// asinh(x) = ln(x + sqrt(x² + 1))
713
    ///
714
    /// # Properties
715
    ///
716
    /// - Domain: (-∞, +∞)
717
    /// - Range: (-∞, +∞)
718
    /// - Odd function: asinh(-x) = -asinh(x)
719
    /// - asinh(0) = 0
720
    /// - Inverse of sinh: asinh(sinh(x)) = x
721
    ///
722
    /// # Examples
723
    ///
724
    /// ```
725
    /// use trueno::Vector;
726
    ///
727
    /// let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
728
    /// let result = v.asinh().unwrap();
729
    /// assert!((result.as_slice()[0] - 0.0).abs() < 1e-5);
730
    /// ```
731
0
    pub fn asinh(&self) -> Result<Vector<f32>> {
732
0
        let asinh_data: Vec<f32> = self.data.iter().map(|x| x.asinh()).collect();
733
0
        Ok(Vector {
734
0
            data: asinh_data,
735
0
            backend: self.backend,
736
0
        })
737
0
    }
738
739
    /// Computes the inverse hyperbolic cosine (acosh) of each element.
740
    ///
741
    /// # Mathematical Definition
742
    ///
743
    /// acosh(x) = ln(x + sqrt(x² - 1))
744
    ///
745
    /// # Properties
746
    ///
747
    /// - Domain: [1, +∞)
748
    /// - Range: [0, +∞)
749
    /// - acosh(1) = 0
750
    /// - Inverse of cosh: acosh(cosh(x)) = x for x >= 0
751
    ///
752
    /// # Examples
753
    ///
754
    /// ```
755
    /// use trueno::Vector;
756
    ///
757
    /// let v = Vector::from_slice(&[1.0, 2.0, 3.0]);
758
    /// let result = v.acosh().unwrap();
759
    /// assert!((result.as_slice()[0] - 0.0).abs() < 1e-5);
760
    /// ```
761
0
    pub fn acosh(&self) -> Result<Vector<f32>> {
762
0
        let acosh_data: Vec<f32> = self.data.iter().map(|x| x.acosh()).collect();
763
0
        Ok(Vector {
764
0
            data: acosh_data,
765
0
            backend: self.backend,
766
0
        })
767
0
    }
768
769
    /// Computes the inverse hyperbolic tangent (atanh) of each element.
770
    ///
771
    /// Domain: (-1, 1)
772
    /// Range: (-∞, +∞)
773
    ///
774
    /// # Examples
775
    ///
776
    /// ```
777
    /// use trueno::Vector;
778
    ///
779
    /// let v = Vector::from_slice(&[0.0, 0.5, -0.5]);
780
    /// let result = v.atanh().unwrap();
781
    /// // atanh(0) = 0, atanh(0.5) ≈ 0.549, atanh(-0.5) ≈ -0.549
782
    /// ```
783
0
    pub fn atanh(&self) -> Result<Vector<f32>> {
784
0
        let atanh_data: Vec<f32> = self.data.iter().map(|x| x.atanh()).collect();
785
0
        Ok(Vector {
786
0
            data: atanh_data,
787
0
            backend: self.backend,
788
0
        })
789
0
    }
790
}
791
792
#[cfg(test)]
793
mod tests {
794
    use super::*;
795
796
    // ========== Exponential Functions ==========
797
798
    #[test]
799
    fn test_exp_basic() {
800
        let v = Vector::from_slice(&[0.0, 1.0, 2.0]);
801
        let result = v.exp().unwrap();
802
        assert!((result.as_slice()[0] - 1.0).abs() < 1e-6); // e^0 = 1
803
        assert!((result.as_slice()[1] - std::f32::consts::E).abs() < 1e-5); // e^1 = e
804
        assert!((result.as_slice()[2] - std::f32::consts::E.powi(2)).abs() < 1e-4);
805
    }
806
807
    #[test]
808
    fn test_exp_empty() {
809
        let v = Vector::<f32>::from_slice(&[]);
810
        let result = v.exp().unwrap();
811
        assert!(result.is_empty());
812
    }
813
814
    #[test]
815
    fn test_exp_negative() {
816
        let v = Vector::from_slice(&[-1.0, -2.0]);
817
        let result = v.exp().unwrap();
818
        assert!((result.as_slice()[0] - 1.0 / std::f32::consts::E).abs() < 1e-5);
819
    }
820
821
    #[test]
822
    fn test_ln_basic() {
823
        let v = Vector::from_slice(&[1.0, std::f32::consts::E, std::f32::consts::E.powi(2)]);
824
        let result = v.ln().unwrap();
825
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
826
        assert!((result.as_slice()[1] - 1.0).abs() < 1e-5);
827
        assert!((result.as_slice()[2] - 2.0).abs() < 1e-5);
828
    }
829
830
    #[test]
831
    fn test_ln_empty() {
832
        let v = Vector::<f32>::from_slice(&[]);
833
        let result = v.ln().unwrap();
834
        assert!(result.is_empty());
835
    }
836
837
    #[test]
838
    fn test_log2_basic() {
839
        let v = Vector::from_slice(&[1.0, 2.0, 4.0, 8.0]);
840
        let result = v.log2().unwrap();
841
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
842
        assert!((result.as_slice()[1] - 1.0).abs() < 1e-6);
843
        assert!((result.as_slice()[2] - 2.0).abs() < 1e-6);
844
        assert!((result.as_slice()[3] - 3.0).abs() < 1e-6);
845
    }
846
847
    #[test]
848
    fn test_log2_empty() {
849
        let v = Vector::<f32>::from_slice(&[]);
850
        let result = v.log2().unwrap();
851
        assert!(result.is_empty());
852
    }
853
854
    #[test]
855
    fn test_log10_basic() {
856
        let v = Vector::from_slice(&[1.0, 10.0, 100.0, 1000.0]);
857
        let result = v.log10().unwrap();
858
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
859
        assert!((result.as_slice()[1] - 1.0).abs() < 1e-5);
860
        assert!((result.as_slice()[2] - 2.0).abs() < 1e-5);
861
        assert!((result.as_slice()[3] - 3.0).abs() < 1e-4);
862
    }
863
864
    #[test]
865
    fn test_log10_empty() {
866
        let v = Vector::<f32>::from_slice(&[]);
867
        let result = v.log10().unwrap();
868
        assert!(result.is_empty());
869
    }
870
871
    // ========== Trigonometric Functions ==========
872
873
    #[test]
874
    fn test_sin_basic() {
875
        let v = Vector::from_slice(&[0.0, std::f32::consts::PI / 2.0, std::f32::consts::PI]);
876
        let result = v.sin().unwrap();
877
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
878
        assert!((result.as_slice()[1] - 1.0).abs() < 1e-6);
879
        assert!((result.as_slice()[2] - 0.0).abs() < 1e-5);
880
    }
881
882
    #[test]
883
    fn test_sin_empty() {
884
        let v = Vector::<f32>::from_slice(&[]);
885
        let result = v.sin().unwrap();
886
        assert!(result.is_empty());
887
    }
888
889
    #[test]
890
    fn test_cos_basic() {
891
        let v = Vector::from_slice(&[0.0, std::f32::consts::PI / 2.0, std::f32::consts::PI]);
892
        let result = v.cos().unwrap();
893
        assert!((result.as_slice()[0] - 1.0).abs() < 1e-6);
894
        assert!((result.as_slice()[1] - 0.0).abs() < 1e-6);
895
        assert!((result.as_slice()[2] - (-1.0)).abs() < 1e-5);
896
    }
897
898
    #[test]
899
    fn test_cos_empty() {
900
        let v = Vector::<f32>::from_slice(&[]);
901
        let result = v.cos().unwrap();
902
        assert!(result.is_empty());
903
    }
904
905
    #[test]
906
    fn test_tan_basic() {
907
        let v = Vector::from_slice(&[0.0, std::f32::consts::PI / 4.0]);
908
        let result = v.tan().unwrap();
909
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
910
        assert!((result.as_slice()[1] - 1.0).abs() < 1e-5);
911
    }
912
913
    #[test]
914
    fn test_tan_empty() {
915
        let v = Vector::<f32>::from_slice(&[]);
916
        let result = v.tan().unwrap();
917
        assert!(result.is_empty());
918
    }
919
920
    #[test]
921
    fn test_asin_basic() {
922
        let v = Vector::from_slice(&[0.0, 0.5, 1.0]);
923
        let result = v.asin().unwrap();
924
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
925
        assert!((result.as_slice()[1] - 0.5236).abs() < 1e-3); // ~π/6
926
        assert!((result.as_slice()[2] - std::f32::consts::FRAC_PI_2).abs() < 1e-5);
927
    }
928
929
    #[test]
930
    fn test_asin_empty() {
931
        let v = Vector::<f32>::from_slice(&[]);
932
        let result = v.asin().unwrap();
933
        assert!(result.is_empty());
934
    }
935
936
    #[test]
937
    fn test_acos_basic() {
938
        let v = Vector::from_slice(&[1.0, 0.5, 0.0]);
939
        let result = v.acos().unwrap();
940
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
941
        assert!((result.as_slice()[1] - 1.0472).abs() < 1e-3); // ~π/3
942
        assert!((result.as_slice()[2] - std::f32::consts::FRAC_PI_2).abs() < 1e-5);
943
    }
944
945
    #[test]
946
    fn test_acos_empty() {
947
        let v = Vector::<f32>::from_slice(&[]);
948
        let result = v.acos().unwrap();
949
        assert!(result.is_empty());
950
    }
951
952
    #[test]
953
    fn test_atan_basic() {
954
        let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
955
        let result = v.atan().unwrap();
956
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
957
        assert!((result.as_slice()[1] - std::f32::consts::FRAC_PI_4).abs() < 1e-5);
958
        assert!((result.as_slice()[2] - (-std::f32::consts::FRAC_PI_4)).abs() < 1e-5);
959
    }
960
961
    #[test]
962
    fn test_atan_empty() {
963
        let v = Vector::<f32>::from_slice(&[]);
964
        let result = v.atan().unwrap();
965
        assert!(result.is_empty());
966
    }
967
968
    // ========== Hyperbolic Functions ==========
969
970
    #[test]
971
    fn test_sinh_basic() {
972
        let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
973
        let result = v.sinh().unwrap();
974
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
975
        assert!((result.as_slice()[1] - 1.1752).abs() < 1e-3);
976
        assert!((result.as_slice()[2] - (-1.1752)).abs() < 1e-3);
977
    }
978
979
    #[test]
980
    fn test_sinh_empty() {
981
        let v = Vector::<f32>::from_slice(&[]);
982
        let result = v.sinh().unwrap();
983
        assert!(result.is_empty());
984
    }
985
986
    #[test]
987
    fn test_cosh_basic() {
988
        let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
989
        let result = v.cosh().unwrap();
990
        assert!((result.as_slice()[0] - 1.0).abs() < 1e-6);
991
        assert!((result.as_slice()[1] - 1.5431).abs() < 1e-3);
992
        assert!((result.as_slice()[2] - 1.5431).abs() < 1e-3); // cosh is even
993
    }
994
995
    #[test]
996
    fn test_cosh_empty() {
997
        let v = Vector::<f32>::from_slice(&[]);
998
        let result = v.cosh().unwrap();
999
        assert!(result.is_empty());
1000
    }
1001
1002
    #[test]
1003
    fn test_tanh_basic() {
1004
        let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
1005
        let result = v.tanh().unwrap();
1006
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
1007
        assert!((result.as_slice()[1] - 0.7616).abs() < 1e-3);
1008
        assert!((result.as_slice()[2] - (-0.7616)).abs() < 1e-3);
1009
    }
1010
1011
    #[test]
1012
    fn test_tanh_empty() {
1013
        let v = Vector::<f32>::from_slice(&[]);
1014
        // tanh returns EmptyVector error for empty input
1015
        assert!(matches!(v.tanh(), Err(TruenoError::EmptyVector)));
1016
    }
1017
1018
    #[test]
1019
    fn test_asinh_basic() {
1020
        let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
1021
        let result = v.asinh().unwrap();
1022
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
1023
        assert!((result.as_slice()[1] - 0.8814).abs() < 1e-3);
1024
        assert!((result.as_slice()[2] - (-0.8814)).abs() < 1e-3);
1025
    }
1026
1027
    #[test]
1028
    fn test_asinh_empty() {
1029
        let v = Vector::<f32>::from_slice(&[]);
1030
        let result = v.asinh().unwrap();
1031
        assert!(result.is_empty());
1032
    }
1033
1034
    #[test]
1035
    fn test_acosh_basic() {
1036
        let v = Vector::from_slice(&[1.0, 2.0, 3.0]);
1037
        let result = v.acosh().unwrap();
1038
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
1039
        assert!((result.as_slice()[1] - 1.3170).abs() < 1e-3);
1040
        assert!((result.as_slice()[2] - 1.7627).abs() < 1e-3);
1041
    }
1042
1043
    #[test]
1044
    fn test_acosh_empty() {
1045
        let v = Vector::<f32>::from_slice(&[]);
1046
        let result = v.acosh().unwrap();
1047
        assert!(result.is_empty());
1048
    }
1049
1050
    #[test]
1051
    fn test_atanh_basic() {
1052
        let v = Vector::from_slice(&[0.0, 0.5, -0.5]);
1053
        let result = v.atanh().unwrap();
1054
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
1055
        assert!((result.as_slice()[1] - 0.5493).abs() < 1e-3);
1056
        assert!((result.as_slice()[2] - (-0.5493)).abs() < 1e-3);
1057
    }
1058
1059
    #[test]
1060
    fn test_atanh_empty() {
1061
        let v = Vector::<f32>::from_slice(&[]);
1062
        let result = v.atanh().unwrap();
1063
        assert!(result.is_empty());
1064
    }
1065
1066
    // ========== Backend-specific Tests ==========
1067
1068
    #[test]
1069
    fn test_exp_scalar_backend() {
1070
        let v = Vector::from_slice_with_backend(&[0.0, 1.0, 2.0], Backend::Scalar);
1071
        let result = v.exp().unwrap();
1072
        assert!((result.as_slice()[0] - 1.0).abs() < 1e-6);
1073
    }
1074
1075
    #[test]
1076
    #[cfg(target_arch = "x86_64")]
1077
    fn test_exp_sse2_backend() {
1078
        let v = Vector::from_slice_with_backend(&[0.0, 1.0, 2.0, 3.0], Backend::SSE2);
1079
        let result = v.exp().unwrap();
1080
        assert!((result.as_slice()[0] - 1.0).abs() < 1e-6);
1081
    }
1082
1083
    #[test]
1084
    #[cfg(target_arch = "x86_64")]
1085
    fn test_exp_avx2_backend() {
1086
        if !is_x86_feature_detected!("avx2") {
1087
            return;
1088
        }
1089
        let v = Vector::from_slice_with_backend(&[0.0; 16], Backend::AVX2);
1090
        let result = v.exp().unwrap();
1091
        for val in result.as_slice() {
1092
            assert!((val - 1.0).abs() < 1e-6);
1093
        }
1094
    }
1095
1096
    #[test]
1097
    fn test_sin_scalar_backend() {
1098
        let v = Vector::from_slice_with_backend(&[0.0, std::f32::consts::FRAC_PI_2], Backend::Scalar);
1099
        let result = v.sin().unwrap();
1100
        assert!((result.as_slice()[0] - 0.0).abs() < 1e-6);
1101
        assert!((result.as_slice()[1] - 1.0).abs() < 1e-6);
1102
    }
1103
1104
    #[test]
1105
    fn test_cos_scalar_backend() {
1106
        let v = Vector::from_slice_with_backend(&[0.0, std::f32::consts::PI], Backend::Scalar);
1107
        let result = v.cos().unwrap();
1108
        assert!((result.as_slice()[0] - 1.0).abs() < 1e-6);
1109
        assert!((result.as_slice()[1] - (-1.0)).abs() < 1e-5);
1110
    }
1111
1112
    // ========== Large Array Tests ==========
1113
1114
    #[test]
1115
    fn test_exp_large() {
1116
        let v = Vector::from_slice(&[1.0; 1000]);
1117
        let result = v.exp().unwrap();
1118
        assert_eq!(result.len(), 1000);
1119
        for val in result.as_slice() {
1120
            assert!((val - std::f32::consts::E).abs() < 1e-5);
1121
        }
1122
    }
1123
1124
    #[test]
1125
    fn test_sin_large() {
1126
        let v = Vector::from_slice(&[0.0; 1000]);
1127
        let result = v.sin().unwrap();
1128
        assert_eq!(result.len(), 1000);
1129
        for val in result.as_slice() {
1130
            assert!((val - 0.0).abs() < 1e-6);
1131
        }
1132
    }
1133
1134
    // ========== Inverse Relationship Tests ==========
1135
1136
    #[test]
1137
    fn test_exp_ln_inverse() {
1138
        let v = Vector::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0]);
1139
        let exp_result = v.exp().unwrap();
1140
        let roundtrip = exp_result.ln().unwrap();
1141
        for (orig, rt) in v.as_slice().iter().zip(roundtrip.as_slice()) {
1142
            assert!((orig - rt).abs() < 1e-5);
1143
        }
1144
    }
1145
1146
    #[test]
1147
    fn test_sin_asin_inverse() {
1148
        let v = Vector::from_slice(&[0.0, 0.3, 0.5, 0.7]);
1149
        let sin_result = v.sin().unwrap();
1150
        let roundtrip = sin_result.asin().unwrap();
1151
        for (orig, rt) in v.as_slice().iter().zip(roundtrip.as_slice()) {
1152
            assert!((orig - rt).abs() < 1e-5);
1153
        }
1154
    }
1155
1156
    #[test]
1157
    fn test_cos_acos_inverse() {
1158
        let v = Vector::from_slice(&[0.0, 0.3, 0.5, 0.7]);
1159
        let cos_result = v.cos().unwrap();
1160
        let roundtrip = cos_result.acos().unwrap();
1161
        for (orig, rt) in v.as_slice().iter().zip(roundtrip.as_slice()) {
1162
            assert!((orig - rt).abs() < 1e-5);
1163
        }
1164
    }
1165
1166
    #[test]
1167
    fn test_sinh_asinh_inverse() {
1168
        let v = Vector::from_slice(&[0.0, 1.0, 2.0, -1.0, -2.0]);
1169
        let sinh_result = v.sinh().unwrap();
1170
        let roundtrip = sinh_result.asinh().unwrap();
1171
        for (orig, rt) in v.as_slice().iter().zip(roundtrip.as_slice()) {
1172
            assert!((orig - rt).abs() < 1e-4);
1173
        }
1174
    }
1175
1176
    #[test]
1177
    fn test_tanh_atanh_inverse() {
1178
        let v = Vector::from_slice(&[0.0, 0.3, 0.5, -0.3, -0.5]);
1179
        let tanh_result = v.tanh().unwrap();
1180
        let roundtrip = tanh_result.atanh().unwrap();
1181
        for (orig, rt) in v.as_slice().iter().zip(roundtrip.as_slice()) {
1182
            assert!((orig - rt).abs() < 1e-4);
1183
        }
1184
    }
1185
}