Skip to main content

trueno/vector/ops/transcendental/
hyperbolic.rs

1//! Hyperbolic and inverse hyperbolic functions: `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`
2
3#[cfg(target_arch = "x86_64")]
4use crate::backends::avx2::Avx2Backend;
5#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
6use crate::backends::neon::NeonBackend;
7use crate::backends::scalar::ScalarBackend;
8#[cfg(target_arch = "x86_64")]
9use crate::backends::sse2::Sse2Backend;
10#[cfg(target_arch = "wasm32")]
11use crate::backends::wasm::WasmBackend;
12use crate::backends::VectorBackend;
13use crate::vector::Vector;
14use crate::{Backend, Result, TruenoError};
15
16impl Vector<f32> {
17    /// Computes the hyperbolic sine (sinh) of each element.
18    ///
19    /// # Mathematical Definition
20    ///
21    /// sinh(x) = (e^x - e^(-x)) / 2
22    ///
23    /// # Properties
24    ///
25    /// - Domain: (-∞, +∞)
26    /// - Range: (-∞, +∞)
27    /// - Odd function: sinh(-x) = -sinh(x)
28    /// - sinh(0) = 0
29    ///
30    /// # Examples
31    ///
32    /// ```
33    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
34    /// use trueno::Vector;
35    ///
36    /// let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
37    /// let result = v.sinh()?;
38    /// assert!((result.as_slice()[0] - 0.0).abs() < 1e-5);
39    /// # Ok(())
40    /// # }
41    /// ```
42    pub fn sinh(&self) -> Result<Vector<f32>> {
43        let sinh_data: Vec<f32> = self.data.iter().map(|x| x.sinh()).collect();
44        Ok(Vector { data: sinh_data, backend: self.backend })
45    }
46
47    /// Computes the hyperbolic cosine (cosh) of each element.
48    ///
49    /// # Mathematical Definition
50    ///
51    /// cosh(x) = (e^x + e^(-x)) / 2
52    ///
53    /// # Properties
54    ///
55    /// - Domain: (-∞, +∞)
56    /// - Range: [1, +∞)
57    /// - Even function: cosh(-x) = cosh(x)
58    /// - cosh(0) = 1
59    /// - Always positive: cosh(x) ≥ 1 for all x
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
65    /// use trueno::Vector;
66    ///
67    /// let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
68    /// let result = v.cosh()?;
69    /// assert!((result.as_slice()[0] - 1.0).abs() < 1e-5);
70    /// # Ok(())
71    /// # }
72    /// ```
73    pub fn cosh(&self) -> Result<Vector<f32>> {
74        let cosh_data: Vec<f32> = self.data.iter().map(|x| x.cosh()).collect();
75        Ok(Vector { data: cosh_data, backend: self.backend })
76    }
77
78    /// Computes the hyperbolic tangent (tanh) of each element.
79    ///
80    /// # Mathematical Definition
81    ///
82    /// tanh(x) = sinh(x) / cosh(x) = (e^x - e^(-x)) / (e^x + e^(-x))
83    ///
84    /// # Properties
85    ///
86    /// - Domain: (-∞, +∞)
87    /// - Range: (-1, 1)
88    /// - Odd function: tanh(-x) = -tanh(x)
89    /// - tanh(0) = 0
90    /// - Bounded: -1 < tanh(x) < 1 for all x
91    /// - Commonly used as activation function in neural networks
92    ///
93    /// # Examples
94    ///
95    /// ```
96    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
97    /// use trueno::Vector;
98    ///
99    /// let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
100    /// let result = v.tanh()?;
101    /// assert!((result.as_slice()[0] - 0.0).abs() < 1e-5);
102    /// // All values are in range (-1, 1)
103    /// assert!(result.as_slice().iter().all(|&x| x > -1.0 && x < 1.0));
104    /// # Ok(())
105    /// # }
106    /// ```
107    pub fn tanh(&self) -> Result<Vector<f32>> {
108        if self.data.is_empty() {
109            return Err(TruenoError::EmptyVector);
110        }
111
112        // OpComplexity::Low - GPU threshold: >100K elements
113        #[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
114        const GPU_THRESHOLD: usize = usize::MAX; // GPU DISABLED - 2-800x slower, see docs/performance-analysis.md
115
116        // Try GPU first for large vectors
117        #[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
118        {
119            if self.data.len() >= GPU_THRESHOLD {
120                use crate::backends::gpu::GpuDevice;
121                if GpuDevice::is_available() {
122                    let gpu = GpuDevice::new().map_err(TruenoError::InvalidInput)?;
123                    let mut result = vec![0.0; self.data.len()];
124                    if gpu.tanh(&self.data, &mut result).is_ok() {
125                        return Ok(Vector::from_vec(result));
126                    }
127                }
128            }
129        }
130
131        // Uninit: backend writes every element before any read.
132        let n = self.len();
133        let mut result: Vec<f32> = Vec::with_capacity(n);
134        // SAFETY: Backend writes all elements before any read.
135        unsafe {
136            result.set_len(n);
137        }
138
139        // Dispatch to appropriate SIMD backend
140        // SAFETY: Unsafe block delegates to backend implementation which maintains safety invariants
141        unsafe {
142            match self.backend {
143                Backend::Scalar => {
144                    ScalarBackend::tanh(&self.data, &mut result);
145                }
146                #[cfg(target_arch = "x86_64")]
147                Backend::SSE2 | Backend::AVX => {
148                    Sse2Backend::tanh(&self.data, &mut result);
149                }
150                #[cfg(target_arch = "x86_64")]
151                Backend::AVX2 | Backend::AVX512 => {
152                    Avx2Backend::tanh(&self.data, &mut result);
153                }
154                #[cfg(not(target_arch = "x86_64"))]
155                Backend::SSE2 | Backend::AVX | Backend::AVX2 | Backend::AVX512 => {
156                    ScalarBackend::tanh(&self.data, &mut result);
157                }
158                #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
159                Backend::NEON => {
160                    NeonBackend::tanh(&self.data, &mut result);
161                }
162                #[cfg(not(any(target_arch = "aarch64", target_arch = "arm")))]
163                Backend::NEON => {
164                    ScalarBackend::tanh(&self.data, &mut result);
165                }
166                #[cfg(target_arch = "wasm32")]
167                Backend::WasmSIMD => {
168                    WasmBackend::tanh(&self.data, &mut result);
169                }
170                #[cfg(not(target_arch = "wasm32"))]
171                Backend::WasmSIMD => {
172                    ScalarBackend::tanh(&self.data, &mut result);
173                }
174                Backend::GPU | Backend::Auto => {
175                    // Auto should have been resolved at Vector creation
176                    // GPU falls back to best available SIMD
177                    #[cfg(target_arch = "x86_64")]
178                    {
179                        if is_x86_feature_detected!("avx2") {
180                            Avx2Backend::tanh(&self.data, &mut result);
181                        } else {
182                            Sse2Backend::tanh(&self.data, &mut result);
183                        }
184                    }
185                    #[cfg(not(target_arch = "x86_64"))]
186                    {
187                        ScalarBackend::tanh(&self.data, &mut result);
188                    }
189                }
190            }
191        }
192
193        Ok(Vector { data: result, backend: self.backend })
194    }
195
196    /// Computes the inverse hyperbolic sine (asinh) of each element.
197    ///
198    /// # Mathematical Definition
199    ///
200    /// asinh(x) = ln(x + sqrt(x² + 1))
201    ///
202    /// # Properties
203    ///
204    /// - Domain: (-∞, +∞)
205    /// - Range: (-∞, +∞)
206    /// - Odd function: asinh(-x) = -asinh(x)
207    /// - asinh(0) = 0
208    /// - Inverse of sinh: asinh(sinh(x)) = x
209    ///
210    /// # Examples
211    ///
212    /// ```
213    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
214    /// use trueno::Vector;
215    ///
216    /// let v = Vector::from_slice(&[0.0, 1.0, -1.0]);
217    /// let result = v.asinh()?;
218    /// assert!((result.as_slice()[0] - 0.0).abs() < 1e-5);
219    /// # Ok(())
220    /// # }
221    /// ```
222    pub fn asinh(&self) -> Result<Vector<f32>> {
223        let asinh_data: Vec<f32> = self.data.iter().map(|x| x.asinh()).collect();
224        Ok(Vector { data: asinh_data, backend: self.backend })
225    }
226
227    /// Computes the inverse hyperbolic cosine (acosh) of each element.
228    ///
229    /// # Mathematical Definition
230    ///
231    /// acosh(x) = ln(x + sqrt(x² - 1))
232    ///
233    /// # Properties
234    ///
235    /// - Domain: [1, +∞)
236    /// - Range: [0, +∞)
237    /// - acosh(1) = 0
238    /// - Inverse of cosh: acosh(cosh(x)) = x for x >= 0
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
244    /// use trueno::Vector;
245    ///
246    /// let v = Vector::from_slice(&[1.0, 2.0, 3.0]);
247    /// let result = v.acosh()?;
248    /// assert!((result.as_slice()[0] - 0.0).abs() < 1e-5);
249    /// # Ok(())
250    /// # }
251    /// ```
252    pub fn acosh(&self) -> Result<Vector<f32>> {
253        let acosh_data: Vec<f32> = self.data.iter().map(|x| x.acosh()).collect();
254        Ok(Vector { data: acosh_data, backend: self.backend })
255    }
256
257    /// Computes the inverse hyperbolic tangent (atanh) of each element.
258    ///
259    /// Domain: (-1, 1)
260    /// Range: (-∞, +∞)
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
266    /// use trueno::Vector;
267    ///
268    /// let v = Vector::from_slice(&[0.0, 0.5, -0.5]);
269    /// let result = v.atanh()?;
270    /// // atanh(0) = 0, atanh(0.5) ≈ 0.549, atanh(-0.5) ≈ -0.549
271    /// # Ok(())
272    /// # }
273    /// ```
274    pub fn atanh(&self) -> Result<Vector<f32>> {
275        let atanh_data: Vec<f32> = self.data.iter().map(|x| x.atanh()).collect();
276        Ok(Vector { data: atanh_data, backend: self.backend })
277    }
278}