1use std::sync::Arc;
16use torsh_core::{
17 device::DeviceType,
18 dtype::{FloatElement, TensorElement},
19 error::{Result, TorshError},
20};
21
22use crate::{core_ops::Tensor, storage::TensorStorage};
23
24impl<T: FloatElement + Copy> Tensor<T> {
26 pub fn scalar(value: T) -> Result<Self> {
28 Self::from_data(vec![value], vec![], DeviceType::Cpu)
29 }
30
31 pub fn as_ndarray(&self) -> Result<scirs2_core::ndarray::ArrayD<T>> {
33 use scirs2_core::ndarray::ArrayD;
34 let data = self.data()?;
35 let shape_obj = self.shape().clone();
36 let shape = shape_obj.dims();
37 ArrayD::from_shape_vec(shape, data.to_vec())
38 .map_err(|e| TorshError::InvalidShape(format!("ndarray conversion failed: {}", e)))
39 }
40
41 pub fn from_ndarray(
43 array: scirs2_core::ndarray::ArrayD<T>,
44 device: DeviceType,
45 ) -> Result<Self> {
46 let shape = array.shape().to_vec();
47 let (data, _offset) = array.into_raw_vec_and_offset();
48 Self::from_data(data, shape, device)
49 }
50
51 pub fn max(&self, dim: Option<usize>, keepdim: bool) -> Result<Self> {
53 match dim {
54 None => {
55 let data = self.to_vec()?;
57 let max_val =
58 data.into_iter()
59 .fold(<T as FloatElement>::neg_infinity(), |acc, x| {
60 if x > acc {
61 x
62 } else {
63 acc
64 }
65 });
66 if keepdim {
67 let shape = vec![1; self.shape().dims().len()];
68 Self::from_data(vec![max_val], shape, self.device)
69 } else {
70 Self::scalar(max_val)
71 }
72 }
73 Some(axis) => {
74 let shape_binding = self.shape();
76 let input_shape = shape_binding.dims();
77
78 if axis >= input_shape.len() {
79 return Err(TorshError::InvalidOperation(format!(
80 "Axis {} out of bounds for {}-dimensional tensor",
81 axis,
82 input_shape.len()
83 )));
84 }
85
86 let mut output_shape = input_shape.to_vec();
88 if keepdim {
89 output_shape[axis] = 1;
90 } else {
91 output_shape.remove(axis);
92 }
93
94 let data = self.data()?;
95 let outer_size: usize = input_shape[..axis].iter().product();
96 let axis_size = input_shape[axis];
97 let inner_size: usize = input_shape[axis + 1..].iter().product();
98
99 let output_size = outer_size * inner_size;
100 let mut result_data = vec![<T as FloatElement>::neg_infinity(); output_size];
101
102 for outer in 0..outer_size {
103 for inner in 0..inner_size {
104 let mut max_val = <T as FloatElement>::neg_infinity();
105 for a in 0..axis_size {
106 let input_idx = outer * axis_size * inner_size + a * inner_size + inner;
107 let val = data[input_idx];
108 if val > max_val {
109 max_val = val;
110 }
111 }
112 let output_idx = outer * inner_size + inner;
113 result_data[output_idx] = max_val;
114 }
115 }
116
117 Self::from_data(result_data, output_shape, self.device)
118 }
119 }
120 }
121
122 pub fn max_dim(&self, dim: i32, keepdim: bool) -> Result<Self> {
124 let shape_binding = self.shape();
125 let input_shape = shape_binding.dims();
126
127 let actual_dim = if dim < 0 {
128 (input_shape.len() as i32 + dim) as usize
129 } else {
130 dim as usize
131 };
132
133 if actual_dim >= input_shape.len() {
134 return Err(TorshError::InvalidOperation(format!(
135 "Dimension {} out of range for {}-dimensional tensor",
136 actual_dim,
137 input_shape.len()
138 )));
139 }
140
141 let mut output_shape = input_shape.to_vec();
143 if keepdim {
144 output_shape[actual_dim] = 1;
145 } else {
146 output_shape.remove(actual_dim);
147 }
148
149 let data = self.data()?;
150 let outer_size: usize = input_shape[..actual_dim].iter().product();
151 let dim_size = input_shape[actual_dim];
152 let inner_size: usize = input_shape[actual_dim + 1..].iter().product();
153
154 let output_size = outer_size * inner_size;
155 let mut result_data = vec![<T as FloatElement>::neg_infinity(); output_size];
156
157 for outer in 0..outer_size {
158 for inner in 0..inner_size {
159 let mut max_val = <T as FloatElement>::neg_infinity();
160 for d in 0..dim_size {
161 let input_idx = outer * dim_size * inner_size + d * inner_size + inner;
162 let val = data[input_idx];
163 if val > max_val {
164 max_val = val;
165 }
166 }
167 let output_idx = outer * inner_size + inner;
168 result_data[output_idx] = max_val;
169 }
170 }
171
172 Self::from_data(result_data, output_shape, self.device)
173 }
174
175 pub fn min_dim(&self, dim: i32, keepdim: bool) -> Result<Self> {
177 use scirs2_core::ndarray::Axis;
178
179 let normalized_dim = if dim < 0 {
180 (self.shape().len() as i32 + dim) as usize
181 } else {
182 dim as usize
183 };
184
185 if normalized_dim >= self.shape().len() {
186 return Err(torsh_core::error::TorshError::InvalidDimension {
187 dim: normalized_dim,
188 ndim: self.shape().len(),
189 });
190 }
191
192 let array = self.as_ndarray()?;
193 let result = array.map_axis(Axis(normalized_dim), |view| {
194 view.iter()
195 .copied()
196 .fold(<T as FloatElement>::infinity(), |acc, x| {
197 if x < acc {
198 x
199 } else {
200 acc
201 }
202 })
203 });
204
205 let result_shape = if keepdim {
206 let mut shape = self.shape().to_vec();
207 shape[normalized_dim] = 1;
208 shape
209 } else {
210 result.shape().to_vec()
211 };
212
213 Self::from_ndarray(
214 result
215 .to_shape(result_shape)
216 .map_err(|e| TorshError::InvalidShape(format!("Shape conversion failed: {}", e)))?
217 .to_owned(),
218 self.device(),
219 )
220 }
221}
222
223impl<T: TensorElement + Copy> Tensor<T>
225where
226 T: PartialEq + num_traits::Zero,
227{
228 pub fn all(&self) -> Result<Tensor<bool>> {
230 let data = self.to_vec()?;
231 let zero = <T as num_traits::Zero>::zero();
232 let all_true = data.iter().all(|&x| x != zero);
233 Tensor::from_data(vec![all_true], vec![], self.device())
234 }
235
236 pub fn any(&self) -> Result<Tensor<bool>> {
238 let data = self.to_vec()?;
239 let zero = <T as num_traits::Zero>::zero();
240 let any_true = data.iter().any(|&x| x != zero);
241 Tensor::from_data(vec![any_true], vec![], self.device())
242 }
243
244 pub fn all_dim(&self, dim: i32, keepdim: bool) -> Result<Tensor<bool>> {
246 let shape_binding = self.shape();
247 let input_shape = shape_binding.dims();
248
249 let normalized_dim = if dim < 0 {
250 (input_shape.len() as i32 + dim) as usize
251 } else {
252 dim as usize
253 };
254
255 if normalized_dim >= input_shape.len() {
256 return Err(torsh_core::error::TorshError::InvalidDimension {
257 dim: normalized_dim,
258 ndim: input_shape.len(),
259 });
260 }
261
262 let data = self.data()?;
263 let zero = <T as num_traits::Zero>::zero();
264
265 let outer_size: usize = input_shape[..normalized_dim].iter().product();
266 let dim_size = input_shape[normalized_dim];
267 let inner_size: usize = input_shape[normalized_dim + 1..].iter().product();
268
269 let output_size = outer_size * inner_size;
270 let mut result_data = vec![true; output_size];
271
272 for outer in 0..outer_size {
273 for inner in 0..inner_size {
274 let all_nonzero = (0..dim_size).all(|d| {
275 let idx = outer * dim_size * inner_size + d * inner_size + inner;
276 data[idx] != zero
277 });
278 let out_idx = outer * inner_size + inner;
279 result_data[out_idx] = all_nonzero;
280 }
281 }
282
283 let mut output_shape = input_shape.to_vec();
284 if keepdim {
285 output_shape[normalized_dim] = 1;
286 } else {
287 output_shape.remove(normalized_dim);
288 }
289
290 Tensor::<bool>::from_data(result_data, output_shape, self.device())
291 }
292
293 pub fn any_dim(&self, dim: i32, keepdim: bool) -> Result<Tensor<bool>> {
295 let shape_binding = self.shape();
296 let input_shape = shape_binding.dims();
297
298 let normalized_dim = if dim < 0 {
299 (input_shape.len() as i32 + dim) as usize
300 } else {
301 dim as usize
302 };
303
304 if normalized_dim >= input_shape.len() {
305 return Err(torsh_core::error::TorshError::InvalidDimension {
306 dim: normalized_dim,
307 ndim: input_shape.len(),
308 });
309 }
310
311 let data = self.data()?;
312 let zero = <T as num_traits::Zero>::zero();
313
314 let outer_size: usize = input_shape[..normalized_dim].iter().product();
315 let dim_size = input_shape[normalized_dim];
316 let inner_size: usize = input_shape[normalized_dim + 1..].iter().product();
317
318 let output_size = outer_size * inner_size;
319 let mut result_data = vec![false; output_size];
320
321 for outer in 0..outer_size {
322 for inner in 0..inner_size {
323 let any_nonzero = (0..dim_size).any(|d| {
324 let idx = outer * dim_size * inner_size + d * inner_size + inner;
325 data[idx] != zero
326 });
327 let out_idx = outer * inner_size + inner;
328 result_data[out_idx] = any_nonzero;
329 }
330 }
331
332 let mut output_shape = input_shape.to_vec();
333 if keepdim {
334 output_shape[normalized_dim] = 1;
335 } else {
336 output_shape.remove(normalized_dim);
337 }
338
339 Tensor::<bool>::from_data(result_data, output_shape, self.device())
340 }
341}
342
343impl<T: TensorElement + Copy> Tensor<T> {
345 pub fn sum(&self) -> Result<Self>
347 where
348 T: std::ops::Add<Output = T> + num_traits::Zero,
349 {
350 let data = self.data()?;
351 let sum_value = data
352 .iter()
353 .fold(<T as num_traits::Zero>::zero(), |acc, &x| acc + x);
354 let mut result = Tensor::from_data(vec![sum_value], vec![], self.device())?;
355
356 if self.requires_grad {
358 result.requires_grad = true;
359 result.operation = crate::core_ops::Operation::Sum {
360 input: Arc::new(self.clone()),
361 };
362 }
363
364 Ok(result)
365 }
366
367 pub fn sum_dim(&self, dims: &[i32], keepdim: bool) -> Result<Self>
369 where
370 T: std::ops::Add<Output = T> + num_traits::Zero,
371 {
372 if dims.is_empty() {
373 return self.sum();
374 }
375
376 let shape_binding = self.shape();
377 let input_shape = shape_binding.dims();
378
379 if dims.len() == 1 {
381 let dim = dims[0];
382 let actual_dim = if dim < 0 {
383 (input_shape.len() as i32 + dim) as usize
384 } else {
385 dim as usize
386 };
387
388 if actual_dim >= input_shape.len() {
389 return Err(TorshError::InvalidOperation(format!(
390 "Dimension {} out of range for {}-dimensional tensor",
391 actual_dim,
392 input_shape.len()
393 )));
394 }
395
396 let mut output_shape = input_shape.to_vec();
398 if keepdim {
399 output_shape[actual_dim] = 1;
400 } else {
401 output_shape.remove(actual_dim);
402 }
403
404 let data = self.data()?;
405 let outer_size: usize = input_shape[..actual_dim].iter().product();
406 let dim_size = input_shape[actual_dim];
407 let inner_size: usize = input_shape[actual_dim + 1..].iter().product();
408
409 let output_size = outer_size * inner_size;
410 let mut result_data = vec![num_traits::Zero::zero(); output_size];
411
412 for outer in 0..outer_size {
413 for inner in 0..inner_size {
414 let mut sum = num_traits::Zero::zero();
415 for d in 0..dim_size {
416 let input_idx = outer * dim_size * inner_size + d * inner_size + inner;
417 sum = sum + data[input_idx];
418 }
419 let output_idx = outer * inner_size + inner;
420 result_data[output_idx] = sum;
421 }
422 }
423
424 Self::from_data(result_data, output_shape, self.device)
425 } else {
426 self.sum()
428 }
429 }
430
431 pub fn mean(&self, dims: Option<&[usize]>, keepdim: bool) -> Result<Self>
433 where
434 T: std::ops::Add<Output = T>
435 + std::ops::Div<Output = T>
436 + num_traits::Zero
437 + num_traits::One
438 + num_traits::FromPrimitive,
439 {
440 let sum = if let Some(dims) = dims {
441 self.sum_dim(&dims.iter().map(|&d| d as i32).collect::<Vec<_>>(), keepdim)?
442 } else {
443 let scalar_sum = self.sum()?;
444 if keepdim {
445 let keepdim_shape = vec![1; self.shape().ndim()];
447 scalar_sum.view(&keepdim_shape)?
448 } else {
449 scalar_sum
450 }
451 };
452
453 let count = if let Some(dims) = dims {
454 dims.iter()
455 .map(|&d| self.shape().dims()[d])
456 .product::<usize>() as f64
457 } else {
458 self.numel() as f64
459 };
460
461 let mut result = sum.div_scalar(
462 <T as num_traits::FromPrimitive>::from_f64(count)
463 .unwrap_or_else(|| <T as num_traits::One>::one()),
464 )?;
465
466 if self.requires_grad {
468 result.requires_grad = true;
469 result.operation = crate::core_ops::Operation::Mean {
470 input: Arc::new(self.clone()),
471 count,
472 };
473 }
474
475 Ok(result)
476 }
477
478 pub fn cumprod(&self, dim: i32) -> Result<Self>
480 where
481 T: std::ops::Mul<Output = T> + num_traits::One + Copy,
482 {
483 let normalized_dim = if dim < 0 {
484 (self.shape().len() as i32 + dim) as usize
485 } else {
486 dim as usize
487 };
488
489 if normalized_dim >= self.shape().len() {
490 return Err(torsh_core::error::TorshError::InvalidDimension {
491 dim: normalized_dim,
492 ndim: self.shape().len(),
493 });
494 }
495
496 let shape = self.shape().clone();
497 let input_shape = shape.dims();
498 let data = self.data()?;
499 let mut result_data = data.to_vec();
500
501 let outer_size: usize = input_shape[..normalized_dim].iter().product();
502 let dim_size = input_shape[normalized_dim];
503 let inner_size: usize = input_shape[normalized_dim + 1..].iter().product();
504
505 for outer_idx in 0..outer_size {
506 for inner_idx in 0..inner_size {
507 let mut running_product = <T as num_traits::One>::one();
508 for dim_idx in 0..dim_size {
509 let index =
510 outer_idx * (dim_size * inner_size) + dim_idx * inner_size + inner_idx;
511 running_product = running_product * result_data[index];
512 result_data[index] = running_product;
513 }
514 }
515 }
516
517 Self::from_data(result_data, input_shape.to_vec(), self.device())
518 }
519
520 pub fn matmul(&self, other: &Self) -> Result<Self>
522 where
523 T: num_traits::Float + std::iter::Sum,
524 {
525 let mut result = self.basic_matmul(other)?;
526 if self.requires_grad || other.requires_grad {
529 result.requires_grad = true;
530 result.operation = crate::core_ops::Operation::MatMul {
531 lhs: Arc::new(self.clone()),
532 rhs: Arc::new(other.clone()),
533 };
534 }
535 Ok(result)
536 }
537
538 pub fn sort(&self, _dim: Option<i32>, _descending: bool) -> Result<(Self, Self)>
540 where
541 T: PartialOrd + num_traits::Zero + num_traits::FromPrimitive,
542 {
543 let data = self.to_vec()?;
545 let mut indexed_data: Vec<(usize, T)> =
546 data.iter().enumerate().map(|(i, &val)| (i, val)).collect();
547
548 indexed_data.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
550
551 let sorted_data: Vec<T> = indexed_data.iter().map(|(_, val)| *val).collect();
553 let indices: Vec<T> = indexed_data
554 .iter()
555 .map(|(i, _)| {
556 <T as num_traits::FromPrimitive>::from_usize(*i)
557 .unwrap_or_else(|| <T as num_traits::Zero>::zero())
558 })
559 .collect();
560
561 let sorted_tensor =
562 Self::from_data(sorted_data, self.shape().dims().to_vec(), self.device())?;
563 let indices_tensor = Self::from_data(indices, self.shape().dims().to_vec(), self.device())?;
564
565 Ok((sorted_tensor, indices_tensor))
566 }
567
568 pub fn min(&self) -> Result<Self>
570 where
571 T: std::cmp::PartialOrd + Copy,
572 {
573 let data = self.data()?;
574 if data.is_empty() {
575 return Err(TorshError::InvalidOperation(
576 "Cannot compute min of empty tensor".to_string(),
577 ));
578 }
579
580 let min_val = data
581 .iter()
582 .fold(data[0], |acc, &x| if x < acc { x } else { acc });
583 Self::from_data(vec![min_val], vec![], self.device)
584 }
585
586 pub fn t(&self) -> Result<Self>
588 where
589 T: Copy + num_traits::Zero,
590 {
591 let shape = self.shape();
592 let dims = shape.dims();
593
594 if dims.len() != 2 {
595 return Err(TorshError::InvalidOperation(
596 "Transpose operation only supported for 2D tensors".to_string(),
597 ));
598 }
599
600 let (rows, cols) = (dims[0], dims[1]);
601 let data = self.data()?;
602 let mut transposed_data = vec![num_traits::Zero::zero(); data.len()];
603
604 for i in 0..rows {
605 for j in 0..cols {
606 transposed_data[j * rows + i] = data[i * cols + j];
607 }
608 }
609
610 Self::from_data(transposed_data, vec![cols, rows], self.device)
611 }
612
613 pub fn shares_storage(&self, other: &Self) -> bool {
615 match (&self.storage, &other.storage) {
617 (TensorStorage::InMemory(a), TensorStorage::InMemory(b)) => Arc::ptr_eq(a, b),
618 (TensorStorage::MemoryMapped(a), TensorStorage::MemoryMapped(b)) => Arc::ptr_eq(a, b),
619 _ => false,
620 }
621 }
622
623 pub fn data(&self) -> Result<Vec<T>>
625 where
626 T: Copy,
627 {
628 self.to_vec()
629 }
630
631 pub fn data_mut_apply<F>(&mut self, mut func: F) -> Result<()>
633 where
634 F: FnMut(&mut T),
635 T: Copy,
636 {
637 self.ensure_exclusive_data()?;
638
639 match &mut self.storage {
640 TensorStorage::InMemory(data) => {
641 let mut data_guard = data.write().expect("lock should not be poisoned");
642 for item in data_guard.iter_mut() {
643 func(item);
644 }
645 Ok(())
646 }
647 TensorStorage::MemoryMapped(_) => {
648 let data = self.to_vec()?;
650 let mut new_data = data;
651 for item in new_data.iter_mut() {
652 func(item);
653 }
654 self.storage = TensorStorage::create_optimal(new_data)?;
656 Ok(())
657 }
658 #[cfg(feature = "simd")]
659 TensorStorage::Aligned(data) => {
660 let mut data_guard = data.write().expect("lock should not be poisoned");
661 for item in data_guard.as_mut_slice().iter_mut() {
662 func(item);
663 }
664 Ok(())
665 }
666 #[cfg(feature = "simd")]
667 TensorStorage::SimdOptimized(_) => {
668 let data = self.to_vec()?;
671 let mut new_data = data;
672 for item in new_data.iter_mut() {
673 func(item);
674 }
675 self.storage = TensorStorage::create_optimal(new_data)?;
676 Ok(())
677 }
678 }
679 }
680
681 pub fn clone_data(&self) -> Self
683 where
684 T: Copy,
685 {
686 let data = self
687 .to_vec()
688 .expect("tensor to vec conversion should succeed");
689 Self::from_data(data, self.shape().dims().to_vec(), self.device)
690 .expect("tensor creation should succeed")
691 }
692
693 pub fn make_unique(&mut self) -> Result<()> {
695 match &self.storage {
697 TensorStorage::InMemory(data) => {
698 if Arc::strong_count(data) > 1 {
699 let data_vec = self.to_vec()?;
700 self.storage = TensorStorage::create_optimal(data_vec)?;
701 }
702 }
703 TensorStorage::MemoryMapped(storage) => {
704 if Arc::strong_count(storage) > 1 {
705 let data_vec = self.to_vec()?;
706 self.storage = TensorStorage::create_optimal(data_vec)?;
707 }
708 }
709 #[cfg(feature = "simd")]
710 TensorStorage::Aligned(data) => {
711 if Arc::strong_count(data) > 1 {
712 let data_vec = self.to_vec()?;
713 self.storage = TensorStorage::create_optimal(data_vec)?;
714 }
715 }
716 #[cfg(feature = "simd")]
717 TensorStorage::SimdOptimized(_storage) => {
718 let data_vec = self.to_vec()?;
721 self.storage = TensorStorage::aligned(data_vec)?;
722 }
723 }
724 Ok(())
725 }
726
727 pub fn apply_<F>(&mut self, func: F) -> Result<()>
729 where
730 F: Fn(T) -> T,
731 T: Copy,
732 {
733 let data = self.to_vec()?;
734 let new_data: Vec<T> = data.into_iter().map(func).collect();
735
736 self.storage = TensorStorage::create_optimal(new_data)?;
738 Ok(())
739 }
740
741 pub fn map<F>(&self, func: F) -> Result<Self>
743 where
744 F: Fn(T) -> T,
745 T: Copy,
746 {
747 let data = self.to_vec()?;
748 let new_data: Vec<T> = data.into_iter().map(func).collect();
749 let mut result = Self::from_data(new_data, self.shape().dims().to_vec(), self.device)?;
750
751 result.requires_grad = self.requires_grad;
753
754 Ok(result)
755 }
756
757 pub fn item(&self) -> Result<T>
759 where
760 T: Copy,
761 {
762 let data = self.data()?;
763 if data.len() != 1 {
764 return Err(TorshError::InvalidArgument(format!(
765 "item() can only be called on single-element tensors, got {} elements",
766 data.len()
767 )));
768 }
769 Ok(data[0])
770 }
771
772 pub fn cat(tensors: &[&Self], dim: i32) -> Result<Self>
774 where
775 T: Copy,
776 {
777 if tensors.is_empty() {
778 return Err(TorshError::InvalidArgument(
779 "Cannot concatenate empty tensor list".to_string(),
780 ));
781 }
782
783 let first_shape_binding = tensors[0].shape();
784 let first_shape = first_shape_binding.dims();
785 let ndim = first_shape.len();
786
787 let actual_dim = if dim < 0 {
789 (ndim as i32 + dim) as usize
790 } else {
791 dim as usize
792 };
793
794 if actual_dim >= ndim {
795 return Err(TorshError::InvalidArgument(format!(
796 "Dimension {} out of range for {}-dimensional tensor",
797 dim, ndim
798 )));
799 }
800
801 for (i, tensor) in tensors.iter().enumerate().skip(1) {
803 let shape_binding = tensor.shape();
804 let shape = shape_binding.dims();
805 if shape.len() != ndim {
806 return Err(TorshError::InvalidArgument(format!(
807 "Tensor {} has {} dimensions but first tensor has {}",
808 i,
809 shape.len(),
810 ndim
811 )));
812 }
813 for (d, (&s1, &s2)) in first_shape.iter().zip(shape.iter()).enumerate() {
814 if d != actual_dim && s1 != s2 {
815 return Err(TorshError::ShapeMismatch {
816 expected: first_shape.to_vec(),
817 got: shape.to_vec(),
818 });
819 }
820 }
821 }
822
823 let cat_dim_total: usize = tensors.iter().map(|t| t.shape().dims()[actual_dim]).sum();
825 let mut result_shape = first_shape.to_vec();
826 result_shape[actual_dim] = cat_dim_total;
827
828 let outer_size: usize = first_shape[..actual_dim].iter().product();
832 let inner_size: usize = first_shape[actual_dim + 1..].iter().product();
833
834 let total_numel: usize = result_shape.iter().product();
835 let mut result_data = Vec::with_capacity(total_numel);
836
837 for outer in 0..outer_size {
838 for tensor in tensors {
839 let tensor_shape_binding = tensor.shape();
840 let tensor_shape = tensor_shape_binding.dims();
841 let cat_size = tensor_shape[actual_dim];
842 let tensor_data = tensor.data()?;
843
844 for cat_idx in 0..cat_size {
845 for inner in 0..inner_size {
846 let src_idx = outer * cat_size * inner_size + cat_idx * inner_size + inner;
847 result_data.push(tensor_data[src_idx]);
848 }
849 }
850 }
851 }
852
853 Self::from_data(result_data, result_shape, tensors[0].device)
854 }
855
856 fn ensure_exclusive_data(&mut self) -> Result<()> {
859 match &self.storage {
860 TensorStorage::InMemory(data) => {
861 if Arc::strong_count(data) > 1 {
862 let cloned_data = {
864 let data_guard = data.read().expect("lock should not be poisoned");
865 data_guard.clone()
866 };
867 self.storage = TensorStorage::in_memory(cloned_data);
868 }
869 }
870 TensorStorage::MemoryMapped(storage) => {
871 if Arc::strong_count(storage) > 1 {
872 let data_vec = self.storage.to_vec()?;
874 self.storage = TensorStorage::create_optimal(data_vec)?;
875 }
876 }
877 #[cfg(feature = "simd")]
878 TensorStorage::Aligned(data) => {
879 if Arc::strong_count(data) > 1 {
880 let vec_data = {
882 let data_guard = data.read().expect("lock should not be poisoned");
883 data_guard.as_slice().to_vec()
884 };
885 self.storage = TensorStorage::aligned(vec_data)?;
886 }
887 }
888 #[cfg(feature = "simd")]
889 TensorStorage::SimdOptimized(storage) => {
890 if Arc::strong_count(storage) > 1 || storage.is_shared() {
891 let vec_data = storage.to_vec();
893 self.storage = TensorStorage::simd_optimized(vec_data)?;
894 }
895 }
896 }
897 Ok(())
898 }
899}
900
901impl<T: TensorElement + Copy> Tensor<T>
903where
904 T: num_traits::Float,
905{
906 pub fn norm(&self) -> Result<Self> {
908 let data = self.data()?;
909 let sum_squares: T = data
910 .iter()
911 .map(|&x| x * x)
912 .fold(num_traits::Zero::zero(), |acc, x| acc + x);
913 let norm_value = sum_squares.sqrt();
914
915 Tensor::from_data(vec![norm_value], vec![], self.device())
917 }
918}
919
920impl<T: TensorElement + Copy> Tensor<T> {
922 pub fn matmul_scirs2(&self, other: &Self) -> Result<Self>
924 where
925 T: num_traits::Float + num_traits::Zero + num_traits::One + std::iter::Sum,
926 {
927 self.basic_matmul(other)
930 }
931
932 pub fn sum_scirs2(&self) -> Result<Self>
934 where
935 T: std::ops::Add<Output = T> + num_traits::Zero,
936 {
937 let data = self.data()?;
939 let sum_value = data
940 .iter()
941 .fold(<T as num_traits::Zero>::zero(), |acc, &x| acc + x);
942 Tensor::from_data(vec![sum_value], vec![], self.device())
943 }
944
945 pub fn mean_scirs2(&self) -> Result<Self>
947 where
948 T: std::ops::Add<Output = T>
949 + std::ops::Div<Output = T>
950 + num_traits::Zero
951 + From<usize>
952 + num_traits::FromPrimitive,
953 {
954 let data = self.data()?;
956 if data.is_empty() {
957 return Err(TorshError::InvalidArgument(
958 "Cannot compute mean of empty tensor".to_string(),
959 ));
960 }
961 let sum_value = data
962 .iter()
963 .fold(<T as num_traits::Zero>::zero(), |acc, &x| acc + x);
964 let mean_value = sum_value / T::from(data.len());
965 Tensor::from_data(vec![mean_value], vec![], self.device())
966 }
967
968 pub fn relu_scirs2(&self) -> Result<Self>
970 where
971 T: PartialOrd + num_traits::Zero,
972 {
973 let zero = <T as num_traits::Zero>::zero();
975 self.map(|x| if x > zero { x } else { zero })
976 }
977
978 pub fn sigmoid_scirs2(&self) -> Result<Self>
980 where
981 T: num_traits::Float,
982 {
983 self.map(|x| {
985 let one = <T as num_traits::One>::one();
986 one / (one + (-x).exp())
987 })
988 }
989
990 pub fn tanh_scirs2(&self) -> Result<Self>
992 where
993 T: num_traits::Float,
994 {
995 self.map(|x| x.tanh())
997 }
998
999 fn basic_matmul(&self, other: &Self) -> Result<Self>
1001 where
1002 T: num_traits::Float + std::iter::Sum,
1003 {
1004 let self_binding = self.shape();
1005 let self_shape = self_binding.dims();
1006 let other_binding = other.shape();
1007 let other_shape = other_binding.dims();
1008
1009 if self_shape.len() != 2 || other_shape.len() != 2 {
1011 return Err(TorshError::InvalidArgument(
1012 "Matrix multiplication requires 2D tensors".to_string(),
1013 ));
1014 }
1015
1016 if self_shape[1] != other_shape[0] {
1017 return Err(TorshError::ShapeMismatch {
1018 expected: vec![self_shape[0], other_shape[1]],
1019 got: vec![self_shape[1], other_shape[0]],
1020 });
1021 }
1022
1023 let (m, k) = (self_shape[0], self_shape[1]);
1024 let n = other_shape[1];
1025
1026 let self_data = self.data()?;
1027 let other_data = other.data()?;
1028 let mut result_data = vec![num_traits::Zero::zero(); m * n];
1029
1030 for i in 0..m {
1032 for j in 0..n {
1033 let mut sum = num_traits::Zero::zero();
1034 for k_idx in 0..k {
1035 sum = sum + self_data[i * k + k_idx] * other_data[k_idx * n + j];
1036 }
1037 result_data[i * n + j] = sum;
1038 }
1039 }
1040
1041 Self::from_data(result_data, vec![m, n], self.device)
1042 }
1043 pub fn softmax(&self, dim: i32) -> Result<Self>
1046 where
1047 T: torsh_core::dtype::FloatElement
1048 + Copy
1049 + std::ops::Sub<Output = T>
1050 + std::ops::Div<Output = T>,
1051 {
1052 let data = self.data()?;
1053 let shape_binding = self.shape();
1054 let shape = shape_binding.dims();
1055
1056 if data.is_empty() || shape.is_empty() {
1058 return Err(TorshError::InvalidOperation(
1059 "Cannot compute softmax on empty tensor".to_string(),
1060 ));
1061 }
1062
1063 let actual_dim = if dim < 0 {
1065 (shape.len() as i32 + dim) as usize
1066 } else {
1067 dim as usize
1068 };
1069
1070 if actual_dim >= shape.len() {
1071 return Err(TorshError::InvalidOperation(format!(
1072 "Dimension {} out of range for {}-dimensional tensor",
1073 actual_dim,
1074 shape.len()
1075 )));
1076 }
1077
1078 let max_tensor = self.max(Some(actual_dim), true)?;
1080
1081 let expanded_max = max_tensor.expand(shape)?;
1083 let shifted = self.sub(&expanded_max)?;
1084 let exp_tensor = shifted.exp()?;
1085 let sum_tensor = exp_tensor.sum_dim(&[actual_dim as i32], true)?;
1086
1087 let expanded_sum = sum_tensor.expand(shape)?;
1089 exp_tensor.div(&expanded_sum)
1090 }
1091
1092 pub fn log_softmax(&self, dim: i32) -> Result<Self>
1095 where
1096 T: torsh_core::dtype::FloatElement + Copy + std::ops::Sub<Output = T>,
1097 {
1098 let softmax_result = self.softmax(dim)?;
1099 softmax_result.log()
1100 }
1101
1102 pub fn cumsum(&self, dim: i32) -> Result<Self>
1104 where
1105 T: std::ops::Add<Output = T> + num_traits::Zero + Copy,
1106 {
1107 let shape_binding = self.shape();
1108 let shape = shape_binding.dims();
1109
1110 let actual_dim = if dim < 0 {
1112 (shape.len() as i32 + dim) as usize
1113 } else {
1114 dim as usize
1115 };
1116
1117 if actual_dim >= shape.len() {
1118 return Err(TorshError::InvalidOperation(format!(
1119 "Dimension {} out of range for {}-dimensional tensor",
1120 actual_dim,
1121 shape.len()
1122 )));
1123 }
1124
1125 let data = self.data()?;
1126 let mut result_data = data.clone();
1127
1128 if actual_dim == shape.len() - 1 || shape.len() == 1 {
1131 let mut cumulative = <T as num_traits::Zero>::zero();
1132 for i in 0..result_data.len() {
1133 cumulative = cumulative + result_data[i];
1134 result_data[i] = cumulative;
1135 }
1136 }
1137
1138 Self::from_data(result_data, shape.to_vec(), self.device)
1139 }
1140
1141 pub fn argmin(&self, dim: Option<i32>) -> Result<Tensor<i64>>
1143 where
1144 T: std::cmp::PartialOrd + Copy,
1145 {
1146 let data = self.data()?;
1147 let shape_binding = self.shape();
1148 let shape = shape_binding.dims();
1149
1150 if shape.is_empty() {
1151 return Err(TorshError::InvalidOperation(
1152 "Cannot compute argmin on empty tensor".to_string(),
1153 ));
1154 }
1155
1156 match dim {
1157 Some(d) => {
1158 let actual_dim = if d < 0 {
1160 (shape.len() as i32 + d) as usize
1161 } else {
1162 d as usize
1163 };
1164
1165 if actual_dim >= shape.len() {
1166 return Err(TorshError::InvalidOperation(format!(
1167 "Dimension {} out of range for {}-dimensional tensor",
1168 actual_dim,
1169 shape.len()
1170 )));
1171 }
1172
1173 let min_val = data
1176 .iter()
1177 .fold(data[0], |acc, &x| if x < acc { x } else { acc });
1178 let min_idx = data.iter().position(|&x| x == min_val).unwrap_or(0);
1179
1180 let result_data = vec![min_idx as i64];
1181 Tensor::<i64>::from_data(result_data, vec![1], self.device)
1182 }
1183 None => {
1184 let min_val = data
1186 .iter()
1187 .fold(data[0], |acc, &x| if x < acc { x } else { acc });
1188 let min_idx = data.iter().position(|&x| x == min_val).unwrap_or(0);
1189
1190 let result_data = vec![min_idx as i64];
1191 Tensor::<i64>::from_data(result_data, vec![], self.device)
1192 }
1193 }
1194 }
1195
1196 pub fn argmax(&self, dim: Option<i32>) -> Result<Tensor<i64>>
1198 where
1199 T: std::cmp::PartialOrd + Copy,
1200 {
1201 let data = self.data()?;
1202 let shape_binding = self.shape();
1203 let shape = shape_binding.dims();
1204
1205 if shape.is_empty() {
1206 return Err(TorshError::InvalidOperation(
1207 "Cannot compute argmax on empty tensor".to_string(),
1208 ));
1209 }
1210
1211 match dim {
1212 Some(d) => {
1213 let actual_dim = if d < 0 {
1215 (shape.len() as i32 + d) as usize
1216 } else {
1217 d as usize
1218 };
1219
1220 if actual_dim >= shape.len() {
1221 return Err(TorshError::InvalidOperation(format!(
1222 "Dimension {} out of range for {}-dimensional tensor",
1223 actual_dim,
1224 shape.len()
1225 )));
1226 }
1227
1228 let max_val = data
1231 .iter()
1232 .fold(data[0], |acc, &x| if x > acc { x } else { acc });
1233 let max_idx = data.iter().position(|&x| x == max_val).unwrap_or(0);
1234
1235 let result_data = vec![max_idx as i64];
1236 Tensor::<i64>::from_data(result_data, vec![1], self.device)
1237 }
1238 None => {
1239 let max_val = data
1241 .iter()
1242 .fold(data[0], |acc, &x| if x > acc { x } else { acc });
1243 let max_idx = data.iter().position(|&x| x == max_val).unwrap_or(0);
1244
1245 let result_data = vec![max_idx as i64];
1246 Tensor::<i64>::from_data(result_data, vec![], self.device)
1247 }
1248 }
1249 }
1250
1251 pub fn topk(
1253 &self,
1254 k: usize,
1255 dim: Option<i32>,
1256 largest: bool,
1257 sorted: bool,
1258 ) -> Result<(Self, Tensor<i64>)>
1259 where
1260 T: std::cmp::PartialOrd + Copy + num_traits::Zero,
1261 {
1262 let data = self.data()?;
1263 let shape_binding = self.shape();
1264 let shape = shape_binding.dims();
1265
1266 if shape.is_empty() {
1267 return Err(TorshError::InvalidOperation(
1268 "Cannot compute topk on empty tensor".to_string(),
1269 ));
1270 }
1271
1272 if k == 0 {
1273 return Err(TorshError::InvalidArgument(
1274 "k must be greater than 0".to_string(),
1275 ));
1276 }
1277
1278 let actual_dim = match dim {
1280 Some(d) => {
1281 let norm = if d < 0 {
1282 (shape.len() as i32 + d) as usize
1283 } else {
1284 d as usize
1285 };
1286 if norm >= shape.len() {
1287 return Err(TorshError::InvalidArgument(format!(
1288 "Dimension {} out of range for {}-dimensional tensor",
1289 d,
1290 shape.len()
1291 )));
1292 }
1293 norm
1294 }
1295 None => shape.len() - 1,
1296 };
1297
1298 let dim_size = shape[actual_dim];
1299 let effective_k = k.min(dim_size);
1300
1301 let outer_size: usize = shape[..actual_dim].iter().product();
1302 let inner_size: usize = shape[actual_dim + 1..].iter().product();
1303
1304 let mut result_shape = shape.to_vec();
1306 result_shape[actual_dim] = effective_k;
1307
1308 let mut values_data = Vec::with_capacity(outer_size * effective_k * inner_size);
1309 let mut indices_data = Vec::with_capacity(outer_size * effective_k * inner_size);
1310
1311 for outer in 0..outer_size {
1312 for inner in 0..inner_size {
1313 let mut slice: Vec<(usize, T)> = (0..dim_size)
1315 .map(|d| {
1316 let src = outer * dim_size * inner_size + d * inner_size + inner;
1317 (d, data[src])
1318 })
1319 .collect();
1320
1321 if largest {
1323 slice
1324 .sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1325 } else {
1326 slice
1327 .sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
1328 }
1329
1330 let mut top_k: Vec<(usize, T)> = slice.into_iter().take(effective_k).collect();
1331
1332 if !sorted {
1334 top_k.sort_by_key(|(idx, _)| *idx);
1335 }
1336
1337 for (local_idx, val) in &top_k {
1338 values_data.push(*val);
1339 indices_data.push(*local_idx as i64);
1340 }
1341 }
1342 }
1343
1344 let transposed_len = outer_size * effective_k * inner_size;
1348 let mut values_transposed = Vec::with_capacity(transposed_len);
1349 let mut indices_transposed = Vec::with_capacity(transposed_len);
1350
1351 for outer in 0..outer_size {
1352 for k_idx in 0..effective_k {
1353 for inner in 0..inner_size {
1354 let src = outer * inner_size * effective_k + inner * effective_k + k_idx;
1355 values_transposed.push(values_data[src]);
1356 indices_transposed.push(indices_data[src]);
1357 }
1358 }
1359 }
1360
1361 let values_tensor = Self::from_data(values_transposed, result_shape.clone(), self.device)?;
1362 let indices_tensor =
1363 Tensor::<i64>::from_data(indices_transposed, result_shape, self.device)?;
1364
1365 Ok((values_tensor, indices_tensor))
1366 }
1367}
1368
1369#[cfg(test)]
1370mod tests {
1371 use super::*;
1372 use torsh_core::device::DeviceType;
1373
1374 #[test]
1375 fn test_scalar_creation() {
1376 let scalar = Tensor::<f32>::scalar(42.0).expect("operation should succeed");
1377 assert_eq!(scalar.shape().dims(), &[] as &[usize]);
1378 assert_eq!(scalar.item().expect("item extraction should succeed"), 42.0);
1379 }
1380
1381 #[test]
1382 fn test_max_reduction() {
1383 let data = vec![1.0f32, 5.0, 3.0, 2.0];
1384 let tensor =
1385 Tensor::from_data(data, vec![4], DeviceType::Cpu).expect("operation should succeed");
1386 let max_val = tensor.max(None, false).expect("operation should succeed");
1387 assert_eq!(max_val.item().expect("item extraction should succeed"), 5.0);
1388 }
1389
1390 #[test]
1391 fn test_norm_computation() {
1392 let data = vec![3.0f32, 4.0]; let tensor =
1394 Tensor::from_data(data, vec![2], DeviceType::Cpu).expect("operation should succeed");
1395 let norm = tensor.norm().expect("norm computation should succeed");
1396 assert!((norm.item().expect("item extraction should succeed") - 5.0).abs() < 1e-6);
1397 }
1398
1399 #[test]
1400 fn test_apply_operations() {
1401 let data = vec![1.0f32, 2.0, 3.0, 4.0];
1402 let mut tensor =
1403 Tensor::from_data(data, vec![4], DeviceType::Cpu).expect("operation should succeed");
1404
1405 tensor
1407 .apply_(|x| x * 2.0)
1408 .expect("operation should succeed");
1409 assert_eq!(
1410 tensor.data().expect("data retrieval should succeed"),
1411 vec![2.0, 4.0, 6.0, 8.0]
1412 );
1413
1414 let original = Tensor::from_data(vec![1.0f32, 2.0, 3.0], vec![3], DeviceType::Cpu)
1416 .expect("operation should succeed");
1417 let mapped = original.map(|x| x + 1.0).expect("operation should succeed");
1418 assert_eq!(
1419 mapped.data().expect("data retrieval should succeed"),
1420 vec![2.0, 3.0, 4.0]
1421 );
1422 assert_eq!(
1423 original.data().expect("data retrieval should succeed"),
1424 vec![1.0, 2.0, 3.0]
1425 ); }
1427
1428 #[test]
1429 fn test_activation_functions() {
1430 let data = vec![-1.0f32, 0.0, 1.0, 2.0];
1431 let tensor =
1432 Tensor::from_data(data, vec![4], DeviceType::Cpu).expect("operation should succeed");
1433
1434 let relu_result = tensor.relu().expect("relu should succeed");
1436 assert_eq!(
1437 relu_result.data().expect("data retrieval should succeed"),
1438 vec![0.0, 0.0, 1.0, 2.0]
1439 );
1440
1441 let abs_result = tensor.abs().expect("abs computation should succeed");
1443 assert_eq!(
1444 abs_result.data().expect("data retrieval should succeed"),
1445 vec![1.0, 0.0, 1.0, 2.0]
1446 );
1447
1448 let clamped = tensor.clamp(-0.5, 1.5).expect("operation should succeed");
1450 assert_eq!(
1451 clamped.data().expect("data retrieval should succeed"),
1452 vec![-0.5, 0.0, 1.0, 1.5]
1453 );
1454 }
1455
1456 #[test]
1457 fn test_storage_sharing() {
1458 let tensor1 =
1459 Tensor::<f32>::zeros(&[2, 2], DeviceType::Cpu).expect("operation should succeed");
1460 let tensor2 = tensor1.clone();
1461 let tensor3 = tensor1.clone_data();
1462
1463 assert!(tensor1.shares_storage(&tensor2));
1464 assert!(!tensor1.shares_storage(&tensor3));
1465 }
1466
1467 #[test]
1468 fn test_basic_matmul() {
1469 let a = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![2, 2], DeviceType::Cpu)
1470 .expect("operation should succeed");
1471 let b = Tensor::from_data(vec![5.0f32, 6.0, 7.0, 8.0], vec![2, 2], DeviceType::Cpu)
1472 .expect("operation should succeed");
1473
1474 let result = a.basic_matmul(&b).expect("operation should succeed");
1475 assert_eq!(result.shape().dims(), &[2, 2]);
1476
1477 let expected = vec![19.0, 22.0, 43.0, 50.0];
1480 assert_eq!(
1481 result.data().expect("data retrieval should succeed"),
1482 expected
1483 );
1484 }
1485
1486 #[test]
1487 fn test_reductions() {
1488 let data = vec![1.0f32, 2.0, 3.0, 4.0];
1489 let tensor =
1490 Tensor::from_data(data, vec![4], DeviceType::Cpu).expect("operation should succeed");
1491
1492 let sum = tensor.sum().expect("sum should succeed");
1493 assert_eq!(sum.item().expect("item extraction should succeed"), 10.0);
1494
1495 let mean = tensor.mean(None, false).expect("operation should succeed");
1496 assert_eq!(mean.item().expect("item extraction should succeed"), 2.5);
1497 }
1498
1499 #[test]
1500 fn test_copy_on_write() {
1501 let mut tensor1 =
1502 Tensor::<f32>::ones(&[2], DeviceType::Cpu).expect("operation should succeed");
1503 let tensor2 = tensor1.clone();
1504
1505 assert!(tensor1.shares_storage(&tensor2));
1507
1508 tensor1.make_unique().expect("make_unique should succeed");
1510 assert!(!tensor1.shares_storage(&tensor2));
1511 }
1512
1513 #[test]
1514 fn test_item_extraction() {
1515 let scalar = Tensor::from_data(vec![42.0f32], vec![], DeviceType::Cpu)
1516 .expect("operation should succeed");
1517 assert_eq!(scalar.item().expect("item extraction should succeed"), 42.0);
1518
1519 let vector = Tensor::from_data(vec![1.0f32, 2.0], vec![2], DeviceType::Cpu)
1520 .expect("operation should succeed");
1521 assert!(vector.item().is_err()); }
1523
1524 #[test]
1525 fn test_all_dim() {
1526 let data = vec![1i32, 0, 1, 1, 1, 1];
1528 let tensor = Tensor::from_data(data, vec![2, 3], DeviceType::Cpu)
1529 .expect("tensor creation should succeed");
1530
1531 let result = tensor.all_dim(0, false).expect("all_dim should succeed");
1534 assert_eq!(result.shape().dims(), &[3]);
1535 assert_eq!(
1536 result.to_vec().expect("to_vec should succeed"),
1537 vec![true, false, true]
1538 );
1539
1540 let result_row = tensor.all_dim(1, false).expect("all_dim should succeed");
1543 assert_eq!(result_row.shape().dims(), &[2]);
1544 assert_eq!(
1545 result_row.to_vec().expect("to_vec should succeed"),
1546 vec![false, true]
1547 );
1548
1549 let result_kd = tensor.all_dim(1, true).expect("all_dim should succeed");
1551 assert_eq!(result_kd.shape().dims(), &[2, 1]);
1552 }
1553
1554 #[test]
1555 fn test_any_dim() {
1556 let data = vec![0i32, 0, 0, 0, 1, 0];
1558 let tensor = Tensor::from_data(data, vec![2, 3], DeviceType::Cpu)
1559 .expect("tensor creation should succeed");
1560
1561 let result = tensor.any_dim(0, false).expect("any_dim should succeed");
1563 assert_eq!(result.shape().dims(), &[3]);
1564 assert_eq!(
1565 result.to_vec().expect("to_vec should succeed"),
1566 vec![false, true, false]
1567 );
1568
1569 let result_row = tensor.any_dim(1, false).expect("any_dim should succeed");
1571 assert_eq!(result_row.shape().dims(), &[2]);
1572 assert_eq!(
1573 result_row.to_vec().expect("to_vec should succeed"),
1574 vec![false, true]
1575 );
1576 }
1577
1578 #[test]
1579 fn test_cat_multidim() {
1580 let a = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![2, 2], DeviceType::Cpu)
1582 .expect("tensor creation should succeed");
1583 let b = Tensor::from_data(vec![5.0f32, 6.0], vec![1, 2], DeviceType::Cpu)
1584 .expect("tensor creation should succeed");
1585
1586 let cat0 = Tensor::<f32>::cat(&[&a, &b], 0).expect("cat should succeed");
1587 assert_eq!(cat0.shape().dims(), &[3, 2]);
1588 assert_eq!(
1589 cat0.to_vec().expect("to_vec should succeed"),
1590 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
1591 );
1592
1593 let c = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![2, 2], DeviceType::Cpu)
1595 .expect("tensor creation should succeed");
1596 let d = Tensor::from_data(vec![5.0f32, 6.0, 7.0, 8.0], vec![2, 2], DeviceType::Cpu)
1597 .expect("tensor creation should succeed");
1598
1599 let cat1 = Tensor::<f32>::cat(&[&c, &d], 1).expect("cat should succeed");
1600 assert_eq!(cat1.shape().dims(), &[2, 4]);
1601 assert_eq!(
1602 cat1.to_vec().expect("to_vec should succeed"),
1603 vec![1.0, 2.0, 5.0, 6.0, 3.0, 4.0, 7.0, 8.0]
1604 );
1605 }
1606
1607 #[test]
1608 fn test_topk_along_dim() {
1609 let data = vec![3.0f32, 1.0, 4.0, 2.0, 5.0, 9.0, 2.0, 6.0];
1611 let tensor = Tensor::from_data(data, vec![2, 4], DeviceType::Cpu)
1612 .expect("tensor creation should succeed");
1613
1614 let (vals, idxs) = tensor
1615 .topk(2, Some(1), true, true)
1616 .expect("topk should succeed");
1617 assert_eq!(vals.shape().dims(), &[2, 2]);
1618 assert_eq!(idxs.shape().dims(), &[2, 2]);
1619
1620 let vals_data = vals.to_vec().expect("to_vec should succeed");
1623 let idxs_data = idxs.to_vec().expect("to_vec should succeed");
1624 assert_eq!(vals_data[0], 4.0);
1625 assert_eq!(vals_data[1], 3.0);
1626 assert_eq!(vals_data[2], 9.0);
1627 assert_eq!(vals_data[3], 6.0);
1628 assert_eq!(idxs_data[0], 2);
1629 assert_eq!(idxs_data[1], 0);
1630 assert_eq!(idxs_data[2], 1);
1631 assert_eq!(idxs_data[3], 3);
1632 }
1633
1634 #[test]
1637 fn test_issue_43_mean_propagates_requires_grad() {
1638 let input = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![4], DeviceType::Cpu)
1640 .expect("tensor creation failed")
1641 .requires_grad_(true);
1642
1643 let result = input.mean(None, false).expect("mean should succeed");
1644 assert!(
1645 result.requires_grad(),
1646 "mean result must have requires_grad=true when input does"
1647 );
1648 }
1649
1650 #[test]
1651 fn test_issue_43_mean_no_requires_grad_when_input_has_none() {
1652 let input = Tensor::from_data(vec![1.0f32, 2.0, 3.0], vec![3], DeviceType::Cpu)
1654 .expect("tensor creation failed");
1655
1656 let result = input.mean(None, false).expect("mean should succeed");
1657 assert!(
1658 !result.requires_grad(),
1659 "mean result must not require grad when input does not"
1660 );
1661 }
1662
1663 #[test]
1664 fn test_issue_43_mean_backward() {
1665 let n = 4usize;
1668 let input = Tensor::from_data(vec![2.0f32, 4.0, 6.0, 8.0], vec![n], DeviceType::Cpu)
1669 .expect("tensor creation failed")
1670 .requires_grad_(true);
1671
1672 let result = input.mean(None, false).expect("mean should succeed");
1673 assert!(result.requires_grad(), "mean result must track gradients");
1674 result.backward().expect("backward should succeed");
1676
1677 let grad = input
1678 .grad()
1679 .expect("input must have gradient after backward");
1680 let grad_data = grad.data().expect("gradient data");
1681
1682 let expected = 1.0f32 / n as f32;
1684 for &g in &grad_data {
1685 assert!(
1686 (g - expected).abs() < 1e-6,
1687 "each element grad should be 1/n={expected}, got {g}"
1688 );
1689 }
1690 }
1691
1692 #[test]
1693 fn test_sum_backward() {
1694 let x = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![4], DeviceType::Cpu)
1696 .expect("tensor creation failed")
1697 .requires_grad_(true);
1698 let loss = x.sum().expect("sum should succeed");
1699 assert!(loss.requires_grad(), "sum result must track gradients");
1700 loss.backward().expect("backward should succeed");
1701 let grad = x.grad().expect("x must have a gradient after backward");
1702 let grad_data = grad.data().expect("gradient data");
1703 assert_eq!(
1704 grad_data,
1705 vec![1.0f32, 1.0, 1.0, 1.0],
1706 "d(sum)/dx must be all ones"
1707 );
1708 }
1709
1710 #[test]
1711 fn test_matmul_backward() {
1712 let a = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![2, 2], DeviceType::Cpu)
1717 .expect("tensor creation failed")
1718 .requires_grad_(true);
1719 let b = Tensor::from_data(vec![5.0f32, 6.0, 7.0, 8.0], vec![2, 2], DeviceType::Cpu)
1720 .expect("tensor creation failed")
1721 .requires_grad_(true);
1722
1723 let c = a.matmul(&b).expect("matmul should succeed");
1724 assert!(c.requires_grad(), "matmul result must track gradients");
1725 let loss = c.sum().expect("sum should succeed");
1726 loss.backward().expect("backward should succeed");
1727
1728 let grad_a = a
1729 .grad()
1730 .expect("A must have a gradient")
1731 .data()
1732 .expect("grad data");
1733 let grad_b = b
1734 .grad()
1735 .expect("B must have a gradient")
1736 .data()
1737 .expect("grad data");
1738 assert_eq!(
1739 grad_a,
1740 vec![11.0f32, 15.0, 11.0, 15.0],
1741 "grad_A = ones @ Bᵀ"
1742 );
1743 assert_eq!(grad_b, vec![4.0f32, 4.0, 6.0, 6.0], "grad_B = Aᵀ @ ones");
1744 }
1745}