1use num_complex::{Complex32, Complex64};
2use num_traits::{One, Zero};
3use strided_kernel::{map_into, reduce, zip_map2_into, StridedView};
4use tenferro_core_ops::PrimitiveOpKind;
5
6use super::{tensor_from_array, typed_array_uninit_from_pool, typed_view, typed_view_from_view};
7use crate::buffer_pool::{BufferPool, PoolScalar};
8use tenferro_tensor::{
9 BackendId, CapabilityAxis, DType, Tensor, TensorRank, TensorRead, TensorScalar, TensorView,
10 TypedTensor, TypedTensorView,
11};
12
13trait UnaryAnalyticElem: Copy + Clone + One + Zero {
14 fn exp_elem(self) -> Self;
15 fn log_elem(self) -> Self;
16 fn sin_elem(self) -> Self;
17 fn cos_elem(self) -> Self;
18 fn tanh_elem(self) -> Self;
19 fn sqrt_elem(self) -> Self;
20 fn rsqrt_elem(self) -> Self;
21 fn expm1_elem(self) -> Self;
22 fn log1p_elem(self) -> Self;
23}
24
25trait PowElem: Copy + Clone + Zero {
26 fn pow_elem(self, exponent: Self) -> Self;
27}
28
29trait IntegerPowElem: PowElem + TensorScalar + PoolScalar + Send + Sync + 'static {
30 fn is_negative_exponent(self) -> bool;
31 fn wrapping_pow_nonnegative(self, exponent: Self) -> Self;
32}
33
34macro_rules! impl_real_analytic_elem {
35 ($ty:ty) => {
36 impl UnaryAnalyticElem for $ty {
37 fn exp_elem(self) -> Self {
38 self.exp()
39 }
40
41 fn log_elem(self) -> Self {
42 self.ln()
43 }
44
45 fn sin_elem(self) -> Self {
46 self.sin()
47 }
48
49 fn cos_elem(self) -> Self {
50 self.cos()
51 }
52
53 fn tanh_elem(self) -> Self {
54 self.tanh()
55 }
56
57 fn sqrt_elem(self) -> Self {
58 self.sqrt()
59 }
60
61 fn rsqrt_elem(self) -> Self {
62 Self::one() / self.sqrt()
63 }
64
65 fn expm1_elem(self) -> Self {
66 self.exp_m1()
67 }
68
69 fn log1p_elem(self) -> Self {
70 self.ln_1p()
71 }
72 }
73
74 impl PowElem for $ty {
75 fn pow_elem(self, exponent: Self) -> Self {
76 self.powf(exponent)
77 }
78 }
79 };
80}
81
82macro_rules! impl_complex_analytic_elem {
83 ($ty:ty) => {
84 impl UnaryAnalyticElem for $ty {
85 fn exp_elem(self) -> Self {
86 self.exp()
87 }
88
89 fn log_elem(self) -> Self {
90 self.ln()
91 }
92
93 fn sin_elem(self) -> Self {
94 self.sin()
95 }
96
97 fn cos_elem(self) -> Self {
98 self.cos()
99 }
100
101 fn tanh_elem(self) -> Self {
102 self.tanh()
103 }
104
105 fn sqrt_elem(self) -> Self {
106 self.sqrt()
107 }
108
109 fn rsqrt_elem(self) -> Self {
110 Self::one() / self.sqrt()
111 }
112
113 fn expm1_elem(self) -> Self {
114 self.exp() - Self::one()
115 }
116
117 fn log1p_elem(self) -> Self {
118 (self + Self::one()).ln()
119 }
120 }
121
122 impl PowElem for $ty {
123 fn pow_elem(self, exponent: Self) -> Self {
124 self.powc(exponent)
125 }
126 }
127 };
128}
129
130impl_real_analytic_elem!(f32);
131impl_real_analytic_elem!(f64);
132impl_complex_analytic_elem!(Complex32);
133impl_complex_analytic_elem!(Complex64);
134
135macro_rules! impl_integer_pow_elem {
136 ($ty:ty) => {
137 impl PowElem for $ty {
138 fn pow_elem(self, exponent: Self) -> Self {
139 self.wrapping_pow_nonnegative(exponent)
140 }
141 }
142
143 impl IntegerPowElem for $ty {
144 fn is_negative_exponent(self) -> bool {
145 self < 0
146 }
147
148 fn wrapping_pow_nonnegative(self, exponent: Self) -> Self {
149 let mut base = self;
150 let mut exp = exponent as u64;
151 let mut acc: Self = 1;
152 while exp != 0 {
153 if exp & 1 == 1 {
154 acc = acc.wrapping_mul(base);
155 }
156 exp >>= 1;
157 if exp != 0 {
158 base = base.wrapping_mul(base);
159 }
160 }
161 acc
162 }
163 }
164 };
165}
166
167impl_integer_pow_elem!(i32);
168impl_integer_pow_elem!(i64);
169
170fn with_local_pool<T>(f: impl FnOnce(&mut BufferPool) -> T) -> T {
171 let mut buffers = BufferPool::new();
172 f(&mut buffers)
173}
174
175enum AnalyticReadView<'a> {
176 F32(TypedTensorView<'a, f32>),
177 F64(TypedTensorView<'a, f64>),
178 I32(TypedTensorView<'a, i32>),
179 I64(TypedTensorView<'a, i64>),
180 Bool,
181 C32(TypedTensorView<'a, Complex32>),
182 C64(TypedTensorView<'a, Complex64>),
183}
184
185fn read_as_analytic_view(input: TensorRead<'_>) -> AnalyticReadView<'_> {
186 match input {
187 TensorRead::Tensor(Tensor::F32(tensor)) => AnalyticReadView::F32(tensor.as_view()),
188 TensorRead::Tensor(Tensor::F64(tensor)) => AnalyticReadView::F64(tensor.as_view()),
189 TensorRead::Tensor(Tensor::I32(tensor)) => AnalyticReadView::I32(tensor.as_view()),
190 TensorRead::Tensor(Tensor::I64(tensor)) => AnalyticReadView::I64(tensor.as_view()),
191 TensorRead::Tensor(Tensor::Bool(_)) => AnalyticReadView::Bool,
192 TensorRead::Tensor(Tensor::C32(tensor)) => AnalyticReadView::C32(tensor.as_view()),
193 TensorRead::Tensor(Tensor::C64(tensor)) => AnalyticReadView::C64(tensor.as_view()),
194 TensorRead::View(TensorView::F32(view)) => AnalyticReadView::F32(view),
195 TensorRead::View(TensorView::F64(view)) => AnalyticReadView::F64(view),
196 TensorRead::View(TensorView::I32(view)) => AnalyticReadView::I32(view),
197 TensorRead::View(TensorView::I64(view)) => AnalyticReadView::I64(view),
198 TensorRead::View(TensorView::Bool(_)) => AnalyticReadView::Bool,
199 TensorRead::View(TensorView::C32(view)) => AnalyticReadView::C32(view),
200 TensorRead::View(TensorView::C64(view)) => AnalyticReadView::C64(view),
201 }
202}
203
204fn typed_unary_with_pool<T>(
205 op: &'static str,
206 buffers: &mut BufferPool,
207 input: &TypedTensor<T>,
208 f: impl Fn(T) -> T + Copy + Sync,
209) -> crate::Result<TypedTensor<T>>
210where
211 T: Copy + PoolScalar + 'static,
212{
213 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
215 map_into(&mut out.view_mut(), &typed_view(op, input)?, f)
216 .map_err(|err| crate::Error::backend_failure(op, err))?;
217 Ok(tensor_from_array(out))
218}
219
220fn typed_unary_view_with_pool<T, R>(
221 op: &'static str,
222 buffers: &mut BufferPool,
223 input: &TypedTensorView<'_, T, R>,
224 f: impl Fn(T) -> T + Copy + Sync,
225) -> crate::Result<TypedTensor<T>>
226where
227 T: Copy + PoolScalar + 'static,
228 R: TensorRank,
229{
230 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
232 map_into(&mut out.view_mut(), &typed_view_from_view(op, input)?, f)
233 .map_err(|err| crate::Error::backend_failure(op, err))?;
234 Ok(tensor_from_array(out))
235}
236
237fn typed_unary_tensor_with_pool<T>(
238 op: &'static str,
239 buffers: &mut BufferPool,
240 input: &TypedTensor<T>,
241 f: impl Fn(T) -> T + Copy + Sync,
242) -> crate::Result<Tensor>
243where
244 T: Copy + PoolScalar + TensorScalar + 'static,
245{
246 let out = typed_unary_with_pool(op, buffers, input, f)?;
247 Ok(T::typed_tensor_into_tensor(out))
248}
249
250fn typed_unary_view_tensor_with_pool<T, R>(
251 op: &'static str,
252 buffers: &mut BufferPool,
253 input: &TypedTensorView<'_, T, R>,
254 f: impl Fn(T) -> T + Copy + Sync,
255) -> crate::Result<Tensor>
256where
257 T: Copy + PoolScalar + TensorScalar + 'static,
258 R: TensorRank,
259{
260 let out = typed_unary_view_with_pool(op, buffers, input, f)?;
261 Ok(T::typed_tensor_into_tensor(out))
262}
263
264fn require_cpu_capability(
265 op_kind: PrimitiveOpKind,
266 op: &'static str,
267 dtype: DType,
268 axis: CapabilityAxis,
269) -> crate::Result<()> {
270 let supported = crate::cpu_capabilities()
271 .iter()
272 .copied()
273 .find(|entry| entry.op == op_kind && entry.dtype == dtype)
274 .is_some_and(|entry| entry.axis(axis).is_supported());
275 if supported {
276 Ok(())
277 } else {
278 Err(crate::Error::unsupported_op_dtype(
279 op,
280 dtype,
281 BackendId::Cpu,
282 ))
283 }
284}
285
286fn strided_view_contains<T>(
287 op: &'static str,
288 view: &StridedView<'_, T>,
289 pred: impl Fn(T) -> bool + Copy + Sync,
290) -> crate::Result<bool>
291where
292 T: Copy + Send + Sync,
293{
294 reduce(view, pred, |lhs, rhs| lhs || rhs, false)
295 .map_err(|err| crate::Error::backend_failure(op, err))
296}
297
298fn ensure_nonnegative_integer_exponents<T>(
299 op: &'static str,
300 rhs: &StridedView<'_, T>,
301) -> crate::Result<()>
302where
303 T: IntegerPowElem,
304{
305 if strided_view_contains(op, rhs, |value| value.is_negative_exponent())? {
306 return Err(crate::Error::negative_integer_exponent(op, T::dtype()));
307 }
308 Ok(())
309}
310
311fn typed_pow_view_with_pool<T, L, R>(
312 op: &'static str,
313 buffers: &mut BufferPool,
314 lhs: &TypedTensorView<'_, T, L>,
315 rhs: &TypedTensorView<'_, T, R>,
316) -> crate::Result<TypedTensor<T>>
317where
318 T: PowElem + PoolScalar + 'static,
319 L: TensorRank,
320 R: TensorRank,
321{
322 let output_shape = if lhs.shape() == rhs.shape() {
323 lhs.shape()
324 } else if lhs.shape().is_empty() {
325 rhs.shape()
326 } else if rhs.shape().is_empty() {
327 lhs.shape()
328 } else {
329 return Err(crate::Error::ShapeMismatch {
330 op,
331 lhs: lhs.shape().to_vec(),
332 rhs: rhs.shape().to_vec(),
333 });
334 };
335 let mut out = unsafe { typed_array_uninit_from_pool(buffers, output_shape) }?;
337 if lhs.shape() == rhs.shape() {
338 zip_map2_into(
339 &mut out.view_mut(),
340 &typed_view_from_view(op, lhs)?,
341 &typed_view_from_view(op, rhs)?,
342 |x, y| x.pow_elem(y),
343 )
344 .map_err(|err| crate::Error::backend_failure(op, err))?;
345 } else if lhs.shape().is_empty() {
346 let scalar = typed_view_from_view(op, lhs)?.get(&[]);
347 map_into(&mut out.view_mut(), &typed_view_from_view(op, rhs)?, |x| {
348 scalar.pow_elem(x)
349 })
350 .map_err(|err| crate::Error::backend_failure(op, err))?;
351 } else {
352 let scalar = typed_view_from_view(op, rhs)?.get(&[]);
353 map_into(&mut out.view_mut(), &typed_view_from_view(op, lhs)?, |x| {
354 x.pow_elem(scalar)
355 })
356 .map_err(|err| crate::Error::backend_failure(op, err))?;
357 }
358 Ok(tensor_from_array(out))
359}
360
361macro_rules! define_unary_analytic_dispatch {
362 ($dispatch_fn:ident, $dispatch_with_pool_fn:ident, $dispatch_read_with_pool_fn:ident, $op_kind:ident, $elem_fn:ident) => {
363 #[cfg(test)]
364 pub(crate) fn $dispatch_fn(input: &Tensor) -> crate::Result<Tensor> {
365 with_local_pool(|buffers| $dispatch_with_pool_fn(buffers, input))
366 }
367
368 pub(crate) fn $dispatch_with_pool_fn(
369 buffers: &mut BufferPool,
370 input: &Tensor,
371 ) -> crate::Result<Tensor> {
372 require_cpu_capability(
373 PrimitiveOpKind::$op_kind,
374 stringify!($dispatch_fn),
375 input.dtype(),
376 CapabilityAxis::OwnedResult,
377 )?;
378 tenferro_tensor::with_scalar!(
379 input,
380 float_complex,
381 backend = BackendId::Cpu,
382 op = stringify!($dispatch_fn),
383 |tensor| -> crate::Result<Tensor> {
384 typed_unary_tensor_with_pool(stringify!($dispatch_fn), buffers, tensor, |x| {
385 x.$elem_fn()
386 })
387 }
388 )
389 }
390
391 pub(crate) fn $dispatch_read_with_pool_fn(
392 buffers: &mut BufferPool,
393 input: TensorRead<'_>,
394 ) -> crate::Result<Tensor> {
395 let dtype = input.dtype();
396 require_cpu_capability(
397 PrimitiveOpKind::$op_kind,
398 stringify!($dispatch_fn),
399 dtype,
400 CapabilityAxis::ReadInputs,
401 )?;
402 tenferro_tensor::with_scalar_read!(
403 input,
404 float_complex,
405 backend = BackendId::Cpu,
406 op = stringify!($dispatch_fn),
407 |view| -> crate::Result<Tensor> {
408 typed_unary_view_tensor_with_pool(
409 stringify!($dispatch_fn),
410 buffers,
411 &view,
412 |x| x.$elem_fn(),
413 )
414 }
415 )
416 }
417 };
418}
419
420define_unary_analytic_dispatch!(exp, exp_with_pool, exp_read_with_pool, Exp, exp_elem);
421define_unary_analytic_dispatch!(log, log_with_pool, log_read_with_pool, Log, log_elem);
422define_unary_analytic_dispatch!(sin, sin_with_pool, sin_read_with_pool, Sin, sin_elem);
423define_unary_analytic_dispatch!(cos, cos_with_pool, cos_read_with_pool, Cos, cos_elem);
424define_unary_analytic_dispatch!(tanh, tanh_with_pool, tanh_read_with_pool, Tanh, tanh_elem);
425define_unary_analytic_dispatch!(sqrt, sqrt_with_pool, sqrt_read_with_pool, Sqrt, sqrt_elem);
426define_unary_analytic_dispatch!(
427 rsqrt,
428 rsqrt_with_pool,
429 rsqrt_read_with_pool,
430 Rsqrt,
431 rsqrt_elem
432);
433define_unary_analytic_dispatch!(
434 expm1,
435 expm1_with_pool,
436 expm1_read_with_pool,
437 Expm1,
438 expm1_elem
439);
440define_unary_analytic_dispatch!(
441 log1p,
442 log1p_with_pool,
443 log1p_read_with_pool,
444 Log1p,
445 log1p_elem
446);
447
448pub fn pow(lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
449 with_local_pool(|buffers| pow_with_pool(buffers, lhs, rhs))
450}
451
452pub(crate) fn pow_with_pool(
453 buffers: &mut BufferPool,
454 lhs: &Tensor,
455 rhs: &Tensor,
456) -> crate::Result<Tensor> {
457 match (lhs, rhs) {
458 (Tensor::F32(a), Tensor::F32(b)) => Ok(Tensor::F32(typed_pow_with_pool(buffers, a, b)?)),
459 (Tensor::F64(a), Tensor::F64(b)) => Ok(Tensor::F64(typed_pow_with_pool(buffers, a, b)?)),
460 (Tensor::I32(a), Tensor::I32(b)) => {
461 Ok(Tensor::I32(typed_integer_pow_with_pool(buffers, a, b)?))
462 }
463 (Tensor::I64(a), Tensor::I64(b)) => {
464 Ok(Tensor::I64(typed_integer_pow_with_pool(buffers, a, b)?))
465 }
466 (Tensor::C32(a), Tensor::C32(b)) => Ok(Tensor::C32(typed_pow_with_pool(buffers, a, b)?)),
467 (Tensor::C64(a), Tensor::C64(b)) => Ok(Tensor::C64(typed_pow_with_pool(buffers, a, b)?)),
468 _ => Err(crate::Error::DTypeMismatch {
469 op: "pow",
470 lhs: lhs.dtype(),
471 rhs: rhs.dtype(),
472 }),
473 }
474}
475
476pub(crate) fn pow_read_with_pool(
477 buffers: &mut BufferPool,
478 lhs: TensorRead<'_>,
479 rhs: TensorRead<'_>,
480) -> crate::Result<Tensor> {
481 let lhs_dtype = lhs.dtype();
482 let rhs_dtype = rhs.dtype();
483 match (read_as_analytic_view(lhs), read_as_analytic_view(rhs)) {
484 (AnalyticReadView::F32(a), AnalyticReadView::F32(b)) => Ok(Tensor::F32(
485 typed_pow_view_with_pool("pow", buffers, &a, &b)?,
486 )),
487 (AnalyticReadView::F64(a), AnalyticReadView::F64(b)) => Ok(Tensor::F64(
488 typed_pow_view_with_pool("pow", buffers, &a, &b)?,
489 )),
490 (AnalyticReadView::I32(a), AnalyticReadView::I32(b)) => Ok(Tensor::I32(
491 typed_integer_pow_view_with_pool(buffers, &a, &b)?,
492 )),
493 (AnalyticReadView::I64(a), AnalyticReadView::I64(b)) => Ok(Tensor::I64(
494 typed_integer_pow_view_with_pool(buffers, &a, &b)?,
495 )),
496 (AnalyticReadView::C32(a), AnalyticReadView::C32(b)) => Ok(Tensor::C32(
497 typed_pow_view_with_pool("pow", buffers, &a, &b)?,
498 )),
499 (AnalyticReadView::C64(a), AnalyticReadView::C64(b)) => Ok(Tensor::C64(
500 typed_pow_view_with_pool("pow", buffers, &a, &b)?,
501 )),
502 _ => Err(crate::Error::DTypeMismatch {
503 op: "pow",
504 lhs: lhs_dtype,
505 rhs: rhs_dtype,
506 }),
507 }
508}
509
510fn typed_pow_with_pool<T>(
511 buffers: &mut BufferPool,
512 lhs: &TypedTensor<T>,
513 rhs: &TypedTensor<T>,
514) -> crate::Result<TypedTensor<T>>
515where
516 T: PowElem + PoolScalar,
517{
518 let output_shape = if lhs.shape() == rhs.shape() {
519 lhs.shape()
520 } else if lhs.shape().is_empty() {
521 rhs.shape()
522 } else if rhs.shape().is_empty() {
523 lhs.shape()
524 } else {
525 return Err(crate::Error::ShapeMismatch {
526 op: "pow",
527 lhs: lhs.shape().to_vec(),
528 rhs: rhs.shape().to_vec(),
529 });
530 };
531 let mut out = unsafe { typed_array_uninit_from_pool(buffers, output_shape) }?;
533 if lhs.shape() == rhs.shape() {
534 zip_map2_into(
535 &mut out.view_mut(),
536 &typed_view("pow", lhs)?,
537 &typed_view("pow", rhs)?,
538 |x, y| x.pow_elem(y),
539 )
540 .map_err(|err| crate::Error::backend_failure("pow", err))?;
541 } else if lhs.shape().is_empty() {
542 let scalar = typed_view("pow", lhs)?.get(&[]);
543 map_into(&mut out.view_mut(), &typed_view("pow", rhs)?, |x| {
544 scalar.pow_elem(x)
545 })
546 .map_err(|err| crate::Error::backend_failure("pow", err))?;
547 } else {
548 let scalar = typed_view("pow", rhs)?.get(&[]);
549 map_into(&mut out.view_mut(), &typed_view("pow", lhs)?, |x| {
550 x.pow_elem(scalar)
551 })
552 .map_err(|err| crate::Error::backend_failure("pow", err))?;
553 }
554 Ok(tensor_from_array(out))
555}
556
557fn typed_integer_pow_with_pool<T>(
558 buffers: &mut BufferPool,
559 lhs: &TypedTensor<T>,
560 rhs: &TypedTensor<T>,
561) -> crate::Result<TypedTensor<T>>
562where
563 T: IntegerPowElem,
564{
565 let rhs_view = typed_view("pow", rhs)?;
566 ensure_nonnegative_integer_exponents("pow", &rhs_view)?;
567 typed_pow_with_pool(buffers, lhs, rhs)
568}
569
570fn typed_integer_pow_view_with_pool<T, L, R>(
571 buffers: &mut BufferPool,
572 lhs: &TypedTensorView<'_, T, L>,
573 rhs: &TypedTensorView<'_, T, R>,
574) -> crate::Result<TypedTensor<T>>
575where
576 T: IntegerPowElem,
577 L: TensorRank,
578 R: TensorRank,
579{
580 let rhs_view = typed_view_from_view("pow", rhs)?;
581 ensure_nonnegative_integer_exponents("pow", &rhs_view)?;
582 typed_pow_view_with_pool("pow", buffers, lhs, rhs)
583}