1use std::ops::{Add, Div, Mul, Neg};
2use std::sync::Arc;
3
4use num_complex::Complex;
5use num_traits::{One, Zero};
6use strided_kernel::{
7 batched_outer_product_into, broadcast_mul_into, map_into, mul_into, zip_map2_into,
8 zip_map3_into,
9};
10
11use crate::buffer_pool::{BufferPool, PoolScalar};
12use crate::ConjElem;
13use tenferro_tensor::{
14 col_major_strides, CompareDir, DType, Tensor, TensorOwnedView, TensorRank, TensorRead,
15 TensorValue, TensorView, TypedTensor, TypedTensorView,
16};
17
18use super::{
19 tensor_from_array, typed_array_uninit_from_pool, typed_host_data, typed_view,
20 typed_view_from_view,
21};
22
23macro_rules! dispatch_ternary_result_with_pool {
24 ($op:literal, $a:expr, $b:expr, $c:expr, |$x:ident, $y:ident, $z:ident| $body:expr) => {
25 match ($a, $b, $c) {
26 (Tensor::F32($x), Tensor::F32($y), Tensor::F32($z)) => Ok(Tensor::F32($body?)),
27 (Tensor::F64($x), Tensor::F64($y), Tensor::F64($z)) => Ok(Tensor::F64($body?)),
28 _ => Err(crate::Error::backend_failure($op, "dtype mismatch")),
29 }
30 };
31}
32
33fn dtype_pair_error(op: &'static str, lhs: DType, rhs: DType) -> crate::Error {
34 if lhs == rhs {
35 crate::Error::backend_failure(op, format!("unsupported dtype {lhs:?}"))
36 } else {
37 crate::Error::DTypeMismatch { op, lhs, rhs }
38 }
39}
40
41fn tensor_pair_error(op: &'static str, lhs: &Tensor, rhs: &Tensor) -> crate::Error {
42 dtype_pair_error(op, lhs.dtype(), rhs.dtype())
43}
44
45fn read_pair_error(op: &'static str, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Error {
46 dtype_pair_error(op, lhs.dtype(), rhs.dtype())
47}
48
49fn is_complex_dtype(dtype: DType) -> bool {
50 matches!(dtype, DType::C32 | DType::C64)
51}
52
53fn ordered_complex_error(op: &'static str) -> crate::Error {
54 crate::Error::InvalidConfig {
55 op,
56 message: "complex tensors do not have a total order; compute abs/norm explicitly before ordered operations".into(),
57 }
58}
59
60fn reject_complex_ordered_dtypes(op: &'static str, dtypes: &[DType]) -> crate::Result<()> {
61 if dtypes.iter().copied().any(is_complex_dtype) {
62 return Err(ordered_complex_error(op));
63 }
64 Ok(())
65}
66
67pub(crate) trait Tier2Elem: Copy + Clone + One + Zero + Send + Sync {
68 fn abs_elem(self) -> Self;
69 fn sign_elem(self) -> Self;
70}
71
72pub(crate) trait OrderedElem: Copy + Clone + Send + Sync {
75 fn max_elem(self, other: Self) -> Self;
76 fn min_elem(self, other: Self) -> Self;
77}
78
79pub(crate) trait CompareElem: Copy + Send + Sync {
80 fn compare_elem(self, other: Self, dir: &CompareDir) -> bool;
81}
82
83macro_rules! impl_tier2_elem_real {
84 ($ty:ty) => {
85 impl Tier2Elem for $ty {
86 fn abs_elem(self) -> Self {
87 self.abs()
88 }
89
90 fn sign_elem(self) -> Self {
91 if self == Self::zero() {
92 Self::zero()
93 } else {
94 self.signum()
95 }
96 }
97 }
98
99 impl OrderedElem for $ty {
100 fn max_elem(self, other: Self) -> Self {
101 if self.is_nan() || other.is_nan() {
102 <$ty>::NAN
103 } else if self >= other {
104 self
105 } else {
106 other
107 }
108 }
109
110 fn min_elem(self, other: Self) -> Self {
111 if self.is_nan() || other.is_nan() {
112 <$ty>::NAN
113 } else if self <= other {
114 self
115 } else {
116 other
117 }
118 }
119 }
120
121 impl CompareElem for $ty {
122 fn compare_elem(self, other: Self, dir: &CompareDir) -> bool {
123 match dir {
124 CompareDir::Eq => self == other,
125 CompareDir::Lt => self < other,
126 CompareDir::Le => self <= other,
127 CompareDir::Gt => self > other,
128 CompareDir::Ge => self >= other,
129 }
130 }
131 }
132 };
133}
134
135macro_rules! impl_tier2_elem_complex {
136 ($real:ty) => {
137 impl Tier2Elem for Complex<$real> {
138 fn abs_elem(self) -> Self {
139 Self::new(self.norm(), <$real>::zero())
140 }
141
142 fn sign_elem(self) -> Self {
143 if self.is_zero() {
144 Self::zero()
145 } else {
146 self / self.abs_elem()
147 }
148 }
149 }
150 };
151}
152
153impl_tier2_elem_real!(f32);
154impl_tier2_elem_real!(f64);
155impl_tier2_elem_complex!(f32);
156impl_tier2_elem_complex!(f64);
157
158macro_rules! impl_compare_elem_ord {
159 ($ty:ty) => {
160 impl CompareElem for $ty {
161 fn compare_elem(self, other: Self, dir: &CompareDir) -> bool {
162 match dir {
163 CompareDir::Eq => self == other,
164 CompareDir::Lt => self < other,
165 CompareDir::Le => self <= other,
166 CompareDir::Gt => self > other,
167 CompareDir::Ge => self >= other,
168 }
169 }
170 }
171 };
172}
173
174impl_compare_elem_ord!(i32);
175impl_compare_elem_ord!(i64);
176impl_compare_elem_ord!(bool);
177
178fn complex_scalar_tensor<T>(scalar: T) -> crate::Result<TypedTensor<Complex<T>>>
179where
180 T: Copy + Clone + Zero,
181{
182 TypedTensor::from_vec_col_major(vec![], vec![Complex::new(scalar, T::zero())])
183}
184
185fn complex_scalar_tensor_from_tensor<T>(
186 input: &TypedTensor<T>,
187) -> crate::Result<TypedTensor<Complex<T>>>
188where
189 T: Copy + Clone + Zero,
190{
191 complex_scalar_tensor(typed_host_data("add", input)?[0])
192}
193
194fn complex_scalar_tensor_from_view<T, R>(
195 input: &TypedTensorView<'_, T, R>,
196) -> crate::Result<TypedTensor<Complex<T>>>
197where
198 T: Copy + Clone + Zero + 'static,
199 R: TensorRank,
200{
201 complex_scalar_tensor(typed_view_from_view("add", input)?.get(&[]))
202}
203
204fn with_local_pool<T>(f: impl FnOnce(&mut BufferPool) -> T) -> T {
205 let mut buffers = BufferPool::new();
206 f(&mut buffers)
207}
208
209pub fn add(lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
224 with_local_pool(|buffers| add_with_pool(buffers, lhs, rhs))
225}
226
227pub(crate) fn add_with_pool(
228 buffers: &mut BufferPool,
229 lhs: &Tensor,
230 rhs: &Tensor,
231) -> crate::Result<Tensor> {
232 match (lhs, rhs) {
233 (Tensor::F32(a), Tensor::F32(b)) => Ok(Tensor::F32(typed_add_with_pool(buffers, a, b)?)),
234 (Tensor::F64(a), Tensor::F64(b)) => Ok(Tensor::F64(typed_add_with_pool(buffers, a, b)?)),
235 (Tensor::I32(a), Tensor::I32(b)) => Ok(Tensor::I32(typed_add_with_pool(buffers, a, b)?)),
236 (Tensor::I64(a), Tensor::I64(b)) => Ok(Tensor::I64(typed_add_with_pool(buffers, a, b)?)),
237 (Tensor::C32(a), Tensor::C32(b)) => Ok(Tensor::C32(typed_add_with_pool(buffers, a, b)?)),
238 (Tensor::C64(a), Tensor::C64(b)) => Ok(Tensor::C64(typed_add_with_pool(buffers, a, b)?)),
239 (Tensor::F32(a), Tensor::C32(b)) if a.shape().is_empty() => {
240 let scalar = complex_scalar_tensor(typed_host_data("add", a)?[0])?;
241 Ok(Tensor::C32(typed_add_with_pool(buffers, &scalar, b)?))
242 }
243 (Tensor::C32(a), Tensor::F32(b)) if b.shape().is_empty() => {
244 let scalar = complex_scalar_tensor(typed_host_data("add", b)?[0])?;
245 Ok(Tensor::C32(typed_add_with_pool(buffers, a, &scalar)?))
246 }
247 (Tensor::F64(a), Tensor::C64(b)) if a.shape().is_empty() => {
248 let scalar = complex_scalar_tensor(typed_host_data("add", a)?[0])?;
249 Ok(Tensor::C64(typed_add_with_pool(buffers, &scalar, b)?))
250 }
251 (Tensor::C64(a), Tensor::F64(b)) if b.shape().is_empty() => {
252 let scalar = complex_scalar_tensor(typed_host_data("add", b)?[0])?;
253 Ok(Tensor::C64(typed_add_with_pool(buffers, a, &scalar)?))
254 }
255 _ => Err(tensor_pair_error("add", lhs, rhs)),
256 }
257}
258
259pub(crate) fn add_read_with_pool(
260 buffers: &mut BufferPool,
261 lhs: TensorRead<'_>,
262 rhs: TensorRead<'_>,
263) -> crate::Result<Tensor> {
264 if let (TensorRead::Tensor(lhs), TensorRead::Tensor(rhs)) = (&lhs, &rhs) {
265 return add_with_pool(buffers, lhs, rhs);
266 }
267
268 macro_rules! dispatch {
269 ($variant:ident) => {
270 match (&lhs, &rhs) {
271 (
272 TensorRead::Tensor(Tensor::$variant(a)),
273 TensorRead::View(TensorView::$variant(b)),
274 ) => {
275 let a = a.as_view();
276 return Ok(Tensor::$variant(typed_add_view_with_pool(buffers, &a, b)?));
277 }
278 (
279 TensorRead::View(TensorView::$variant(a)),
280 TensorRead::Tensor(Tensor::$variant(b)),
281 ) => {
282 let b = b.as_view();
283 return Ok(Tensor::$variant(typed_add_view_with_pool(buffers, a, &b)?));
284 }
285 (
286 TensorRead::View(TensorView::$variant(a)),
287 TensorRead::View(TensorView::$variant(b)),
288 ) => {
289 return Ok(Tensor::$variant(typed_add_view_with_pool(buffers, a, b)?));
290 }
291 _ => {}
292 }
293 };
294 }
295
296 macro_rules! dispatch_real_complex_scalar {
297 ($real_variant:ident, $complex_variant:ident) => {
298 match (&lhs, &rhs) {
299 (
300 TensorRead::Tensor(Tensor::$real_variant(real)),
301 TensorRead::View(TensorView::$complex_variant(complex)),
302 ) if real.shape().is_empty() => {
303 let scalar = complex_scalar_tensor_from_tensor(real)?;
304 let scalar = scalar.as_view();
305 return Ok(Tensor::$complex_variant(typed_add_view_with_pool(
306 buffers, &scalar, complex,
307 )?));
308 }
309 (
310 TensorRead::View(TensorView::$real_variant(real)),
311 TensorRead::Tensor(Tensor::$complex_variant(complex)),
312 ) if real.shape().is_empty() => {
313 let scalar = complex_scalar_tensor_from_view(real)?;
314 let scalar = scalar.as_view();
315 let complex = complex.as_view();
316 return Ok(Tensor::$complex_variant(typed_add_view_with_pool(
317 buffers, &scalar, &complex,
318 )?));
319 }
320 (
321 TensorRead::View(TensorView::$real_variant(real)),
322 TensorRead::View(TensorView::$complex_variant(complex)),
323 ) if real.shape().is_empty() => {
324 let scalar = complex_scalar_tensor_from_view(real)?;
325 let scalar = scalar.as_view();
326 return Ok(Tensor::$complex_variant(typed_add_view_with_pool(
327 buffers, &scalar, complex,
328 )?));
329 }
330 (
331 TensorRead::Tensor(Tensor::$complex_variant(complex)),
332 TensorRead::View(TensorView::$real_variant(real)),
333 ) if real.shape().is_empty() => {
334 let complex = complex.as_view();
335 let scalar = complex_scalar_tensor_from_view(real)?;
336 let scalar = scalar.as_view();
337 return Ok(Tensor::$complex_variant(typed_add_view_with_pool(
338 buffers, &complex, &scalar,
339 )?));
340 }
341 (
342 TensorRead::View(TensorView::$complex_variant(complex)),
343 TensorRead::Tensor(Tensor::$real_variant(real)),
344 ) if real.shape().is_empty() => {
345 let scalar = complex_scalar_tensor_from_tensor(real)?;
346 let scalar = scalar.as_view();
347 return Ok(Tensor::$complex_variant(typed_add_view_with_pool(
348 buffers, complex, &scalar,
349 )?));
350 }
351 (
352 TensorRead::View(TensorView::$complex_variant(complex)),
353 TensorRead::View(TensorView::$real_variant(real)),
354 ) if real.shape().is_empty() => {
355 let scalar = complex_scalar_tensor_from_view(real)?;
356 let scalar = scalar.as_view();
357 return Ok(Tensor::$complex_variant(typed_add_view_with_pool(
358 buffers, complex, &scalar,
359 )?));
360 }
361 _ => {}
362 }
363 };
364 }
365
366 dispatch_real_complex_scalar!(F32, C32);
367 dispatch_real_complex_scalar!(F64, C64);
368
369 dispatch!(F32);
370 dispatch!(F64);
371 dispatch!(I32);
372 dispatch!(I64);
373 dispatch!(C32);
374 dispatch!(C64);
375
376 Err(read_pair_error("add", lhs, rhs))
377}
378
379pub fn mul(lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
394 with_local_pool(|buffers| mul_with_pool(buffers, lhs, rhs))
395}
396
397fn binary_read_with_pool(
398 op: &'static str,
399 buffers: &mut BufferPool,
400 lhs: TensorRead<'_>,
401 rhs: TensorRead<'_>,
402 f: impl FnOnce(&mut BufferPool, &Tensor, &Tensor) -> crate::Result<Tensor>,
403) -> crate::Result<Tensor> {
404 if let (Some(lhs), Some(rhs)) = (lhs.as_tensor(), rhs.as_tensor()) {
405 return f(buffers, lhs, rhs);
406 }
407
408 Err(read_pair_error(op, lhs, rhs))
409}
410
411pub(crate) fn mul_with_pool(
412 buffers: &mut BufferPool,
413 lhs: &Tensor,
414 rhs: &Tensor,
415) -> crate::Result<Tensor> {
416 match (lhs, rhs) {
417 (Tensor::F32(a), Tensor::F32(b)) => Ok(Tensor::F32(typed_mul_with_pool(buffers, a, b)?)),
418 (Tensor::F64(a), Tensor::F64(b)) => Ok(Tensor::F64(typed_mul_with_pool(buffers, a, b)?)),
419 (Tensor::I32(a), Tensor::I32(b)) => Ok(Tensor::I32(typed_mul_with_pool(buffers, a, b)?)),
420 (Tensor::I64(a), Tensor::I64(b)) => Ok(Tensor::I64(typed_mul_with_pool(buffers, a, b)?)),
421 (Tensor::C32(a), Tensor::C32(b)) => Ok(Tensor::C32(typed_mul_with_pool(buffers, a, b)?)),
422 (Tensor::C64(a), Tensor::C64(b)) => Ok(Tensor::C64(typed_mul_with_pool(buffers, a, b)?)),
423 (Tensor::F32(a), Tensor::C32(b)) if a.shape().is_empty() => {
424 let scalar = complex_scalar_tensor(typed_host_data("mul", a)?[0])?;
425 Ok(Tensor::C32(typed_mul_with_pool(buffers, &scalar, b)?))
426 }
427 (Tensor::C32(a), Tensor::F32(b)) if b.shape().is_empty() => {
428 let scalar = complex_scalar_tensor(typed_host_data("mul", b)?[0])?;
429 Ok(Tensor::C32(typed_mul_with_pool(buffers, a, &scalar)?))
430 }
431 (Tensor::F64(a), Tensor::C64(b)) if a.shape().is_empty() => {
432 let scalar = complex_scalar_tensor(typed_host_data("mul", a)?[0])?;
433 Ok(Tensor::C64(typed_mul_with_pool(buffers, &scalar, b)?))
434 }
435 (Tensor::C64(a), Tensor::F64(b)) if b.shape().is_empty() => {
436 let scalar = complex_scalar_tensor(typed_host_data("mul", b)?[0])?;
437 Ok(Tensor::C64(typed_mul_with_pool(buffers, a, &scalar)?))
438 }
439 _ => Err(tensor_pair_error("mul", lhs, rhs)),
440 }
441}
442
443pub(crate) fn mul_read_with_pool(
444 buffers: &mut BufferPool,
445 lhs: TensorRead<'_>,
446 rhs: TensorRead<'_>,
447) -> crate::Result<Tensor> {
448 if let (TensorRead::Tensor(lhs), TensorRead::Tensor(rhs)) = (&lhs, &rhs) {
449 return mul_with_pool(buffers, lhs, rhs);
450 }
451
452 macro_rules! dispatch {
453 ($variant:ident) => {
454 match (&lhs, &rhs) {
455 (
456 TensorRead::Tensor(Tensor::$variant(a)),
457 TensorRead::View(TensorView::$variant(b)),
458 ) => {
459 let a = a.as_view();
460 return Ok(Tensor::$variant(typed_mul_view_with_pool(buffers, &a, b)?));
461 }
462 (
463 TensorRead::View(TensorView::$variant(a)),
464 TensorRead::Tensor(Tensor::$variant(b)),
465 ) => {
466 let b = b.as_view();
467 return Ok(Tensor::$variant(typed_mul_view_with_pool(buffers, a, &b)?));
468 }
469 (
470 TensorRead::View(TensorView::$variant(a)),
471 TensorRead::View(TensorView::$variant(b)),
472 ) => {
473 return Ok(Tensor::$variant(typed_mul_view_with_pool(buffers, a, b)?));
474 }
475 _ => {}
476 }
477 };
478 }
479
480 macro_rules! dispatch_real_complex_scalar {
481 ($real_variant:ident, $complex_variant:ident) => {
482 match (&lhs, &rhs) {
483 (
484 TensorRead::Tensor(Tensor::$real_variant(real)),
485 TensorRead::View(TensorView::$complex_variant(complex)),
486 ) if real.shape().is_empty() => {
487 let scalar = complex_scalar_tensor_from_tensor(real)?;
488 let scalar = scalar.as_view();
489 return Ok(Tensor::$complex_variant(typed_mul_view_with_pool(
490 buffers, &scalar, complex,
491 )?));
492 }
493 (
494 TensorRead::View(TensorView::$real_variant(real)),
495 TensorRead::Tensor(Tensor::$complex_variant(complex)),
496 ) if real.shape().is_empty() => {
497 let scalar = complex_scalar_tensor_from_view(real)?;
498 let scalar = scalar.as_view();
499 let complex = complex.as_view();
500 return Ok(Tensor::$complex_variant(typed_mul_view_with_pool(
501 buffers, &scalar, &complex,
502 )?));
503 }
504 (
505 TensorRead::View(TensorView::$real_variant(real)),
506 TensorRead::View(TensorView::$complex_variant(complex)),
507 ) if real.shape().is_empty() => {
508 let scalar = complex_scalar_tensor_from_view(real)?;
509 let scalar = scalar.as_view();
510 return Ok(Tensor::$complex_variant(typed_mul_view_with_pool(
511 buffers, &scalar, complex,
512 )?));
513 }
514 (
515 TensorRead::Tensor(Tensor::$complex_variant(complex)),
516 TensorRead::View(TensorView::$real_variant(real)),
517 ) if real.shape().is_empty() => {
518 let complex = complex.as_view();
519 let scalar = complex_scalar_tensor_from_view(real)?;
520 let scalar = scalar.as_view();
521 return Ok(Tensor::$complex_variant(typed_mul_view_with_pool(
522 buffers, &complex, &scalar,
523 )?));
524 }
525 (
526 TensorRead::View(TensorView::$complex_variant(complex)),
527 TensorRead::Tensor(Tensor::$real_variant(real)),
528 ) if real.shape().is_empty() => {
529 let scalar = complex_scalar_tensor_from_tensor(real)?;
530 let scalar = scalar.as_view();
531 return Ok(Tensor::$complex_variant(typed_mul_view_with_pool(
532 buffers, complex, &scalar,
533 )?));
534 }
535 (
536 TensorRead::View(TensorView::$complex_variant(complex)),
537 TensorRead::View(TensorView::$real_variant(real)),
538 ) if real.shape().is_empty() => {
539 let scalar = complex_scalar_tensor_from_view(real)?;
540 let scalar = scalar.as_view();
541 return Ok(Tensor::$complex_variant(typed_mul_view_with_pool(
542 buffers, complex, &scalar,
543 )?));
544 }
545 _ => {}
546 }
547 };
548 }
549
550 dispatch_real_complex_scalar!(F32, C32);
551 dispatch_real_complex_scalar!(F64, C64);
552
553 dispatch!(F32);
554 dispatch!(F64);
555 dispatch!(I32);
556 dispatch!(I64);
557 dispatch!(C32);
558 dispatch!(C64);
559
560 binary_read_with_pool("mul", buffers, lhs, rhs, mul_with_pool)
561}
562
563enum CpuReadView<'a> {
564 F32(TypedTensorView<'a, f32>),
565 F64(TypedTensorView<'a, f64>),
566 I32(TypedTensorView<'a, i32>),
567 I64(TypedTensorView<'a, i64>),
568 Bool(TypedTensorView<'a, bool>),
569 C32(TypedTensorView<'a, Complex<f32>>),
570 C64(TypedTensorView<'a, Complex<f64>>),
571}
572
573fn read_as_cpu_view(input: TensorRead<'_>) -> CpuReadView<'_> {
574 match input {
575 TensorRead::Tensor(Tensor::F32(tensor)) => CpuReadView::F32(tensor.as_view()),
576 TensorRead::Tensor(Tensor::F64(tensor)) => CpuReadView::F64(tensor.as_view()),
577 TensorRead::Tensor(Tensor::I32(tensor)) => CpuReadView::I32(tensor.as_view()),
578 TensorRead::Tensor(Tensor::I64(tensor)) => CpuReadView::I64(tensor.as_view()),
579 TensorRead::Tensor(Tensor::Bool(tensor)) => CpuReadView::Bool(tensor.as_view()),
580 TensorRead::Tensor(Tensor::C32(tensor)) => CpuReadView::C32(tensor.as_view()),
581 TensorRead::Tensor(Tensor::C64(tensor)) => CpuReadView::C64(tensor.as_view()),
582 TensorRead::View(TensorView::F32(view)) => CpuReadView::F32(view),
583 TensorRead::View(TensorView::F64(view)) => CpuReadView::F64(view),
584 TensorRead::View(TensorView::I32(view)) => CpuReadView::I32(view),
585 TensorRead::View(TensorView::I64(view)) => CpuReadView::I64(view),
586 TensorRead::View(TensorView::Bool(view)) => CpuReadView::Bool(view),
587 TensorRead::View(TensorView::C32(view)) => CpuReadView::C32(view),
588 TensorRead::View(TensorView::C64(view)) => CpuReadView::C64(view),
589 }
590}
591
592fn typed_binary_view_with_pool<T, L, R>(
593 op: &'static str,
594 buffers: &mut BufferPool,
595 lhs: &TypedTensorView<'_, T, L>,
596 rhs: &TypedTensorView<'_, T, R>,
597 f: impl Fn(T, T) -> T + Copy + Sync,
598) -> crate::Result<TypedTensor<T>>
599where
600 T: Copy + PoolScalar + 'static,
601 L: TensorRank,
602 R: TensorRank,
603{
604 if lhs.shape() == rhs.shape() {
605 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
607 zip_map2_into(
608 &mut out.view_mut(),
609 &typed_view_from_view(op, lhs)?,
610 &typed_view_from_view(op, rhs)?,
611 f,
612 )
613 .map_err(|err| crate::Error::backend_failure(op, err))?;
614 Ok(tensor_from_array(out))
615 } else if lhs.shape().is_empty() {
616 let scalar = typed_view_from_view(op, lhs)?.get(&[]);
617 let mut out = unsafe { typed_array_uninit_from_pool(buffers, rhs.shape()) }?;
619 map_into(&mut out.view_mut(), &typed_view_from_view(op, rhs)?, |x| {
620 f(scalar, x)
621 })
622 .map_err(|err| crate::Error::backend_failure(op, err))?;
623 Ok(tensor_from_array(out))
624 } else if rhs.shape().is_empty() {
625 let scalar = typed_view_from_view(op, rhs)?.get(&[]);
626 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
628 map_into(&mut out.view_mut(), &typed_view_from_view(op, lhs)?, |x| {
629 f(x, scalar)
630 })
631 .map_err(|err| crate::Error::backend_failure(op, err))?;
632 Ok(tensor_from_array(out))
633 } else {
634 Err(crate::Error::ShapeMismatch {
635 op,
636 lhs: lhs.shape().to_vec(),
637 rhs: rhs.shape().to_vec(),
638 })
639 }
640}
641
642fn typed_unary_view_with_pool<T, R>(
643 op: &'static str,
644 buffers: &mut BufferPool,
645 input: &TypedTensorView<'_, T, R>,
646 f: impl Fn(T) -> T + Copy + Sync,
647) -> crate::Result<TypedTensor<T>>
648where
649 T: Copy + PoolScalar + 'static,
650 R: TensorRank,
651{
652 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
654 map_into(&mut out.view_mut(), &typed_view_from_view(op, input)?, f)
655 .map_err(|err| crate::Error::backend_failure(op, err))?;
656 Ok(tensor_from_array(out))
657}
658
659fn typed_same_shape_binary_view_with_pool<T, O, L, R>(
660 op: &'static str,
661 buffers: &mut BufferPool,
662 lhs: &TypedTensorView<'_, T, L>,
663 rhs: &TypedTensorView<'_, T, R>,
664 f: impl Fn(T, T) -> O + Copy + Sync,
665) -> crate::Result<TypedTensor<O>>
666where
667 T: Copy + Send + Sync + 'static,
668 O: Copy + PoolScalar,
669 L: TensorRank,
670 R: TensorRank,
671{
672 if lhs.shape() != rhs.shape() {
673 return Err(crate::Error::ShapeMismatch {
674 op,
675 lhs: lhs.shape().to_vec(),
676 rhs: rhs.shape().to_vec(),
677 });
678 }
679 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
681 zip_map2_into(
682 &mut out.view_mut(),
683 &typed_view_from_view(op, lhs)?,
684 &typed_view_from_view(op, rhs)?,
685 f,
686 )
687 .map_err(|err| crate::Error::backend_failure(op, err))?;
688 Ok(tensor_from_array(out))
689}
690
691fn typed_select_view_with_pool<T, P, A, B>(
692 buffers: &mut BufferPool,
693 pred: &TypedTensorView<'_, bool, P>,
694 on_true: &TypedTensorView<'_, T, A>,
695 on_false: &TypedTensorView<'_, T, B>,
696) -> crate::Result<TypedTensor<T>>
697where
698 T: Copy + PoolScalar + 'static,
699 P: TensorRank,
700 A: TensorRank,
701 B: TensorRank,
702{
703 if pred.shape() != on_true.shape() {
704 return Err(crate::Error::ShapeMismatch {
705 op: "select",
706 lhs: pred.shape().to_vec(),
707 rhs: on_true.shape().to_vec(),
708 });
709 }
710 if pred.shape() != on_false.shape() {
711 return Err(crate::Error::ShapeMismatch {
712 op: "select",
713 lhs: pred.shape().to_vec(),
714 rhs: on_false.shape().to_vec(),
715 });
716 }
717 let mut out = unsafe { typed_array_uninit_from_pool(buffers, pred.shape()) }?;
719 zip_map3_into(
720 &mut out.view_mut(),
721 &typed_view_from_view("select", pred)?,
722 &typed_view_from_view("select", on_true)?,
723 &typed_view_from_view("select", on_false)?,
724 |p, t, f| if p { t } else { f },
725 )
726 .map_err(|err| crate::Error::backend_failure("select", err))?;
727 Ok(tensor_from_array(out))
728}
729
730fn typed_clamp_view_with_pool<T, I, L, U>(
731 buffers: &mut BufferPool,
732 input: &TypedTensorView<'_, T, I>,
733 lower: &TypedTensorView<'_, T, L>,
734 upper: &TypedTensorView<'_, T, U>,
735) -> crate::Result<TypedTensor<T>>
736where
737 T: OrderedElem + PoolScalar + 'static,
738 I: TensorRank,
739 L: TensorRank,
740 U: TensorRank,
741{
742 if input.shape() != lower.shape() {
743 return Err(crate::Error::ShapeMismatch {
744 op: "clamp",
745 lhs: input.shape().to_vec(),
746 rhs: lower.shape().to_vec(),
747 });
748 }
749 if input.shape() != upper.shape() {
750 return Err(crate::Error::ShapeMismatch {
751 op: "clamp",
752 lhs: input.shape().to_vec(),
753 rhs: upper.shape().to_vec(),
754 });
755 }
756 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
758 zip_map3_into(
759 &mut out.view_mut(),
760 &typed_view_from_view("clamp", input)?,
761 &typed_view_from_view("clamp", lower)?,
762 &typed_view_from_view("clamp", upper)?,
763 |x, lo, hi| hi.min_elem(lo.max_elem(x)),
764 )
765 .map_err(|err| crate::Error::backend_failure("clamp", err))?;
766 Ok(tensor_from_array(out))
767}
768
769#[derive(Clone, Copy)]
770enum SplitOuterProductLayout {
771 LhsPrefix,
772 RhsPrefix,
773}
774
775struct SplitOuterProductPlan {
776 #[allow(dead_code)]
777 rows: usize,
778 #[allow(dead_code)]
779 cols: usize,
780 #[allow(dead_code)]
781 batches: usize,
782 layout: SplitOuterProductLayout,
783 lhs_free_axes: Vec<usize>,
784 rhs_free_axes: Vec<usize>,
785 lhs_batch_axes: Vec<usize>,
786 rhs_batch_axes: Vec<usize>,
787}
788
789struct OuterProductAxisPartition {
790 lhs_free_output_axes: Vec<usize>,
791 rhs_free_output_axes: Vec<usize>,
792 batch_output_axes: Vec<usize>,
793 lhs_free_axes: Vec<usize>,
794 rhs_free_axes: Vec<usize>,
795 lhs_batch_axes: Vec<usize>,
796 rhs_batch_axes: Vec<usize>,
797}
798
799fn shape_matches_dims(source_shape: &[usize], output_shape: &[usize], dims: &[usize]) -> bool {
800 source_shape.len() == dims.len()
801 && source_shape
802 .iter()
803 .zip(dims.iter())
804 .all(|(&dim, &axis)| output_shape.get(axis).copied() == Some(dim))
805}
806
807fn axes_by_output(dims: &[usize], output_rank: usize) -> Option<Vec<Option<usize>>> {
808 let mut axes = vec![None; output_rank];
809 for (src_axis, &dst_axis) in dims.iter().enumerate() {
810 let slot = axes.get_mut(dst_axis)?;
811 if slot.replace(src_axis).is_some() {
812 return None;
813 }
814 }
815 Some(axes)
816}
817
818fn axes_shape_product<T>(
819 op: &'static str,
820 view: &TypedTensorView<'_, T>,
821 axes: &[usize],
822) -> crate::Result<usize>
823where
824 T: 'static,
825{
826 axes.iter().try_fold(1usize, |acc, &axis| {
827 acc.checked_mul(view.shape()[axis])
828 .ok_or_else(|| crate::Error::backend_failure(op, "shape size overflows usize"))
829 })
830}
831
832fn classify_outer_product_axes(
833 lhs_dims: &[usize],
834 rhs_dims: &[usize],
835 output_rank: usize,
836) -> Option<OuterProductAxisPartition> {
837 let lhs_axes_by_output = axes_by_output(lhs_dims, output_rank)?;
838 let rhs_axes_by_output = axes_by_output(rhs_dims, output_rank)?;
839
840 let mut lhs_free_output_axes = Vec::new();
841 let mut rhs_free_output_axes = Vec::new();
842 let mut batch_output_axes = Vec::new();
843 let mut lhs_free_axes = Vec::new();
844 let mut rhs_free_axes = Vec::new();
845 let mut lhs_batch_axes = Vec::new();
846 let mut rhs_batch_axes = Vec::new();
847
848 for output_axis in 0..output_rank {
849 match (
850 lhs_axes_by_output[output_axis],
851 rhs_axes_by_output[output_axis],
852 ) {
853 (Some(lhs_axis), Some(rhs_axis)) => {
854 batch_output_axes.push(output_axis);
855 lhs_batch_axes.push(lhs_axis);
856 rhs_batch_axes.push(rhs_axis);
857 }
858 (Some(lhs_axis), None) => {
859 lhs_free_output_axes.push(output_axis);
860 lhs_free_axes.push(lhs_axis);
861 }
862 (None, Some(rhs_axis)) => {
863 rhs_free_output_axes.push(output_axis);
864 rhs_free_axes.push(rhs_axis);
865 }
866 (None, None) => return None,
867 }
868 }
869
870 Some(OuterProductAxisPartition {
871 lhs_free_output_axes,
872 rhs_free_output_axes,
873 batch_output_axes,
874 lhs_free_axes,
875 rhs_free_axes,
876 lhs_batch_axes,
877 rhs_batch_axes,
878 })
879}
880
881fn output_axes_match_partition(output_rank: usize, groups: &[&[usize]]) -> bool {
882 groups
883 .iter()
884 .flat_map(|group| group.iter().copied())
885 .eq(0..output_rank)
886}
887
888fn split_outer_product_plan<T>(
889 lhs: &TypedTensorView<'_, T>,
890 lhs_shape: &[usize],
891 lhs_dims: &[usize],
892 rhs: &TypedTensorView<'_, T>,
893 rhs_shape: &[usize],
894 rhs_dims: &[usize],
895) -> crate::Result<Option<SplitOuterProductPlan>>
896where
897 T: 'static,
898{
899 let output_rank = lhs_shape.len();
900 if lhs_shape != rhs_shape
901 || !shape_matches_dims(lhs.shape(), lhs_shape, lhs_dims)
902 || !shape_matches_dims(rhs.shape(), rhs_shape, rhs_dims)
903 || lhs.backend_buffer().is_some()
904 || rhs.backend_buffer().is_some()
905 || lhs.offset() < 0
906 || rhs.offset() < 0
907 || lhs.strides().iter().any(|&stride| stride < 0)
908 || rhs.strides().iter().any(|&stride| stride < 0)
909 {
910 return Ok(None);
911 }
912
913 let Some(partition) = classify_outer_product_axes(lhs_dims, rhs_dims, output_rank) else {
914 return Ok(None);
915 };
916
917 let lhs_free_size = axes_shape_product("broadcast_multiply", lhs, &partition.lhs_free_axes)?;
918 let rhs_free_size = axes_shape_product("broadcast_multiply", rhs, &partition.rhs_free_axes)?;
919 if lhs_free_size <= 1 || rhs_free_size <= 1 {
920 return Ok(None);
921 }
922 let batches = axes_shape_product("broadcast_multiply", lhs, &partition.lhs_batch_axes)?;
923
924 let lhs_prefix = output_axes_match_partition(
925 output_rank,
926 &[
927 &partition.lhs_free_output_axes,
928 &partition.rhs_free_output_axes,
929 &partition.batch_output_axes,
930 ],
931 );
932 if lhs_prefix {
933 return Ok(Some(SplitOuterProductPlan {
934 rows: lhs_free_size,
935 cols: rhs_free_size,
936 batches,
937 layout: SplitOuterProductLayout::LhsPrefix,
938 lhs_free_axes: partition.lhs_free_axes,
939 rhs_free_axes: partition.rhs_free_axes,
940 lhs_batch_axes: partition.lhs_batch_axes,
941 rhs_batch_axes: partition.rhs_batch_axes,
942 }));
943 }
944
945 let rhs_prefix = output_axes_match_partition(
946 output_rank,
947 &[
948 &partition.rhs_free_output_axes,
949 &partition.lhs_free_output_axes,
950 &partition.batch_output_axes,
951 ],
952 );
953 if rhs_prefix {
954 return Ok(Some(SplitOuterProductPlan {
955 rows: rhs_free_size,
956 cols: lhs_free_size,
957 batches,
958 layout: SplitOuterProductLayout::RhsPrefix,
959 lhs_free_axes: partition.lhs_free_axes,
960 rhs_free_axes: partition.rhs_free_axes,
961 lhs_batch_axes: partition.lhs_batch_axes,
962 rhs_batch_axes: partition.rhs_batch_axes,
963 }));
964 }
965
966 Ok(None)
967}
968
969fn try_outer_product_with_pool<T>(
970 buffers: &mut BufferPool,
971 lhs: &TypedTensorView<'_, T>,
972 lhs_shape: &[usize],
973 lhs_dims: &[usize],
974 rhs: &TypedTensorView<'_, T>,
975 rhs_shape: &[usize],
976 rhs_dims: &[usize],
977) -> crate::Result<Option<TypedTensor<T>>>
978where
979 T: Copy + Clone + Mul<Output = T> + PoolScalar + 'static,
980{
981 let Some(plan) = split_outer_product_plan(lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims)?
982 else {
983 return Ok(None);
984 };
985
986 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs_shape) }?;
988 let lhs_view = typed_view_from_view("broadcast_multiply", lhs)?;
989 let rhs_view = typed_view_from_view("broadcast_multiply", rhs)?;
990 match plan.layout {
991 SplitOuterProductLayout::LhsPrefix => {
992 let lhs_perm: Vec<_> = plan
993 .lhs_free_axes
994 .iter()
995 .chain(plan.lhs_batch_axes.iter())
996 .copied()
997 .collect();
998 let rhs_perm: Vec<_> = plan
999 .rhs_free_axes
1000 .iter()
1001 .chain(plan.rhs_batch_axes.iter())
1002 .copied()
1003 .collect();
1004 let lhs_outer = lhs_view
1005 .permute(&lhs_perm)
1006 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1007 let rhs_outer = rhs_view
1008 .permute(&rhs_perm)
1009 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1010 batched_outer_product_into(
1011 &mut out.view_mut(),
1012 &lhs_outer,
1013 &rhs_outer,
1014 plan.lhs_free_axes.len(),
1015 plan.rhs_free_axes.len(),
1016 )
1017 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1018 }
1019 SplitOuterProductLayout::RhsPrefix => {
1020 let lhs_perm: Vec<_> = plan
1021 .lhs_free_axes
1022 .iter()
1023 .chain(plan.lhs_batch_axes.iter())
1024 .copied()
1025 .collect();
1026 let rhs_perm: Vec<_> = plan
1027 .rhs_free_axes
1028 .iter()
1029 .chain(plan.rhs_batch_axes.iter())
1030 .copied()
1031 .collect();
1032 let lhs_outer = lhs_view
1033 .permute(&lhs_perm)
1034 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1035 let rhs_outer = rhs_view
1036 .permute(&rhs_perm)
1037 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1038 batched_outer_product_into(
1039 &mut out.view_mut(),
1040 &rhs_outer,
1041 &lhs_outer,
1042 plan.rhs_free_axes.len(),
1043 plan.lhs_free_axes.len(),
1044 )
1045 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1046 }
1047 }
1048 Ok(Some(tensor_from_array(out)))
1049}
1050
1051struct LazyOuterProduct<T> {
1052 base: TypedTensor<T>,
1053 shape: Vec<usize>,
1054 strides: Vec<isize>,
1055}
1056
1057fn axes_by_physical_stride<T>(view: &TypedTensorView<'_, T>, axes: &[usize]) -> Vec<usize>
1058where
1059 T: 'static,
1060{
1061 let mut sorted = axes.to_vec();
1062 sorted.sort_by(|&lhs_axis, &rhs_axis| {
1063 view.strides()[lhs_axis]
1064 .cmp(&view.strides()[rhs_axis])
1065 .then_with(|| lhs_axis.cmp(&rhs_axis))
1066 });
1067 sorted
1068}
1069
1070fn append_axis_shapes<T>(shape: &mut Vec<usize>, view: &TypedTensorView<'_, T>, axes: &[usize])
1071where
1072 T: 'static,
1073{
1074 shape.extend(axes.iter().map(|&axis| view.shape()[axis]));
1075}
1076
1077fn set_lazy_stride(
1078 logical_strides: &mut [Option<isize>],
1079 output_axis: usize,
1080 stride: isize,
1081) -> crate::Result<()> {
1082 let rank = logical_strides.len();
1083 let slot = logical_strides
1084 .get_mut(output_axis)
1085 .ok_or(crate::Error::AxisOutOfBounds {
1086 op: "broadcast_multiply",
1087 axis: output_axis,
1088 rank,
1089 })?;
1090 if slot.replace(stride).is_some() {
1091 return Err(crate::Error::DuplicateAxis {
1092 op: "broadcast_multiply",
1093 axis: output_axis,
1094 role: "lazy output layout",
1095 });
1096 }
1097 Ok(())
1098}
1099
1100struct LazyOuterProductStrideSpec<'a> {
1101 output_shape: &'a [usize],
1102 base_shape: &'a [usize],
1103 leading_axes: &'a [usize],
1104 leading_dims: &'a [usize],
1105 trailing_axes: &'a [usize],
1106 trailing_dims: &'a [usize],
1107 lhs_batch_axes: &'a [usize],
1108 rhs_batch_axes: &'a [usize],
1109 lhs_dims: &'a [usize],
1110 rhs_dims: &'a [usize],
1111}
1112
1113fn lazy_outer_product_strides(spec: LazyOuterProductStrideSpec<'_>) -> crate::Result<Vec<isize>> {
1114 let base_strides = col_major_strides(spec.base_shape)?;
1115 let mut logical_strides = vec![None; spec.output_shape.len()];
1116 let mut base_axis = 0usize;
1117
1118 for &axis in spec.leading_axes {
1119 set_lazy_stride(
1120 &mut logical_strides,
1121 spec.leading_dims[axis],
1122 base_strides[base_axis],
1123 )?;
1124 base_axis += 1;
1125 }
1126 for &axis in spec.trailing_axes {
1127 set_lazy_stride(
1128 &mut logical_strides,
1129 spec.trailing_dims[axis],
1130 base_strides[base_axis],
1131 )?;
1132 base_axis += 1;
1133 }
1134 for (&lhs_axis, &rhs_axis) in spec.lhs_batch_axes.iter().zip(spec.rhs_batch_axes.iter()) {
1135 let output_axis = spec.lhs_dims[lhs_axis];
1136 if spec.rhs_dims[rhs_axis] != output_axis {
1137 return Err(crate::Error::backend_failure(
1138 "broadcast_multiply",
1139 "batch axes disagree while building lazy outer-product layout",
1140 ));
1141 }
1142 set_lazy_stride(&mut logical_strides, output_axis, base_strides[base_axis])?;
1143 base_axis += 1;
1144 }
1145
1146 logical_strides
1147 .into_iter()
1148 .collect::<Option<Vec<_>>>()
1149 .ok_or_else(|| {
1150 crate::Error::backend_failure(
1151 "broadcast_multiply",
1152 "lazy outer-product layout did not cover every output axis",
1153 )
1154 })
1155}
1156
1157fn lazy_outer_product_value(
1158 tensor: Tensor,
1159 shape: Vec<usize>,
1160 strides: Vec<isize>,
1161) -> crate::Result<TensorValue> {
1162 Ok(TensorValue::View(TensorOwnedView::from_parts(
1163 Arc::new(tensor),
1164 shape,
1165 strides,
1166 0,
1167 )?))
1168}
1169
1170fn try_lazy_outer_product_with_pool<T>(
1171 buffers: &mut BufferPool,
1172 lhs: &TypedTensorView<'_, T>,
1173 lhs_shape: &[usize],
1174 lhs_dims: &[usize],
1175 rhs: &TypedTensorView<'_, T>,
1176 rhs_shape: &[usize],
1177 rhs_dims: &[usize],
1178) -> crate::Result<Option<LazyOuterProduct<T>>>
1179where
1180 T: Copy + Clone + Mul<Output = T> + PoolScalar + 'static,
1181{
1182 let Some(plan) = split_outer_product_plan(lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims)?
1183 else {
1184 return Ok(None);
1185 };
1186
1187 let lhs_free_axes = axes_by_physical_stride(lhs, &plan.lhs_free_axes);
1188 let rhs_free_axes = axes_by_physical_stride(rhs, &plan.rhs_free_axes);
1189 if lhs_free_axes == plan.lhs_free_axes && rhs_free_axes == plan.rhs_free_axes {
1190 return Ok(None);
1191 }
1192
1193 let lhs_view = typed_view_from_view("broadcast_multiply", lhs)?;
1194 let rhs_view = typed_view_from_view("broadcast_multiply", rhs)?;
1195
1196 match plan.layout {
1197 SplitOuterProductLayout::LhsPrefix => {
1198 let lhs_perm: Vec<_> = lhs_free_axes
1199 .iter()
1200 .chain(plan.lhs_batch_axes.iter())
1201 .copied()
1202 .collect();
1203 let rhs_perm: Vec<_> = rhs_free_axes
1204 .iter()
1205 .chain(plan.rhs_batch_axes.iter())
1206 .copied()
1207 .collect();
1208 let lhs_outer = lhs_view
1209 .permute(&lhs_perm)
1210 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1211 let rhs_outer = rhs_view
1212 .permute(&rhs_perm)
1213 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1214
1215 let mut base_shape = Vec::with_capacity(lhs_shape.len());
1216 append_axis_shapes(&mut base_shape, lhs, &lhs_free_axes);
1217 append_axis_shapes(&mut base_shape, rhs, &rhs_free_axes);
1218 append_axis_shapes(&mut base_shape, lhs, &plan.lhs_batch_axes);
1219 let strides = lazy_outer_product_strides(LazyOuterProductStrideSpec {
1220 output_shape: lhs_shape,
1221 base_shape: &base_shape,
1222 leading_axes: &lhs_free_axes,
1223 leading_dims: lhs_dims,
1224 trailing_axes: &rhs_free_axes,
1225 trailing_dims: rhs_dims,
1226 lhs_batch_axes: &plan.lhs_batch_axes,
1227 rhs_batch_axes: &plan.rhs_batch_axes,
1228 lhs_dims,
1229 rhs_dims,
1230 })?;
1231
1232 let mut base = unsafe { typed_array_uninit_from_pool(buffers, &base_shape) }?;
1234 batched_outer_product_into(
1235 &mut base.view_mut(),
1236 &lhs_outer,
1237 &rhs_outer,
1238 lhs_free_axes.len(),
1239 rhs_free_axes.len(),
1240 )
1241 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1242 Ok(Some(LazyOuterProduct {
1243 base: tensor_from_array(base),
1244 shape: lhs_shape.to_vec(),
1245 strides,
1246 }))
1247 }
1248 SplitOuterProductLayout::RhsPrefix => {
1249 let lhs_perm: Vec<_> = lhs_free_axes
1250 .iter()
1251 .chain(plan.lhs_batch_axes.iter())
1252 .copied()
1253 .collect();
1254 let rhs_perm: Vec<_> = rhs_free_axes
1255 .iter()
1256 .chain(plan.rhs_batch_axes.iter())
1257 .copied()
1258 .collect();
1259 let lhs_outer = lhs_view
1260 .permute(&lhs_perm)
1261 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1262 let rhs_outer = rhs_view
1263 .permute(&rhs_perm)
1264 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1265
1266 let mut base_shape = Vec::with_capacity(lhs_shape.len());
1267 append_axis_shapes(&mut base_shape, rhs, &rhs_free_axes);
1268 append_axis_shapes(&mut base_shape, lhs, &lhs_free_axes);
1269 append_axis_shapes(&mut base_shape, lhs, &plan.lhs_batch_axes);
1270 let strides = lazy_outer_product_strides(LazyOuterProductStrideSpec {
1271 output_shape: lhs_shape,
1272 base_shape: &base_shape,
1273 leading_axes: &rhs_free_axes,
1274 leading_dims: rhs_dims,
1275 trailing_axes: &lhs_free_axes,
1276 trailing_dims: lhs_dims,
1277 lhs_batch_axes: &plan.lhs_batch_axes,
1278 rhs_batch_axes: &plan.rhs_batch_axes,
1279 lhs_dims,
1280 rhs_dims,
1281 })?;
1282
1283 let mut base = unsafe { typed_array_uninit_from_pool(buffers, &base_shape) }?;
1285 batched_outer_product_into(
1286 &mut base.view_mut(),
1287 &rhs_outer,
1288 &lhs_outer,
1289 rhs_free_axes.len(),
1290 lhs_free_axes.len(),
1291 )
1292 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1293 Ok(Some(LazyOuterProduct {
1294 base: tensor_from_array(base),
1295 shape: lhs_shape.to_vec(),
1296 strides,
1297 }))
1298 }
1299 }
1300}
1301
1302#[allow(clippy::too_many_arguments)]
1303fn typed_broadcast_mul_view_with_pool<T, L, R>(
1304 buffers: &mut BufferPool,
1305 lhs: &TypedTensorView<'_, T, L>,
1306 lhs_shape: &[usize],
1307 lhs_dims: &[usize],
1308 rhs: &TypedTensorView<'_, T, R>,
1309 rhs_shape: &[usize],
1310 rhs_dims: &[usize],
1311) -> crate::Result<TypedTensor<T>>
1312where
1313 T: Copy + Clone + Zero + Mul<Output = T> + PoolScalar + 'static,
1314 L: TensorRank,
1315 R: TensorRank,
1316{
1317 if lhs_shape != rhs_shape {
1318 return Err(crate::Error::ShapeMismatch {
1319 op: "broadcast_multiply",
1320 lhs: lhs_shape.to_vec(),
1321 rhs: rhs_shape.to_vec(),
1322 });
1323 }
1324 let output_rank = lhs_shape.len();
1325 let lhs_is_scalar = lhs.shape().is_empty() && lhs_dims.is_empty();
1326 let rhs_is_scalar = rhs.shape().is_empty() && rhs_dims.is_empty();
1327 let lhs_is_full_output =
1328 lhs.shape() == lhs_shape && lhs_dims.iter().copied().eq(0..output_rank);
1329 let rhs_is_full_output =
1330 rhs.shape() == rhs_shape && rhs_dims.iter().copied().eq(0..output_rank);
1331 if lhs_is_scalar && rhs_is_scalar {
1332 let lhs_scalar = typed_view_from_view("broadcast_multiply", lhs)?.get(&[]);
1333 let rhs_scalar = typed_view_from_view("broadcast_multiply", rhs)?.get(&[]);
1334 return filled_broadcast_multiply_tensor(buffers, lhs_shape, lhs_scalar * rhs_scalar);
1335 }
1336 if lhs_is_scalar && rhs_is_full_output {
1337 let scalar = typed_view_from_view("broadcast_multiply", lhs)?.get(&[]);
1338 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs_shape) }?;
1340 map_into(
1341 &mut out.view_mut(),
1342 &typed_view_from_view("broadcast_multiply", rhs)?,
1343 |x| scalar * x,
1344 )
1345 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1346 return Ok(tensor_from_array(out));
1347 }
1348 if rhs_is_scalar && lhs_is_full_output {
1349 let scalar = typed_view_from_view("broadcast_multiply", rhs)?.get(&[]);
1350 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs_shape) }?;
1352 map_into(
1353 &mut out.view_mut(),
1354 &typed_view_from_view("broadcast_multiply", lhs)?,
1355 |x| x * scalar,
1356 )
1357 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1358 return Ok(tensor_from_array(out));
1359 }
1360
1361 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs_shape) }?;
1363 let lhs_view = typed_view_from_view("broadcast_multiply", lhs)?;
1364 let rhs_view = typed_view_from_view("broadcast_multiply", rhs)?;
1365 broadcast_mul_into(
1366 &mut out.view_mut(),
1367 &lhs_view,
1368 lhs_dims,
1369 &rhs_view,
1370 rhs_dims,
1371 )
1372 .map_err(|err| crate::Error::backend_failure("broadcast_multiply", err))?;
1373 Ok(tensor_from_array(out))
1374}
1375
1376fn filled_broadcast_multiply_tensor<T>(
1377 buffers: &mut BufferPool,
1378 shape: &[usize],
1379 fill: T,
1380) -> crate::Result<TypedTensor<T>>
1381where
1382 T: Copy + Clone + PoolScalar + 'static,
1383{
1384 let len = shape.iter().try_fold(1usize, |acc, &dim| {
1385 acc.checked_mul(dim).ok_or_else(|| {
1386 crate::Error::backend_failure("broadcast_multiply", "output shape size overflows usize")
1387 })
1388 })?;
1389 let mut data = unsafe { T::pool_acquire(buffers, len) };
1391 data.fill(fill);
1392 TypedTensor::from_vec_col_major(shape.to_vec(), data)
1393}
1394
1395#[allow(clippy::too_many_arguments)]
1396pub(crate) fn broadcast_multiply_read_with_pool(
1397 buffers: &mut BufferPool,
1398 lhs: TensorRead<'_>,
1399 lhs_shape: &[usize],
1400 lhs_dims: &[usize],
1401 rhs: TensorRead<'_>,
1402 rhs_shape: &[usize],
1403 rhs_dims: &[usize],
1404) -> crate::Result<Option<Tensor>> {
1405 let lhs = read_as_cpu_view(lhs);
1406 let rhs = read_as_cpu_view(rhs);
1407
1408 macro_rules! dispatch {
1409 ($variant:ident, $lhs:expr, $rhs:expr) => {{
1410 if let Some(out) = try_outer_product_with_pool(
1411 buffers, &$lhs, lhs_shape, lhs_dims, &$rhs, rhs_shape, rhs_dims,
1412 )? {
1413 return Ok(Some(Tensor::$variant(out)));
1414 }
1415 Ok(Some(Tensor::$variant(typed_broadcast_mul_view_with_pool(
1416 buffers, &$lhs, lhs_shape, lhs_dims, &$rhs, rhs_shape, rhs_dims,
1417 )?)))
1418 }};
1419 }
1420
1421 match (lhs, rhs) {
1422 (CpuReadView::F32(lhs), CpuReadView::F32(rhs)) => dispatch!(F32, lhs, rhs),
1423 (CpuReadView::F64(lhs), CpuReadView::F64(rhs)) => dispatch!(F64, lhs, rhs),
1424 (CpuReadView::I32(lhs), CpuReadView::I32(rhs)) => dispatch!(I32, lhs, rhs),
1425 (CpuReadView::I64(lhs), CpuReadView::I64(rhs)) => dispatch!(I64, lhs, rhs),
1426 (CpuReadView::C32(lhs), CpuReadView::C32(rhs)) => dispatch!(C32, lhs, rhs),
1427 (CpuReadView::C64(lhs), CpuReadView::C64(rhs)) => dispatch!(C64, lhs, rhs),
1428 _ => Ok(None),
1429 }
1430}
1431
1432#[allow(clippy::too_many_arguments)]
1433pub(crate) fn broadcast_multiply_value_with_pool(
1434 buffers: &mut BufferPool,
1435 lhs: TensorRead<'_>,
1436 lhs_shape: &[usize],
1437 lhs_dims: &[usize],
1438 rhs: TensorRead<'_>,
1439 rhs_shape: &[usize],
1440 rhs_dims: &[usize],
1441) -> crate::Result<Option<TensorValue>> {
1442 let lhs_view = read_as_cpu_view(lhs.clone());
1443 let rhs_view = read_as_cpu_view(rhs.clone());
1444
1445 macro_rules! dispatch_lazy {
1446 ($variant:ident, $lhs:expr, $rhs:expr) => {{
1447 if let Some(out) = try_lazy_outer_product_with_pool(
1448 buffers, &$lhs, lhs_shape, lhs_dims, &$rhs, rhs_shape, rhs_dims,
1449 )? {
1450 return Ok(Some(lazy_outer_product_value(
1451 Tensor::$variant(out.base),
1452 out.shape,
1453 out.strides,
1454 )?));
1455 }
1456 }};
1457 }
1458
1459 match (lhs_view, rhs_view) {
1460 (CpuReadView::F32(lhs_view), CpuReadView::F32(rhs_view)) => {
1461 dispatch_lazy!(F32, lhs_view, rhs_view);
1462 }
1463 (CpuReadView::F64(lhs_view), CpuReadView::F64(rhs_view)) => {
1464 dispatch_lazy!(F64, lhs_view, rhs_view);
1465 }
1466 (CpuReadView::I32(lhs_view), CpuReadView::I32(rhs_view)) => {
1467 dispatch_lazy!(I32, lhs_view, rhs_view);
1468 }
1469 (CpuReadView::I64(lhs_view), CpuReadView::I64(rhs_view)) => {
1470 dispatch_lazy!(I64, lhs_view, rhs_view);
1471 }
1472 (CpuReadView::C32(lhs_view), CpuReadView::C32(rhs_view)) => {
1473 dispatch_lazy!(C32, lhs_view, rhs_view);
1474 }
1475 (CpuReadView::C64(lhs_view), CpuReadView::C64(rhs_view)) => {
1476 dispatch_lazy!(C64, lhs_view, rhs_view);
1477 }
1478 _ => {}
1479 }
1480
1481 broadcast_multiply_read_with_pool(buffers, lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims)
1482 .map(|tensor| tensor.map(TensorValue::from_tensor))
1483}
1484
1485pub fn div(lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
1500 with_local_pool(|buffers| div_with_pool(buffers, lhs, rhs))
1501}
1502
1503pub(crate) fn div_with_pool(
1504 buffers: &mut BufferPool,
1505 lhs: &Tensor,
1506 rhs: &Tensor,
1507) -> crate::Result<Tensor> {
1508 match (lhs, rhs) {
1509 (Tensor::F32(a), Tensor::F32(b)) => Ok(Tensor::F32(typed_div_with_pool(buffers, a, b)?)),
1510 (Tensor::F64(a), Tensor::F64(b)) => Ok(Tensor::F64(typed_div_with_pool(buffers, a, b)?)),
1511 (Tensor::C32(a), Tensor::C32(b)) => Ok(Tensor::C32(typed_div_with_pool(buffers, a, b)?)),
1512 (Tensor::C64(a), Tensor::C64(b)) => Ok(Tensor::C64(typed_div_with_pool(buffers, a, b)?)),
1513 (Tensor::F32(a), Tensor::C32(b)) if a.shape().is_empty() => {
1514 let scalar = complex_scalar_tensor(typed_host_data("div", a)?[0])?;
1515 Ok(Tensor::C32(typed_div_with_pool(buffers, &scalar, b)?))
1516 }
1517 (Tensor::C32(a), Tensor::F32(b)) if b.shape().is_empty() => {
1518 let scalar = complex_scalar_tensor(typed_host_data("div", b)?[0])?;
1519 Ok(Tensor::C32(typed_div_with_pool(buffers, a, &scalar)?))
1520 }
1521 (Tensor::F64(a), Tensor::C64(b)) if a.shape().is_empty() => {
1522 let scalar = complex_scalar_tensor(typed_host_data("div", a)?[0])?;
1523 Ok(Tensor::C64(typed_div_with_pool(buffers, &scalar, b)?))
1524 }
1525 (Tensor::C64(a), Tensor::F64(b)) if b.shape().is_empty() => {
1526 let scalar = complex_scalar_tensor(typed_host_data("div", b)?[0])?;
1527 Ok(Tensor::C64(typed_div_with_pool(buffers, a, &scalar)?))
1528 }
1529 _ => Err(crate::Error::DTypeMismatch {
1530 op: "div",
1531 lhs: lhs.dtype(),
1532 rhs: rhs.dtype(),
1533 }),
1534 }
1535}
1536
1537pub(crate) fn div_read_with_pool(
1538 buffers: &mut BufferPool,
1539 lhs: TensorRead<'_>,
1540 rhs: TensorRead<'_>,
1541) -> crate::Result<Tensor> {
1542 let lhs_dtype = lhs.dtype();
1543 let rhs_dtype = rhs.dtype();
1544 match (read_as_cpu_view(lhs), read_as_cpu_view(rhs)) {
1545 (CpuReadView::F32(a), CpuReadView::F32(b)) => Ok(Tensor::F32(typed_binary_view_with_pool(
1546 "div",
1547 buffers,
1548 &a,
1549 &b,
1550 |x, y| x / y,
1551 )?)),
1552 (CpuReadView::F64(a), CpuReadView::F64(b)) => Ok(Tensor::F64(typed_binary_view_with_pool(
1553 "div",
1554 buffers,
1555 &a,
1556 &b,
1557 |x, y| x / y,
1558 )?)),
1559 (CpuReadView::C32(a), CpuReadView::C32(b)) => Ok(Tensor::C32(typed_binary_view_with_pool(
1560 "div",
1561 buffers,
1562 &a,
1563 &b,
1564 |x, y| x / y,
1565 )?)),
1566 (CpuReadView::C64(a), CpuReadView::C64(b)) => Ok(Tensor::C64(typed_binary_view_with_pool(
1567 "div",
1568 buffers,
1569 &a,
1570 &b,
1571 |x, y| x / y,
1572 )?)),
1573 (CpuReadView::F32(real), CpuReadView::C32(complex)) if real.shape().is_empty() => {
1574 let scalar = complex_scalar_tensor_from_view(&real)?;
1575 let scalar = scalar.as_view();
1576 Ok(Tensor::C32(typed_binary_view_with_pool(
1577 "div",
1578 buffers,
1579 &scalar,
1580 &complex,
1581 |x, y| x / y,
1582 )?))
1583 }
1584 (CpuReadView::C32(complex), CpuReadView::F32(real)) if real.shape().is_empty() => {
1585 let scalar = complex_scalar_tensor_from_view(&real)?;
1586 let scalar = scalar.as_view();
1587 Ok(Tensor::C32(typed_binary_view_with_pool(
1588 "div",
1589 buffers,
1590 &complex,
1591 &scalar,
1592 |x, y| x / y,
1593 )?))
1594 }
1595 (CpuReadView::F64(real), CpuReadView::C64(complex)) if real.shape().is_empty() => {
1596 let scalar = complex_scalar_tensor_from_view(&real)?;
1597 let scalar = scalar.as_view();
1598 Ok(Tensor::C64(typed_binary_view_with_pool(
1599 "div",
1600 buffers,
1601 &scalar,
1602 &complex,
1603 |x, y| x / y,
1604 )?))
1605 }
1606 (CpuReadView::C64(complex), CpuReadView::F64(real)) if real.shape().is_empty() => {
1607 let scalar = complex_scalar_tensor_from_view(&real)?;
1608 let scalar = scalar.as_view();
1609 Ok(Tensor::C64(typed_binary_view_with_pool(
1610 "div",
1611 buffers,
1612 &complex,
1613 &scalar,
1614 |x, y| x / y,
1615 )?))
1616 }
1617 _ => Err(crate::Error::DTypeMismatch {
1618 op: "div",
1619 lhs: lhs_dtype,
1620 rhs: rhs_dtype,
1621 }),
1622 }
1623}
1624
1625pub fn neg(input: &Tensor) -> crate::Result<Tensor> {
1639 with_local_pool(|buffers| neg_with_pool(buffers, input))
1640}
1641
1642pub(crate) fn neg_with_pool(buffers: &mut BufferPool, input: &Tensor) -> crate::Result<Tensor> {
1643 match input {
1644 Tensor::F32(t) => Ok(Tensor::F32(typed_neg_with_pool(buffers, t)?)),
1645 Tensor::F64(t) => Ok(Tensor::F64(typed_neg_with_pool(buffers, t)?)),
1646 Tensor::I32(_) | Tensor::I64(_) | Tensor::Bool(_) => Err(crate::Error::backend_failure(
1647 "neg",
1648 format!("unsupported dtype {:?}", input.dtype()),
1649 )),
1650 Tensor::C32(t) => Ok(Tensor::C32(typed_neg_with_pool(buffers, t)?)),
1651 Tensor::C64(t) => Ok(Tensor::C64(typed_neg_with_pool(buffers, t)?)),
1652 }
1653}
1654
1655pub(crate) fn neg_read_with_pool(
1656 buffers: &mut BufferPool,
1657 input: TensorRead<'_>,
1658) -> crate::Result<Tensor> {
1659 let dtype = input.dtype();
1660 match read_as_cpu_view(input) {
1661 CpuReadView::F32(t) => Ok(Tensor::F32(typed_unary_view_with_pool(
1662 "neg",
1663 buffers,
1664 &t,
1665 |x| -x,
1666 )?)),
1667 CpuReadView::F64(t) => Ok(Tensor::F64(typed_unary_view_with_pool(
1668 "neg",
1669 buffers,
1670 &t,
1671 |x| -x,
1672 )?)),
1673 CpuReadView::C32(t) => Ok(Tensor::C32(typed_unary_view_with_pool(
1674 "neg",
1675 buffers,
1676 &t,
1677 |x| -x,
1678 )?)),
1679 CpuReadView::C64(t) => Ok(Tensor::C64(typed_unary_view_with_pool(
1680 "neg",
1681 buffers,
1682 &t,
1683 |x| -x,
1684 )?)),
1685 _ => Err(crate::Error::backend_failure(
1686 "neg",
1687 format!("unsupported dtype {dtype:?}"),
1688 )),
1689 }
1690}
1691
1692pub fn conj(input: &Tensor) -> crate::Result<Tensor> {
1707 with_local_pool(|buffers| conj_with_pool(buffers, input))
1708}
1709
1710pub(crate) fn conj_with_pool(buffers: &mut BufferPool, input: &Tensor) -> crate::Result<Tensor> {
1711 match input {
1712 Tensor::F32(t) => Ok(Tensor::F32(typed_conj_with_pool(buffers, t)?)),
1713 Tensor::F64(t) => Ok(Tensor::F64(typed_conj_with_pool(buffers, t)?)),
1714 Tensor::I32(_) | Tensor::I64(_) | Tensor::Bool(_) => Err(crate::Error::backend_failure(
1715 "conj",
1716 format!("unsupported dtype {:?}", input.dtype()),
1717 )),
1718 Tensor::C32(t) => Ok(Tensor::C32(typed_conj_with_pool(buffers, t)?)),
1719 Tensor::C64(t) => Ok(Tensor::C64(typed_conj_with_pool(buffers, t)?)),
1720 }
1721}
1722
1723pub(crate) fn conj_read_with_pool(
1724 buffers: &mut BufferPool,
1725 input: TensorRead<'_>,
1726) -> crate::Result<Tensor> {
1727 let dtype = input.dtype();
1728 match read_as_cpu_view(input) {
1729 CpuReadView::F32(t) => Ok(Tensor::F32(typed_unary_view_with_pool(
1730 "conj",
1731 buffers,
1732 &t,
1733 |x| x.conj_elem(),
1734 )?)),
1735 CpuReadView::F64(t) => Ok(Tensor::F64(typed_unary_view_with_pool(
1736 "conj",
1737 buffers,
1738 &t,
1739 |x| x.conj_elem(),
1740 )?)),
1741 CpuReadView::C32(t) => Ok(Tensor::C32(typed_unary_view_with_pool(
1742 "conj",
1743 buffers,
1744 &t,
1745 |x| x.conj_elem(),
1746 )?)),
1747 CpuReadView::C64(t) => Ok(Tensor::C64(typed_unary_view_with_pool(
1748 "conj",
1749 buffers,
1750 &t,
1751 |x| x.conj_elem(),
1752 )?)),
1753 _ => Err(crate::Error::backend_failure(
1754 "conj",
1755 format!("unsupported dtype {dtype:?}"),
1756 )),
1757 }
1758}
1759
1760pub fn abs(input: &Tensor) -> crate::Result<Tensor> {
1776 with_local_pool(|buffers| abs_with_pool(buffers, input))
1777}
1778
1779pub(crate) fn abs_with_pool(buffers: &mut BufferPool, input: &Tensor) -> crate::Result<Tensor> {
1780 match input {
1781 Tensor::F32(t) => Ok(Tensor::F32(typed_abs_with_pool(buffers, t)?)),
1782 Tensor::F64(t) => Ok(Tensor::F64(typed_abs_with_pool(buffers, t)?)),
1783 Tensor::I32(_) | Tensor::I64(_) | Tensor::Bool(_) => Err(crate::Error::backend_failure(
1784 "abs",
1785 format!("unsupported dtype {:?}", input.dtype()),
1786 )),
1787 Tensor::C32(t) => Ok(Tensor::F32(typed_complex_abs_with_pool(buffers, t)?)),
1788 Tensor::C64(t) => Ok(Tensor::F64(typed_complex_abs_with_pool(buffers, t)?)),
1789 }
1790}
1791
1792pub(crate) fn abs_read_with_pool(
1793 buffers: &mut BufferPool,
1794 input: TensorRead<'_>,
1795) -> crate::Result<Tensor> {
1796 let dtype = input.dtype();
1797 match read_as_cpu_view(input) {
1798 CpuReadView::F32(t) => Ok(Tensor::F32(typed_unary_view_with_pool(
1799 "abs",
1800 buffers,
1801 &t,
1802 |x| x.abs_elem(),
1803 )?)),
1804 CpuReadView::F64(t) => Ok(Tensor::F64(typed_unary_view_with_pool(
1805 "abs",
1806 buffers,
1807 &t,
1808 |x| x.abs_elem(),
1809 )?)),
1810 CpuReadView::C32(t) => Ok(Tensor::F32(typed_complex_abs_view_with_pool(buffers, &t)?)),
1811 CpuReadView::C64(t) => Ok(Tensor::F64(typed_complex_abs_view_with_pool(buffers, &t)?)),
1812 _ => Err(crate::Error::backend_failure(
1813 "abs",
1814 format!("unsupported dtype {dtype:?}"),
1815 )),
1816 }
1817}
1818
1819pub fn sign(input: &Tensor) -> crate::Result<Tensor> {
1833 with_local_pool(|buffers| sign_with_pool(buffers, input))
1834}
1835
1836pub(crate) fn sign_with_pool(buffers: &mut BufferPool, input: &Tensor) -> crate::Result<Tensor> {
1837 match input {
1838 Tensor::F32(t) => Ok(Tensor::F32(typed_sign_with_pool(buffers, t)?)),
1839 Tensor::F64(t) => Ok(Tensor::F64(typed_sign_with_pool(buffers, t)?)),
1840 Tensor::I32(_) | Tensor::I64(_) | Tensor::Bool(_) => Err(crate::Error::backend_failure(
1841 "sign",
1842 format!("unsupported dtype {:?}", input.dtype()),
1843 )),
1844 Tensor::C32(t) => Ok(Tensor::C32(typed_sign_with_pool(buffers, t)?)),
1845 Tensor::C64(t) => Ok(Tensor::C64(typed_sign_with_pool(buffers, t)?)),
1846 }
1847}
1848
1849pub(crate) fn sign_read_with_pool(
1850 buffers: &mut BufferPool,
1851 input: TensorRead<'_>,
1852) -> crate::Result<Tensor> {
1853 let dtype = input.dtype();
1854 match read_as_cpu_view(input) {
1855 CpuReadView::F32(t) => Ok(Tensor::F32(typed_unary_view_with_pool(
1856 "sign",
1857 buffers,
1858 &t,
1859 |x| x.sign_elem(),
1860 )?)),
1861 CpuReadView::F64(t) => Ok(Tensor::F64(typed_unary_view_with_pool(
1862 "sign",
1863 buffers,
1864 &t,
1865 |x| x.sign_elem(),
1866 )?)),
1867 CpuReadView::C32(t) => Ok(Tensor::C32(typed_unary_view_with_pool(
1868 "sign",
1869 buffers,
1870 &t,
1871 |x| x.sign_elem(),
1872 )?)),
1873 CpuReadView::C64(t) => Ok(Tensor::C64(typed_unary_view_with_pool(
1874 "sign",
1875 buffers,
1876 &t,
1877 |x| x.sign_elem(),
1878 )?)),
1879 _ => Err(crate::Error::backend_failure(
1880 "sign",
1881 format!("unsupported dtype {dtype:?}"),
1882 )),
1883 }
1884}
1885
1886pub fn maximum(lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
1901 with_local_pool(|buffers| maximum_with_pool(buffers, lhs, rhs))
1902}
1903
1904pub(crate) fn maximum_with_pool(
1905 buffers: &mut BufferPool,
1906 lhs: &Tensor,
1907 rhs: &Tensor,
1908) -> crate::Result<Tensor> {
1909 reject_complex_ordered_dtypes("maximum", &[lhs.dtype(), rhs.dtype()])?;
1910
1911 match (lhs, rhs) {
1912 (Tensor::F32(a), Tensor::F32(b)) => {
1913 Ok(Tensor::F32(typed_maximum_with_pool(buffers, a, b)?))
1914 }
1915 (Tensor::F64(a), Tensor::F64(b)) => {
1916 Ok(Tensor::F64(typed_maximum_with_pool(buffers, a, b)?))
1917 }
1918 _ => Err(tensor_pair_error("maximum", lhs, rhs)),
1919 }
1920}
1921
1922pub(crate) fn maximum_read_with_pool(
1923 buffers: &mut BufferPool,
1924 lhs: TensorRead<'_>,
1925 rhs: TensorRead<'_>,
1926) -> crate::Result<Tensor> {
1927 let lhs_dtype = lhs.dtype();
1928 let rhs_dtype = rhs.dtype();
1929 reject_complex_ordered_dtypes("maximum", &[lhs_dtype, rhs_dtype])?;
1930
1931 match (read_as_cpu_view(lhs), read_as_cpu_view(rhs)) {
1932 (CpuReadView::F32(a), CpuReadView::F32(b)) => Ok(Tensor::F32(
1933 typed_same_shape_binary_view_with_pool("maximum", buffers, &a, &b, |x, y| {
1934 x.max_elem(y)
1935 })?,
1936 )),
1937 (CpuReadView::F64(a), CpuReadView::F64(b)) => Ok(Tensor::F64(
1938 typed_same_shape_binary_view_with_pool("maximum", buffers, &a, &b, |x, y| {
1939 x.max_elem(y)
1940 })?,
1941 )),
1942 _ => Err(dtype_pair_error("maximum", lhs_dtype, rhs_dtype)),
1943 }
1944}
1945
1946pub fn minimum(lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor> {
1961 with_local_pool(|buffers| minimum_with_pool(buffers, lhs, rhs))
1962}
1963
1964pub(crate) fn minimum_with_pool(
1965 buffers: &mut BufferPool,
1966 lhs: &Tensor,
1967 rhs: &Tensor,
1968) -> crate::Result<Tensor> {
1969 reject_complex_ordered_dtypes("minimum", &[lhs.dtype(), rhs.dtype()])?;
1970
1971 match (lhs, rhs) {
1972 (Tensor::F32(a), Tensor::F32(b)) => {
1973 Ok(Tensor::F32(typed_minimum_with_pool(buffers, a, b)?))
1974 }
1975 (Tensor::F64(a), Tensor::F64(b)) => {
1976 Ok(Tensor::F64(typed_minimum_with_pool(buffers, a, b)?))
1977 }
1978 _ => Err(tensor_pair_error("minimum", lhs, rhs)),
1979 }
1980}
1981
1982pub(crate) fn minimum_read_with_pool(
1983 buffers: &mut BufferPool,
1984 lhs: TensorRead<'_>,
1985 rhs: TensorRead<'_>,
1986) -> crate::Result<Tensor> {
1987 let lhs_dtype = lhs.dtype();
1988 let rhs_dtype = rhs.dtype();
1989 reject_complex_ordered_dtypes("minimum", &[lhs_dtype, rhs_dtype])?;
1990
1991 match (read_as_cpu_view(lhs), read_as_cpu_view(rhs)) {
1992 (CpuReadView::F32(a), CpuReadView::F32(b)) => Ok(Tensor::F32(
1993 typed_same_shape_binary_view_with_pool("minimum", buffers, &a, &b, |x, y| {
1994 x.min_elem(y)
1995 })?,
1996 )),
1997 (CpuReadView::F64(a), CpuReadView::F64(b)) => Ok(Tensor::F64(
1998 typed_same_shape_binary_view_with_pool("minimum", buffers, &a, &b, |x, y| {
1999 x.min_elem(y)
2000 })?,
2001 )),
2002 _ => Err(dtype_pair_error("minimum", lhs_dtype, rhs_dtype)),
2003 }
2004}
2005
2006pub fn compare(lhs: &Tensor, rhs: &Tensor, dir: &CompareDir) -> crate::Result<Tensor> {
2021 with_local_pool(|buffers| compare_with_pool(buffers, lhs, rhs, dir))
2022}
2023
2024pub(crate) fn compare_with_pool(
2025 buffers: &mut BufferPool,
2026 lhs: &Tensor,
2027 rhs: &Tensor,
2028 dir: &CompareDir,
2029) -> crate::Result<Tensor> {
2030 reject_complex_ordered_dtypes("compare", &[lhs.dtype(), rhs.dtype()])?;
2031
2032 match (lhs, rhs) {
2033 (Tensor::F32(a), Tensor::F32(b)) => {
2034 Ok(Tensor::Bool(typed_compare_with_pool(buffers, a, b, dir)?))
2035 }
2036 (Tensor::F64(a), Tensor::F64(b)) => {
2037 Ok(Tensor::Bool(typed_compare_with_pool(buffers, a, b, dir)?))
2038 }
2039 (Tensor::I32(a), Tensor::I32(b)) => {
2040 Ok(Tensor::Bool(typed_compare_with_pool(buffers, a, b, dir)?))
2041 }
2042 (Tensor::I64(a), Tensor::I64(b)) => {
2043 Ok(Tensor::Bool(typed_compare_with_pool(buffers, a, b, dir)?))
2044 }
2045 (Tensor::Bool(a), Tensor::Bool(b)) => {
2046 Ok(Tensor::Bool(typed_compare_with_pool(buffers, a, b, dir)?))
2047 }
2048 _ => Err(crate::Error::DTypeMismatch {
2049 op: "compare",
2050 lhs: lhs.dtype(),
2051 rhs: rhs.dtype(),
2052 }),
2053 }
2054}
2055
2056pub(crate) fn compare_read_with_pool(
2057 buffers: &mut BufferPool,
2058 lhs: TensorRead<'_>,
2059 rhs: TensorRead<'_>,
2060 dir: &CompareDir,
2061) -> crate::Result<Tensor> {
2062 let lhs_dtype = lhs.dtype();
2063 let rhs_dtype = rhs.dtype();
2064 reject_complex_ordered_dtypes("compare", &[lhs_dtype, rhs_dtype])?;
2065
2066 match (read_as_cpu_view(lhs), read_as_cpu_view(rhs)) {
2067 (CpuReadView::F32(a), CpuReadView::F32(b)) => Ok(Tensor::Bool(
2068 typed_same_shape_binary_view_with_pool("compare", buffers, &a, &b, |x, y| {
2069 x.compare_elem(y, dir)
2070 })?,
2071 )),
2072 (CpuReadView::F64(a), CpuReadView::F64(b)) => Ok(Tensor::Bool(
2073 typed_same_shape_binary_view_with_pool("compare", buffers, &a, &b, |x, y| {
2074 x.compare_elem(y, dir)
2075 })?,
2076 )),
2077 (CpuReadView::I32(a), CpuReadView::I32(b)) => Ok(Tensor::Bool(
2078 typed_same_shape_binary_view_with_pool("compare", buffers, &a, &b, |x, y| {
2079 x.compare_elem(y, dir)
2080 })?,
2081 )),
2082 (CpuReadView::I64(a), CpuReadView::I64(b)) => Ok(Tensor::Bool(
2083 typed_same_shape_binary_view_with_pool("compare", buffers, &a, &b, |x, y| {
2084 x.compare_elem(y, dir)
2085 })?,
2086 )),
2087 (CpuReadView::Bool(a), CpuReadView::Bool(b)) => Ok(Tensor::Bool(
2088 typed_same_shape_binary_view_with_pool("compare", buffers, &a, &b, |x, y| {
2089 x.compare_elem(y, dir)
2090 })?,
2091 )),
2092 _ => Err(crate::Error::DTypeMismatch {
2093 op: "compare",
2094 lhs: lhs_dtype,
2095 rhs: rhs_dtype,
2096 }),
2097 }
2098}
2099
2100pub fn select(pred: &Tensor, on_true: &Tensor, on_false: &Tensor) -> crate::Result<Tensor> {
2116 with_local_pool(|buffers| select_with_pool(buffers, pred, on_true, on_false))
2117}
2118
2119pub(crate) fn select_with_pool(
2120 buffers: &mut BufferPool,
2121 pred: &Tensor,
2122 on_true: &Tensor,
2123 on_false: &Tensor,
2124) -> crate::Result<Tensor> {
2125 match (pred, on_true, on_false) {
2126 (Tensor::Bool(p), Tensor::F32(t), Tensor::F32(f)) => {
2127 Ok(Tensor::F32(typed_select_with_pool(buffers, p, t, f)?))
2128 }
2129 (Tensor::Bool(p), Tensor::F64(t), Tensor::F64(f)) => {
2130 Ok(Tensor::F64(typed_select_with_pool(buffers, p, t, f)?))
2131 }
2132 (Tensor::Bool(p), Tensor::I32(t), Tensor::I32(f)) => {
2133 Ok(Tensor::I32(typed_select_with_pool(buffers, p, t, f)?))
2134 }
2135 (Tensor::Bool(p), Tensor::I64(t), Tensor::I64(f)) => {
2136 Ok(Tensor::I64(typed_select_with_pool(buffers, p, t, f)?))
2137 }
2138 (Tensor::Bool(p), Tensor::Bool(t), Tensor::Bool(f)) => {
2139 Ok(Tensor::Bool(typed_select_with_pool(buffers, p, t, f)?))
2140 }
2141 (Tensor::Bool(p), Tensor::C32(t), Tensor::C32(f)) => {
2142 Ok(Tensor::C32(typed_select_with_pool(buffers, p, t, f)?))
2143 }
2144 (Tensor::Bool(p), Tensor::C64(t), Tensor::C64(f)) => {
2145 Ok(Tensor::C64(typed_select_with_pool(buffers, p, t, f)?))
2146 }
2147 (Tensor::Bool(_), _, _) => Err(crate::Error::DTypeMismatch {
2148 op: "select",
2149 lhs: on_true.dtype(),
2150 rhs: on_false.dtype(),
2151 }),
2152 _ => Err(crate::Error::DTypeMismatch {
2153 op: "select",
2154 lhs: pred.dtype(),
2155 rhs: crate::DType::Bool,
2156 }),
2157 }
2158}
2159
2160pub(crate) fn select_read_with_pool(
2161 buffers: &mut BufferPool,
2162 pred: TensorRead<'_>,
2163 on_true: TensorRead<'_>,
2164 on_false: TensorRead<'_>,
2165) -> crate::Result<Tensor> {
2166 let pred_dtype = pred.dtype();
2167 let true_dtype = on_true.dtype();
2168 let false_dtype = on_false.dtype();
2169 match (
2170 read_as_cpu_view(pred),
2171 read_as_cpu_view(on_true),
2172 read_as_cpu_view(on_false),
2173 ) {
2174 (CpuReadView::Bool(p), CpuReadView::F32(t), CpuReadView::F32(f)) => Ok(Tensor::F32(
2175 typed_select_view_with_pool(buffers, &p, &t, &f)?,
2176 )),
2177 (CpuReadView::Bool(p), CpuReadView::F64(t), CpuReadView::F64(f)) => Ok(Tensor::F64(
2178 typed_select_view_with_pool(buffers, &p, &t, &f)?,
2179 )),
2180 (CpuReadView::Bool(p), CpuReadView::I32(t), CpuReadView::I32(f)) => Ok(Tensor::I32(
2181 typed_select_view_with_pool(buffers, &p, &t, &f)?,
2182 )),
2183 (CpuReadView::Bool(p), CpuReadView::I64(t), CpuReadView::I64(f)) => Ok(Tensor::I64(
2184 typed_select_view_with_pool(buffers, &p, &t, &f)?,
2185 )),
2186 (CpuReadView::Bool(p), CpuReadView::Bool(t), CpuReadView::Bool(f)) => Ok(Tensor::Bool(
2187 typed_select_view_with_pool(buffers, &p, &t, &f)?,
2188 )),
2189 (CpuReadView::Bool(p), CpuReadView::C32(t), CpuReadView::C32(f)) => Ok(Tensor::C32(
2190 typed_select_view_with_pool(buffers, &p, &t, &f)?,
2191 )),
2192 (CpuReadView::Bool(p), CpuReadView::C64(t), CpuReadView::C64(f)) => Ok(Tensor::C64(
2193 typed_select_view_with_pool(buffers, &p, &t, &f)?,
2194 )),
2195 (CpuReadView::Bool(_), _, _) => Err(crate::Error::DTypeMismatch {
2196 op: "select",
2197 lhs: true_dtype,
2198 rhs: false_dtype,
2199 }),
2200 _ => Err(crate::Error::DTypeMismatch {
2201 op: "select",
2202 lhs: pred_dtype,
2203 rhs: crate::DType::Bool,
2204 }),
2205 }
2206}
2207
2208pub fn clamp(input: &Tensor, lower: &Tensor, upper: &Tensor) -> crate::Result<Tensor> {
2224 with_local_pool(|buffers| clamp_with_pool(buffers, input, lower, upper))
2225}
2226
2227pub(crate) fn clamp_with_pool(
2228 buffers: &mut BufferPool,
2229 input: &Tensor,
2230 lower: &Tensor,
2231 upper: &Tensor,
2232) -> crate::Result<Tensor> {
2233 reject_complex_ordered_dtypes("clamp", &[input.dtype(), lower.dtype(), upper.dtype()])?;
2234
2235 dispatch_ternary_result_with_pool!("clamp", input, lower, upper, |x, lo, hi| {
2236 typed_clamp_with_pool(buffers, x, lo, hi)
2237 })
2238}
2239
2240pub(crate) fn clamp_read_with_pool(
2241 buffers: &mut BufferPool,
2242 input: TensorRead<'_>,
2243 lower: TensorRead<'_>,
2244 upper: TensorRead<'_>,
2245) -> crate::Result<Tensor> {
2246 let input_dtype = input.dtype();
2247 let lower_dtype = lower.dtype();
2248 let upper_dtype = upper.dtype();
2249 reject_complex_ordered_dtypes("clamp", &[input_dtype, lower_dtype, upper_dtype])?;
2250
2251 match (
2252 read_as_cpu_view(input),
2253 read_as_cpu_view(lower),
2254 read_as_cpu_view(upper),
2255 ) {
2256 (CpuReadView::F32(input), CpuReadView::F32(lower), CpuReadView::F32(upper)) => Ok(
2257 Tensor::F32(typed_clamp_view_with_pool(buffers, &input, &lower, &upper)?),
2258 ),
2259 (CpuReadView::F64(input), CpuReadView::F64(lower), CpuReadView::F64(upper)) => Ok(
2260 Tensor::F64(typed_clamp_view_with_pool(buffers, &input, &lower, &upper)?),
2261 ),
2262 _ => Err(crate::Error::DTypeMismatch {
2263 op: "clamp",
2264 lhs: input_dtype,
2265 rhs: lower_dtype,
2266 }),
2267 }
2268}
2269
2270pub(crate) fn typed_add_with_pool<T>(
2271 buffers: &mut BufferPool,
2272 lhs: &TypedTensor<T>,
2273 rhs: &TypedTensor<T>,
2274) -> crate::Result<TypedTensor<T>>
2275where
2276 T: Copy + Clone + Zero + Add<Output = T> + PoolScalar,
2277{
2278 if lhs.shape() == rhs.shape() {
2279 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2281 zip_map2_into(
2282 &mut out.view_mut(),
2283 &typed_view("add", lhs)?,
2284 &typed_view("add", rhs)?,
2285 |x, y| x + y,
2286 )
2287 .map_err(|err| crate::Error::backend_failure("add", err.to_string()))?;
2288 Ok(tensor_from_array(out))
2289 } else if lhs.shape().is_empty() {
2290 let scalar = typed_host_data("add", lhs)?[0];
2291 let mut out = unsafe { typed_array_uninit_from_pool(buffers, rhs.shape()) }?;
2293 map_into(&mut out.view_mut(), &typed_view("add", rhs)?, |x| {
2294 scalar + x
2295 })
2296 .map_err(|err| crate::Error::backend_failure("add", err.to_string()))?;
2297 Ok(tensor_from_array(out))
2298 } else if rhs.shape().is_empty() {
2299 let scalar = typed_host_data("add", rhs)?[0];
2300 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2302 map_into(&mut out.view_mut(), &typed_view("add", lhs)?, |x| {
2303 x + scalar
2304 })
2305 .map_err(|err| crate::Error::backend_failure("add", err.to_string()))?;
2306 Ok(tensor_from_array(out))
2307 } else {
2308 Err(crate::Error::ShapeMismatch {
2309 op: "add",
2310 lhs: lhs.shape().to_vec(),
2311 rhs: rhs.shape().to_vec(),
2312 })
2313 }
2314}
2315
2316pub(crate) fn typed_add_view_with_pool<T, L, R>(
2317 buffers: &mut BufferPool,
2318 lhs: &TypedTensorView<'_, T, L>,
2319 rhs: &TypedTensorView<'_, T, R>,
2320) -> crate::Result<TypedTensor<T>>
2321where
2322 T: Copy + Clone + Zero + Add<Output = T> + PoolScalar + 'static,
2323 L: TensorRank,
2324 R: TensorRank,
2325{
2326 if lhs.shape() == rhs.shape() {
2327 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2329 zip_map2_into(
2330 &mut out.view_mut(),
2331 &typed_view_from_view("add", lhs)?,
2332 &typed_view_from_view("add", rhs)?,
2333 |x, y| x + y,
2334 )
2335 .map_err(|err| crate::Error::backend_failure("add", err.to_string()))?;
2336 Ok(tensor_from_array(out))
2337 } else if lhs.shape().is_empty() {
2338 let scalar = typed_view_from_view("add", lhs)?.get(&[]);
2339 let mut out = unsafe { typed_array_uninit_from_pool(buffers, rhs.shape()) }?;
2341 map_into(
2342 &mut out.view_mut(),
2343 &typed_view_from_view("add", rhs)?,
2344 |x| scalar + x,
2345 )
2346 .map_err(|err| crate::Error::backend_failure("add", err.to_string()))?;
2347 Ok(tensor_from_array(out))
2348 } else if rhs.shape().is_empty() {
2349 let scalar = typed_view_from_view("add", rhs)?.get(&[]);
2350 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2352 map_into(
2353 &mut out.view_mut(),
2354 &typed_view_from_view("add", lhs)?,
2355 |x| x + scalar,
2356 )
2357 .map_err(|err| crate::Error::backend_failure("add", err.to_string()))?;
2358 Ok(tensor_from_array(out))
2359 } else {
2360 Err(crate::Error::ShapeMismatch {
2361 op: "add",
2362 lhs: lhs.shape().to_vec(),
2363 rhs: rhs.shape().to_vec(),
2364 })
2365 }
2366}
2367
2368pub(crate) fn typed_mul_with_pool<T>(
2369 buffers: &mut BufferPool,
2370 lhs: &TypedTensor<T>,
2371 rhs: &TypedTensor<T>,
2372) -> crate::Result<TypedTensor<T>>
2373where
2374 T: Copy + Clone + Zero + Mul<Output = T> + PoolScalar + 'static,
2375{
2376 if lhs.shape() == rhs.shape() {
2377 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2379 mul_into(
2380 &mut out.view_mut(),
2381 &typed_view("mul", lhs)?,
2382 &typed_view("mul", rhs)?,
2383 )
2384 .map_err(|err| crate::Error::backend_failure("mul", err))?;
2385 Ok(tensor_from_array(out))
2386 } else if lhs.shape().is_empty() {
2387 let scalar = typed_host_data("mul", lhs)?[0];
2388 let mut out = unsafe { typed_array_uninit_from_pool(buffers, rhs.shape()) }?;
2390 map_into(&mut out.view_mut(), &typed_view("mul", rhs)?, |x| {
2391 scalar * x
2392 })
2393 .map_err(|err| crate::Error::backend_failure("mul", err))?;
2394 Ok(tensor_from_array(out))
2395 } else if rhs.shape().is_empty() {
2396 let scalar = typed_host_data("mul", rhs)?[0];
2397 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2399 map_into(&mut out.view_mut(), &typed_view("mul", lhs)?, |x| {
2400 x * scalar
2401 })
2402 .map_err(|err| crate::Error::backend_failure("mul", err))?;
2403 Ok(tensor_from_array(out))
2404 } else {
2405 Err(crate::Error::ShapeMismatch {
2406 op: "mul",
2407 lhs: lhs.shape().to_vec(),
2408 rhs: rhs.shape().to_vec(),
2409 })
2410 }
2411}
2412
2413pub(crate) fn typed_mul_view_with_pool<T, L, R>(
2414 buffers: &mut BufferPool,
2415 lhs: &TypedTensorView<'_, T, L>,
2416 rhs: &TypedTensorView<'_, T, R>,
2417) -> crate::Result<TypedTensor<T>>
2418where
2419 T: Copy + Clone + Zero + Mul<Output = T> + PoolScalar + 'static,
2420 L: TensorRank,
2421 R: TensorRank,
2422{
2423 if lhs.shape() == rhs.shape() {
2424 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2426 mul_into(
2427 &mut out.view_mut(),
2428 &typed_view_from_view("mul", lhs)?,
2429 &typed_view_from_view("mul", rhs)?,
2430 )
2431 .map_err(|err| crate::Error::backend_failure("mul", err))?;
2432 Ok(tensor_from_array(out))
2433 } else if lhs.shape().is_empty() {
2434 let scalar = typed_view_from_view("mul", lhs)?.get(&[]);
2435 let mut out = unsafe { typed_array_uninit_from_pool(buffers, rhs.shape()) }?;
2437 map_into(
2438 &mut out.view_mut(),
2439 &typed_view_from_view("mul", rhs)?,
2440 |x| scalar * x,
2441 )
2442 .map_err(|err| crate::Error::backend_failure("mul", err))?;
2443 Ok(tensor_from_array(out))
2444 } else if rhs.shape().is_empty() {
2445 let scalar = typed_view_from_view("mul", rhs)?.get(&[]);
2446 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2448 map_into(
2449 &mut out.view_mut(),
2450 &typed_view_from_view("mul", lhs)?,
2451 |x| x * scalar,
2452 )
2453 .map_err(|err| crate::Error::backend_failure("mul", err))?;
2454 Ok(tensor_from_array(out))
2455 } else {
2456 Err(crate::Error::ShapeMismatch {
2457 op: "mul",
2458 lhs: lhs.shape().to_vec(),
2459 rhs: rhs.shape().to_vec(),
2460 })
2461 }
2462}
2463
2464pub(crate) fn typed_div_with_pool<T>(
2465 buffers: &mut BufferPool,
2466 lhs: &TypedTensor<T>,
2467 rhs: &TypedTensor<T>,
2468) -> crate::Result<TypedTensor<T>>
2469where
2470 T: Copy + Clone + Zero + Div<Output = T> + PoolScalar,
2471{
2472 if lhs.shape() == rhs.shape() {
2473 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2475 zip_map2_into(
2476 &mut out.view_mut(),
2477 &typed_view("div", lhs)?,
2478 &typed_view("div", rhs)?,
2479 |x, y| x / y,
2480 )
2481 .map_err(|err| crate::Error::backend_failure("div", err))?;
2482 Ok(tensor_from_array(out))
2483 } else if lhs.shape().is_empty() {
2484 let scalar = typed_host_data("div", lhs)?[0];
2485 let mut out = unsafe { typed_array_uninit_from_pool(buffers, rhs.shape()) }?;
2487 map_into(&mut out.view_mut(), &typed_view("div", rhs)?, |x| {
2488 scalar / x
2489 })
2490 .map_err(|err| crate::Error::backend_failure("div", err))?;
2491 Ok(tensor_from_array(out))
2492 } else if rhs.shape().is_empty() {
2493 let scalar = typed_host_data("div", rhs)?[0];
2494 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2496 map_into(&mut out.view_mut(), &typed_view("div", lhs)?, |x| {
2497 x / scalar
2498 })
2499 .map_err(|err| crate::Error::backend_failure("div", err))?;
2500 Ok(tensor_from_array(out))
2501 } else {
2502 Err(crate::Error::ShapeMismatch {
2503 op: "div",
2504 lhs: lhs.shape().to_vec(),
2505 rhs: rhs.shape().to_vec(),
2506 })
2507 }
2508}
2509
2510pub(crate) fn typed_neg_with_pool<T>(
2511 buffers: &mut BufferPool,
2512 input: &TypedTensor<T>,
2513) -> crate::Result<TypedTensor<T>>
2514where
2515 T: Copy + Clone + Zero + Neg<Output = T> + PoolScalar,
2516{
2517 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
2519 map_into(&mut out.view_mut(), &typed_view("neg", input)?, |x| -x)
2520 .map_err(|err| crate::Error::backend_failure("neg", err))?;
2521 Ok(tensor_from_array(out))
2522}
2523
2524pub(crate) fn typed_conj_with_pool<T>(
2525 buffers: &mut BufferPool,
2526 input: &TypedTensor<T>,
2527) -> crate::Result<TypedTensor<T>>
2528where
2529 T: Copy + Clone + Zero + ConjElem + PoolScalar,
2530{
2531 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
2533 map_into(&mut out.view_mut(), &typed_view("conj", input)?, |x| {
2534 x.conj_elem()
2535 })
2536 .map_err(|err| crate::Error::backend_failure("conj", err))?;
2537 Ok(tensor_from_array(out))
2538}
2539
2540pub(crate) fn typed_abs_with_pool<T>(
2541 buffers: &mut BufferPool,
2542 input: &TypedTensor<T>,
2543) -> crate::Result<TypedTensor<T>>
2544where
2545 T: Tier2Elem + PoolScalar,
2546{
2547 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
2549 map_into(&mut out.view_mut(), &typed_view("abs", input)?, |x| {
2550 x.abs_elem()
2551 })
2552 .map_err(|err| crate::Error::backend_failure("abs", err))?;
2553 Ok(tensor_from_array(out))
2554}
2555
2556fn typed_complex_abs_with_pool<T>(
2557 buffers: &mut BufferPool,
2558 input: &TypedTensor<Complex<T>>,
2559) -> crate::Result<TypedTensor<T>>
2560where
2561 T: num_traits::Float + PoolScalar,
2562{
2563 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
2565 map_into(&mut out.view_mut(), &typed_view("abs", input)?, |x| {
2566 x.norm()
2567 })
2568 .map_err(|err| crate::Error::backend_failure("abs", err))?;
2569 Ok(tensor_from_array(out))
2570}
2571
2572fn typed_complex_abs_view_with_pool<T, R>(
2573 buffers: &mut BufferPool,
2574 input: &TypedTensorView<'_, Complex<T>, R>,
2575) -> crate::Result<TypedTensor<T>>
2576where
2577 T: num_traits::Float + PoolScalar + 'static,
2578 R: TensorRank,
2579{
2580 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
2582 map_into(
2583 &mut out.view_mut(),
2584 &typed_view_from_view("abs", input)?,
2585 |x| x.norm(),
2586 )
2587 .map_err(|err| crate::Error::backend_failure("abs", err))?;
2588 Ok(tensor_from_array(out))
2589}
2590
2591pub(crate) fn typed_sign_with_pool<T>(
2592 buffers: &mut BufferPool,
2593 input: &TypedTensor<T>,
2594) -> crate::Result<TypedTensor<T>>
2595where
2596 T: Tier2Elem + PoolScalar,
2597{
2598 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
2600 map_into(&mut out.view_mut(), &typed_view("sign", input)?, |x| {
2601 x.sign_elem()
2602 })
2603 .map_err(|err| crate::Error::backend_failure("sign", err))?;
2604 Ok(tensor_from_array(out))
2605}
2606
2607pub(crate) fn typed_maximum_with_pool<T>(
2608 buffers: &mut BufferPool,
2609 lhs: &TypedTensor<T>,
2610 rhs: &TypedTensor<T>,
2611) -> crate::Result<TypedTensor<T>>
2612where
2613 T: OrderedElem + PoolScalar,
2614{
2615 if lhs.shape() != rhs.shape() {
2616 return Err(crate::Error::ShapeMismatch {
2617 op: "maximum",
2618 lhs: lhs.shape().to_vec(),
2619 rhs: rhs.shape().to_vec(),
2620 });
2621 }
2622 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2624 zip_map2_into(
2625 &mut out.view_mut(),
2626 &typed_view("maximum", lhs)?,
2627 &typed_view("maximum", rhs)?,
2628 |x, y| x.max_elem(y),
2629 )
2630 .map_err(|err| crate::Error::backend_failure("maximum", err))?;
2631 Ok(tensor_from_array(out))
2632}
2633
2634pub(crate) fn typed_minimum_with_pool<T>(
2635 buffers: &mut BufferPool,
2636 lhs: &TypedTensor<T>,
2637 rhs: &TypedTensor<T>,
2638) -> crate::Result<TypedTensor<T>>
2639where
2640 T: OrderedElem + PoolScalar,
2641{
2642 if lhs.shape() != rhs.shape() {
2643 return Err(crate::Error::ShapeMismatch {
2644 op: "minimum",
2645 lhs: lhs.shape().to_vec(),
2646 rhs: rhs.shape().to_vec(),
2647 });
2648 }
2649 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2651 zip_map2_into(
2652 &mut out.view_mut(),
2653 &typed_view("minimum", lhs)?,
2654 &typed_view("minimum", rhs)?,
2655 |x, y| x.min_elem(y),
2656 )
2657 .map_err(|err| crate::Error::backend_failure("minimum", err))?;
2658 Ok(tensor_from_array(out))
2659}
2660
2661pub(crate) fn typed_compare_with_pool<T>(
2662 buffers: &mut BufferPool,
2663 lhs: &TypedTensor<T>,
2664 rhs: &TypedTensor<T>,
2665 dir: &CompareDir,
2666) -> crate::Result<TypedTensor<bool>>
2667where
2668 T: CompareElem,
2669{
2670 if lhs.shape() != rhs.shape() {
2671 return Err(crate::Error::ShapeMismatch {
2672 op: "compare",
2673 lhs: lhs.shape().to_vec(),
2674 rhs: rhs.shape().to_vec(),
2675 });
2676 }
2677 let mut out = unsafe { typed_array_uninit_from_pool(buffers, lhs.shape()) }?;
2679 zip_map2_into(
2680 &mut out.view_mut(),
2681 &typed_view("compare", lhs)?,
2682 &typed_view("compare", rhs)?,
2683 |x, y| x.compare_elem(y, dir),
2684 )
2685 .map_err(|err| crate::Error::backend_failure("compare", err))?;
2686 Ok(tensor_from_array(out))
2687}
2688
2689pub(crate) fn typed_select_with_pool<T>(
2690 buffers: &mut BufferPool,
2691 pred: &TypedTensor<bool>,
2692 on_true: &TypedTensor<T>,
2693 on_false: &TypedTensor<T>,
2694) -> crate::Result<TypedTensor<T>>
2695where
2696 T: Copy + PoolScalar,
2697{
2698 if pred.shape() != on_true.shape() {
2699 return Err(crate::Error::ShapeMismatch {
2700 op: "select",
2701 lhs: pred.shape().to_vec(),
2702 rhs: on_true.shape().to_vec(),
2703 });
2704 }
2705 if pred.shape() != on_false.shape() {
2706 return Err(crate::Error::ShapeMismatch {
2707 op: "select",
2708 lhs: pred.shape().to_vec(),
2709 rhs: on_false.shape().to_vec(),
2710 });
2711 }
2712 let mut out = unsafe { typed_array_uninit_from_pool(buffers, pred.shape()) }?;
2714 zip_map3_into(
2715 &mut out.view_mut(),
2716 &typed_view("select", pred)?,
2717 &typed_view("select", on_true)?,
2718 &typed_view("select", on_false)?,
2719 |p, t, f| if p { t } else { f },
2720 )
2721 .map_err(|err| crate::Error::backend_failure("select", err))?;
2722 Ok(tensor_from_array(out))
2723}
2724
2725pub(crate) fn typed_clamp_with_pool<T>(
2726 buffers: &mut BufferPool,
2727 input: &TypedTensor<T>,
2728 lower: &TypedTensor<T>,
2729 upper: &TypedTensor<T>,
2730) -> crate::Result<TypedTensor<T>>
2731where
2732 T: OrderedElem + PoolScalar,
2733{
2734 if input.shape() != lower.shape() {
2735 return Err(crate::Error::ShapeMismatch {
2736 op: "clamp",
2737 lhs: input.shape().to_vec(),
2738 rhs: lower.shape().to_vec(),
2739 });
2740 }
2741 if input.shape() != upper.shape() {
2742 return Err(crate::Error::ShapeMismatch {
2743 op: "clamp",
2744 lhs: input.shape().to_vec(),
2745 rhs: upper.shape().to_vec(),
2746 });
2747 }
2748 let mut out = unsafe { typed_array_uninit_from_pool(buffers, input.shape()) }?;
2750 zip_map3_into(
2751 &mut out.view_mut(),
2752 &typed_view("clamp", input)?,
2753 &typed_view("clamp", lower)?,
2754 &typed_view("clamp", upper)?,
2755 |x, lo, hi| hi.min_elem(lo.max_elem(x)),
2756 )
2757 .map_err(|err| crate::Error::backend_failure("clamp", err))?;
2758 Ok(tensor_from_array(out))
2759}
2760
2761#[cfg(test)]
2762mod tests;