Skip to main content

ruda_tensor_host/ops/
int.rs

1//! Int tensor operations for the Host backend.
2
3use alloc::vec::Vec;
4use ruda_tensor::{
5    Distribution, ExecutionError, Scalar, TensorData,
6    ops::IntTensorOps,
7    tensor::{BoolTensor, Device, FloatTensor, IntTensor},
8};
9use ruda_tensor::{IntDType, Shape, Slice};
10
11use crate::{Host, HostTensor, ops::matmul};
12
13impl IntTensorOps<Host> for Host {
14    fn int_from_data(data: TensorData, _device: &Device<Host>) -> IntTensor<Host> {
15        HostTensor::from_data(data)
16    }
17
18    async fn int_into_data(tensor: IntTensor<Host>) -> Result<TensorData, ExecutionError> {
19        Ok(tensor.into_data())
20    }
21
22    fn int_device(_tensor: &IntTensor<Host>) -> Device<Host> {
23        Default::default()
24    }
25
26    fn int_to_device(tensor: IntTensor<Host>, _device: &Device<Host>) -> IntTensor<Host> {
27        tensor
28    }
29
30    fn int_cat(tensors: Vec<IntTensor<Host>>, dim: usize) -> IntTensor<Host> {
31        crate::ops::cat::cat(tensors, dim)
32    }
33
34    fn int_reshape(tensor: IntTensor<Host>, shape: Shape) -> IntTensor<Host> {
35        tensor.reshape(shape)
36    }
37
38    fn int_slice(tensor: IntTensor<Host>, slices: &[Slice]) -> IntTensor<Host> {
39        crate::ops::slice::slice(tensor, slices)
40    }
41
42    fn int_empty(shape: Shape, _device: &Device<Host>, dtype: IntDType) -> IntTensor<Host> {
43        HostTensor::empty(shape, dtype.into())
44    }
45
46    fn int_mask_where(
47        tensor: IntTensor<Host>,
48        mask: BoolTensor<Host>,
49        value: IntTensor<Host>,
50    ) -> IntTensor<Host> {
51        ruprim_host::mask::dispatch::int_mask_where(tensor, mask, value)
52    }
53
54    fn int_mask_fill(
55        tensor: IntTensor<Host>,
56        mask: BoolTensor<Host>,
57        value: Scalar,
58    ) -> IntTensor<Host> {
59        ruprim_host::mask::dispatch::int_mask_fill(tensor, mask, value)
60    }
61
62    fn int_slice_assign(
63        tensor: IntTensor<Host>,
64        slices: &[Slice],
65        value: IntTensor<Host>,
66    ) -> IntTensor<Host> {
67        crate::ops::slice::slice_assign(tensor, slices, value)
68    }
69
70    /// Gather ints along `dim` at the given indices.
71    ///
72    /// The `tensor` dispatches on its own int dtype (I8/I16/I32/I64 signed or
73    /// U8/U16/U32/U64 unsigned). The `indices` tensor may be any of those
74    /// widths too - it's normalised to `isize` by the shared `read_indices`
75    /// helper in `ops::gather_scatter` before the kernel runs, so callers are
76    /// not required to pre-convert to I64.
77    fn int_gather(
78        dim: usize,
79        tensor: IntTensor<Host>,
80        indices: IntTensor<Host>,
81    ) -> IntTensor<Host> {
82        ruprim_host::gather_scatter::dispatch_int::int_gather(dim, tensor, indices)
83    }
84
85    /// Scatter-add int values at the given indices along `dim`.
86    ///
87    /// `tensor` and `value` must share the same int dtype; `indices` may be
88    /// any supported int width. See [`int_gather`](Self::int_gather) for the
89    /// full index-width policy.
90    fn int_scatter_add(
91        dim: usize,
92        tensor: IntTensor<Host>,
93        indices: IntTensor<Host>,
94        value: IntTensor<Host>,
95    ) -> IntTensor<Host> {
96        ruprim_host::gather_scatter::dispatch_int::int_scatter_add(dim, tensor, indices, value)
97    }
98
99    fn int_scatter_nd(
100        data: IntTensor<Host>,
101        indices: IntTensor<Host>,
102        values: IntTensor<Host>,
103        reduction: ruda_tensor::tensor::IndexingUpdateOp,
104    ) -> IntTensor<Host> {
105        ruprim_host::gather_scatter::dispatch_int::int_scatter_nd(data, indices, values, reduction)
106    }
107
108    fn int_gather_nd(data: IntTensor<Host>, indices: IntTensor<Host>) -> IntTensor<Host> {
109        ruprim_host::gather_scatter::dispatch_int::int_gather_nd(data, indices)
110    }
111
112    /// Select ints along `dim` by a 1D index tensor.
113    ///
114    /// The `indices` tensor may be any supported int width. See
115    /// [`int_gather`](Self::int_gather) for the full index-width policy.
116    fn int_select(
117        tensor: IntTensor<Host>,
118        dim: usize,
119        indices: IntTensor<Host>,
120    ) -> IntTensor<Host> {
121        ruprim_host::gather_scatter::dispatch_int::int_select(tensor, dim, indices)
122    }
123
124    /// Select-add int values at a 1D index tensor along `dim`.
125    ///
126    /// `tensor` and `value` must share the same int dtype; `indices` may be
127    /// any supported int width. See [`int_gather`](Self::int_gather) for the
128    /// full index-width policy.
129    fn int_select_add(
130        tensor: IntTensor<Host>,
131        dim: usize,
132        indices: IntTensor<Host>,
133        value: IntTensor<Host>,
134    ) -> IntTensor<Host> {
135        ruprim_host::gather_scatter::dispatch_int::int_select_add(tensor, dim, indices, value)
136    }
137
138    fn int_equal(
139        lhs: IntTensor<Host>,
140        rhs: IntTensor<Host>,
141        out_dtype: ruda_tensor::BoolDType,
142    ) -> BoolTensor<Host> {
143        crate::ops::comparison::int_equal(lhs, rhs, out_dtype)
144    }
145
146    fn int_equal_elem(
147        lhs: IntTensor<Host>,
148        rhs: Scalar,
149        out_dtype: ruda_tensor::BoolDType,
150    ) -> BoolTensor<Host> {
151        ruprim_host::comparison::dispatch::int_equal_elem(lhs, rhs, out_dtype)
152    }
153
154    fn int_greater(
155        lhs: IntTensor<Host>,
156        rhs: IntTensor<Host>,
157        out_dtype: ruda_tensor::BoolDType,
158    ) -> BoolTensor<Host> {
159        crate::ops::comparison::int_greater(lhs, rhs, out_dtype)
160    }
161
162    fn int_greater_elem(
163        lhs: IntTensor<Host>,
164        rhs: Scalar,
165        out_dtype: ruda_tensor::BoolDType,
166    ) -> BoolTensor<Host> {
167        ruprim_host::comparison::dispatch::int_greater_elem(lhs, rhs, out_dtype)
168    }
169
170    fn int_greater_equal(
171        lhs: IntTensor<Host>,
172        rhs: IntTensor<Host>,
173        out_dtype: ruda_tensor::BoolDType,
174    ) -> BoolTensor<Host> {
175        crate::ops::comparison::int_greater_equal(lhs, rhs, out_dtype)
176    }
177
178    fn int_greater_equal_elem(
179        lhs: IntTensor<Host>,
180        rhs: Scalar,
181        out_dtype: ruda_tensor::BoolDType,
182    ) -> BoolTensor<Host> {
183        ruprim_host::comparison::dispatch::int_greater_equal_elem(lhs, rhs, out_dtype)
184    }
185
186    fn int_lower(
187        lhs: IntTensor<Host>,
188        rhs: IntTensor<Host>,
189        out_dtype: ruda_tensor::BoolDType,
190    ) -> BoolTensor<Host> {
191        crate::ops::comparison::int_lower(lhs, rhs, out_dtype)
192    }
193
194    fn int_lower_elem(
195        lhs: IntTensor<Host>,
196        rhs: Scalar,
197        out_dtype: ruda_tensor::BoolDType,
198    ) -> BoolTensor<Host> {
199        ruprim_host::comparison::dispatch::int_lower_elem(lhs, rhs, out_dtype)
200    }
201
202    fn int_lower_equal(
203        lhs: IntTensor<Host>,
204        rhs: IntTensor<Host>,
205        out_dtype: ruda_tensor::BoolDType,
206    ) -> BoolTensor<Host> {
207        crate::ops::comparison::int_lower_equal(lhs, rhs, out_dtype)
208    }
209
210    fn int_lower_equal_elem(
211        lhs: IntTensor<Host>,
212        rhs: Scalar,
213        out_dtype: ruda_tensor::BoolDType,
214    ) -> BoolTensor<Host> {
215        ruprim_host::comparison::dispatch::int_lower_equal_elem(lhs, rhs, out_dtype)
216    }
217
218    fn int_add(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
219        ruprim_host::binary::dispatch_int::int_add(lhs, rhs)
220    }
221
222    fn int_add_scalar(lhs: IntTensor<Host>, rhs: Scalar) -> IntTensor<Host> {
223        ruprim_host::binary::dispatch_int::int_add_scalar(lhs, rhs)
224    }
225
226    fn int_sub(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
227        ruprim_host::binary::dispatch_int::int_sub(lhs, rhs)
228    }
229
230    fn int_sub_scalar(lhs: IntTensor<Host>, rhs: Scalar) -> IntTensor<Host> {
231        ruprim_host::binary::dispatch_int::int_sub_scalar(lhs, rhs)
232    }
233
234    fn int_mul(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
235        ruprim_host::binary::dispatch_int::int_mul(lhs, rhs)
236    }
237
238    fn int_mul_scalar(lhs: IntTensor<Host>, rhs: Scalar) -> IntTensor<Host> {
239        ruprim_host::binary::dispatch_int::int_mul_scalar(lhs, rhs)
240    }
241
242    fn int_div(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
243        ruprim_host::binary::dispatch_int::int_div(lhs, rhs)
244    }
245
246    fn int_div_scalar(lhs: IntTensor<Host>, rhs: Scalar) -> IntTensor<Host> {
247        ruprim_host::binary::dispatch_int::int_div_scalar(lhs, rhs)
248    }
249
250    fn int_remainder(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
251        ruprim_host::binary::dispatch_int::int_remainder(lhs, rhs)
252    }
253
254    fn int_remainder_scalar(lhs: IntTensor<Host>, rhs: Scalar) -> IntTensor<Host> {
255        ruprim_host::binary::dispatch_int::int_remainder_scalar(lhs, rhs)
256    }
257
258    // Precision limits: i64/u64 > 2^24 for f32/f16/bf16, > 2^53 for f64.
259    fn int_into_float(
260        tensor: IntTensor<Host>,
261        out_dtype: ruda_tensor::FloatDType,
262    ) -> FloatTensor<Host> {
263        ruprim_host::cast::int_into_float(tensor, out_dtype)
264    }
265
266    fn int_swap_dims(tensor: IntTensor<Host>, dim1: usize, dim2: usize) -> IntTensor<Host> {
267        tensor.transpose(dim1, dim2)
268    }
269
270    fn int_permute(tensor: IntTensor<Host>, axes: &[usize]) -> IntTensor<Host> {
271        tensor.permute(axes)
272    }
273
274    fn int_flip(tensor: IntTensor<Host>, axes: &[usize]) -> IntTensor<Host> {
275        crate::ops::flip::flip(tensor, axes)
276    }
277
278    fn int_random(
279        shape: Shape,
280        distribution: Distribution,
281        _device: &Device<Host>,
282        dtype: IntDType,
283    ) -> IntTensor<Host> {
284        rurand_host::int_random(shape, distribution, dtype)
285    }
286
287    fn int_expand(tensor: IntTensor<Host>, shape: Shape) -> IntTensor<Host> {
288        crate::ops::expand::expand(tensor, shape)
289    }
290
291    fn int_matmul(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
292        matmul::int_matmul(lhs, rhs)
293    }
294
295    fn int_sum(tensor: IntTensor<Host>) -> IntTensor<Host> {
296        crate::ops::reduce::sum(tensor)
297    }
298
299    fn int_sum_dim(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
300        crate::ops::reduce::sum_dim(tensor, dim)
301    }
302
303    fn int_prod(tensor: IntTensor<Host>) -> IntTensor<Host> {
304        crate::ops::reduce::prod(tensor)
305    }
306
307    fn int_prod_dim(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
308        crate::ops::reduce::prod_dim(tensor, dim)
309    }
310
311    fn int_mean_dim(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
312        crate::ops::reduce::mean_dim(tensor, dim)
313    }
314
315    fn int_cumsum(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
316        ruprim_host::cumulative::dispatch::int_cumsum(tensor, dim)
317    }
318
319    fn int_cumprod(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
320        ruprim_host::cumulative::dispatch::int_cumprod(tensor, dim)
321    }
322
323    fn int_cummin(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
324        ruprim_host::cumulative::dispatch::int_cummin(tensor, dim)
325    }
326
327    fn int_cummax(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
328        ruprim_host::cumulative::dispatch::int_cummax(tensor, dim)
329    }
330
331    fn int_argmax(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
332        crate::ops::reduce::argmax(tensor, dim)
333    }
334
335    fn int_argtopk(tensor: IntTensor<Host>, dim: usize, k: usize) -> IntTensor<Host> {
336        ruprim_host::sort::dispatch::int_argtopk(tensor, dim, k)
337    }
338
339    fn int_argmin(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
340        crate::ops::reduce::argmin(tensor, dim)
341    }
342
343    fn int_abs(tensor: IntTensor<Host>) -> IntTensor<Host> {
344        crate::ops::unary::int_abs(tensor)
345    }
346
347    fn bitwise_and(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
348        ruprim_host::binary::dispatch_int::bitwise_and(lhs, rhs)
349    }
350
351    fn bitwise_and_scalar(lhs: IntTensor<Host>, rhs: Scalar) -> IntTensor<Host> {
352        ruprim_host::binary::dispatch_int::bitwise_and_scalar(lhs, rhs)
353    }
354
355    fn bitwise_or(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
356        ruprim_host::binary::dispatch_int::bitwise_or(lhs, rhs)
357    }
358
359    fn bitwise_or_scalar(lhs: IntTensor<Host>, rhs: Scalar) -> IntTensor<Host> {
360        ruprim_host::binary::dispatch_int::bitwise_or_scalar(lhs, rhs)
361    }
362
363    fn bitwise_xor(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
364        ruprim_host::binary::dispatch_int::bitwise_xor(lhs, rhs)
365    }
366
367    fn bitwise_xor_scalar(lhs: IntTensor<Host>, rhs: Scalar) -> IntTensor<Host> {
368        ruprim_host::binary::dispatch_int::bitwise_xor_scalar(lhs, rhs)
369    }
370
371    fn bitwise_not(tensor: IntTensor<Host>) -> IntTensor<Host> {
372        ruprim_host::binary::dispatch_int::bitwise_not(tensor)
373    }
374
375    // Shift amounts masked to type width via wrapping_shl/wrapping_shr.
376    fn bitwise_left_shift(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
377        ruprim_host::binary::dispatch_int::bitwise_left_shift(lhs, rhs)
378    }
379
380    fn bitwise_left_shift_scalar(lhs: IntTensor<Host>, rhs: Scalar) -> IntTensor<Host> {
381        ruprim_host::binary::dispatch_int::bitwise_left_shift_scalar(lhs, rhs)
382    }
383
384    fn bitwise_right_shift(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
385        ruprim_host::binary::dispatch_int::bitwise_right_shift(lhs, rhs)
386    }
387
388    fn bitwise_right_shift_scalar(lhs: IntTensor<Host>, rhs: Scalar) -> IntTensor<Host> {
389        ruprim_host::binary::dispatch_int::bitwise_right_shift_scalar(lhs, rhs)
390    }
391
392    fn int_cast(tensor: IntTensor<Host>, dtype: IntDType) -> IntTensor<Host> {
393        ruprim_host::cast::int_cast(tensor, dtype)
394    }
395
396    fn int_unfold(
397        tensor: IntTensor<Host>,
398        dim: usize,
399        size: usize,
400        step: usize,
401    ) -> IntTensor<Host> {
402        crate::ops::unfold::unfold_int(tensor, dim, size, step)
403    }
404
405    fn int_neg(tensor: IntTensor<Host>) -> IntTensor<Host> {
406        ruprim_host::unary::dispatch_int::int_neg(tensor)
407    }
408
409    fn int_clamp(tensor: IntTensor<Host>, min: Scalar, max: Scalar) -> IntTensor<Host> {
410        ruprim_host::unary::dispatch_int::int_clamp(tensor, min, max)
411    }
412
413    fn int_clamp_min(tensor: IntTensor<Host>, min: Scalar) -> IntTensor<Host> {
414        ruprim_host::unary::dispatch_int::int_clamp_min(tensor, min)
415    }
416
417    fn int_clamp_max(tensor: IntTensor<Host>, max: Scalar) -> IntTensor<Host> {
418        ruprim_host::unary::dispatch_int::int_clamp_max(tensor, max)
419    }
420
421    fn int_sign(tensor: IntTensor<Host>) -> IntTensor<Host> {
422        ruprim_host::unary::dispatch_int::int_sign(tensor)
423    }
424
425    fn int_mean(tensor: IntTensor<Host>) -> IntTensor<Host> {
426        ruprim_host::reduce::dispatch::int_mean(tensor)
427    }
428
429    fn int_max(tensor: IntTensor<Host>) -> IntTensor<Host> {
430        crate::ops::reduce::max(tensor)
431    }
432
433    fn int_max_dim(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
434        crate::ops::reduce::max_dim(tensor, dim)
435    }
436
437    fn int_min(tensor: IntTensor<Host>) -> IntTensor<Host> {
438        crate::ops::reduce::min(tensor)
439    }
440
441    fn int_min_dim(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
442        crate::ops::reduce::min_dim(tensor, dim)
443    }
444
445    fn int_max_dim_with_indices(
446        tensor: IntTensor<Host>,
447        dim: usize,
448    ) -> (IntTensor<Host>, IntTensor<Host>) {
449        crate::ops::reduce::max_dim_with_indices(tensor, dim)
450    }
451
452    fn int_min_dim_with_indices(
453        tensor: IntTensor<Host>,
454        dim: usize,
455    ) -> (IntTensor<Host>, IntTensor<Host>) {
456        crate::ops::reduce::min_dim_with_indices(tensor, dim)
457    }
458
459    fn int_any(tensor: IntTensor<Host>, out_dtype: ruda_tensor::BoolDType) -> BoolTensor<Host> {
460        crate::ops::comparison::any_int(tensor, out_dtype)
461    }
462
463    fn int_any_dim(
464        tensor: IntTensor<Host>,
465        dim: usize,
466        out_dtype: ruda_tensor::BoolDType,
467    ) -> BoolTensor<Host> {
468        crate::ops::comparison::any_int_dim(tensor, dim, out_dtype)
469    }
470
471    fn int_all(tensor: IntTensor<Host>, out_dtype: ruda_tensor::BoolDType) -> BoolTensor<Host> {
472        crate::ops::comparison::all_int(tensor, out_dtype)
473    }
474
475    fn int_all_dim(
476        tensor: IntTensor<Host>,
477        dim: usize,
478        out_dtype: ruda_tensor::BoolDType,
479    ) -> BoolTensor<Host> {
480        crate::ops::comparison::all_int_dim(tensor, dim, out_dtype)
481    }
482
483    fn int_powi(lhs: IntTensor<Host>, rhs: IntTensor<Host>) -> IntTensor<Host> {
484        ruprim_host::binary::dispatch_int::int_powi(lhs, rhs)
485    }
486
487    fn int_zeros(shape: Shape, _device: &Device<Host>, dtype: IntDType) -> IntTensor<Host> {
488        HostTensor::zeros(shape, dtype.into())
489    }
490
491    fn int_ones(shape: Shape, _device: &Device<Host>, dtype: IntDType) -> IntTensor<Host> {
492        ruprim_host::fill::int_ones(shape, dtype)
493    }
494
495    fn int_full(
496        shape: Shape,
497        fill_value: ruda_tensor::Scalar,
498        _device: &Device<Host>,
499        dtype: IntDType,
500    ) -> IntTensor<Host> {
501        ruprim_host::fill::int_full(shape, fill_value, dtype)
502    }
503
504    fn int_transpose(tensor: IntTensor<Host>) -> IntTensor<Host> {
505        let ndims = tensor.layout().num_dims();
506        if ndims < 2 {
507            return tensor;
508        }
509        tensor.transpose(ndims - 2, ndims - 1)
510    }
511
512    fn int_repeat_dim(tensor: IntTensor<Host>, dim: usize, times: usize) -> IntTensor<Host> {
513        crate::ops::repeat_dim::repeat_dim(tensor, dim, times)
514    }
515
516    fn int_not_equal(
517        lhs: IntTensor<Host>,
518        rhs: IntTensor<Host>,
519        out_dtype: ruda_tensor::BoolDType,
520    ) -> BoolTensor<Host> {
521        crate::ops::comparison::int_not_equal(lhs, rhs, out_dtype)
522    }
523
524    fn int_not_equal_elem(
525        lhs: IntTensor<Host>,
526        rhs: ruda_tensor::Scalar,
527        out_dtype: ruda_tensor::BoolDType,
528    ) -> BoolTensor<Host> {
529        ruprim_host::comparison::dispatch::int_not_equal_elem(lhs, rhs, out_dtype)
530    }
531
532    fn int_sort(tensor: IntTensor<Host>, dim: usize, descending: bool) -> IntTensor<Host> {
533        crate::ops::sort::sort(tensor, dim, descending)
534    }
535
536    fn int_sort_with_indices(
537        tensor: IntTensor<Host>,
538        dim: usize,
539        descending: bool,
540    ) -> (IntTensor<Host>, IntTensor<Host>) {
541        crate::ops::sort::sort_with_indices(tensor, dim, descending)
542    }
543
544    fn int_argsort(tensor: IntTensor<Host>, dim: usize, descending: bool) -> IntTensor<Host> {
545        crate::ops::sort::argsort(tensor, dim, descending)
546    }
547
548    fn int_powi_scalar(lhs: IntTensor<Host>, rhs: ruda_tensor::Scalar) -> IntTensor<Host> {
549        ruprim_host::binary::dispatch_int::int_powi_scalar(lhs, rhs)
550    }
551
552    fn int_powi_scalar_impl(lhs: IntTensor<Host>, rhs: ruda_tensor::Scalar) -> IntTensor<Host> {
553        ruprim_host::binary::dispatch_int::int_powi_scalar_impl(lhs, rhs)
554    }
555
556    fn int_max_abs(tensor: IntTensor<Host>) -> IntTensor<Host> {
557        ruprim_host::reduce::dispatch::int_max_abs(tensor)
558    }
559
560    fn int_max_abs_dim(tensor: IntTensor<Host>, dim: usize) -> IntTensor<Host> {
561        ruprim_host::reduce::dispatch::int_max_abs_dim(tensor, dim)
562    }
563
564    fn int_arange(
565        range: core::ops::Range<i64>,
566        _device: &Device<Host>,
567        dtype: IntDType,
568    ) -> IntTensor<Host> {
569        Self::int_arange_step(range, 1, &Default::default(), dtype)
570    }
571
572    fn int_arange_step(
573        range: core::ops::Range<i64>,
574        step: usize,
575        _device: &Device<Host>,
576        dtype: IntDType,
577    ) -> IntTensor<Host> {
578        ruprim_host::fill::int_arange_step(range, step, dtype)
579    }
580}
581
582// Tests kept here exercise flex-specific behavior: dtype storage
583// selection for every int width (I16/I32/U8/U16/U32/I64/U64), and edge
584// cases of the dtype-specific kernels (u64 wrap, i64::MIN abs/neg, bit
585// shift at width). Plain int arithmetic, scalar ops, bool->int cast
586// smokes, and negative-stride (flipped/transposed) variants have been
587// migrated to ruda-backend-tests so they run against every backend.
588// When adding new tests, keep them here only if they probe flex dtype
589// storage; otherwise add them to
590// crates/ruda-backend-tests/tests/tensor/int/ops/.
591#[cfg(test)]
592mod tests {
593    use alloc::vec;
594    use ruda_tensor::TensorData;
595    use ruda_tensor::ops::IntTensorOps;
596
597    use crate::Host;
598    use crate::HostTensor;
599
600    #[test]
601    fn test_u64_div_large_values() {
602        let a = HostTensor::from_data(TensorData::new(vec![u64::MAX], [1]));
603        let b = HostTensor::from_data(TensorData::new(vec![2u64], [1]));
604        let result = Host::int_div(a, b);
605        let values: Vec<u64> = bytemuck::cast_slice(&result.into_data().bytes).to_vec();
606        assert_eq!(values[0], u64::MAX / 2);
607    }
608
609    #[test]
610    fn test_u64_remainder_large_values() {
611        let a = HostTensor::from_data(TensorData::new(vec![u64::MAX], [1]));
612        let b = HostTensor::from_data(TensorData::new(vec![2u64], [1]));
613        let result = Host::int_remainder(a, b);
614        let values: Vec<u64> = bytemuck::cast_slice(&result.into_data().bytes).to_vec();
615        assert_eq!(values[0], u64::MAX % 2);
616    }
617
618    #[test]
619    fn test_int_abs_min_value() {
620        // i64::MIN.abs() panics in debug; wrapping_abs returns MIN (matches PyTorch)
621        let a = HostTensor::from_data(TensorData::new(vec![i64::MIN], [1]));
622        let result = Host::int_abs(a);
623        let values: Vec<i64> = bytemuck::cast_slice(&result.into_data().bytes).to_vec();
624        assert_eq!(values[0], i64::MIN.wrapping_abs());
625    }
626
627    #[test]
628    fn test_int_neg_min_value() {
629        // i64::MIN negation panics in debug; wrapping_neg returns MIN (matches PyTorch)
630        let a = HostTensor::from_data(TensorData::new(vec![i64::MIN], [1]));
631        let result = Host::int_neg(a);
632        let values: Vec<i64> = bytemuck::cast_slice(&result.into_data().bytes).to_vec();
633        assert_eq!(values[0], i64::MIN.wrapping_neg());
634    }
635
636    #[test]
637    fn test_int_shift_large_amount() {
638        // Shift by >= bit width panics without wrapping; should not crash
639        let a = HostTensor::from_data(TensorData::new(vec![1i64], [1]));
640        let b = HostTensor::from_data(TensorData::new(vec![64i64], [1]));
641        let _left = Host::bitwise_left_shift(a.clone(), b.clone());
642        let _right = Host::bitwise_right_shift(a, b);
643    }
644
645    #[test]
646    fn test_int_into_float_f64() {
647        use ruda_tensor::ops::IntTensorOps;
648        use ruda_tensor::FloatDType;
649
650        let t = HostTensor::from_data(TensorData::new(vec![1i64, 2, -3], [3]));
651        let result = Host::int_into_float(t, FloatDType::F64);
652        assert_eq!(result.dtype(), ruda_tensor::DType::F64);
653        let data: Vec<f64> = result.into_data().to_vec().unwrap();
654        assert_eq!(data, vec![1.0f64, 2.0, -3.0]);
655    }
656
657    #[test]
658    fn test_u64_add_scalar_large() {
659        let t = HostTensor::from_data(TensorData::new(vec![1u64, 2, 3], [3]));
660        let big: u64 = (i64::MAX as u64) + 100;
661        let result = Host::int_add_scalar(t, ruda_tensor::Scalar::from(big));
662        let data: Vec<u64> = result.into_data().to_vec().unwrap();
663        assert_eq!(data, vec![big + 1, big + 2, big + 3]);
664    }
665
666    #[test]
667    fn test_u64_greater_elem_large() {
668        let big: u64 = (i64::MAX as u64) + 100;
669        let t = HostTensor::from_data(TensorData::new(vec![big, big + 1, big - 1], [3]));
670        let result = Host::int_greater_elem(
671            t,
672            ruda_tensor::Scalar::from(big),
673            ruda_tensor::BoolStore::Native,
674        );
675        let data: Vec<bool> = result.into_data().to_vec().unwrap();
676        assert_eq!(data, vec![false, true, false]);
677    }
678
679    #[test]
680    fn test_int_mask_fill_i32() {
681        let t = HostTensor::from_data(TensorData::new(vec![1i32, 2, 3, 4], [4]));
682        let mask = HostTensor::from_data(TensorData::new(vec![true, false, true, false], [4]));
683        let result = Host::int_mask_fill(t, mask, ruda_tensor::Scalar::from(0i64));
684        let data: Vec<i32> = result.into_data().to_vec().unwrap();
685        assert_eq!(data, vec![0, 2, 0, 4]);
686    }
687
688    #[test]
689    fn test_int_mask_fill_i16() {
690        let t = HostTensor::from_data(TensorData::new(vec![10i16, 20, 30, 40], [4]));
691        let mask = HostTensor::from_data(TensorData::new(vec![false, true, false, true], [4]));
692        let result = Host::int_mask_fill(t, mask, ruda_tensor::Scalar::from(-1i64));
693        let data: Vec<i16> = result.into_data().to_vec().unwrap();
694        assert_eq!(data, vec![10, -1, 30, -1]);
695    }
696
697    #[test]
698    fn test_int_mask_fill_u8() {
699        let t = HostTensor::from_data(TensorData::new(vec![1u8, 2, 3, 4], [4]));
700        let mask = HostTensor::from_data(TensorData::new(vec![true, true, false, false], [4]));
701        let result = Host::int_mask_fill(t, mask, ruda_tensor::Scalar::from(255i64));
702        let data: Vec<u8> = result.into_data().to_vec().unwrap();
703        assert_eq!(data, vec![255, 255, 3, 4]);
704    }
705
706    #[test]
707    fn test_int_mask_fill_u32() {
708        let t = HostTensor::from_data(TensorData::new(vec![100u32, 200, 300], [3]));
709        let mask = HostTensor::from_data(TensorData::new(vec![true, false, true], [3]));
710        let result = Host::int_mask_fill(t, mask, ruda_tensor::Scalar::from(0i64));
711        let data: Vec<u32> = result.into_data().to_vec().unwrap();
712        assert_eq!(data, vec![0, 200, 0]);
713    }
714
715    #[test]
716    fn test_int_mask_where_i32() {
717        let t = HostTensor::from_data(TensorData::new(vec![1i32, 2, 3, 4], [4]));
718        let mask = HostTensor::from_data(TensorData::new(vec![true, false, true, false], [4]));
719        let v = HostTensor::from_data(TensorData::new(vec![10i32, 20, 30, 40], [4]));
720        let result = Host::int_mask_where(t, mask, v);
721        let data: Vec<i32> = result.into_data().to_vec().unwrap();
722        assert_eq!(data, vec![10, 2, 30, 4]);
723    }
724
725    #[test]
726    fn test_int_mask_where_u8() {
727        let t = HostTensor::from_data(TensorData::new(vec![1u8, 2, 3, 4], [4]));
728        let mask = HostTensor::from_data(TensorData::new(vec![false, true, false, true], [4]));
729        let v = HostTensor::from_data(TensorData::new(vec![10u8, 20, 30, 40], [4]));
730        let result = Host::int_mask_where(t, mask, v);
731        let data: Vec<u8> = result.into_data().to_vec().unwrap();
732        assert_eq!(data, vec![1, 20, 3, 40]);
733    }
734
735    #[test]
736    fn test_int_gather_i32() {
737        let t = HostTensor::from_data(TensorData::new(vec![10i32, 20, 30, 40, 50, 60], [2, 3]));
738        let indices = HostTensor::from_data(TensorData::new(vec![2i64, 0, 1, 2], [2, 2]));
739        let result = Host::int_gather(1, t, indices);
740        let data: Vec<i32> = result.into_data().to_vec().unwrap();
741        assert_eq!(data, vec![30, 10, 50, 60]);
742    }
743
744    #[test]
745    fn test_int_select_u16() {
746        let t = HostTensor::from_data(TensorData::new(vec![10u16, 20, 30, 40, 50, 60], [2, 3]));
747        let indices = HostTensor::from_data(TensorData::new(vec![0i64, 1], [2]));
748        let result = Host::int_select(t, 1, indices);
749        let data: Vec<u16> = result.into_data().to_vec().unwrap();
750        assert_eq!(data, vec![10, 20, 40, 50]);
751    }
752
753    #[test]
754    fn test_int_cumsum_i32() {
755        let t = HostTensor::from_data(TensorData::new(vec![1i32, 2, 3, 4], [4]));
756        let result = Host::int_cumsum(t, 0);
757        let data: Vec<i32> = result.into_data().to_vec().unwrap();
758        assert_eq!(data, vec![1, 3, 6, 10]);
759    }
760
761    #[test]
762    fn test_int_cumprod_u8() {
763        let t = HostTensor::from_data(TensorData::new(vec![1u8, 2, 3, 4], [4]));
764        let result = Host::int_cumprod(t, 0);
765        let data: Vec<u8> = result.into_data().to_vec().unwrap();
766        assert_eq!(data, vec![1, 2, 6, 24]);
767    }
768
769    #[test]
770    fn test_int_cummin_i32() {
771        let t = HostTensor::from_data(TensorData::new(vec![3i32, 1, 4, 1, 5], [5]));
772        let result = Host::int_cummin(t, 0);
773        let data: Vec<i32> = result.into_data().to_vec().unwrap();
774        assert_eq!(data, vec![3, 1, 1, 1, 1]);
775    }
776
777    #[test]
778    fn test_int_cummax_u16() {
779        let t = HostTensor::from_data(TensorData::new(vec![3u16, 1, 4, 1, 5], [5]));
780        let result = Host::int_cummax(t, 0);
781        let data: Vec<u16> = result.into_data().to_vec().unwrap();
782        assert_eq!(data, vec![3, 3, 4, 4, 5]);
783    }
784
785    #[test]
786    fn test_int_scatter_add_i32() {
787        let t = HostTensor::from_data(TensorData::new(vec![0i32, 0, 0], [1, 3]));
788        let indices = HostTensor::from_data(TensorData::new(vec![0i64, 2, 1], [1, 3]));
789        let values = HostTensor::from_data(TensorData::new(vec![10i32, 20, 30], [1, 3]));
790        let result = Host::int_scatter_add(1, t, indices, values);
791        let data: Vec<i32> = result.into_data().to_vec().unwrap();
792        assert_eq!(data, vec![10, 30, 20]);
793    }
794
795    #[test]
796    fn test_int_select_add_u8() {
797        let t = HostTensor::from_data(TensorData::new(vec![1u8, 2, 3], [3]));
798        let indices = HostTensor::from_data(TensorData::new(vec![0i64, 2], [2]));
799        let values = HostTensor::from_data(TensorData::new(vec![10u8, 20], [2]));
800        let result = Host::int_select_add(t, 0, indices, values);
801        let data: Vec<u8> = result.into_data().to_vec().unwrap();
802        assert_eq!(data, vec![11, 2, 23]);
803    }
804
805    #[test]
806    fn test_int_random_i32() {
807        use ruda_tensor::{DType, Distribution, ops::IntTensorOps};
808        use ruda_tensor::{IntDType, Shape};
809
810        let shape = Shape::from(vec![100]);
811        let dist = Distribution::Uniform(0.0, 10.0);
812        let device = crate::HostDevice;
813        let t = Host::int_random(shape, dist, &device, IntDType::I32);
814        assert_eq!(t.dtype(), DType::I32);
815        let data: Vec<i32> = t.into_data().to_vec().unwrap();
816        assert!(data.iter().all(|&v| (0..=10).contains(&v)));
817    }
818
819    #[test]
820    fn test_int_random_u8() {
821        use ruda_tensor::{DType, Distribution, ops::IntTensorOps};
822        use ruda_tensor::{IntDType, Shape};
823
824        let shape = Shape::from(vec![50]);
825        let dist = Distribution::Uniform(0.0, 100.0);
826        let device = crate::HostDevice;
827        let t = Host::int_random(shape, dist, &device, IntDType::U8);
828        assert_eq!(t.dtype(), DType::U8);
829    }
830
831    #[test]
832    fn test_int_mean_i32() {
833        use ruda_tensor::{DType, ops::IntTensorOps};
834
835        let t = HostTensor::from_data(TensorData::new(vec![10i32, 20, 30], [3]));
836        let result = Host::int_mean(t);
837        assert_eq!(result.dtype(), DType::I32);
838        let data: Vec<i32> = result.into_data().to_vec().unwrap();
839        assert_eq!(data, vec![20]); // (10 + 20 + 30) / 3 = 20
840    }
841}