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 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 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
214pub 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 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 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 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 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 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 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 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 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 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 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 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 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}