Skip to main content

tenferro_cpu/
elementwise.rs

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
72// Keep ordering separate from abs/sign so complex tensors cannot silently pick
73// a magnitude ordering. Callers should compute abs/norm explicitly first.
74pub(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
209/// Add two CPU tensors elementwise.
210///
211/// # Examples
212///
213/// ```
214/// use tenferro_cpu::add;
215/// use tenferro_tensor::Tensor;
216///
217/// let a = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
218/// let b = Tensor::from_vec_col_major(vec![2], vec![3.0_f64, 4.0])?;
219/// let out = add(&a, &b)?;
220/// assert_eq!(out.as_slice::<f64>().unwrap(), &[4.0, 6.0]);
221/// # Ok::<(), tenferro_tensor::Error>(())
222/// ```
223pub 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
379/// Multiply two CPU tensors elementwise.
380///
381/// # Examples
382///
383/// ```
384/// use tenferro_cpu::mul;
385/// use tenferro_tensor::Tensor;
386///
387/// let a = Tensor::from_vec_col_major(vec![2], vec![2.0_f64, 3.0])?;
388/// let b = Tensor::from_vec_col_major(vec![2], vec![4.0_f64, 5.0])?;
389/// let out = mul(&a, &b)?;
390/// assert_eq!(out.as_slice::<f64>().unwrap(), &[8.0, 15.0]);
391/// # Ok::<(), tenferro_tensor::Error>(())
392/// ```
393pub 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        // SAFETY: the following kernel overwrites every output element before any read.
606        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        // SAFETY: the following kernel overwrites every output element before any read.
618        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        // SAFETY: the following kernel overwrites every output element before any read.
627        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    // SAFETY: the following kernel overwrites every output element before any read.
653    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    // SAFETY: the following kernel overwrites every output element before any read.
680    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    // SAFETY: the following kernel overwrites every output element before any read.
718    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    // SAFETY: the following kernel overwrites every output element before any read.
757    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    // SAFETY: every element in the column-major output is assigned below.
987    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            // SAFETY: every element in the physical base output is assigned below.
1233            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            // SAFETY: every element in the physical base output is assigned below.
1284            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        // SAFETY: map_into overwrites every output element.
1339        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        // SAFETY: map_into overwrites every output element.
1351        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    // SAFETY: broadcast_mul_into overwrites every output element.
1362    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    // SAFETY: every pooled element is initialized with `fill` before tensor construction.
1390    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
1485/// Divide two CPU tensors elementwise.
1486///
1487/// # Examples
1488///
1489/// ```
1490/// use tenferro_cpu::div;
1491/// use tenferro_tensor::Tensor;
1492///
1493/// let a = Tensor::from_vec_col_major(vec![2], vec![8.0_f64, 15.0])?;
1494/// let b = Tensor::from_vec_col_major(vec![2], vec![2.0_f64, 5.0])?;
1495/// let out = div(&a, &b)?;
1496/// assert_eq!(out.as_slice::<f64>().unwrap(), &[4.0, 3.0]);
1497/// # Ok::<(), tenferro_tensor::Error>(())
1498/// ```
1499pub 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
1625/// Negate a CPU tensor elementwise.
1626///
1627/// # Examples
1628///
1629/// ```
1630/// use tenferro_cpu::neg;
1631/// use tenferro_tensor::Tensor;
1632///
1633/// let input = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, -2.0])?;
1634/// let out = neg(&input)?;
1635/// assert_eq!(out.as_slice::<f64>().unwrap(), &[-1.0, 2.0]);
1636/// # Ok::<(), tenferro_tensor::Error>(())
1637/// ```
1638pub 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
1692/// Conjugate a real or complex CPU tensor elementwise.
1693///
1694/// # Examples
1695///
1696/// ```
1697/// use num_complex::Complex64;
1698/// use tenferro_cpu::conj;
1699/// use tenferro_tensor::Tensor;
1700///
1701/// let input = Tensor::from_vec_col_major(vec![1], vec![Complex64::new(1.0, 2.0)])?;
1702/// let out = conj(&input)?;
1703/// assert_eq!(out.as_slice::<Complex64>().unwrap(), &[Complex64::new(1.0, -2.0)]);
1704/// # Ok::<(), tenferro_tensor::Error>(())
1705/// ```
1706pub 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
1760/// Compute elementwise absolute values.
1761///
1762/// Complex inputs return real magnitudes (`C32 -> F32`, `C64 -> F64`).
1763///
1764/// # Examples
1765///
1766/// ```
1767/// use tenferro_cpu::abs;
1768/// use tenferro_tensor::Tensor;
1769///
1770/// let input = Tensor::from_vec_col_major(vec![2], vec![-3.0_f64, 4.0])?;
1771/// let out = abs(&input)?;
1772/// assert_eq!(out.as_slice::<f64>().unwrap(), &[3.0, 4.0]);
1773/// # Ok::<(), tenferro_tensor::Error>(())
1774/// ```
1775pub 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
1819/// Compute elementwise signs.
1820///
1821/// # Examples
1822///
1823/// ```
1824/// use tenferro_cpu::sign;
1825/// use tenferro_tensor::Tensor;
1826///
1827/// let input = Tensor::from_vec_col_major(vec![3], vec![-2.0_f64, 0.0, 3.0])?;
1828/// let out = sign(&input)?;
1829/// assert_eq!(out.as_slice::<f64>().unwrap(), &[-1.0, 0.0, 1.0]);
1830/// # Ok::<(), tenferro_tensor::Error>(())
1831/// ```
1832pub 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
1886/// Compute elementwise maximum values.
1887///
1888/// # Examples
1889///
1890/// ```
1891/// use tenferro_cpu::maximum;
1892/// use tenferro_tensor::Tensor;
1893///
1894/// let a = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 5.0])?;
1895/// let b = Tensor::from_vec_col_major(vec![2], vec![3.0_f64, 4.0])?;
1896/// let out = maximum(&a, &b)?;
1897/// assert_eq!(out.as_slice::<f64>().unwrap(), &[3.0, 5.0]);
1898/// # Ok::<(), tenferro_tensor::Error>(())
1899/// ```
1900pub 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
1946/// Compute elementwise minimum values.
1947///
1948/// # Examples
1949///
1950/// ```
1951/// use tenferro_cpu::minimum;
1952/// use tenferro_tensor::Tensor;
1953///
1954/// let a = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 5.0])?;
1955/// let b = Tensor::from_vec_col_major(vec![2], vec![3.0_f64, 4.0])?;
1956/// let out = minimum(&a, &b)?;
1957/// assert_eq!(out.as_slice::<f64>().unwrap(), &[1.0, 4.0]);
1958/// # Ok::<(), tenferro_tensor::Error>(())
1959/// ```
1960pub 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
2006/// Compare two CPU tensors elementwise.
2007///
2008/// # Examples
2009///
2010/// ```
2011/// use tenferro_cpu::compare;
2012/// use tenferro_tensor::{CompareDir, Tensor};
2013///
2014/// let a = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 5.0])?;
2015/// let b = Tensor::from_vec_col_major(vec![2], vec![3.0_f64, 4.0])?;
2016/// let out = compare(&a, &b, &CompareDir::Gt)?;
2017/// assert_eq!(out.as_slice::<bool>().unwrap(), &[false, true]);
2018/// # Ok::<(), tenferro_tensor::Error>(())
2019/// ```
2020pub 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
2100/// Select values from two tensors using a boolean predicate tensor.
2101///
2102/// # Examples
2103///
2104/// ```
2105/// use tenferro_cpu::select;
2106/// use tenferro_tensor::Tensor;
2107///
2108/// let pred = Tensor::from_vec_col_major(vec![2], vec![true, false])?;
2109/// let on_true = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
2110/// let on_false = Tensor::from_vec_col_major(vec![2], vec![3.0_f64, 4.0])?;
2111/// let out = select(&pred, &on_true, &on_false)?;
2112/// assert_eq!(out.as_slice::<f64>().unwrap(), &[1.0, 4.0]);
2113/// # Ok::<(), tenferro_tensor::Error>(())
2114/// ```
2115pub 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
2208/// Clamp CPU tensor values elementwise between lower and upper bounds.
2209///
2210/// # Examples
2211///
2212/// ```
2213/// use tenferro_cpu::clamp;
2214/// use tenferro_tensor::Tensor;
2215///
2216/// let input = Tensor::from_vec_col_major(vec![3], vec![-1.0_f64, 2.0, 8.0])?;
2217/// let lower = Tensor::from_vec_col_major(vec![3], vec![0.0_f64, 0.0, 0.0])?;
2218/// let upper = Tensor::from_vec_col_major(vec![3], vec![5.0_f64, 5.0, 5.0])?;
2219/// let out = clamp(&input, &lower, &upper)?;
2220/// assert_eq!(out.as_slice::<f64>().unwrap(), &[0.0, 2.0, 5.0]);
2221/// # Ok::<(), tenferro_tensor::Error>(())
2222/// ```
2223pub 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        // SAFETY: zip_map2_into overwrites every output element.
2280        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        // SAFETY: map_into overwrites every output element.
2292        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        // SAFETY: map_into overwrites every output element.
2301        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        // SAFETY: zip_map2_into overwrites every output element.
2328        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        // SAFETY: map_into overwrites every output element.
2340        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        // SAFETY: map_into overwrites every output element.
2351        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        // SAFETY: mul_into overwrites every output element.
2378        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        // SAFETY: map_into overwrites every output element.
2389        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        // SAFETY: map_into overwrites every output element.
2398        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        // SAFETY: mul_into overwrites every output element.
2425        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        // SAFETY: map_into overwrites every output element.
2436        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        // SAFETY: map_into overwrites every output element.
2447        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        // SAFETY: zip_map2_into overwrites every output element.
2474        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        // SAFETY: map_into overwrites every output element.
2486        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        // SAFETY: map_into overwrites every output element.
2495        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    // SAFETY: map_into overwrites every output element.
2518    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    // SAFETY: map_into overwrites every output element.
2532    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    // SAFETY: map_into overwrites every output element.
2548    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    // SAFETY: the following kernel overwrites every output element before any read.
2564    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    // SAFETY: the following kernel overwrites every output element before any read.
2581    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    // SAFETY: map_into overwrites every output element.
2599    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    // SAFETY: zip_map2_into overwrites every output element.
2623    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    // SAFETY: zip_map2_into overwrites every output element.
2650    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    // SAFETY: zip_map2_into overwrites every output element.
2678    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    // SAFETY: zip_map3_into overwrites every output element.
2713    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    // SAFETY: zip_map3_into overwrites every output element.
2749    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;