1use std::sync::{Arc, RwLock};
16use torsh_core::{
17 dtype::TensorElement,
18 error::{Result, TorshError},
19 shape::Shape,
20};
21
22use crate::core_ops::{Operation, Tensor};
23use crate::memory_pool::global_acquire_uninit;
24
25impl<T: TensorElement + Copy> Tensor<T> {
26 pub fn size(&self, dim: i32) -> Result<usize> {
28 self.shape().size(dim)
29 }
30
31 pub fn view(&self, shape: &[i32]) -> Result<Self> {
78 let infer_count = shape.iter().filter(|&&x| x == -1).count();
80 if infer_count > 1 {
81 return Err(TorshError::InvalidShape(
82 "Only one dimension can be inferred (only one -1 allowed)".to_string(),
83 ));
84 }
85
86 let new_shape: Result<Vec<usize>> = shape
87 .iter()
88 .map(|&d| {
89 if d == -1 {
90 let known_dims: Result<Vec<usize>> = shape
92 .iter()
93 .filter(|&&x| x != -1)
94 .map(|&x| {
95 if x < 0 {
96 Err(TorshError::InvalidShape(format!(
97 "Invalid dimension size: {x} (negative dimensions not allowed except -1)"
98 )))
99 } else {
100 Ok(x as usize)
101 }
102 })
103 .collect();
104
105 let known_dims = known_dims?;
106
107 let known_product = known_dims.iter().try_fold(1usize, |acc, &dim| {
109 acc.checked_mul(dim).ok_or_else(|| {
110 TorshError::InvalidShape(
111 "Shape dimensions too large (would overflow)".to_string()
112 )
113 })
114 })?;
115
116 if known_product == 0 {
117 return Err(TorshError::InvalidShape(
118 "Cannot infer dimension with zero-sized dimensions".to_string(),
119 ));
120 }
121
122 let total = self.numel();
123 if total % known_product != 0 {
124 return Err(TorshError::InvalidShape(
125 "Cannot infer dimension: size is not divisible".to_string(),
126 ));
127 }
128
129 Ok(total / known_product)
130 } else if d < 0 {
131 Err(TorshError::InvalidShape(format!(
132 "Invalid dimension size: {d}"
133 )))
134 } else {
135 Ok(d as usize)
136 }
137 })
138 .collect();
139
140 let new_shape = new_shape?;
141
142 let new_numel = new_shape.iter().try_fold(1usize, |acc, &dim| {
144 acc.checked_mul(dim).ok_or_else(|| {
145 TorshError::InvalidShape(
146 "Reshaped tensor would be too large (would overflow)".to_string(),
147 )
148 })
149 })?;
150
151 if new_numel != self.numel() {
152 return Err(TorshError::InvalidShape(format!(
153 "Shape {:?} is invalid for tensor of size {}",
154 new_shape,
155 self.numel()
156 )));
157 }
158
159 let data = self.to_vec()?;
161 Self::from_data(data, new_shape, self.device)
162 }
163
164 pub fn view_as(&self, shape: &[usize]) -> Result<Self> {
167 let new_numel = shape.iter().product::<usize>();
169 if new_numel != self.numel() {
170 return Err(TorshError::InvalidShape(format!(
171 "Shape {:?} is invalid for tensor of size {}",
172 shape,
173 self.numel()
174 )));
175 }
176
177 if !self.is_contiguous() {
180 return Err(TorshError::InvalidShape(
181 "Cannot create efficient view of non-contiguous tensor".to_string(),
182 ));
183 }
184
185 Ok(Self {
187 storage: self.storage.clone(),
188 shape: Shape::new(shape.to_vec()),
189 device: self.device,
190 requires_grad: self.requires_grad,
191 grad: Arc::new(RwLock::new(None)), operation: Operation::Leaf, strides: None, storage_offset: self.storage_offset,
195 base_tensor: if self.is_view() {
196 self.base_tensor.clone()
198 } else {
199 Some(Arc::downgrade(&Arc::new(self.clone())))
201 },
202 })
203 }
204
205 pub fn slice_tensor(&self, dim: usize, start: usize, end: usize) -> Result<Self> {
207 if dim >= self.ndim() {
208 return Err(TorshError::InvalidArgument(format!(
209 "Dimension {} out of range for tensor with {} dimensions",
210 dim,
211 self.ndim()
212 )));
213 }
214
215 let shape = self.shape.dims();
216 if start >= shape[dim] || end > shape[dim] || start >= end {
217 return Err(TorshError::InvalidArgument(format!(
218 "Invalid slice range [{}:{}] for dimension {} of size {}",
219 start, end, dim, shape[dim]
220 )));
221 }
222
223 let mut new_shape = shape.to_vec();
225 new_shape[dim] = end - start;
226
227 let current_strides = self.strides();
229 let offset_adjustment = start * current_strides[dim];
230
231 Ok(Self {
232 storage: self.storage.clone(),
233 shape: Shape::new(new_shape),
234 device: self.device,
235 requires_grad: self.requires_grad,
236 grad: Arc::new(RwLock::new(None)),
237 operation: Operation::Leaf,
238 strides: Some(current_strides),
239 storage_offset: self.storage_offset + offset_adjustment,
240 base_tensor: if self.is_view() {
241 self.base_tensor.clone()
242 } else {
243 Some(Arc::downgrade(&Arc::new(self.clone())))
244 },
245 })
246 }
247
248 pub fn transpose_view(&self, dim0: usize, dim1: usize) -> Result<Self> {
250 if dim0 >= self.ndim() || dim1 >= self.ndim() {
251 return Err(TorshError::InvalidArgument(format!(
252 "Dimensions {} and {} out of range for tensor with {} dimensions",
253 dim0,
254 dim1,
255 self.ndim()
256 )));
257 }
258
259 if dim0 == dim1 {
260 return Ok(self.clone());
261 }
262
263 let mut new_shape = self.shape.dims().to_vec();
265 let mut new_strides = self.strides();
266
267 new_shape.swap(dim0, dim1);
269 new_strides.swap(dim0, dim1);
270
271 Ok(Self {
272 storage: self.storage.clone(),
273 shape: Shape::new(new_shape),
274 device: self.device,
275 requires_grad: self.requires_grad,
276 grad: Arc::new(RwLock::new(None)),
277 operation: Operation::Leaf,
278 strides: Some(new_strides),
279 storage_offset: self.storage_offset,
280 base_tensor: if self.is_view() {
281 self.base_tensor.clone()
282 } else {
283 Some(Arc::downgrade(&Arc::new(self.clone())))
284 },
285 })
286 }
287
288 pub fn squeeze_tensor(&self, dim: usize) -> Result<Self> {
290 if dim >= self.ndim() {
291 return Err(TorshError::InvalidArgument(format!(
292 "Dimension {} out of range for tensor with {} dimensions",
293 dim,
294 self.ndim()
295 )));
296 }
297
298 let shape = self.shape.dims();
299 if shape[dim] != 1 {
300 return Err(TorshError::InvalidArgument(format!(
301 "Cannot squeeze dimension {} of size {}",
302 dim, shape[dim]
303 )));
304 }
305
306 let mut new_shape = shape.to_vec();
308 new_shape.remove(dim);
309
310 let mut new_strides = self.strides();
311 new_strides.remove(dim);
312
313 Ok(Self {
314 storage: self.storage.clone(),
315 shape: Shape::new(new_shape),
316 device: self.device,
317 requires_grad: self.requires_grad,
318 grad: Arc::new(RwLock::new(None)),
319 operation: Operation::Leaf,
320 strides: Some(new_strides),
321 storage_offset: self.storage_offset,
322 base_tensor: if self.is_view() {
323 self.base_tensor.clone()
324 } else {
325 Some(Arc::downgrade(&Arc::new(self.clone())))
326 },
327 })
328 }
329
330 pub fn unsqueeze_tensor(&self, dim: usize) -> Result<Self> {
332 if dim > self.ndim() {
333 return Err(TorshError::InvalidArgument(format!(
334 "Dimension {} out of range for insertion in tensor with {} dimensions",
335 dim,
336 self.ndim()
337 )));
338 }
339
340 let mut new_shape = self.shape.dims().to_vec();
342 new_shape.insert(dim, 1);
343
344 let mut new_strides = self.strides();
345 let new_stride = if dim == new_shape.len() - 1 {
347 1 } else {
349 new_strides[dim] };
351 new_strides.insert(dim, new_stride);
352
353 Ok(Self {
354 storage: self.storage.clone(),
355 shape: Shape::new(new_shape),
356 device: self.device,
357 requires_grad: self.requires_grad,
358 grad: Arc::new(RwLock::new(None)),
359 operation: Operation::Leaf,
360 strides: Some(new_strides),
361 storage_offset: self.storage_offset,
362 base_tensor: if self.is_view() {
363 self.base_tensor.clone()
364 } else {
365 Some(Arc::downgrade(&Arc::new(self.clone())))
366 },
367 })
368 }
369
370 pub fn transpose(&self, dim0: i32, dim1: i32) -> Result<Self> {
415 let ndim = self.ndim();
416 let dim0 = if dim0 < 0 {
417 (ndim as i32 + dim0) as usize
418 } else {
419 dim0 as usize
420 };
421 let dim1 = if dim1 < 0 {
422 (ndim as i32 + dim1) as usize
423 } else {
424 dim1 as usize
425 };
426
427 if dim0 >= ndim || dim1 >= ndim {
428 return Err(TorshError::InvalidArgument(format!(
429 "Dimensions {} and {} out of range for tensor with {} dimensions",
430 dim0, dim1, ndim
431 )));
432 }
433
434 if ndim == 2 && dim0 != dim1 {
435 self.transpose_2d()
436 } else {
437 self.transpose_view(dim0, dim1)
438 }
439 }
440
441 fn transpose_2d(&self) -> Result<Self> {
443 let shape = self.shape.dims();
444 if shape.len() != 2 {
445 return Err(TorshError::InvalidArgument(
446 "transpose_2d only works with 2D tensors".to_string(),
447 ));
448 }
449
450 let (rows, cols) = (shape[0], shape[1]);
451 let data = self.to_vec()?;
452 let n = data.len();
453 let mut buf = global_acquire_uninit::<T>(n);
454 let uninit = buf.as_uninit_slice_mut();
455 let mut count = 0;
456
457 for col in 0..cols {
458 for row in 0..rows {
459 uninit[count].write(data[row * cols + col]);
460 count += 1;
461 }
462 }
463
464 let transposed_data = buf.into_vec(count);
465 Self::from_data(transposed_data, vec![cols, rows], self.device)
466 }
467
468 pub fn permute(&self, dims: &[i32]) -> Result<Self> {
470 let ndim = self.ndim();
471
472 if dims.len() != ndim {
473 return Err(TorshError::InvalidArgument(format!(
474 "Number of dimensions in permutation ({}) doesn't match tensor dimensions ({})",
475 dims.len(),
476 ndim
477 )));
478 }
479
480 let perm_dims: Result<Vec<usize>> = dims
482 .iter()
483 .map(|&d| {
484 let dim = if d < 0 { ndim as i32 + d } else { d } as usize;
485 if dim >= ndim {
486 Err(TorshError::InvalidArgument(format!(
487 "Dimension {} out of range for tensor with {} dimensions",
488 d, ndim
489 )))
490 } else {
491 Ok(dim)
492 }
493 })
494 .collect();
495
496 let perm_dims = perm_dims?;
497
498 let mut sorted_dims = perm_dims.clone();
500 sorted_dims.sort_unstable();
501 for i in 0..ndim {
502 if sorted_dims[i] != i {
503 return Err(TorshError::InvalidArgument(
504 "Permutation must contain each dimension exactly once".to_string(),
505 ));
506 }
507 }
508
509 let old_shape = self.shape.dims();
511 let old_strides = self.strides();
512
513 let new_shape: Vec<usize> = perm_dims.iter().map(|&i| old_shape[i]).collect();
514 let new_strides: Vec<usize> = perm_dims.iter().map(|&i| old_strides[i]).collect();
515
516 Ok(Self {
517 storage: self.storage.clone(),
518 shape: Shape::new(new_shape),
519 device: self.device,
520 requires_grad: self.requires_grad,
521 grad: Arc::new(RwLock::new(None)),
522 operation: Operation::Leaf,
523 strides: Some(new_strides),
524 storage_offset: self.storage_offset,
525 base_tensor: if self.is_view() {
526 self.base_tensor.clone()
527 } else {
528 Some(Arc::downgrade(&Arc::new(self.clone())))
529 },
530 })
531 }
532
533 pub fn squeeze(&self, dim: i32) -> Result<Self> {
574 let ndim = self.ndim();
575 let dim = if dim < 0 {
576 (ndim as i32 + dim) as usize
577 } else {
578 dim as usize
579 };
580
581 self.squeeze_tensor(dim)
582 }
583
584 pub fn squeeze_all(&self) -> Result<Self> {
586 let shape = self.shape.dims();
587 let new_shape: Vec<usize> = shape.iter().copied().filter(|&s| s != 1).collect();
588
589 if new_shape.is_empty() {
590 let data = self.to_vec()?;
592 Self::from_data(data, vec![], self.device)
593 } else {
594 let data = self.to_vec()?;
595 Self::from_data(data, new_shape, self.device)
596 }
597 }
598
599 pub fn unsqueeze(&self, dim: i32) -> Result<Self> {
639 let ndim = self.ndim();
640 let dim = if dim < 0 {
641 (ndim as i32 + dim + 1) as usize
642 } else {
643 dim as usize
644 };
645
646 self.unsqueeze_tensor(dim)
647 }
648
649 pub fn reshape(&self, shape: &[i32]) -> Result<Self> {
681 self.view(shape)
682 }
683
684 pub fn is_contiguous(&self) -> bool {
686 let default_strides = self.compute_default_strides();
688 let current_strides = self.strides();
689
690 current_strides == default_strides
691 }
692
693 pub fn contiguous(&self) -> Result<Self> {
695 if self.is_contiguous() {
696 Ok(self.clone())
697 } else {
698 let data = self.to_vec()?;
700 Self::from_data(data, self.shape.dims().to_vec(), self.device)
701 }
702 }
703
704 pub fn expand(&self, shape: &[usize]) -> Result<Self> {
706 let old_shape = self.shape.dims();
707
708 if shape.len() < old_shape.len() {
710 return Err(TorshError::InvalidShape(
711 "Cannot expand to smaller number of dimensions".to_string(),
712 ));
713 }
714
715 let offset = shape.len() - old_shape.len();
725 let current_strides = self.strides();
726 let mut new_strides = vec![0usize; shape.len()];
727 for (i, &old_dim) in old_shape.iter().enumerate() {
728 let new_dim = shape[offset + i];
729 if old_dim == new_dim {
730 new_strides[offset + i] = current_strides[i];
731 } else if old_dim == 1 {
732 new_strides[offset + i] = 0;
733 } else {
734 return Err(TorshError::InvalidShape(format!(
735 "Cannot expand dimension {} from {} to {}",
736 i, old_dim, new_dim
737 )));
738 }
739 }
740 Ok(Self {
743 storage: self.storage.clone(),
744 shape: Shape::new(shape.to_vec()),
745 device: self.device,
746 requires_grad: false,
747 grad: Arc::new(RwLock::new(None)),
748 operation: Operation::Leaf,
749 strides: Some(new_strides),
750 storage_offset: self.storage_offset,
751 base_tensor: if self.is_view() {
752 self.base_tensor.clone()
753 } else {
754 Some(Arc::downgrade(&Arc::new(self.clone())))
755 },
756 })
757 }
758
759 pub fn movedim(&self, source: &[isize], destination: &[isize]) -> Result<Self> {
774 if source.len() != destination.len() {
775 return Err(TorshError::InvalidArgument(
776 "source and destination must have the same length".to_string(),
777 ));
778 }
779
780 let ndim = self.ndim();
781
782 let norm_source: Result<Vec<usize>> = source
784 .iter()
785 .map(|&d| {
786 let dim = if d < 0 {
787 (ndim as isize + d) as usize
788 } else {
789 d as usize
790 };
791 if dim >= ndim {
792 Err(TorshError::InvalidArgument(format!(
793 "Dimension {} out of range for {}-D tensor",
794 d, ndim
795 )))
796 } else {
797 Ok(dim)
798 }
799 })
800 .collect();
801 let norm_source = norm_source?;
802
803 let norm_dest: Result<Vec<usize>> = destination
804 .iter()
805 .map(|&d| {
806 let dim = if d < 0 {
807 (ndim as isize + d) as usize
808 } else {
809 d as usize
810 };
811 if dim >= ndim {
812 Err(TorshError::InvalidArgument(format!(
813 "Dimension {} out of range for {}-D tensor",
814 d, ndim
815 )))
816 } else {
817 Ok(dim)
818 }
819 })
820 .collect();
821 let norm_dest = norm_dest?;
822
823 for i in 0..norm_source.len() {
825 for j in i + 1..norm_source.len() {
826 if norm_source[i] == norm_source[j] {
827 return Err(TorshError::InvalidArgument(
828 "repeated dim in source".to_string(),
829 ));
830 }
831 }
832 }
833
834 for i in 0..norm_dest.len() {
836 for j in i + 1..norm_dest.len() {
837 if norm_dest[i] == norm_dest[j] {
838 return Err(TorshError::InvalidArgument(
839 "repeated dim in destination".to_string(),
840 ));
841 }
842 }
843 }
844
845 let mut result_perm = vec![0; ndim];
847 let mut used = vec![false; ndim];
848
849 for (&src, &dst) in norm_source.iter().zip(norm_dest.iter()) {
851 result_perm[dst] = src;
852 used[dst] = true;
853 }
854
855 let remaining_dims: Vec<usize> = (0..ndim).filter(|d| !norm_source.contains(d)).collect();
857
858 let mut remaining_idx = 0;
859 for i in 0..ndim {
860 if !used[i] {
861 result_perm[i] = remaining_dims[remaining_idx];
862 remaining_idx += 1;
863 }
864 }
865
866 let perm_i32: Vec<i32> = result_perm.iter().map(|&d| d as i32).collect();
868 self.permute(&perm_i32)
869 }
870
871 pub fn moveaxis(&self, source: &[isize], destination: &[isize]) -> Result<Self> {
880 self.movedim(source, destination)
881 }
882
883 pub fn swapaxes(&self, axis0: isize, axis1: isize) -> Result<Self> {
898 let ndim = self.ndim();
899
900 let dim0 = if axis0 < 0 {
902 (ndim as isize + axis0) as usize
903 } else {
904 axis0 as usize
905 };
906 let dim1 = if axis1 < 0 {
907 (ndim as isize + axis1) as usize
908 } else {
909 axis1 as usize
910 };
911
912 if dim0 >= ndim {
913 return Err(TorshError::InvalidArgument(format!(
914 "Dimension {} out of range for {}-D tensor",
915 axis0, ndim
916 )));
917 }
918 if dim1 >= ndim {
919 return Err(TorshError::InvalidArgument(format!(
920 "Dimension {} out of range for {}-D tensor",
921 axis1, ndim
922 )));
923 }
924
925 let mut perm: Vec<i32> = (0..ndim as i32).collect();
927 perm.swap(dim0, dim1);
928
929 self.permute(&perm)
930 }
931
932 pub fn swapdims(&self, dim0: isize, dim1: isize) -> Result<Self> {
937 self.swapaxes(dim0, dim1)
938 }
939
940 pub fn broadcast_to(&self, shape: &[usize]) -> Result<Self> {
954 self.expand(shape)
956 }
957
958 pub fn expand_as(&self, other: &Self) -> Result<Self> {
973 self.broadcast_to(other.shape().dims())
974 }
975}
976
977#[cfg(test)]
978mod tests {
979 use super::*;
980 use torsh_core::device::DeviceType;
981
982 #[test]
983 fn test_tensor_view() {
984 let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
985 let tensor = Tensor::from_data(data, vec![2, 3], DeviceType::Cpu)
986 .expect("tensor creation should succeed");
987
988 let reshaped = tensor.view(&[3, 2]).expect("view should succeed");
989 assert_eq!(reshaped.shape().dims(), &[3, 2]);
990 assert_eq!(reshaped.numel(), 6);
991 }
992
993 #[test]
994 fn test_tensor_view_with_inference() {
995 let data = vec![1.0f32; 24];
996 let tensor = Tensor::from_data(data, vec![2, 3, 4], DeviceType::Cpu)
997 .expect("tensor creation should succeed");
998
999 let reshaped = tensor.view(&[6, -1]).expect("view should succeed");
1000 assert_eq!(reshaped.shape().dims(), &[6, 4]);
1001 }
1002
1003 #[test]
1004 fn test_tensor_slice() {
1005 let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
1006 let tensor = Tensor::from_data(data, vec![2, 3], DeviceType::Cpu)
1007 .expect("tensor creation should succeed");
1008
1009 let slice = tensor.slice_tensor(1, 1, 3).expect("slice should succeed");
1010 assert_eq!(slice.shape().dims(), &[2, 2]);
1011 }
1012
1013 #[test]
1014 fn test_tensor_transpose() {
1015 let data = vec![1.0f32, 2.0, 3.0, 4.0];
1016 let tensor = Tensor::from_data(data, vec![2, 2], DeviceType::Cpu)
1017 .expect("tensor creation should succeed");
1018
1019 let transposed = tensor.transpose(0, 1).expect("transpose should succeed");
1020 assert_eq!(transposed.shape().dims(), &[2, 2]);
1021 assert_eq!(
1022 transposed.get(&[0, 1]).expect("data access should succeed"),
1023 3.0
1024 );
1025 assert_eq!(
1026 transposed.get(&[1, 0]).expect("data access should succeed"),
1027 2.0
1028 );
1029 }
1030
1031 #[test]
1032 fn test_tensor_squeeze_unsqueeze() {
1033 let data = vec![1.0f32, 2.0, 3.0];
1034 let tensor = Tensor::from_data(data, vec![1, 3], DeviceType::Cpu)
1035 .expect("tensor creation should succeed");
1036
1037 let squeezed = tensor.squeeze(0).expect("squeeze should succeed");
1038 assert_eq!(squeezed.shape().dims(), &[3]);
1039
1040 let unsqueezed = squeezed.unsqueeze(0).expect("unsqueeze should succeed");
1041 assert_eq!(unsqueezed.shape().dims(), &[1, 3]);
1042 }
1043
1044 #[test]
1045 fn test_tensor_permute() {
1046 let data = vec![1.0f32; 24];
1047 let tensor = Tensor::from_data(data, vec![2, 3, 4], DeviceType::Cpu)
1048 .expect("tensor creation should succeed");
1049
1050 let permuted = tensor.permute(&[2, 0, 1]).expect("permute should succeed");
1051 assert_eq!(permuted.shape().dims(), &[4, 2, 3]);
1052 }
1053
1054 #[test]
1055 fn test_is_contiguous() {
1056 let data = vec![1.0f32, 2.0, 3.0, 4.0];
1057 let tensor = Tensor::from_data(data, vec![2, 2], DeviceType::Cpu)
1058 .expect("tensor creation should succeed");
1059 assert!(tensor.is_contiguous());
1060
1061 let transposed = tensor
1062 .transpose_view(0, 1)
1063 .expect("transpose view should succeed");
1064 assert!(!transposed.is_contiguous());
1065
1066 let contiguous = transposed.contiguous().expect("contiguous should succeed");
1067 assert!(contiguous.is_contiguous());
1068 }
1069
1070 #[test]
1071 fn test_expand() {
1072 let data = vec![1.0f32, 2.0];
1073 let tensor = Tensor::from_data(data, vec![1, 2], DeviceType::Cpu)
1074 .expect("tensor creation should succeed");
1075
1076 let expanded = tensor.expand(&[3, 2]).expect("expand should succeed");
1077 assert_eq!(expanded.shape().dims(), &[3, 2]);
1078 assert_eq!(expanded.numel(), 6);
1079
1080 assert_eq!(
1082 expanded.to_vec().expect("to_vec should succeed"),
1083 vec![1.0, 2.0, 1.0, 2.0, 1.0, 2.0]
1084 );
1085
1086 assert_eq!(expanded.strides(), vec![0, 1]);
1089 assert!(!expanded.is_contiguous());
1090 assert!(expanded.is_view());
1091 }
1092
1093 #[test]
1094 fn test_expand_new_leading_dimension() {
1095 let tensor = Tensor::from_data(vec![7.0f32, 8.0], vec![2], DeviceType::Cpu)
1097 .expect("tensor creation should succeed");
1098
1099 let expanded = tensor.expand(&[3, 2]).expect("expand should succeed");
1100 assert_eq!(expanded.shape().dims(), &[3, 2]);
1101
1102 assert_eq!(expanded.strides(), vec![0, 1]);
1104 assert_eq!(
1105 expanded.to_vec().expect("to_vec should succeed"),
1106 vec![7.0, 8.0, 7.0, 8.0, 7.0, 8.0]
1107 );
1108
1109 assert_eq!(expanded.get(&[0, 1]).expect("get should succeed"), 8.0);
1111 assert_eq!(expanded.get(&[2, 0]).expect("get should succeed"), 7.0);
1112 }
1113
1114 #[test]
1115 fn test_expand_zero_copy_no_data_duplication() {
1116 let source = Tensor::from_data(vec![5.0f32], vec![1], DeviceType::Cpu)
1119 .expect("tensor creation should succeed");
1120 let source_memory = source.memory_usage();
1121
1122 let expanded = source.expand(&[1024, 1024]).expect("expand should succeed");
1123 assert_eq!(expanded.numel(), 1024 * 1024);
1124
1125 assert_eq!(expanded.memory_usage(), source_memory);
1127 assert!(expanded.memory_usage() < expanded.numel() * std::mem::size_of::<f32>());
1128
1129 assert_eq!(expanded.strides(), vec![0, 0]);
1131 assert!(!expanded.is_contiguous());
1132 assert!(expanded.is_view());
1133
1134 assert_eq!(expanded.get(&[0, 0]).expect("get should succeed"), 5.0);
1136 assert_eq!(expanded.get(&[500, 700]).expect("get should succeed"), 5.0);
1137 assert_eq!(
1138 expanded.get(&[1023, 1023]).expect("get should succeed"),
1139 5.0
1140 );
1141 }
1142
1143 #[test]
1144 fn test_expand_rejects_incompatible_dimension() {
1145 let tensor = Tensor::from_data(vec![1.0f32, 2.0, 3.0], vec![3], DeviceType::Cpu)
1147 .expect("tensor creation should succeed");
1148 assert!(tensor.expand(&[5]).is_err());
1149 let matrix = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![2, 2], DeviceType::Cpu)
1151 .expect("tensor creation should succeed");
1152 assert!(matrix.expand(&[4]).is_err());
1153 }
1154
1155 #[test]
1156 fn test_view_error_handling() {
1157 let data = vec![1.0f32, 2.0, 3.0];
1158 let tensor = Tensor::from_data(data, vec![3], DeviceType::Cpu)
1159 .expect("tensor creation should succeed");
1160
1161 assert!(tensor.view(&[2, 2]).is_err());
1163
1164 assert!(tensor.view(&[-1, -1]).is_err());
1166 }
1167
1168 #[test]
1169 fn test_movedim_single() {
1170 let tensor = Tensor::from_data(vec![1.0f32; 24], vec![2, 3, 4], DeviceType::Cpu)
1171 .expect("tensor creation should succeed");
1172
1173 let result = tensor.movedim(&[0], &[2]).expect("movedim should succeed");
1175 assert_eq!(result.shape().dims(), &[3, 4, 2]);
1176 }
1177
1178 #[test]
1179 fn test_movedim_multiple() {
1180 let tensor = Tensor::from_data(vec![1.0f32; 24], vec![2, 3, 4], DeviceType::Cpu)
1181 .expect("tensor creation should succeed");
1182
1183 let result = tensor
1185 .movedim(&[0, 1], &[2, 0])
1186 .expect("movedim should succeed");
1187 assert_eq!(result.shape().dims(), &[3, 4, 2]);
1188 }
1189
1190 #[test]
1191 fn test_movedim_negative_indices() {
1192 let tensor = Tensor::from_data(vec![1.0f32; 24], vec![2, 3, 4], DeviceType::Cpu)
1193 .expect("tensor creation should succeed");
1194
1195 let result = tensor.movedim(&[-1], &[0]).expect("movedim should succeed");
1197 assert_eq!(result.shape().dims(), &[4, 2, 3]);
1198 }
1199
1200 #[test]
1201 fn test_moveaxis_alias() {
1202 let tensor = Tensor::from_data(vec![1.0f32; 24], vec![2, 3, 4], DeviceType::Cpu)
1203 .expect("tensor creation should succeed");
1204
1205 let result1 = tensor.movedim(&[0], &[2]).expect("movedim should succeed");
1206 let result2 = tensor
1207 .moveaxis(&[0], &[2])
1208 .expect("moveaxis should succeed");
1209 assert_eq!(result1.shape().dims(), result2.shape().dims());
1210 }
1211
1212 #[test]
1213 fn test_swapaxes_simple() {
1214 let tensor = Tensor::from_data(
1215 vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0],
1216 vec![2, 3],
1217 DeviceType::Cpu,
1218 )
1219 .expect("tensor creation should succeed");
1220
1221 let result = tensor.swapaxes(0, 1).expect("swapaxes should succeed");
1223 assert_eq!(result.shape().dims(), &[3, 2]);
1224 }
1225
1226 #[test]
1227 fn test_swapaxes_3d() {
1228 let tensor = Tensor::from_data(vec![1.0f32; 24], vec![2, 3, 4], DeviceType::Cpu)
1229 .expect("tensor creation should succeed");
1230
1231 let result = tensor.swapaxes(0, 2).expect("swapaxes should succeed");
1233 assert_eq!(result.shape().dims(), &[4, 3, 2]);
1234 }
1235
1236 #[test]
1237 fn test_swapaxes_negative_indices() {
1238 let tensor = Tensor::from_data(vec![1.0f32; 24], vec![2, 3, 4], DeviceType::Cpu)
1239 .expect("tensor creation should succeed");
1240
1241 let result = tensor.swapaxes(-1, -2).expect("swapaxes should succeed");
1243 assert_eq!(result.shape().dims(), &[2, 4, 3]);
1244 }
1245
1246 #[test]
1247 fn test_swapdims_alias() {
1248 let tensor = Tensor::from_data(vec![1.0f32; 24], vec![2, 3, 4], DeviceType::Cpu)
1249 .expect("tensor creation should succeed");
1250
1251 let result1 = tensor.swapaxes(0, 2).expect("swapaxes should succeed");
1252 let result2 = tensor.swapdims(0, 2).expect("swapdims should succeed");
1253 assert_eq!(result1.shape().dims(), result2.shape().dims());
1254 }
1255
1256 #[test]
1257 fn test_broadcast_to_same_shape() {
1258 let tensor = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![2, 2], DeviceType::Cpu)
1259 .expect("tensor creation should succeed");
1260
1261 let result = tensor
1262 .broadcast_to(&[2, 2])
1263 .expect("broadcast_to should succeed");
1264 assert_eq!(result.shape().dims(), &[2, 2]);
1265 }
1266
1267 #[test]
1268 fn test_broadcast_to_expand_dim() {
1269 let tensor = Tensor::from_data(vec![1.0f32, 2.0], vec![1, 2], DeviceType::Cpu)
1270 .expect("tensor creation should succeed");
1271
1272 let result = tensor
1274 .broadcast_to(&[3, 2])
1275 .expect("broadcast_to should succeed");
1276 assert_eq!(result.shape().dims(), &[3, 2]);
1277 }
1278
1279 #[test]
1280 fn test_expand_as_basic() {
1281 let tensor = Tensor::from_data(vec![1.0f32, 2.0], vec![1, 2], DeviceType::Cpu)
1282 .expect("tensor creation should succeed");
1283
1284 let target = Tensor::from_data(vec![0.0f32; 6], vec![3, 2], DeviceType::Cpu)
1285 .expect("tensor creation should succeed");
1286
1287 let result = tensor.expand_as(&target).expect("expand_as should succeed");
1288 assert_eq!(result.shape().dims(), target.shape().dims());
1289 assert_eq!(result.shape().dims(), &[3, 2]);
1290 }
1291}