Skip to main content

torsh_tensor/
math_ops_trig.rs

1//! Transcendental and trigonometric math operations for tensors.
2//!
3//! This module is included by `math_ops.rs` via `#[path]` and re-exported with `pub use`.
4//! It covers all floating-point transcendental functions:
5//! - Square root, exponential, logarithms
6//! - Trigonometric: sin, cos, tan, asin, acos, atan
7//! - Hyperbolic: sinh, cosh, tanh
8//! - Activation: GELU, leaky ReLU
9//! - Rounding: floor, ceil, round, trunc, fract
10//! - Power, negation, sign
11
12use super::*;
13
14// Mathematical functions for floating-point tensors
15impl<T: TensorElement + Copy> Tensor<T>
16where
17    T: scirs2_core::numeric::Float + torsh_core::dtype::FloatElement,
18{
19    /// Square root of all elements
20    pub fn sqrt(&self) -> Result<Self> {
21        self.map(|x| x.sqrt())
22    }
23
24    /// Square of all elements
25    pub fn square(&self) -> Result<Self> {
26        self.map(|x| x * x)
27    }
28
29    /// Reciprocal square root of all elements (1/sqrt(x))
30    pub fn rsqrt(&self) -> Result<Self> {
31        self.map(|x| T::from(1.0).expect("numeric conversion should succeed") / x.sqrt())
32    }
33
34    /// Reciprocal of all elements (1/x)
35    pub fn reciprocal(&self) -> Result<Self> {
36        self.map(|x| T::from(1.0).expect("numeric conversion should succeed") / x)
37    }
38
39    /// Exponential of all elements
40    pub fn exp(&self) -> Result<Self> {
41        self.map(|x| x.exp())
42    }
43
44    /// Natural logarithm of all elements
45    pub fn ln(&self) -> Result<Self> {
46        self.map(|x| x.ln())
47    }
48
49    /// Logarithm base 10 of all elements
50    pub fn log10(&self) -> Result<Self> {
51        self.map(|x| x.log10())
52    }
53
54    /// Logarithm base 2 of all elements
55    pub fn log2(&self) -> Result<Self> {
56        self.map(|x| x.log2())
57    }
58
59    /// Natural logarithm of all elements
60    pub fn log(&self) -> Result<Self> {
61        self.map(|x| x.ln())
62    }
63
64    /// Sine of all elements
65    pub fn sin(&self) -> Result<Self> {
66        self.map(|x| x.sin())
67    }
68
69    /// Cosine of all elements
70    pub fn cos(&self) -> Result<Self> {
71        self.map(|x| x.cos())
72    }
73
74    /// Tangent of all elements
75    pub fn tan(&self) -> Result<Self> {
76        self.map(|x| x.tan())
77    }
78
79    /// GELU (Gaussian Error Linear Unit) activation function with GPU and SIMD optimization
80    pub fn gelu(&self) -> Result<Self> {
81        // GPU fast path (deferred): GELU is not yet a variant of oxicuda's
82        // `ComputeBackend` `UnaryOp`.  oxicuda-blas already ships
83        // `elementwise::unary::gelu`, so once `UnaryOp::Gelu` is added upstream
84        // (TODO(oxicuda-unaryop-gelu)) dispatch here via
85        // `crate::gpu_dispatch::try_unary_f32(self, UnaryOp::Gelu)`.
86
87        // ✅ SciRS2 SIMD Optimization - Vectorized GELU for f32 tensors
88        #[cfg(feature = "simd")]
89        {
90            if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() && self.numel() > 1000 {
91                return self.simd_gelu_f32();
92            }
93        }
94
95        // ✅ SciRS2 Parallel Processing - Use parallel computation for medium tensors
96        #[cfg(feature = "parallel")]
97        {
98            if self.numel() > 100 {
99                return self.parallel_map(|x| self.compute_gelu_scalar(x));
100            }
101        }
102
103        // Fallback to sequential processing
104        // GELU(x) = 0.5 * x * (1 + tanh(sqrt(2/π) * (x + 0.044715 * x^3)))
105        self.map(|x| self.compute_gelu_scalar(x))
106    }
107
108    /// Compute GELU for a single scalar value
109    fn compute_gelu_scalar(&self, x: T) -> T {
110        let pi = T::from(std::f64::consts::PI).expect("numeric conversion should succeed");
111        let two = T::from(2.0).expect("numeric conversion should succeed");
112        let sqrt_2_over_pi = (two / pi).sqrt();
113        let point_044715 = T::from(0.044715).expect("numeric conversion should succeed");
114        let one = <T as scirs2_core::numeric::One>::one();
115        let half = T::from(0.5).expect("numeric conversion should succeed");
116
117        let x_cubed = x * x * x;
118        let tanh_input = sqrt_2_over_pi * (x + point_044715 * x_cubed);
119        half * x * (one + tanh_input.tanh())
120    }
121
122    /// SIMD-optimized GELU activation function for f32 tensors
123    #[cfg(feature = "simd")]
124    fn simd_gelu_f32(&self) -> Result<Self> {
125        use scirs2_core::ndarray::ArrayView1;
126
127        let data = self.data()?;
128
129        // Cast to f32 for SIMD operations
130        let data_f32: &[f32] =
131            unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, data.len()) };
132
133        // Create ArrayView1 for SIMD function
134        let data_view = ArrayView1::from(data_f32);
135
136        // Use scirs2_core SIMD-accelerated GELU
137        let result_array = adaptive_simd::adaptive_simd_gelu_f32(&data_view);
138
139        // Convert result back to T type
140        let result_vec: Vec<T> = result_array
141            .to_vec()
142            .into_iter()
143            .map(|f| unsafe { std::mem::transmute_copy::<f32, T>(&f) })
144            .collect();
145
146        Self::from_data(
147            result_vec,
148            self.shape().dims().to_vec(),
149            self.device.clone(),
150        )
151    }
152
153    /// Leaky ReLU activation function with negative slope
154    pub fn leaky_relu(&self, negative_slope: T) -> Result<Self> {
155        // GPU fast path (deferred): parameterized LeakyReLU has no oxicuda
156        // `ComputeBackend` `UnaryOp` variant (the flat `unary` op takes no
157        // scalar parameter).  TODO(oxicuda-unaryop-leakyrelu): add a
158        // parameterized variant upstream, then dispatch via gpu_dispatch.
159        self.map(|x| {
160            if x > scirs2_core::numeric::Zero::zero() {
161                x
162            } else {
163                negative_slope * x
164            }
165        })
166    }
167
168    /// Arcsine of all elements
169    pub fn asin(&self) -> Result<Self> {
170        self.map(|x| x.asin())
171    }
172
173    /// Arccosine of all elements
174    pub fn acos(&self) -> Result<Self> {
175        self.map(|x| x.acos())
176    }
177
178    /// Arctangent of all elements
179    pub fn atan(&self) -> Result<Self> {
180        self.map(|x| x.atan())
181    }
182
183    /// Hyperbolic sine of all elements
184    pub fn sinh(&self) -> Result<Self> {
185        self.map(|x| x.sinh())
186    }
187
188    /// Hyperbolic cosine of all elements
189    pub fn cosh(&self) -> Result<Self> {
190        self.map(|x| x.cosh())
191    }
192
193    /// Hyperbolic tangent of all elements
194    pub fn tanh(&self) -> Result<Self> {
195        // GPU fast path: f32 CUDA tensors dispatch to oxicuda's ComputeBackend.
196        // Declines to None (CPU fallback) unless a GPU backend is active.
197        #[cfg(feature = "gpu")]
198        if let Some(result) =
199            crate::gpu_dispatch::try_unary_f32(self, crate::gpu_dispatch::UnaryOp::Tanh)
200        {
201            return Ok(result);
202        }
203
204        self.map(|x| x.tanh())
205    }
206
207    /// Power function (element-wise)
208    pub fn pow(&self, exponent: T) -> Result<Self>
209    where
210        T: TensorElement + Into<f32>,
211    {
212        // Convert T to f32 for the Operation::Power storage
213        let exponent_f32: f32 = exponent.into();
214
215        let mut result = self.map(|x| x.powf(exponent))?;
216
217        // Set up gradient computation if needed
218        if self.requires_grad {
219            result.requires_grad = true;
220            result.operation = Operation::Power {
221                input: Arc::new(self.clone()),
222                exponent: exponent_f32,
223            };
224        }
225
226        Ok(result)
227    }
228
229    /// Power function with scalar exponent (alias for pow)
230    pub fn pow_scalar(&self, exponent: T) -> Result<Self>
231    where
232        T: TensorElement + Into<f32>,
233    {
234        self.pow(exponent)
235    }
236
237    /// Power function with tensor exponents
238    pub fn pow_tensor(&self, exponent: &Self) -> Result<Self> {
239        self.elementwise_operation(exponent, |base, exp| base.powf(exp))
240    }
241
242    /// Floor of all elements
243    pub fn floor(&self) -> Result<Self> {
244        self.map(|x| x.floor())
245    }
246
247    /// Ceiling of all elements
248    pub fn ceil(&self) -> Result<Self> {
249        self.map(|x| x.ceil())
250    }
251
252    /// Round to nearest integer
253    pub fn round(&self) -> Result<Self> {
254        self.map(|x| x.round())
255    }
256
257    /// Truncate to integer part
258    pub fn trunc(&self) -> Result<Self> {
259        self.map(|x| x.trunc())
260    }
261
262    /// Fractional part
263    pub fn fract(&self) -> Result<Self> {
264        self.map(|x| x.fract())
265    }
266
267    /// Negation of all elements
268    pub fn neg(&self) -> Result<Self>
269    where
270        T: std::ops::Neg<Output = T>,
271    {
272        self.map(|x| -x)
273    }
274
275    /// Sign of all elements (-1, 0, or 1)
276    pub fn sign(&self) -> Result<Self> {
277        self.map(|x| {
278            if x > <T as scirs2_core::numeric::Zero>::zero() {
279                <T as scirs2_core::numeric::One>::one()
280            } else if x < <T as scirs2_core::numeric::Zero>::zero() {
281                -<T as scirs2_core::numeric::One>::one()
282            } else {
283                <T as scirs2_core::numeric::Zero>::zero()
284            }
285        })
286    }
287}