Skip to main content

torsh_tensor/
advanced_ops.rs

1//! Advanced tensor operations including reductions, linear algebra, and backend integration
2//!
3//! This module provides high-level tensor operations including reductions, linear algebra
4//! operations, SciRS2 backend integration, and advanced data manipulation functions.
5//!
6//! # Features
7//!
8//! - **Reductions**: max, norm, sum, mean operations
9//! - **Linear algebra**: Matrix multiplication and vector operations
10//! - **SciRS2 integration**: Optimized backend operations for performance
11//! - **Activation functions**: ReLU, sigmoid, tanh through SciRS2 backend
12//! - **Functional programming**: Apply operations and data transformations
13//! - **Memory management**: Copy-on-write semantics and unique data operations
14
15use 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
24// Float-specific operations
25impl<T: FloatElement + Copy> Tensor<T> {
26    /// Create a 0-dimensional tensor (scalar) from a single value
27    pub fn scalar(value: T) -> Result<Self> {
28        Self::from_data(vec![value], vec![], DeviceType::Cpu)
29    }
30
31    /// Convert tensor to ndarray
32    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    /// Create tensor from ndarray
42    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    /// Maximum element in tensor
52    pub fn max(&self, dim: Option<usize>, keepdim: bool) -> Result<Self> {
53        match dim {
54            None => {
55                // Global maximum
56                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                // Maximum along specific dimension
75                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                // Calculate output shape
87                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    /// Maximum along specified dimension
123    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        // Calculate output shape
142        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    /// Minimum along specified dimension
176    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
223/// Boolean reduction operations for tensors
224impl<T: TensorElement + Copy> Tensor<T>
225where
226    T: PartialEq + num_traits::Zero,
227{
228    /// Check if all elements are non-zero (true)
229    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    /// Check if any element is non-zero (true)
237    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    /// Check if all elements along dimension are non-zero (true)
245    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    /// Check if any element along dimension is non-zero (true)
294    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
343// General tensor operations
344impl<T: TensorElement + Copy> Tensor<T> {
345    /// Compute sum of all elements
346    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        // Record the sum operation for autograd: d(sum)/dx_i = 1 for every element.
357        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    /// Compute sum along specified dimensions
368    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        // Handle single dimension case (most common)
380        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            // Calculate output shape
397            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            // For multiple dimensions, fall back to full sum for now
427            self.sum()
428        }
429    }
430
431    /// Compute mean along specified dimensions
432    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                // Reshape scalar to tensor with same ndim as original, all dims = 1
446                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        // Propagate requires_grad and record operation for autograd
467        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    /// Compute cumulative product along specified dimension
479    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    /// Matrix multiplication
521    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        // Record the matmul operation for autograd (2-D). Backward computes
527        // dL/dlhs = grad @ rhsᵀ and dL/drhs = lhsᵀ @ grad.
528        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    /// Sort tensor along specified dimension
539    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        // Simple implementation - sort entire tensor as 1D
544        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        // Sort by value
549        indexed_data.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
550
551        // Extract sorted data and indices
552        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    /// Min reduction method without trait bounds (for Iterator compatibility)
569    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    /// Transpose operation (2D tensor)
587    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    /// Check if two tensors share the same underlying storage
614    pub fn shares_storage(&self, other: &Self) -> bool {
615        // For storage abstraction, we need to check the underlying storage
616        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    /// Get data as a vector (backward compatibility method)
624    pub fn data(&self) -> Result<Vec<T>>
625    where
626        T: Copy,
627    {
628        self.to_vec()
629    }
630
631    /// Apply a function to all elements in-place using direct storage access
632    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                // For memory-mapped storage, we need to read-modify-write
649                let data = self.to_vec()?;
650                let mut new_data = data;
651                for item in new_data.iter_mut() {
652                    func(item);
653                }
654                // Write back the data
655                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                // SimdOptimized should have been converted by ensure_exclusive_data()
669                // If we reach here, something went wrong - convert to optimal storage and retry
670                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    /// Clone the tensor with independent data (deep copy)
682    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    /// Ensure tensor has unique data (copy-on-write semantics)
694    pub fn make_unique(&mut self) -> Result<()> {
695        // For storage-based approach, create new storage if shared
696        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                // SimdOptimized storage is immutable by design (optimized for read-heavy workloads)
719                // Always convert to Aligned storage which supports both SIMD and mutation
720                let data_vec = self.to_vec()?;
721                self.storage = TensorStorage::aligned(data_vec)?;
722            }
723        }
724        Ok(())
725    }
726
727    /// Apply function in-place
728    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        // Update storage with new data
737        self.storage = TensorStorage::create_optimal(new_data)?;
738        Ok(())
739    }
740
741    /// Apply function element-wise to create new tensor
742    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        // Preserve gradient tracking flag from original tensor
752        result.requires_grad = self.requires_grad;
753
754        Ok(result)
755    }
756
757    /// Extract a scalar value from a single-element tensor
758    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    /// Concatenate tensors along a dimension
773    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        // Normalize dim (allow negative indexing)
788        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        // Validate all tensors have compatible shapes (same on all dims except actual_dim)
802        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        // Compute output shape: same as input except actual_dim is sum of all cat dims
824        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        // Gather all data in order, interleaving elements for proper layout
829        // Outer = product of dims before actual_dim
830        // Cat stride = product of dims after actual_dim (inner)
831        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    /// Ensure exclusive ownership of data using copy-on-write semantics
857    /// If the data is shared (Arc has multiple strong references), clone it
858    fn ensure_exclusive_data(&mut self) -> Result<()> {
859        match &self.storage {
860            TensorStorage::InMemory(data) => {
861                if Arc::strong_count(data) > 1 {
862                    // Data is shared, need to clone it to get exclusive access
863                    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                    // Clone memory-mapped storage by converting to vec and back
873                    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                    // Data is shared, need to clone it to get exclusive access
881                    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                    // SimdOptimized uses COW - copy the data to get exclusive access
892                    let vec_data = storage.to_vec();
893                    self.storage = TensorStorage::simd_optimized(vec_data)?;
894                }
895            }
896        }
897        Ok(())
898    }
899}
900
901// Numeric operations
902impl<T: TensorElement + Copy> Tensor<T>
903where
904    T: num_traits::Float,
905{
906    /// Compute the L2 norm of the tensor
907    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        // Return scalar tensor (1-element tensor with shape [])
916        Tensor::from_data(vec![norm_value], vec![], self.device())
917    }
918}
919
920// SciRS2 backend integration (placeholder implementations)
921impl<T: TensorElement + Copy> Tensor<T> {
922    /// Use SciRS2 backend for optimized matrix multiplication
923    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        // TODO: Integrate with actual SciRS2 backend
928        // For now, implement basic matrix multiplication
929        self.basic_matmul(other)
930    }
931
932    /// Use SciRS2 backend for optimized sum reduction
933    pub fn sum_scirs2(&self) -> Result<Self>
934    where
935        T: std::ops::Add<Output = T> + num_traits::Zero,
936    {
937        // TODO: Integrate with actual SciRS2 backend
938        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    /// Use SciRS2 backend for optimized mean reduction
946    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        // TODO: Integrate with actual SciRS2 backend
955        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    /// Use SciRS2 backend for optimized ReLU activation
969    pub fn relu_scirs2(&self) -> Result<Self>
970    where
971        T: PartialOrd + num_traits::Zero,
972    {
973        // TODO: Integrate with actual SciRS2 backend
974        let zero = <T as num_traits::Zero>::zero();
975        self.map(|x| if x > zero { x } else { zero })
976    }
977
978    /// Use SciRS2 backend for optimized sigmoid activation
979    pub fn sigmoid_scirs2(&self) -> Result<Self>
980    where
981        T: num_traits::Float,
982    {
983        // TODO: Integrate with actual SciRS2 backend
984        self.map(|x| {
985            let one = <T as num_traits::One>::one();
986            one / (one + (-x).exp())
987        })
988    }
989
990    /// Use SciRS2 backend for optimized tanh activation
991    pub fn tanh_scirs2(&self) -> Result<Self>
992    where
993        T: num_traits::Float,
994    {
995        // TODO: Integrate with actual SciRS2 backend
996        self.map(|x| x.tanh())
997    }
998
999    /// Basic matrix multiplication implementation
1000    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        // Check dimensions for matrix multiplication
1010        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        // Basic matrix multiplication
1031        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    /// Softmax activation along specified dimension
1044    /// Computes softmax(x_i) = exp(x_i) / sum(exp(x_j)) for all j
1045    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        // Validate tensor has data
1057        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        // Handle negative dimension
1064        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        // For numerical stability, subtract max before exp
1079        let max_tensor = self.max(Some(actual_dim), true)?;
1080
1081        // Expand max_tensor to match input shape for broadcasting
1082        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        // Expand sum_tensor to match exp_tensor shape for broadcasting
1088        let expanded_sum = sum_tensor.expand(shape)?;
1089        exp_tensor.div(&expanded_sum)
1090    }
1091
1092    /// Log softmax activation along specified dimension
1093    /// Computes log_softmax(x_i) = log(softmax(x_i))
1094    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    /// Computes cumulative sum along a dimension
1103    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        // Handle negative dimension
1111        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        // Simplified cumsum implementation for now
1129        // This is a basic implementation that works along the flattened array
1130        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    /// Find the indices of minimum values along a dimension
1142    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                // Handle negative dimension
1159                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                // For simplicity, return the first minimum index found
1174                // This is a basic implementation - real argmin would handle the specified dimension properly
1175                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                // Find argmin over the entire flattened tensor
1185                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    /// Find the indices of maximum values along a dimension
1197    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                // Handle negative dimension
1214                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                // For simplicity, return the first maximum index found
1229                // This is a basic implementation - real argmax would handle the specified dimension properly
1230                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                // Find argmax over the entire flattened tensor
1240                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    /// Returns the k largest elements along a dimension
1252    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        // Determine actual dimension to operate on (default: last dim)
1279        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        // Output shape: same as input but actual_dim replaced with k
1305        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                // Gather (local_index, value) pairs along actual_dim for this (outer, inner) slice
1314                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                // Sort by value to find top-k candidates
1322                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                // When sorted=false, restore original (position) order
1333                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        // Re-arrange from (outer, k, inner) to match result_shape layout
1345        // Currently we have data as outer * inner * k interleaved; need outer * k * inner
1346        // Transpose inner and k dimensions
1347        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]; // 3-4-5 triangle
1393        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        // Test apply_
1406        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        // Test map
1415        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        ); // Original unchanged
1426    }
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        // Test ReLU
1435        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        // Test abs
1442        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        // Test clamp
1449        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        // Expected: [1*5+2*7, 1*6+2*8] = [19, 22]
1478        //           [3*5+4*7, 3*6+4*8] = [43, 50]
1479        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        // Both should share storage initially
1506        assert!(tensor1.shares_storage(&tensor2));
1507
1508        // After make_unique, they should not share storage
1509        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()); // Should fail for multi-element tensor
1522    }
1523
1524    #[test]
1525    fn test_all_dim() {
1526        // Shape [2, 3]: [[1, 0, 1], [1, 1, 1]]
1527        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        // all along dim 0 (rows): per column check
1532        // col0: 1&&1=true, col1: 0&&1=false, col2: 1&&1=true
1533        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        // all along dim 1 (cols): per row check
1541        // row0: 1&&0&&1=false, row1: 1&&1&&1=true
1542        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        // keepdim=true preserves dimension
1550        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        // Shape [2, 3]: [[0, 0, 0], [0, 1, 0]]
1557        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        // any along dim 0: col0: false, col1: true, col2: false
1562        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        // any along dim 1: row0: false, row1: true
1570        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        // Test concatenation along dim 0
1581        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        // Test concatenation along dim 1
1594        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        // 2x4 tensor, topk along dim 1
1610        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        // Row 0: [3, 1, 4, 2] -> top2 = [4, 3] at positions [2, 0]
1621        // Row 1: [5, 9, 2, 6] -> top2 = [9, 6] at positions [1, 3]
1622        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    // --- Regression tests for issue #43: mean must propagate requires_grad ---
1635
1636    #[test]
1637    fn test_issue_43_mean_propagates_requires_grad() {
1638        // A tensor with requires_grad=true; mean result must also require grad.
1639        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        // When input does not require grad, result should not either.
1653        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        // For mean of n elements, backward with upstream grad=1 distributes 1/n to each element.
1666        // mean() already reduces to a scalar, so backward() can be called directly.
1667        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        // mean(None) with keepdim=false produces a scalar (numel=1), so backward is valid
1675        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        // Each element should receive 1.0 / n = 0.25
1683        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        // loss = sum(x); d(loss)/dx_i = 1 for every element.
1695        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        // C = A @ B, loss = sum(C). grad_C = ones, so:
1713        //   grad_A = ones @ Bᵀ = [[11,15],[11,15]]
1714        //   grad_B = Aᵀ @ ones = [[4,4],[6,6]]
1715        // for A = [[1,2],[3,4]], B = [[5,6],[7,8]].
1716        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}