Skip to main content

tenferro_cpu/
structural.rs

1use num_complex::{Complex32, Complex64};
2use num_traits::Zero;
3use strided_kernel::{col_major_strides, copy_into, map_into, Identity, StridedView};
4
5use crate::{
6    buffer_pool::{BufferPool, PoolScalar},
7    flat_to_multi,
8};
9use tenferro_tensor::{DType, Tensor, TensorRank, TypedTensor, TypedTensorView};
10
11#[cfg(test)]
12use super::typed_array_uninit;
13use super::{
14    cpu_backend_buffer_error, tensor_from_array, typed_array_uninit_from_pool, typed_host_data,
15    typed_view, typed_view_from_view,
16};
17
18fn with_local_pool<T>(f: impl FnOnce(&mut BufferPool) -> T) -> T {
19    let mut buffers = BufferPool::new();
20    f(&mut buffers)
21}
22
23fn validate_rank(op: &'static str, expected: usize, actual: usize) -> crate::Result<()> {
24    if expected != actual {
25        return Err(crate::Error::RankMismatch {
26            op,
27            expected,
28            actual,
29        });
30    }
31    Ok(())
32}
33
34fn validate_axis(op: &'static str, axis: usize, rank: usize) -> crate::Result<()> {
35    if axis >= rank {
36        return Err(crate::Error::AxisOutOfBounds { op, axis, rank });
37    }
38    Ok(())
39}
40
41fn validate_axes_distinct(op: &'static str, axis_a: usize, axis_b: usize) -> crate::Result<()> {
42    if axis_a == axis_b {
43        return Err(crate::Error::DuplicateAxis {
44            op,
45            axis: axis_a,
46            role: "axes",
47        });
48    }
49    Ok(())
50}
51
52fn checked_shape_product(
53    op: &'static str,
54    role: &'static str,
55    shape: &[usize],
56) -> crate::Result<usize> {
57    shape.iter().try_fold(1usize, |acc, &dim| {
58        acc.checked_mul(dim)
59            .ok_or_else(|| crate::Error::InvalidConfig {
60                op,
61                message: format!("{role} element count overflows usize"),
62            })
63    })
64}
65
66fn validate_permutation(op: &'static str, perm: &[usize], rank: usize) -> crate::Result<()> {
67    validate_rank(op, rank, perm.len())?;
68    let mut seen = vec![false; rank];
69    for &axis in perm {
70        validate_axis(op, axis, rank)?;
71        if seen[axis] {
72            return Err(crate::Error::DuplicateAxis {
73                op,
74                axis,
75                role: "perm",
76            });
77        }
78        seen[axis] = true;
79    }
80    Ok(())
81}
82
83macro_rules! dispatch_tensor_unary_result {
84    ($input:expr, |$tensor:ident| $body:expr) => {
85        match $input {
86            Tensor::F32($tensor) => Ok(Tensor::F32($body?)),
87            Tensor::F64($tensor) => Ok(Tensor::F64($body?)),
88            Tensor::I32($tensor) => Ok(Tensor::I32($body?)),
89            Tensor::I64($tensor) => Ok(Tensor::I64($body?)),
90            Tensor::Bool($tensor) => Ok(Tensor::Bool($body?)),
91            Tensor::C32($tensor) => Ok(Tensor::C32($body?)),
92            Tensor::C64($tensor) => Ok(Tensor::C64($body?)),
93        }
94    };
95}
96
97macro_rules! dispatch_tensor_unary_with_bool_special_result {
98    ($input:expr, |$tensor:ident| $body:expr, bool |$bool_tensor:ident| $bool_body:expr) => {
99        match $input {
100            Tensor::F32($tensor) => Ok(Tensor::F32($body?)),
101            Tensor::F64($tensor) => Ok(Tensor::F64($body?)),
102            Tensor::I32($tensor) => Ok(Tensor::I32($body?)),
103            Tensor::I64($tensor) => Ok(Tensor::I64($body?)),
104            Tensor::Bool($bool_tensor) => Ok(Tensor::Bool($bool_body?)),
105            Tensor::C32($tensor) => Ok(Tensor::C32($body?)),
106            Tensor::C64($tensor) => Ok(Tensor::C64($body?)),
107        }
108    };
109}
110
111fn host_view<'a, T: Copy>(
112    op: &'static str,
113    tensor: &'a TypedTensor<T>,
114) -> crate::Result<StridedView<'a, T, Identity>> {
115    match tensor.buffer() {
116        crate::Buffer::Host(data) => {
117            let strides = col_major_strides(tensor.shape());
118            StridedView::new(data.as_slice(), tensor.shape(), &strides, 0)
119                .map_err(|err| crate::Error::backend_failure(op, err))
120        }
121        crate::Buffer::Backend(_) => Err(cpu_backend_buffer_error(op)),
122    }
123}
124
125fn copy_view_to_array<T: Copy + Clone + Send + Sync>(
126    op: &'static str,
127    mut out: strided_kernel::StridedArray<T>,
128    src: &StridedView<'_, T>,
129) -> crate::Result<TypedTensor<T>> {
130    copy_into(&mut out.view_mut(), src).map_err(|err| crate::Error::backend_failure(op, err))?;
131    Ok(tensor_from_array(out))
132}
133
134fn zeroed_tensor_from_pool<T>(
135    buffers: &mut BufferPool,
136    op: &'static str,
137    shape: Vec<usize>,
138) -> crate::Result<TypedTensor<T>>
139where
140    T: Zero + Clone + PoolScalar + 'static,
141{
142    filled_tensor_from_pool(buffers, op, shape, T::zero())
143}
144
145fn filled_tensor_from_pool<T>(
146    buffers: &mut BufferPool,
147    op: &'static str,
148    shape: Vec<usize>,
149    fill: T,
150) -> crate::Result<TypedTensor<T>>
151where
152    T: Copy + Clone + PoolScalar + 'static,
153{
154    let len = checked_shape_product(op, "output shape", &shape)?;
155    // SAFETY: every pooled element is initialized with `fill` before returning.
156    let mut data = unsafe { T::pool_acquire(buffers, len) };
157    data.fill(fill);
158    TypedTensor::from_vec_col_major(shape, data)
159}
160
161fn clone_host_tensor_from_pool<T>(
162    buffers: &mut BufferPool,
163    op: &'static str,
164    tensor: &TypedTensor<T>,
165) -> crate::Result<TypedTensor<T>>
166where
167    T: Copy + PoolScalar + 'static,
168{
169    let input = match tensor.buffer() {
170        crate::Buffer::Host(data) => data.as_slice(),
171        crate::Buffer::Backend(_) => return Err(cpu_backend_buffer_error(op)),
172    };
173    // SAFETY: copy_from_slice initializes every pooled element before returning.
174    let mut data = unsafe { T::pool_acquire(buffers, input.len()) };
175    data.copy_from_slice(input);
176    TypedTensor::from_buffer_col_major(
177        tensor.shape().to_vec(),
178        crate::Buffer::Host(data),
179        tensor.placement().clone(),
180    )
181}
182
183pub fn transpose(input: &Tensor, perm: &[usize]) -> crate::Result<Tensor> {
184    with_local_pool(|buffers| transpose_with_pool(buffers, input, perm))
185}
186
187pub(crate) fn transpose_with_pool(
188    buffers: &mut BufferPool,
189    input: &Tensor,
190    perm: &[usize],
191) -> crate::Result<Tensor> {
192    dispatch_tensor_unary_result!(input, |t| typed_transpose_with_pool(buffers, t, perm))
193}
194
195pub fn reshape(input: &Tensor, shape: &[usize]) -> crate::Result<Tensor> {
196    dispatch_tensor_unary_result!(input, |t| typed_reshape(t, shape))
197}
198
199pub fn broadcast_in_dim(input: &Tensor, shape: &[usize], dims: &[usize]) -> crate::Result<Tensor> {
200    with_local_pool(|buffers| broadcast_in_dim_with_pool(buffers, input, shape, dims))
201}
202
203pub(crate) fn broadcast_in_dim_with_pool(
204    buffers: &mut BufferPool,
205    input: &Tensor,
206    shape: &[usize],
207    dims: &[usize],
208) -> crate::Result<Tensor> {
209    dispatch_tensor_unary_result!(input, |t| typed_broadcast_in_dim_with_pool(
210        buffers, t, shape, dims
211    ))
212}
213
214/// Convert a tensor to another dtype using checked dtype conversion.
215///
216/// Use `TensorStructural::cast` when an explicit lossy dtype projection is
217/// intended.
218///
219/// # Examples
220///
221/// ```rust
222/// use tenferro_cpu::CpuBackend;
223/// use tenferro_tensor::{DType, Tensor, TensorStructural};
224///
225/// let mut backend = CpuBackend::new();
226/// let x = Tensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
227/// let y = backend.convert(&x, DType::F64).unwrap();
228/// assert_eq!(y.as_slice::<f64>().unwrap(), &[1.0, 2.0]);
229/// ```
230///
231/// # Errors
232///
233/// Returns an error when the requested conversion is outside tenferro's checked
234/// dtype-promotion lattice.
235pub fn convert(input: &Tensor, to: DType) -> crate::Result<Tensor> {
236    with_local_pool(|buffers| convert_with_pool(buffers, input, to))
237}
238
239pub(crate) fn convert_with_pool(
240    buffers: &mut BufferPool,
241    input: &Tensor,
242    to: DType,
243) -> crate::Result<Tensor> {
244    tenferro_tensor::validate::validate_convert_dtype("convert", input.dtype(), to)?;
245    cast_with_pool(buffers, input, to)
246}
247
248pub(crate) fn cast_with_pool(
249    buffers: &mut BufferPool,
250    input: &Tensor,
251    to: DType,
252) -> crate::Result<Tensor> {
253    macro_rules! converted {
254        ($variant:ident, $tensor:expr, $map:expr) => {
255            Ok(Tensor::$variant(typed_convert_with_pool(
256                buffers, $tensor, $map,
257            )?))
258        };
259    }
260
261    match (input, to) {
262        (Tensor::F32(t), DType::F32) => Ok(Tensor::F32(t.clone())),
263        (Tensor::F32(t), DType::F64) => converted!(F64, t, |x| x as f64),
264        (Tensor::F32(t), DType::I32) => {
265            validate_real_values_cast_to_i32(t, |x| x as f64)?;
266            converted!(I32, t, |x| x as i32)
267        }
268        (Tensor::F32(t), DType::I64) => {
269            validate_real_values_cast_to_i64(t, |x| x as f64)?;
270            converted!(I64, t, |x| x as i64)
271        }
272        (Tensor::F32(t), DType::Bool) => converted!(Bool, t, |x| x != 0.0),
273        (Tensor::F32(t), DType::C32) => converted!(C32, t, |x| Complex32::new(x, 0.0)),
274        (Tensor::F32(t), DType::C64) => {
275            converted!(C64, t, |x| Complex64::new(x as f64, 0.0))
276        }
277        (Tensor::F64(t), DType::F32) => converted!(F32, t, |x| x as f32),
278        (Tensor::F64(t), DType::F64) => Ok(Tensor::F64(t.clone())),
279        (Tensor::F64(t), DType::I32) => {
280            validate_real_values_cast_to_i32(t, |x| x)?;
281            converted!(I32, t, |x| x as i32)
282        }
283        (Tensor::F64(t), DType::I64) => {
284            validate_real_values_cast_to_i64(t, |x| x)?;
285            converted!(I64, t, |x| x as i64)
286        }
287        (Tensor::F64(t), DType::Bool) => converted!(Bool, t, |x| x != 0.0),
288        (Tensor::F64(t), DType::C32) => {
289            converted!(C32, t, |x| Complex32::new(x as f32, 0.0))
290        }
291        (Tensor::F64(t), DType::C64) => converted!(C64, t, |x| Complex64::new(x, 0.0)),
292        (Tensor::I32(t), DType::F32) => converted!(F32, t, |x| x as f32),
293        (Tensor::I32(t), DType::F64) => converted!(F64, t, |x| x as f64),
294        (Tensor::I32(t), DType::I32) => Ok(Tensor::I32(t.clone())),
295        (Tensor::I32(t), DType::I64) => converted!(I64, t, |x| x as i64),
296        (Tensor::I32(t), DType::Bool) => converted!(Bool, t, |x| x != 0),
297        (Tensor::I32(t), DType::C32) => {
298            converted!(C32, t, |x| Complex32::new(x as f32, 0.0))
299        }
300        (Tensor::I32(t), DType::C64) => {
301            converted!(C64, t, |x| Complex64::new(x as f64, 0.0))
302        }
303        (Tensor::I64(t), DType::F32) => converted!(F32, t, |x| x as f32),
304        (Tensor::I64(t), DType::F64) => converted!(F64, t, |x| x as f64),
305        (Tensor::I64(t), DType::I32) => converted!(I32, t, |x| x as i32),
306        (Tensor::I64(t), DType::I64) => Ok(Tensor::I64(t.clone())),
307        (Tensor::I64(t), DType::Bool) => converted!(Bool, t, |x| x != 0),
308        (Tensor::I64(t), DType::C32) => {
309            converted!(C32, t, |x| Complex32::new(x as f32, 0.0))
310        }
311        (Tensor::I64(t), DType::C64) => {
312            converted!(C64, t, |x| Complex64::new(x as f64, 0.0))
313        }
314        (Tensor::Bool(t), DType::F32) => converted!(F32, t, |x| if x { 1.0 } else { 0.0 }),
315        (Tensor::Bool(t), DType::F64) => converted!(F64, t, |x| if x { 1.0 } else { 0.0 }),
316        (Tensor::Bool(t), DType::I32) => converted!(I32, t, |x| if x { 1 } else { 0 }),
317        (Tensor::Bool(t), DType::I64) => converted!(I64, t, |x| if x { 1 } else { 0 }),
318        (Tensor::Bool(t), DType::Bool) => Ok(Tensor::Bool(t.clone())),
319        (Tensor::Bool(t), DType::C32) => {
320            converted!(C32, t, |x| Complex32::new(if x { 1.0 } else { 0.0 }, 0.0))
321        }
322        (Tensor::Bool(t), DType::C64) => {
323            converted!(C64, t, |x| Complex64::new(if x { 1.0 } else { 0.0 }, 0.0))
324        }
325        (Tensor::C32(t), DType::F32) => converted!(F32, t, |z| z.re),
326        (Tensor::C32(t), DType::F64) => converted!(F64, t, |z| z.re as f64),
327        (Tensor::C32(t), DType::I32) => {
328            validate_real_values_cast_to_i32(t, |z| z.re as f64)?;
329            converted!(I32, t, |z| z.re as i32)
330        }
331        (Tensor::C32(t), DType::I64) => {
332            validate_real_values_cast_to_i64(t, |z| z.re as f64)?;
333            converted!(I64, t, |z| z.re as i64)
334        }
335        (Tensor::C32(t), DType::Bool) => converted!(Bool, t, |z| z.re != 0.0 || z.im != 0.0),
336        (Tensor::C32(t), DType::C32) => Ok(Tensor::C32(t.clone())),
337        (Tensor::C32(t), DType::C64) => {
338            converted!(C64, t, |z| Complex64::new(z.re as f64, z.im as f64))
339        }
340        (Tensor::C64(t), DType::F32) => converted!(F32, t, |z| z.re as f32),
341        (Tensor::C64(t), DType::F64) => converted!(F64, t, |z| z.re),
342        (Tensor::C64(t), DType::I32) => {
343            validate_real_values_cast_to_i32(t, |z| z.re)?;
344            converted!(I32, t, |z| z.re as i32)
345        }
346        (Tensor::C64(t), DType::I64) => {
347            validate_real_values_cast_to_i64(t, |z| z.re)?;
348            converted!(I64, t, |z| z.re as i64)
349        }
350        (Tensor::C64(t), DType::Bool) => converted!(Bool, t, |z| z.re != 0.0 || z.im != 0.0),
351        (Tensor::C64(t), DType::C32) => {
352            converted!(C32, t, |z| Complex32::new(z.re as f32, z.im as f32))
353        }
354        (Tensor::C64(t), DType::C64) => Ok(Tensor::C64(t.clone())),
355    }
356}
357
358fn validate_real_values_cast_to_i32<S: Copy>(
359    tensor: &TypedTensor<S>,
360    real: impl Fn(S) -> f64,
361) -> crate::Result<()> {
362    for &value in typed_host_data("cast", tensor)? {
363        validate_real_cast_to_i32(real(value))?;
364    }
365    Ok(())
366}
367
368fn validate_real_values_cast_to_i64<S: Copy>(
369    tensor: &TypedTensor<S>,
370    real: impl Fn(S) -> f64,
371) -> crate::Result<()> {
372    for &value in typed_host_data("cast", tensor)? {
373        validate_real_cast_to_i64(real(value))?;
374    }
375    Ok(())
376}
377
378fn validate_real_cast_to_i32(value: f64) -> crate::Result<()> {
379    if !value.is_finite() {
380        return Err(invalid_cast_value(format!(
381            "real value must be finite when casting to i32, got {value}"
382        )));
383    }
384    if value < i32::MIN as f64 || value > i32::MAX as f64 {
385        return Err(invalid_cast_value(format!(
386            "real value {value} is out of i32 range"
387        )));
388    }
389    Ok(())
390}
391
392fn validate_real_cast_to_i64(value: f64) -> crate::Result<()> {
393    const I64_MIN_F64: f64 = -9_223_372_036_854_775_808.0;
394    const I64_MAX_EXCLUSIVE_F64: f64 = 9_223_372_036_854_775_808.0;
395
396    if !value.is_finite() {
397        return Err(invalid_cast_value(format!(
398            "real value must be finite when casting to i64, got {value}"
399        )));
400    }
401    if !(I64_MIN_F64..I64_MAX_EXCLUSIVE_F64).contains(&value) {
402        return Err(invalid_cast_value(format!(
403            "real value {value} is out of i64 range"
404        )));
405    }
406    Ok(())
407}
408
409fn invalid_cast_value(message: String) -> crate::Error {
410    crate::Error::InvalidConfig {
411        op: "cast",
412        message,
413    }
414}
415
416pub fn extract_diagonal(input: &Tensor, axis_a: usize, axis_b: usize) -> crate::Result<Tensor> {
417    with_local_pool(|buffers| extract_diagonal_with_pool(buffers, input, axis_a, axis_b))
418}
419
420pub(crate) fn extract_diagonal_with_pool(
421    buffers: &mut BufferPool,
422    input: &Tensor,
423    axis_a: usize,
424    axis_b: usize,
425) -> crate::Result<Tensor> {
426    dispatch_tensor_unary_result!(input, |t| typed_extract_diagonal_with_pool(
427        buffers, t, axis_a, axis_b
428    ))
429}
430
431pub fn embed_diagonal(input: &Tensor, axis_a: usize, axis_b: usize) -> crate::Result<Tensor> {
432    with_local_pool(|buffers| embed_diagonal_with_pool(buffers, input, axis_a, axis_b))
433}
434
435pub(crate) fn embed_diagonal_with_pool(
436    buffers: &mut BufferPool,
437    input: &Tensor,
438    axis_a: usize,
439    axis_b: usize,
440) -> crate::Result<Tensor> {
441    dispatch_tensor_unary_with_bool_special_result!(
442        input,
443        |t| typed_embed_diagonal_with_pool(buffers, t, axis_a, axis_b),
444        bool | t
445            | typed_embed_diagonal_impl(t, axis_a, axis_b, |shape| {
446                filled_tensor_from_pool(buffers, "embed_diagonal", shape, false)
447            })
448    )
449}
450
451pub fn tril(input: &Tensor, k: i64) -> crate::Result<Tensor> {
452    with_local_pool(|buffers| tril_with_pool(buffers, input, k))
453}
454
455pub(crate) fn tril_with_pool(
456    buffers: &mut BufferPool,
457    input: &Tensor,
458    k: i64,
459) -> crate::Result<Tensor> {
460    dispatch_tensor_unary_with_bool_special_result!(
461        input,
462        |t| typed_tril_with_pool(buffers, t, k),
463        bool | t | typed_triangular_mask_with_fill_pool(buffers, t, k, false, false)
464    )
465}
466
467pub fn triu(input: &Tensor, k: i64) -> crate::Result<Tensor> {
468    with_local_pool(|buffers| triu_with_pool(buffers, input, k))
469}
470
471pub(crate) fn triu_with_pool(
472    buffers: &mut BufferPool,
473    input: &Tensor,
474    k: i64,
475) -> crate::Result<Tensor> {
476    dispatch_tensor_unary_with_bool_special_result!(
477        input,
478        |t| typed_triu_with_pool(buffers, t, k),
479        bool | t | typed_triangular_mask_with_fill_pool(buffers, t, k, true, false)
480    )
481}
482
483#[cfg(test)]
484pub(crate) fn typed_transpose<T: Copy + Clone + Send + Sync>(
485    tensor: &TypedTensor<T>,
486    perm: &[usize],
487) -> crate::Result<TypedTensor<T>> {
488    validate_permutation("transpose", perm, tensor.shape().len())?;
489    let src = host_view("transpose", tensor)?;
490    let permuted = src
491        .permute(perm)
492        .map_err(|err| crate::Error::backend_failure("transpose", err))?;
493    // SAFETY: copy_into overwrites every output element.
494    let out = unsafe { typed_array_uninit(permuted.dims()) };
495    copy_view_to_array("transpose", out, &permuted)
496}
497
498fn typed_transpose_view_impl<T, R>(
499    view: &TypedTensorView<'_, T, R>,
500    perm: &[usize],
501    make_out: impl FnOnce(&[usize]) -> crate::Result<strided_kernel::StridedArray<T>>,
502) -> crate::Result<TypedTensor<T>>
503where
504    T: Copy + Clone + Send + Sync + 'static,
505    R: TensorRank,
506{
507    validate_permutation("transpose", perm, view.shape().len())?;
508    let src = typed_view_from_view("transpose", view)?;
509    let permuted = src
510        .permute(perm)
511        .map_err(|err| crate::Error::backend_failure("transpose", err))?;
512    checked_shape_product("transpose", "output shape", permuted.dims())?;
513    // SAFETY: copy_into overwrites every output element.
514    let out = make_out(permuted.dims())?;
515    copy_view_to_array("transpose", out, &permuted)
516}
517
518pub(crate) fn typed_transpose_with_pool<T>(
519    buffers: &mut BufferPool,
520    tensor: &TypedTensor<T>,
521    perm: &[usize],
522) -> crate::Result<TypedTensor<T>>
523where
524    T: Copy + Clone + PoolScalar + 'static,
525{
526    typed_transpose_view_with_pool(buffers, &tensor.as_view(), perm)
527}
528
529pub(crate) fn typed_transpose_view_with_pool<T, R>(
530    buffers: &mut BufferPool,
531    view: &TypedTensorView<'_, T, R>,
532    perm: &[usize],
533) -> crate::Result<TypedTensor<T>>
534where
535    T: Copy + Clone + PoolScalar + 'static,
536    R: TensorRank,
537{
538    typed_transpose_view_impl(view, perm, |shape| unsafe {
539        // SAFETY: transpose materialization copies every output element before returning.
540        typed_array_uninit_from_pool(buffers, shape)
541    })
542}
543
544pub fn typed_reshape<T: Clone + 'static>(
545    tensor: &TypedTensor<T>,
546    shape: &[usize],
547) -> crate::Result<TypedTensor<T>> {
548    let old_n = checked_shape_product("reshape", "input shape", tensor.shape())?;
549    let new_n = checked_shape_product("reshape", "output shape", shape)?;
550    if old_n != new_n {
551        return Err(crate::Error::ShapeMismatch {
552            op: "reshape",
553            lhs: tensor.shape().to_vec(),
554            rhs: shape.to_vec(),
555        });
556    }
557    TypedTensor::from_buffer_col_major(
558        shape.to_vec(),
559        tensor.buffer().clone(),
560        tensor.placement().clone(),
561    )
562}
563
564#[cfg(test)]
565pub(crate) fn typed_broadcast_in_dim<T: Copy + Clone + Send + Sync>(
566    tensor: &TypedTensor<T>,
567    shape: &[usize],
568    dims: &[usize],
569) -> crate::Result<TypedTensor<T>> {
570    typed_broadcast_in_dim_impl(tensor, shape, dims, |shape| unsafe {
571        // SAFETY: broadcast materialization writes every output element before returning.
572        Ok(typed_array_uninit(shape))
573    })
574}
575
576pub(crate) fn typed_broadcast_in_dim_with_pool<T>(
577    buffers: &mut BufferPool,
578    tensor: &TypedTensor<T>,
579    shape: &[usize],
580    dims: &[usize],
581) -> crate::Result<TypedTensor<T>>
582where
583    T: Copy + Clone + PoolScalar,
584{
585    typed_broadcast_in_dim_impl(tensor, shape, dims, |shape| unsafe {
586        // SAFETY: broadcast materialization writes every output element before returning.
587        typed_array_uninit_from_pool(buffers, shape)
588    })
589}
590
591fn typed_broadcast_in_dim_impl<T>(
592    tensor: &TypedTensor<T>,
593    shape: &[usize],
594    dims: &[usize],
595    make_out: impl FnOnce(&[usize]) -> crate::Result<strided_kernel::StridedArray<T>>,
596) -> crate::Result<TypedTensor<T>>
597where
598    T: Copy + Clone + Send + Sync,
599{
600    validate_rank("broadcast_in_dim", tensor.shape().len(), dims.len())?;
601    let mut seen = vec![false; shape.len()];
602    let mut base_dims = vec![1usize; shape.len()];
603    let mut base_strides = vec![0isize; shape.len()];
604    let source_strides = col_major_strides(tensor.shape());
605    for (src_axis, &dst_axis) in dims.iter().enumerate() {
606        validate_axis("broadcast_in_dim", dst_axis, shape.len())?;
607        if seen[dst_axis] {
608            return Err(crate::Error::DuplicateAxis {
609                op: "broadcast_in_dim",
610                axis: dst_axis,
611                role: "dims",
612            });
613        }
614        seen[dst_axis] = true;
615        let source_dim = tensor.shape()[src_axis];
616        let target_dim = shape[dst_axis];
617        if source_dim != target_dim && source_dim != 1 {
618            return Err(crate::Error::ShapeMismatch {
619                op: "broadcast_in_dim",
620                lhs: tensor.shape().to_vec(),
621                rhs: shape.to_vec(),
622            });
623        }
624        base_dims[dst_axis] = source_dim;
625        base_strides[dst_axis] = source_strides[src_axis];
626    }
627    let base: StridedView<'_, T, Identity> = match tensor.buffer() {
628        crate::Buffer::Host(data) => {
629            StridedView::new(data.as_slice(), &base_dims, &base_strides, 0)
630                .map_err(|err| crate::Error::backend_failure("broadcast_in_dim", err))?
631        }
632        crate::Buffer::Backend(_) => return Err(cpu_backend_buffer_error("broadcast_in_dim")),
633    };
634    let broadcast: StridedView<'_, T, Identity> = base
635        .broadcast(shape)
636        .map_err(|err| crate::Error::backend_failure("broadcast_in_dim", err))?;
637    checked_shape_product("broadcast_in_dim", "output shape", shape)?;
638    // SAFETY: copy_into overwrites every output element.
639    let mut out = make_out(shape)?;
640    copy_into(&mut out.view_mut(), &broadcast)
641        .map_err(|err| crate::Error::backend_failure("broadcast_in_dim", err))?;
642    Ok(tensor_from_array(out))
643}
644
645fn typed_convert_with_pool<S, T>(
646    buffers: &mut BufferPool,
647    tensor: &TypedTensor<S>,
648    f: impl Fn(S) -> T + Sync,
649) -> crate::Result<TypedTensor<T>>
650where
651    S: Copy + Send + Sync,
652    T: Copy + Clone + PoolScalar,
653{
654    // SAFETY: map_into overwrites every output element.
655    let mut out = unsafe { typed_array_uninit_from_pool(buffers, tensor.shape()) }?;
656    map_into(&mut out.view_mut(), &typed_view("convert", tensor)?, f)
657        .map_err(|err| crate::Error::backend_failure("convert", err))?;
658    Ok(tensor_from_array(out))
659}
660
661#[cfg(test)]
662pub(crate) fn typed_extract_diagonal<T: Copy + Clone + Send + Sync>(
663    tensor: &TypedTensor<T>,
664    axis_a: usize,
665    axis_b: usize,
666) -> crate::Result<TypedTensor<T>> {
667    validate_axis("extract_diagonal", axis_a, tensor.shape().len())?;
668    validate_axis("extract_diagonal", axis_b, tensor.shape().len())?;
669    validate_axes_distinct("extract_diagonal", axis_a, axis_b)?;
670
671    let diag = host_view("extract_diagonal", tensor)?
672        .diagonal_view(&[(axis_a, axis_b)])
673        .map_err(|err| crate::Error::backend_failure("extract_diagonal", err))?;
674    // SAFETY: copy_into overwrites every output element.
675    let mut out = unsafe { typed_array_uninit(diag.dims()) };
676    copy_into(&mut out.view_mut(), &diag)
677        .map_err(|err| crate::Error::backend_failure("extract_diagonal", err))?;
678    Ok(tensor_from_array(out))
679}
680
681pub(crate) fn typed_extract_diagonal_with_pool<T>(
682    buffers: &mut BufferPool,
683    tensor: &TypedTensor<T>,
684    axis_a: usize,
685    axis_b: usize,
686) -> crate::Result<TypedTensor<T>>
687where
688    T: Copy + Clone + PoolScalar,
689{
690    validate_axis("extract_diagonal", axis_a, tensor.shape().len())?;
691    validate_axis("extract_diagonal", axis_b, tensor.shape().len())?;
692    validate_axes_distinct("extract_diagonal", axis_a, axis_b)?;
693
694    let diag = host_view("extract_diagonal", tensor)?
695        .diagonal_view(&[(axis_a, axis_b)])
696        .map_err(|err| crate::Error::backend_failure("extract_diagonal", err))?;
697    // SAFETY: copy_into overwrites every output element.
698    let mut out = unsafe { typed_array_uninit_from_pool(buffers, diag.dims()) }?;
699    copy_into(&mut out.view_mut(), &diag)
700        .map_err(|err| crate::Error::backend_failure("extract_diagonal", err))?;
701    Ok(tensor_from_array(out))
702}
703
704#[cfg(test)]
705pub(crate) fn typed_embed_diagonal<T: Copy + Zero + Clone>(
706    tensor: &TypedTensor<T>,
707    axis_a: usize,
708    axis_b: usize,
709) -> crate::Result<TypedTensor<T>> {
710    typed_embed_diagonal_impl(tensor, axis_a, axis_b, TypedTensor::zeros)
711}
712
713pub(crate) fn typed_embed_diagonal_with_pool<T>(
714    buffers: &mut BufferPool,
715    tensor: &TypedTensor<T>,
716    axis_a: usize,
717    axis_b: usize,
718) -> crate::Result<TypedTensor<T>>
719where
720    T: Copy + Zero + Clone + PoolScalar + 'static,
721{
722    typed_embed_diagonal_impl(tensor, axis_a, axis_b, |shape| {
723        zeroed_tensor_from_pool(buffers, "embed_diagonal", shape)
724    })
725}
726
727fn typed_embed_diagonal_impl<T>(
728    tensor: &TypedTensor<T>,
729    axis_a: usize,
730    axis_b: usize,
731    make_zeroed: impl FnOnce(Vec<usize>) -> crate::Result<TypedTensor<T>>,
732) -> crate::Result<TypedTensor<T>>
733where
734    T: Copy + Clone,
735{
736    validate_axis("embed_diagonal", axis_a, tensor.shape().len())?;
737    if axis_b > tensor.shape().len() {
738        return Err(crate::Error::AxisOutOfBounds {
739            op: "embed_diagonal",
740            axis: axis_b,
741            rank: tensor.shape().len(),
742        });
743    }
744
745    let n = tensor.shape()[axis_a];
746    let mut out_shape = tensor.shape().to_vec();
747    out_shape.insert(axis_b, n);
748    let mut out = make_zeroed(out_shape)?;
749
750    let in_rank = tensor.shape().len();
751    let out_rank = out.shape().len();
752    let mut in_idx = vec![0usize; in_rank];
753    let mut out_idx = vec![0usize; out_rank];
754
755    let input_data = match tensor.buffer() {
756        crate::Buffer::Host(data) => data.as_slice(),
757        crate::Buffer::Backend(_) => return Err(cpu_backend_buffer_error("embed_diagonal")),
758    };
759
760    // Intentionally sequential: embed_diagonal writes a sparse diagonal subset
761    // into a zeroed output and has no current strided-kernel parallel primitive.
762    for (flat, value) in input_data
763        .iter()
764        .copied()
765        .enumerate()
766        .take(tensor.n_elements())
767    {
768        flat_to_multi(flat, tensor.shape(), &mut in_idx);
769        let diag_val = in_idx[axis_a];
770        let mut src_axis = 0usize;
771        for (out_axis, out_slot) in out_idx.iter_mut().enumerate().take(out_rank) {
772            if out_axis == axis_b {
773                *out_slot = diag_val;
774            } else {
775                *out_slot = in_idx[src_axis];
776                src_axis += 1;
777            }
778        }
779        *out.get_mut(&out_idx)? = value;
780    }
781    Ok(out)
782}
783
784#[cfg(test)]
785pub(crate) fn typed_tril<T: Copy + Zero + Clone>(
786    tensor: &TypedTensor<T>,
787    k: i64,
788) -> crate::Result<TypedTensor<T>> {
789    typed_triangular_mask(tensor, k, false)
790}
791
792pub(crate) fn typed_tril_with_pool<T>(
793    buffers: &mut BufferPool,
794    tensor: &TypedTensor<T>,
795    k: i64,
796) -> crate::Result<TypedTensor<T>>
797where
798    T: Copy + Zero + Clone + PoolScalar + 'static,
799{
800    typed_triangular_mask_with_fill_pool(buffers, tensor, k, false, T::zero())
801}
802
803#[cfg(test)]
804pub(crate) fn typed_triu<T: Copy + Zero + Clone>(
805    tensor: &TypedTensor<T>,
806    k: i64,
807) -> crate::Result<TypedTensor<T>> {
808    typed_triangular_mask(tensor, k, true)
809}
810
811pub(crate) fn typed_triu_with_pool<T>(
812    buffers: &mut BufferPool,
813    tensor: &TypedTensor<T>,
814    k: i64,
815) -> crate::Result<TypedTensor<T>>
816where
817    T: Copy + Zero + Clone + PoolScalar + 'static,
818{
819    typed_triangular_mask_with_fill_pool(buffers, tensor, k, true, T::zero())
820}
821
822#[cfg(test)]
823fn typed_triangular_mask<T: Copy + Zero + Clone>(
824    tensor: &TypedTensor<T>,
825    k: i64,
826    upper: bool,
827) -> crate::Result<TypedTensor<T>> {
828    let op = if upper { "triu" } else { "tril" };
829    if tensor.shape().len() < 2 {
830        return Err(crate::Error::RankMismatch {
831            op,
832            expected: 2,
833            actual: tensor.shape().len(),
834        });
835    }
836
837    let rows = tensor.shape()[0];
838    let cols = tensor.shape()[1];
839    if tensor.shape().contains(&0) {
840        return Ok(tensor.clone());
841    }
842
843    let (batch_count, block_size) = checked_triangular_extent(op, tensor.shape(), rows, cols)?;
844    let mut out = tensor.clone();
845    let data = out.host_data_mut()?;
846
847    // Intentionally sequential: triangular masks are index-dependent in the
848    // innermost matrix plane and remain a dedicated CPU-kernel exception.
849    for batch_idx in 0..batch_count {
850        for col in 0..cols {
851            let boundary = col as i128 - k as i128;
852            for row in 0..rows {
853                let row_idx = row;
854                let row = row_idx as i128;
855                let keep = if upper {
856                    row <= boundary
857                } else {
858                    row >= boundary
859                };
860                if !keep {
861                    let offset =
862                        checked_triangular_offset(op, batch_idx, block_size, col, rows, row_idx)?;
863                    data[offset] = T::zero();
864                }
865            }
866        }
867    }
868
869    Ok(out)
870}
871
872fn typed_triangular_mask_with_fill_pool<T>(
873    buffers: &mut BufferPool,
874    tensor: &TypedTensor<T>,
875    k: i64,
876    upper: bool,
877    fill: T,
878) -> crate::Result<TypedTensor<T>>
879where
880    T: Copy + Clone + PoolScalar + 'static,
881{
882    let op = if upper { "triu" } else { "tril" };
883    if tensor.shape().len() < 2 {
884        return Err(crate::Error::RankMismatch {
885            op,
886            expected: 2,
887            actual: tensor.shape().len(),
888        });
889    }
890
891    let rows = tensor.shape()[0];
892    let cols = tensor.shape()[1];
893    if tensor.shape().contains(&0) {
894        return Ok(tensor.clone());
895    }
896
897    let (batch_count, block_size) = checked_triangular_extent(op, tensor.shape(), rows, cols)?;
898    let mut out = clone_host_tensor_from_pool(buffers, op, tensor)?;
899    let data = out.host_data_mut()?;
900
901    // Intentionally sequential: triangular masks are index-dependent in the
902    // innermost matrix plane and remain a dedicated CPU-kernel exception.
903    for batch_idx in 0..batch_count {
904        for col in 0..cols {
905            let boundary = col as i128 - k as i128;
906            for row in 0..rows {
907                let row_idx = row;
908                let row = row_idx as i128;
909                let keep = if upper {
910                    row <= boundary
911                } else {
912                    row >= boundary
913                };
914                if !keep {
915                    let offset =
916                        checked_triangular_offset(op, batch_idx, block_size, col, rows, row_idx)?;
917                    data[offset] = fill;
918                }
919            }
920        }
921    }
922
923    Ok(out)
924}
925
926fn checked_triangular_extent(
927    op: &'static str,
928    shape: &[usize],
929    rows: usize,
930    cols: usize,
931) -> crate::Result<(usize, usize)> {
932    let batch_count = shape[2..].iter().try_fold(1usize, |acc, &dim| {
933        acc.checked_mul(dim)
934            .ok_or_else(|| crate::Error::InvalidConfig {
935                op,
936                message: format!("batch extent overflows usize: {acc} * {dim}"),
937            })
938    })?;
939    let block_size = rows
940        .checked_mul(cols)
941        .ok_or_else(|| crate::Error::InvalidConfig {
942            op,
943            message: format!("matrix block size overflows usize: {rows} * {cols}"),
944        })?;
945    Ok((batch_count, block_size))
946}
947
948fn checked_triangular_offset(
949    op: &'static str,
950    batch_idx: usize,
951    block_size: usize,
952    col: usize,
953    rows: usize,
954    row_idx: usize,
955) -> crate::Result<usize> {
956    let base = batch_idx
957        .checked_mul(block_size)
958        .ok_or_else(|| crate::Error::InvalidConfig {
959            op,
960            message: format!("batch offset overflows usize: {batch_idx} * {block_size}"),
961        })?;
962    let col_offset = col
963        .checked_mul(rows)
964        .ok_or_else(|| crate::Error::InvalidConfig {
965            op,
966            message: format!("column offset overflows usize: {col} * {rows}"),
967        })?;
968    base.checked_add(col_offset)
969        .and_then(|offset| offset.checked_add(row_idx))
970        .ok_or_else(|| crate::Error::InvalidConfig {
971            op,
972            message: "triangular mask offset overflows usize".to_string(),
973        })
974}