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}