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