Skip to main content

tenferro_tensor/
types.rs

1use num_complex::{Complex, Complex32, Complex64};
2use num_traits::{One, Zero};
3use std::any::Any;
4use std::fmt::Debug;
5use std::sync::Arc;
6
7use crate::config::SliceConfig;
8use tenferro_tensor_core::SliceSpec as CoreSliceSpec;
9pub use tenferro_tensor_core::{DynRank, Rank, TensorLayout, TensorRank};
10
11mod accessors;
12mod shape_packing;
13mod strided_view;
14
15pub use strided_view::StridedSliceSpec;
16
17/// Memory location for tensor storage.
18///
19/// # Examples
20///
21/// ```rust
22/// use tenferro_tensor::MemoryKind;
23///
24/// let kind = MemoryKind::UnpinnedHost;
25/// ```
26#[derive(Clone, Debug, PartialEq, Eq, Hash)]
27pub enum MemoryKind {
28    Device,
29    PinnedHost,
30    UnpinnedHost,
31    Managed,
32    Other(String),
33}
34
35/// Compute device family.
36///
37/// # Examples
38///
39/// ```rust
40/// use tenferro_tensor::DeviceKind;
41///
42/// let kind = DeviceKind::Cpu;
43/// ```
44#[derive(Clone, Debug, PartialEq, Eq, Hash)]
45pub enum DeviceKind {
46    Cpu,
47    Gpu(GpuBackendKind),
48    Other(String),
49}
50
51/// GPU backend family used by placement metadata.
52///
53/// # Examples
54///
55/// ```rust
56/// use tenferro_tensor::GpuBackendKind;
57///
58/// let kind = GpuBackendKind::Cuda;
59/// let webgpu = GpuBackendKind::WebGpu;
60/// assert_ne!(kind, webgpu);
61/// ```
62#[derive(Clone, Debug, PartialEq, Eq, Hash)]
63pub enum GpuBackendKind {
64    Cuda,
65    WebGpu,
66    Rocm,
67    Other(String),
68}
69
70/// Concrete compute device identifier.
71///
72/// # Examples
73///
74/// ```rust
75/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind};
76///
77/// let device = DeviceId {
78///     kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
79///     ordinal: 0,
80/// };
81/// ```
82#[derive(Clone, Debug, PartialEq, Eq, Hash)]
83pub struct DeviceId {
84    pub kind: DeviceKind,
85    pub ordinal: usize,
86}
87
88/// Placement metadata for a tensor buffer.
89///
90/// # Examples
91///
92/// ```rust
93/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind, MemoryKind, Placement};
94///
95/// let placement = Placement {
96///     memory_kind: MemoryKind::Device,
97///     device: Some(DeviceId {
98///         kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
99///         ordinal: 0,
100///     }),
101/// };
102/// ```
103#[derive(Clone, Debug, PartialEq, Eq, Hash)]
104pub struct Placement {
105    pub memory_kind: MemoryKind,
106    pub device: Option<DeviceId>,
107}
108
109/// Backend-owned buffer handle.
110///
111/// `BufferHandle::new` creates an empty opaque handle. Use
112/// [`BufferHandle::new_with_len`] when test or adapter code needs to model a
113/// non-empty backend allocation.
114///
115/// # Examples
116///
117/// ```rust
118/// use tenferro_tensor::BufferHandle;
119///
120/// let handle = BufferHandle::<f64>::new(7);
121/// ```
122#[derive(Clone)]
123pub struct BufferHandle<T> {
124    id: u64,
125    len: usize,
126    _phantom: std::marker::PhantomData<T>,
127}
128
129impl<T> Debug for BufferHandle<T> {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        f.debug_struct("BufferHandle")
132            .field("id", &self.id)
133            .finish()
134    }
135}
136
137impl<T> BufferHandle<T> {
138    /// Create a new backend buffer handle.
139    ///
140    /// # Examples
141    ///
142    /// ```rust
143    /// use tenferro_tensor::BufferHandle;
144    ///
145    /// let handle = BufferHandle::<f64>::new(1);
146    /// assert_eq!(tenferro_tensor::BackendBuffer::len(&handle), 0);
147    /// ```
148    pub fn new(id: u64) -> Self {
149        Self::new_with_len(id, 0)
150    }
151
152    /// Create a new backend buffer handle with a logical element count.
153    ///
154    /// # Examples
155    ///
156    /// ```rust
157    /// use tenferro_tensor::{BackendBuffer, BufferHandle};
158    ///
159    /// let handle = BufferHandle::<f64>::new_with_len(1, 4);
160    /// assert_eq!(BackendBuffer::len(&handle), 4);
161    /// ```
162    pub fn new_with_len(id: u64, len: usize) -> Self {
163        Self {
164            id,
165            len,
166            _phantom: std::marker::PhantomData,
167        }
168    }
169}
170
171/// Opaque backend-owned tensor buffer.
172///
173/// Tensor core never inspects backend-native allocations directly. Backend
174/// crates store their own concrete handle types behind this trait and
175/// downcast inside the owning backend only.
176///
177/// # Examples
178///
179/// ```rust
180/// use std::sync::Arc;
181/// use tenferro_tensor::{BackendBuffer, BufferHandle};
182///
183/// let buffer: Arc<dyn BackendBuffer<f64>> = Arc::new(BufferHandle::<f64>::new_with_len(7, 2));
184/// assert_eq!(buffer.backend_family(), "opaque");
185/// assert_eq!(buffer.len(), 2);
186/// ```
187pub trait BackendBuffer<T>: Debug + Send + Sync + 'static {
188    /// Stable backend family identifier.
189    fn backend_family(&self) -> &'static str;
190
191    /// Number of logical elements in the backend allocation.
192    fn len(&self) -> usize;
193
194    /// Returns `true` when the backend allocation is empty.
195    fn is_empty(&self) -> bool {
196        self.len() == 0
197    }
198
199    /// Type-erased access for the backend crate that owns the concrete handle.
200    fn as_any(&self) -> &dyn Any;
201}
202
203impl<T: Send + Sync + 'static> BackendBuffer<T> for BufferHandle<T> {
204    fn backend_family(&self) -> &'static str {
205        "opaque"
206    }
207
208    fn len(&self) -> usize {
209        self.len
210    }
211
212    fn as_any(&self) -> &dyn Any {
213        self
214    }
215}
216
217/// Tensor storage.
218///
219/// # Examples
220///
221/// ```rust
222/// use tenferro_tensor::Buffer;
223///
224/// let host = Buffer::Host(vec![1.0_f64, 2.0]);
225/// ```
226#[derive(Clone, Debug)]
227pub enum Buffer<T> {
228    Host(Vec<T>),
229    Backend(Arc<dyn BackendBuffer<T>>),
230}
231
232impl<T: 'static> Buffer<T> {
233    /// Return the physical element count in this buffer.
234    ///
235    /// # Examples
236    ///
237    /// ```rust
238    /// use tenferro_tensor::Buffer;
239    ///
240    /// assert_eq!(Buffer::Host(vec![1_i32, 2]).len(), 2);
241    /// ```
242    pub fn len(&self) -> usize {
243        match self {
244            Self::Host(data) => data.len(),
245            Self::Backend(buffer) => buffer.len(),
246        }
247    }
248
249    /// Return whether this buffer has no physical elements.
250    ///
251    /// # Examples
252    ///
253    /// ```rust
254    /// use tenferro_tensor::Buffer;
255    ///
256    /// assert!(Buffer::<i32>::Host(Vec::new()).is_empty());
257    /// ```
258    pub fn is_empty(&self) -> bool {
259        self.len() == 0
260    }
261
262    /// Return whether the storage is backend-owned rather than host-owned.
263    ///
264    /// # Examples
265    ///
266    /// ```rust
267    /// use tenferro_tensor::Buffer;
268    ///
269    /// assert!(!Buffer::Host(vec![1_i32]).is_backend());
270    /// ```
271    pub fn is_backend(&self) -> bool {
272        matches!(self, Self::Backend(_))
273    }
274}
275
276/// Runtime typed tensor storage with compile-time scalar type and rank metadata.
277///
278/// Owned tensors are compact column-major. Arbitrary strides and metadata-only
279/// layout changes are represented by [`TypedTensorView`] and
280/// [`TypedTensorViewMut`]. The buffer may be host-backed or backend-backed;
281/// host-inspection methods do not download backend buffers implicitly.
282///
283/// # Examples
284///
285/// ```
286/// use tenferro_tensor::{Rank, Tensor, TypedTensor};
287///
288/// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
289/// assert_eq!(t.shape(), &[2, 2]);
290///
291/// let static_rank = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
292/// assert_eq!(static_rank.rank(), 2);
293///
294/// let dynamic = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64; 4]).unwrap();
295/// assert_eq!(dynamic.shape(), &[2, 2]);
296/// ```
297///
298/// The `R` parameter stores rank metadata. It defaults to dynamic rank
299/// (`DynRank`); use [`Rank<N>`](Rank) for compile-time rank validation.
300/// The dtype-erased [`Tensor`] enum remains dynamic-rank.
301#[derive(Clone, Debug)]
302pub struct TypedTensor<T, R: TensorRank = DynRank> {
303    buffer: Buffer<T>,
304    layout: TensorLayout<R>,
305    placement: Placement,
306}
307
308/// Borrowed tensor buffer reference used by read-only typed views.
309///
310/// # Examples
311///
312/// ```rust
313/// use tenferro_tensor::TensorBufferRef;
314///
315/// let data = [1_i32, 2];
316/// let buffer = TensorBufferRef::Host(&data);
317/// assert_eq!(buffer.len(), 2);
318/// ```
319#[derive(Debug)]
320pub enum TensorBufferRef<'a, T> {
321    Host(&'a [T]),
322    Backend(Arc<dyn BackendBuffer<T>>),
323}
324
325impl<T> Clone for TensorBufferRef<'_, T> {
326    fn clone(&self) -> Self {
327        match self {
328            Self::Host(data) => Self::Host(data),
329            Self::Backend(buffer) => Self::Backend(Arc::clone(buffer)),
330        }
331    }
332}
333
334impl<T: 'static> TensorBufferRef<'_, T> {
335    /// Return the logical length of the backing allocation.
336    ///
337    /// # Examples
338    ///
339    /// ```rust
340    /// use tenferro_tensor::TensorBufferRef;
341    ///
342    /// let data = [1_i32, 2, 3];
343    /// assert_eq!(TensorBufferRef::Host(&data).len(), 3);
344    /// ```
345    pub fn len(&self) -> usize {
346        match self {
347            Self::Host(data) => data.len(),
348            Self::Backend(buffer) => buffer.len(),
349        }
350    }
351
352    /// Return whether the backing allocation is empty.
353    ///
354    /// # Examples
355    ///
356    /// ```rust
357    /// use tenferro_tensor::TensorBufferRef;
358    ///
359    /// let data: [f64; 0] = [];
360    /// assert!(TensorBufferRef::Host(&data).is_empty());
361    /// ```
362    pub fn is_empty(&self) -> bool {
363        self.len() == 0
364    }
365}
366
367/// Borrowed tensor buffer reference used by mutable typed views.
368///
369/// Backend buffers can be represented for residency metadata, but this crate
370/// does not expose host mutation for backend-native allocations.
371///
372/// # Examples
373///
374/// ```rust
375/// use tenferro_tensor::TensorBufferRefMut;
376///
377/// let mut data = [1_i32, 2];
378/// let buffer = TensorBufferRefMut::Host(&mut data);
379/// assert_eq!(buffer.len(), 2);
380/// ```
381#[derive(Debug)]
382pub enum TensorBufferRefMut<'a, T> {
383    Host(&'a mut [T]),
384    Backend(Arc<dyn BackendBuffer<T>>),
385}
386
387impl<T: 'static> TensorBufferRefMut<'_, T> {
388    /// Return the logical length of the backing allocation.
389    ///
390    /// # Examples
391    ///
392    /// ```rust
393    /// use tenferro_tensor::TensorBufferRefMut;
394    ///
395    /// let mut data = [1_i32, 2, 3];
396    /// assert_eq!(TensorBufferRefMut::Host(&mut data).len(), 3);
397    /// ```
398    pub fn len(&self) -> usize {
399        match self {
400            Self::Host(data) => data.len(),
401            Self::Backend(buffer) => buffer.len(),
402        }
403    }
404
405    /// Return whether the backing allocation is empty.
406    ///
407    /// # Examples
408    ///
409    /// ```rust
410    /// use tenferro_tensor::TensorBufferRefMut;
411    ///
412    /// let mut data: [f64; 0] = [];
413    /// assert!(TensorBufferRefMut::Host(&mut data).is_empty());
414    /// ```
415    pub fn is_empty(&self) -> bool {
416        self.len() == 0
417    }
418}
419
420/// Read-only borrowed view of typed tensor storage with arbitrary strides.
421///
422/// `TypedTensorView` is the typed representation for layout-only tensor
423/// transformations. It borrows an existing host or backend allocation and
424/// carries a logical shape, strides, and an offset. Slicing, reshaping when
425/// stride-compatible, and [`transpose_view`](TypedTensorView::transpose_view)
426/// update only metadata and do not copy storage.
427///
428/// Use [`TypedTensorView::to_contiguous`] when a compact owned
429/// [`TypedTensor`] is required. Use [`TypedTensorView::as_slice`] only when the
430/// current view is contiguous in the requested layout.
431///
432/// # Examples
433///
434/// ```rust
435/// use tenferro_tensor::{Rank, TypedTensorView};
436///
437/// let data = [1_i32, 2, 3, 4];
438/// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
439/// assert_eq!(view.get(&[1, 1]), Some(&4));
440/// # Ok::<(), tenferro_tensor::Error>(())
441/// ```
442#[derive(Clone, Debug)]
443pub struct TypedTensorView<'a, T, R: TensorRank = DynRank> {
444    buffer: TensorBufferRef<'a, T>,
445    layout: TensorLayout<R>,
446    placement: Placement,
447}
448
449impl<'a, T: 'static> TypedTensorView<'a, T, DynRank> {
450    /// Create a borrowed dynamic-rank view over compact column-major host data.
451    ///
452    /// # Examples
453    ///
454    /// ```rust
455    /// use tenferro_tensor::TypedTensorView;
456    ///
457    /// let data = [1_i32, 2, 3, 4];
458    /// let view = TypedTensorView::from_col_major(&[2, 2], &data)?;
459    /// assert_eq!(view.strides(), &[1, 2]);
460    /// # Ok::<(), tenferro_tensor::Error>(())
461    /// ```
462    pub fn from_col_major(shape: &[usize], data: &'a [T]) -> crate::Result<Self> {
463        let layout = TensorLayout::<DynRank>::compact(shape.to_vec().into())
464            .map_err(|err| tensor_layout_error("TypedTensorView::from_col_major", err))?;
465        Self::from_buffer_ref(
466            layout.shape().to_vec(),
467            layout.strides().to_vec(),
468            layout.offset(),
469            TensorBufferRef::Host(data),
470            default_placement(),
471            "TypedTensorView::from_col_major",
472        )
473    }
474
475    /// Create a borrowed host view from explicit layout metadata.
476    ///
477    /// # Examples
478    ///
479    /// ```rust
480    /// use tenferro_tensor::TypedTensorView;
481    ///
482    /// let data = [1_i32, 2, 3];
483    /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
484    /// assert_eq!(view.get(&[2]), Some(&1));
485    /// # Ok::<(), tenferro_tensor::Error>(())
486    /// ```
487    pub fn from_slice(
488        shape: impl AsRef<[usize]>,
489        strides: impl AsRef<[isize]>,
490        offset: isize,
491        data: &'a [T],
492    ) -> crate::Result<Self> {
493        Self::from_buffer_ref(
494            shape.as_ref().to_vec(),
495            strides.as_ref().to_vec(),
496            offset,
497            TensorBufferRef::Host(data),
498            default_placement(),
499            "TypedTensorView::from_slice",
500        )
501    }
502}
503
504impl<'a, T: 'static, R: TensorRank> TypedTensorView<'a, T, R> {
505    /// Create a rank-generic borrowed host view from explicit layout metadata.
506    ///
507    /// # Examples
508    ///
509    /// ```rust
510    /// use tenferro_tensor::{Rank, TypedTensorView};
511    ///
512    /// let data = [1_i32, 2, 3, 4];
513    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
514    /// assert_eq!(view.get(&[1, 1]), Some(&4));
515    /// # Ok::<(), tenferro_tensor::Error>(())
516    /// ```
517    pub fn from_slice_ranked(
518        shape: impl Into<R::Shape>,
519        strides: impl Into<R::Strides>,
520        offset: isize,
521        data: &'a [T],
522    ) -> crate::Result<Self> {
523        Self::from_buffer_ref(
524            shape,
525            strides,
526            offset,
527            TensorBufferRef::Host(data),
528            default_placement(),
529            "TypedTensorView::from_slice_ranked",
530        )
531    }
532
533    fn from_buffer_ref(
534        shape: impl Into<R::Shape>,
535        strides: impl Into<R::Strides>,
536        offset: isize,
537        buffer: TensorBufferRef<'a, T>,
538        placement: Placement,
539        op: &'static str,
540    ) -> crate::Result<Self> {
541        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
542            .map_err(|err| tensor_layout_error(op, err))?;
543        Ok(Self {
544            buffer,
545            layout,
546            placement,
547        })
548    }
549
550    /// Return the logical shape.
551    ///
552    /// # Examples
553    ///
554    /// ```rust
555    /// use tenferro_tensor::TypedTensorView;
556    ///
557    /// let data = [0_i32; 2];
558    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
559    /// assert_eq!(view.shape(), &[2]);
560    /// # Ok::<(), tenferro_tensor::Error>(())
561    /// ```
562    pub fn shape(&self) -> &[usize] {
563        self.layout.shape()
564    }
565
566    /// Return strides in element units.
567    ///
568    /// # Examples
569    ///
570    /// ```rust
571    /// use tenferro_tensor::TypedTensorView;
572    ///
573    /// let data = [0_i32; 2];
574    /// let view = TypedTensorView::from_slice(vec![2], vec![-1], 1, &data)?;
575    /// assert_eq!(view.strides(), &[-1]);
576    /// # Ok::<(), tenferro_tensor::Error>(())
577    /// ```
578    pub fn strides(&self) -> &[isize] {
579        self.layout.strides()
580    }
581
582    /// Return the physical element offset.
583    ///
584    /// # Examples
585    ///
586    /// ```rust
587    /// use tenferro_tensor::TypedTensorView;
588    ///
589    /// let data = [1_i32, 2];
590    /// let view = TypedTensorView::from_slice(vec![1], vec![1], 1, &data)?;
591    /// assert_eq!(view.offset(), 1);
592    /// # Ok::<(), tenferro_tensor::Error>(())
593    /// ```
594    pub fn offset(&self) -> isize {
595        self.layout.offset()
596    }
597
598    /// Return the borrowed host storage backing this view.
599    ///
600    /// This exposes the entire backing host allocation, not just the logical
601    /// slice covered by this view. Use [`TypedTensorView::as_slice`] when the
602    /// caller needs the contiguous logical region instead.
603    ///
604    /// # Examples
605    ///
606    /// ```rust
607    /// use tenferro_tensor::TypedTensorView;
608    ///
609    /// let data = [1_i32, 2];
610    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
611    /// assert_eq!(view.host_storage()?, &[1, 2]);
612    /// # Ok::<(), tenferro_tensor::Error>(())
613    /// ```
614    pub fn host_storage(&self) -> crate::Result<&'a [T]> {
615        match &self.buffer {
616            TensorBufferRef::Host(data) => Ok(data),
617            TensorBufferRef::Backend(_) => Err(crate::Error::backend_failure(
618                "TypedTensorView::host_storage",
619                "backend buffers cannot expose host storage; download explicitly first",
620            )),
621        }
622    }
623
624    /// Return the number of logical elements in this view.
625    ///
626    /// # Examples
627    ///
628    /// ```rust
629    /// use tenferro_tensor::TypedTensorView;
630    ///
631    /// let data = [0_i32; 6];
632    /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
633    /// assert_eq!(view.n_elements(), 6);
634    /// # Ok::<(), tenferro_tensor::Error>(())
635    /// ```
636    pub fn n_elements(&self) -> usize {
637        // Invariant: public view constructors validate logical element count.
638        match checked_view_element_count(self.shape(), "TypedTensorView::n_elements") {
639            Ok(n) => n,
640            Err(err) => {
641                unreachable!("TypedTensorView layout shape is validated at construction: {err}")
642            }
643        }
644    }
645
646    /// Return layout metadata for this view.
647    ///
648    /// # Examples
649    ///
650    /// ```rust
651    /// use tenferro_tensor::TypedTensorView;
652    ///
653    /// let data = [1_i32, 2];
654    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
655    /// assert!(view.layout().is_compact_col_major().unwrap());
656    /// # Ok::<(), tenferro_tensor::Error>(())
657    /// ```
658    pub fn layout(&self) -> &TensorLayout<R> {
659        &self.layout
660    }
661
662    /// Return placement metadata for this view.
663    ///
664    /// # Examples
665    ///
666    /// ```rust
667    /// use tenferro_tensor::{MemoryKind, TypedTensorView};
668    ///
669    /// let data = [1_i32];
670    /// let view = TypedTensorView::from_slice(vec![1], vec![1], 0, &data)?;
671    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
672    /// # Ok::<(), tenferro_tensor::Error>(())
673    /// ```
674    pub fn placement(&self) -> &Placement {
675        &self.placement
676    }
677
678    /// Return the backend allocation for backend integrations.
679    #[doc(hidden)]
680    pub fn backend_buffer(&self) -> Option<&Arc<dyn BackendBuffer<T>>> {
681        match &self.buffer {
682            TensorBufferRef::Host(_) => None,
683            TensorBufferRef::Backend(buffer) => Some(buffer),
684        }
685    }
686
687    /// Compute the physical element offset for a logical index.
688    ///
689    /// # Examples
690    ///
691    /// ```rust
692    /// use tenferro_tensor::TypedTensorView;
693    ///
694    /// let data = [1_i32, 2, 3];
695    /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
696    /// assert_eq!(view.linear_offset(&[2]), Some(0));
697    /// # Ok::<(), tenferro_tensor::Error>(())
698    /// ```
699    pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
700        checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
701    }
702
703    /// Compute the physical element offset for a logical index, returning a typed error.
704    ///
705    /// # Examples
706    ///
707    /// ```rust
708    /// use tenferro_tensor::TypedTensorView;
709    ///
710    /// let data = [1_i32, 2, 3];
711    /// let view = TypedTensorView::from_slice([3], [-1], 2, &data)?;
712    /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
713    /// # Ok::<(), tenferro_tensor::Error>(())
714    /// ```
715    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
716        checked_view_offset_result(
717            self.shape(),
718            self.strides(),
719            self.offset(),
720            indices,
721            "TypedTensorView::layout_linear_offset",
722        )
723    }
724
725    /// Return whether this view is compact column-major.
726    ///
727    /// # Examples
728    ///
729    /// ```rust
730    /// use tenferro_tensor::TypedTensorView;
731    ///
732    /// let data = [1_i32, 2];
733    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
734    /// assert!(view.is_col_major_contiguous()?);
735    /// # Ok::<(), tenferro_tensor::Error>(())
736    /// ```
737    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
738        self.layout
739            .is_compact_col_major()
740            .map_err(|err| tensor_layout_error("TypedTensorView::is_col_major_contiguous", err))
741    }
742
743    /// Return a compact string summary of this view's layout metadata.
744    ///
745    /// # Examples
746    ///
747    /// ```rust
748    /// use tenferro_tensor::TypedTensorView;
749    ///
750    /// let data = [1_i32, 2];
751    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
752    /// assert!(view.layout_summary().contains("shape=[2]"));
753    /// # Ok::<(), tenferro_tensor::Error>(())
754    /// ```
755    pub fn layout_summary(&self) -> String {
756        layout_summary(self.shape(), self.strides(), self.offset())
757    }
758
759    /// Assert this view is compact column-major.
760    ///
761    /// # Examples
762    ///
763    /// ```rust
764    /// use tenferro_tensor::TypedTensorView;
765    ///
766    /// let data = [1_i32, 2];
767    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
768    /// view.assert_col_major_contiguous()?;
769    /// # Ok::<(), tenferro_tensor::Error>(())
770    /// ```
771    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
772        assert_layout_col_major_contiguous(
773            self.is_col_major_contiguous()?,
774            self.shape(),
775            self.strides(),
776            self.offset(),
777            "TypedTensorView::assert_col_major_contiguous",
778        )
779    }
780
781    /// Borrow one host element by logical index.
782    ///
783    /// Returns `None` for out-of-bounds indices and backend buffers.
784    ///
785    /// # Examples
786    ///
787    /// ```rust
788    /// use tenferro_tensor::TypedTensorView;
789    ///
790    /// let data = [1_i32, 2];
791    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
792    /// assert_eq!(view.get(&[1]), Some(&2));
793    /// # Ok::<(), tenferro_tensor::Error>(())
794    /// ```
795    pub fn get(&self, indices: &[usize]) -> Option<&T> {
796        let offset = self.linear_offset(indices)?;
797        match &self.buffer {
798            TensorBufferRef::Host(data) => data.get(offset),
799            TensorBufferRef::Backend(_) => None,
800        }
801    }
802
803    /// Borrow the contiguous host slice covered by this view.
804    ///
805    /// Returns an explicit error for backend buffers and for non-contiguous
806    /// layouts. This method never downloads or materializes backend data.
807    ///
808    /// # Examples
809    ///
810    /// ```rust
811    /// use tenferro_tensor::TypedTensorView;
812    ///
813    /// let data = [1_i32, 2, 3];
814    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?;
815    /// assert_eq!(view.as_slice()?, &[2, 3]);
816    /// # Ok::<(), tenferro_tensor::Error>(())
817    /// ```
818    pub fn as_slice(&self) -> crate::Result<&'a [T]> {
819        let data =
820            match &self.buffer {
821                TensorBufferRef::Host(data) => data,
822                TensorBufferRef::Backend(_) => return Err(crate::Error::backend_failure(
823                    "TypedTensorView::as_slice",
824                    "backend buffers cannot be inspected as host slices; download explicitly first",
825                )),
826            };
827        contiguous_layout_slice(self.layout(), data, "TypedTensorView::as_slice")
828    }
829
830    /// Materialize this view as compact column-major host tensor storage.
831    ///
832    /// This is an explicit same-placement copy boundary. Host placement
833    /// metadata is preserved on the materialized tensor. Backend buffers return
834    /// an error here instead of being downloaded implicitly; backend-specific
835    /// compacting paths must stay on that backend.
836    ///
837    /// # Examples
838    ///
839    /// ```rust
840    /// use tenferro_tensor::{Rank, TypedTensor};
841    ///
842    /// let tensor = TypedTensor::<i32, Rank<2>>::from_vec_col_major([2, 2], vec![1, 2, 3, 4]).unwrap();
843    /// let transposed = tensor.as_view().transpose_view([1, 0])?;
844    /// let compact = transposed.to_contiguous()?;
845    /// assert_eq!(compact.as_slice()?, &[1, 3, 2, 4]);
846    /// # Ok::<(), tenferro_tensor::Error>(())
847    /// ```
848    pub fn to_contiguous(&self) -> crate::Result<TypedTensor<T, R>>
849    where
850        T: Clone,
851    {
852        let op = "TypedTensorView::to_contiguous";
853        let data = materialize_view_buffer_col_major(
854            self.shape(),
855            self.strides(),
856            self.offset(),
857            &self.buffer,
858            op,
859        )?;
860        let shape = R::shape_from_vec(self.shape().to_vec().into())
861            .map_err(|err| tensor_layout_error(op, err))?;
862        TypedTensor::from_buffer_col_major(shape, Buffer::Host(data), self.placement.clone())
863    }
864
865    /// Return a metadata-only axis permutation.
866    ///
867    /// # Examples
868    ///
869    /// ```rust
870    /// use tenferro_tensor::{Rank, TypedTensorView};
871    ///
872    /// let data = [1_i32, 2, 3, 4, 5, 6];
873    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 3], [1, 2], 0, &data)?;
874    /// let transposed = view.transpose_view([1, 0])?;
875    /// assert_eq!(transposed.shape(), &[3, 2]);
876    /// # Ok::<(), tenferro_tensor::Error>(())
877    /// ```
878    pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
879        let layout = self
880            .layout
881            .transpose_view(axes)
882            .map_err(|err| tensor_layout_error("TypedTensorView::transpose_view", err))?;
883        Ok(Self {
884            buffer: self.buffer.clone(),
885            layout,
886            placement: self.placement.clone(),
887        })
888    }
889
890    /// Return a metadata-only slice using one [`StridedSliceSpec`] per axis.
891    ///
892    /// # Examples
893    ///
894    /// ```rust
895    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
896    ///
897    /// let data = [1_i32, 2, 3];
898    /// let view = TypedTensorView::from_slice(vec![3], vec![1], 0, &data)?;
899    /// let reversed = view.try_slice(&[StridedSliceSpec::reverse()])?;
900    /// assert_eq!(reversed.get(&[0]), Some(&3));
901    /// # Ok::<(), tenferro_tensor::Error>(())
902    /// ```
903    pub fn try_slice(&self, slices: &[StridedSliceSpec]) -> crate::Result<Self> {
904        let specs = core_slice_specs(slices, self.shape(), "TypedTensorView::try_slice")?;
905        let layout = self
906            .layout
907            .slice_view(specs, self.buffer.len())
908            .map_err(|err| tensor_layout_error("TypedTensorView::try_slice", err))?;
909        Ok(Self {
910            buffer: self.buffer.clone(),
911            layout,
912            placement: self.placement.clone(),
913        })
914    }
915
916    /// Return a metadata-only slice along one axis.
917    ///
918    /// # Examples
919    ///
920    /// ```rust
921    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
922    ///
923    /// let data = [1_i32, 2, 3, 4];
924    /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
925    /// assert_eq!(view.try_slice_axis(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
926    /// # Ok::<(), tenferro_tensor::Error>(())
927    /// ```
928    pub fn try_slice_axis(&self, axis: usize, slice: StridedSliceSpec) -> crate::Result<Self> {
929        let slices = slice_axis_specs(
930            self.shape().len(),
931            axis,
932            slice,
933            "TypedTensorView::try_slice_axis",
934        )?;
935        self.try_slice(&slices)
936    }
937
938    /// Return a metadata-only dynamic-rank reshape for contiguous column-major views.
939    ///
940    /// # Examples
941    ///
942    /// ```rust
943    /// use tenferro_tensor::TypedTensorView;
944    ///
945    /// let data = [1_i32, 2, 3, 4];
946    /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
947    /// assert_eq!(view.try_reshape(&[4])?.shape(), &[4]);
948    /// # Ok::<(), tenferro_tensor::Error>(())
949    /// ```
950    pub fn try_reshape(&self, shape: &[usize]) -> crate::Result<TypedTensorView<'a, T, DynRank>> {
951        let layout = reshape_layout_dyn(
952            &self.layout,
953            shape,
954            self.buffer.len(),
955            "TypedTensorView::try_reshape",
956        )?;
957        Ok(TypedTensorView {
958            buffer: self.buffer.clone(),
959            layout,
960            placement: self.placement.clone(),
961        })
962    }
963}
964
965/// Mutable borrowed view of typed tensor storage with arbitrary strides.
966///
967/// # Examples
968///
969/// ```rust
970/// use tenferro_tensor::TypedTensorViewMut;
971///
972/// let mut data = [1_i32, 2, 3];
973/// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
974/// *view.get_mut(&[2]).unwrap() = 10;
975/// assert_eq!(view.as_read_only().get(&[2]), Some(&10));
976/// # Ok::<(), tenferro_tensor::Error>(())
977/// ```
978#[derive(Debug)]
979pub struct TypedTensorViewMut<'a, T, R: TensorRank = DynRank> {
980    buffer: TensorBufferRefMut<'a, T>,
981    layout: TensorLayout<R>,
982    placement: Placement,
983}
984
985/// Pair of mutable tensor views returned by disjoint multi-slice operations.
986///
987/// # Examples
988///
989/// ```rust
990/// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut, TypedTensorViewMutPair};
991///
992/// let mut data = [1_i32, 2, 3, 4];
993/// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
994/// let pair: TypedTensorViewMutPair<'_, i32> = view
995///     .try_multi_slice_mut(
996///         &[StridedSliceSpec::new(0, Some(2), 1)],
997///         &[StridedSliceSpec::new(2, Some(4), 1)],
998///     )
999///     ?
1000///     .unwrap();
1001/// assert_eq!(pair.0.shape(), &[2]);
1002/// assert_eq!(pair.1.shape(), &[2]);
1003/// # Ok::<(), tenferro_tensor::Error>(())
1004/// ```
1005pub type TypedTensorViewMutPair<'a, T, R = DynRank> =
1006    (TypedTensorViewMut<'a, T, R>, TypedTensorViewMut<'a, T, R>);
1007
1008impl<'a, T: 'static> TypedTensorViewMut<'a, T, DynRank> {
1009    /// Create a mutable dynamic-rank view over compact column-major host data.
1010    ///
1011    /// # Examples
1012    ///
1013    /// ```rust
1014    /// use tenferro_tensor::TypedTensorViewMut;
1015    ///
1016    /// let mut data = [1_i32, 2, 3, 4];
1017    /// let view = TypedTensorViewMut::from_col_major(&[2, 2], &mut data)?;
1018    /// assert_eq!(view.strides(), &[1, 2]);
1019    /// # Ok::<(), tenferro_tensor::Error>(())
1020    /// ```
1021    pub fn from_col_major(shape: &[usize], data: &'a mut [T]) -> crate::Result<Self> {
1022        let layout = TensorLayout::<DynRank>::compact(shape.to_vec().into())
1023            .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_col_major", err))?;
1024        Self::from_buffer_ref_mut(
1025            layout.shape().to_vec(),
1026            layout.strides().to_vec(),
1027            layout.offset(),
1028            TensorBufferRefMut::Host(data),
1029            default_placement(),
1030            "TypedTensorViewMut::from_col_major",
1031        )
1032    }
1033
1034    /// Create a mutable host view from explicit layout metadata.
1035    ///
1036    /// Layouts where distinct logical elements can alias the same physical
1037    /// element are rejected.
1038    ///
1039    /// # Examples
1040    ///
1041    /// ```rust
1042    /// use tenferro_tensor::TypedTensorViewMut;
1043    ///
1044    /// let mut data = [1_i32, 2];
1045    /// assert!(TypedTensorViewMut::from_slice(vec![2], vec![0], 0, &mut data).is_err());
1046    /// ```
1047    pub fn from_slice(
1048        shape: impl AsRef<[usize]>,
1049        strides: impl AsRef<[isize]>,
1050        offset: isize,
1051        data: &'a mut [T],
1052    ) -> crate::Result<Self> {
1053        Self::from_buffer_ref_mut(
1054            shape.as_ref().to_vec(),
1055            strides.as_ref().to_vec(),
1056            offset,
1057            TensorBufferRefMut::Host(data),
1058            default_placement(),
1059            "TypedTensorViewMut::from_slice",
1060        )
1061    }
1062}
1063
1064impl<'a, T: 'static, R: TensorRank> TypedTensorViewMut<'a, T, R> {
1065    /// Create a rank-generic mutable host view from explicit layout metadata.
1066    ///
1067    /// # Examples
1068    ///
1069    /// ```rust
1070    /// use tenferro_tensor::{Rank, TypedTensorViewMut};
1071    ///
1072    /// let mut data = [1_i32, 2, 3, 4];
1073    /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
1074    /// assert_eq!(view.shape(), &[2, 2]);
1075    /// # Ok::<(), tenferro_tensor::Error>(())
1076    /// ```
1077    pub fn from_slice_ranked(
1078        shape: impl Into<R::Shape>,
1079        strides: impl Into<R::Strides>,
1080        offset: isize,
1081        data: &'a mut [T],
1082    ) -> crate::Result<Self> {
1083        Self::from_buffer_ref_mut(
1084            shape,
1085            strides,
1086            offset,
1087            TensorBufferRefMut::Host(data),
1088            default_placement(),
1089            "TypedTensorViewMut::from_slice_ranked",
1090        )
1091    }
1092
1093    fn from_buffer_ref_mut(
1094        shape: impl Into<R::Shape>,
1095        strides: impl Into<R::Strides>,
1096        offset: isize,
1097        buffer: TensorBufferRefMut<'a, T>,
1098        placement: Placement,
1099        op: &'static str,
1100    ) -> crate::Result<Self> {
1101        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
1102            .map_err(|err| tensor_layout_error(op, err))?;
1103        layout
1104            .validate_mutable_no_overlap()
1105            .map_err(|err| tensor_layout_error(op, err))?;
1106        Ok(Self {
1107            buffer,
1108            layout,
1109            placement,
1110        })
1111    }
1112
1113    /// Return the logical shape.
1114    ///
1115    /// # Examples
1116    ///
1117    /// ```rust
1118    /// use tenferro_tensor::TypedTensorViewMut;
1119    ///
1120    /// let mut data = [0_i32; 2];
1121    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1122    /// assert_eq!(view.shape(), &[2]);
1123    /// # Ok::<(), tenferro_tensor::Error>(())
1124    /// ```
1125    pub fn shape(&self) -> &[usize] {
1126        self.layout.shape()
1127    }
1128
1129    /// Return strides in element units.
1130    ///
1131    /// # Examples
1132    ///
1133    /// ```rust
1134    /// use tenferro_tensor::TypedTensorViewMut;
1135    ///
1136    /// let mut data = [0_i32; 2];
1137    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![-1], 1, &mut data)?;
1138    /// assert_eq!(view.strides(), &[-1]);
1139    /// # Ok::<(), tenferro_tensor::Error>(())
1140    /// ```
1141    pub fn strides(&self) -> &[isize] {
1142        self.layout.strides()
1143    }
1144
1145    /// Return the physical element offset.
1146    ///
1147    /// # Examples
1148    ///
1149    /// ```rust
1150    /// use tenferro_tensor::TypedTensorViewMut;
1151    ///
1152    /// let mut data = [1_i32, 2];
1153    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 1, &mut data)?;
1154    /// assert_eq!(view.offset(), 1);
1155    /// # Ok::<(), tenferro_tensor::Error>(())
1156    /// ```
1157    pub fn offset(&self) -> isize {
1158        self.layout.offset()
1159    }
1160
1161    /// Return the borrowed host storage backing this view.
1162    ///
1163    /// This exposes the entire backing host allocation, not just the logical
1164    /// slice covered by this view. Use [`TypedTensorViewMut::as_read_only`]
1165    /// with [`TypedTensorView::as_slice`] when the caller needs the contiguous
1166    /// logical region instead.
1167    ///
1168    /// # Examples
1169    ///
1170    /// ```rust
1171    /// use tenferro_tensor::TypedTensorViewMut;
1172    ///
1173    /// let mut data = [1_i32, 2];
1174    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1175    /// assert_eq!(view.host_storage()?, &[1, 2]);
1176    /// # Ok::<(), tenferro_tensor::Error>(())
1177    /// ```
1178    pub fn host_storage(&self) -> crate::Result<&[T]> {
1179        match &self.buffer {
1180            TensorBufferRefMut::Host(data) => Ok(data),
1181            TensorBufferRefMut::Backend(_) => Err(crate::Error::backend_failure(
1182                "TypedTensorViewMut::host_storage",
1183                "backend buffers cannot expose host storage; download explicitly first",
1184            )),
1185        }
1186    }
1187
1188    /// Mutably borrow the host storage backing this view.
1189    ///
1190    /// This exposes the entire backing host allocation, not just the logical
1191    /// slice covered by this view. Use [`TypedTensorViewMut::copy_from_contiguous`]
1192    /// or element accessors when mutating the logical region instead.
1193    ///
1194    /// # Examples
1195    ///
1196    /// ```rust
1197    /// use tenferro_tensor::TypedTensorViewMut;
1198    ///
1199    /// let mut data = [1_i32, 2];
1200    /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1201    /// view.host_storage_mut()?[0] = 3;
1202    /// assert_eq!(view.get(&[0]), Some(&3));
1203    /// # Ok::<(), tenferro_tensor::Error>(())
1204    /// ```
1205    pub fn host_storage_mut(&mut self) -> crate::Result<&mut [T]> {
1206        match &mut self.buffer {
1207            TensorBufferRefMut::Host(data) => Ok(data),
1208            TensorBufferRefMut::Backend(_) => Err(crate::Error::backend_failure(
1209                "TypedTensorViewMut::host_storage_mut",
1210                "backend buffers cannot expose mutable host storage; download explicitly first",
1211            )),
1212        }
1213    }
1214
1215    /// Return the number of logical elements in this view.
1216    ///
1217    /// # Examples
1218    ///
1219    /// ```rust
1220    /// use tenferro_tensor::TypedTensorViewMut;
1221    ///
1222    /// let mut data = [0_i32; 6];
1223    /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
1224    /// assert_eq!(view.n_elements(), 6);
1225    /// # Ok::<(), tenferro_tensor::Error>(())
1226    /// ```
1227    pub fn n_elements(&self) -> usize {
1228        // Invariant: public mutable view constructors validate logical element count.
1229        match checked_view_element_count(self.shape(), "TypedTensorViewMut::n_elements") {
1230            Ok(n) => n,
1231            Err(err) => {
1232                unreachable!("TypedTensorViewMut layout shape is validated at construction: {err}")
1233            }
1234        }
1235    }
1236
1237    /// Return layout metadata for this view.
1238    ///
1239    /// # Examples
1240    ///
1241    /// ```rust
1242    /// use tenferro_tensor::TypedTensorViewMut;
1243    ///
1244    /// let mut data = [1_i32, 2];
1245    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1246    /// assert!(view.layout().is_compact_col_major().unwrap());
1247    /// # Ok::<(), tenferro_tensor::Error>(())
1248    /// ```
1249    pub fn layout(&self) -> &TensorLayout<R> {
1250        &self.layout
1251    }
1252
1253    /// Return placement metadata for this view.
1254    ///
1255    /// # Examples
1256    ///
1257    /// ```rust
1258    /// use tenferro_tensor::{MemoryKind, TypedTensorViewMut};
1259    ///
1260    /// let mut data = [1_i32];
1261    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1262    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
1263    /// # Ok::<(), tenferro_tensor::Error>(())
1264    /// ```
1265    pub fn placement(&self) -> &Placement {
1266        &self.placement
1267    }
1268
1269    /// Return the backend allocation for backend integrations.
1270    #[doc(hidden)]
1271    pub fn backend_buffer(&self) -> Option<&Arc<dyn BackendBuffer<T>>> {
1272        match &self.buffer {
1273            TensorBufferRefMut::Host(_) => None,
1274            TensorBufferRefMut::Backend(buffer) => Some(buffer),
1275        }
1276    }
1277
1278    /// Compute the physical element offset for a logical index.
1279    ///
1280    /// # Examples
1281    ///
1282    /// ```rust
1283    /// use tenferro_tensor::TypedTensorViewMut;
1284    ///
1285    /// let mut data = [1_i32, 2, 3];
1286    /// let view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
1287    /// assert_eq!(view.linear_offset(&[2]), Some(0));
1288    /// # Ok::<(), tenferro_tensor::Error>(())
1289    /// ```
1290    pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
1291        checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
1292    }
1293
1294    /// Compute the physical element offset for a logical index, returning a typed error.
1295    ///
1296    /// # Examples
1297    ///
1298    /// ```rust
1299    /// use tenferro_tensor::TypedTensorViewMut;
1300    ///
1301    /// let mut data = [1_i32, 2, 3];
1302    /// let view = TypedTensorViewMut::from_slice([3], [-1], 2, &mut data)?;
1303    /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
1304    /// # Ok::<(), tenferro_tensor::Error>(())
1305    /// ```
1306    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
1307        checked_view_offset_result(
1308            self.shape(),
1309            self.strides(),
1310            self.offset(),
1311            indices,
1312            "TypedTensorViewMut::layout_linear_offset",
1313        )
1314    }
1315
1316    /// Return whether this mutable view is compact column-major.
1317    ///
1318    /// # Examples
1319    ///
1320    /// ```rust
1321    /// use tenferro_tensor::TypedTensorViewMut;
1322    ///
1323    /// let mut data = [1_i32, 2];
1324    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
1325    /// assert!(view.is_col_major_contiguous()?);
1326    /// # Ok::<(), tenferro_tensor::Error>(())
1327    /// ```
1328    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
1329        self.layout
1330            .is_compact_col_major()
1331            .map_err(|err| tensor_layout_error("TypedTensorViewMut::is_col_major_contiguous", err))
1332    }
1333
1334    /// Return a compact string summary of this mutable view's layout metadata.
1335    ///
1336    /// # Examples
1337    ///
1338    /// ```rust
1339    /// use tenferro_tensor::TypedTensorViewMut;
1340    ///
1341    /// let mut data = [1_i32, 2];
1342    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
1343    /// assert!(view.layout_summary().contains("shape=[2]"));
1344    /// # Ok::<(), tenferro_tensor::Error>(())
1345    /// ```
1346    pub fn layout_summary(&self) -> String {
1347        layout_summary(self.shape(), self.strides(), self.offset())
1348    }
1349
1350    /// Assert this mutable view is compact column-major.
1351    ///
1352    /// # Examples
1353    ///
1354    /// ```rust
1355    /// use tenferro_tensor::TypedTensorViewMut;
1356    ///
1357    /// let mut data = [1_i32, 2];
1358    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
1359    /// view.assert_col_major_contiguous()?;
1360    /// # Ok::<(), tenferro_tensor::Error>(())
1361    /// ```
1362    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
1363        assert_layout_col_major_contiguous(
1364            self.is_col_major_contiguous()?,
1365            self.shape(),
1366            self.strides(),
1367            self.offset(),
1368            "TypedTensorViewMut::assert_col_major_contiguous",
1369        )
1370    }
1371
1372    /// Borrow one host element by logical index.
1373    ///
1374    /// # Examples
1375    ///
1376    /// ```rust
1377    /// use tenferro_tensor::TypedTensorViewMut;
1378    ///
1379    /// let mut data = [1_i32, 2];
1380    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1381    /// assert_eq!(view.get(&[1]), Some(&2));
1382    /// # Ok::<(), tenferro_tensor::Error>(())
1383    /// ```
1384    pub fn get(&self, indices: &[usize]) -> Option<&T> {
1385        let offset = self.linear_offset(indices)?;
1386        match &self.buffer {
1387            TensorBufferRefMut::Host(data) => data.get(offset),
1388            TensorBufferRefMut::Backend(_) => None,
1389        }
1390    }
1391
1392    /// Mutably borrow one host element by logical index.
1393    ///
1394    /// # Examples
1395    ///
1396    /// ```rust
1397    /// use tenferro_tensor::TypedTensorViewMut;
1398    ///
1399    /// let mut data = [1_i32, 2];
1400    /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1401    /// *view.get_mut(&[1]).unwrap() = 20;
1402    /// assert_eq!(view.get(&[1]), Some(&20));
1403    /// # Ok::<(), tenferro_tensor::Error>(())
1404    /// ```
1405    pub fn get_mut(&mut self, indices: &[usize]) -> Option<&mut T> {
1406        let offset = self.linear_offset(indices)?;
1407        match &mut self.buffer {
1408            TensorBufferRefMut::Host(data) => data.get_mut(offset),
1409            TensorBufferRefMut::Backend(_) => None,
1410        }
1411    }
1412
1413    /// Copy compact column-major host tensor values into this mutable view.
1414    ///
1415    /// This is an explicit copy-back boundary. Backend source or destination
1416    /// buffers return an error instead of transferring data implicitly.
1417    ///
1418    /// # Examples
1419    ///
1420    /// ```rust
1421    /// use tenferro_tensor::{Rank, TypedTensor};
1422    ///
1423    /// let mut tensor = TypedTensor::<i32, Rank<2>>::from_vec_col_major([2, 2], vec![0, 0, 0, 0]).unwrap();
1424    /// let src = TypedTensor::<i32, Rank<2>>::from_vec_col_major([2, 2], vec![1, 2, 3, 4]).unwrap();
1425    /// tensor.as_view_mut().transpose_view([1, 0])?.copy_from_contiguous(&src)?;
1426    /// assert_eq!(tensor.as_slice()?, &[1, 3, 2, 4]);
1427    /// # Ok::<(), tenferro_tensor::Error>(())
1428    /// ```
1429    pub fn copy_from_contiguous(&mut self, src: &TypedTensor<T, R>) -> crate::Result<()>
1430    where
1431        T: Clone,
1432    {
1433        let op = "TypedTensorViewMut::copy_from_contiguous";
1434        if self.shape() != src.shape() {
1435            return Err(crate::Error::InvalidConfig {
1436                op,
1437                message: format!(
1438                    "shape mismatch: destination {:?} does not match source {:?}",
1439                    self.shape(),
1440                    src.shape()
1441                ),
1442            });
1443        }
1444
1445        let src_data = match &src.buffer {
1446            Buffer::Host(data) => contiguous_layout_slice(src.layout(), data, op)?,
1447            Buffer::Backend(_) => {
1448                return Err(crate::Error::backend_failure(
1449                    op,
1450                    "source backend buffer cannot be copied through host memory; download explicitly first",
1451                ))
1452            }
1453        };
1454
1455        let shape = self.shape().to_vec();
1456        let strides = self.strides().to_vec();
1457        let offset = self.offset();
1458        let dst_data = match &mut self.buffer {
1459            TensorBufferRefMut::Host(data) => data,
1460            TensorBufferRefMut::Backend(_) => {
1461                return Err(crate::Error::backend_failure(
1462                    op,
1463                    "destination backend buffer cannot be updated through host memory; download explicitly first",
1464                ))
1465            }
1466        };
1467
1468        let mut src_iter = src_data.iter();
1469        for_each_layout_offset_col_major(&shape, &strides, offset, op, |offset| {
1470            let value = src_iter.next().ok_or_else(|| crate::Error::InvalidConfig {
1471                op,
1472                message: "source tensor ended before destination view".to_string(),
1473            })?;
1474            let dst = dst_data
1475                .get_mut(offset)
1476                .ok_or_else(|| crate::Error::InvalidConfig {
1477                    op,
1478                    message: "destination view offset is outside host buffer".to_string(),
1479                })?;
1480            *dst = value.clone();
1481            Ok(())
1482        })?;
1483        if src_iter.next().is_some() {
1484            return Err(crate::Error::InvalidConfig {
1485                op,
1486                message: "source tensor has elements remaining after destination copy".to_string(),
1487            });
1488        }
1489        Ok(())
1490    }
1491
1492    /// Borrow this mutable view as a read-only view.
1493    ///
1494    /// # Examples
1495    ///
1496    /// ```rust
1497    /// use tenferro_tensor::TypedTensorViewMut;
1498    ///
1499    /// let mut data = [1_i32];
1500    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1501    /// assert_eq!(view.as_read_only().get(&[0]), Some(&1));
1502    /// # Ok::<(), tenferro_tensor::Error>(())
1503    /// ```
1504    pub fn as_read_only(&self) -> TypedTensorView<'_, T, R> {
1505        let buffer = match &self.buffer {
1506            TensorBufferRefMut::Host(data) => TensorBufferRef::Host(data),
1507            TensorBufferRefMut::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
1508        };
1509        TypedTensorView {
1510            buffer,
1511            layout: self.layout.clone(),
1512            placement: self.placement.clone(),
1513        }
1514    }
1515
1516    /// Convert this mutable view into a read-only view.
1517    ///
1518    /// # Examples
1519    ///
1520    /// ```rust
1521    /// use tenferro_tensor::TypedTensorViewMut;
1522    ///
1523    /// let mut data = [1_i32];
1524    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1525    /// assert_eq!(view.into_read_only().get(&[0]), Some(&1));
1526    /// # Ok::<(), tenferro_tensor::Error>(())
1527    /// ```
1528    pub fn into_read_only(self) -> TypedTensorView<'a, T, R> {
1529        let buffer = match self.buffer {
1530            TensorBufferRefMut::Host(data) => TensorBufferRef::Host(data),
1531            TensorBufferRefMut::Backend(buffer) => TensorBufferRef::Backend(buffer),
1532        };
1533        TypedTensorView {
1534            buffer,
1535            layout: self.layout,
1536            placement: self.placement,
1537        }
1538    }
1539
1540    /// Consume this mutable view and return a metadata-only axis permutation.
1541    ///
1542    /// # Examples
1543    ///
1544    /// ```rust
1545    /// use tenferro_tensor::{Rank, TypedTensorViewMut};
1546    ///
1547    /// let mut data = [1_i32, 2, 3, 4];
1548    /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
1549    /// let transposed = view.transpose_view([1, 0])?;
1550    /// assert_eq!(transposed.strides(), &[2, 1]);
1551    /// # Ok::<(), tenferro_tensor::Error>(())
1552    /// ```
1553    pub fn transpose_view(
1554        self,
1555        axes: impl AsRef<[usize]>,
1556    ) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
1557        let Self {
1558            buffer,
1559            layout,
1560            placement,
1561        } = self;
1562        let layout = layout
1563            .transpose_view(axes)
1564            .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
1565        layout
1566            .validate_mutable_no_overlap()
1567            .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
1568        match buffer {
1569            TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
1570                buffer: TensorBufferRefMut::Host(data),
1571                layout,
1572                placement,
1573            }),
1574            TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
1575                buffer: TensorBufferRefMut::Backend(buffer),
1576                layout,
1577                placement,
1578            }),
1579        }
1580    }
1581
1582    /// Return a mutable metadata-only slice using one [`StridedSliceSpec`] per axis.
1583    ///
1584    /// # Examples
1585    ///
1586    /// ```rust
1587    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
1588    ///
1589    /// let mut data = [1_i32, 2, 3];
1590    /// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![1], 0, &mut data)?;
1591    /// *view.try_slice(&[StridedSliceSpec::reverse()])?.get_mut(&[0]).unwrap() = 30;
1592    /// assert_eq!(view.get(&[2]), Some(&30));
1593    /// # Ok::<(), tenferro_tensor::Error>(())
1594    /// ```
1595    pub fn try_slice(
1596        &mut self,
1597        slices: &[StridedSliceSpec],
1598    ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
1599        let specs = core_slice_specs(slices, self.shape(), "TypedTensorViewMut::try_slice")?;
1600        let layout = self
1601            .layout
1602            .slice_view(specs, self.buffer.len())
1603            .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_slice", err))?;
1604        layout
1605            .validate_mutable_no_overlap()
1606            .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_slice", err))?;
1607        let placement = self.placement.clone();
1608        match &mut self.buffer {
1609            TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
1610                buffer: TensorBufferRefMut::Host(data),
1611                layout,
1612                placement,
1613            }),
1614            TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
1615                buffer: TensorBufferRefMut::Backend(Arc::clone(buffer)),
1616                layout,
1617                placement,
1618            }),
1619        }
1620    }
1621
1622    /// Return a mutable metadata-only slice along one axis.
1623    ///
1624    /// # Examples
1625    ///
1626    /// ```rust
1627    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
1628    ///
1629    /// let mut data = [1_i32, 2, 3, 4];
1630    /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
1631    /// assert_eq!(view.try_slice_axis(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
1632    /// # Ok::<(), tenferro_tensor::Error>(())
1633    /// ```
1634    pub fn try_slice_axis(
1635        &mut self,
1636        axis: usize,
1637        slice: StridedSliceSpec,
1638    ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
1639        let slices = slice_axis_specs(
1640            self.shape().len(),
1641            axis,
1642            slice,
1643            "TypedTensorViewMut::try_slice_axis",
1644        )?;
1645        self.try_slice(&slices)
1646    }
1647
1648    /// Return two mutable metadata-only slices when their physical ranges are disjoint.
1649    ///
1650    /// # Examples
1651    ///
1652    /// ```rust
1653    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
1654    ///
1655    /// let mut data = [1_i32, 2, 3, 4];
1656    /// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
1657    /// let (left, right) = view
1658    ///     .try_multi_slice_mut(
1659    ///         &[StridedSliceSpec::new(0, Some(2), 1)],
1660    ///         &[StridedSliceSpec::new(2, Some(4), 1)],
1661    ///     )
1662    ///     ?
1663    ///     .unwrap();
1664    /// assert_eq!(left.shape(), &[2]);
1665    /// assert_eq!(right.shape(), &[2]);
1666    /// # Ok::<(), tenferro_tensor::Error>(())
1667    /// ```
1668    pub fn try_multi_slice_mut(
1669        &mut self,
1670        first: &[StridedSliceSpec],
1671        second: &[StridedSliceSpec],
1672    ) -> crate::Result<Option<TypedTensorViewMutPair<'_, T, R>>> {
1673        let op = "TypedTensorViewMut::try_multi_slice_mut";
1674        let first_specs = core_slice_specs(first, self.shape(), op)?;
1675        let second_specs = core_slice_specs(second, self.shape(), op)?;
1676        let buffer_len = self.buffer.len();
1677        let first_layout = self
1678            .layout
1679            .slice_view(first_specs, buffer_len)
1680            .map_err(|err| tensor_layout_error(op, err))?;
1681        let second_layout = self
1682            .layout
1683            .slice_view(second_specs, buffer_len)
1684            .map_err(|err| tensor_layout_error(op, err))?;
1685        first_layout
1686            .validate_mutable_no_overlap()
1687            .map_err(|err| tensor_layout_error(op, err))?;
1688        second_layout
1689            .validate_mutable_no_overlap()
1690            .map_err(|err| tensor_layout_error(op, err))?;
1691
1692        match (
1693            reachable_layout_span(
1694                first_layout.shape(),
1695                first_layout.strides(),
1696                first_layout.offset(),
1697            )?,
1698            reachable_layout_span(
1699                second_layout.shape(),
1700                second_layout.strides(),
1701                second_layout.offset(),
1702            )?,
1703        ) {
1704            (Some(first_span), Some(second_span)) => {
1705                let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
1706                let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
1707                let (first_data, second_data) = match &mut self.buffer {
1708                    TensorBufferRefMut::Host(data) => {
1709                        match split_two_mut_ranges(data, first_span, second_span) {
1710                            Some(ranges) => ranges,
1711                            None => return Ok(None),
1712                        }
1713                    }
1714                    TensorBufferRefMut::Backend(_) => return Ok(None),
1715                };
1716                let first_view = view_mut_from_layout_and_slice(
1717                    &first_layout,
1718                    first_offset,
1719                    first_data,
1720                    self.placement.clone(),
1721                )?;
1722                let second_view = view_mut_from_layout_and_slice(
1723                    &second_layout,
1724                    second_offset,
1725                    second_data,
1726                    self.placement.clone(),
1727                )?;
1728                Ok(Some((first_view, second_view)))
1729            }
1730            (None, Some(second_span)) => {
1731                let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
1732                let (_, after_start) = match &mut self.buffer {
1733                    TensorBufferRefMut::Host(data) => data.split_at_mut(second_span.0),
1734                    TensorBufferRefMut::Backend(_) => return Ok(None),
1735                };
1736                let (second_data, _) = after_start.split_at_mut(second_span.1 - second_span.0 + 1);
1737                let first_view = view_mut_from_layout_and_slice(
1738                    &first_layout,
1739                    0,
1740                    &mut [],
1741                    self.placement.clone(),
1742                )?;
1743                let second_view = view_mut_from_layout_and_slice(
1744                    &second_layout,
1745                    second_offset,
1746                    second_data,
1747                    self.placement.clone(),
1748                )?;
1749                Ok(Some((first_view, second_view)))
1750            }
1751            (Some(first_span), None) => {
1752                let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
1753                let (_, after_start) = match &mut self.buffer {
1754                    TensorBufferRefMut::Host(data) => data.split_at_mut(first_span.0),
1755                    TensorBufferRefMut::Backend(_) => return Ok(None),
1756                };
1757                let (first_data, _) = after_start.split_at_mut(first_span.1 - first_span.0 + 1);
1758                let first_view = view_mut_from_layout_and_slice(
1759                    &first_layout,
1760                    first_offset,
1761                    first_data,
1762                    self.placement.clone(),
1763                )?;
1764                let second_view = view_mut_from_layout_and_slice(
1765                    &second_layout,
1766                    0,
1767                    &mut [],
1768                    self.placement.clone(),
1769                )?;
1770                Ok(Some((first_view, second_view)))
1771            }
1772            (None, None) => {
1773                let first_view = view_mut_from_layout_and_slice(
1774                    &first_layout,
1775                    0,
1776                    &mut [],
1777                    self.placement.clone(),
1778                )?;
1779                let second_view = view_mut_from_layout_and_slice(
1780                    &second_layout,
1781                    0,
1782                    &mut [],
1783                    self.placement.clone(),
1784                )?;
1785                Ok(Some((first_view, second_view)))
1786            }
1787        }
1788    }
1789
1790    /// Return a mutable metadata-only dynamic-rank reshape for contiguous views.
1791    ///
1792    /// # Examples
1793    ///
1794    /// ```rust
1795    /// use tenferro_tensor::TypedTensorViewMut;
1796    ///
1797    /// let mut data = [1_i32, 2, 3, 4];
1798    /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
1799    /// assert_eq!(view.try_reshape(&[4])?.shape(), &[4]);
1800    /// # Ok::<(), tenferro_tensor::Error>(())
1801    /// ```
1802    pub fn try_reshape(
1803        &mut self,
1804        shape: &[usize],
1805    ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>> {
1806        let layout = reshape_layout_dyn(
1807            &self.layout,
1808            shape,
1809            self.buffer.len(),
1810            "TypedTensorViewMut::try_reshape",
1811        )?;
1812        layout
1813            .validate_mutable_no_overlap()
1814            .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_reshape", err))?;
1815        let placement = self.placement.clone();
1816        match &mut self.buffer {
1817            TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
1818                buffer: TensorBufferRefMut::Host(data),
1819                layout,
1820                placement,
1821            }),
1822            TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
1823                buffer: TensorBufferRefMut::Backend(Arc::clone(buffer)),
1824                layout,
1825                placement,
1826            }),
1827        }
1828    }
1829}
1830
1831/// Runtime scalar dtype tag.
1832///
1833/// # Examples
1834///
1835/// ```rust
1836/// use tenferro_tensor::DType;
1837///
1838/// assert_eq!(DType::F64 as u8, DType::F64 as u8);
1839/// ```
1840#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1841pub enum DType {
1842    F32,
1843    F64,
1844    I32,
1845    I64,
1846    Bool,
1847    C32,
1848    C64,
1849}
1850
1851/// Sealed trait for scalar types that can be stored in a [`Tensor`].
1852///
1853/// This trait is implemented for `f64`, `f32`, `i32`, `i64`, `bool`,
1854/// [`Complex64`], and [`Complex32`].
1855///
1856/// # Examples
1857///
1858/// ```
1859/// use tenferro_tensor::TensorScalar;
1860///
1861/// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
1862/// assert_eq!(tensor.as_slice::<f64>()?, [1.0, 2.0].as_slice());
1863/// # Ok::<(), tenferro_tensor::Error>(())
1864/// ```
1865pub trait TensorScalar: Copy + Clone + Send + Sync + 'static + private::Sealed {
1866    /// Real-valued counterpart of this scalar type.
1867    type Real: TensorScalar;
1868
1869    /// The [`DType`] tag corresponding to this scalar type.
1870    ///
1871    /// # Examples
1872    ///
1873    /// ```
1874    /// use tenferro_tensor::{DType, TensorScalar};
1875    ///
1876    /// assert_eq!(f64::dtype(), DType::F64);
1877    /// assert_eq!(f32::dtype(), DType::F32);
1878    /// ```
1879    fn dtype() -> DType;
1880
1881    /// Wrap typed column-major data into a [`Tensor`] enum variant.
1882    fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor>;
1883
1884    /// Borrow a typed tensor as a dtype-erased [`TensorRead`] view.
1885    ///
1886    /// This keeps the typed tensor borrowed instead of copying host data into
1887    /// a new dynamic tensor.
1888    ///
1889    /// # Examples
1890    ///
1891    /// ```
1892    /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
1893    ///
1894    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
1895    /// let read = f64::tensor_read(&tensor);
1896    /// assert_eq!(read.dtype(), DType::F64);
1897    /// assert_eq!(read.shape(), &[2]);
1898    /// ```
1899    fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_>;
1900
1901    /// Wrap a typed borrowed view as a dtype-erased [`TensorView`].
1902    ///
1903    /// # Examples
1904    ///
1905    /// ```
1906    /// use tenferro_tensor::{DType, TensorScalar, TypedTensorView};
1907    ///
1908    /// let data = [1.0_f64];
1909    /// let view = TypedTensorView::from_col_major(&[1], &data)?;
1910    /// assert_eq!(f64::tensor_view(view).dtype(), DType::F64);
1911    /// # Ok::<(), tenferro_tensor::Error>(())
1912    /// ```
1913    fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a>;
1914
1915    /// Mutably borrow a typed tensor as a dtype-erased [`TensorWrite`] view.
1916    ///
1917    /// This keeps the typed output borrowed instead of wrapping it in a
1918    /// temporary dynamic tensor.
1919    ///
1920    /// # Examples
1921    ///
1922    /// ```
1923    /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
1924    ///
1925    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0]).unwrap();
1926    /// let write = f64::tensor_write(&mut tensor);
1927    /// assert_eq!(write.dtype(), DType::F64);
1928    /// ```
1929    fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_>;
1930
1931    /// Borrow the host data from a [`Tensor`].
1932    fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]>;
1933
1934    /// Mutably borrow the host data from a [`Tensor`].
1935    ///
1936    /// # Examples
1937    ///
1938    /// ```
1939    /// use tenferro_tensor::{Tensor, TensorScalar};
1940    ///
1941    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
1942    /// <f64 as TensorScalar>::as_slice_mut(&mut tensor)?[0] = 3.0;
1943    ///
1944    /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
1945    /// # Ok::<(), tenferro_tensor::Error>(())
1946    /// ```
1947    fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]>;
1948
1949    /// Extract a [`TypedTensor<Self>`] from a dynamic [`Tensor`].
1950    ///
1951    /// # Examples
1952    ///
1953    /// ```
1954    /// use tenferro_tensor::{Tensor, TensorScalar};
1955    ///
1956    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
1957    /// let typed = <f64 as TensorScalar>::into_typed(tensor)?;
1958    ///
1959    /// assert_eq!(typed.as_slice()?, &[1.0, 2.0]);
1960    /// # Ok::<(), tenferro_tensor::Error>(())
1961    /// ```
1962    fn into_typed(tensor: Tensor) -> crate::Result<TypedTensor<Self>>;
1963}
1964
1965mod private {
1966    pub trait Sealed {}
1967
1968    impl Sealed for f64 {}
1969    impl Sealed for f32 {}
1970    impl Sealed for i32 {}
1971    impl Sealed for i64 {}
1972    impl Sealed for bool {}
1973    impl Sealed for num_complex::Complex64 {}
1974    impl Sealed for num_complex::Complex32 {}
1975}
1976
1977macro_rules! impl_tensor_scalar {
1978    ($ty:ty, $real:ty, $dtype:ident, $variant:ident) => {
1979        impl TensorScalar for $ty {
1980            type Real = $real;
1981
1982            fn dtype() -> DType {
1983                DType::$dtype
1984            }
1985
1986            fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor> {
1987                TypedTensor::from_vec_col_major(shape, data).map(Tensor::$variant)
1988            }
1989
1990            fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_> {
1991                TensorRead::from_view(TensorView::$variant(tensor.as_view()))
1992            }
1993
1994            fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a> {
1995                TensorView::$variant(view)
1996            }
1997
1998            fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_> {
1999                TensorWrite::from_view(TensorViewMut::$variant(tensor.as_view_mut()))
2000            }
2001
2002            fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]> {
2003                let actual = tensor.dtype();
2004                match tensor {
2005                    Tensor::$variant(t) => t.host_data(),
2006                    _ => Err(crate::Error::DTypeMismatch {
2007                        op: "Tensor::as_slice",
2008                        lhs: Self::dtype(),
2009                        rhs: actual,
2010                    }),
2011                }
2012            }
2013
2014            fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]> {
2015                let actual = tensor.dtype();
2016                match tensor {
2017                    Tensor::$variant(t) => t.host_data_mut(),
2018                    _ => Err(crate::Error::DTypeMismatch {
2019                        op: "Tensor::as_slice_mut",
2020                        lhs: Self::dtype(),
2021                        rhs: actual,
2022                    }),
2023                }
2024            }
2025
2026            fn into_typed(tensor: Tensor) -> crate::Result<TypedTensor<Self>> {
2027                let actual = tensor.dtype();
2028                match tensor {
2029                    Tensor::$variant(inner) => Ok(inner),
2030                    _ => Err(crate::Error::DTypeMismatch {
2031                        op: "TensorScalar::into_typed",
2032                        lhs: Self::dtype(),
2033                        rhs: actual,
2034                    }),
2035                }
2036            }
2037        }
2038    };
2039}
2040
2041impl_tensor_scalar!(f64, f64, F64, F64);
2042impl_tensor_scalar!(f32, f32, F32, F32);
2043impl_tensor_scalar!(i64, i64, I64, I64);
2044impl_tensor_scalar!(i32, i32, I32, I32);
2045impl_tensor_scalar!(bool, bool, Bool, Bool);
2046impl_tensor_scalar!(Complex64, f64, C64, C64);
2047impl_tensor_scalar!(Complex32, f32, C32, C32);
2048
2049/// Dynamic tensor enum over the supported scalar types.
2050///
2051/// The enum keeps dtype dynamic and rank dynamic. Use
2052/// [`TypedTensor<T, R>`](TypedTensor) directly when the scalar type or rank
2053/// should be represented in Rust's type system.
2054///
2055/// # Examples
2056///
2057/// ```rust
2058/// use tenferro_tensor::{Tensor, TypedTensor};
2059///
2060/// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
2061/// assert_eq!(t.shape(), &[2]);
2062///
2063/// let erased = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 2.0]).unwrap();
2064/// assert_eq!(erased.shape().len(), 2);
2065/// ```
2066#[derive(Clone, Debug)]
2067pub enum Tensor {
2068    F32(TypedTensor<f32>),
2069    F64(TypedTensor<f64>),
2070    I32(TypedTensor<i32>),
2071    I64(TypedTensor<i64>),
2072    Bool(TypedTensor<bool>),
2073    C32(TypedTensor<Complex<f32>>),
2074    C64(TypedTensor<Complex<f64>>),
2075}
2076
2077/// Dynamic read-only borrowed tensor view.
2078///
2079/// `TensorView` keeps dtype erased while borrowing typed view metadata and
2080/// storage. Use [`TypedTensorView`] directly when the scalar type is statically
2081/// known.
2082///
2083/// # Examples
2084///
2085/// ```
2086/// use tenferro_tensor::{DType, TensorView, TypedTensorView};
2087///
2088/// let data = [1_i32, 2, 3, 4];
2089/// let typed = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data)?;
2090/// let view = TensorView::I32(typed);
2091///
2092/// assert_eq!(view.dtype(), DType::I32);
2093/// assert_eq!(view.shape(), &[2, 2]);
2094/// # Ok::<(), tenferro_tensor::Error>(())
2095/// ```
2096#[derive(Clone, Debug)]
2097pub enum TensorView<'a> {
2098    F32(TypedTensorView<'a, f32>),
2099    F64(TypedTensorView<'a, f64>),
2100    I32(TypedTensorView<'a, i32>),
2101    I64(TypedTensorView<'a, i64>),
2102    Bool(TypedTensorView<'a, bool>),
2103    C32(TypedTensorView<'a, Complex<f32>>),
2104    C64(TypedTensorView<'a, Complex<f64>>),
2105}
2106
2107/// Dynamic mutable borrowed tensor view.
2108///
2109/// `TensorViewMut` is the mutable counterpart to [`TensorView`]. It keeps the
2110/// dtype erased while preserving the typed mutable view's shape, strides, and
2111/// offset metadata.
2112///
2113/// # Examples
2114///
2115/// ```
2116/// use tenferro_tensor::{DType, TensorViewMut, TypedTensorViewMut};
2117///
2118/// let mut data = [1.0_f64, 2.0];
2119/// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?);
2120/// assert_eq!(view.dtype(), DType::F64);
2121/// # Ok::<(), tenferro_tensor::Error>(())
2122/// ```
2123#[allow(clippy::large_enum_variant)]
2124#[derive(Debug)]
2125pub enum TensorViewMut<'a> {
2126    F32(TypedTensorViewMut<'a, f32>),
2127    F64(TypedTensorViewMut<'a, f64>),
2128    I32(TypedTensorViewMut<'a, i32>),
2129    I64(TypedTensorViewMut<'a, i64>),
2130    Bool(TypedTensorViewMut<'a, bool>),
2131    C32(TypedTensorViewMut<'a, Complex<f32>>),
2132    C64(TypedTensorViewMut<'a, Complex<f64>>),
2133}
2134
2135/// Read-only tensor input accepted by synchronous eager kernels.
2136///
2137/// `TensorRead` lets kernels accept either an owned tensor reference or a
2138/// borrowed [`TensorView`] without forcing callers to materialize first.
2139/// The `View` variant preserves arbitrary strides and offsets, so kernels that
2140/// support strided reads can consume transposes, slices, and broadcasts directly.
2141///
2142/// `TensorRead` is intentionally borrowed. It is an input-dispatch type, not an
2143/// owned lazy tensor value. APIs that need to store a lazy layout result should
2144/// keep an owned base tensor plus layout metadata, then expose a `TensorRead`
2145/// only for the duration of kernel dispatch.
2146///
2147/// # Examples
2148///
2149/// ```
2150/// use tenferro_tensor::{DType, Tensor, TensorRead};
2151///
2152/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
2153/// let read = TensorRead::from_tensor(&tensor);
2154///
2155/// assert_eq!(read.dtype(), DType::F64);
2156/// assert_eq!(read.shape(), &[2]);
2157/// ```
2158// Keep borrowed views inline to avoid allocation on read-only tensor dispatch paths.
2159#[allow(clippy::large_enum_variant)]
2160#[derive(Clone, Debug)]
2161pub enum TensorRead<'a> {
2162    Tensor(&'a Tensor),
2163    View(TensorView<'a>),
2164}
2165
2166/// Mutable tensor output accepted by synchronous eager kernels.
2167///
2168/// `TensorWrite` mirrors [`TensorRead`] for output dispatch: it can target an
2169/// owned compact [`Tensor`] or a borrowed mutable [`TensorViewMut`]. The target
2170/// is never resized.
2171///
2172/// # Examples
2173///
2174/// ```
2175/// use tenferro_tensor::{Tensor, TensorWrite};
2176///
2177/// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
2178/// let write = TensorWrite::from_tensor(&mut tensor);
2179/// assert_eq!(write.shape(), &[1]);
2180/// # Ok::<(), tenferro_tensor::Error>(())
2181/// ```
2182#[allow(clippy::large_enum_variant)]
2183#[derive(Debug)]
2184pub enum TensorWrite<'a> {
2185    Tensor(&'a mut Tensor),
2186    View(TensorViewMut<'a>),
2187}
2188
2189/// Owned lazy tensor view over a shared base tensor.
2190///
2191/// This stores only ownership of the base allocation plus logical layout
2192/// metadata. Borrow it as [`TensorRead`] for kernels that understand strides,
2193/// or materialize it explicitly with [`TensorOwnedView::to_tensor`].
2194#[derive(Clone, Debug)]
2195pub struct TensorOwnedView {
2196    base: Arc<Tensor>,
2197    layout: TensorLayout<DynRank>,
2198}
2199
2200/// Owned tensor value that can be compact or a lazy view.
2201///
2202/// `TensorValue` is the owned counterpart to [`TensorRead`]. It is suitable for
2203/// storing eager results that should remain lazy until an operation actually
2204/// requires compact materialized storage.
2205#[derive(Clone, Debug)]
2206pub enum TensorValue {
2207    Tensor(Arc<Tensor>),
2208    View(TensorOwnedView),
2209}
2210
2211impl TensorOwnedView {
2212    /// Create an owned view preserving the base tensor's current layout.
2213    pub fn from_tensor(base: Arc<Tensor>) -> Self {
2214        let layout = tensor_layout(base.as_ref());
2215        Self { base, layout }
2216    }
2217
2218    /// Create an owned view with explicit layout metadata.
2219    pub fn from_parts(
2220        base: Arc<Tensor>,
2221        shape: Vec<usize>,
2222        strides: Vec<isize>,
2223        offset: isize,
2224    ) -> crate::Result<Self> {
2225        let layout = TensorLayout::from_parts(
2226            shape.into(),
2227            strides.into(),
2228            offset,
2229            tensor_buffer_len(&base),
2230        )
2231        .map_err(|err| tensor_layout_error("TensorOwnedView::from_parts", err))?;
2232        Ok(Self { base, layout })
2233    }
2234
2235    pub fn dtype(&self) -> DType {
2236        self.base.dtype()
2237    }
2238
2239    pub fn shape(&self) -> &[usize] {
2240        self.layout.shape()
2241    }
2242
2243    pub fn strides(&self) -> &[isize] {
2244        self.layout.strides()
2245    }
2246
2247    pub fn offset(&self) -> isize {
2248        self.layout.offset()
2249    }
2250
2251    pub fn tensor_view(&self) -> TensorView<'_> {
2252        tensor_view_with_layout(self.base.as_ref(), self.layout.clone())
2253    }
2254
2255    pub fn tensor_read(&self) -> TensorRead<'_> {
2256        TensorRead::from_view(self.tensor_view())
2257    }
2258
2259    /// Materialize this owned view into an owned compact tensor.
2260    ///
2261    /// This returns an explicit error for backend-backed views because no
2262    /// backend context is available for an implicit download.
2263    ///
2264    /// # Examples
2265    ///
2266    /// ```rust
2267    /// use std::sync::Arc;
2268    /// use tenferro_tensor::{Tensor, TensorOwnedView};
2269    ///
2270    /// let base = Arc::new(Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap());
2271    /// let view = TensorOwnedView::from_tensor(base);
2272    /// let tensor = view.to_tensor()?;
2273    /// assert_eq!(tensor.shape(), &[2]);
2274    /// # Ok::<(), tenferro_tensor::Error>(())
2275    /// ```
2276    pub fn to_tensor(&self) -> crate::Result<Tensor> {
2277        self.tensor_view().to_tensor()
2278    }
2279
2280    pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
2281        let layout = self
2282            .layout
2283            .transpose_view(axes)
2284            .map_err(|err| tensor_layout_error("TensorOwnedView::transpose_view", err))?;
2285        Ok(Self {
2286            base: Arc::clone(&self.base),
2287            layout,
2288        })
2289    }
2290
2291    pub fn reshape_view(&self, shape: &[usize]) -> crate::Result<Self> {
2292        let layout = reshape_layout_dyn(
2293            &self.layout,
2294            shape,
2295            tensor_buffer_len(&self.base),
2296            "TensorOwnedView::reshape_view",
2297        )?;
2298        Ok(Self {
2299            base: Arc::clone(&self.base),
2300            layout,
2301        })
2302    }
2303
2304    pub fn slice_view(&self, config: &SliceConfig) -> crate::Result<Self> {
2305        let op = "TensorOwnedView::slice_view";
2306        if config.starts.len() != self.shape().len() {
2307            return Err(crate::Error::RankMismatch {
2308                op,
2309                expected: self.shape().len(),
2310                actual: config.starts.len(),
2311            });
2312        }
2313        if config.limits.len() != self.shape().len() {
2314            return Err(crate::Error::RankMismatch {
2315                op,
2316                expected: self.shape().len(),
2317                actual: config.limits.len(),
2318            });
2319        }
2320        if config.strides.len() != self.shape().len() {
2321            return Err(crate::Error::RankMismatch {
2322                op,
2323                expected: self.shape().len(),
2324                actual: config.strides.len(),
2325            });
2326        }
2327
2328        let mut slices = Vec::with_capacity(self.shape().len());
2329        for ((&start, &limit), &stride) in config
2330            .starts
2331            .iter()
2332            .zip(config.limits.iter())
2333            .zip(config.strides.iter())
2334        {
2335            let start = isize::try_from(start).map_err(|_| crate::Error::InvalidConfig {
2336                op,
2337                message: format!("slice start {start} does not fit in isize"),
2338            })?;
2339            let limit = isize::try_from(limit).map_err(|_| crate::Error::InvalidConfig {
2340                op,
2341                message: format!("slice limit {limit} does not fit in isize"),
2342            })?;
2343            let stride = isize::try_from(stride).map_err(|_| crate::Error::InvalidConfig {
2344                op,
2345                message: format!("slice stride {stride} does not fit in isize"),
2346            })?;
2347            slices.push(StridedSliceSpec::new(start, Some(limit), stride));
2348        }
2349
2350        let specs = core_slice_specs(&slices, self.shape(), op)?;
2351        let layout = self
2352            .layout
2353            .slice_view(&specs, tensor_buffer_len(&self.base))
2354            .map_err(|err| tensor_layout_error(op, err))?;
2355        Ok(Self {
2356            base: Arc::clone(&self.base),
2357            layout,
2358        })
2359    }
2360
2361    pub fn broadcast_in_dim_view(&self, shape: &[usize], dims: &[usize]) -> crate::Result<Self> {
2362        let layout = self
2363            .layout
2364            .broadcast_in_dim_view::<DynRank>(
2365                shape.to_vec().into(),
2366                dims,
2367                tensor_buffer_len(&self.base),
2368            )
2369            .map_err(|err| tensor_layout_error("TensorOwnedView::broadcast_in_dim_view", err))?;
2370        Ok(Self {
2371            base: Arc::clone(&self.base),
2372            layout,
2373        })
2374    }
2375}
2376
2377impl TensorValue {
2378    pub fn from_tensor(tensor: Tensor) -> Self {
2379        Self::Tensor(Arc::new(tensor))
2380    }
2381
2382    pub fn from_tensor_arc(tensor: Arc<Tensor>) -> Self {
2383        Self::Tensor(tensor)
2384    }
2385
2386    pub fn as_tensor_arc(&self) -> Option<&Arc<Tensor>> {
2387        match self {
2388            Self::Tensor(tensor) => Some(tensor),
2389            Self::View(_) => None,
2390        }
2391    }
2392
2393    pub fn dtype(&self) -> DType {
2394        match self {
2395            Self::Tensor(tensor) => tensor.dtype(),
2396            Self::View(view) => view.dtype(),
2397        }
2398    }
2399
2400    pub fn shape(&self) -> &[usize] {
2401        match self {
2402            Self::Tensor(tensor) => tensor.shape(),
2403            Self::View(view) => view.shape(),
2404        }
2405    }
2406
2407    pub fn tensor_read(&self) -> TensorRead<'_> {
2408        match self {
2409            Self::Tensor(tensor) => TensorRead::from_tensor(tensor.as_ref()),
2410            Self::View(view) => view.tensor_read(),
2411        }
2412    }
2413
2414    /// Materialize this tensor value into an owned compact tensor.
2415    ///
2416    /// Compact tensor values are cloned. Lazy host views are materialized.
2417    /// Backend-backed views return an explicit error instead of panicking.
2418    ///
2419    /// # Examples
2420    ///
2421    /// ```rust
2422    /// use tenferro_tensor::{Tensor, TensorValue};
2423    ///
2424    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major(
2425    ///     vec![2],
2426    ///     vec![1.0_f64, 2.0],
2427    /// ).unwrap());
2428    /// let tensor = value.to_tensor()?;
2429    /// assert_eq!(tensor.shape(), &[2]);
2430    /// # Ok::<(), tenferro_tensor::Error>(())
2431    /// ```
2432    pub fn to_tensor(&self) -> crate::Result<Tensor> {
2433        match self {
2434            Self::Tensor(tensor) => Ok(tensor.as_ref().clone()),
2435            Self::View(view) => view.to_tensor(),
2436        }
2437    }
2438
2439    pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
2440        match self {
2441            Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2442                .transpose_view(axes)
2443                .map(Self::View),
2444            Self::View(view) => view.transpose_view(axes).map(Self::View),
2445        }
2446    }
2447
2448    pub fn reshape_view(&self, shape: &[usize]) -> crate::Result<Self> {
2449        match self {
2450            Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2451                .reshape_view(shape)
2452                .map(Self::View),
2453            Self::View(view) => view.reshape_view(shape).map(Self::View),
2454        }
2455    }
2456
2457    pub fn slice_view(&self, config: &SliceConfig) -> crate::Result<Self> {
2458        match self {
2459            Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2460                .slice_view(config)
2461                .map(Self::View),
2462            Self::View(view) => view.slice_view(config).map(Self::View),
2463        }
2464    }
2465
2466    pub fn broadcast_in_dim_view(&self, shape: &[usize], dims: &[usize]) -> crate::Result<Self> {
2467        match self {
2468            Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2469                .broadcast_in_dim_view(shape, dims)
2470                .map(Self::View),
2471            Self::View(view) => view.broadcast_in_dim_view(shape, dims).map(Self::View),
2472        }
2473    }
2474}
2475
2476fn tensor_layout(tensor: &Tensor) -> TensorLayout<DynRank> {
2477    match tensor {
2478        Tensor::F32(tensor) => tensor.layout.clone(),
2479        Tensor::F64(tensor) => tensor.layout.clone(),
2480        Tensor::I32(tensor) => tensor.layout.clone(),
2481        Tensor::I64(tensor) => tensor.layout.clone(),
2482        Tensor::Bool(tensor) => tensor.layout.clone(),
2483        Tensor::C32(tensor) => tensor.layout.clone(),
2484        Tensor::C64(tensor) => tensor.layout.clone(),
2485    }
2486}
2487
2488fn tensor_buffer_len(tensor: &Tensor) -> usize {
2489    match tensor {
2490        Tensor::F32(tensor) => buffer_len(&tensor.buffer),
2491        Tensor::F64(tensor) => buffer_len(&tensor.buffer),
2492        Tensor::I32(tensor) => buffer_len(&tensor.buffer),
2493        Tensor::I64(tensor) => buffer_len(&tensor.buffer),
2494        Tensor::Bool(tensor) => buffer_len(&tensor.buffer),
2495        Tensor::C32(tensor) => buffer_len(&tensor.buffer),
2496        Tensor::C64(tensor) => buffer_len(&tensor.buffer),
2497    }
2498}
2499
2500fn buffer_len<T: 'static>(buffer: &Buffer<T>) -> usize {
2501    match buffer {
2502        Buffer::Host(data) => data.len(),
2503        Buffer::Backend(buffer) => buffer.len(),
2504    }
2505}
2506
2507fn tensor_view_with_layout(tensor: &Tensor, layout: TensorLayout<DynRank>) -> TensorView<'_> {
2508    match tensor {
2509        Tensor::F32(tensor) => TensorView::F32(typed_view_with_layout(tensor, layout)),
2510        Tensor::F64(tensor) => TensorView::F64(typed_view_with_layout(tensor, layout)),
2511        Tensor::I32(tensor) => TensorView::I32(typed_view_with_layout(tensor, layout)),
2512        Tensor::I64(tensor) => TensorView::I64(typed_view_with_layout(tensor, layout)),
2513        Tensor::Bool(tensor) => TensorView::Bool(typed_view_with_layout(tensor, layout)),
2514        Tensor::C32(tensor) => TensorView::C32(typed_view_with_layout(tensor, layout)),
2515        Tensor::C64(tensor) => TensorView::C64(typed_view_with_layout(tensor, layout)),
2516    }
2517}
2518
2519fn typed_view_with_layout<T: 'static>(
2520    tensor: &TypedTensor<T>,
2521    layout: TensorLayout<DynRank>,
2522) -> TypedTensorView<'_, T> {
2523    let buffer = match &tensor.buffer {
2524        Buffer::Host(data) => TensorBufferRef::Host(data),
2525        Buffer::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
2526    };
2527    TypedTensorView {
2528        buffer,
2529        layout,
2530        placement: tensor.placement.clone(),
2531    }
2532}
2533
2534/// Wrap an `f64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2535///
2536/// # Examples
2537///
2538/// ```
2539/// use tenferro_tensor::{Tensor, TypedTensor};
2540///
2541/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
2542/// let tensor: Tensor = typed.into();
2543/// assert_eq!(tensor.shape(), &[2]);
2544/// ```
2545impl From<TypedTensor<f64>> for Tensor {
2546    fn from(t: TypedTensor<f64>) -> Self {
2547        Tensor::F64(t)
2548    }
2549}
2550
2551/// Wrap an `f32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2552///
2553/// # Examples
2554///
2555/// ```
2556/// use tenferro_tensor::{Tensor, TypedTensor};
2557///
2558/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
2559/// let tensor: Tensor = typed.into();
2560/// assert_eq!(tensor.shape(), &[2]);
2561/// ```
2562impl From<TypedTensor<f32>> for Tensor {
2563    fn from(t: TypedTensor<f32>) -> Self {
2564        Tensor::F32(t)
2565    }
2566}
2567
2568/// Wrap an `i64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2569///
2570/// # Examples
2571///
2572/// ```
2573/// use tenferro_tensor::{DType, Tensor, TypedTensor};
2574///
2575/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i64, 2]).unwrap();
2576/// let tensor: Tensor = typed.into();
2577/// assert_eq!(tensor.dtype(), DType::I64);
2578/// assert_eq!(tensor.shape(), &[2]);
2579/// ```
2580impl From<TypedTensor<i64>> for Tensor {
2581    fn from(t: TypedTensor<i64>) -> Self {
2582        Tensor::I64(t)
2583    }
2584}
2585
2586/// Wrap an `i32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2587///
2588/// # Examples
2589///
2590/// ```
2591/// use tenferro_tensor::{DType, Tensor, TypedTensor};
2592///
2593/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i32, 2]).unwrap();
2594/// let tensor: Tensor = typed.into();
2595/// assert_eq!(tensor.dtype(), DType::I32);
2596/// assert_eq!(tensor.shape(), &[2]);
2597/// ```
2598impl From<TypedTensor<i32>> for Tensor {
2599    fn from(t: TypedTensor<i32>) -> Self {
2600        Tensor::I32(t)
2601    }
2602}
2603
2604/// Wrap a `bool` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2605///
2606/// # Examples
2607///
2608/// ```
2609/// use tenferro_tensor::{DType, Tensor, TypedTensor};
2610///
2611/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![true, false]).unwrap();
2612/// let tensor: Tensor = typed.into();
2613/// assert_eq!(tensor.dtype(), DType::Bool);
2614/// assert_eq!(tensor.shape(), &[2]);
2615/// ```
2616impl From<TypedTensor<bool>> for Tensor {
2617    fn from(t: TypedTensor<bool>) -> Self {
2618        Tensor::Bool(t)
2619    }
2620}
2621
2622/// Wrap a [`Complex64`] [`TypedTensor`] into the corresponding [`Tensor`]
2623/// variant.
2624///
2625/// # Examples
2626///
2627/// ```
2628/// use num_complex::Complex64;
2629/// use tenferro_tensor::{Tensor, TypedTensor};
2630///
2631/// let typed = TypedTensor::from_vec_col_major(
2632///     vec![1],
2633///     vec![Complex64::new(1.0, 2.0)],
2634/// ).unwrap();
2635/// let tensor: Tensor = typed.into();
2636/// assert_eq!(tensor.shape(), &[1]);
2637/// ```
2638impl From<TypedTensor<Complex<f64>>> for Tensor {
2639    fn from(t: TypedTensor<Complex<f64>>) -> Self {
2640        Tensor::C64(t)
2641    }
2642}
2643
2644/// Wrap a [`Complex32`] [`TypedTensor`] into the corresponding [`Tensor`]
2645/// variant.
2646///
2647/// # Examples
2648///
2649/// ```
2650/// use num_complex::Complex32;
2651/// use tenferro_tensor::{Tensor, TypedTensor};
2652///
2653/// let typed = TypedTensor::from_vec_col_major(
2654///     vec![1],
2655///     vec![Complex32::new(1.0, 2.0)],
2656/// ).unwrap();
2657/// let tensor: Tensor = typed.into();
2658/// assert_eq!(tensor.shape(), &[1]);
2659/// ```
2660impl From<TypedTensor<Complex<f32>>> for Tensor {
2661    fn from(t: TypedTensor<Complex<f32>>) -> Self {
2662        Tensor::C32(t)
2663    }
2664}
2665
2666impl<'a> TensorView<'a> {
2667    /// Create a dynamic `f32` view over compact column-major host data.
2668    ///
2669    /// # Examples
2670    ///
2671    /// ```
2672    /// use tenferro_tensor::{DType, TensorView};
2673    ///
2674    /// let data = [1.0_f32, 2.0];
2675    /// let view = TensorView::f32(&[2], &data)?;
2676    /// assert_eq!(view.dtype(), DType::F32);
2677    /// # Ok::<(), tenferro_tensor::Error>(())
2678    /// ```
2679    pub fn f32(shape: &'a [usize], data: &'a [f32]) -> crate::Result<Self> {
2680        Ok(Self::F32(TypedTensorView::from_col_major(shape, data)?))
2681    }
2682
2683    /// Create a dynamic `f64` view over compact column-major host data.
2684    ///
2685    /// # Examples
2686    ///
2687    /// ```
2688    /// use tenferro_tensor::{DType, TensorView};
2689    ///
2690    /// let data = [1.0_f64, 2.0];
2691    /// let view = TensorView::f64(&[2], &data)?;
2692    /// assert_eq!(view.dtype(), DType::F64);
2693    /// # Ok::<(), tenferro_tensor::Error>(())
2694    /// ```
2695    pub fn f64(shape: &'a [usize], data: &'a [f64]) -> crate::Result<Self> {
2696        Ok(Self::F64(TypedTensorView::from_col_major(shape, data)?))
2697    }
2698
2699    /// Create a dynamic `i64` view over compact column-major host data.
2700    ///
2701    /// # Examples
2702    ///
2703    /// ```
2704    /// use tenferro_tensor::{DType, TensorView};
2705    ///
2706    /// let data = [1_i64, 2];
2707    /// let view = TensorView::i64(&[2], &data)?;
2708    /// assert_eq!(view.dtype(), DType::I64);
2709    /// # Ok::<(), tenferro_tensor::Error>(())
2710    /// ```
2711    pub fn i64(shape: &'a [usize], data: &'a [i64]) -> crate::Result<Self> {
2712        Ok(Self::I64(TypedTensorView::from_col_major(shape, data)?))
2713    }
2714
2715    /// Create a dynamic `i32` view over compact column-major host data.
2716    ///
2717    /// # Examples
2718    ///
2719    /// ```
2720    /// use tenferro_tensor::{DType, TensorView};
2721    ///
2722    /// let data = [1_i32, 2];
2723    /// let view = TensorView::i32(&[2], &data)?;
2724    /// assert_eq!(view.dtype(), DType::I32);
2725    /// # Ok::<(), tenferro_tensor::Error>(())
2726    /// ```
2727    pub fn i32(shape: &'a [usize], data: &'a [i32]) -> crate::Result<Self> {
2728        Ok(Self::I32(TypedTensorView::from_col_major(shape, data)?))
2729    }
2730
2731    /// Create a dynamic `bool` view over compact column-major host data.
2732    ///
2733    /// # Examples
2734    ///
2735    /// ```
2736    /// use tenferro_tensor::{DType, TensorView};
2737    ///
2738    /// let data = [true, false];
2739    /// let view = TensorView::bool(&[2], &data)?;
2740    /// assert_eq!(view.dtype(), DType::Bool);
2741    /// # Ok::<(), tenferro_tensor::Error>(())
2742    /// ```
2743    pub fn bool(shape: &'a [usize], data: &'a [bool]) -> crate::Result<Self> {
2744        Ok(Self::Bool(TypedTensorView::from_col_major(shape, data)?))
2745    }
2746
2747    /// Create a dynamic `Complex32` view over compact column-major host data.
2748    ///
2749    /// # Examples
2750    ///
2751    /// ```
2752    /// use num_complex::Complex32;
2753    /// use tenferro_tensor::{DType, TensorView};
2754    ///
2755    /// let data = [Complex32::new(1.0, 2.0)];
2756    /// let view = TensorView::c32(&[1], &data)?;
2757    /// assert_eq!(view.dtype(), DType::C32);
2758    /// # Ok::<(), tenferro_tensor::Error>(())
2759    /// ```
2760    pub fn c32(shape: &'a [usize], data: &'a [Complex32]) -> crate::Result<Self> {
2761        Ok(Self::C32(TypedTensorView::from_col_major(shape, data)?))
2762    }
2763
2764    /// Create a dynamic `Complex64` view over compact column-major host data.
2765    ///
2766    /// # Examples
2767    ///
2768    /// ```
2769    /// use num_complex::Complex64;
2770    /// use tenferro_tensor::{DType, TensorView};
2771    ///
2772    /// let data = [Complex64::new(1.0, 2.0)];
2773    /// let view = TensorView::c64(&[1], &data)?;
2774    /// assert_eq!(view.dtype(), DType::C64);
2775    /// # Ok::<(), tenferro_tensor::Error>(())
2776    /// ```
2777    pub fn c64(shape: &'a [usize], data: &'a [Complex64]) -> crate::Result<Self> {
2778        Ok(Self::C64(TypedTensorView::from_col_major(shape, data)?))
2779    }
2780
2781    pub fn dtype(&self) -> DType {
2782        match self {
2783            Self::F32(_) => DType::F32,
2784            Self::F64(_) => DType::F64,
2785            Self::I32(_) => DType::I32,
2786            Self::I64(_) => DType::I64,
2787            Self::Bool(_) => DType::Bool,
2788            Self::C32(_) => DType::C32,
2789            Self::C64(_) => DType::C64,
2790        }
2791    }
2792
2793    pub fn shape(&self) -> &[usize] {
2794        match self {
2795            Self::F32(t) => t.shape(),
2796            Self::F64(t) => t.shape(),
2797            Self::I32(t) => t.shape(),
2798            Self::I64(t) => t.shape(),
2799            Self::Bool(t) => t.shape(),
2800            Self::C32(t) => t.shape(),
2801            Self::C64(t) => t.shape(),
2802        }
2803    }
2804
2805    /// Return strides in element units.
2806    pub fn strides(&self) -> &[isize] {
2807        match self {
2808            Self::F32(t) => t.strides(),
2809            Self::F64(t) => t.strides(),
2810            Self::I32(t) => t.strides(),
2811            Self::I64(t) => t.strides(),
2812            Self::Bool(t) => t.strides(),
2813            Self::C32(t) => t.strides(),
2814            Self::C64(t) => t.strides(),
2815        }
2816    }
2817
2818    /// Return the physical element offset.
2819    pub fn offset(&self) -> isize {
2820        match self {
2821            Self::F32(t) => t.offset(),
2822            Self::F64(t) => t.offset(),
2823            Self::I32(t) => t.offset(),
2824            Self::I64(t) => t.offset(),
2825            Self::Bool(t) => t.offset(),
2826            Self::C32(t) => t.offset(),
2827            Self::C64(t) => t.offset(),
2828        }
2829    }
2830
2831    /// Compute the physical element offset for a logical index.
2832    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2833        match self {
2834            Self::F32(t) => t.layout_linear_offset(indices),
2835            Self::F64(t) => t.layout_linear_offset(indices),
2836            Self::I32(t) => t.layout_linear_offset(indices),
2837            Self::I64(t) => t.layout_linear_offset(indices),
2838            Self::Bool(t) => t.layout_linear_offset(indices),
2839            Self::C32(t) => t.layout_linear_offset(indices),
2840            Self::C64(t) => t.layout_linear_offset(indices),
2841        }
2842    }
2843
2844    /// Return whether this view is compact column-major.
2845    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
2846        match self {
2847            Self::F32(t) => t.is_col_major_contiguous(),
2848            Self::F64(t) => t.is_col_major_contiguous(),
2849            Self::I32(t) => t.is_col_major_contiguous(),
2850            Self::I64(t) => t.is_col_major_contiguous(),
2851            Self::Bool(t) => t.is_col_major_contiguous(),
2852            Self::C32(t) => t.is_col_major_contiguous(),
2853            Self::C64(t) => t.is_col_major_contiguous(),
2854        }
2855    }
2856
2857    /// Return a compact string summary of this view's layout metadata.
2858    pub fn layout_summary(&self) -> String {
2859        layout_summary(self.shape(), self.strides(), self.offset())
2860    }
2861
2862    /// Assert this view is compact column-major.
2863    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
2864        assert_layout_col_major_contiguous(
2865            self.is_col_major_contiguous()?,
2866            self.shape(),
2867            self.strides(),
2868            self.offset(),
2869            "TensorView::assert_col_major_contiguous",
2870        )
2871    }
2872
2873    /// Materialize this host view into an owned tensor.
2874    ///
2875    /// This method has no backend context and does not download backend
2876    /// buffers. Use a backend-specific `TensorViewCanonicalization` method or
2877    /// an explicit device transfer before materializing backend views on the
2878    /// host.
2879    ///
2880    /// # Examples
2881    ///
2882    /// ```rust
2883    /// use tenferro_tensor::{DType, TensorView};
2884    ///
2885    /// let data = [1.0_f64, 2.0];
2886    /// let view = TensorView::f64(&[2], &data)?;
2887    /// let tensor = view.to_tensor()?;
2888    /// assert_eq!(tensor.dtype(), DType::F64);
2889    /// # Ok::<(), tenferro_tensor::Error>(())
2890    /// ```
2891    pub fn to_tensor(&self) -> crate::Result<Tensor> {
2892        match self {
2893            Self::F32(t) => {
2894                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::F32)
2895            }
2896            Self::F64(t) => {
2897                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::F64)
2898            }
2899            Self::I32(t) => {
2900                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::I32)
2901            }
2902            Self::I64(t) => {
2903                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::I64)
2904            }
2905            Self::Bool(t) => {
2906                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::Bool)
2907            }
2908            Self::C32(t) => {
2909                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::C32)
2910            }
2911            Self::C64(t) => {
2912                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::C64)
2913            }
2914        }
2915    }
2916}
2917
2918impl<'a> TensorViewMut<'a> {
2919    /// Create a dynamic `f64` mutable view over compact column-major host data.
2920    pub fn f64(shape: &'a [usize], data: &'a mut [f64]) -> crate::Result<Self> {
2921        Ok(Self::F64(TypedTensorViewMut::from_col_major(shape, data)?))
2922    }
2923
2924    pub fn dtype(&self) -> DType {
2925        match self {
2926            Self::F32(_) => DType::F32,
2927            Self::F64(_) => DType::F64,
2928            Self::I32(_) => DType::I32,
2929            Self::I64(_) => DType::I64,
2930            Self::Bool(_) => DType::Bool,
2931            Self::C32(_) => DType::C32,
2932            Self::C64(_) => DType::C64,
2933        }
2934    }
2935
2936    pub fn shape(&self) -> &[usize] {
2937        match self {
2938            Self::F32(t) => t.shape(),
2939            Self::F64(t) => t.shape(),
2940            Self::I32(t) => t.shape(),
2941            Self::I64(t) => t.shape(),
2942            Self::Bool(t) => t.shape(),
2943            Self::C32(t) => t.shape(),
2944            Self::C64(t) => t.shape(),
2945        }
2946    }
2947
2948    pub fn strides(&self) -> &[isize] {
2949        match self {
2950            Self::F32(t) => t.strides(),
2951            Self::F64(t) => t.strides(),
2952            Self::I32(t) => t.strides(),
2953            Self::I64(t) => t.strides(),
2954            Self::Bool(t) => t.strides(),
2955            Self::C32(t) => t.strides(),
2956            Self::C64(t) => t.strides(),
2957        }
2958    }
2959
2960    pub fn offset(&self) -> isize {
2961        match self {
2962            Self::F32(t) => t.offset(),
2963            Self::F64(t) => t.offset(),
2964            Self::I32(t) => t.offset(),
2965            Self::I64(t) => t.offset(),
2966            Self::Bool(t) => t.offset(),
2967            Self::C32(t) => t.offset(),
2968            Self::C64(t) => t.offset(),
2969        }
2970    }
2971
2972    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2973        match self {
2974            Self::F32(t) => t.layout_linear_offset(indices),
2975            Self::F64(t) => t.layout_linear_offset(indices),
2976            Self::I32(t) => t.layout_linear_offset(indices),
2977            Self::I64(t) => t.layout_linear_offset(indices),
2978            Self::Bool(t) => t.layout_linear_offset(indices),
2979            Self::C32(t) => t.layout_linear_offset(indices),
2980            Self::C64(t) => t.layout_linear_offset(indices),
2981        }
2982    }
2983
2984    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
2985        match self {
2986            Self::F32(t) => t.is_col_major_contiguous(),
2987            Self::F64(t) => t.is_col_major_contiguous(),
2988            Self::I32(t) => t.is_col_major_contiguous(),
2989            Self::I64(t) => t.is_col_major_contiguous(),
2990            Self::Bool(t) => t.is_col_major_contiguous(),
2991            Self::C32(t) => t.is_col_major_contiguous(),
2992            Self::C64(t) => t.is_col_major_contiguous(),
2993        }
2994    }
2995
2996    pub fn layout_summary(&self) -> String {
2997        layout_summary(self.shape(), self.strides(), self.offset())
2998    }
2999
3000    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
3001        assert_layout_col_major_contiguous(
3002            self.is_col_major_contiguous()?,
3003            self.shape(),
3004            self.strides(),
3005            self.offset(),
3006            "TensorViewMut::assert_col_major_contiguous",
3007        )
3008    }
3009
3010    pub fn as_read_only(&self) -> TensorView<'_> {
3011        match self {
3012            Self::F32(t) => TensorView::F32(t.as_read_only()),
3013            Self::F64(t) => TensorView::F64(t.as_read_only()),
3014            Self::I32(t) => TensorView::I32(t.as_read_only()),
3015            Self::I64(t) => TensorView::I64(t.as_read_only()),
3016            Self::Bool(t) => TensorView::Bool(t.as_read_only()),
3017            Self::C32(t) => TensorView::C32(t.as_read_only()),
3018            Self::C64(t) => TensorView::C64(t.as_read_only()),
3019        }
3020    }
3021
3022    pub fn copy_from_tensor(&mut self, src: &Tensor) -> crate::Result<()> {
3023        copy_tensor_to_view_mut(self, src, "TensorViewMut::copy_from_tensor")
3024    }
3025}
3026
3027impl<'a> TensorRead<'a> {
3028    pub fn from_tensor(tensor: &'a Tensor) -> Self {
3029        Self::Tensor(tensor)
3030    }
3031
3032    pub fn from_view(view: TensorView<'a>) -> Self {
3033        Self::View(view)
3034    }
3035
3036    pub fn dtype(&self) -> DType {
3037        match self {
3038            Self::Tensor(tensor) => tensor.dtype(),
3039            Self::View(view) => view.dtype(),
3040        }
3041    }
3042
3043    pub fn shape(&self) -> &[usize] {
3044        match self {
3045            Self::Tensor(tensor) => tensor.shape(),
3046            Self::View(view) => view.shape(),
3047        }
3048    }
3049
3050    pub fn strides(&self) -> crate::Result<Vec<isize>> {
3051        match self {
3052            Self::Tensor(tensor) => col_major_strides(tensor.shape()),
3053            Self::View(view) => Ok(view.strides().to_vec()),
3054        }
3055    }
3056
3057    pub fn offset(&self) -> isize {
3058        match self {
3059            Self::Tensor(_) => 0,
3060            Self::View(view) => view.offset(),
3061        }
3062    }
3063
3064    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
3065        match self {
3066            Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
3067            Self::View(view) => view.layout_linear_offset(indices),
3068        }
3069    }
3070
3071    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
3072        match self {
3073            Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
3074            Self::View(view) => view.is_col_major_contiguous(),
3075        }
3076    }
3077
3078    pub fn layout_summary(&self) -> String {
3079        let strides = match self.strides() {
3080            Ok(strides) => strides,
3081            Err(err) => return format!("layout unavailable: {err}"),
3082        };
3083        layout_summary(self.shape(), &strides, self.offset())
3084    }
3085
3086    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
3087        let strides = self.strides()?;
3088        assert_layout_col_major_contiguous(
3089            self.is_col_major_contiguous()?,
3090            self.shape(),
3091            &strides,
3092            self.offset(),
3093            "TensorRead::assert_col_major_contiguous",
3094        )
3095    }
3096
3097    pub fn as_tensor(&self) -> Option<&'a Tensor> {
3098        match self {
3099            Self::Tensor(tensor) => Some(*tensor),
3100            Self::View(_) => None,
3101        }
3102    }
3103
3104    /// Convert an owned tensor reference or host view into an owned tensor.
3105    ///
3106    /// This method clones owned tensor inputs and materializes host views. It
3107    /// has no backend context and does not download backend buffers. Use a
3108    /// backend-specific `TensorViewCanonicalization` method or an explicit
3109    /// device transfer before materializing backend views on the host.
3110    ///
3111    /// # Examples
3112    ///
3113    /// ```rust
3114    /// use tenferro_tensor::{TensorRead, TensorView};
3115    ///
3116    /// let data = [1_i32, 2, 3];
3117    /// let read = TensorRead::from_view(TensorView::i32(&[3], &data)?);
3118    /// let tensor = read.to_tensor()?;
3119    /// assert_eq!(tensor.shape(), &[3]);
3120    /// # Ok::<(), tenferro_tensor::Error>(())
3121    /// ```
3122    pub fn to_tensor(&self) -> crate::Result<Tensor> {
3123        match self {
3124            Self::Tensor(tensor) => Ok((*tensor).clone()),
3125            Self::View(view) => view.to_tensor(),
3126        }
3127    }
3128}
3129
3130impl<'a> TensorWrite<'a> {
3131    pub fn from_tensor(tensor: &'a mut Tensor) -> Self {
3132        Self::Tensor(tensor)
3133    }
3134
3135    pub fn from_view(view: TensorViewMut<'a>) -> Self {
3136        Self::View(view)
3137    }
3138
3139    pub fn dtype(&self) -> DType {
3140        match self {
3141            Self::Tensor(tensor) => tensor.dtype(),
3142            Self::View(view) => view.dtype(),
3143        }
3144    }
3145
3146    pub fn shape(&self) -> &[usize] {
3147        match self {
3148            Self::Tensor(tensor) => tensor.shape(),
3149            Self::View(view) => view.shape(),
3150        }
3151    }
3152
3153    pub fn strides(&self) -> crate::Result<Vec<isize>> {
3154        match self {
3155            Self::Tensor(tensor) => col_major_strides(tensor.shape()),
3156            Self::View(view) => Ok(view.strides().to_vec()),
3157        }
3158    }
3159
3160    pub fn offset(&self) -> isize {
3161        match self {
3162            Self::Tensor(_) => 0,
3163            Self::View(view) => view.offset(),
3164        }
3165    }
3166
3167    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
3168        match self {
3169            Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
3170            Self::View(view) => view.layout_linear_offset(indices),
3171        }
3172    }
3173
3174    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
3175        match self {
3176            Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
3177            Self::View(view) => view.is_col_major_contiguous(),
3178        }
3179    }
3180
3181    pub fn layout_summary(&self) -> String {
3182        let strides = match self.strides() {
3183            Ok(strides) => strides,
3184            Err(err) => return format!("layout unavailable: {err}"),
3185        };
3186        layout_summary(self.shape(), &strides, self.offset())
3187    }
3188
3189    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
3190        let strides = self.strides()?;
3191        assert_layout_col_major_contiguous(
3192            self.is_col_major_contiguous()?,
3193            self.shape(),
3194            &strides,
3195            self.offset(),
3196            "TensorWrite::assert_col_major_contiguous",
3197        )
3198    }
3199
3200    pub fn copy_from_tensor(&mut self, src: &Tensor) -> crate::Result<()> {
3201        match self {
3202            Self::Tensor(dst) => copy_tensor_to_tensor(dst, src, "TensorWrite::copy_from_tensor"),
3203            Self::View(view) => copy_tensor_to_view_mut(view, src, "TensorWrite::copy_from_tensor"),
3204        }
3205    }
3206}
3207
3208/// Column-major strides derived from a shape.
3209///
3210/// # Examples
3211///
3212/// ```rust
3213/// use tenferro_tensor::col_major_strides;
3214///
3215/// assert_eq!(col_major_strides(&[2, 3])?, vec![1, 2]);
3216/// # Ok::<(), tenferro_tensor::Error>(())
3217/// ```
3218pub fn col_major_strides(shape: &[usize]) -> crate::Result<Vec<isize>> {
3219    let mut strides = Vec::with_capacity(shape.len());
3220    let mut stride = 1isize;
3221    for &extent in shape {
3222        strides.push(stride);
3223        let extent = isize::try_from(extent).map_err(|_| crate::Error::InvalidConfig {
3224            op: "col_major_strides",
3225            message: format!("shape extent {extent} does not fit in isize"),
3226        })?;
3227        stride = stride
3228            .checked_mul(extent)
3229            .ok_or_else(|| crate::Error::InvalidConfig {
3230                op: "col_major_strides",
3231                message: format!("column-major stride overflows for shape {shape:?}"),
3232            })?;
3233    }
3234    Ok(strides)
3235}
3236
3237fn try_linear_offset_for_shape(
3238    shape: &[usize],
3239    indices: &[usize],
3240    op: &'static str,
3241) -> crate::Result<usize> {
3242    if indices.len() != shape.len() {
3243        return Err(crate::Error::RankMismatch {
3244            op,
3245            expected: shape.len(),
3246            actual: indices.len(),
3247        });
3248    }
3249    let mut offset = 0usize;
3250    let mut stride = 1usize;
3251    for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
3252        if idx >= extent {
3253            return Err(crate::Error::InvalidConfig {
3254                op,
3255                message: format!("index {idx} out of bounds for axis {axis} extent {extent}"),
3256            });
3257        }
3258        offset = offset
3259            .checked_add(
3260                idx.checked_mul(stride)
3261                    .ok_or_else(|| crate::Error::InvalidConfig {
3262                        op,
3263                        message: "linear offset multiply overflows".to_string(),
3264                    })?,
3265            )
3266            .ok_or_else(|| crate::Error::InvalidConfig {
3267                op,
3268                message: "linear offset add overflows".to_string(),
3269            })?;
3270        stride = stride
3271            .checked_mul(extent)
3272            .ok_or_else(|| crate::Error::InvalidConfig {
3273                op,
3274                message: "linear offset stride overflows".to_string(),
3275            })?;
3276    }
3277    Ok(offset)
3278}
3279
3280fn checked_view_offset_result(
3281    shape: &[usize],
3282    strides: &[isize],
3283    base_offset: isize,
3284    indices: &[usize],
3285    op: &'static str,
3286) -> crate::Result<usize> {
3287    if indices.len() != shape.len() {
3288        return Err(crate::Error::RankMismatch {
3289            op,
3290            expected: shape.len(),
3291            actual: indices.len(),
3292        });
3293    }
3294    for (axis, (&index, &extent)) in indices.iter().zip(shape).enumerate() {
3295        if index >= extent {
3296            return Err(crate::Error::InvalidConfig {
3297                op,
3298                message: format!("index {index} out of bounds for axis {axis} extent {extent}"),
3299            });
3300        }
3301    }
3302    checked_view_offset(shape, strides, base_offset, indices).ok_or_else(|| {
3303        crate::Error::InvalidConfig {
3304            op,
3305            message: format!(
3306                "layout offset overflow for shape={shape:?} strides={strides:?} offset={base_offset} indices={indices:?}"
3307            ),
3308        }
3309    })
3310}
3311
3312fn layout_summary(shape: &[usize], strides: &[isize], offset: isize) -> String {
3313    format!("shape={shape:?} strides={strides:?} offset={offset}")
3314}
3315
3316fn assert_layout_col_major_contiguous(
3317    is_contiguous: bool,
3318    shape: &[usize],
3319    strides: &[isize],
3320    offset: isize,
3321    op: &'static str,
3322) -> crate::Result<()> {
3323    if is_contiguous {
3324        Ok(())
3325    } else {
3326        Err(crate::Error::InvalidConfig {
3327            op,
3328            message: format!(
3329                "expected compact column-major layout, got {}",
3330                layout_summary(shape, strides, offset)
3331            ),
3332        })
3333    }
3334}
3335
3336fn validate_tensor_copy_target(
3337    dst_dtype: DType,
3338    dst_shape: &[usize],
3339    src: &Tensor,
3340    op: &'static str,
3341) -> crate::Result<()> {
3342    if dst_dtype != src.dtype() {
3343        return Err(crate::Error::DTypeMismatch {
3344            op,
3345            lhs: dst_dtype,
3346            rhs: src.dtype(),
3347        });
3348    }
3349    if dst_shape != src.shape() {
3350        return Err(crate::Error::ShapeMismatch {
3351            op,
3352            lhs: dst_shape.to_vec(),
3353            rhs: src.shape().to_vec(),
3354        });
3355    }
3356    Ok(())
3357}
3358
3359fn copy_tensor_to_tensor(dst: &mut Tensor, src: &Tensor, op: &'static str) -> crate::Result<()> {
3360    validate_tensor_copy_target(dst.dtype(), dst.shape(), src, op)?;
3361    macro_rules! copy_variant {
3362        ($variant:ident) => {
3363            if let (Tensor::$variant(dst), Tensor::$variant(src)) = (&mut *dst, src) {
3364                dst.host_data_mut()?.clone_from_slice(src.host_data()?);
3365                return Ok(());
3366            }
3367        };
3368    }
3369    copy_variant!(F32);
3370    copy_variant!(F64);
3371    copy_variant!(I32);
3372    copy_variant!(I64);
3373    copy_variant!(Bool);
3374    copy_variant!(C32);
3375    copy_variant!(C64);
3376    Err(crate::Error::DTypeMismatch {
3377        op,
3378        lhs: dst.dtype(),
3379        rhs: src.dtype(),
3380    })
3381}
3382
3383fn copy_tensor_to_view_mut(
3384    dst: &mut TensorViewMut<'_>,
3385    src: &Tensor,
3386    op: &'static str,
3387) -> crate::Result<()> {
3388    validate_tensor_copy_target(dst.dtype(), dst.shape(), src, op)?;
3389    macro_rules! copy_variant {
3390        ($variant:ident) => {
3391            if let (TensorViewMut::$variant(dst), Tensor::$variant(src)) = (&mut *dst, src) {
3392                return dst.copy_from_contiguous(src);
3393            }
3394        };
3395    }
3396    copy_variant!(F32);
3397    copy_variant!(F64);
3398    copy_variant!(I32);
3399    copy_variant!(I64);
3400    copy_variant!(Bool);
3401    copy_variant!(C32);
3402    copy_variant!(C64);
3403    Err(crate::Error::DTypeMismatch {
3404        op,
3405        lhs: dst.dtype(),
3406        rhs: src.dtype(),
3407    })
3408}
3409
3410fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
3411    shape.iter().try_fold(1usize, |acc, &dim| {
3412        acc.checked_mul(dim)
3413            .ok_or_else(|| crate::Error::InvalidConfig {
3414                op,
3415                message: format!("shape product overflows for shape {shape:?}"),
3416            })
3417    })
3418}
3419
3420fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
3421    let n = try_shape_product(shape, op)?;
3422    if data_len != n {
3423        return Err(crate::Error::InvalidConfig {
3424            op,
3425            message: format!("data length {data_len} does not match shape product {n}"),
3426        });
3427    }
3428    Ok(())
3429}
3430
3431fn try_compact_layout<R: TensorRank>(
3432    shape: impl Into<R::Shape>,
3433    op: &'static str,
3434) -> crate::Result<TensorLayout<R>> {
3435    TensorLayout::compact(shape.into()).map_err(|err| tensor_layout_error(op, err))
3436}
3437
3438fn tensor_layout_error(op: &'static str, err: tenferro_tensor_core::Error) -> crate::Error {
3439    match err {
3440        tenferro_tensor_core::Error::RankMismatch { expected, actual } => {
3441            crate::Error::RankMismatch {
3442                op,
3443                expected,
3444                actual,
3445            }
3446        }
3447        tenferro_tensor_core::Error::AxisOutOfBounds { axis, rank } => {
3448            crate::Error::AxisOutOfBounds { op, axis, rank }
3449        }
3450        tenferro_tensor_core::Error::DuplicateAxis { axis } => crate::Error::DuplicateAxis {
3451            op,
3452            axis,
3453            role: "permutation",
3454        },
3455        tenferro_tensor_core::Error::InvalidPermutationLength { expected, actual } => {
3456            crate::Error::RankMismatch {
3457                op,
3458                expected,
3459                actual,
3460            }
3461        }
3462        other => crate::Error::InvalidConfig {
3463            op,
3464            message: other.to_string(),
3465        },
3466    }
3467}
3468
3469fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
3470    if shape.contains(&0) {
3471        return Ok(0);
3472    }
3473    shape.iter().try_fold(1usize, |product, &dim| {
3474        product
3475            .checked_mul(dim)
3476            .ok_or_else(|| crate::Error::InvalidConfig {
3477                op,
3478                message: format!("shape product overflows for shape {shape:?}"),
3479            })
3480    })
3481}
3482
3483fn checked_view_offset(
3484    shape: &[usize],
3485    strides: &[isize],
3486    base_offset: isize,
3487    indices: &[usize],
3488) -> Option<usize> {
3489    if indices.len() != shape.len() {
3490        return None;
3491    }
3492
3493    let mut offset = base_offset;
3494    for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
3495        if index >= extent {
3496            return None;
3497        }
3498        let index = isize::try_from(index).ok()?;
3499        let delta = index.checked_mul(stride)?;
3500        offset = offset.checked_add(delta)?;
3501    }
3502
3503    usize::try_from(offset).ok()
3504}
3505
3506fn for_each_layout_offset_col_major(
3507    shape: &[usize],
3508    strides: &[isize],
3509    base_offset: isize,
3510    op: &'static str,
3511    mut f: impl FnMut(usize) -> crate::Result<()>,
3512) -> crate::Result<()> {
3513    if shape.len() != strides.len() {
3514        return Err(crate::Error::InvalidConfig {
3515            op,
3516            message: format!(
3517                "shape rank {} does not match stride rank {}",
3518                shape.len(),
3519                strides.len()
3520            ),
3521        });
3522    }
3523
3524    if shape.contains(&0) {
3525        return Ok(());
3526    }
3527
3528    let mut offset = base_offset;
3529    if shape.is_empty() {
3530        let offset = usize::try_from(offset).map_err(|_| crate::Error::InvalidConfig {
3531            op,
3532            message: "view offset is negative".to_string(),
3533        })?;
3534        return f(offset);
3535    }
3536
3537    let mut index = vec![0usize; shape.len()];
3538    loop {
3539        let physical = usize::try_from(offset).map_err(|_| crate::Error::InvalidConfig {
3540            op,
3541            message: "view offset is negative".to_string(),
3542        })?;
3543        f(physical)?;
3544
3545        let mut advance_axis = None;
3546        for axis in 0..shape.len() {
3547            let next_index =
3548                index[axis]
3549                    .checked_add(1)
3550                    .ok_or_else(|| crate::Error::InvalidConfig {
3551                        op,
3552                        message: "logical index overflows".to_string(),
3553                    })?;
3554            if next_index < shape[axis] {
3555                advance_axis = Some((axis, next_index));
3556                break;
3557            }
3558        }
3559
3560        let Some((advance_axis, next_index)) = advance_axis else {
3561            return Ok(());
3562        };
3563
3564        for axis in 0..advance_axis {
3565            let steps = isize::try_from(index[axis]).map_err(|_| crate::Error::InvalidConfig {
3566                op,
3567                message: "logical index does not fit in isize".to_string(),
3568            })?;
3569            let rewind =
3570                strides[axis]
3571                    .checked_mul(steps)
3572                    .ok_or_else(|| crate::Error::InvalidConfig {
3573                        op,
3574                        message: "stride rewind overflows".to_string(),
3575                    })?;
3576            offset = offset
3577                .checked_sub(rewind)
3578                .ok_or_else(|| crate::Error::InvalidConfig {
3579                    op,
3580                    message: "view offset rewind overflows".to_string(),
3581                })?;
3582            index[axis] = 0;
3583        }
3584
3585        offset = offset.checked_add(strides[advance_axis]).ok_or_else(|| {
3586            crate::Error::InvalidConfig {
3587                op,
3588                message: "view offset overflows".to_string(),
3589            }
3590        })?;
3591        index[advance_axis] = next_index;
3592    }
3593}
3594
3595fn reachable_layout_span(
3596    shape: &[usize],
3597    strides: &[isize],
3598    offset: isize,
3599) -> crate::Result<Option<(usize, usize)>> {
3600    if shape.contains(&0) {
3601        return Ok(None);
3602    }
3603
3604    let mut min_offset = offset;
3605    let mut max_offset = offset;
3606    for (&extent, &stride) in shape.iter().zip(strides) {
3607        let steps =
3608            isize::try_from(extent.saturating_sub(1)).map_err(|_| crate::Error::InvalidConfig {
3609                op: "TypedTensorViewMut::try_multi_slice_mut",
3610                message: "shape extent does not fit in isize".to_string(),
3611            })?;
3612        let end = stride
3613            .checked_mul(steps)
3614            .ok_or_else(|| crate::Error::InvalidConfig {
3615                op: "TypedTensorViewMut::try_multi_slice_mut",
3616                message: "stride span overflows".to_string(),
3617            })?;
3618        let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
3619        min_offset =
3620            min_offset
3621                .checked_add(axis_min)
3622                .ok_or_else(|| crate::Error::InvalidConfig {
3623                    op: "TypedTensorViewMut::try_multi_slice_mut",
3624                    message: "minimum reachable offset overflows".to_string(),
3625                })?;
3626        max_offset =
3627            max_offset
3628                .checked_add(axis_max)
3629                .ok_or_else(|| crate::Error::InvalidConfig {
3630                    op: "TypedTensorViewMut::try_multi_slice_mut",
3631                    message: "maximum reachable offset overflows".to_string(),
3632                })?;
3633    }
3634
3635    let min_offset = usize::try_from(min_offset).map_err(|_| crate::Error::InvalidConfig {
3636        op: "TypedTensorViewMut::try_multi_slice_mut",
3637        message: "minimum reachable offset is negative".to_string(),
3638    })?;
3639    let max_offset = usize::try_from(max_offset).map_err(|_| crate::Error::InvalidConfig {
3640        op: "TypedTensorViewMut::try_multi_slice_mut",
3641        message: "maximum reachable offset is negative".to_string(),
3642    })?;
3643    Ok(Some((min_offset, max_offset)))
3644}
3645
3646fn split_two_mut_ranges<T>(
3647    data: &mut [T],
3648    first: (usize, usize),
3649    second: (usize, usize),
3650) -> Option<(&mut [T], &mut [T])> {
3651    if first.1 < second.0 {
3652        let (_, after_first_start) = data.split_at_mut(first.0);
3653        let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
3654        let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
3655        let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
3656        Some((first_slice, second_slice))
3657    } else if second.1 < first.0 {
3658        let (_, after_second_start) = data.split_at_mut(second.0);
3659        let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
3660        let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
3661        let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
3662        Some((first_slice, second_slice))
3663    } else {
3664        None
3665    }
3666}
3667
3668fn adjusted_view_offset(offset: isize, span_start: usize) -> crate::Result<isize> {
3669    let span_start = isize::try_from(span_start).map_err(|_| crate::Error::InvalidConfig {
3670        op: "TypedTensorViewMut::try_multi_slice_mut",
3671        message: "view span start does not fit in isize".to_string(),
3672    })?;
3673    offset
3674        .checked_sub(span_start)
3675        .ok_or_else(|| crate::Error::InvalidConfig {
3676            op: "TypedTensorViewMut::try_multi_slice_mut",
3677            message: "adjusted view offset overflows".to_string(),
3678        })
3679}
3680
3681fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
3682    layout: &TensorLayout<R>,
3683    offset: isize,
3684    data: &'a mut [T],
3685    placement: Placement,
3686) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
3687    let shape = R::shape_from_vec(layout.shape().to_vec().into())
3688        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
3689    let strides = R::strides_from_vec(layout.strides().to_vec().into())
3690        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
3691    TypedTensorViewMut::from_buffer_ref_mut(
3692        shape,
3693        strides,
3694        offset,
3695        TensorBufferRefMut::Host(data),
3696        placement,
3697        "TypedTensorViewMut::try_multi_slice_mut",
3698    )
3699}
3700
3701fn contiguous_layout_slice<'a, T, R: TensorRank>(
3702    layout: &TensorLayout<R>,
3703    data: &'a [T],
3704    op: &'static str,
3705) -> crate::Result<&'a [T]> {
3706    if !layout
3707        .is_compact_col_major()
3708        .map_err(|err| tensor_layout_error(op, err))?
3709    {
3710        return Err(crate::Error::InvalidConfig {
3711            op,
3712            message: "view is not contiguous column-major".to_string(),
3713        });
3714    }
3715    let len = checked_view_element_count(layout.shape(), op)?;
3716    let start = usize::try_from(layout.offset()).map_err(|_| crate::Error::InvalidConfig {
3717        op,
3718        message: "view offset is negative".to_string(),
3719    })?;
3720    let end = start
3721        .checked_add(len)
3722        .ok_or_else(|| crate::Error::InvalidConfig {
3723            op,
3724            message: "contiguous view range overflows".to_string(),
3725        })?;
3726    data.get(start..end)
3727        .ok_or_else(|| crate::Error::InvalidConfig {
3728            op,
3729            message: "contiguous view range is outside host buffer".to_string(),
3730        })
3731}
3732
3733fn materialize_view_buffer_col_major<T: Clone>(
3734    shape: &[usize],
3735    strides: &[isize],
3736    offset: isize,
3737    buffer: &TensorBufferRef<'_, T>,
3738    op: &'static str,
3739) -> crate::Result<Vec<T>> {
3740    let source = match buffer {
3741        TensorBufferRef::Host(data) => *data,
3742        TensorBufferRef::Backend(_) => return Err(crate::Error::backend_failure(
3743            op,
3744            "backend buffers cannot be materialized through host memory; download explicitly first",
3745        )),
3746    };
3747
3748    let n_elements = checked_view_element_count(shape, op)?;
3749    let mut out = Vec::with_capacity(n_elements);
3750    for_each_layout_offset_col_major(shape, strides, offset, op, |physical| {
3751        let value = source
3752            .get(physical)
3753            .ok_or_else(|| crate::Error::InvalidConfig {
3754                op,
3755                message: "view offset is outside host buffer".to_string(),
3756            })?;
3757        out.push(value.clone());
3758        Ok(())
3759    })?;
3760    Ok(out)
3761}
3762
3763fn relaxed_col_major_contiguous(
3764    shape: &[usize],
3765    strides: &[isize],
3766    op: &'static str,
3767) -> crate::Result<bool> {
3768    if shape.contains(&0) {
3769        return Ok(true);
3770    }
3771
3772    let mut expected = 1isize;
3773    for (&extent, &stride) in shape.iter().zip(strides) {
3774        if extent <= 1 {
3775            continue;
3776        }
3777        if stride != expected {
3778            return Ok(false);
3779        }
3780        let extent = isize::try_from(extent).map_err(|_| crate::Error::InvalidConfig {
3781            op,
3782            message: "shape extent does not fit in isize".to_string(),
3783        })?;
3784        expected = expected
3785            .checked_mul(extent)
3786            .ok_or_else(|| crate::Error::InvalidConfig {
3787                op,
3788                message: "contiguous stride overflows".to_string(),
3789            })?;
3790    }
3791    Ok(true)
3792}
3793
3794fn reshape_layout_dyn<R: TensorRank>(
3795    layout: &TensorLayout<R>,
3796    shape: &[usize],
3797    buffer_len: usize,
3798    op: &'static str,
3799) -> crate::Result<TensorLayout<DynRank>> {
3800    match layout.reshape_view_as::<DynRank>(shape.to_vec().into(), buffer_len) {
3801        Ok(layout) => Ok(layout),
3802        Err(err) => {
3803            if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
3804                return Err(tensor_layout_error(op, err));
3805            }
3806            let from = checked_view_element_count(layout.shape(), op)?;
3807            let to = checked_view_element_count(shape, op)?;
3808            if from != to {
3809                return Err(tensor_layout_error(
3810                    op,
3811                    tenferro_tensor_core::Error::ReshapeElementCountMismatch { from, to },
3812                ));
3813            }
3814            TensorLayout::<DynRank>::compact(shape.to_vec().into())
3815                .and_then(|compact| {
3816                    TensorLayout::from_parts(
3817                        compact.shape().to_vec().into(),
3818                        compact.strides().to_vec().into(),
3819                        layout.offset(),
3820                        buffer_len,
3821                    )
3822                })
3823                .map_err(|err| tensor_layout_error(op, err))
3824        }
3825    }
3826}
3827
3828fn core_slice_specs(
3829    slices: &[StridedSliceSpec],
3830    shape: &[usize],
3831    op: &'static str,
3832) -> crate::Result<Vec<CoreSliceSpec>> {
3833    if slices.len() != shape.len() {
3834        return Err(crate::Error::RankMismatch {
3835            op,
3836            expected: shape.len(),
3837            actual: slices.len(),
3838        });
3839    }
3840
3841    let mut specs = Vec::with_capacity(slices.len());
3842    for (slice, &axis_len) in slices.iter().zip(shape) {
3843        specs.push(core_slice_spec(*slice, axis_len, op)?);
3844    }
3845    Ok(specs)
3846}
3847
3848fn core_slice_spec(
3849    slice: StridedSliceSpec,
3850    axis_len: usize,
3851    op: &'static str,
3852) -> crate::Result<CoreSliceSpec> {
3853    if slice.step() == 0 {
3854        return Err(crate::Error::InvalidConfig {
3855            op,
3856            message: "slice step must not be zero".to_string(),
3857        });
3858    }
3859
3860    let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
3861    let end = match slice.end() {
3862        Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
3863        None => isize::try_from(axis_len).map_err(|_| crate::Error::InvalidConfig {
3864            op,
3865            message: format!("axis length {axis_len} does not fit in isize"),
3866        })?,
3867    };
3868
3869    if slice.step() > 0 {
3870        return Ok(CoreSliceSpec {
3871            start,
3872            end,
3873            step: slice.step(),
3874        });
3875    }
3876
3877    if start >= end {
3878        return Ok(CoreSliceSpec {
3879            start,
3880            end: start,
3881            step: slice.step(),
3882        });
3883    }
3884
3885    Ok(CoreSliceSpec {
3886        start: end
3887            .checked_sub(1)
3888            .ok_or_else(|| crate::Error::InvalidConfig {
3889                op,
3890                message: "negative-step slice start overflows".to_string(),
3891            })?,
3892        end: start
3893            .checked_sub(1)
3894            .ok_or_else(|| crate::Error::InvalidConfig {
3895                op,
3896                message: "negative-step slice end overflows".to_string(),
3897            })?,
3898        step: slice.step(),
3899    })
3900}
3901
3902fn normalize_strided_bound(
3903    bound: isize,
3904    axis_len: usize,
3905    op: &'static str,
3906    role: &'static str,
3907) -> crate::Result<isize> {
3908    let axis_len = isize::try_from(axis_len).map_err(|_| crate::Error::InvalidConfig {
3909        op,
3910        message: format!("axis length {axis_len} does not fit in isize"),
3911    })?;
3912    let bound = if bound < 0 {
3913        axis_len
3914            .checked_add(bound)
3915            .ok_or_else(|| crate::Error::InvalidConfig {
3916                op,
3917                message: format!("{role} {bound} overflows"),
3918            })?
3919    } else {
3920        bound
3921    };
3922    if !(0..=axis_len).contains(&bound) {
3923        return Err(crate::Error::InvalidConfig {
3924            op,
3925            message: format!("{role} {bound} is outside 0..={axis_len}"),
3926        });
3927    }
3928    Ok(bound)
3929}
3930
3931fn slice_axis_specs(
3932    rank: usize,
3933    axis: usize,
3934    slice: StridedSliceSpec,
3935    op: &'static str,
3936) -> crate::Result<Vec<StridedSliceSpec>> {
3937    if axis >= rank {
3938        return Err(crate::Error::AxisOutOfBounds { op, axis, rank });
3939    }
3940
3941    let mut slices = vec![StridedSliceSpec::all(); rank];
3942    slices[axis] = slice;
3943    Ok(slices)
3944}
3945
3946pub(crate) fn materialize_typed_view_col_major<T: Clone + 'static, R: TensorRank>(
3947    view: &TypedTensorView<'_, T, R>,
3948    op: &'static str,
3949) -> crate::Result<TypedTensor<T>> {
3950    let data = materialize_view_buffer_col_major(
3951        view.shape(),
3952        view.strides(),
3953        view.offset(),
3954        &view.buffer,
3955        op,
3956    )?;
3957    TypedTensor::from_vec_col_major(view.shape().to_vec(), data)
3958}
3959
3960pub(crate) fn default_placement() -> Placement {
3961    Placement {
3962        memory_kind: MemoryKind::UnpinnedHost,
3963        device: None,
3964    }
3965}
3966
3967fn typed_tensor_from_vec_col_major<T, R: TensorRank>(
3968    shape: impl Into<R::Shape>,
3969    data: Vec<T>,
3970    op: &'static str,
3971) -> crate::Result<TypedTensor<T, R>> {
3972    try_typed_tensor_from_vec_col_major(shape, data, op)
3973}
3974
3975fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
3976    shape: impl Into<R::Shape>,
3977    data: Vec<T>,
3978    op: &'static str,
3979) -> crate::Result<TypedTensor<T, R>> {
3980    let layout = try_compact_layout(shape, op)?;
3981    try_checked_shape_len(layout.shape(), data.len(), op)?;
3982    Ok(TypedTensor {
3983        buffer: Buffer::Host(data),
3984        layout,
3985        placement: default_placement(),
3986    })
3987}
3988
3989fn typed_tensor_zeros<T: Clone + Zero, R: TensorRank>(
3990    shape: impl Into<R::Shape>,
3991) -> crate::Result<TypedTensor<T, R>> {
3992    try_typed_tensor_zeros(shape)
3993}
3994
3995fn try_typed_tensor_zeros<T: Clone + Zero, R: TensorRank>(
3996    shape: impl Into<R::Shape>,
3997) -> crate::Result<TypedTensor<T, R>> {
3998    let layout = try_compact_layout(shape, "zeros")?;
3999    let n = try_shape_product(layout.shape(), "zeros")?;
4000    Ok(TypedTensor {
4001        buffer: Buffer::Host(vec![T::zero(); n]),
4002        layout,
4003        placement: default_placement(),
4004    })
4005}
4006
4007fn typed_tensor_ones<T: Clone + One + Zero, R: TensorRank>(
4008    shape: impl Into<R::Shape>,
4009) -> crate::Result<TypedTensor<T, R>> {
4010    try_typed_tensor_ones(shape)
4011}
4012
4013fn try_typed_tensor_ones<T: Clone + One + Zero, R: TensorRank>(
4014    shape: impl Into<R::Shape>,
4015) -> crate::Result<TypedTensor<T, R>> {
4016    let layout = try_compact_layout(shape, "ones")?;
4017    let n = try_shape_product(layout.shape(), "ones")?;
4018    Ok(TypedTensor {
4019        buffer: Buffer::Host(vec![T::one(); n]),
4020        layout,
4021        placement: default_placement(),
4022    })
4023}
4024
4025fn typed_tensor_from_buffer_col_major<T: 'static, R: TensorRank>(
4026    shape: impl Into<R::Shape>,
4027    buffer: Buffer<T>,
4028    placement: Placement,
4029) -> crate::Result<TypedTensor<T, R>> {
4030    try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
4031}
4032
4033fn try_typed_tensor_from_buffer_col_major<T: 'static, R: TensorRank>(
4034    shape: impl Into<R::Shape>,
4035    buffer: Buffer<T>,
4036    placement: Placement,
4037) -> crate::Result<TypedTensor<T, R>> {
4038    let layout = try_compact_layout(shape, "from_buffer_col_major")?;
4039    let len = buffer.len();
4040    try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
4041    Ok(TypedTensor {
4042        buffer,
4043        layout,
4044        placement,
4045    })
4046}
4047
4048impl<T: Clone + Zero, R: TensorRank> TypedTensor<T, R> {
4049    /// Allocate a zero-filled tensor.
4050    ///
4051    /// # Examples
4052    ///
4053    /// ```rust
4054    /// use tenferro_tensor::TypedTensor;
4055    ///
4056    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
4057    /// assert_eq!(t.n_elements(), 6);
4058    /// ```
4059    pub fn zeros(shape: impl Into<R::Shape>) -> crate::Result<Self> {
4060        typed_tensor_zeros(shape)
4061    }
4062}
4063
4064impl<T: Clone + One + Zero, R: TensorRank> TypedTensor<T, R> {
4065    /// Allocate a one-filled tensor.
4066    ///
4067    /// # Examples
4068    ///
4069    /// ```rust
4070    /// use tenferro_tensor::TypedTensor;
4071    ///
4072    /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
4073    /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
4074    /// ```
4075    pub fn ones(shape: impl Into<R::Shape>) -> crate::Result<Self> {
4076        typed_tensor_ones(shape)
4077    }
4078}
4079
4080impl<T, R: TensorRank> TypedTensor<T, R> {
4081    /// Create a tensor from an existing buffer and compact column-major layout.
4082    ///
4083    /// This preserves the owned tensor invariant that layout metadata is
4084    /// compact column-major, including for backend-owned buffers.
4085    ///
4086    /// # Examples
4087    ///
4088    /// ```
4089    /// use tenferro_tensor::{Buffer, Placement, TypedTensor};
4090    ///
4091    /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
4092    ///     vec![2],
4093    ///     Buffer::Host(vec![1.0, 2.0]),
4094    ///     Placement {
4095    ///         memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
4096    ///         device: None,
4097    ///     },
4098    /// )
4099    /// .unwrap();
4100    /// assert_eq!(tensor.shape(), &[2]);
4101    /// ```
4102    pub fn from_buffer_col_major(
4103        shape: impl Into<R::Shape>,
4104        buffer: Buffer<T>,
4105        placement: Placement,
4106    ) -> crate::Result<Self>
4107    where
4108        T: 'static,
4109    {
4110        typed_tensor_from_buffer_col_major(shape, buffer, placement)
4111    }
4112
4113    /// Convert this tensor into static rank metadata after validating its rank.
4114    ///
4115    /// The buffer and placement are preserved. This method changes only the
4116    /// compile-time rank marker on the owned compact column-major tensor.
4117    ///
4118    /// # Examples
4119    ///
4120    /// ```rust
4121    /// use tenferro_tensor::{Rank, TypedTensor};
4122    ///
4123    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
4124    /// let ranked: TypedTensor<f64, Rank<2>> = tensor.try_into_rank::<2>()?;
4125    /// assert_eq!(ranked.shape(), &[2, 3]);
4126    /// # Ok::<(), tenferro_tensor::Error>(())
4127    /// ```
4128    pub fn try_into_rank<const N: usize>(self) -> crate::Result<TypedTensor<T, Rank<N>>> {
4129        let op = "TypedTensor::try_into_rank";
4130        let shape = <Rank<N> as TensorRank>::shape_from_vec(self.shape().to_vec().into())
4131            .map_err(|err| tensor_layout_error(op, err))?;
4132        let layout =
4133            TensorLayout::<Rank<N>>::compact(shape).map_err(|err| tensor_layout_error(op, err))?;
4134        Ok(TypedTensor {
4135            buffer: self.buffer,
4136            layout,
4137            placement: self.placement,
4138        })
4139    }
4140
4141    /// Number of elements in the tensor.
4142    ///
4143    /// # Examples
4144    ///
4145    /// ```rust
4146    /// use tenferro_tensor::TypedTensor;
4147    ///
4148    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
4149    /// assert_eq!(t.n_elements(), 6);
4150    /// ```
4151    pub fn n_elements(&self) -> usize {
4152        // Invariant: owned tensor constructors validate compact shape length against buffer length.
4153        match try_shape_product(self.shape(), "TypedTensor::n_elements") {
4154            Ok(n) => n,
4155            Err(err) => {
4156                unreachable!("TypedTensor compact shape is validated at construction: {err}")
4157            }
4158        }
4159    }
4160
4161    /// Tensor shape.
4162    ///
4163    /// # Examples
4164    ///
4165    /// ```
4166    /// use tenferro_tensor::TypedTensor;
4167    ///
4168    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
4169    /// assert_eq!(t.shape(), &[2]);
4170    /// ```
4171    pub fn shape(&self) -> &[usize] {
4172        self.layout.shape()
4173    }
4174
4175    /// Tensor rank.
4176    ///
4177    /// # Examples
4178    ///
4179    /// ```
4180    /// use tenferro_tensor::TypedTensor;
4181    ///
4182    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
4183    /// assert_eq!(t.rank(), 2);
4184    /// ```
4185    pub fn rank(&self) -> usize {
4186        self.shape().len()
4187    }
4188
4189    /// Tensor layout metadata.
4190    ///
4191    /// Owned typed tensors are always compact column-major layouts.
4192    ///
4193    /// # Examples
4194    ///
4195    /// ```
4196    /// use tenferro_tensor::TypedTensor;
4197    ///
4198    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
4199    /// assert_eq!(t.layout().strides(), &[1, 2]);
4200    /// ```
4201    pub fn layout(&self) -> &TensorLayout<R> {
4202        &self.layout
4203    }
4204
4205    /// Return the storage backing this tensor.
4206    ///
4207    /// This is an explicit storage-inspection API for backend glue and tests.
4208    /// Host value inspection should prefer [`TypedTensor::host_data`] when the
4209    /// caller requires host storage.
4210    ///
4211    /// # Examples
4212    ///
4213    /// ```
4214    /// use tenferro_tensor::{Buffer, TypedTensor};
4215    ///
4216    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
4217    /// assert!(matches!(t.buffer(), Buffer::Host(_)));
4218    /// ```
4219    pub fn buffer(&self) -> &Buffer<T> {
4220        &self.buffer
4221    }
4222
4223    /// Return placement metadata for this tensor.
4224    ///
4225    /// # Examples
4226    ///
4227    /// ```
4228    /// use tenferro_tensor::{MemoryKind, TypedTensor};
4229    ///
4230    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
4231    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
4232    /// ```
4233    pub fn placement(&self) -> &Placement {
4234        &self.placement
4235    }
4236
4237    /// Replace placement metadata without changing the storage buffer.
4238    ///
4239    /// # Examples
4240    ///
4241    /// ```
4242    /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
4243    ///
4244    /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
4245    /// t.set_placement(Placement {
4246    ///     memory_kind: MemoryKind::PinnedHost,
4247    ///     device: None,
4248    /// });
4249    /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
4250    /// ```
4251    pub fn set_placement(&mut self, placement: Placement) {
4252        self.placement = placement;
4253    }
4254
4255    /// Borrow this tensor as a typed view preserving rank and layout metadata.
4256    ///
4257    /// # Examples
4258    ///
4259    /// ```rust
4260    /// use tenferro_tensor::{Rank, TypedTensor};
4261    ///
4262    /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
4263    /// let view = tensor.as_view();
4264    /// assert_eq!(view.strides(), &[1, 2]);
4265    /// ```
4266    pub fn as_view(&self) -> TypedTensorView<'_, T, R>
4267    where
4268        T: 'static,
4269    {
4270        let buffer = match &self.buffer {
4271            Buffer::Host(data) => TensorBufferRef::Host(data),
4272            Buffer::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
4273        };
4274        TypedTensorView {
4275            buffer,
4276            layout: self.layout.clone(),
4277            placement: self.placement.clone(),
4278        }
4279    }
4280
4281    /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
4282    ///
4283    /// # Examples
4284    ///
4285    /// ```rust
4286    /// use tenferro_tensor::TypedTensor;
4287    ///
4288    /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
4289    /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
4290    /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
4291    /// ```
4292    pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
4293    where
4294        T: 'static,
4295    {
4296        let layout = self.layout.clone();
4297        let placement = self.placement.clone();
4298        let buffer = match &mut self.buffer {
4299            Buffer::Host(data) => TensorBufferRefMut::Host(data),
4300            Buffer::Backend(buffer) => TensorBufferRefMut::Backend(Arc::clone(buffer)),
4301        };
4302        TypedTensorViewMut {
4303            buffer,
4304            layout,
4305            placement,
4306        }
4307    }
4308
4309    /// Consume this tensor and return its layout metadata.
4310    ///
4311    /// # Examples
4312    ///
4313    /// ```
4314    /// use tenferro_tensor::TypedTensor;
4315    ///
4316    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
4317    /// assert!(t.into_layout().is_compact_col_major().unwrap());
4318    /// ```
4319    pub fn into_layout(self) -> TensorLayout<R> {
4320        self.layout
4321    }
4322
4323    /// Consume this tensor and return its storage, layout, and placement.
4324    ///
4325    /// # Examples
4326    ///
4327    /// ```
4328    /// use tenferro_tensor::{Buffer, TypedTensor};
4329    ///
4330    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
4331    /// let (buffer, layout, placement) = t.into_parts();
4332    /// assert!(matches!(buffer, Buffer::Host(_)));
4333    /// assert_eq!(layout.shape(), &[2]);
4334    /// assert!(placement.device.is_none());
4335    /// ```
4336    pub fn into_parts(self) -> (Buffer<T>, TensorLayout<R>, Placement) {
4337        (self.buffer, self.layout, self.placement)
4338    }
4339}
4340
4341impl<T: Clone, R: TensorRank> TypedTensor<T, R> {
4342    /// Create a tensor from a column-major buffer.
4343    ///
4344    /// # Examples
4345    ///
4346    /// ```
4347    /// use tenferro_tensor::TypedTensor;
4348    ///
4349    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
4350    /// assert_eq!(t.get(&[1, 0])?, &2.0);
4351    /// # Ok::<(), tenferro_tensor::Error>(())
4352    /// ```
4353    pub fn from_vec_col_major(shape: impl Into<R::Shape>, data: Vec<T>) -> crate::Result<Self> {
4354        typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
4355    }
4356
4357    /// Consume this tensor and return its owned column-major host buffer.
4358    ///
4359    /// # Examples
4360    ///
4361    /// ```
4362    /// use tenferro_tensor::TypedTensor;
4363    ///
4364    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
4365    /// let (shape, data) = t.into_vec_col_major().unwrap();
4366    /// assert_eq!(shape, vec![2]);
4367    /// assert_eq!(data, vec![1.0, 2.0]);
4368    /// ```
4369    pub fn into_vec_col_major(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
4370        let shape = self.shape().to_vec();
4371        match self.buffer {
4372            Buffer::Host(data) => Ok((shape, data)),
4373            Buffer::Backend(_) => Err(crate::Error::backend_failure(
4374                "into_vec_col_major",
4375                "backend buffers cannot be exported as host Vec",
4376            )),
4377        }
4378    }
4379
4380    /// Borrow the host buffer.
4381    ///
4382    /// # Examples
4383    ///
4384    /// ```rust
4385    /// use tenferro_tensor::TypedTensor;
4386    ///
4387    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
4388    /// assert_eq!(t.host_data()?, &[1.0, 2.0]);
4389    /// # Ok::<(), tenferro_tensor::Error>(())
4390    /// ```
4391    pub fn host_data(&self) -> crate::Result<&[T]> {
4392        match &self.buffer {
4393            Buffer::Host(v) => Ok(v),
4394            Buffer::Backend(_) => Err(crate::Error::backend_failure(
4395                "TypedTensor::host_data",
4396                "backend buffers cannot be inspected as host slices; download explicitly first",
4397            )),
4398        }
4399    }
4400
4401    /// View the tensor data as a flat slice.
4402    ///
4403    /// This is an alias for `host_data()` for API consistency with
4404    /// `Tensor::as_slice`.
4405    ///
4406    /// # Examples
4407    ///
4408    /// ```
4409    /// use tenferro_tensor::TypedTensor;
4410    ///
4411    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
4412    /// assert_eq!(t.as_slice()?, &[1.0, 2.0]);
4413    /// # Ok::<(), tenferro_tensor::Error>(())
4414    /// ```
4415    pub fn as_slice(&self) -> crate::Result<&[T]> {
4416        self.host_data()
4417    }
4418
4419    /// Mutably borrow the host buffer.
4420    ///
4421    /// # Examples
4422    ///
4423    /// ```rust
4424    /// use tenferro_tensor::TypedTensor;
4425    ///
4426    /// let mut t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
4427    /// t.host_data_mut()?[0] = 3.0;
4428    /// assert_eq!(t.host_data()?, &[3.0, 0.0]);
4429    /// # Ok::<(), tenferro_tensor::Error>(())
4430    /// ```
4431    pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
4432        match &mut self.buffer {
4433            Buffer::Host(v) => Ok(v),
4434            Buffer::Backend(_) => Err(crate::Error::backend_failure(
4435                "TypedTensor::host_data_mut",
4436                "backend buffers cannot be mutated as host slices; download explicitly first",
4437            )),
4438        }
4439    }
4440
4441    /// Compute the linear physical-buffer offset for a logical index.
4442    ///
4443    /// # Examples
4444    ///
4445    /// ```rust
4446    /// use tenferro_tensor::TypedTensor;
4447    ///
4448    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
4449    /// assert_eq!(t.linear_offset(&[1, 2])?, 5);
4450    /// # Ok::<(), tenferro_tensor::Error>(())
4451    /// ```
4452    pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
4453        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
4454    }
4455
4456    /// Compute the physical element offset for a logical index.
4457    ///
4458    /// # Examples
4459    ///
4460    /// ```rust
4461    /// use tenferro_tensor::TypedTensor;
4462    ///
4463    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
4464    /// assert_eq!(t.layout_linear_offset(&[1, 2])?, 5);
4465    /// # Ok::<(), tenferro_tensor::Error>(())
4466    /// ```
4467    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
4468        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::layout_linear_offset")
4469    }
4470
4471    /// Return whether this owned tensor is compact column-major.
4472    ///
4473    /// # Examples
4474    ///
4475    /// ```rust
4476    /// use tenferro_tensor::TypedTensor;
4477    ///
4478    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
4479    /// assert!(t.is_col_major_contiguous()?);
4480    /// # Ok::<(), tenferro_tensor::Error>(())
4481    /// ```
4482    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
4483        self.layout
4484            .is_compact_col_major()
4485            .map_err(|err| tensor_layout_error("TypedTensor::is_col_major_contiguous", err))
4486    }
4487
4488    /// Return a compact string summary of this tensor's layout metadata.
4489    ///
4490    /// # Examples
4491    ///
4492    /// ```rust
4493    /// use tenferro_tensor::TypedTensor;
4494    ///
4495    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
4496    /// assert!(t.layout_summary().contains("shape=[2]"));
4497    /// # Ok::<(), tenferro_tensor::Error>(())
4498    /// ```
4499    pub fn layout_summary(&self) -> String {
4500        layout_summary(self.shape(), self.layout.strides(), self.layout.offset())
4501    }
4502
4503    /// Assert this tensor is compact column-major.
4504    ///
4505    /// # Examples
4506    ///
4507    /// ```rust
4508    /// use tenferro_tensor::TypedTensor;
4509    ///
4510    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
4511    /// t.assert_col_major_contiguous()?;
4512    /// # Ok::<(), tenferro_tensor::Error>(())
4513    /// ```
4514    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
4515        assert_layout_col_major_contiguous(
4516            self.is_col_major_contiguous()?,
4517            self.shape(),
4518            self.layout.strides(),
4519            self.layout.offset(),
4520            "TypedTensor::assert_col_major_contiguous",
4521        )
4522    }
4523
4524    /// Borrow a single element by multi-index.
4525    ///
4526    /// # Examples
4527    ///
4528    /// ```rust
4529    /// use tenferro_tensor::TypedTensor;
4530    ///
4531    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
4532    /// assert_eq!(t.get(&[1])?, &2.0);
4533    /// # Ok::<(), tenferro_tensor::Error>(())
4534    /// ```
4535    pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
4536        let off = self.linear_offset(indices)?;
4537        self.host_data()?
4538            .get(off)
4539            .ok_or_else(|| crate::Error::InvalidConfig {
4540                op: "TypedTensor::get",
4541                message: format!("linear offset {off} is outside host buffer"),
4542            })
4543    }
4544
4545    /// Mutably borrow a single element by multi-index.
4546    ///
4547    /// # Examples
4548    ///
4549    /// ```rust
4550    /// use tenferro_tensor::TypedTensor;
4551    ///
4552    /// let mut t = TypedTensor::<f64>::zeros(vec![1]).unwrap();
4553    /// *t.get_mut(&[0])? = 7.0;
4554    /// assert_eq!(t.host_data()?, &[7.0]);
4555    /// # Ok::<(), tenferro_tensor::Error>(())
4556    /// ```
4557    pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
4558        let off = self.linear_offset(indices)?;
4559        self.host_data_mut()?
4560            .get_mut(off)
4561            .ok_or_else(|| crate::Error::InvalidConfig {
4562                op: "TypedTensor::get_mut",
4563                message: format!("linear offset {off} is outside host buffer"),
4564            })
4565    }
4566}
4567
4568impl Tensor {
4569    /// Create a tensor from a shape and column-major flat data.
4570    ///
4571    /// This is the `Tensor`-level equivalent of
4572    /// `TypedTensor::<T>::from_vec_col_major`.
4573    ///
4574    /// # Examples
4575    ///
4576    /// ```
4577    /// use tenferro_tensor::Tensor;
4578    ///
4579    /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
4580    /// assert_eq!(t.shape(), &[2, 2]);
4581    /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
4582    /// ```
4583    pub fn from_vec_col_major<T: TensorScalar>(
4584        shape: Vec<usize>,
4585        data: Vec<T>,
4586    ) -> crate::Result<Self> {
4587        T::into_tensor(shape, data)
4588    }
4589
4590    /// Tensor shape.
4591    ///
4592    /// # Examples
4593    ///
4594    /// ```rust
4595    /// use tenferro_tensor::{Tensor, TypedTensor};
4596    ///
4597    /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
4598    /// assert_eq!(t.shape(), &[2]);
4599    /// ```
4600    pub fn shape(&self) -> &[usize] {
4601        match self {
4602            Tensor::F32(t) => t.shape(),
4603            Tensor::F64(t) => t.shape(),
4604            Tensor::I32(t) => t.shape(),
4605            Tensor::I64(t) => t.shape(),
4606            Tensor::Bool(t) => t.shape(),
4607            Tensor::C32(t) => t.shape(),
4608            Tensor::C64(t) => t.shape(),
4609        }
4610    }
4611
4612    /// Tensor dtype tag.
4613    ///
4614    /// # Examples
4615    ///
4616    /// ```rust
4617    /// use tenferro_tensor::{DType, Tensor, TypedTensor};
4618    ///
4619    /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
4620    /// assert_eq!(t.dtype(), DType::F64);
4621    /// ```
4622    pub fn dtype(&self) -> DType {
4623        match self {
4624            Tensor::F32(_) => DType::F32,
4625            Tensor::F64(_) => DType::F64,
4626            Tensor::I32(_) => DType::I32,
4627            Tensor::I64(_) => DType::I64,
4628            Tensor::Bool(_) => DType::Bool,
4629            Tensor::C32(_) => DType::C32,
4630            Tensor::C64(_) => DType::C64,
4631        }
4632    }
4633
4634    /// Return placement metadata for this dtype-erased tensor.
4635    ///
4636    /// # Examples
4637    ///
4638    /// ```rust
4639    /// use tenferro_tensor::{MemoryKind, Tensor};
4640    ///
4641    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
4642    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
4643    /// ```
4644    pub fn placement(&self) -> &Placement {
4645        match self {
4646            Tensor::F32(t) => t.placement(),
4647            Tensor::F64(t) => t.placement(),
4648            Tensor::I32(t) => t.placement(),
4649            Tensor::I64(t) => t.placement(),
4650            Tensor::Bool(t) => t.placement(),
4651            Tensor::C32(t) => t.placement(),
4652            Tensor::C64(t) => t.placement(),
4653        }
4654    }
4655
4656    /// Return whether this tensor is backed by backend-native storage.
4657    ///
4658    /// # Examples
4659    ///
4660    /// ```rust
4661    /// use tenferro_tensor::Tensor;
4662    ///
4663    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
4664    /// assert!(!t.is_backend_buffer());
4665    /// ```
4666    pub fn is_backend_buffer(&self) -> bool {
4667        match self {
4668            Tensor::F32(t) => t.buffer().is_backend(),
4669            Tensor::F64(t) => t.buffer().is_backend(),
4670            Tensor::I32(t) => t.buffer().is_backend(),
4671            Tensor::I64(t) => t.buffer().is_backend(),
4672            Tensor::Bool(t) => t.buffer().is_backend(),
4673            Tensor::C32(t) => t.buffer().is_backend(),
4674            Tensor::C64(t) => t.buffer().is_backend(),
4675        }
4676    }
4677
4678    /// Compute the physical element offset for a logical index.
4679    ///
4680    /// # Examples
4681    ///
4682    /// ```rust
4683    /// use tenferro_tensor::Tensor;
4684    ///
4685    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
4686    /// assert_eq!(t.layout_linear_offset(&[1])?, 1);
4687    /// # Ok::<(), tenferro_tensor::Error>(())
4688    /// ```
4689    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
4690        match self {
4691            Tensor::F32(t) => t.layout_linear_offset(indices),
4692            Tensor::F64(t) => t.layout_linear_offset(indices),
4693            Tensor::I32(t) => t.layout_linear_offset(indices),
4694            Tensor::I64(t) => t.layout_linear_offset(indices),
4695            Tensor::Bool(t) => t.layout_linear_offset(indices),
4696            Tensor::C32(t) => t.layout_linear_offset(indices),
4697            Tensor::C64(t) => t.layout_linear_offset(indices),
4698        }
4699    }
4700
4701    /// Return whether this tensor is compact column-major.
4702    ///
4703    /// # Examples
4704    ///
4705    /// ```rust
4706    /// use tenferro_tensor::Tensor;
4707    ///
4708    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
4709    /// assert!(t.is_col_major_contiguous()?);
4710    /// # Ok::<(), tenferro_tensor::Error>(())
4711    /// ```
4712    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
4713        match self {
4714            Tensor::F32(t) => t.is_col_major_contiguous(),
4715            Tensor::F64(t) => t.is_col_major_contiguous(),
4716            Tensor::I32(t) => t.is_col_major_contiguous(),
4717            Tensor::I64(t) => t.is_col_major_contiguous(),
4718            Tensor::Bool(t) => t.is_col_major_contiguous(),
4719            Tensor::C32(t) => t.is_col_major_contiguous(),
4720            Tensor::C64(t) => t.is_col_major_contiguous(),
4721        }
4722    }
4723
4724    /// Return a compact string summary of this tensor's layout metadata.
4725    ///
4726    /// # Examples
4727    ///
4728    /// ```rust
4729    /// use tenferro_tensor::Tensor;
4730    ///
4731    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
4732    /// assert!(t.layout_summary().contains("shape=[2]"));
4733    /// # Ok::<(), tenferro_tensor::Error>(())
4734    /// ```
4735    pub fn layout_summary(&self) -> String {
4736        let layout = tensor_layout(self);
4737        layout_summary(layout.shape(), layout.strides(), layout.offset())
4738    }
4739
4740    /// Assert this tensor is compact column-major.
4741    ///
4742    /// # Examples
4743    ///
4744    /// ```rust
4745    /// use tenferro_tensor::Tensor;
4746    ///
4747    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
4748    /// t.assert_col_major_contiguous()?;
4749    /// # Ok::<(), tenferro_tensor::Error>(())
4750    /// ```
4751    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
4752        let layout = tensor_layout(self);
4753        assert_layout_col_major_contiguous(
4754            self.is_col_major_contiguous()?,
4755            layout.shape(),
4756            layout.strides(),
4757            layout.offset(),
4758            "Tensor::assert_col_major_contiguous",
4759        )
4760    }
4761
4762    /// Try to borrow the host data as a typed slice.
4763    ///
4764    /// Returns an error if the tensor dtype does not match `T`.
4765    ///
4766    /// # Examples
4767    ///
4768    /// ```
4769    /// use tenferro_tensor::{Tensor, TypedTensor};
4770    ///
4771    /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
4772    /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
4773    /// assert!(t.as_slice::<f32>().is_err());
4774    /// ```
4775    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
4776        T::as_slice(self)
4777    }
4778
4779    /// Consume this tensor and return its owned column-major buffer when the
4780    /// dtype matches.
4781    ///
4782    /// # Examples
4783    ///
4784    /// ```
4785    /// use tenferro_tensor::Tensor;
4786    ///
4787    /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
4788    /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
4789    /// ```
4790    pub fn into_vec_col_major<T: TensorScalar>(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
4791        let typed = T::into_typed(self)?;
4792        typed.into_vec_col_major()
4793    }
4794}
4795
4796// Kept for crate-local layout tests while tensor indexing helpers remain split
4797// across tensor and CPU crates.
4798#[allow(dead_code)]
4799pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
4800    for i in 0..shape.len() {
4801        if shape[i] == 0 {
4802            out[i] = 0;
4803        } else {
4804            out[i] = flat % shape[i];
4805            flat /= shape[i];
4806        }
4807    }
4808}