1use std::cmp::Ordering;
2use crate::{AutogradMetaT, Dim, NumDType, Result, Storage, Tensor, WithDType};
3use paste::paste;
4
5macro_rules! reduce_impl {
11 ($fn_name:ident, $reduce:ident, $op:ident) => {
12 paste! {
13 pub fn $fn_name<D: Dim>(&self, axis: D) -> Result<Self> {
14 let (storage, dims) = self.compute_reduce_op(axis, $reduce, false)?;
15 let meta = T::AutogradMeta::on_reduce_op(self, &dims, crate::ReduceOp::$op);
16 Ok(Self::from_storage(storage, dims, meta))
17 }
18
19 pub fn [< $fn_name _keepdim >]<D: Dim>(&self, axis: D) -> Result<Self> {
20 let (storage, dims) = self.compute_reduce_op(axis, $reduce, true)?;
21 let meta = T::AutogradMeta::on_reduce_op(self, &dims, crate::ReduceOp::$op);
22 Ok(Self::from_storage(storage, dims, meta))
23 }
24
25 pub fn [< $fn_name _all >](&self) -> Result<Self> {
26 self.flatten_all()?.$fn_name(0)
27 }
28 }
29 };
30}
31
32impl<T: NumDType> Tensor<T> {
33 reduce_impl!(sum, ReduceSum, Sum);
34 reduce_impl!(min, ReduceMin, Min);
35 reduce_impl!(max, ReduceMax, Max);
36 reduce_impl!(mean, ReduceMean, Mean);
37
38 pub fn var_keepdim<D: Dim>(&self, axis: D) -> Result<Self> {
39 let mean = self.mean_keepdim(axis)?; let delta = self.broadcast_sub(&mean)?; let delta_pow = &delta * δ delta_pow.mean_keepdim(axis)
44 }
45
46 pub fn var_unbiased_keepdim<D: Dim>(&self, axis: D) -> Result<Self> {
47 let n = T::from_usize(self.dim(axis)?);
48 let biased_var = self.var_keepdim(axis)?;
49
50 let correction = n / (n - T::one());
52 Ok(correction * biased_var)
53 }
54
55 pub fn var<D: Dim>(&self, axis: D) -> Result<Self> {
56 let v = self.var_keepdim(axis)?;
57 let v = v.squeeze(axis)?;
58 Ok(v)
59 }
60
61 pub fn var_unbiased<D: Dim>(&self, axis: D) -> Result<Self> {
62 let v = self.var_unbiased_keepdim(axis)?;
63 let v = v.squeeze(axis)?;
64 Ok(v)
65 }
66
67 pub fn var_all(&self) -> Result<Self> {
68 self.flatten_all()?.var(0)
69 }
70
71 pub fn var_unbiased_all(&self) -> Result<Self> {
72 self.flatten_all()?.var_unbiased(0)
73 }
74
75 pub fn argmin_keepdim<D: Dim>(&self, axis: D) -> Result<Tensor<u32>> {
76 let (storage, dims) = self.compute_reduce_op(axis, ReduceArgMin, true)?;
77 Ok(Tensor::from_storage(storage, dims, Default::default()))
78 }
79
80 pub fn argmin<D: Dim>(&self, axis: D) -> Result<Tensor<u32>> {
81 let (storage, dims) = self.compute_reduce_op(axis, ReduceArgMin, false)?;
82 Ok(Tensor::from_storage(storage, dims, Default::default()))
83 }
84
85 pub fn argmax_keepdim<D: Dim>(&self, axis: D) -> Result<Tensor<u32>> {
86 let (storage, dims) = self.compute_reduce_op(axis, ReduceArgMax, true)?;
87 Ok(Tensor::from_storage(storage, dims, Default::default()))
88 }
89
90 pub fn argmax<D: Dim>(&self, axis: D) -> Result<Tensor<u32>> {
91 let (storage, dims) = self.compute_reduce_op(axis, ReduceArgMax, false)?;
92 Ok(Tensor::from_storage(storage, dims, Default::default()))
93 }
94}
95
96impl Tensor<bool> {
97 pub fn all_all(&self) -> crate::Result<bool> {
98 self.iter().map(|mut i| i.all(|a| a))
99 }
100
101 pub fn any_all(&self) -> crate::Result<bool> {
102 self.iter().map(|mut i| i.any(|a| a))
103 }
104
105 pub fn all_keepdim<D: Dim>(&self, axis: D) -> Result<Tensor<bool>> {
106 self.reduce_op(axis, ReduceAll, true, Default::default())
107 }
108
109 pub fn any_keepdim<D: Dim>(&self, axis: D) -> Result<Tensor<bool>> {
110 self.reduce_op(axis, ReduceAny, true, Default::default())
111 }
112
113 pub fn all<D: Dim>(&self, axis: D) -> Result<Tensor<bool>> {
114 self.reduce_op(axis, ReduceAll, false, Default::default())
115 }
116
117 pub fn any<D: Dim>(&self, axis: D) -> Result<Tensor<bool>> {
118 self.reduce_op(axis, ReduceAny, false, Default::default())
119 }
120}
121
122impl<T: WithDType> Tensor<T> {
123 pub(crate) fn reduce_op<'a, R, Op, D>(&'a self, reduce_dim: D, op: Op, keepdim: bool, meta: R::AutogradMeta) -> Result<Tensor<R>>
124 where
125 R: WithDType,
126 D: Dim,
127 Op: ReduceOp<T, Output = R>,
128 {
129 let (storage, shape) = self.compute_reduce_op(reduce_dim, op, keepdim)?;
130 Ok(Tensor::<R>::from_storage(storage, shape, meta))
131 }
132
133 pub(crate) fn compute_reduce_op<'a, R, Op, D>(&'a self, reduce_dim: D, _: Op, keepdim: bool) -> Result<(Storage<R>, Vec<usize>)>
134 where
135 R: WithDType,
136 D: Dim,
137 Op: ReduceOp<T, Output = R>,
138 {
139 let reduce_dim = reduce_dim.to_index(self.shape(), Op::NAME)?;
140 assert!(reduce_dim < self.layout().dims().len());
141 let reduce_dim_stride = self.layout().stride()[reduce_dim];
142 let reduce_dim_size = self.layout().dims()[reduce_dim];
143
144 let dst_storage = self.storage_read()?;
145 let dst_data = dst_storage.data();
146
147 let dst = if self.is_contiguous() && reduce_dim_stride == 1 {
148 let dst_data = &dst_data[self.layout().start_offset..];
149 (0..self.layout().element_count() / reduce_dim_size).into_iter()
150 .map(|i| {
151 let chunk = &dst_data[i*reduce_dim_size..i*reduce_dim_size + reduce_dim_size];
152 let v = Op::op(chunk.iter().copied());
153 v
154 }).collect()
155 } else {
156 let dst_len = self.layout().element_count() / reduce_dim_size;
157 let mut dst: Vec<R> = Vec::with_capacity(dst_len);
158 unsafe { dst.set_len(dst_len) };
159 let layout = self.layout().narrow(reduce_dim, 0, 1)?;
162 if reduce_dim_stride == 1 {
163 for (dst_index, src_index) in layout.storage_indices().enumerate() {
164 let end_index = src_index + reduce_dim_size;
165 let chunk = &dst_data[src_index..end_index];
166 let v = Op::op(chunk.iter().copied());
167 dst[dst_index] = v;
168 }
169 } else {
170 for (dst_index, src_index) in layout.storage_indices().enumerate() {
171 let arr: DimArray<'_, T> = DimArray {
172 src: &dst_data[src_index..],
173 size: reduce_dim_size,
174 stride: reduce_dim_stride
175 };
176 let iter: DimArrayIter<'_, T> = arr.into_iter();
177 let v = Op::op(iter);
178 dst[dst_index] = v;
179 }
180 }
181
182 dst
183 };
184
185 let storage = Storage::new(dst);
186 let mut shape = self.dims().to_vec();
187
188 if keepdim {
189 shape[reduce_dim] = 1;
190 } else {
191 shape.remove(reduce_dim);
192 }
193
194 Ok((storage, shape))
195 }
196}
197
198impl<T: NumDType> Tensor<T> {
199 pub fn topk<D: Dim>(&self, k: usize, dim: D) -> crate::Result<(Tensor<T>, Tensor<u32>)> {
200 let reduce_dim = dim.to_index(self.shape(), "topk")?;
201 assert!(reduce_dim < self.layout().dims().len());
202 let reduce_dim_stride = self.layout().stride()[reduce_dim];
203 let reduce_dim_size = self.layout().dims()[reduce_dim];
204 if k > reduce_dim_size {
205 return Err(crate::Error::IndexOutOfRange { max_size: reduce_dim_size, index: k, op: "topk"});
206 }
207
208 let dst_len = self.layout().element_count() / reduce_dim_size * k;
210 let mut indices: Vec<u32> = Vec::with_capacity(dst_len);
212 let mut values: Vec<T> = Vec::with_capacity(dst_len);
213 let mut indexed: Vec<(u32, T)> = Vec::with_capacity(reduce_dim_size);
214
215 let storage = self.storage_read()?;
217 let storage_slice = storage.data();
218 let layout = self.layout().narrow(reduce_dim, 0, 1)?;
219 for src_index in layout.storage_indices() {
220 let arr: DimArray<'_, T> = DimArray {
222 src: &storage_slice[src_index..],
223 size: reduce_dim_size,
224 stride: reduce_dim_stride
225 };
226 indexed.clear();
228 indexed.extend(arr.into_iter().enumerate().map(|(i, v)| (i as u32, v)));
229
230 assert!(indexed.len() >= k);
231 if k < indexed.len() {
232 indexed.select_nth_unstable_by(k - 1, |a, b| {
234 b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal)
235 });
236 indexed[..k].sort_unstable_by(|a, b| {
238 b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal)
239 });
240 } else {
241 indexed.sort_unstable_by(|a, b| {
242 b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal)
243 });
244 }
245
246 let topk_slice = &indexed[..k];
247
248 values.extend(topk_slice.iter().map(|(_, v)| *v));
249 indices.extend(topk_slice.iter().map(|(i, _)| *i));
250 }
251
252 assert_eq!(indices.len(), dst_len);
253 assert_eq!(values.len(), dst_len);
254
255 let mut physical_shape = self.dims().to_vec();
256 physical_shape.remove(reduce_dim);
257 physical_shape.push(k);
258
259 let temp_values = Tensor::from_vec(values, physical_shape.clone())?;
260 let temp_indices = Tensor::from_vec(indices, physical_shape.clone())?;
261
262 if reduce_dim == self.dims().len() - 1 {
264 return Ok((temp_values, temp_indices));
265 }
266
267 let ndims = physical_shape.len();
269 let mut permute_axes: Vec<usize> = (0..ndims - 1).collect(); permute_axes.insert(reduce_dim, ndims - 1);
271
272 let final_values = temp_values.permute(permute_axes.as_ref())?.contiguous()?;
273 let final_indices = temp_indices.permute(permute_axes)?.contiguous()?;
274
275 Ok((final_values, final_indices))
276 }
277}
278
279pub trait ReduceOp<D: WithDType> {
280 type Output: WithDType;
281 const NAME: &'static str;
282 fn op<I>(iter: I) -> Self::Output
283 where
284 I: Iterator<Item = D> + ExactSizeIterator + Clone;
285}
286
287pub struct ReduceAll;
288impl ReduceOp<bool> for ReduceAll {
289 type Output = bool;
290 const NAME: &'static str = "all";
291 fn op<I>(mut iter: I) -> Self::Output
292 where I: Iterator<Item = bool> + ExactSizeIterator + Clone {
293 iter.all(|b| b)
294 }
295}
296
297pub struct ReduceAny;
298impl ReduceOp<bool> for ReduceAny {
299 type Output = bool;
300 const NAME: &'static str = "any";
301 fn op<I>(mut iter: I) -> Self::Output
302 where I: Iterator<Item = bool> + ExactSizeIterator + Clone {
303 iter.any(|b| b)
304 }
305}
306
307pub struct ReduceSum;
308impl<D: NumDType> ReduceOp<D> for ReduceSum {
309 const NAME: &'static str = "sum";
310 type Output = D;
311 fn op<I>(iter: I) -> Self::Output
312 where I: Iterator<Item = D> + ExactSizeIterator + Clone {
313 iter.sum::<D>()
314 }
315}
316
317pub struct ReduceMean;
318impl<D: NumDType> ReduceOp<D> for ReduceMean {
319 type Output = D;
320 const NAME: &'static str = "mean";
321 fn op<I>(iter: I) -> Self::Output
322 where I: Iterator<Item = D> + ExactSizeIterator + Clone {
323 let len = iter.len();
324 iter.sum::<D>() / D::from_usize(len)
325 }
326}
327
328pub struct ReduceVar;
329impl<D: NumDType> ReduceOp<D> for ReduceVar {
330 type Output = D;
331 const NAME: &'static str = "var";
332 fn op<I>(iter: I) -> Self::Output
333 where I: Iterator<Item = D> + ExactSizeIterator + Clone {
334 let len = iter.len();
335 if len == 0 { return D::zero(); }
336
337 let mean = ReduceMean::op(iter.clone());
339
340 let mut sum_sq_diff = D::zero();
341 for v in iter {
342 let diff = v - mean;
343 sum_sq_diff += diff * diff;
344 }
345
346 sum_sq_diff / D::from_usize(len)
347 }
348}
349
350pub struct ReduceProduct;
351impl<D: NumDType> ReduceOp<D> for ReduceProduct {
352 type Output = D;
353 const NAME: &'static str = "product";
354 fn op<I>(iter: I) -> Self::Output
355 where I: Iterator<Item = D> + ExactSizeIterator + Clone {
356 iter.product::<D>()
357 }
358}
359
360pub struct ReduceMin;
361impl<D: NumDType> ReduceOp<D> for ReduceMin {
362 type Output = D;
363 const NAME: &'static str = "min";
364 fn op<I>(iter: I) -> Self::Output
365 where I: Iterator<Item = D> + ExactSizeIterator + Clone {
366 iter.reduce(|a, b| D::minimum(a, b)).unwrap()
367 }
368}
369
370pub struct ReduceMax;
371impl<D: NumDType> ReduceOp<D> for ReduceMax {
372 type Output = D;
373 const NAME: &'static str = "max";
374 fn op<I>(iter: I) -> Self::Output
375 where I: Iterator<Item = D> + ExactSizeIterator + Clone {
376 iter.reduce(|a, b| D::maximum(a, b)).unwrap()
377 }
378}
379
380pub struct ReduceArgMin;
381impl<D: NumDType> ReduceOp<D> for ReduceArgMin {
382 type Output = u32;
383 const NAME: &'static str = "arg_min";
384 fn op<I>(iter: I) -> Self::Output
385 where I: Iterator<Item = D> + ExactSizeIterator + Clone {
386 iter.enumerate()
387 .reduce(|(ia, a), (ib, b)| {
388 if a.partial_cmp(&b) == Some(std::cmp::Ordering::Less) {
389 (ia, a)
390 } else {
391 (ib, b)
392 }
393 }).unwrap().0 as u32
394 }
395}
396
397pub struct ReduceArgMax;
398impl<D: NumDType> ReduceOp<D> for ReduceArgMax {
399 type Output = u32;
400 const NAME: &'static str = "arg_max";
401 fn op<I>(iter: I) -> Self::Output
402 where I: Iterator<Item = D> + ExactSizeIterator + Clone {
403 iter.enumerate()
404 .reduce(|(ia, a), (ib, b)| {
405 if a.partial_cmp(&b) == Some(std::cmp::Ordering::Greater) {
406 (ia, a)
407 } else {
408 (ib, b)
409 }
410 }).unwrap().0 as u32
411 }
412}
413
414#[derive(Clone)]
415pub struct DimArray<'a, T> {
416 pub(crate) src: &'a [T],
417 pub(crate) size: usize,
418 pub(crate) stride: usize
419}
420
421impl<'a, T: WithDType> DimArray<'a, T> {
422 pub fn get(&self, index: usize) -> T {
423 self.src[index * self.stride]
424 }
425
426 #[allow(unused)]
427 pub fn to_vec(&self) -> Vec<T> {
428 let mut v = vec![];
429 for i in 0..self.size {
430 v.push(self.get(i));
431 }
432 v
433 }
434}
435
436impl<'a, T: WithDType> IntoIterator for DimArray<'a, T> {
437 type IntoIter = DimArrayIter<'a, T>;
438 type Item = T;
439 fn into_iter(self) -> Self::IntoIter {
440 DimArrayIter::<'a, T> {
441 array: self,
442 index: 0,
443 }
444 }
445}
446
447#[derive(Clone)]
448pub struct DimArrayIter<'a, T> {
449 array: DimArray<'a, T>,
450 index: usize,
451}
452
453impl<'a, T: WithDType> Iterator for DimArrayIter<'a, T> {
454 type Item = T;
455 fn next(&mut self) -> Option<T> {
456 if self.index >= self.array.size {
457 None
458 } else {
459 let index = self.index;
460 self.index += 1;
461 Some(self.array.get(index))
462 }
463 }
464}
465
466impl<'a, T: WithDType> ExactSizeIterator for DimArrayIter<'a, T> {
467 fn len(&self) -> usize {
468 self.array.size
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475
476 #[test]
477 fn test_sum_matrix_axis0() -> crate::Result<()> {
478 let arr = Tensor::new(&[[1, 2, 3], [3, 4, 5]]).unwrap();
482 let s = arr.sum(0).unwrap();
483 let expected = Tensor::new(&[4, 6, 8]).unwrap();
484 assert!(s.allclose(&expected, 1e-5, 1e-8).unwrap());
485
486 let arr = arr.transpose_last()?.copy()?.transpose_last()?;
487 let s = arr.sum(0).unwrap();
488 assert!(s.allclose(&expected, 1e-5, 1e-8).unwrap());
489
490 Ok(())
491 }
492
493 #[test]
494 fn test_sum_matrix_axis1() -> crate::Result<()> {
495 let arr = Tensor::new(&[[1, 2, 3], [3, 4, 5]]).unwrap();
499 let s = arr.sum(1).unwrap();
500 let expected = Tensor::new(&[6, 12]).unwrap();
501 assert!(s.allclose(&expected, 1e-5, 1e-8).unwrap());
502
503 let arr = arr.transpose_last()?.copy()?.transpose_last()?;
504 let s = arr.sum(1).unwrap();
505 assert!(s.allclose(&expected, 1e-5, 1e-8).unwrap());
506
507 Ok(())
508 }
509
510 #[test]
511 fn test_sum_ones_axis() {
512 let arr = Tensor::ones((2, 3)).unwrap();
516 let s0 = arr.sum(0).unwrap(); let s1 = arr.sum(1).unwrap(); let expected0 = Tensor::new(&[2, 2, 2]).unwrap();
520 let expected1 = Tensor::new(&[3, 3]).unwrap();
521
522 assert!(s0.allclose(&expected0, 1e-5, 1e-8).unwrap());
523 assert!(s1.allclose(&expected1, 1e-5, 1e-8).unwrap());
524 }
525
526 #[test]
527 fn test_min_matrix_axis0() {
528 let arr = Tensor::new(&[[1, 2, 3], [3, 1, 0]]).unwrap();
532 let m = arr.min(0).unwrap();
533 let expected = Tensor::new(&[1, 1, 0]).unwrap();
534 assert!(m.allclose(&expected, 1e-5, 1e-8).unwrap());
535 }
536
537 #[test]
538 fn test_max_matrix_axis1() {
539 let arr = Tensor::new(&[[1, 2, 3], [3, 1, 0]]).unwrap();
543 let m = arr.max(1).unwrap();
544 let expected = Tensor::new(&[3, 3]).unwrap();
545 assert!(m.allclose(&expected, 1e-5, 1e-8).unwrap());
546 }
547
548 #[test]
549 fn test_aragmin_matrix_axis0() {
550 let arr = Tensor::new(&[[1, 2, 3], [3, 1, 0]]).unwrap();
554 let m = arr.argmin(0).unwrap();
555 let expected = Tensor::new(&[0, 1, 1]).unwrap();
556 assert!(m.allclose(&expected, 1e-5, 1e-8).unwrap());
557 }
558
559 #[test]
560 fn test_sum_all() {
561 let arr = Tensor::new(&[[1, 2], [3, 4]]).unwrap();
563 let s = arr.sum_all().unwrap();
564 let expected = Tensor::new(10).unwrap();
565 assert!(s.allclose(&expected, 1e-5, 1e-8).unwrap());
566 }
567
568 #[test]
569 fn test_mean_all() {
570 let arr = Tensor::new(&[[1.0, 2.0], [3.0, 4.0]]).unwrap();
572 let m = arr.mean_all().unwrap();
573 let expected = Tensor::new(2.5).unwrap();
574 assert!(m.allclose(&expected, 1e-5, 1e-8).unwrap());
575 }
576
577 #[test]
578 fn test_min_max_all() {
579 let arr = Tensor::new(&[[10, 2, 5], [8, 1, 9]]).unwrap();
583
584 let min_val = arr.min_all().unwrap();
585 let max_val = arr.max_all().unwrap();
586
587 let expected_min = Tensor::new(1).unwrap();
588 let expected_max = Tensor::new(10).unwrap();
589
590 assert!(min_val.allclose(&expected_min, 1e-5, 1e-8).unwrap());
591 assert!(max_val.allclose(&expected_max, 1e-5, 1e-8).unwrap());
592 }
593
594 #[test]
595 fn test_argmax_matrix_axis1() {
596 let arr = Tensor::new(&[[1, 2, 3], [3, 1, 0]]).unwrap();
600 let m = arr.argmax(1).unwrap();
601 let expected = Tensor::new(&[2, 0]).unwrap();
602 assert!(m.allclose(&expected, 1e-5, 1e-8).unwrap());
603 }
604
605 #[test]
606 fn test_reductions_with_negatives() {
607 let arr = Tensor::new(&[[-2.0, 0.0, 2.0]]).unwrap();
613
614 assert!(arr.sum_all().unwrap().allclose(&Tensor::new(0.0).unwrap(), 1e-5, 1e-8).unwrap());
615 assert!(arr.mean_all().unwrap().allclose(&Tensor::new(0.0).unwrap(), 1e-5, 1e-8).unwrap());
616
617 let expected_var = Tensor::new(2.66666666666666666).unwrap();
618 assert!(arr.var_all().unwrap().allclose(&expected_var, 1e-5, 1e-8).unwrap());
619 }
620
621 #[test]
622 fn test_topk_1d() {
623 let arr = Tensor::new(&[10, 50, 20, 40, 30]).unwrap();
628 let (val, idx) = arr.topk(3, 0).unwrap();
629
630 let expected_val = Tensor::new(&[50, 40, 30]).unwrap();
631 let expected_idx = Tensor::new(&[1u32, 3, 4]).unwrap(); assert!(val.allclose(&expected_val, 1e-5, 1e-8).unwrap());
634 assert!(idx.allclose(&expected_idx, 1e-5, 1e-8).unwrap());
635 }
636
637 #[test]
638 fn test_topk_matrix_axis1() {
639 let arr = Tensor::new(&[[1, 5, 2], [8, 4, 6]]).unwrap();
646 let (val, idx) = arr.topk(2, 1).unwrap();
647
648 let expected_val = Tensor::new(&[[5, 2], [8, 6]]).unwrap();
649 let expected_idx = Tensor::new(&[[1u32, 2], [0, 2]]).unwrap();
650
651 assert!(val.allclose(&expected_val, 1e-5, 1e-8).unwrap());
652 assert!(idx.allclose(&expected_idx, 1e-5, 1e-8).unwrap());
653 }
654
655 #[test]
656 fn test_topk_matrix_axis0() {
657 let arr = Tensor::new(&[
672 [1, 5, 2],
673 [8, 4, 6],
674 [3, 7, 9]
675 ]).unwrap();
676 let (val, idx) = arr.topk(2, 0).unwrap();
677
678 let expected_val = Tensor::new(&[[8, 7, 9], [3, 5, 6]]).unwrap();
679 let expected_idx = Tensor::new(&[[1u32, 2, 2], [2, 0, 1]]).unwrap();
680
681 assert!(val.allclose(&expected_val, 1e-5, 1e-8).unwrap());
682 assert!(idx.allclose(&expected_idx, 1e-5, 1e-8).unwrap());
683 }
684
685 #[test]
686 fn test_topk_full_sort() {
687 let arr = Tensor::new(&[[3, 1, 2]]).unwrap();
690 let (val, idx) = arr.topk(3, 1).unwrap();
691
692 let expected_val = Tensor::new(&[[3, 2, 1]]).unwrap();
693 let expected_idx = Tensor::new(&[[0u32, 2, 1]]).unwrap();
694
695 assert!(val.allclose(&expected_val, 1e-5, 1e-8).unwrap());
696 assert!(idx.allclose(&expected_idx, 1e-5, 1e-8).unwrap());
697 }
698
699 #[test]
700 fn test_large_ops() {
701 let a = Tensor::randn(0.0, 1.0, (1000, 4096)).unwrap();
702 let now = std::time::Instant::now();
703 let s = a.sum_keepdim(1).unwrap();
704 println!("{:?}", std::time::Instant::now() - now);
705 println!("{:?}", s.dims());
706 }
707}