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 pow<B: TensorBackend>(
47 &self,
48 rhs: &TypedTensor<T>,
49 backend: &mut B,
50 ) -> Result<TypedTensor<T>> {
51 pow(self, rhs, backend)
52 }
53
54 fn maximum<B: TensorBackend>(
55 &self,
56 rhs: &TypedTensor<T>,
57 backend: &mut B,
58 ) -> Result<TypedTensor<T>> {
59 maximum(self, rhs, backend)
60 }
61
62 fn minimum<B: TensorBackend>(
63 &self,
64 rhs: &TypedTensor<T>,
65 backend: &mut B,
66 ) -> Result<TypedTensor<T>> {
67 minimum(self, rhs, backend)
68 }
69
70 fn neg<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
71 neg(self, backend)
72 }
73
74 fn abs<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
75 abs(self, backend)
76 }
77
78 fn sign<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
79 sign(self, backend)
80 }
81
82 fn conj<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
83 conj(self, backend)
84 }
85
86 fn exp<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
87 exp(self, backend)
88 }
89
90 fn log<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
91 log(self, backend)
92 }
93
94 fn sin<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
95 sin(self, backend)
96 }
97
98 fn cos<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
99 cos(self, backend)
100 }
101
102 fn tanh<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
103 tanh(self, backend)
104 }
105
106 fn sqrt<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
107 sqrt(self, backend)
108 }
109
110 fn rsqrt<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
111 rsqrt(self, backend)
112 }
113
114 fn expm1<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
115 expm1(self, backend)
116 }
117
118 fn log1p<B: TensorBackend>(&self, backend: &mut B) -> Result<TypedTensor<T>> {
119 log1p(self, backend)
120 }
121
122 fn compare<B: TensorBackend>(
123 &self,
124 rhs: &TypedTensor<T>,
125 dir: CompareDir,
126 backend: &mut B,
127 ) -> Result<TypedTensor<bool>> {
128 compare(self, rhs, dir, backend)
129 }
130
131 fn clamp<B: TensorBackend>(
132 &self,
133 lower: &TypedTensor<T>,
134 upper: &TypedTensor<T>,
135 backend: &mut B,
136 ) -> Result<TypedTensor<T>> {
137 clamp(self, lower, upper, backend)
138 }
139
140 fn matmul<B: TensorBackend>(
141 &self,
142 rhs: &TypedTensor<T>,
143 backend: &mut B,
144 ) -> Result<TypedTensor<T>> {
145 matmul(self, rhs, backend)
146 }
147
148 fn reduce_sum<B: TensorBackend>(
149 &self,
150 axes: &[usize],
151 backend: &mut B,
152 ) -> Result<TypedTensor<T>> {
153 reduce_sum(self, axes, backend)
154 }
155
156 fn reshape<B: TensorBackend>(
157 &self,
158 shape: &[usize],
159 backend: &mut B,
160 ) -> Result<TypedTensor<T>> {
161 reshape(self, shape, backend)
162 }
163
164 fn transpose<B: TensorBackend>(
165 &self,
166 perm: &[usize],
167 backend: &mut B,
168 ) -> Result<TypedTensor<T>> {
169 transpose(self, perm, backend)
170 }
171
172 fn broadcast_in_dim<B: TensorBackend>(
173 &self,
174 shape: &[usize],
175 dims: &[usize],
176 backend: &mut B,
177 ) -> Result<TypedTensor<T>> {
178 broadcast_in_dim(self, shape, dims, backend)
179 }
180}
181
182impl TypedTensorMaskOpsExt for TypedTensor<bool> {
183 fn where_select<T: TensorScalar, B: TensorBackend>(
184 &self,
185 on_true: &TypedTensor<T>,
186 on_false: &TypedTensor<T>,
187 backend: &mut B,
188 ) -> Result<TypedTensor<T>> {
189 where_select(self, on_true, on_false, backend)
190 }
191}
192
193fn add<T: TensorScalar>(
206 lhs: &TypedTensor<T>,
207 rhs: &TypedTensor<T>,
208 backend: &mut impl TensorBackend,
209) -> Result<TypedTensor<T>> {
210 let (lhs, rhs) = broadcast_binary_read(lhs, rhs, backend)?;
211 let out =
212 backend.with_backend_session(|exec| exec.add_read(lhs.tensor_read(), rhs.tensor_read()))?;
213 into_typed_result("add", out)
214}
215
216macro_rules! unary_fn {
217 ($name:ident, $method:ident, $summary:literal) => {
218 #[doc = $summary]
219 #[doc = concat!("let y = x.", stringify!($name), "(&mut backend).unwrap();")]
228 fn $name<T: TensorScalar>(
230 input: &TypedTensor<T>,
231 backend: &mut impl TensorBackend,
232 ) -> Result<TypedTensor<T>> {
233 let out = backend.with_backend_session(|exec| exec.$method(T::tensor_read(input)))?;
234 into_typed_result(stringify!($name), out)
235 }
236 };
237}
238
239macro_rules! binary_fn {
240 ($name:ident, $method:ident, $summary:literal) => {
241 #[doc = $summary]
242 #[doc = concat!("let z = x.", stringify!($name), "(&y, &mut backend).unwrap();")]
252 fn $name<T: TensorScalar>(
254 lhs: &TypedTensor<T>,
255 rhs: &TypedTensor<T>,
256 backend: &mut impl TensorBackend,
257 ) -> Result<TypedTensor<T>> {
258 let (lhs, rhs) = broadcast_binary_read(lhs, rhs, backend)?;
259 let out = backend
260 .with_backend_session(|exec| exec.$method(lhs.tensor_read(), rhs.tensor_read()))?;
261 into_typed_result(stringify!($name), out)
262 }
263 };
264}
265
266binary_fn!(
267 mul,
268 mul_read,
269 "Elementwise multiplication with NumPy-style broadcasting."
270);
271binary_fn!(
272 div,
273 div_read,
274 "Elementwise division with NumPy-style broadcasting."
275);
276binary_fn!(
277 pow,
278 pow_read,
279 "Elementwise power with NumPy-style broadcasting."
280);
281binary_fn!(
282 maximum,
283 maximum_read,
284 "Elementwise maximum with NumPy-style broadcasting."
285);
286binary_fn!(
287 minimum,
288 minimum_read,
289 "Elementwise minimum with NumPy-style broadcasting."
290);
291
292unary_fn!(neg, neg_read, "Elementwise negation.");
293unary_fn!(abs, abs_read, "Elementwise absolute value.");
294unary_fn!(sign, sign_read, "Elementwise sign.");
295unary_fn!(conj, conj_read, "Elementwise complex conjugate.");
296unary_fn!(exp, exp_read, "Elementwise exponential.");
297unary_fn!(log, log_read, "Elementwise natural logarithm.");
298unary_fn!(sin, sin_read, "Elementwise sine.");
299unary_fn!(cos, cos_read, "Elementwise cosine.");
300unary_fn!(tanh, tanh_read, "Elementwise hyperbolic tangent.");
301unary_fn!(sqrt, sqrt_read, "Elementwise square root.");
302unary_fn!(rsqrt, rsqrt_read, "Elementwise reciprocal square root.");
303unary_fn!(expm1, expm1_read, "Elementwise `exp(x) - 1`.");
304unary_fn!(log1p, log1p_read, "Elementwise `log(1 + x)`.");
305
306fn sub<T: TensorScalar>(
319 lhs: &TypedTensor<T>,
320 rhs: &TypedTensor<T>,
321 backend: &mut impl TensorBackend,
322) -> Result<TypedTensor<T>> {
323 let (lhs, rhs) = broadcast_binary_read(lhs, rhs, backend)?;
324 let neg_rhs = backend.with_backend_session(|exec| exec.neg_read(rhs.tensor_read()))?;
325 let out = backend.with_backend_session(|exec| {
326 exec.add_read(lhs.tensor_read(), TensorRead::from_tensor(&neg_rhs))
327 })?;
328 into_typed_result("sub", out)
329}
330
331fn compare<T: TensorScalar>(
347 lhs: &TypedTensor<T>,
348 rhs: &TypedTensor<T>,
349 dir: CompareDir,
350 backend: &mut impl TensorBackend,
351) -> Result<TypedTensor<bool>> {
352 let (lhs, rhs) = broadcast_binary_read(lhs, rhs, backend)?;
353 let out = backend.with_backend_session(|exec| {
354 exec.compare_read(lhs.tensor_read(), rhs.tensor_read(), &dir)
355 })?;
356 into_typed_result("compare", out)
357}
358
359fn where_select<T: TensorScalar>(
375 condition: &TypedTensor<bool>,
376 on_true: &TypedTensor<T>,
377 on_false: &TypedTensor<T>,
378 backend: &mut impl TensorBackend,
379) -> Result<TypedTensor<T>> {
380 let (condition, on_true, on_false) =
381 broadcast_ternary_read(condition, on_true, on_false, backend)?;
382 let out = backend.with_backend_session(|exec| {
383 exec.select_read(
384 condition.tensor_read(),
385 on_true.tensor_read(),
386 on_false.tensor_read(),
387 )
388 })?;
389 into_typed_result("where_select", out)
390}
391
392fn clamp<T: TensorScalar>(
406 input: &TypedTensor<T>,
407 lower: &TypedTensor<T>,
408 upper: &TypedTensor<T>,
409 backend: &mut impl TensorBackend,
410) -> Result<TypedTensor<T>> {
411 let (input, lower, upper) = broadcast_ternary_read(input, lower, upper, backend)?;
412 let out = backend.with_backend_session(|exec| {
413 exec.clamp_read(
414 input.tensor_read(),
415 lower.tensor_read(),
416 upper.tensor_read(),
417 )
418 })?;
419 into_typed_result("clamp", out)
420}
421
422fn matmul<T: TensorScalar>(
437 a: &TypedTensor<T>,
438 b: &TypedTensor<T>,
439 backend: &mut impl TensorBackend,
440) -> Result<TypedTensor<T>> {
441 let config = matmul_config_for_shapes("matmul", a.shape(), b.shape())?;
442 let out = backend.with_backend_session(|exec| {
443 exec.dot_general_read(T::tensor_read(a), T::tensor_read(b), &config)
444 })?;
445 into_typed_result("matmul", out)
446}
447
448fn reduce_sum<T: TensorScalar>(
465 input: &TypedTensor<T>,
466 axes: &[usize],
467 backend: &mut impl TensorBackend,
468) -> Result<TypedTensor<T>> {
469 let out =
470 backend.with_backend_session(|exec| exec.reduce_sum_read(T::tensor_read(input), axes))?;
471 into_typed_result("reduce_sum", out)
472}
473
474fn reshape<T: TensorScalar>(
487 input: &TypedTensor<T>,
488 shape: &[usize],
489 backend: &mut impl TensorBackend,
490) -> Result<TypedTensor<T>> {
491 let out =
492 backend.with_backend_session(|exec| exec.reshape_read(T::tensor_read(input), shape))?;
493 into_typed_result("reshape", out)
494}
495
496fn transpose<T: TensorScalar>(
509 input: &TypedTensor<T>,
510 perm: &[usize],
511 backend: &mut impl TensorBackend,
512) -> Result<TypedTensor<T>> {
513 let out =
514 backend.with_backend_session(|exec| exec.transpose_read(T::tensor_read(input), perm))?;
515 into_typed_result("transpose", out)
516}
517
518fn broadcast_in_dim<T: TensorScalar>(
534 input: &TypedTensor<T>,
535 shape: &[usize],
536 dims: &[usize],
537 backend: &mut impl TensorBackend,
538) -> Result<TypedTensor<T>> {
539 let out = backend.with_backend_session(|exec| {
540 exec.broadcast_in_dim_read(T::tensor_read(input), shape, dims)
541 })?;
542 into_typed_result("broadcast_in_dim", out)
543}
544
545enum ReadInput<'a> {
546 Borrowed(TensorRead<'a>),
547 Owned(Tensor),
548}
549
550impl ReadInput<'_> {
551 fn tensor_read(&self) -> TensorRead<'_> {
552 match self {
553 Self::Borrowed(read) => read.clone(),
554 Self::Owned(tensor) => TensorRead::from_tensor(tensor),
555 }
556 }
557}
558
559fn broadcast_binary_read<'a, T: TensorScalar>(
560 lhs: &'a TypedTensor<T>,
561 rhs: &'a TypedTensor<T>,
562 backend: &mut impl TensorBackend,
563) -> Result<(ReadInput<'a>, ReadInput<'a>)> {
564 let shape = broadcast_shape(lhs.shape(), rhs.shape()).map_err(broadcast_error)?;
565 Ok((
566 broadcast_to_read(lhs, &shape, backend)?,
567 broadcast_to_read(rhs, &shape, backend)?,
568 ))
569}
570
571fn broadcast_ternary_read<'a, C: TensorScalar, T: TensorScalar>(
572 first: &'a TypedTensor<C>,
573 second: &'a TypedTensor<T>,
574 third: &'a TypedTensor<T>,
575 backend: &mut impl TensorBackend,
576) -> Result<(ReadInput<'a>, ReadInput<'a>, ReadInput<'a>)> {
577 let shape = broadcast_shapes([first.shape(), second.shape(), third.shape()])
578 .map_err(broadcast_error)?;
579 Ok((
580 broadcast_to_read(first, &shape, backend)?,
581 broadcast_to_read(second, &shape, backend)?,
582 broadcast_to_read(third, &shape, backend)?,
583 ))
584}
585
586fn broadcast_to_read<'a, T: TensorScalar>(
587 input: &'a TypedTensor<T>,
588 target_shape: &[usize],
589 backend: &mut impl TensorBackend,
590) -> Result<ReadInput<'a>> {
591 if input.shape() == target_shape {
592 return Ok(ReadInput::Borrowed(T::tensor_read(input)));
593 }
594
595 let plan = broadcast_input_plan(input.shape(), target_shape).map_err(broadcast_error)?;
596 let source = if plan.source_shape == input.shape() {
597 ReadInput::Borrowed(T::tensor_read(input))
598 } else {
599 let reshaped = backend.with_backend_session(|exec| {
600 exec.reshape_read(T::tensor_read(input), &plan.source_shape)
601 })?;
602 ReadInput::Owned(reshaped)
603 };
604 let out = backend.with_backend_session(|exec| {
605 exec.broadcast_in_dim_read(source.tensor_read(), target_shape, &plan.dims)
606 })?;
607 Ok(ReadInput::Owned(out))
608}
609
610fn broadcast_error(err: impl std::fmt::Display) -> Error {
611 Error::backend_failure("broadcast", err.to_string())
612}
613
614fn into_typed_result<T: TensorScalar>(op: &'static str, tensor: Tensor) -> Result<TypedTensor<T>> {
615 let actual = tensor.dtype();
616 T::into_typed(tensor).map_err(|_| Error::DTypeMismatch {
617 op,
618 lhs: T::dtype(),
619 rhs: actual,
620 })
621}