1use tenferro_ops::broadcast::{broadcast_input_plan, broadcast_shape, broadcast_shapes};
7use tenferro_tensor::validate::matmul_config_for_shapes;
8use tenferro_tensor::{CompareDir, Error, Result, Tensor, TensorBackend, TensorRead, TensorScalar};
9
10use crate::{TypedTensorMaskOpsExt, TypedTensorOpsExt};
11use tenferro_tensor::TypedTensor;
12
13impl<T: TensorScalar> TypedTensorOpsExt<T> for TypedTensor<T> {
14 fn add<B: TensorBackend>(
15 &self,
16 rhs: &TypedTensor<T>,
17 backend: &mut B,
18 ) -> Result<TypedTensor<T>> {
19 add(self, rhs, backend)
20 }
21
22 fn sub<B: TensorBackend>(
23 &self,
24 rhs: &TypedTensor<T>,
25 backend: &mut B,
26 ) -> Result<TypedTensor<T>> {
27 sub(self, rhs, backend)
28 }
29
30 fn mul<B: TensorBackend>(
31 &self,
32 rhs: &TypedTensor<T>,
33 backend: &mut B,
34 ) -> Result<TypedTensor<T>> {
35 mul(self, rhs, backend)
36 }
37
38 fn div<B: TensorBackend>(
39 &self,
40 rhs: &TypedTensor<T>,
41 backend: &mut B,
42 ) -> Result<TypedTensor<T>> {
43 div(self, rhs, backend)
44 }
45
46 fn rem<B: TensorBackend>(
47 &self,
48 rhs: &TypedTensor<T>,
49 backend: &mut B,
50 ) -> Result<TypedTensor<T>> {
51 rem(self, rhs, backend)
52 }
53
54 fn pow<B: TensorBackend>(
55 &self,
56 rhs: &TypedTensor<T>,
57 backend: &mut B,
58 ) -> Result<TypedTensor<T>> {
59 pow(self, rhs, backend)
60 }
61
62 fn maximum<B: TensorBackend>(
63 &self,
64 rhs: &TypedTensor<T>,
65 backend: &mut B,
66 ) -> Result<TypedTensor<T>> {
67 maximum(self, rhs, backend)
68 }
69
70 fn minimum<B: TensorBackend>(
71 &self,
72 rhs: &TypedTensor<T>,
73 backend: &mut B,
74 ) -> Result<TypedTensor<T>> {
75 minimum(self, rhs, backend)
76 }
77
78 fn neg<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
79 neg(self, backend)
80 }
81
82 fn abs<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
83 abs(self, backend)
84 }
85
86 fn sign<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
87 sign(self, backend)
88 }
89
90 fn conj<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
91 conj(self, backend)
92 }
93
94 fn exp<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
95 exp(self, backend)
96 }
97
98 fn log<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
99 log(self, backend)
100 }
101
102 fn sin<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
103 sin(self, backend)
104 }
105
106 fn cos<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
107 cos(self, backend)
108 }
109
110 fn tanh<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
111 tanh(self, backend)
112 }
113
114 fn sqrt<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
115 sqrt(self, backend)
116 }
117
118 fn rsqrt<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
119 rsqrt(self, backend)
120 }
121
122 fn expm1<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
123 expm1(self, backend)
124 }
125
126 fn log1p<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
127 log1p(self, backend)
128 }
129
130 fn compare<B: TensorBackend>(
131 &self,
132 rhs: &TypedTensor<T>,
133 dir: CompareDir,
134 backend: &mut B,
135 ) -> Result<TypedTensor<bool>> {
136 compare(self, rhs, dir, backend)
137 }
138
139 fn clamp<B: TensorBackend>(
140 &self,
141 lower: &TypedTensor<T>,
142 upper: &TypedTensor<T>,
143 backend: &mut B,
144 ) -> Result<TypedTensor<T>> {
145 clamp(self, lower, upper, backend)
146 }
147
148 fn matmul<B: TensorBackend>(
149 &self,
150 rhs: &TypedTensor<T>,
151 backend: &mut B,
152 ) -> Result<TypedTensor<T>> {
153 matmul(self, rhs, backend)
154 }
155
156 fn reduce_sum<B: TensorBackend>(
157 &self,
158 axes: &[usize],
159 backend: &mut B,
160 ) -> Result<TypedTensor<T>> {
161 reduce_sum(self, axes, backend)
162 }
163
164 fn reshape<B: TensorBackend>(
165 &self,
166 shape: &[usize],
167 backend: &mut B,
168 ) -> Result<TypedTensor<T>> {
169 reshape(self, shape, backend)
170 }
171
172 fn transpose<B: TensorBackend>(
173 &self,
174 perm: &[usize],
175 backend: &mut B,
176 ) -> Result<TypedTensor<T>> {
177 transpose(self, perm, backend)
178 }
179
180 fn broadcast_in_dim<B: TensorBackend>(
181 &self,
182 shape: &[usize],
183 dims: &[usize],
184 backend: &mut B,
185 ) -> Result<TypedTensor<T>> {
186 broadcast_in_dim(self, shape, dims, backend)
187 }
188}
189
190impl TypedTensorMaskOpsExt for TypedTensor<bool> {
191 fn where_select<T: TensorScalar, B: TensorBackend>(
192 &self,
193 on_true: &TypedTensor<T>,
194 on_false: &TypedTensor<T>,
195 backend: &mut B,
196 ) -> Result<TypedTensor<T>> {
197 where_select(self, on_true, on_false, backend)
198 }
199}
200
201fn add<T: TensorScalar>(
214 lhs: &TypedTensor<T>,
215 rhs: &TypedTensor<T>,
216 backend: &mut impl TensorBackend,
217) -> Result<TypedTensor<T>> {
218 let (lhs, rhs) = broadcast_binary_read(lhs, rhs, backend)?;
219 let out =
220 backend.with_backend_session(|exec| exec.add_read(lhs.tensor_read(), rhs.tensor_read()))?;
221 into_typed_result("add", out)
222}
223
224macro_rules! unary_fn {
225 ($name:ident, $method:ident, $summary:literal) => {
226 #[doc = $summary]
227 #[doc = concat!("let y = x.", stringify!($name), "(&mut backend).unwrap();")]
236 fn $name<T: TensorScalar>(
238 input: &TypedTensor<T>,
239 backend: &mut impl TensorBackend,
240 ) -> Result<TypedTensor<T>> {
241 let out = backend.with_backend_session(|exec| exec.$method(T::tensor_read(input)))?;
242 into_typed_result(stringify!($name), out)
243 }
244 };
245}
246
247macro_rules! binary_fn {
248 ($name:ident, $method:ident, $summary:literal) => {
249 #[doc = $summary]
250 #[doc = concat!("let z = x.", stringify!($name), "(&y, &mut backend).unwrap();")]
260 fn $name<T: TensorScalar>(
262 lhs: &TypedTensor<T>,
263 rhs: &TypedTensor<T>,
264 backend: &mut impl TensorBackend,
265 ) -> Result<TypedTensor<T>> {
266 let (lhs, rhs) = broadcast_binary_read(lhs, rhs, backend)?;
267 let out = backend
268 .with_backend_session(|exec| exec.$method(lhs.tensor_read(), rhs.tensor_read()))?;
269 into_typed_result(stringify!($name), out)
270 }
271 };
272}
273
274binary_fn!(
275 mul,
276 mul_read,
277 "Elementwise multiplication with NumPy-style broadcasting."
278);
279binary_fn!(
280 div,
281 div_read,
282 "Elementwise division with NumPy-style broadcasting."
283);
284binary_fn!(
285 rem,
286 rem_read,
287 "Elementwise remainder with NumPy-style broadcasting."
288);
289binary_fn!(
290 pow,
291 pow_read,
292 "Elementwise power with NumPy-style broadcasting."
293);
294binary_fn!(
295 maximum,
296 maximum_read,
297 "Elementwise maximum with NumPy-style broadcasting."
298);
299binary_fn!(
300 minimum,
301 minimum_read,
302 "Elementwise minimum with NumPy-style broadcasting."
303);
304
305unary_fn!(neg, neg_read, "Elementwise negation.");
306unary_fn!(abs, abs_read, "Elementwise absolute value.");
307unary_fn!(sign, sign_read, "Elementwise sign.");
308unary_fn!(conj, conj_read, "Elementwise complex conjugate.");
309unary_fn!(exp, exp_read, "Elementwise exponential.");
310unary_fn!(log, log_read, "Elementwise natural logarithm.");
311unary_fn!(sin, sin_read, "Elementwise sine.");
312unary_fn!(cos, cos_read, "Elementwise cosine.");
313unary_fn!(tanh, tanh_read, "Elementwise hyperbolic tangent.");
314unary_fn!(sqrt, sqrt_read, "Elementwise square root.");
315unary_fn!(rsqrt, rsqrt_read, "Elementwise reciprocal square root.");
316unary_fn!(expm1, expm1_read, "Elementwise `exp(x) - 1`.");
317unary_fn!(log1p, log1p_read, "Elementwise `log(1 + x)`.");
318
319fn sub<T: TensorScalar>(
332 lhs: &TypedTensor<T>,
333 rhs: &TypedTensor<T>,
334 backend: &mut impl TensorBackend,
335) -> Result<TypedTensor<T>> {
336 let (lhs, rhs) = broadcast_binary_read(lhs, rhs, backend)?;
337 let out =
338 backend.with_backend_session(|exec| exec.sub_read(lhs.tensor_read(), rhs.tensor_read()))?;
339 into_typed_result("sub", out)
340}
341
342fn compare<T: TensorScalar>(
358 lhs: &TypedTensor<T>,
359 rhs: &TypedTensor<T>,
360 dir: CompareDir,
361 backend: &mut impl TensorBackend,
362) -> Result<TypedTensor<bool>> {
363 let (lhs, rhs) = broadcast_binary_read(lhs, rhs, backend)?;
364 let out = backend.with_backend_session(|exec| {
365 exec.compare_read(lhs.tensor_read(), rhs.tensor_read(), &dir)
366 })?;
367 into_typed_result("compare", out)
368}
369
370fn where_select<T: TensorScalar>(
386 condition: &TypedTensor<bool>,
387 on_true: &TypedTensor<T>,
388 on_false: &TypedTensor<T>,
389 backend: &mut impl TensorBackend,
390) -> Result<TypedTensor<T>> {
391 let (condition, on_true, on_false) =
392 broadcast_ternary_read(condition, on_true, on_false, backend)?;
393 let out = backend.with_backend_session(|exec| {
394 exec.select_read(
395 condition.tensor_read(),
396 on_true.tensor_read(),
397 on_false.tensor_read(),
398 )
399 })?;
400 into_typed_result("where_select", out)
401}
402
403fn clamp<T: TensorScalar>(
417 input: &TypedTensor<T>,
418 lower: &TypedTensor<T>,
419 upper: &TypedTensor<T>,
420 backend: &mut impl TensorBackend,
421) -> Result<TypedTensor<T>> {
422 let (input, lower, upper) = broadcast_ternary_read(input, lower, upper, backend)?;
423 let out = backend.with_backend_session(|exec| {
424 exec.clamp_read(
425 input.tensor_read(),
426 lower.tensor_read(),
427 upper.tensor_read(),
428 )
429 })?;
430 into_typed_result("clamp", out)
431}
432
433fn matmul<T: TensorScalar>(
448 a: &TypedTensor<T>,
449 b: &TypedTensor<T>,
450 backend: &mut impl TensorBackend,
451) -> Result<TypedTensor<T>> {
452 let config = matmul_config_for_shapes("matmul", a.shape(), b.shape())?;
453 let out = backend.with_backend_session(|exec| {
454 exec.dot_general_read(T::tensor_read(a), T::tensor_read(b), &config)
455 })?;
456 into_typed_result("matmul", out)
457}
458
459fn reduce_sum<T: TensorScalar>(
476 input: &TypedTensor<T>,
477 axes: &[usize],
478 backend: &mut impl TensorBackend,
479) -> Result<TypedTensor<T>> {
480 let out =
481 backend.with_backend_session(|exec| exec.reduce_sum_read(T::tensor_read(input), axes))?;
482 into_typed_result("reduce_sum", out)
483}
484
485fn reshape<T: TensorScalar>(
498 input: &TypedTensor<T>,
499 shape: &[usize],
500 backend: &mut impl TensorBackend,
501) -> Result<TypedTensor<T>> {
502 let out =
503 backend.with_backend_session(|exec| exec.reshape_read(T::tensor_read(input), shape))?;
504 into_typed_result("reshape", out)
505}
506
507fn transpose<T: TensorScalar>(
520 input: &TypedTensor<T>,
521 perm: &[usize],
522 backend: &mut impl TensorBackend,
523) -> Result<TypedTensor<T>> {
524 let out =
525 backend.with_backend_session(|exec| exec.transpose_read(T::tensor_read(input), perm))?;
526 into_typed_result("transpose", out)
527}
528
529fn broadcast_in_dim<T: TensorScalar>(
545 input: &TypedTensor<T>,
546 shape: &[usize],
547 dims: &[usize],
548 backend: &mut impl TensorBackend,
549) -> Result<TypedTensor<T>> {
550 let out = backend.with_backend_session(|exec| {
551 exec.broadcast_in_dim_read(T::tensor_read(input), shape, dims)
552 })?;
553 into_typed_result("broadcast_in_dim", out)
554}
555
556enum ReadInput<'a> {
557 Borrowed(TensorRead<'a>),
558 Owned(Tensor),
559}
560
561impl ReadInput<'_> {
562 fn tensor_read(&self) -> TensorRead<'_> {
563 match self {
564 Self::Borrowed(read) => read.clone(),
565 Self::Owned(tensor) => TensorRead::from_tensor(tensor),
566 }
567 }
568}
569
570fn broadcast_binary_read<'a, T: TensorScalar>(
571 lhs: &'a TypedTensor<T>,
572 rhs: &'a TypedTensor<T>,
573 backend: &mut impl TensorBackend,
574) -> Result<(ReadInput<'a>, ReadInput<'a>)> {
575 let shape = broadcast_shape(lhs.shape(), rhs.shape()).map_err(broadcast_error)?;
576 Ok((
577 broadcast_to_read(lhs, &shape, backend)?,
578 broadcast_to_read(rhs, &shape, backend)?,
579 ))
580}
581
582fn broadcast_ternary_read<'a, C: TensorScalar, T: TensorScalar>(
583 first: &'a TypedTensor<C>,
584 second: &'a TypedTensor<T>,
585 third: &'a TypedTensor<T>,
586 backend: &mut impl TensorBackend,
587) -> Result<(ReadInput<'a>, ReadInput<'a>, ReadInput<'a>)> {
588 let shape = broadcast_shapes([first.shape(), second.shape(), third.shape()])
589 .map_err(broadcast_error)?;
590 Ok((
591 broadcast_to_read(first, &shape, backend)?,
592 broadcast_to_read(second, &shape, backend)?,
593 broadcast_to_read(third, &shape, backend)?,
594 ))
595}
596
597fn broadcast_to_read<'a, T: TensorScalar>(
598 input: &'a TypedTensor<T>,
599 target_shape: &[usize],
600 backend: &mut impl TensorBackend,
601) -> Result<ReadInput<'a>> {
602 if input.shape() == target_shape {
603 return Ok(ReadInput::Borrowed(T::tensor_read(input)));
604 }
605
606 let plan = broadcast_input_plan(input.shape(), target_shape).map_err(broadcast_error)?;
607 let source = if plan.source_shape == input.shape() {
608 ReadInput::Borrowed(T::tensor_read(input))
609 } else {
610 let reshaped = backend.with_backend_session(|exec| {
611 exec.reshape_read(T::tensor_read(input), &plan.source_shape)
612 })?;
613 ReadInput::Owned(reshaped)
614 };
615 let out = backend.with_backend_session(|exec| {
616 exec.broadcast_in_dim_read(source.tensor_read(), target_shape, &plan.dims)
617 })?;
618 Ok(ReadInput::Owned(out))
619}
620
621fn broadcast_error(err: impl std::fmt::Display) -> Error {
622 Error::backend_failure("broadcast", err.to_string())
623}
624
625fn into_typed_result<T: TensorScalar>(op: &'static str, tensor: Tensor) -> Result<TypedTensor<T>> {
626 let actual = tensor.dtype();
627 T::into_typed(tensor).map_err(|_| Error::DTypeMismatch {
628 op,
629 lhs: T::dtype(),
630 rhs: actual,
631 })
632}