Skip to main content

torsh_tensor/data_ops/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::core_ops::Tensor;
6use torsh_core::{
7    device::DeviceType,
8    dtype::TensorElement,
9    error::{Result, TorshError},
10};
11
12impl<T: TensorElement + Copy> Tensor<T> {
13    /// Create tensor from a scalar value repeated to fill the shape
14    pub fn from_scalar(value: T, shape: &[usize], device: DeviceType) -> Result<Self>
15    where
16        T: Copy,
17    {
18        let numel = shape.iter().product::<usize>();
19        let data = vec![value; numel];
20        Self::from_data(data, shape.to_vec(), device)
21    }
22    /// Fill tensor with a single value (in-place)
23    ///
24    /// A device-resident tensor is demoted to host storage first: device buffers
25    /// are immutable, so an in-place write has to happen on the host.
26    ///
27    /// # Aliasing
28    /// Follows the crate-wide in-place write contract (see
29    /// [`Tensor::set_item_flat`]): a base tensor is isolated copy-on-write, so a
30    /// `.clone()` snapshot keeps its old values, while a view writes *through*
31    /// to the tensor it aliases. The write is stride- and offset-aware, so
32    /// filling a row view touches that row and nothing else.
33    pub fn fill_(&mut self, value: T) -> Result<()>
34    where
35        T: Copy,
36    {
37        #[cfg(feature = "gpu")]
38        if self.storage.is_device() {
39            self.make_unique()?;
40        }
41        if !self.is_view() {
42            self.make_unique()?;
43        }
44
45        let numel = self.numel();
46        if self.has_default_layout() {
47            // Contiguous (possibly offset) block: one straight run of stores.
48            // The offset is what a `slice_tensor` row view carries — writing
49            // `storage[0..numel]` here would clobber the base's *prefix*.
50            let offset = self.storage_offset;
51            for i in 0..numel {
52                self.storage.set(offset + i, value)?;
53            }
54            return Ok(());
55        }
56        // Strided view (a transpose, a stepped slice): every logical position
57        // has to be mapped through the strides individually.
58        for i in 0..numel {
59            self.set_item_flat(i, value)?;
60        }
61        Ok(())
62    }
63    /// Zero out the tensor (in-place)
64    pub fn zero_(&mut self) -> Result<()>
65    where
66        T: Copy,
67    {
68        self.fill_(T::zero())
69    }
70    /// Fill with ones (in-place)
71    pub fn ones_(&mut self) -> Result<()>
72    where
73        T: Copy,
74    {
75        self.fill_(T::one())
76    }
77    /// Copy data from another tensor (in-place), copy-on-write safe.
78    ///
79    /// Delegates to [`Tensor::copy_from`], so a snapshot taken with `.clone()`
80    /// (which shares storage) is never clobbered by the write.
81    pub fn copy_(&mut self, other: &Self) -> Result<()>
82    where
83        T: Copy,
84    {
85        self.copy_from(other)
86    }
87
88    /// Overwrite this tensor's contents with `other`'s, copy-on-write safe.
89    ///
90    /// The two tensors must have the same shape. Before writing, this makes the
91    /// storage uniquely owned (and contiguous), so any tensor that shares this
92    /// one's buffer — a `.clone()` snapshot, or a base tensor this is a view of —
93    /// keeps its old values. This is the primitive optimizers use to write an
94    /// updated parameter back without disturbing retained snapshots.
95    pub fn copy_from(&mut self, other: &Self) -> Result<()>
96    where
97        T: Copy,
98    {
99        if self.shape() != other.shape() {
100            return Err(TorshError::ShapeMismatch {
101                expected: self.shape().dims().to_vec(),
102                got: other.shape().dims().to_vec(),
103            });
104        }
105        // Read the source *before* isolating our storage: the source may alias
106        // it (e.g. a clone), and `make_unique` would otherwise leave `other`
107        // pointing at the pre-copy buffer.
108        let other_data = other.to_vec()?;
109        // A strided/offset view aliases a sub-region of another tensor's buffer,
110        // and PyTorch's in-place-on-a-view semantics write *through* to that base
111        // (that is exactly how scatter-style aggregation into slices works).
112        // `set_slice` already performs a stride-aware store, mapping each logical
113        // element back to its physical slot in the base storage. Skipping the CoW
114        // step here is deliberate: `make_unique` would detach the view and the
115        // write would never reach the base. The contiguous-base path below keeps
116        // its copy-on-write behaviour so optimizer snapshots stay intact.
117        if self.is_view() {
118            return self.set_slice(0, &other_data);
119        }
120        self.make_unique()?;
121        self.set_slice(0, &other_data)
122    }
123
124    /// Overwrite this tensor's contents from a flat, row-major slice,
125    /// copy-on-write safe.
126    ///
127    /// `data.len()` must equal this tensor's element count. Like
128    /// [`Tensor::copy_from`], the storage is made uniquely owned first, so
129    /// shared snapshots are preserved.
130    pub fn set_data(&mut self, data: &[T]) -> Result<()>
131    where
132        T: Copy,
133    {
134        let numel = self.numel();
135        if data.len() != numel {
136            return Err(TorshError::InvalidArgument(format!(
137                "set_data: slice has {} elements but the tensor holds {}",
138                data.len(),
139                numel
140            )));
141        }
142        // See `copy_from`: an in-place write into a view writes through to the
143        // base via the stride-aware `set_slice`; the contiguous path stays
144        // copy-on-write so shared snapshots are preserved.
145        if self.is_view() {
146            return self.set_slice(0, data);
147        }
148        self.make_unique()?;
149        self.set_slice(0, data)
150    }
151    /// Get an element by multi-dimensional index
152    pub fn get_item(&self, indices: &[usize]) -> Result<T>
153    where
154        T: Copy,
155    {
156        if indices.len() != self.ndim() {
157            return Err(TorshError::InvalidArgument(format!(
158                "Expected {} indices, got {}",
159                self.ndim(),
160                indices.len()
161            )));
162        }
163        let binding = self.shape();
164        let shape = binding.dims();
165        for (i, &idx) in indices.iter().enumerate() {
166            if idx >= shape[i] {
167                return Err(TorshError::IndexOutOfBounds {
168                    index: idx,
169                    size: shape[i],
170                });
171            }
172        }
173        let flat_index = self.multi_to_flat_index(indices)?;
174        self.get_item_flat(flat_index)
175    }
176    /// Set an element by multi-dimensional index
177    ///
178    /// # Aliasing
179    /// See [`Tensor::set_item_flat`]: a base tensor is isolated copy-on-write
180    /// before the write, a view writes through to the tensor it aliases.
181    pub fn set_item(&mut self, indices: &[usize], value: T) -> Result<()>
182    where
183        T: Copy,
184    {
185        if indices.len() != self.ndim() {
186            return Err(TorshError::InvalidArgument(format!(
187                "Expected {} indices, got {}",
188                self.ndim(),
189                indices.len()
190            )));
191        }
192        let binding = self.shape();
193        let shape = binding.dims();
194        for (i, &idx) in indices.iter().enumerate() {
195            if idx >= shape[i] {
196                return Err(TorshError::IndexOutOfBounds {
197                    index: idx,
198                    size: shape[i],
199                });
200            }
201        }
202        let flat_index = self.multi_to_flat_index(indices)?;
203        self.set_item_flat(flat_index, value)
204    }
205    /// Get element by flat index
206    pub fn get_item_flat(&self, index: usize) -> Result<T>
207    where
208        T: Copy,
209    {
210        if index >= self.numel() {
211            return Err(TorshError::IndexOutOfBounds {
212                index,
213                size: self.numel(),
214            });
215        }
216        // Convert flat view-space index → multi-dim → storage index via strides+offset
217        let shape = self.shape();
218        let dims = shape.dims();
219        let ndim = dims.len();
220        let mut multi_dim = vec![0usize; ndim];
221        let mut remaining = index;
222        for i in (0..ndim).rev() {
223            if dims[i] > 0 {
224                multi_dim[i] = remaining % dims[i];
225                remaining /= dims[i];
226            }
227        }
228        let strides = self.strides();
229        let storage_idx = self.storage_offset
230            + multi_dim
231                .iter()
232                .zip(strides.iter())
233                .map(|(&m, &s)| m * s)
234                .sum::<usize>();
235        self.storage.get(storage_idx)
236    }
237    /// Set element by flat index
238    ///
239    /// A device-resident tensor is demoted to host storage first (see
240    /// [`Tensor::fill_`]).
241    ///
242    /// # Aliasing
243    /// This is the reference implementation of the crate's in-place write
244    /// contract, which every `&mut self` writer follows:
245    ///
246    /// * A **base** tensor (default layout, zero storage offset) is made
247    ///   uniquely owned first, so `Tensor::clone()` — which shares storage
248    ///   rather than deep-copying — behaves as a value: writing through one
249    ///   handle never reaches the other. This is exactly what the in-place
250    ///   arithmetic ops do via
251    ///   [`prepare_for_inplace`](Tensor::make_unique), and what optimizer
252    ///   snapshots and checkpointing rely on.
253    /// * A **view** (custom strides or a non-zero storage offset) keeps
254    ///   PyTorch's write-*through* aliasing: the store is mapped back through
255    ///   the view's strides into the base tensor's buffer, which is how
256    ///   scatter-style aggregation into slices works.
257    ///
258    /// A `reshape`/`view` alias of a whole tensor keeps the default layout, so
259    /// it counts as a base tensor here and is isolated rather than written
260    /// through — the same classification [`Tensor::copy_from`] and
261    /// [`Tensor::set_data`] already use.
262    pub fn set_item_flat(&mut self, index: usize, value: T) -> Result<()>
263    where
264        T: Copy,
265    {
266        #[cfg(feature = "gpu")]
267        if self.storage.is_device() {
268            self.make_unique()?;
269        }
270        if !self.is_view() {
271            self.make_unique()?;
272        }
273        if index >= self.numel() {
274            return Err(TorshError::IndexOutOfBounds {
275                index,
276                size: self.numel(),
277            });
278        }
279        // Convert flat view-space index → multi-dim → storage index via strides+offset
280        let shape = self.shape();
281        let dims = shape.dims();
282        let ndim = dims.len();
283        let mut multi_dim = vec![0usize; ndim];
284        let mut remaining = index;
285        for i in (0..ndim).rev() {
286            if dims[i] > 0 {
287                multi_dim[i] = remaining % dims[i];
288                remaining /= dims[i];
289            }
290        }
291        let strides = self.strides();
292        let storage_idx = self.storage_offset
293            + multi_dim
294                .iter()
295                .zip(strides.iter())
296                .map(|(&m, &s)| m * s)
297                .sum::<usize>();
298        self.storage.set(storage_idx, value)
299    }
300    /// Convert multi-dimensional indices to flat index
301    pub fn multi_to_flat_index(&self, indices: &[usize]) -> Result<usize> {
302        let binding = self.shape();
303        let shape = binding.dims();
304        if indices.len() != shape.len() {
305            return Err(TorshError::InvalidArgument(format!(
306                "Expected {} indices, got {}",
307                shape.len(),
308                indices.len()
309            )));
310        }
311        let mut flat_index = 0;
312        let mut stride = 1;
313        for i in (0..indices.len()).rev() {
314            flat_index += indices[i] * stride;
315            stride *= shape[i];
316        }
317        Ok(flat_index)
318    }
319    /// Gather values along an axis using indices
320    ///
321    /// The result joins the autograd graph as an
322    /// [`Operation::Gather`](crate::core_ops::Operation::Gather): every
323    /// output element records the *logical* index of the input element it was
324    /// copied from, so the backward pass scatters the output gradient back and
325    /// **accumulates** wherever an input element was read more than once.
326    ///
327    /// The index map is only built when gradients are actually being recorded —
328    /// inference keeps producing a plain leaf and pays no per-element
329    /// allocation.
330    pub fn gather(&self, dim: usize, indices: &Tensor<i64>) -> Result<Self> {
331        if dim >= self.ndim() {
332            return Err(TorshError::InvalidArgument(format!(
333                "Dimension {} out of range for tensor with {} dimensions",
334                dim,
335                self.ndim()
336            )));
337        }
338        let record = crate::should_record_grad(self.requires_grad);
339        let self_data = self.to_vec()?;
340        let indices_data = indices.to_vec()?;
341        let mut result_data = Vec::new();
342        // `index_map[o]` is the input's logical (row-major) index that fed
343        // output position `o` — the negative-folded one, matching the element
344        // actually read below.
345        let mut index_map = Vec::with_capacity(if record { indices_data.len() } else { 0 });
346        let result_shape = indices.shape().dims().to_vec();
347        if self.ndim() == 1 {
348            for &index in &indices_data {
349                let idx = if index < 0 {
350                    (self.shape().dims()[0] as i64 + index) as usize
351                } else {
352                    index as usize
353                };
354                if idx >= self.shape().dims()[0] {
355                    return Err(TorshError::InvalidArgument(format!(
356                        "Index {} out of range for tensor with size {}",
357                        index,
358                        self.shape().dims()[0]
359                    )));
360                }
361                result_data.push(self_data[idx]);
362                if record {
363                    index_map.push(idx);
364                }
365            }
366        } else {
367            let self_shape_ref = self.shape();
368            let self_shape = self_shape_ref.dims();
369            let indices_shape_ref = indices.shape();
370            let indices_shape = indices_shape_ref.dims();
371            let dim_size = self_shape[dim];
372            let mut self_strides = vec![1; self_shape.len()];
373            let mut indices_strides = vec![1; indices_shape.len()];
374            for i in (0..self_shape.len() - 1).rev() {
375                self_strides[i] = self_strides[i + 1] * self_shape[i + 1];
376            }
377            for i in (0..indices_shape.len() - 1).rev() {
378                indices_strides[i] = indices_strides[i + 1] * indices_shape[i + 1];
379            }
380            let total_elements = indices_data.len();
381            for (i, &index_value) in indices_data.iter().enumerate().take(total_elements) {
382                let mut indices_coords = vec![0; indices_shape.len()];
383                let mut temp_i = i;
384                for j in 0..indices_shape.len() {
385                    indices_coords[j] = temp_i / indices_strides[j];
386                    temp_i %= indices_strides[j];
387                }
388                let idx = if index_value < 0 {
389                    (dim_size as i64 + index_value) as usize
390                } else {
391                    index_value as usize
392                };
393                if idx >= dim_size {
394                    return Err(TorshError::InvalidArgument(format!(
395                        "Index {index_value} out of range for dimension {dim} with size {dim_size}"
396                    )));
397                }
398                let mut self_coords = indices_coords.clone();
399                if dim < self_coords.len() {
400                    self_coords[dim] = idx;
401                }
402                let mut flat_idx = 0;
403                for j in 0..self_coords.len() {
404                    flat_idx += self_coords[j] * self_strides[j];
405                }
406                result_data.push(self_data[flat_idx]);
407                if record {
408                    // `self_strides` are `self`'s default row-major strides, so
409                    // `flat_idx` is already a logical index into `self`.
410                    index_map.push(flat_idx);
411                }
412            }
413        }
414        let mut result = Self::from_data(result_data, result_shape, self.device)?;
415        // Guarded on the same `record` the map was built under: grad mode is a
416        // process-global flag, so without this a thread that turns it on between
417        // the two reads would record a node with an *empty* index map, and the
418        // backward pass would fail its length check instead of running.
419        if record {
420            self.record_gather(&mut result, index_map);
421        }
422        Ok(result)
423    }
424    /// Scatter values along an axis using indices
425    pub fn scatter(&self, dim: usize, indices: &Tensor<i64>, src: &Tensor<T>) -> Result<Self> {
426        if dim >= self.ndim() {
427            return Err(TorshError::InvalidArgument(format!(
428                "Dimension {} out of range for tensor with {} dimensions",
429                dim,
430                self.ndim()
431            )));
432        }
433        let mut result_data = self.to_vec()?;
434        let indices_data = indices.to_vec()?;
435        let src_data = src.to_vec()?;
436        if indices_data.len() != src_data.len() {
437            return Err(TorshError::InvalidArgument(
438                "Indices and source tensor must have the same number of elements".to_string(),
439            ));
440        }
441        if self.ndim() == 1 {
442            for (i, &index) in indices_data.iter().enumerate() {
443                let idx = if index < 0 {
444                    (self.shape().dims()[0] as i64 + index) as usize
445                } else {
446                    index as usize
447                };
448                if idx >= self.shape().dims()[0] {
449                    return Err(TorshError::InvalidArgument(format!(
450                        "Index {} out of range for tensor with size {}",
451                        index,
452                        self.shape().dims()[0]
453                    )));
454                }
455                result_data[idx] = src_data[i];
456            }
457        } else {
458            let self_shape_ref = self.shape();
459            let self_shape = self_shape_ref.dims();
460            let indices_shape_ref = indices.shape();
461            let indices_shape = indices_shape_ref.dims();
462            let dim_size = self_shape[dim];
463            let mut self_strides = vec![1; self_shape.len()];
464            let mut indices_strides = vec![1; indices_shape.len()];
465            for i in (0..self_shape.len() - 1).rev() {
466                self_strides[i] = self_strides[i + 1] * self_shape[i + 1];
467            }
468            for i in (0..indices_shape.len() - 1).rev() {
469                indices_strides[i] = indices_strides[i + 1] * indices_shape[i + 1];
470            }
471            let total_elements = indices_data.len();
472            for (i, &index_value) in indices_data.iter().enumerate().take(total_elements) {
473                let mut indices_coords = vec![0; indices_shape.len()];
474                let mut temp_i = i;
475                for j in 0..indices_shape.len() {
476                    indices_coords[j] = temp_i / indices_strides[j];
477                    temp_i %= indices_strides[j];
478                }
479                let idx = if index_value < 0 {
480                    (dim_size as i64 + index_value) as usize
481                } else {
482                    index_value as usize
483                };
484                if idx >= dim_size {
485                    return Err(TorshError::InvalidArgument(format!(
486                        "Index {index_value} out of range for dimension {dim} with size {dim_size}"
487                    )));
488                }
489                let mut self_coords = indices_coords.clone();
490                if dim < self_coords.len() {
491                    self_coords[dim] = idx;
492                }
493                let mut flat_idx = 0;
494                for j in 0..self_coords.len() {
495                    flat_idx += self_coords[j] * self_strides[j];
496                }
497                result_data[flat_idx] = src_data[i];
498            }
499        }
500        Self::from_data(result_data, self.shape().dims().to_vec(), self.device)
501    }
502    /// Scatter values along an axis using indices and add to existing values
503    ///
504    /// # PyTorch Compatibility
505    /// Equivalent to `torch.scatter_add(tensor, dim, index, src)`
506    ///
507    /// # Arguments
508    /// * `dim` - Dimension along which to index
509    /// * `indices` - Index tensor (same shape as src)
510    /// * `src` - Source tensor containing values to add
511    ///
512    /// # Examples
513    /// ```ignore
514    /// let tensor = Tensor::zeros(&[5], DeviceType::Cpu)?;
515    /// let indices = Tensor::from_data(vec![0i64, 1, 2, 0, 1], vec![5], DeviceType::Cpu)?;
516    /// let src = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0, 5.0], vec![5], DeviceType::Cpu)?;
517    /// let result = tensor.scatter_add(0, &indices, &src)?;
518    /// // result[0] += 1.0 + 4.0 = 5.0
519    /// // result[1] += 2.0 + 5.0 = 7.0
520    /// // result[2] += 3.0 = 3.0
521    /// ```
522    pub fn scatter_add(&self, dim: usize, indices: &Tensor<i64>, src: &Tensor<T>) -> Result<Self>
523    where
524        T: std::ops::Add<Output = T>,
525    {
526        if dim >= self.ndim() {
527            return Err(TorshError::InvalidArgument(format!(
528                "Dimension {} out of range for tensor with {} dimensions",
529                dim,
530                self.ndim()
531            )));
532        }
533        let mut result_data = self.to_vec()?;
534        let indices_data = indices.to_vec()?;
535        let src_data = src.to_vec()?;
536        if indices_data.len() != src_data.len() {
537            return Err(TorshError::InvalidArgument(
538                "Indices and source tensor must have the same number of elements".to_string(),
539            ));
540        }
541        if self.ndim() == 1 {
542            for (i, &index) in indices_data.iter().enumerate() {
543                let idx = if index < 0 {
544                    (self.shape().dims()[0] as i64 + index) as usize
545                } else {
546                    index as usize
547                };
548                if idx >= self.shape().dims()[0] {
549                    return Err(TorshError::InvalidArgument(format!(
550                        "Index {} out of range for tensor with size {}",
551                        index,
552                        self.shape().dims()[0]
553                    )));
554                }
555                result_data[idx] = result_data[idx] + src_data[i];
556            }
557        } else {
558            let self_shape_ref = self.shape();
559            let self_shape = self_shape_ref.dims();
560            let indices_shape_ref = indices.shape();
561            let indices_shape = indices_shape_ref.dims();
562            let dim_size = self_shape[dim];
563            let mut self_strides = vec![1; self_shape.len()];
564            let mut indices_strides = vec![1; indices_shape.len()];
565            for i in (0..self_shape.len() - 1).rev() {
566                self_strides[i] = self_strides[i + 1] * self_shape[i + 1];
567            }
568            for i in (0..indices_shape.len() - 1).rev() {
569                indices_strides[i] = indices_strides[i + 1] * indices_shape[i + 1];
570            }
571            let total_elements = indices_data.len();
572            for (i, &index_value) in indices_data.iter().enumerate().take(total_elements) {
573                let mut indices_coords = vec![0; indices_shape.len()];
574                let mut temp_i = i;
575                for j in 0..indices_shape.len() {
576                    indices_coords[j] = temp_i / indices_strides[j];
577                    temp_i %= indices_strides[j];
578                }
579                let idx = if index_value < 0 {
580                    (dim_size as i64 + index_value) as usize
581                } else {
582                    index_value as usize
583                };
584                if idx >= dim_size {
585                    return Err(TorshError::InvalidArgument(format!(
586                        "Index {index_value} out of range for dimension {dim} with size {dim_size}"
587                    )));
588                }
589                let mut self_coords = indices_coords.clone();
590                if dim < self_coords.len() {
591                    self_coords[dim] = idx;
592                }
593                let mut flat_idx = 0;
594                for j in 0..self_coords.len() {
595                    flat_idx += self_coords[j] * self_strides[j];
596                }
597                result_data[flat_idx] = result_data[flat_idx] + src_data[i];
598            }
599        }
600        Self::from_data(result_data, self.shape().dims().to_vec(), self.device)
601    }
602    /// Repeat tensor along specified dimensions
603    pub fn repeat(&self, repeats: &[usize]) -> Result<Self> {
604        if repeats.len() != self.ndim() {
605            return Err(TorshError::InvalidArgument(format!(
606                "Number of repeats {} must match tensor dimensions {}",
607                repeats.len(),
608                self.ndim()
609            )));
610        }
611        let self_data = self.to_vec()?;
612        let shape_binding = self.shape();
613        let self_shape = shape_binding.dims();
614        let new_shape: Vec<usize> = self_shape
615            .iter()
616            .zip(repeats.iter())
617            .map(|(&dim, &repeat)| dim * repeat)
618            .collect();
619        let new_numel = new_shape.iter().product();
620        let mut result_data = Vec::with_capacity(new_numel);
621        for result_idx in 0..new_numel {
622            let mut result_coords = vec![0; new_shape.len()];
623            let mut temp_idx = result_idx;
624            for i in (0..new_shape.len()).rev() {
625                result_coords[i] = temp_idx % new_shape[i];
626                temp_idx /= new_shape[i];
627            }
628            let source_coords: Vec<usize> = result_coords
629                .iter()
630                .zip(self_shape.iter())
631                .map(|(&result_coord, &dim_size)| result_coord % dim_size)
632                .collect();
633            let mut source_idx = 0;
634            let mut stride = 1;
635            for i in (0..self_shape.len()).rev() {
636                source_idx += source_coords[i] * stride;
637                stride *= self_shape[i];
638            }
639            result_data.push(self_data[source_idx]);
640        }
641        Self::from_data(result_data, new_shape, self.device)
642    }
643    /// Add values to tensor at specified indices along a dimension
644    ///
645    /// # PyTorch Compatibility
646    /// Equivalent to `torch.index_add(tensor, dim, index, source, alpha=1.0)`
647    ///
648    /// # Arguments
649    /// * `dim` - Dimension along which to index
650    /// * `index` - 1D tensor containing indices
651    /// * `source` - Source tensor to add
652    ///
653    /// # Examples
654    /// ```ignore
655    /// let tensor = Tensor::zeros(&[3, 5], DeviceType::Cpu)?;
656    /// let index = Tensor::from_data(vec![0i64, 2], vec![2], DeviceType::Cpu)?;
657    /// let source = Tensor::ones(&[2, 5], DeviceType::Cpu)?;
658    /// let result = tensor.index_add(0, &index, &source)?;
659    /// ```
660    pub fn index_add(&self, dim: isize, index: &Tensor<i64>, source: &Self) -> Result<Self>
661    where
662        T: std::ops::Add<Output = T>,
663    {
664        let ndim = self.ndim();
665        let dim = if dim < 0 {
666            (ndim as isize + dim) as usize
667        } else {
668            dim as usize
669        };
670        if dim >= ndim {
671            return Err(TorshError::InvalidArgument(format!(
672                "Dimension {} out of range for {}-D tensor",
673                dim, ndim
674            )));
675        }
676        if index.ndim() != 1 {
677            return Err(TorshError::InvalidArgument(
678                "index must be 1D tensor".to_string(),
679            ));
680        }
681        let index_size = index.shape().dims()[0];
682        let self_shape = self.shape().to_vec();
683        let source_shape = source.shape().to_vec();
684        if source_shape.len() != self_shape.len() {
685            return Err(TorshError::ShapeMismatch {
686                expected: self_shape.clone(),
687                got: source_shape.clone(),
688            });
689        }
690        for (i, (&s, &src_s)) in self_shape.iter().zip(source_shape.iter()).enumerate() {
691            if i == dim {
692                if src_s != index_size {
693                    return Err(TorshError::InvalidArgument(format!(
694                        "source dimension {} size {} must match index size {}",
695                        i, src_s, index_size
696                    )));
697                }
698            } else if s != src_s {
699                return Err(TorshError::ShapeMismatch {
700                    expected: self_shape.clone(),
701                    got: source_shape.clone(),
702                });
703            }
704        }
705        let mut result_data = self.to_vec()?;
706        let source_data = source.to_vec()?;
707        let index_data = index.to_vec()?;
708        let dim_size = self_shape[dim];
709        let outer_size: usize = self_shape[..dim].iter().product();
710        let inner_size: usize = self_shape[dim + 1..].iter().product();
711        for (src_idx_in_dim, &target_idx) in index_data.iter().enumerate() {
712            if target_idx < 0 || target_idx as usize >= dim_size {
713                return Err(TorshError::InvalidArgument(format!(
714                    "Index {} out of range for dimension size {}",
715                    target_idx, dim_size
716                )));
717            }
718            let target_idx = target_idx as usize;
719            for outer in 0..outer_size {
720                for inner in 0..inner_size {
721                    let result_idx =
722                        outer * dim_size * inner_size + target_idx * inner_size + inner;
723                    let source_idx =
724                        outer * index_size * inner_size + src_idx_in_dim * inner_size + inner;
725                    result_data[result_idx] = result_data[result_idx] + source_data[source_idx];
726                }
727            }
728        }
729        Self::from_data(result_data, self_shape, self.device)
730    }
731    /// Copy values from source to tensor at specified indices along a dimension
732    ///
733    /// # PyTorch Compatibility
734    /// Equivalent to `torch.index_copy(tensor, dim, index, source)`
735    ///
736    /// # Arguments
737    /// * `dim` - Dimension along which to index
738    /// * `index` - 1D tensor containing indices
739    /// * `source` - Source tensor to copy from
740    ///
741    /// # Examples
742    /// ```ignore
743    /// let tensor = Tensor::zeros(&[3, 5], DeviceType::Cpu)?;
744    /// let index = Tensor::from_data(vec![0i64, 2], vec![2], DeviceType::Cpu)?;
745    /// let source = Tensor::ones(&[2, 5], DeviceType::Cpu)?;
746    /// let result = tensor.index_copy(0, &index, &source)?;
747    /// ```
748    pub fn index_copy(&self, dim: isize, index: &Tensor<i64>, source: &Self) -> Result<Self> {
749        let ndim = self.ndim();
750        let dim = if dim < 0 {
751            (ndim as isize + dim) as usize
752        } else {
753            dim as usize
754        };
755        if dim >= ndim {
756            return Err(TorshError::InvalidArgument(format!(
757                "Dimension {} out of range for {}-D tensor",
758                dim, ndim
759            )));
760        }
761        if index.ndim() != 1 {
762            return Err(TorshError::InvalidArgument(
763                "index must be 1D tensor".to_string(),
764            ));
765        }
766        let index_size = index.shape().dims()[0];
767        let self_shape = self.shape().to_vec();
768        let source_shape = source.shape().to_vec();
769        if source_shape.len() != self_shape.len() {
770            return Err(TorshError::ShapeMismatch {
771                expected: self_shape.clone(),
772                got: source_shape.clone(),
773            });
774        }
775        for (i, (&s, &src_s)) in self_shape.iter().zip(source_shape.iter()).enumerate() {
776            if i == dim {
777                if src_s != index_size {
778                    return Err(TorshError::InvalidArgument(format!(
779                        "source dimension {} size {} must match index size {}",
780                        i, src_s, index_size
781                    )));
782                }
783            } else if s != src_s {
784                return Err(TorshError::ShapeMismatch {
785                    expected: self_shape.clone(),
786                    got: source_shape.clone(),
787                });
788            }
789        }
790        let mut result_data = self.to_vec()?;
791        let source_data = source.to_vec()?;
792        let index_data = index.to_vec()?;
793        let dim_size = self_shape[dim];
794        let outer_size: usize = self_shape[..dim].iter().product();
795        let inner_size: usize = self_shape[dim + 1..].iter().product();
796        for (src_idx_in_dim, &target_idx) in index_data.iter().enumerate() {
797            if target_idx < 0 || target_idx as usize >= dim_size {
798                return Err(TorshError::InvalidArgument(format!(
799                    "Index {} out of range for dimension size {}",
800                    target_idx, dim_size
801                )));
802            }
803            let target_idx = target_idx as usize;
804            for outer in 0..outer_size {
805                for inner in 0..inner_size {
806                    let result_idx =
807                        outer * dim_size * inner_size + target_idx * inner_size + inner;
808                    let source_idx =
809                        outer * index_size * inner_size + src_idx_in_dim * inner_size + inner;
810                    result_data[result_idx] = source_data[source_idx];
811                }
812            }
813        }
814        Self::from_data(result_data, self_shape, self.device)
815    }
816    /// Fill values in tensor at specified indices along a dimension
817    ///
818    /// # PyTorch Compatibility
819    /// Equivalent to `torch.index_fill(tensor, dim, index, value)`
820    ///
821    /// # Arguments
822    /// * `dim` - Dimension along which to index
823    /// * `index` - 1D tensor containing indices
824    /// * `value` - Scalar value to fill
825    ///
826    /// # Examples
827    /// ```ignore
828    /// let tensor = Tensor::zeros(&[3, 5], DeviceType::Cpu)?;
829    /// let index = Tensor::from_data(vec![0i64, 2], vec![2], DeviceType::Cpu)?;
830    /// let result = tensor.index_fill(0, &index, 3.14)?;
831    /// ```
832    pub fn index_fill(&self, dim: isize, index: &Tensor<i64>, value: T) -> Result<Self> {
833        let ndim = self.ndim();
834        let dim = if dim < 0 {
835            (ndim as isize + dim) as usize
836        } else {
837            dim as usize
838        };
839        if dim >= ndim {
840            return Err(TorshError::InvalidArgument(format!(
841                "Dimension {} out of range for {}-D tensor",
842                dim, ndim
843            )));
844        }
845        if index.ndim() != 1 {
846            return Err(TorshError::InvalidArgument(
847                "index must be 1D tensor".to_string(),
848            ));
849        }
850        let mut result_data = self.to_vec()?;
851        let index_data = index.to_vec()?;
852        let self_shape = self.shape().to_vec();
853        let dim_size = self_shape[dim];
854        let outer_size: usize = self_shape[..dim].iter().product();
855        let inner_size: usize = self_shape[dim + 1..].iter().product();
856        for &target_idx in index_data.iter() {
857            if target_idx < 0 || target_idx as usize >= dim_size {
858                return Err(TorshError::InvalidArgument(format!(
859                    "Index {} out of range for dimension size {}",
860                    target_idx, dim_size
861                )));
862            }
863            let target_idx = target_idx as usize;
864            for outer in 0..outer_size {
865                for inner in 0..inner_size {
866                    let result_idx =
867                        outer * dim_size * inner_size + target_idx * inner_size + inner;
868                    result_data[result_idx] = value;
869                }
870            }
871        }
872        Self::from_data(result_data, self_shape, self.device)
873    }
874    /// Place values at specified flat indices (in-place-like operation, returns new tensor)
875    ///
876    /// # PyTorch Compatibility
877    /// Equivalent to `torch.put_(tensor, indices, values)` but returns new tensor
878    ///
879    /// # Arguments
880    /// * `indices` - 1D tensor of flat indices
881    /// * `values` - 1D tensor of values (must match indices length or be broadcastable)
882    ///
883    /// # Examples
884    /// ```ignore
885    /// let tensor = Tensor::zeros(&[3, 3], DeviceType::Cpu)?;  // [[0,0,0],[0,0,0],[0,0,0]]
886    /// let indices = Tensor::from_data(vec![0i64, 4, 8], vec![3], DeviceType::Cpu)?;
887    /// let values = Tensor::from_data(vec![1.0f32, 2.0, 3.0], vec![3], DeviceType::Cpu)?;
888    /// let result = tensor.put_(&indices, &values)?;  // [[1,0,0],[0,2,0],[0,0,3]]
889    /// ```
890    pub fn put_(&self, indices: &Tensor<i64>, values: &Tensor<T>) -> Result<Self> {
891        if indices.ndim() != 1 {
892            return Err(TorshError::InvalidArgument(
893                "indices must be 1D tensor".to_string(),
894            ));
895        }
896        if values.ndim() != 1 {
897            return Err(TorshError::InvalidArgument(
898                "values must be 1D tensor".to_string(),
899            ));
900        }
901        let indices_data = indices.to_vec()?;
902        let values_data = values.to_vec()?;
903        if indices_data.len() != values_data.len() {
904            return Err(TorshError::InvalidArgument(format!(
905                "Number of values {} must match number of indices {}",
906                values_data.len(),
907                indices_data.len()
908            )));
909        }
910        let mut result_data = self.to_vec()?;
911        let numel = self.numel();
912        for (i, &index) in indices_data.iter().enumerate() {
913            let idx = if index < 0 {
914                ((numel as i64) + index) as usize
915            } else {
916                index as usize
917            };
918            if idx >= numel {
919                return Err(TorshError::InvalidArgument(format!(
920                    "Index {} out of range for tensor with {} elements",
921                    index, numel
922                )));
923            }
924            result_data[idx] = values_data[i];
925        }
926        Self::from_data(result_data, self.shape().dims().to_vec(), self.device)
927    }
928    /// Scatter values from source tensor where mask is true (PyTorch-compatible)
929    ///
930    /// Copies values from the source tensor to positions where the mask is true.
931    /// The mask must have the same shape as self. Source values are taken sequentially
932    /// and placed at positions where mask is true.
933    ///
934    /// # PyTorch Compatibility
935    /// Equivalent to `torch.masked_scatter(tensor, mask, source)`
936    ///
937    /// # Arguments
938    /// * `mask` - Boolean tensor with same shape as self
939    /// * `source` - Tensor containing values to scatter (must have at least as many elements as true values in mask)
940    ///
941    /// # Examples
942    /// ```ignore
943    /// let tensor = Tensor::zeros(&[3, 3], DeviceType::Cpu)?;
944    /// let mask = Tensor::from_data(
945    ///     vec![true, false, false, false, true, false, false, false, true],
946    ///     vec![3, 3],
947    ///     DeviceType::Cpu
948    /// )?;
949    /// let source = Tensor::from_data(vec![1.0f32, 2.0, 3.0], vec![3], DeviceType::Cpu)?;
950    /// let result = tensor.masked_scatter(&mask, &source)?;  // [[1,0,0],[0,2,0],[0,0,3]]
951    /// ```
952    pub fn masked_scatter(&self, mask: &Tensor<bool>, source: &Tensor<T>) -> Result<Self> {
953        if self.shape() != mask.shape() {
954            return Err(TorshError::ShapeMismatch {
955                expected: self.shape().dims().to_vec(),
956                got: mask.shape().dims().to_vec(),
957            });
958        }
959        let mask_data = mask.to_vec()?;
960        let true_count = mask_data.iter().filter(|&&x| x).count();
961        if source.numel() < true_count {
962            return Err(TorshError::InvalidArgument(format!(
963                "Source tensor has {} elements but need {} for scatter (mask has {} true values)",
964                source.numel(),
965                true_count,
966                true_count
967            )));
968        }
969        let self_data = self.to_vec()?;
970        let source_data = source.to_vec()?;
971        let mut result_data = Vec::with_capacity(self_data.len());
972        let mut source_idx = 0;
973        for (i, &self_val) in self_data.iter().enumerate() {
974            if i < mask_data.len() && mask_data[i] {
975                result_data.push(source_data[source_idx]);
976                source_idx += 1;
977            } else {
978                result_data.push(self_val);
979            }
980        }
981        Self::from_data(result_data, self.shape().dims().to_vec(), self.device)
982    }
983    /// Multi-dimensional indexed put operation (PyTorch-compatible)
984    ///
985    /// Places values from source tensor at positions specified by index tensors.
986    /// Each index tensor specifies indices along one dimension. Index tensors must
987    /// be broadcastable to the same shape.
988    ///
989    /// # PyTorch Compatibility
990    /// Equivalent to `torch.index_put(tensor, indices, values)` where indices is a tuple of index tensors
991    ///
992    /// # Arguments
993    /// * `indices` - Slice of index tensors, one per dimension to index
994    /// * `values` - Tensor of values to place (must broadcast to indexed positions)
995    ///
996    /// # Examples
997    /// ```ignore
998    /// // 2D example: index_put a 3x3 matrix with row=[0,1] col=[1,2]
999    /// let tensor = Tensor::zeros(&[3, 3], DeviceType::Cpu)?;
1000    /// let row_idx = Tensor::from_data(vec![0i64, 1], vec![2], DeviceType::Cpu)?;
1001    /// let col_idx = Tensor::from_data(vec![1i64, 2], vec![2], DeviceType::Cpu)?;
1002    /// let values = Tensor::from_data(vec![10.0f32, 20.0], vec![2], DeviceType::Cpu)?;
1003    /// let result = tensor.index_put(&[row_idx, col_idx], &values)?;
1004    /// // result[0,1] = 10.0, result[1,2] = 20.0
1005    /// ```
1006    pub fn index_put(&self, indices: &[Tensor<i64>], values: &Tensor<T>) -> Result<Self> {
1007        if indices.is_empty() {
1008            return Err(TorshError::InvalidArgument(
1009                "indices cannot be empty".to_string(),
1010            ));
1011        }
1012        if indices.len() > self.ndim() {
1013            return Err(TorshError::InvalidArgument(format!(
1014                "Too many indices ({}) for tensor with {} dimensions",
1015                indices.len(),
1016                self.ndim()
1017            )));
1018        }
1019        let index_shape_ref = indices[0].shape();
1020        let index_shape = index_shape_ref.dims();
1021        let num_indices = indices[0].numel();
1022        for idx_tensor in indices.iter() {
1023            if idx_tensor.shape().dims() != index_shape {
1024                return Err(TorshError::ShapeMismatch {
1025                    expected: index_shape.to_vec(),
1026                    got: idx_tensor.shape().dims().to_vec(),
1027                });
1028            }
1029        }
1030        if values.numel() != num_indices && values.numel() != 1 {
1031            return Err(TorshError::InvalidArgument(format!(
1032                "Values tensor has {} elements but need {} (or 1 for broadcasting)",
1033                values.numel(),
1034                num_indices
1035            )));
1036        }
1037        let mut result_data = self.to_vec()?;
1038        let self_shape_ref = self.shape();
1039        let self_shape = self_shape_ref.dims();
1040        let values_data = values.to_vec()?;
1041        let index_data: Result<Vec<Vec<i64>>> = indices.iter().map(|idx| idx.to_vec()).collect();
1042        let index_data = index_data?;
1043        let mut strides = vec![1; self_shape.len()];
1044        for i in (0..self_shape.len() - 1).rev() {
1045            strides[i] = strides[i + 1] * self_shape[i + 1];
1046        }
1047        for i in 0..num_indices {
1048            let value = if values_data.len() == 1 {
1049                values_data[0]
1050            } else {
1051                values_data[i]
1052            };
1053            let mut flat_idx = 0;
1054            for (dim, idx_vec) in index_data.iter().enumerate() {
1055                let mut idx = idx_vec[i];
1056                if idx < 0 {
1057                    idx += self_shape[dim] as i64;
1058                }
1059                if idx < 0 || idx >= self_shape[dim] as i64 {
1060                    return Err(TorshError::InvalidArgument(format!(
1061                        "Index {} out of bounds for dimension {} with size {}",
1062                        idx_vec[i], dim, self_shape[dim]
1063                    )));
1064                }
1065                flat_idx += (idx as usize) * strides[dim];
1066            }
1067            result_data[flat_idx] = value;
1068        }
1069        Self::from_data(result_data, self_shape.to_vec(), self.device)
1070    }
1071    /// Scatter with reduction operation (PyTorch-compatible)
1072    ///
1073    /// Generalized scatter operation that applies a reduction operation (sum, prod, mean, etc.)
1074    /// when scattering values to the same index position.
1075    ///
1076    /// # PyTorch Compatibility
1077    /// Equivalent to `torch.scatter_reduce(tensor, dim, index, src, reduce)`
1078    ///
1079    /// # Arguments
1080    /// * `dim` - Dimension along which to scatter
1081    /// * `indices` - Index tensor specifying where to scatter values
1082    /// * `src` - Source tensor containing values to scatter
1083    /// * `reduce` - Reduction operation ("sum", "prod", "mean", "amax", "amin")
1084    ///
1085    /// # Examples
1086    /// ```ignore
1087    /// let tensor = Tensor::zeros(&[5], DeviceType::Cpu)?;
1088    /// let indices = Tensor::from_data(vec![0i64, 1, 2, 0, 1], vec![5], DeviceType::Cpu)?;
1089    /// let src = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0, 5.0], vec![5], DeviceType::Cpu)?;
1090    /// let result = tensor.scatter_reduce(0, &indices, &src, "sum")?;
1091    /// // result[0] = 1.0 + 4.0 = 5.0 (sum reduction)
1092    /// // result[1] = 2.0 + 5.0 = 7.0
1093    /// ```
1094    pub fn scatter_reduce(
1095        &self,
1096        dim: usize,
1097        indices: &Tensor<i64>,
1098        src: &Tensor<T>,
1099        reduce: &str,
1100    ) -> Result<Self>
1101    where
1102        T: std::ops::Add<Output = T>
1103            + std::ops::Mul<Output = T>
1104            + std::ops::Div<Output = T>
1105            + PartialOrd
1106            + num_traits::FromPrimitive,
1107    {
1108        if dim >= self.ndim() {
1109            return Err(TorshError::InvalidArgument(format!(
1110                "Dimension {} out of range for {}-dimensional tensor",
1111                dim,
1112                self.ndim()
1113            )));
1114        }
1115        if indices.shape() != src.shape() {
1116            return Err(TorshError::ShapeMismatch {
1117                expected: indices.shape().dims().to_vec(),
1118                got: src.shape().dims().to_vec(),
1119            });
1120        }
1121        let indices_data = indices.to_vec()?;
1122        let src_data = src.to_vec()?;
1123        let mut result_data = self.to_vec()?;
1124        let self_shape_ref = self.shape();
1125        let self_shape = self_shape_ref.dims();
1126        let mut counts = if reduce == "mean" {
1127            vec![0usize; result_data.len()]
1128        } else {
1129            vec![]
1130        };
1131        if self.ndim() == 1 {
1132            for (i, &index) in indices_data.iter().enumerate() {
1133                let idx = if index < 0 {
1134                    (self_shape[0] as i64 + index) as usize
1135                } else {
1136                    index as usize
1137                };
1138                if idx >= self_shape[0] {
1139                    return Err(TorshError::InvalidArgument(format!(
1140                        "Index {} out of bounds for dimension size {}",
1141                        index, self_shape[0]
1142                    )));
1143                }
1144                result_data[idx] = match reduce {
1145                    "sum" => result_data[idx] + src_data[i],
1146                    "prod" => result_data[idx] * src_data[i],
1147                    "mean" => {
1148                        counts[idx] += 1;
1149                        result_data[idx] + src_data[i]
1150                    }
1151                    "amax" => {
1152                        if src_data[i] > result_data[idx] {
1153                            src_data[i]
1154                        } else {
1155                            result_data[idx]
1156                        }
1157                    }
1158                    "amin" => {
1159                        if src_data[i] < result_data[idx] {
1160                            src_data[i]
1161                        } else {
1162                            result_data[idx]
1163                        }
1164                    }
1165                    _ => {
1166                        return Err(TorshError::InvalidArgument(format!(
1167                            "Unknown reduce operation: {}. Supported: sum, prod, mean, amax, amin",
1168                            reduce
1169                        )));
1170                    }
1171                };
1172            }
1173            if reduce == "mean" {
1174                for (i, count) in counts.iter().enumerate() {
1175                    if *count > 0 {
1176                        result_data[i] = T::from_usize(*count)
1177                            .and_then(|c| Some(result_data[i] / c))
1178                            .unwrap_or(result_data[i]);
1179                    }
1180                }
1181            }
1182        } else {
1183            let dim_size = self_shape[dim];
1184            let _outer_size: usize = self_shape[..dim].iter().product();
1185            let _inner_size: usize = self_shape[dim + 1..].iter().product();
1186            let mut self_strides = vec![1; self_shape.len()];
1187            for i in (0..self_shape.len() - 1).rev() {
1188                self_strides[i] = self_strides[i + 1] * self_shape[i + 1];
1189            }
1190            let src_shape_ref = src.shape();
1191            let src_shape = src_shape_ref.dims();
1192            let mut src_strides = vec![1; src_shape.len()];
1193            for i in (0..src_shape.len() - 1).rev() {
1194                src_strides[i] = src_strides[i + 1] * src_shape[i + 1];
1195            }
1196            for i in 0..indices_data.len() {
1197                let index = indices_data[i];
1198                let idx = if index < 0 {
1199                    (dim_size as i64 + index) as usize
1200                } else {
1201                    index as usize
1202                };
1203                if idx >= dim_size {
1204                    return Err(TorshError::InvalidArgument(format!(
1205                        "Index {} out of bounds for dimension {} size {}",
1206                        index, dim, dim_size
1207                    )));
1208                }
1209                let mut coords = vec![0; self_shape.len()];
1210                let mut remainder = i;
1211                for (d, &stride) in src_strides.iter().enumerate() {
1212                    coords[d] = remainder / stride;
1213                    remainder %= stride;
1214                }
1215                coords[dim] = idx;
1216                let flat_idx = coords
1217                    .iter()
1218                    .zip(self_strides.iter())
1219                    .map(|(c, s)| c * s)
1220                    .sum::<usize>();
1221                result_data[flat_idx] = match reduce {
1222                    "sum" => result_data[flat_idx] + src_data[i],
1223                    "prod" => result_data[flat_idx] * src_data[i],
1224                    "mean" => {
1225                        counts[flat_idx] += 1;
1226                        result_data[flat_idx] + src_data[i]
1227                    }
1228                    "amax" => {
1229                        if src_data[i] > result_data[flat_idx] {
1230                            src_data[i]
1231                        } else {
1232                            result_data[flat_idx]
1233                        }
1234                    }
1235                    "amin" => {
1236                        if src_data[i] < result_data[flat_idx] {
1237                            src_data[i]
1238                        } else {
1239                            result_data[flat_idx]
1240                        }
1241                    }
1242                    _ => {
1243                        return Err(TorshError::InvalidArgument(format!(
1244                            "Unknown reduce operation: {}",
1245                            reduce
1246                        )));
1247                    }
1248                };
1249            }
1250            if reduce == "mean" {
1251                for (i, count) in counts.iter().enumerate() {
1252                    if *count > 0 {
1253                        result_data[i] = T::from_usize(*count)
1254                            .and_then(|c| Some(result_data[i] / c))
1255                            .unwrap_or(result_data[i]);
1256                    }
1257                }
1258            }
1259        }
1260        Self::from_data(result_data, self_shape.to_vec(), self.device)
1261    }
1262    /// Scatter values to the diagonal (PyTorch-compatible)
1263    ///
1264    /// Embeds the values of src tensor into self along the diagonal elements,
1265    /// with respect to dim1 and dim2. The offset determines which diagonal to use.
1266    ///
1267    /// # PyTorch Compatibility
1268    /// Equivalent to `torch.diagonal_scatter(tensor, src, offset, dim1, dim2)`
1269    ///
1270    /// # Arguments
1271    /// * `src` - Source tensor containing values for the diagonal
1272    /// * `offset` - Diagonal offset (0=main diagonal, >0=above, <0=below)
1273    /// * `dim1` - First dimension (default: 0)
1274    /// * `dim2` - Second dimension (default: 1)
1275    ///
1276    /// # Examples
1277    /// ```ignore
1278    /// let tensor = Tensor::zeros(&[3, 3], DeviceType::Cpu)?;
1279    /// let src = Tensor::from_data(vec![1.0f32, 2.0, 3.0], vec![3], DeviceType::Cpu)?;
1280    /// let result = tensor.diagonal_scatter(&src, 0, 0, 1)?;
1281    /// // result = [[1, 0, 0], [0, 2, 0], [0, 0, 3]]
1282    /// ```
1283    pub fn diagonal_scatter(
1284        &self,
1285        src: &Tensor<T>,
1286        offset: isize,
1287        dim1: usize,
1288        dim2: usize,
1289    ) -> Result<Self> {
1290        if dim1 >= self.ndim() || dim2 >= self.ndim() {
1291            return Err(TorshError::InvalidArgument(format!(
1292                "Dimensions ({}, {}) out of range for {}-dimensional tensor",
1293                dim1,
1294                dim2,
1295                self.ndim()
1296            )));
1297        }
1298        if dim1 == dim2 {
1299            return Err(TorshError::InvalidArgument(
1300                "dim1 and dim2 must be different".to_string(),
1301            ));
1302        }
1303        let self_shape_ref = self.shape();
1304        let self_shape = self_shape_ref.dims();
1305        let dim1_size = self_shape[dim1];
1306        let dim2_size = self_shape[dim2];
1307        let diag_len = if offset >= 0 {
1308            let offset_u = offset as usize;
1309            if offset_u >= dim2_size {
1310                0
1311            } else {
1312                std::cmp::min(dim1_size, dim2_size - offset_u)
1313            }
1314        } else {
1315            let offset_u = (-offset) as usize;
1316            if offset_u >= dim1_size {
1317                0
1318            } else {
1319                std::cmp::min(dim1_size - offset_u, dim2_size)
1320            }
1321        };
1322        if src.numel() != diag_len {
1323            return Err(TorshError::ShapeMismatch {
1324                expected: vec![diag_len],
1325                got: vec![src.numel()],
1326            });
1327        }
1328        let mut result_data = self.to_vec()?;
1329        let src_data = src.to_vec()?;
1330        let mut strides = vec![1; self_shape.len()];
1331        for i in (0..self_shape.len() - 1).rev() {
1332            strides[i] = strides[i + 1] * self_shape[i + 1];
1333        }
1334        for i in 0..diag_len {
1335            let mut indices = vec![0; self_shape.len()];
1336            if offset >= 0 {
1337                indices[dim1] = i;
1338                indices[dim2] = i + offset as usize;
1339            } else {
1340                indices[dim1] = i + (-offset) as usize;
1341                indices[dim2] = i;
1342            }
1343            let mut flat_idx = 0;
1344            for (d, &idx) in indices.iter().enumerate() {
1345                flat_idx += idx * strides[d];
1346            }
1347            result_data[flat_idx] = src_data[i];
1348        }
1349        Self::from_data(result_data, self_shape.to_vec(), self.device)
1350    }
1351    /// Scatter values to a selected slice along dimension (PyTorch-compatible)
1352    ///
1353    /// Embeds the values of src tensor into self at the given index along dimension dim.
1354    /// This is the inverse of `select()` operation.
1355    ///
1356    /// # PyTorch Compatibility
1357    /// Equivalent to `torch.select_scatter(tensor, src, dim, index)`
1358    ///
1359    /// # Arguments
1360    /// * `src` - Source tensor to scatter (shape should match self with dim removed)
1361    /// * `dim` - Dimension along which to select
1362    /// * `index` - Index position to scatter to
1363    ///
1364    /// # Examples
1365    /// ```ignore
1366    /// let tensor = Tensor::zeros(&[3, 4, 5], DeviceType::Cpu)?;
1367    /// let src = Tensor::ones(&[3, 5], DeviceType::Cpu)?; // dim=1 removed
1368    /// let result = tensor.select_scatter(&src, 1, 2)?;
1369    /// // result[:, 2, :] = src
1370    /// ```
1371    pub fn select_scatter(&self, src: &Tensor<T>, dim: isize, index: isize) -> Result<Self> {
1372        let ndim = self.ndim() as isize;
1373        let dim_normalized = if dim < 0 { ndim + dim } else { dim };
1374        if dim_normalized < 0 || dim_normalized >= ndim {
1375            return Err(TorshError::InvalidArgument(format!(
1376                "Dimension {} out of range for {}-dimensional tensor",
1377                dim,
1378                self.ndim()
1379            )));
1380        }
1381        let dim_u = dim_normalized as usize;
1382        let self_shape_ref = self.shape();
1383        let self_shape = self_shape_ref.dims();
1384        let index_normalized = if index < 0 {
1385            (self_shape[dim_u] as isize) + index
1386        } else {
1387            index
1388        };
1389        if index_normalized < 0 || index_normalized >= self_shape[dim_u] as isize {
1390            return Err(TorshError::InvalidArgument(format!(
1391                "Index {} out of bounds for dimension {} with size {}",
1392                index, dim_u, self_shape[dim_u]
1393            )));
1394        }
1395        let index_u = index_normalized as usize;
1396        let expected_src_shape: Vec<usize> = self_shape
1397            .iter()
1398            .enumerate()
1399            .filter(|(i, _)| *i != dim_u)
1400            .map(|(_, &s)| s)
1401            .collect();
1402        let src_shape_ref = src.shape();
1403        let src_shape = src_shape_ref.dims();
1404        if src_shape != expected_src_shape.as_slice() {
1405            return Err(TorshError::ShapeMismatch {
1406                expected: expected_src_shape,
1407                got: src_shape.to_vec(),
1408            });
1409        }
1410        let mut result_data = self.to_vec()?;
1411        let src_data = src.to_vec()?;
1412        let mut self_strides = vec![1; self_shape.len()];
1413        for i in (0..self_shape.len() - 1).rev() {
1414            self_strides[i] = self_strides[i + 1] * self_shape[i + 1];
1415        }
1416        let outer_size: usize = self_shape[..dim_u].iter().product();
1417        let inner_size: usize = self_shape[dim_u + 1..].iter().product();
1418        for outer in 0..outer_size {
1419            for inner in 0..inner_size {
1420                let self_idx =
1421                    outer * (self_shape[dim_u] * inner_size) + index_u * inner_size + inner;
1422                let src_idx = outer * inner_size + inner;
1423                result_data[self_idx] = src_data[src_idx];
1424            }
1425        }
1426        Self::from_data(result_data, self_shape.to_vec(), self.device)
1427    }
1428    /// Scatter values to a slice along dimension (PyTorch-compatible)
1429    ///
1430    /// Embeds the values of src tensor into self along dimension dim, starting at
1431    /// start index, ending at end index, with the given step.
1432    ///
1433    /// # PyTorch Compatibility
1434    /// Equivalent to `torch.slice_scatter(tensor, src, dim, start, end, step)`
1435    ///
1436    /// # Arguments
1437    /// * `src` - Source tensor to scatter
1438    /// * `dim` - Dimension along which to slice
1439    /// * `start` - Starting index (None means 0)
1440    /// * `end` - Ending index (None means size of dim)
1441    /// * `step` - Step size (default: 1)
1442    ///
1443    /// # Examples
1444    /// ```ignore
1445    /// let tensor = Tensor::zeros(&[5, 5], DeviceType::Cpu)?;
1446    /// let src = Tensor::ones(&[2, 5], DeviceType::Cpu)?;
1447    /// let result = tensor.slice_scatter(&src, 0, Some(1), Some(3), 1)?;
1448    /// // result[1:3, :] = src
1449    /// ```
1450    pub fn slice_scatter(
1451        &self,
1452        src: &Tensor<T>,
1453        dim: isize,
1454        start: Option<isize>,
1455        end: Option<isize>,
1456        step: usize,
1457    ) -> Result<Self> {
1458        if step == 0 {
1459            return Err(TorshError::InvalidArgument(
1460                "Step must be greater than 0".to_string(),
1461            ));
1462        }
1463        let ndim = self.ndim() as isize;
1464        let dim_normalized = if dim < 0 { ndim + dim } else { dim };
1465        if dim_normalized < 0 || dim_normalized >= ndim {
1466            return Err(TorshError::InvalidArgument(format!(
1467                "Dimension {} out of range for {}-dimensional tensor",
1468                dim,
1469                self.ndim()
1470            )));
1471        }
1472        let dim_u = dim_normalized as usize;
1473        let self_shape_ref = self.shape();
1474        let self_shape = self_shape_ref.dims();
1475        let dim_size = self_shape[dim_u] as isize;
1476        let start_normalized = start.unwrap_or(0);
1477        let start_normalized = if start_normalized < 0 {
1478            dim_size + start_normalized
1479        } else {
1480            start_normalized
1481        };
1482        let start_normalized = std::cmp::max(0, std::cmp::min(start_normalized, dim_size)) as usize;
1483        let end_normalized = end.unwrap_or(dim_size);
1484        let end_normalized = if end_normalized < 0 {
1485            dim_size + end_normalized
1486        } else {
1487            end_normalized
1488        };
1489        let end_normalized = std::cmp::max(0, std::cmp::min(end_normalized, dim_size)) as usize;
1490        let slice_len = if end_normalized > start_normalized {
1491            (end_normalized - start_normalized + step - 1) / step
1492        } else {
1493            0
1494        };
1495        let mut expected_src_shape = self_shape.to_vec();
1496        expected_src_shape[dim_u] = slice_len;
1497        let src_shape_ref = src.shape();
1498        let src_shape = src_shape_ref.dims();
1499        if src_shape != expected_src_shape.as_slice() {
1500            return Err(TorshError::ShapeMismatch {
1501                expected: expected_src_shape,
1502                got: src_shape.to_vec(),
1503            });
1504        }
1505        let mut result_data = self.to_vec()?;
1506        let src_data = src.to_vec()?;
1507        let mut self_strides = vec![1; self_shape.len()];
1508        for i in (0..self_shape.len() - 1).rev() {
1509            self_strides[i] = self_strides[i + 1] * self_shape[i + 1];
1510        }
1511        let outer_size: usize = self_shape[..dim_u].iter().product();
1512        let inner_size: usize = self_shape[dim_u + 1..].iter().product();
1513        for outer in 0..outer_size {
1514            for slice_idx in 0..slice_len {
1515                let self_dim_idx = start_normalized + slice_idx * step;
1516                for inner in 0..inner_size {
1517                    let self_idx = outer * (self_shape[dim_u] * inner_size)
1518                        + self_dim_idx * inner_size
1519                        + inner;
1520                    let src_idx = outer * (slice_len * inner_size) + slice_idx * inner_size + inner;
1521                    result_data[self_idx] = src_data[src_idx];
1522                }
1523            }
1524        }
1525        Self::from_data(result_data, self_shape.to_vec(), self.device)
1526    }
1527}