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        checked_view_element_count(self.shape(), "TypedTensorView::n_elements")
639            .expect("TypedTensorView layout shape was validated at construction")
640    }
641
642    /// Return layout metadata for this view.
643    ///
644    /// # Examples
645    ///
646    /// ```rust
647    /// use tenferro_tensor::TypedTensorView;
648    ///
649    /// let data = [1_i32, 2];
650    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
651    /// assert!(view.layout().is_compact_col_major());
652    /// # Ok::<(), tenferro_tensor::Error>(())
653    /// ```
654    pub fn layout(&self) -> &TensorLayout<R> {
655        &self.layout
656    }
657
658    /// Return placement metadata for this view.
659    ///
660    /// # Examples
661    ///
662    /// ```rust
663    /// use tenferro_tensor::{MemoryKind, TypedTensorView};
664    ///
665    /// let data = [1_i32];
666    /// let view = TypedTensorView::from_slice(vec![1], vec![1], 0, &data)?;
667    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
668    /// # Ok::<(), tenferro_tensor::Error>(())
669    /// ```
670    pub fn placement(&self) -> &Placement {
671        &self.placement
672    }
673
674    /// Return the backend allocation for backend integrations.
675    #[doc(hidden)]
676    pub fn backend_buffer(&self) -> Option<&Arc<dyn BackendBuffer<T>>> {
677        match &self.buffer {
678            TensorBufferRef::Host(_) => None,
679            TensorBufferRef::Backend(buffer) => Some(buffer),
680        }
681    }
682
683    /// Compute the physical element offset for a logical index.
684    ///
685    /// # Examples
686    ///
687    /// ```rust
688    /// use tenferro_tensor::TypedTensorView;
689    ///
690    /// let data = [1_i32, 2, 3];
691    /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
692    /// assert_eq!(view.linear_offset(&[2]), Some(0));
693    /// # Ok::<(), tenferro_tensor::Error>(())
694    /// ```
695    pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
696        checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
697    }
698
699    /// Borrow one host element by logical index.
700    ///
701    /// Returns `None` for out-of-bounds indices and backend buffers.
702    ///
703    /// # Examples
704    ///
705    /// ```rust
706    /// use tenferro_tensor::TypedTensorView;
707    ///
708    /// let data = [1_i32, 2];
709    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
710    /// assert_eq!(view.get(&[1]), Some(&2));
711    /// # Ok::<(), tenferro_tensor::Error>(())
712    /// ```
713    pub fn get(&self, indices: &[usize]) -> Option<&T> {
714        let offset = self.linear_offset(indices)?;
715        match &self.buffer {
716            TensorBufferRef::Host(data) => data.get(offset),
717            TensorBufferRef::Backend(_) => None,
718        }
719    }
720
721    /// Borrow the contiguous host slice covered by this view.
722    ///
723    /// Returns an explicit error for backend buffers and for non-contiguous
724    /// layouts. This method never downloads or materializes backend data.
725    ///
726    /// # Examples
727    ///
728    /// ```rust
729    /// use tenferro_tensor::TypedTensorView;
730    ///
731    /// let data = [1_i32, 2, 3];
732    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?;
733    /// assert_eq!(view.as_slice()?, &[2, 3]);
734    /// # Ok::<(), tenferro_tensor::Error>(())
735    /// ```
736    pub fn as_slice(&self) -> crate::Result<&'a [T]> {
737        let data =
738            match &self.buffer {
739                TensorBufferRef::Host(data) => data,
740                TensorBufferRef::Backend(_) => return Err(crate::Error::backend_failure(
741                    "TypedTensorView::as_slice",
742                    "backend buffers cannot be inspected as host slices; download explicitly first",
743                )),
744            };
745        contiguous_layout_slice(self.layout(), data, "TypedTensorView::as_slice")
746    }
747
748    /// Materialize this view as compact column-major host tensor storage.
749    ///
750    /// This is an explicit same-placement copy boundary. Host placement
751    /// metadata is preserved on the materialized tensor. Backend buffers return
752    /// an error here instead of being downloaded implicitly; backend-specific
753    /// compacting paths must stay on that backend.
754    ///
755    /// # Examples
756    ///
757    /// ```rust
758    /// use tenferro_tensor::{Rank, TypedTensor};
759    ///
760    /// let tensor = TypedTensor::<i32, Rank<2>>::from_vec_col_major([2, 2], vec![1, 2, 3, 4]).unwrap();
761    /// let transposed = tensor.as_view().transpose_view([1, 0])?;
762    /// let compact = transposed.to_contiguous()?;
763    /// assert_eq!(compact.as_slice()?, &[1, 3, 2, 4]);
764    /// # Ok::<(), tenferro_tensor::Error>(())
765    /// ```
766    pub fn to_contiguous(&self) -> crate::Result<TypedTensor<T, R>>
767    where
768        T: Clone,
769    {
770        let op = "TypedTensorView::to_contiguous";
771        let data = materialize_view_buffer_col_major(
772            self.shape(),
773            self.strides(),
774            self.offset(),
775            &self.buffer,
776            op,
777        )?;
778        let shape = R::shape_from_vec(self.shape().to_vec().into())
779            .map_err(|err| tensor_layout_error(op, err))?;
780        TypedTensor::from_buffer_col_major(shape, Buffer::Host(data), self.placement.clone())
781    }
782
783    /// Return a metadata-only axis permutation.
784    ///
785    /// # Examples
786    ///
787    /// ```rust
788    /// use tenferro_tensor::{Rank, TypedTensorView};
789    ///
790    /// let data = [1_i32, 2, 3, 4, 5, 6];
791    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 3], [1, 2], 0, &data)?;
792    /// let transposed = view.transpose_view([1, 0])?;
793    /// assert_eq!(transposed.shape(), &[3, 2]);
794    /// # Ok::<(), tenferro_tensor::Error>(())
795    /// ```
796    pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
797        let layout = self
798            .layout
799            .transpose_view(axes)
800            .map_err(|err| tensor_layout_error("TypedTensorView::transpose_view", err))?;
801        Ok(Self {
802            buffer: self.buffer.clone(),
803            layout,
804            placement: self.placement.clone(),
805        })
806    }
807
808    /// Return a metadata-only slice using one [`StridedSliceSpec`] per axis.
809    ///
810    /// # Examples
811    ///
812    /// ```rust
813    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
814    ///
815    /// let data = [1_i32, 2, 3];
816    /// let view = TypedTensorView::from_slice(vec![3], vec![1], 0, &data)?;
817    /// let reversed = view.try_slice(&[StridedSliceSpec::reverse()])?;
818    /// assert_eq!(reversed.get(&[0]), Some(&3));
819    /// # Ok::<(), tenferro_tensor::Error>(())
820    /// ```
821    pub fn try_slice(&self, slices: &[StridedSliceSpec]) -> crate::Result<Self> {
822        let specs = core_slice_specs(slices, self.shape(), "TypedTensorView::try_slice")?;
823        let layout = self
824            .layout
825            .slice_view(specs, self.buffer.len())
826            .map_err(|err| tensor_layout_error("TypedTensorView::try_slice", err))?;
827        Ok(Self {
828            buffer: self.buffer.clone(),
829            layout,
830            placement: self.placement.clone(),
831        })
832    }
833
834    /// Return a metadata-only slice along one axis.
835    ///
836    /// # Examples
837    ///
838    /// ```rust
839    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
840    ///
841    /// let data = [1_i32, 2, 3, 4];
842    /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
843    /// assert_eq!(view.try_slice_axis(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
844    /// # Ok::<(), tenferro_tensor::Error>(())
845    /// ```
846    pub fn try_slice_axis(&self, axis: usize, slice: StridedSliceSpec) -> crate::Result<Self> {
847        let slices = slice_axis_specs(
848            self.shape().len(),
849            axis,
850            slice,
851            "TypedTensorView::try_slice_axis",
852        )?;
853        self.try_slice(&slices)
854    }
855
856    /// Return a metadata-only dynamic-rank reshape for contiguous column-major views.
857    ///
858    /// # Examples
859    ///
860    /// ```rust
861    /// use tenferro_tensor::TypedTensorView;
862    ///
863    /// let data = [1_i32, 2, 3, 4];
864    /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
865    /// assert_eq!(view.try_reshape(&[4])?.shape(), &[4]);
866    /// # Ok::<(), tenferro_tensor::Error>(())
867    /// ```
868    pub fn try_reshape(&self, shape: &[usize]) -> crate::Result<TypedTensorView<'a, T, DynRank>> {
869        let layout = reshape_layout_dyn(
870            &self.layout,
871            shape,
872            self.buffer.len(),
873            "TypedTensorView::try_reshape",
874        )?;
875        Ok(TypedTensorView {
876            buffer: self.buffer.clone(),
877            layout,
878            placement: self.placement.clone(),
879        })
880    }
881}
882
883/// Mutable borrowed view of typed tensor storage with arbitrary strides.
884///
885/// # Examples
886///
887/// ```rust
888/// use tenferro_tensor::TypedTensorViewMut;
889///
890/// let mut data = [1_i32, 2, 3];
891/// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
892/// *view.get_mut(&[2]).unwrap() = 10;
893/// assert_eq!(view.as_read_only().get(&[2]), Some(&10));
894/// # Ok::<(), tenferro_tensor::Error>(())
895/// ```
896#[derive(Debug)]
897pub struct TypedTensorViewMut<'a, T, R: TensorRank = DynRank> {
898    buffer: TensorBufferRefMut<'a, T>,
899    layout: TensorLayout<R>,
900    placement: Placement,
901}
902
903impl<'a, T: 'static> TypedTensorViewMut<'a, T, DynRank> {
904    /// Create a mutable dynamic-rank view over compact column-major host data.
905    ///
906    /// # Examples
907    ///
908    /// ```rust
909    /// use tenferro_tensor::TypedTensorViewMut;
910    ///
911    /// let mut data = [1_i32, 2, 3, 4];
912    /// let view = TypedTensorViewMut::from_col_major(&[2, 2], &mut data)?;
913    /// assert_eq!(view.strides(), &[1, 2]);
914    /// # Ok::<(), tenferro_tensor::Error>(())
915    /// ```
916    pub fn from_col_major(shape: &[usize], data: &'a mut [T]) -> crate::Result<Self> {
917        let layout = TensorLayout::<DynRank>::compact(shape.to_vec().into())
918            .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_col_major", err))?;
919        Self::from_buffer_ref_mut(
920            layout.shape().to_vec(),
921            layout.strides().to_vec(),
922            layout.offset(),
923            TensorBufferRefMut::Host(data),
924            default_placement(),
925            "TypedTensorViewMut::from_col_major",
926        )
927    }
928
929    /// Create a mutable host view from explicit layout metadata.
930    ///
931    /// Layouts where distinct logical elements can alias the same physical
932    /// element are rejected.
933    ///
934    /// # Examples
935    ///
936    /// ```rust
937    /// use tenferro_tensor::TypedTensorViewMut;
938    ///
939    /// let mut data = [1_i32, 2];
940    /// assert!(TypedTensorViewMut::from_slice(vec![2], vec![0], 0, &mut data).is_err());
941    /// ```
942    pub fn from_slice(
943        shape: impl AsRef<[usize]>,
944        strides: impl AsRef<[isize]>,
945        offset: isize,
946        data: &'a mut [T],
947    ) -> crate::Result<Self> {
948        Self::from_buffer_ref_mut(
949            shape.as_ref().to_vec(),
950            strides.as_ref().to_vec(),
951            offset,
952            TensorBufferRefMut::Host(data),
953            default_placement(),
954            "TypedTensorViewMut::from_slice",
955        )
956    }
957}
958
959impl<'a, T: 'static, R: TensorRank> TypedTensorViewMut<'a, T, R> {
960    /// Create a rank-generic mutable host view from explicit layout metadata.
961    ///
962    /// # Examples
963    ///
964    /// ```rust
965    /// use tenferro_tensor::{Rank, TypedTensorViewMut};
966    ///
967    /// let mut data = [1_i32, 2, 3, 4];
968    /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
969    /// assert_eq!(view.shape(), &[2, 2]);
970    /// # Ok::<(), tenferro_tensor::Error>(())
971    /// ```
972    pub fn from_slice_ranked(
973        shape: impl Into<R::Shape>,
974        strides: impl Into<R::Strides>,
975        offset: isize,
976        data: &'a mut [T],
977    ) -> crate::Result<Self> {
978        Self::from_buffer_ref_mut(
979            shape,
980            strides,
981            offset,
982            TensorBufferRefMut::Host(data),
983            default_placement(),
984            "TypedTensorViewMut::from_slice_ranked",
985        )
986    }
987
988    fn from_buffer_ref_mut(
989        shape: impl Into<R::Shape>,
990        strides: impl Into<R::Strides>,
991        offset: isize,
992        buffer: TensorBufferRefMut<'a, T>,
993        placement: Placement,
994        op: &'static str,
995    ) -> crate::Result<Self> {
996        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
997            .map_err(|err| tensor_layout_error(op, err))?;
998        layout
999            .validate_mutable_no_overlap()
1000            .map_err(|err| tensor_layout_error(op, err))?;
1001        Ok(Self {
1002            buffer,
1003            layout,
1004            placement,
1005        })
1006    }
1007
1008    /// Return the logical shape.
1009    ///
1010    /// # Examples
1011    ///
1012    /// ```rust
1013    /// use tenferro_tensor::TypedTensorViewMut;
1014    ///
1015    /// let mut data = [0_i32; 2];
1016    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1017    /// assert_eq!(view.shape(), &[2]);
1018    /// # Ok::<(), tenferro_tensor::Error>(())
1019    /// ```
1020    pub fn shape(&self) -> &[usize] {
1021        self.layout.shape()
1022    }
1023
1024    /// Return strides in element units.
1025    ///
1026    /// # Examples
1027    ///
1028    /// ```rust
1029    /// use tenferro_tensor::TypedTensorViewMut;
1030    ///
1031    /// let mut data = [0_i32; 2];
1032    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![-1], 1, &mut data)?;
1033    /// assert_eq!(view.strides(), &[-1]);
1034    /// # Ok::<(), tenferro_tensor::Error>(())
1035    /// ```
1036    pub fn strides(&self) -> &[isize] {
1037        self.layout.strides()
1038    }
1039
1040    /// Return the physical element offset.
1041    ///
1042    /// # Examples
1043    ///
1044    /// ```rust
1045    /// use tenferro_tensor::TypedTensorViewMut;
1046    ///
1047    /// let mut data = [1_i32, 2];
1048    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 1, &mut data)?;
1049    /// assert_eq!(view.offset(), 1);
1050    /// # Ok::<(), tenferro_tensor::Error>(())
1051    /// ```
1052    pub fn offset(&self) -> isize {
1053        self.layout.offset()
1054    }
1055
1056    /// Return the borrowed host storage backing this view.
1057    ///
1058    /// This exposes the entire backing host allocation, not just the logical
1059    /// slice covered by this view. Use [`TypedTensorViewMut::as_read_only`]
1060    /// with [`TypedTensorView::as_slice`] when the caller needs the contiguous
1061    /// logical region instead.
1062    ///
1063    /// # Examples
1064    ///
1065    /// ```rust
1066    /// use tenferro_tensor::TypedTensorViewMut;
1067    ///
1068    /// let mut data = [1_i32, 2];
1069    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1070    /// assert_eq!(view.host_storage()?, &[1, 2]);
1071    /// # Ok::<(), tenferro_tensor::Error>(())
1072    /// ```
1073    pub fn host_storage(&self) -> crate::Result<&[T]> {
1074        match &self.buffer {
1075            TensorBufferRefMut::Host(data) => Ok(data),
1076            TensorBufferRefMut::Backend(_) => Err(crate::Error::backend_failure(
1077                "TypedTensorViewMut::host_storage",
1078                "backend buffers cannot expose host storage; download explicitly first",
1079            )),
1080        }
1081    }
1082
1083    /// Mutably borrow the host storage backing this view.
1084    ///
1085    /// This exposes the entire backing host allocation, not just the logical
1086    /// slice covered by this view. Use [`TypedTensorViewMut::copy_from_contiguous`]
1087    /// or element accessors when mutating the logical region instead.
1088    ///
1089    /// # Examples
1090    ///
1091    /// ```rust
1092    /// use tenferro_tensor::TypedTensorViewMut;
1093    ///
1094    /// let mut data = [1_i32, 2];
1095    /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1096    /// view.host_storage_mut()?[0] = 3;
1097    /// assert_eq!(view.get(&[0]), Some(&3));
1098    /// # Ok::<(), tenferro_tensor::Error>(())
1099    /// ```
1100    pub fn host_storage_mut(&mut self) -> crate::Result<&mut [T]> {
1101        match &mut self.buffer {
1102            TensorBufferRefMut::Host(data) => Ok(data),
1103            TensorBufferRefMut::Backend(_) => Err(crate::Error::backend_failure(
1104                "TypedTensorViewMut::host_storage_mut",
1105                "backend buffers cannot expose mutable host storage; download explicitly first",
1106            )),
1107        }
1108    }
1109
1110    /// Return the number of logical elements in this view.
1111    ///
1112    /// # Examples
1113    ///
1114    /// ```rust
1115    /// use tenferro_tensor::TypedTensorViewMut;
1116    ///
1117    /// let mut data = [0_i32; 6];
1118    /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
1119    /// assert_eq!(view.n_elements(), 6);
1120    /// # Ok::<(), tenferro_tensor::Error>(())
1121    /// ```
1122    pub fn n_elements(&self) -> usize {
1123        // Invariant: public mutable view constructors validate logical element count.
1124        checked_view_element_count(self.shape(), "TypedTensorViewMut::n_elements")
1125            .expect("TypedTensorViewMut layout shape was validated at construction")
1126    }
1127
1128    /// Return layout metadata for this view.
1129    ///
1130    /// # Examples
1131    ///
1132    /// ```rust
1133    /// use tenferro_tensor::TypedTensorViewMut;
1134    ///
1135    /// let mut data = [1_i32, 2];
1136    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1137    /// assert!(view.layout().is_compact_col_major());
1138    /// # Ok::<(), tenferro_tensor::Error>(())
1139    /// ```
1140    pub fn layout(&self) -> &TensorLayout<R> {
1141        &self.layout
1142    }
1143
1144    /// Return placement metadata for this view.
1145    ///
1146    /// # Examples
1147    ///
1148    /// ```rust
1149    /// use tenferro_tensor::{MemoryKind, TypedTensorViewMut};
1150    ///
1151    /// let mut data = [1_i32];
1152    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1153    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
1154    /// # Ok::<(), tenferro_tensor::Error>(())
1155    /// ```
1156    pub fn placement(&self) -> &Placement {
1157        &self.placement
1158    }
1159
1160    /// Return the backend allocation for backend integrations.
1161    #[doc(hidden)]
1162    pub fn backend_buffer(&self) -> Option<&Arc<dyn BackendBuffer<T>>> {
1163        match &self.buffer {
1164            TensorBufferRefMut::Host(_) => None,
1165            TensorBufferRefMut::Backend(buffer) => Some(buffer),
1166        }
1167    }
1168
1169    /// Compute the physical element offset for a logical index.
1170    ///
1171    /// # Examples
1172    ///
1173    /// ```rust
1174    /// use tenferro_tensor::TypedTensorViewMut;
1175    ///
1176    /// let mut data = [1_i32, 2, 3];
1177    /// let view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
1178    /// assert_eq!(view.linear_offset(&[2]), Some(0));
1179    /// # Ok::<(), tenferro_tensor::Error>(())
1180    /// ```
1181    pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
1182        checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
1183    }
1184
1185    /// Borrow one host element by logical index.
1186    ///
1187    /// # Examples
1188    ///
1189    /// ```rust
1190    /// use tenferro_tensor::TypedTensorViewMut;
1191    ///
1192    /// let mut data = [1_i32, 2];
1193    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1194    /// assert_eq!(view.get(&[1]), Some(&2));
1195    /// # Ok::<(), tenferro_tensor::Error>(())
1196    /// ```
1197    pub fn get(&self, indices: &[usize]) -> Option<&T> {
1198        let offset = self.linear_offset(indices)?;
1199        match &self.buffer {
1200            TensorBufferRefMut::Host(data) => data.get(offset),
1201            TensorBufferRefMut::Backend(_) => None,
1202        }
1203    }
1204
1205    /// Mutably borrow one host element by logical index.
1206    ///
1207    /// # Examples
1208    ///
1209    /// ```rust
1210    /// use tenferro_tensor::TypedTensorViewMut;
1211    ///
1212    /// let mut data = [1_i32, 2];
1213    /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1214    /// *view.get_mut(&[1]).unwrap() = 20;
1215    /// assert_eq!(view.get(&[1]), Some(&20));
1216    /// # Ok::<(), tenferro_tensor::Error>(())
1217    /// ```
1218    pub fn get_mut(&mut self, indices: &[usize]) -> Option<&mut T> {
1219        let offset = self.linear_offset(indices)?;
1220        match &mut self.buffer {
1221            TensorBufferRefMut::Host(data) => data.get_mut(offset),
1222            TensorBufferRefMut::Backend(_) => None,
1223        }
1224    }
1225
1226    /// Copy compact column-major host tensor values into this mutable view.
1227    ///
1228    /// This is an explicit copy-back boundary. Backend source or destination
1229    /// buffers return an error instead of transferring data implicitly.
1230    ///
1231    /// # Examples
1232    ///
1233    /// ```rust
1234    /// use tenferro_tensor::{Rank, TypedTensor};
1235    ///
1236    /// let mut tensor = TypedTensor::<i32, Rank<2>>::from_vec_col_major([2, 2], vec![0, 0, 0, 0]).unwrap();
1237    /// let src = TypedTensor::<i32, Rank<2>>::from_vec_col_major([2, 2], vec![1, 2, 3, 4]).unwrap();
1238    /// tensor.as_view_mut().transpose_view([1, 0])?.copy_from_contiguous(&src)?;
1239    /// assert_eq!(tensor.as_slice()?, &[1, 3, 2, 4]);
1240    /// # Ok::<(), tenferro_tensor::Error>(())
1241    /// ```
1242    pub fn copy_from_contiguous(&mut self, src: &TypedTensor<T, R>) -> crate::Result<()>
1243    where
1244        T: Clone,
1245    {
1246        let op = "TypedTensorViewMut::copy_from_contiguous";
1247        if self.shape() != src.shape() {
1248            return Err(crate::Error::InvalidConfig {
1249                op,
1250                message: format!(
1251                    "shape mismatch: destination {:?} does not match source {:?}",
1252                    self.shape(),
1253                    src.shape()
1254                ),
1255            });
1256        }
1257
1258        let src_data = match &src.buffer {
1259            Buffer::Host(data) => contiguous_layout_slice(src.layout(), data, op)?,
1260            Buffer::Backend(_) => {
1261                return Err(crate::Error::backend_failure(
1262                    op,
1263                    "source backend buffer cannot be copied through host memory; download explicitly first",
1264                ))
1265            }
1266        };
1267
1268        let shape = self.shape().to_vec();
1269        let strides = self.strides().to_vec();
1270        let offset = self.offset();
1271        let dst_data = match &mut self.buffer {
1272            TensorBufferRefMut::Host(data) => data,
1273            TensorBufferRefMut::Backend(_) => {
1274                return Err(crate::Error::backend_failure(
1275                    op,
1276                    "destination backend buffer cannot be updated through host memory; download explicitly first",
1277                ))
1278            }
1279        };
1280
1281        let mut src_iter = src_data.iter();
1282        for_each_layout_offset_col_major(&shape, &strides, offset, op, |offset| {
1283            let value = src_iter.next().ok_or_else(|| crate::Error::InvalidConfig {
1284                op,
1285                message: "source tensor ended before destination view".to_string(),
1286            })?;
1287            let dst = dst_data
1288                .get_mut(offset)
1289                .ok_or_else(|| crate::Error::InvalidConfig {
1290                    op,
1291                    message: "destination view offset is outside host buffer".to_string(),
1292                })?;
1293            *dst = value.clone();
1294            Ok(())
1295        })?;
1296        if src_iter.next().is_some() {
1297            return Err(crate::Error::InvalidConfig {
1298                op,
1299                message: "source tensor has elements remaining after destination copy".to_string(),
1300            });
1301        }
1302        Ok(())
1303    }
1304
1305    /// Borrow this mutable view as a read-only view.
1306    ///
1307    /// # Examples
1308    ///
1309    /// ```rust
1310    /// use tenferro_tensor::TypedTensorViewMut;
1311    ///
1312    /// let mut data = [1_i32];
1313    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1314    /// assert_eq!(view.as_read_only().get(&[0]), Some(&1));
1315    /// # Ok::<(), tenferro_tensor::Error>(())
1316    /// ```
1317    pub fn as_read_only(&self) -> TypedTensorView<'_, T, R> {
1318        let buffer = match &self.buffer {
1319            TensorBufferRefMut::Host(data) => TensorBufferRef::Host(data),
1320            TensorBufferRefMut::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
1321        };
1322        TypedTensorView {
1323            buffer,
1324            layout: self.layout.clone(),
1325            placement: self.placement.clone(),
1326        }
1327    }
1328
1329    /// Convert this mutable view into a read-only view.
1330    ///
1331    /// # Examples
1332    ///
1333    /// ```rust
1334    /// use tenferro_tensor::TypedTensorViewMut;
1335    ///
1336    /// let mut data = [1_i32];
1337    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1338    /// assert_eq!(view.into_read_only().get(&[0]), Some(&1));
1339    /// # Ok::<(), tenferro_tensor::Error>(())
1340    /// ```
1341    pub fn into_read_only(self) -> TypedTensorView<'a, T, R> {
1342        let buffer = match self.buffer {
1343            TensorBufferRefMut::Host(data) => TensorBufferRef::Host(data),
1344            TensorBufferRefMut::Backend(buffer) => TensorBufferRef::Backend(buffer),
1345        };
1346        TypedTensorView {
1347            buffer,
1348            layout: self.layout,
1349            placement: self.placement,
1350        }
1351    }
1352
1353    /// Consume this mutable view and return a metadata-only axis permutation.
1354    ///
1355    /// # Examples
1356    ///
1357    /// ```rust
1358    /// use tenferro_tensor::{Rank, TypedTensorViewMut};
1359    ///
1360    /// let mut data = [1_i32, 2, 3, 4];
1361    /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
1362    /// let transposed = view.transpose_view([1, 0])?;
1363    /// assert_eq!(transposed.strides(), &[2, 1]);
1364    /// # Ok::<(), tenferro_tensor::Error>(())
1365    /// ```
1366    pub fn transpose_view(
1367        self,
1368        axes: impl AsRef<[usize]>,
1369    ) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
1370        let Self {
1371            buffer,
1372            layout,
1373            placement,
1374        } = self;
1375        let layout = layout
1376            .transpose_view(axes)
1377            .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
1378        layout
1379            .validate_mutable_no_overlap()
1380            .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
1381        match buffer {
1382            TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
1383                buffer: TensorBufferRefMut::Host(data),
1384                layout,
1385                placement,
1386            }),
1387            TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
1388                buffer: TensorBufferRefMut::Backend(buffer),
1389                layout,
1390                placement,
1391            }),
1392        }
1393    }
1394
1395    /// Return a mutable metadata-only slice using one [`StridedSliceSpec`] per axis.
1396    ///
1397    /// # Examples
1398    ///
1399    /// ```rust
1400    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
1401    ///
1402    /// let mut data = [1_i32, 2, 3];
1403    /// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![1], 0, &mut data)?;
1404    /// *view.try_slice(&[StridedSliceSpec::reverse()])?.get_mut(&[0]).unwrap() = 30;
1405    /// assert_eq!(view.get(&[2]), Some(&30));
1406    /// # Ok::<(), tenferro_tensor::Error>(())
1407    /// ```
1408    pub fn try_slice(
1409        &mut self,
1410        slices: &[StridedSliceSpec],
1411    ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
1412        let specs = core_slice_specs(slices, self.shape(), "TypedTensorViewMut::try_slice")?;
1413        let layout = self
1414            .layout
1415            .slice_view(specs, self.buffer.len())
1416            .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_slice", err))?;
1417        layout
1418            .validate_mutable_no_overlap()
1419            .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_slice", err))?;
1420        let placement = self.placement.clone();
1421        match &mut self.buffer {
1422            TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
1423                buffer: TensorBufferRefMut::Host(data),
1424                layout,
1425                placement,
1426            }),
1427            TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
1428                buffer: TensorBufferRefMut::Backend(Arc::clone(buffer)),
1429                layout,
1430                placement,
1431            }),
1432        }
1433    }
1434
1435    /// Return a mutable metadata-only slice along one axis.
1436    ///
1437    /// # Examples
1438    ///
1439    /// ```rust
1440    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
1441    ///
1442    /// let mut data = [1_i32, 2, 3, 4];
1443    /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
1444    /// assert_eq!(view.try_slice_axis(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
1445    /// # Ok::<(), tenferro_tensor::Error>(())
1446    /// ```
1447    pub fn try_slice_axis(
1448        &mut self,
1449        axis: usize,
1450        slice: StridedSliceSpec,
1451    ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
1452        let slices = slice_axis_specs(
1453            self.shape().len(),
1454            axis,
1455            slice,
1456            "TypedTensorViewMut::try_slice_axis",
1457        )?;
1458        self.try_slice(&slices)
1459    }
1460
1461    /// Return two mutable metadata-only slices when their physical ranges are disjoint.
1462    ///
1463    /// # Examples
1464    ///
1465    /// ```rust
1466    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
1467    ///
1468    /// let mut data = [1_i32, 2, 3, 4];
1469    /// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
1470    /// let (left, right) = view
1471    ///     .try_multi_slice_mut(
1472    ///         &[StridedSliceSpec::new(0, Some(2), 1)],
1473    ///         &[StridedSliceSpec::new(2, Some(4), 1)],
1474    ///     )
1475    ///     .unwrap();
1476    /// assert_eq!(left.shape(), &[2]);
1477    /// assert_eq!(right.shape(), &[2]);
1478    /// # Ok::<(), tenferro_tensor::Error>(())
1479    /// ```
1480    pub fn try_multi_slice_mut(
1481        &mut self,
1482        first: &[StridedSliceSpec],
1483        second: &[StridedSliceSpec],
1484    ) -> Option<(TypedTensorViewMut<'_, T, R>, TypedTensorViewMut<'_, T, R>)> {
1485        let first_specs = core_slice_specs(
1486            first,
1487            self.shape(),
1488            "TypedTensorViewMut::try_multi_slice_mut",
1489        )
1490        .ok()?;
1491        let second_specs = core_slice_specs(
1492            second,
1493            self.shape(),
1494            "TypedTensorViewMut::try_multi_slice_mut",
1495        )
1496        .ok()?;
1497        let buffer_len = self.buffer.len();
1498        let first_layout = self.layout.slice_view(first_specs, buffer_len).ok()?;
1499        let second_layout = self.layout.slice_view(second_specs, buffer_len).ok()?;
1500        first_layout.validate_mutable_no_overlap().ok()?;
1501        second_layout.validate_mutable_no_overlap().ok()?;
1502
1503        match (
1504            reachable_layout_span(
1505                first_layout.shape(),
1506                first_layout.strides(),
1507                first_layout.offset(),
1508            )
1509            .ok()?,
1510            reachable_layout_span(
1511                second_layout.shape(),
1512                second_layout.strides(),
1513                second_layout.offset(),
1514            )
1515            .ok()?,
1516        ) {
1517            (Some(first_span), Some(second_span)) => {
1518                let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
1519                let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
1520                let (first_data, second_data) = match &mut self.buffer {
1521                    TensorBufferRefMut::Host(data) => {
1522                        split_two_mut_ranges(data, first_span, second_span)?
1523                    }
1524                    TensorBufferRefMut::Backend(_) => return None,
1525                };
1526                let first_view = view_mut_from_layout_and_slice(
1527                    &first_layout,
1528                    first_offset,
1529                    first_data,
1530                    self.placement.clone(),
1531                )
1532                .ok()?;
1533                let second_view = view_mut_from_layout_and_slice(
1534                    &second_layout,
1535                    second_offset,
1536                    second_data,
1537                    self.placement.clone(),
1538                )
1539                .ok()?;
1540                Some((first_view, second_view))
1541            }
1542            (None, Some(second_span)) => {
1543                let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
1544                let (_, after_start) = match &mut self.buffer {
1545                    TensorBufferRefMut::Host(data) => data.split_at_mut(second_span.0),
1546                    TensorBufferRefMut::Backend(_) => return None,
1547                };
1548                let (second_data, _) = after_start.split_at_mut(second_span.1 - second_span.0 + 1);
1549                let first_view = view_mut_from_layout_and_slice(
1550                    &first_layout,
1551                    0,
1552                    &mut [],
1553                    self.placement.clone(),
1554                )
1555                .ok()?;
1556                let second_view = view_mut_from_layout_and_slice(
1557                    &second_layout,
1558                    second_offset,
1559                    second_data,
1560                    self.placement.clone(),
1561                )
1562                .ok()?;
1563                Some((first_view, second_view))
1564            }
1565            (Some(first_span), None) => {
1566                let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
1567                let (_, after_start) = match &mut self.buffer {
1568                    TensorBufferRefMut::Host(data) => data.split_at_mut(first_span.0),
1569                    TensorBufferRefMut::Backend(_) => return None,
1570                };
1571                let (first_data, _) = after_start.split_at_mut(first_span.1 - first_span.0 + 1);
1572                let first_view = view_mut_from_layout_and_slice(
1573                    &first_layout,
1574                    first_offset,
1575                    first_data,
1576                    self.placement.clone(),
1577                )
1578                .ok()?;
1579                let second_view = view_mut_from_layout_and_slice(
1580                    &second_layout,
1581                    0,
1582                    &mut [],
1583                    self.placement.clone(),
1584                )
1585                .ok()?;
1586                Some((first_view, second_view))
1587            }
1588            (None, None) => {
1589                let first_view = view_mut_from_layout_and_slice(
1590                    &first_layout,
1591                    0,
1592                    &mut [],
1593                    self.placement.clone(),
1594                )
1595                .ok()?;
1596                let second_view = view_mut_from_layout_and_slice(
1597                    &second_layout,
1598                    0,
1599                    &mut [],
1600                    self.placement.clone(),
1601                )
1602                .ok()?;
1603                Some((first_view, second_view))
1604            }
1605        }
1606    }
1607
1608    /// Return a mutable metadata-only dynamic-rank reshape for contiguous views.
1609    ///
1610    /// # Examples
1611    ///
1612    /// ```rust
1613    /// use tenferro_tensor::TypedTensorViewMut;
1614    ///
1615    /// let mut data = [1_i32, 2, 3, 4];
1616    /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
1617    /// assert_eq!(view.try_reshape(&[4])?.shape(), &[4]);
1618    /// # Ok::<(), tenferro_tensor::Error>(())
1619    /// ```
1620    pub fn try_reshape(
1621        &mut self,
1622        shape: &[usize],
1623    ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>> {
1624        let layout = reshape_layout_dyn(
1625            &self.layout,
1626            shape,
1627            self.buffer.len(),
1628            "TypedTensorViewMut::try_reshape",
1629        )?;
1630        layout
1631            .validate_mutable_no_overlap()
1632            .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_reshape", err))?;
1633        let placement = self.placement.clone();
1634        match &mut self.buffer {
1635            TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
1636                buffer: TensorBufferRefMut::Host(data),
1637                layout,
1638                placement,
1639            }),
1640            TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
1641                buffer: TensorBufferRefMut::Backend(Arc::clone(buffer)),
1642                layout,
1643                placement,
1644            }),
1645        }
1646    }
1647}
1648
1649/// Runtime scalar dtype tag.
1650///
1651/// # Examples
1652///
1653/// ```rust
1654/// use tenferro_tensor::DType;
1655///
1656/// assert_eq!(DType::F64 as u8, DType::F64 as u8);
1657/// ```
1658#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1659pub enum DType {
1660    F32,
1661    F64,
1662    I32,
1663    I64,
1664    Bool,
1665    C32,
1666    C64,
1667}
1668
1669/// Sealed trait for scalar types that can be stored in a [`Tensor`].
1670///
1671/// This trait is implemented for `f64`, `f32`, `i32`, `i64`, `bool`,
1672/// [`Complex64`], and [`Complex32`].
1673///
1674/// # Examples
1675///
1676/// ```
1677/// use tenferro_tensor::TensorScalar;
1678///
1679/// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
1680/// assert_eq!(tensor.as_slice::<f64>()?, [1.0, 2.0].as_slice());
1681/// # Ok::<(), tenferro_tensor::Error>(())
1682/// ```
1683pub trait TensorScalar: Copy + Clone + Send + Sync + 'static + private::Sealed {
1684    /// Real-valued counterpart of this scalar type.
1685    type Real: TensorScalar;
1686
1687    /// The [`DType`] tag corresponding to this scalar type.
1688    ///
1689    /// # Examples
1690    ///
1691    /// ```
1692    /// use tenferro_tensor::{DType, TensorScalar};
1693    ///
1694    /// assert_eq!(f64::dtype(), DType::F64);
1695    /// assert_eq!(f32::dtype(), DType::F32);
1696    /// ```
1697    fn dtype() -> DType;
1698
1699    /// Wrap typed column-major data into a [`Tensor`] enum variant.
1700    fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor>;
1701
1702    /// Borrow a typed tensor as a dtype-erased [`TensorRead`] view.
1703    ///
1704    /// This keeps the typed tensor borrowed instead of copying host data into
1705    /// a new dynamic tensor.
1706    ///
1707    /// # Examples
1708    ///
1709    /// ```
1710    /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
1711    ///
1712    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
1713    /// let read = f64::tensor_read(&tensor);
1714    /// assert_eq!(read.dtype(), DType::F64);
1715    /// assert_eq!(read.shape(), &[2]);
1716    /// ```
1717    fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_>;
1718
1719    /// Borrow the host data from a [`Tensor`].
1720    fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]>;
1721
1722    /// Mutably borrow the host data from a [`Tensor`].
1723    ///
1724    /// # Examples
1725    ///
1726    /// ```
1727    /// use tenferro_tensor::{Tensor, TensorScalar};
1728    ///
1729    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
1730    /// <f64 as TensorScalar>::as_slice_mut(&mut tensor)?[0] = 3.0;
1731    ///
1732    /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
1733    /// # Ok::<(), tenferro_tensor::Error>(())
1734    /// ```
1735    fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]>;
1736
1737    /// Extract a [`TypedTensor<Self>`] from a dynamic [`Tensor`].
1738    ///
1739    /// # Examples
1740    ///
1741    /// ```
1742    /// use tenferro_tensor::{Tensor, TensorScalar};
1743    ///
1744    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
1745    /// let typed = <f64 as TensorScalar>::into_typed(tensor)?;
1746    ///
1747    /// assert_eq!(typed.as_slice()?, &[1.0, 2.0]);
1748    /// # Ok::<(), tenferro_tensor::Error>(())
1749    /// ```
1750    fn into_typed(tensor: Tensor) -> crate::Result<TypedTensor<Self>>;
1751}
1752
1753mod private {
1754    pub trait Sealed {}
1755
1756    impl Sealed for f64 {}
1757    impl Sealed for f32 {}
1758    impl Sealed for i32 {}
1759    impl Sealed for i64 {}
1760    impl Sealed for bool {}
1761    impl Sealed for num_complex::Complex64 {}
1762    impl Sealed for num_complex::Complex32 {}
1763}
1764
1765macro_rules! impl_tensor_scalar {
1766    ($ty:ty, $real:ty, $dtype:ident, $variant:ident) => {
1767        impl TensorScalar for $ty {
1768            type Real = $real;
1769
1770            fn dtype() -> DType {
1771                DType::$dtype
1772            }
1773
1774            fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor> {
1775                TypedTensor::from_vec_col_major(shape, data).map(Tensor::$variant)
1776            }
1777
1778            fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_> {
1779                TensorRead::from_view(TensorView::$variant(tensor.as_view()))
1780            }
1781
1782            fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]> {
1783                let actual = tensor.dtype();
1784                match tensor {
1785                    Tensor::$variant(t) => t.host_data(),
1786                    _ => Err(crate::Error::DTypeMismatch {
1787                        op: "Tensor::as_slice",
1788                        lhs: Self::dtype(),
1789                        rhs: actual,
1790                    }),
1791                }
1792            }
1793
1794            fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]> {
1795                let actual = tensor.dtype();
1796                match tensor {
1797                    Tensor::$variant(t) => t.host_data_mut(),
1798                    _ => Err(crate::Error::DTypeMismatch {
1799                        op: "Tensor::as_slice_mut",
1800                        lhs: Self::dtype(),
1801                        rhs: actual,
1802                    }),
1803                }
1804            }
1805
1806            fn into_typed(tensor: Tensor) -> crate::Result<TypedTensor<Self>> {
1807                let actual = tensor.dtype();
1808                match tensor {
1809                    Tensor::$variant(inner) => Ok(inner),
1810                    _ => Err(crate::Error::DTypeMismatch {
1811                        op: "TensorScalar::into_typed",
1812                        lhs: Self::dtype(),
1813                        rhs: actual,
1814                    }),
1815                }
1816            }
1817        }
1818    };
1819}
1820
1821impl_tensor_scalar!(f64, f64, F64, F64);
1822impl_tensor_scalar!(f32, f32, F32, F32);
1823impl_tensor_scalar!(i64, i64, I64, I64);
1824impl_tensor_scalar!(i32, i32, I32, I32);
1825impl_tensor_scalar!(bool, bool, Bool, Bool);
1826impl_tensor_scalar!(Complex64, f64, C64, C64);
1827impl_tensor_scalar!(Complex32, f32, C32, C32);
1828
1829/// Dynamic tensor enum over the supported scalar types.
1830///
1831/// The enum keeps dtype dynamic and rank dynamic. Use
1832/// [`TypedTensor<T, R>`](TypedTensor) directly when the scalar type or rank
1833/// should be represented in Rust's type system.
1834///
1835/// # Examples
1836///
1837/// ```rust
1838/// use tenferro_tensor::{Tensor, TypedTensor};
1839///
1840/// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
1841/// assert_eq!(t.shape(), &[2]);
1842///
1843/// let erased = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 2.0]).unwrap();
1844/// assert_eq!(erased.shape().len(), 2);
1845/// ```
1846#[derive(Clone, Debug)]
1847pub enum Tensor {
1848    F32(TypedTensor<f32>),
1849    F64(TypedTensor<f64>),
1850    I32(TypedTensor<i32>),
1851    I64(TypedTensor<i64>),
1852    Bool(TypedTensor<bool>),
1853    C32(TypedTensor<Complex<f32>>),
1854    C64(TypedTensor<Complex<f64>>),
1855}
1856
1857/// Dynamic read-only borrowed tensor view.
1858///
1859/// `TensorView` keeps dtype erased while borrowing typed view metadata and
1860/// storage. Use [`TypedTensorView`] directly when the scalar type is statically
1861/// known.
1862///
1863/// # Examples
1864///
1865/// ```
1866/// use tenferro_tensor::{DType, TensorView, TypedTensorView};
1867///
1868/// let data = [1_i32, 2, 3, 4];
1869/// let typed = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data)?;
1870/// let view = TensorView::I32(typed);
1871///
1872/// assert_eq!(view.dtype(), DType::I32);
1873/// assert_eq!(view.shape(), &[2, 2]);
1874/// # Ok::<(), tenferro_tensor::Error>(())
1875/// ```
1876#[derive(Clone, Debug)]
1877pub enum TensorView<'a> {
1878    F32(TypedTensorView<'a, f32>),
1879    F64(TypedTensorView<'a, f64>),
1880    I32(TypedTensorView<'a, i32>),
1881    I64(TypedTensorView<'a, i64>),
1882    Bool(TypedTensorView<'a, bool>),
1883    C32(TypedTensorView<'a, Complex<f32>>),
1884    C64(TypedTensorView<'a, Complex<f64>>),
1885}
1886
1887/// Read-only tensor input accepted by synchronous eager kernels.
1888///
1889/// `TensorRead` lets kernels accept either an owned tensor reference or a
1890/// borrowed [`TensorView`] without forcing callers to materialize first.
1891/// The `View` variant preserves arbitrary strides and offsets, so kernels that
1892/// support strided reads can consume transposes, slices, and broadcasts directly.
1893///
1894/// `TensorRead` is intentionally borrowed. It is an input-dispatch type, not an
1895/// owned lazy tensor value. APIs that need to store a lazy layout result should
1896/// keep an owned base tensor plus layout metadata, then expose a `TensorRead`
1897/// only for the duration of kernel dispatch.
1898///
1899/// # Examples
1900///
1901/// ```
1902/// use tenferro_tensor::{DType, Tensor, TensorRead};
1903///
1904/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
1905/// let read = TensorRead::from_tensor(&tensor);
1906///
1907/// assert_eq!(read.dtype(), DType::F64);
1908/// assert_eq!(read.shape(), &[2]);
1909/// ```
1910// Keep borrowed views inline to avoid allocation on read-only tensor dispatch paths.
1911#[allow(clippy::large_enum_variant)]
1912#[derive(Clone, Debug)]
1913pub enum TensorRead<'a> {
1914    Tensor(&'a Tensor),
1915    View(TensorView<'a>),
1916}
1917
1918/// Owned lazy tensor view over a shared base tensor.
1919///
1920/// This stores only ownership of the base allocation plus logical layout
1921/// metadata. Borrow it as [`TensorRead`] for kernels that understand strides,
1922/// or materialize it explicitly with [`TensorOwnedView::to_tensor`].
1923#[derive(Clone, Debug)]
1924pub struct TensorOwnedView {
1925    base: Arc<Tensor>,
1926    layout: TensorLayout<DynRank>,
1927}
1928
1929/// Owned tensor value that can be compact or a lazy view.
1930///
1931/// `TensorValue` is the owned counterpart to [`TensorRead`]. It is suitable for
1932/// storing eager results that should remain lazy until an operation actually
1933/// requires compact materialized storage.
1934#[derive(Clone, Debug)]
1935pub enum TensorValue {
1936    Tensor(Arc<Tensor>),
1937    View(TensorOwnedView),
1938}
1939
1940impl TensorOwnedView {
1941    /// Create an owned view preserving the base tensor's current layout.
1942    pub fn from_tensor(base: Arc<Tensor>) -> Self {
1943        let layout = tensor_layout(base.as_ref());
1944        Self { base, layout }
1945    }
1946
1947    /// Create an owned view with explicit layout metadata.
1948    pub fn from_parts(
1949        base: Arc<Tensor>,
1950        shape: Vec<usize>,
1951        strides: Vec<isize>,
1952        offset: isize,
1953    ) -> crate::Result<Self> {
1954        let layout = TensorLayout::from_parts(
1955            shape.into(),
1956            strides.into(),
1957            offset,
1958            tensor_buffer_len(&base),
1959        )
1960        .map_err(|err| tensor_layout_error("TensorOwnedView::from_parts", err))?;
1961        Ok(Self { base, layout })
1962    }
1963
1964    pub fn dtype(&self) -> DType {
1965        self.base.dtype()
1966    }
1967
1968    pub fn shape(&self) -> &[usize] {
1969        self.layout.shape()
1970    }
1971
1972    pub fn strides(&self) -> &[isize] {
1973        self.layout.strides()
1974    }
1975
1976    pub fn offset(&self) -> isize {
1977        self.layout.offset()
1978    }
1979
1980    pub fn tensor_view(&self) -> TensorView<'_> {
1981        tensor_view_with_layout(self.base.as_ref(), self.layout.clone())
1982    }
1983
1984    pub fn tensor_read(&self) -> TensorRead<'_> {
1985        TensorRead::from_view(self.tensor_view())
1986    }
1987
1988    /// Materialize this owned view into an owned compact tensor.
1989    ///
1990    /// This returns an explicit error for backend-backed views because no
1991    /// backend context is available for an implicit download.
1992    ///
1993    /// # Examples
1994    ///
1995    /// ```rust
1996    /// use std::sync::Arc;
1997    /// use tenferro_tensor::{Tensor, TensorOwnedView};
1998    ///
1999    /// let base = Arc::new(Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap());
2000    /// let view = TensorOwnedView::from_tensor(base);
2001    /// let tensor = view.to_tensor()?;
2002    /// assert_eq!(tensor.shape(), &[2]);
2003    /// # Ok::<(), tenferro_tensor::Error>(())
2004    /// ```
2005    pub fn to_tensor(&self) -> crate::Result<Tensor> {
2006        self.tensor_view().to_tensor()
2007    }
2008
2009    pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
2010        let layout = self
2011            .layout
2012            .transpose_view(axes)
2013            .map_err(|err| tensor_layout_error("TensorOwnedView::transpose_view", err))?;
2014        Ok(Self {
2015            base: Arc::clone(&self.base),
2016            layout,
2017        })
2018    }
2019
2020    pub fn reshape_view(&self, shape: &[usize]) -> crate::Result<Self> {
2021        let layout = reshape_layout_dyn(
2022            &self.layout,
2023            shape,
2024            tensor_buffer_len(&self.base),
2025            "TensorOwnedView::reshape_view",
2026        )?;
2027        Ok(Self {
2028            base: Arc::clone(&self.base),
2029            layout,
2030        })
2031    }
2032
2033    pub fn slice_view(&self, config: &SliceConfig) -> crate::Result<Self> {
2034        let op = "TensorOwnedView::slice_view";
2035        if config.starts.len() != self.shape().len() {
2036            return Err(crate::Error::RankMismatch {
2037                op,
2038                expected: self.shape().len(),
2039                actual: config.starts.len(),
2040            });
2041        }
2042        if config.limits.len() != self.shape().len() {
2043            return Err(crate::Error::RankMismatch {
2044                op,
2045                expected: self.shape().len(),
2046                actual: config.limits.len(),
2047            });
2048        }
2049        if config.strides.len() != self.shape().len() {
2050            return Err(crate::Error::RankMismatch {
2051                op,
2052                expected: self.shape().len(),
2053                actual: config.strides.len(),
2054            });
2055        }
2056
2057        let mut slices = Vec::with_capacity(self.shape().len());
2058        for ((&start, &limit), &stride) in config
2059            .starts
2060            .iter()
2061            .zip(config.limits.iter())
2062            .zip(config.strides.iter())
2063        {
2064            let start = isize::try_from(start).map_err(|_| crate::Error::InvalidConfig {
2065                op,
2066                message: format!("slice start {start} does not fit in isize"),
2067            })?;
2068            let limit = isize::try_from(limit).map_err(|_| crate::Error::InvalidConfig {
2069                op,
2070                message: format!("slice limit {limit} does not fit in isize"),
2071            })?;
2072            let stride = isize::try_from(stride).map_err(|_| crate::Error::InvalidConfig {
2073                op,
2074                message: format!("slice stride {stride} does not fit in isize"),
2075            })?;
2076            slices.push(StridedSliceSpec::new(start, Some(limit), stride));
2077        }
2078
2079        let specs = core_slice_specs(&slices, self.shape(), op)?;
2080        let layout = self
2081            .layout
2082            .slice_view(&specs, tensor_buffer_len(&self.base))
2083            .map_err(|err| tensor_layout_error(op, err))?;
2084        Ok(Self {
2085            base: Arc::clone(&self.base),
2086            layout,
2087        })
2088    }
2089
2090    pub fn broadcast_in_dim_view(&self, shape: &[usize], dims: &[usize]) -> crate::Result<Self> {
2091        let layout = self
2092            .layout
2093            .broadcast_in_dim_view::<DynRank>(
2094                shape.to_vec().into(),
2095                dims,
2096                tensor_buffer_len(&self.base),
2097            )
2098            .map_err(|err| tensor_layout_error("TensorOwnedView::broadcast_in_dim_view", err))?;
2099        Ok(Self {
2100            base: Arc::clone(&self.base),
2101            layout,
2102        })
2103    }
2104}
2105
2106impl TensorValue {
2107    pub fn from_tensor(tensor: Tensor) -> Self {
2108        Self::Tensor(Arc::new(tensor))
2109    }
2110
2111    pub fn from_tensor_arc(tensor: Arc<Tensor>) -> Self {
2112        Self::Tensor(tensor)
2113    }
2114
2115    pub fn as_tensor_arc(&self) -> Option<&Arc<Tensor>> {
2116        match self {
2117            Self::Tensor(tensor) => Some(tensor),
2118            Self::View(_) => None,
2119        }
2120    }
2121
2122    pub fn dtype(&self) -> DType {
2123        match self {
2124            Self::Tensor(tensor) => tensor.dtype(),
2125            Self::View(view) => view.dtype(),
2126        }
2127    }
2128
2129    pub fn shape(&self) -> &[usize] {
2130        match self {
2131            Self::Tensor(tensor) => tensor.shape(),
2132            Self::View(view) => view.shape(),
2133        }
2134    }
2135
2136    pub fn tensor_read(&self) -> TensorRead<'_> {
2137        match self {
2138            Self::Tensor(tensor) => TensorRead::from_tensor(tensor.as_ref()),
2139            Self::View(view) => view.tensor_read(),
2140        }
2141    }
2142
2143    /// Materialize this tensor value into an owned compact tensor.
2144    ///
2145    /// Compact tensor values are cloned. Lazy host views are materialized.
2146    /// Backend-backed views return an explicit error instead of panicking.
2147    ///
2148    /// # Examples
2149    ///
2150    /// ```rust
2151    /// use tenferro_tensor::{Tensor, TensorValue};
2152    ///
2153    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major(
2154    ///     vec![2],
2155    ///     vec![1.0_f64, 2.0],
2156    /// ).unwrap());
2157    /// let tensor = value.to_tensor()?;
2158    /// assert_eq!(tensor.shape(), &[2]);
2159    /// # Ok::<(), tenferro_tensor::Error>(())
2160    /// ```
2161    pub fn to_tensor(&self) -> crate::Result<Tensor> {
2162        match self {
2163            Self::Tensor(tensor) => Ok(tensor.as_ref().clone()),
2164            Self::View(view) => view.to_tensor(),
2165        }
2166    }
2167
2168    pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
2169        match self {
2170            Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2171                .transpose_view(axes)
2172                .map(Self::View),
2173            Self::View(view) => view.transpose_view(axes).map(Self::View),
2174        }
2175    }
2176
2177    pub fn reshape_view(&self, shape: &[usize]) -> crate::Result<Self> {
2178        match self {
2179            Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2180                .reshape_view(shape)
2181                .map(Self::View),
2182            Self::View(view) => view.reshape_view(shape).map(Self::View),
2183        }
2184    }
2185
2186    pub fn slice_view(&self, config: &SliceConfig) -> crate::Result<Self> {
2187        match self {
2188            Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2189                .slice_view(config)
2190                .map(Self::View),
2191            Self::View(view) => view.slice_view(config).map(Self::View),
2192        }
2193    }
2194
2195    pub fn broadcast_in_dim_view(&self, shape: &[usize], dims: &[usize]) -> crate::Result<Self> {
2196        match self {
2197            Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2198                .broadcast_in_dim_view(shape, dims)
2199                .map(Self::View),
2200            Self::View(view) => view.broadcast_in_dim_view(shape, dims).map(Self::View),
2201        }
2202    }
2203}
2204
2205fn tensor_layout(tensor: &Tensor) -> TensorLayout<DynRank> {
2206    match tensor {
2207        Tensor::F32(tensor) => tensor.layout.clone(),
2208        Tensor::F64(tensor) => tensor.layout.clone(),
2209        Tensor::I32(tensor) => tensor.layout.clone(),
2210        Tensor::I64(tensor) => tensor.layout.clone(),
2211        Tensor::Bool(tensor) => tensor.layout.clone(),
2212        Tensor::C32(tensor) => tensor.layout.clone(),
2213        Tensor::C64(tensor) => tensor.layout.clone(),
2214    }
2215}
2216
2217fn tensor_buffer_len(tensor: &Tensor) -> usize {
2218    match tensor {
2219        Tensor::F32(tensor) => buffer_len(&tensor.buffer),
2220        Tensor::F64(tensor) => buffer_len(&tensor.buffer),
2221        Tensor::I32(tensor) => buffer_len(&tensor.buffer),
2222        Tensor::I64(tensor) => buffer_len(&tensor.buffer),
2223        Tensor::Bool(tensor) => buffer_len(&tensor.buffer),
2224        Tensor::C32(tensor) => buffer_len(&tensor.buffer),
2225        Tensor::C64(tensor) => buffer_len(&tensor.buffer),
2226    }
2227}
2228
2229fn buffer_len<T: 'static>(buffer: &Buffer<T>) -> usize {
2230    match buffer {
2231        Buffer::Host(data) => data.len(),
2232        Buffer::Backend(buffer) => buffer.len(),
2233    }
2234}
2235
2236fn tensor_view_with_layout(tensor: &Tensor, layout: TensorLayout<DynRank>) -> TensorView<'_> {
2237    match tensor {
2238        Tensor::F32(tensor) => TensorView::F32(typed_view_with_layout(tensor, layout)),
2239        Tensor::F64(tensor) => TensorView::F64(typed_view_with_layout(tensor, layout)),
2240        Tensor::I32(tensor) => TensorView::I32(typed_view_with_layout(tensor, layout)),
2241        Tensor::I64(tensor) => TensorView::I64(typed_view_with_layout(tensor, layout)),
2242        Tensor::Bool(tensor) => TensorView::Bool(typed_view_with_layout(tensor, layout)),
2243        Tensor::C32(tensor) => TensorView::C32(typed_view_with_layout(tensor, layout)),
2244        Tensor::C64(tensor) => TensorView::C64(typed_view_with_layout(tensor, layout)),
2245    }
2246}
2247
2248fn typed_view_with_layout<T: 'static>(
2249    tensor: &TypedTensor<T>,
2250    layout: TensorLayout<DynRank>,
2251) -> TypedTensorView<'_, T> {
2252    let buffer = match &tensor.buffer {
2253        Buffer::Host(data) => TensorBufferRef::Host(data),
2254        Buffer::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
2255    };
2256    TypedTensorView {
2257        buffer,
2258        layout,
2259        placement: tensor.placement.clone(),
2260    }
2261}
2262
2263/// Wrap an `f64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2264///
2265/// # Examples
2266///
2267/// ```
2268/// use tenferro_tensor::{Tensor, TypedTensor};
2269///
2270/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
2271/// let tensor: Tensor = typed.into();
2272/// assert_eq!(tensor.shape(), &[2]);
2273/// ```
2274impl From<TypedTensor<f64>> for Tensor {
2275    fn from(t: TypedTensor<f64>) -> Self {
2276        Tensor::F64(t)
2277    }
2278}
2279
2280/// Wrap an `f32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2281///
2282/// # Examples
2283///
2284/// ```
2285/// use tenferro_tensor::{Tensor, TypedTensor};
2286///
2287/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
2288/// let tensor: Tensor = typed.into();
2289/// assert_eq!(tensor.shape(), &[2]);
2290/// ```
2291impl From<TypedTensor<f32>> for Tensor {
2292    fn from(t: TypedTensor<f32>) -> Self {
2293        Tensor::F32(t)
2294    }
2295}
2296
2297/// Wrap an `i64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2298///
2299/// # Examples
2300///
2301/// ```
2302/// use tenferro_tensor::{DType, Tensor, TypedTensor};
2303///
2304/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i64, 2]).unwrap();
2305/// let tensor: Tensor = typed.into();
2306/// assert_eq!(tensor.dtype(), DType::I64);
2307/// assert_eq!(tensor.shape(), &[2]);
2308/// ```
2309impl From<TypedTensor<i64>> for Tensor {
2310    fn from(t: TypedTensor<i64>) -> Self {
2311        Tensor::I64(t)
2312    }
2313}
2314
2315/// Wrap an `i32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2316///
2317/// # Examples
2318///
2319/// ```
2320/// use tenferro_tensor::{DType, Tensor, TypedTensor};
2321///
2322/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i32, 2]).unwrap();
2323/// let tensor: Tensor = typed.into();
2324/// assert_eq!(tensor.dtype(), DType::I32);
2325/// assert_eq!(tensor.shape(), &[2]);
2326/// ```
2327impl From<TypedTensor<i32>> for Tensor {
2328    fn from(t: TypedTensor<i32>) -> Self {
2329        Tensor::I32(t)
2330    }
2331}
2332
2333/// Wrap a `bool` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2334///
2335/// # Examples
2336///
2337/// ```
2338/// use tenferro_tensor::{DType, Tensor, TypedTensor};
2339///
2340/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![true, false]).unwrap();
2341/// let tensor: Tensor = typed.into();
2342/// assert_eq!(tensor.dtype(), DType::Bool);
2343/// assert_eq!(tensor.shape(), &[2]);
2344/// ```
2345impl From<TypedTensor<bool>> for Tensor {
2346    fn from(t: TypedTensor<bool>) -> Self {
2347        Tensor::Bool(t)
2348    }
2349}
2350
2351/// Wrap a [`Complex64`] [`TypedTensor`] into the corresponding [`Tensor`]
2352/// variant.
2353///
2354/// # Examples
2355///
2356/// ```
2357/// use num_complex::Complex64;
2358/// use tenferro_tensor::{Tensor, TypedTensor};
2359///
2360/// let typed = TypedTensor::from_vec_col_major(
2361///     vec![1],
2362///     vec![Complex64::new(1.0, 2.0)],
2363/// ).unwrap();
2364/// let tensor: Tensor = typed.into();
2365/// assert_eq!(tensor.shape(), &[1]);
2366/// ```
2367impl From<TypedTensor<Complex<f64>>> for Tensor {
2368    fn from(t: TypedTensor<Complex<f64>>) -> Self {
2369        Tensor::C64(t)
2370    }
2371}
2372
2373/// Wrap a [`Complex32`] [`TypedTensor`] into the corresponding [`Tensor`]
2374/// variant.
2375///
2376/// # Examples
2377///
2378/// ```
2379/// use num_complex::Complex32;
2380/// use tenferro_tensor::{Tensor, TypedTensor};
2381///
2382/// let typed = TypedTensor::from_vec_col_major(
2383///     vec![1],
2384///     vec![Complex32::new(1.0, 2.0)],
2385/// ).unwrap();
2386/// let tensor: Tensor = typed.into();
2387/// assert_eq!(tensor.shape(), &[1]);
2388/// ```
2389impl From<TypedTensor<Complex<f32>>> for Tensor {
2390    fn from(t: TypedTensor<Complex<f32>>) -> Self {
2391        Tensor::C32(t)
2392    }
2393}
2394
2395impl<'a> TensorView<'a> {
2396    /// Create a dynamic `f32` view over compact column-major host data.
2397    ///
2398    /// # Examples
2399    ///
2400    /// ```
2401    /// use tenferro_tensor::{DType, TensorView};
2402    ///
2403    /// let data = [1.0_f32, 2.0];
2404    /// let view = TensorView::f32(&[2], &data)?;
2405    /// assert_eq!(view.dtype(), DType::F32);
2406    /// # Ok::<(), tenferro_tensor::Error>(())
2407    /// ```
2408    pub fn f32(shape: &'a [usize], data: &'a [f32]) -> crate::Result<Self> {
2409        Ok(Self::F32(TypedTensorView::from_col_major(shape, data)?))
2410    }
2411
2412    /// Create a dynamic `f64` view over compact column-major host data.
2413    ///
2414    /// # Examples
2415    ///
2416    /// ```
2417    /// use tenferro_tensor::{DType, TensorView};
2418    ///
2419    /// let data = [1.0_f64, 2.0];
2420    /// let view = TensorView::f64(&[2], &data)?;
2421    /// assert_eq!(view.dtype(), DType::F64);
2422    /// # Ok::<(), tenferro_tensor::Error>(())
2423    /// ```
2424    pub fn f64(shape: &'a [usize], data: &'a [f64]) -> crate::Result<Self> {
2425        Ok(Self::F64(TypedTensorView::from_col_major(shape, data)?))
2426    }
2427
2428    /// Create a dynamic `i64` view over compact column-major host data.
2429    ///
2430    /// # Examples
2431    ///
2432    /// ```
2433    /// use tenferro_tensor::{DType, TensorView};
2434    ///
2435    /// let data = [1_i64, 2];
2436    /// let view = TensorView::i64(&[2], &data)?;
2437    /// assert_eq!(view.dtype(), DType::I64);
2438    /// # Ok::<(), tenferro_tensor::Error>(())
2439    /// ```
2440    pub fn i64(shape: &'a [usize], data: &'a [i64]) -> crate::Result<Self> {
2441        Ok(Self::I64(TypedTensorView::from_col_major(shape, data)?))
2442    }
2443
2444    /// Create a dynamic `i32` view over compact column-major host data.
2445    ///
2446    /// # Examples
2447    ///
2448    /// ```
2449    /// use tenferro_tensor::{DType, TensorView};
2450    ///
2451    /// let data = [1_i32, 2];
2452    /// let view = TensorView::i32(&[2], &data)?;
2453    /// assert_eq!(view.dtype(), DType::I32);
2454    /// # Ok::<(), tenferro_tensor::Error>(())
2455    /// ```
2456    pub fn i32(shape: &'a [usize], data: &'a [i32]) -> crate::Result<Self> {
2457        Ok(Self::I32(TypedTensorView::from_col_major(shape, data)?))
2458    }
2459
2460    /// Create a dynamic `bool` view over compact column-major host data.
2461    ///
2462    /// # Examples
2463    ///
2464    /// ```
2465    /// use tenferro_tensor::{DType, TensorView};
2466    ///
2467    /// let data = [true, false];
2468    /// let view = TensorView::bool(&[2], &data)?;
2469    /// assert_eq!(view.dtype(), DType::Bool);
2470    /// # Ok::<(), tenferro_tensor::Error>(())
2471    /// ```
2472    pub fn bool(shape: &'a [usize], data: &'a [bool]) -> crate::Result<Self> {
2473        Ok(Self::Bool(TypedTensorView::from_col_major(shape, data)?))
2474    }
2475
2476    /// Create a dynamic `Complex32` view over compact column-major host data.
2477    ///
2478    /// # Examples
2479    ///
2480    /// ```
2481    /// use num_complex::Complex32;
2482    /// use tenferro_tensor::{DType, TensorView};
2483    ///
2484    /// let data = [Complex32::new(1.0, 2.0)];
2485    /// let view = TensorView::c32(&[1], &data)?;
2486    /// assert_eq!(view.dtype(), DType::C32);
2487    /// # Ok::<(), tenferro_tensor::Error>(())
2488    /// ```
2489    pub fn c32(shape: &'a [usize], data: &'a [Complex32]) -> crate::Result<Self> {
2490        Ok(Self::C32(TypedTensorView::from_col_major(shape, data)?))
2491    }
2492
2493    /// Create a dynamic `Complex64` view over compact column-major host data.
2494    ///
2495    /// # Examples
2496    ///
2497    /// ```
2498    /// use num_complex::Complex64;
2499    /// use tenferro_tensor::{DType, TensorView};
2500    ///
2501    /// let data = [Complex64::new(1.0, 2.0)];
2502    /// let view = TensorView::c64(&[1], &data)?;
2503    /// assert_eq!(view.dtype(), DType::C64);
2504    /// # Ok::<(), tenferro_tensor::Error>(())
2505    /// ```
2506    pub fn c64(shape: &'a [usize], data: &'a [Complex64]) -> crate::Result<Self> {
2507        Ok(Self::C64(TypedTensorView::from_col_major(shape, data)?))
2508    }
2509
2510    pub fn dtype(&self) -> DType {
2511        match self {
2512            Self::F32(_) => DType::F32,
2513            Self::F64(_) => DType::F64,
2514            Self::I32(_) => DType::I32,
2515            Self::I64(_) => DType::I64,
2516            Self::Bool(_) => DType::Bool,
2517            Self::C32(_) => DType::C32,
2518            Self::C64(_) => DType::C64,
2519        }
2520    }
2521
2522    pub fn shape(&self) -> &[usize] {
2523        match self {
2524            Self::F32(t) => t.shape(),
2525            Self::F64(t) => t.shape(),
2526            Self::I32(t) => t.shape(),
2527            Self::I64(t) => t.shape(),
2528            Self::Bool(t) => t.shape(),
2529            Self::C32(t) => t.shape(),
2530            Self::C64(t) => t.shape(),
2531        }
2532    }
2533
2534    /// Materialize this host view into an owned tensor.
2535    ///
2536    /// This method has no backend context and does not download backend
2537    /// buffers. Use a backend-specific `TensorViewCanonicalization` method or
2538    /// an explicit device transfer before materializing backend views on the
2539    /// host.
2540    ///
2541    /// # Examples
2542    ///
2543    /// ```rust
2544    /// use tenferro_tensor::{DType, TensorView};
2545    ///
2546    /// let data = [1.0_f64, 2.0];
2547    /// let view = TensorView::f64(&[2], &data)?;
2548    /// let tensor = view.to_tensor()?;
2549    /// assert_eq!(tensor.dtype(), DType::F64);
2550    /// # Ok::<(), tenferro_tensor::Error>(())
2551    /// ```
2552    pub fn to_tensor(&self) -> crate::Result<Tensor> {
2553        match self {
2554            Self::F32(t) => {
2555                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::F32)
2556            }
2557            Self::F64(t) => {
2558                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::F64)
2559            }
2560            Self::I32(t) => {
2561                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::I32)
2562            }
2563            Self::I64(t) => {
2564                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::I64)
2565            }
2566            Self::Bool(t) => {
2567                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::Bool)
2568            }
2569            Self::C32(t) => {
2570                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::C32)
2571            }
2572            Self::C64(t) => {
2573                materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::C64)
2574            }
2575        }
2576    }
2577}
2578
2579impl<'a> TensorRead<'a> {
2580    pub fn from_tensor(tensor: &'a Tensor) -> Self {
2581        Self::Tensor(tensor)
2582    }
2583
2584    pub fn from_view(view: TensorView<'a>) -> Self {
2585        Self::View(view)
2586    }
2587
2588    pub fn dtype(&self) -> DType {
2589        match self {
2590            Self::Tensor(tensor) => tensor.dtype(),
2591            Self::View(view) => view.dtype(),
2592        }
2593    }
2594
2595    pub fn shape(&self) -> &[usize] {
2596        match self {
2597            Self::Tensor(tensor) => tensor.shape(),
2598            Self::View(view) => view.shape(),
2599        }
2600    }
2601
2602    pub fn as_tensor(&self) -> Option<&'a Tensor> {
2603        match self {
2604            Self::Tensor(tensor) => Some(*tensor),
2605            Self::View(_) => None,
2606        }
2607    }
2608
2609    /// Convert an owned tensor reference or host view into an owned tensor.
2610    ///
2611    /// This method clones owned tensor inputs and materializes host views. It
2612    /// has no backend context and does not download backend buffers. Use a
2613    /// backend-specific `TensorViewCanonicalization` method or an explicit
2614    /// device transfer before materializing backend views on the host.
2615    ///
2616    /// # Examples
2617    ///
2618    /// ```rust
2619    /// use tenferro_tensor::{TensorRead, TensorView};
2620    ///
2621    /// let data = [1_i32, 2, 3];
2622    /// let read = TensorRead::from_view(TensorView::i32(&[3], &data)?);
2623    /// let tensor = read.to_tensor()?;
2624    /// assert_eq!(tensor.shape(), &[3]);
2625    /// # Ok::<(), tenferro_tensor::Error>(())
2626    /// ```
2627    pub fn to_tensor(&self) -> crate::Result<Tensor> {
2628        match self {
2629            Self::Tensor(tensor) => Ok((*tensor).clone()),
2630            Self::View(view) => view.to_tensor(),
2631        }
2632    }
2633}
2634
2635/// Column-major strides derived from a shape.
2636///
2637/// # Examples
2638///
2639/// ```rust
2640/// use tenferro_tensor::col_major_strides;
2641///
2642/// assert_eq!(col_major_strides(&[2, 3])?, vec![1, 2]);
2643/// # Ok::<(), tenferro_tensor::Error>(())
2644/// ```
2645pub fn col_major_strides(shape: &[usize]) -> crate::Result<Vec<isize>> {
2646    let mut strides = Vec::with_capacity(shape.len());
2647    let mut stride = 1isize;
2648    for &extent in shape {
2649        strides.push(stride);
2650        let extent = isize::try_from(extent).map_err(|_| crate::Error::InvalidConfig {
2651            op: "col_major_strides",
2652            message: format!("shape extent {extent} does not fit in isize"),
2653        })?;
2654        stride = stride
2655            .checked_mul(extent)
2656            .ok_or_else(|| crate::Error::InvalidConfig {
2657                op: "col_major_strides",
2658                message: format!("column-major stride overflows for shape {shape:?}"),
2659            })?;
2660    }
2661    Ok(strides)
2662}
2663
2664fn try_linear_offset_for_shape(
2665    shape: &[usize],
2666    indices: &[usize],
2667    op: &'static str,
2668) -> crate::Result<usize> {
2669    if indices.len() != shape.len() {
2670        return Err(crate::Error::RankMismatch {
2671            op,
2672            expected: shape.len(),
2673            actual: indices.len(),
2674        });
2675    }
2676    let mut offset = 0usize;
2677    let mut stride = 1usize;
2678    for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
2679        if idx >= extent {
2680            return Err(crate::Error::InvalidConfig {
2681                op,
2682                message: format!("index {idx} out of bounds for axis {axis} extent {extent}"),
2683            });
2684        }
2685        offset = offset
2686            .checked_add(
2687                idx.checked_mul(stride)
2688                    .ok_or_else(|| crate::Error::InvalidConfig {
2689                        op,
2690                        message: "linear offset multiply overflows".to_string(),
2691                    })?,
2692            )
2693            .ok_or_else(|| crate::Error::InvalidConfig {
2694                op,
2695                message: "linear offset add overflows".to_string(),
2696            })?;
2697        stride = stride
2698            .checked_mul(extent)
2699            .ok_or_else(|| crate::Error::InvalidConfig {
2700                op,
2701                message: "linear offset stride overflows".to_string(),
2702            })?;
2703    }
2704    Ok(offset)
2705}
2706
2707fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
2708    shape.iter().try_fold(1usize, |acc, &dim| {
2709        acc.checked_mul(dim)
2710            .ok_or_else(|| crate::Error::InvalidConfig {
2711                op,
2712                message: format!("shape product overflows for shape {shape:?}"),
2713            })
2714    })
2715}
2716
2717fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
2718    let n = try_shape_product(shape, op)?;
2719    if data_len != n {
2720        return Err(crate::Error::InvalidConfig {
2721            op,
2722            message: format!("data length {data_len} does not match shape product {n}"),
2723        });
2724    }
2725    Ok(())
2726}
2727
2728fn try_compact_layout<R: TensorRank>(
2729    shape: impl Into<R::Shape>,
2730    op: &'static str,
2731) -> crate::Result<TensorLayout<R>> {
2732    TensorLayout::compact(shape.into()).map_err(|err| tensor_layout_error(op, err))
2733}
2734
2735fn tensor_layout_error(op: &'static str, err: tenferro_tensor_core::Error) -> crate::Error {
2736    match err {
2737        tenferro_tensor_core::Error::RankMismatch { expected, actual } => {
2738            crate::Error::RankMismatch {
2739                op,
2740                expected,
2741                actual,
2742            }
2743        }
2744        tenferro_tensor_core::Error::AxisOutOfBounds { axis, rank } => {
2745            crate::Error::AxisOutOfBounds { op, axis, rank }
2746        }
2747        tenferro_tensor_core::Error::DuplicateAxis { axis } => crate::Error::DuplicateAxis {
2748            op,
2749            axis,
2750            role: "permutation",
2751        },
2752        tenferro_tensor_core::Error::InvalidPermutationLength { expected, actual } => {
2753            crate::Error::RankMismatch {
2754                op,
2755                expected,
2756                actual,
2757            }
2758        }
2759        other => crate::Error::InvalidConfig {
2760            op,
2761            message: other.to_string(),
2762        },
2763    }
2764}
2765
2766fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
2767    shape.iter().try_fold(1usize, |product, &dim| {
2768        if dim == 0 {
2769            Ok(0)
2770        } else {
2771            product
2772                .checked_mul(dim)
2773                .ok_or_else(|| crate::Error::InvalidConfig {
2774                    op,
2775                    message: format!("shape product overflows for shape {shape:?}"),
2776                })
2777        }
2778    })
2779}
2780
2781fn checked_view_offset(
2782    shape: &[usize],
2783    strides: &[isize],
2784    base_offset: isize,
2785    indices: &[usize],
2786) -> Option<usize> {
2787    if indices.len() != shape.len() {
2788        return None;
2789    }
2790
2791    let mut offset = base_offset;
2792    for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
2793        if index >= extent {
2794            return None;
2795        }
2796        let index = isize::try_from(index).ok()?;
2797        let delta = index.checked_mul(stride)?;
2798        offset = offset.checked_add(delta)?;
2799    }
2800
2801    usize::try_from(offset).ok()
2802}
2803
2804fn for_each_layout_offset_col_major(
2805    shape: &[usize],
2806    strides: &[isize],
2807    base_offset: isize,
2808    op: &'static str,
2809    mut f: impl FnMut(usize) -> crate::Result<()>,
2810) -> crate::Result<()> {
2811    if shape.len() != strides.len() {
2812        return Err(crate::Error::InvalidConfig {
2813            op,
2814            message: format!(
2815                "shape rank {} does not match stride rank {}",
2816                shape.len(),
2817                strides.len()
2818            ),
2819        });
2820    }
2821
2822    if shape.contains(&0) {
2823        return Ok(());
2824    }
2825
2826    let mut offset = base_offset;
2827    if shape.is_empty() {
2828        let offset = usize::try_from(offset).map_err(|_| crate::Error::InvalidConfig {
2829            op,
2830            message: "view offset is negative".to_string(),
2831        })?;
2832        return f(offset);
2833    }
2834
2835    let mut index = vec![0usize; shape.len()];
2836    loop {
2837        let physical = usize::try_from(offset).map_err(|_| crate::Error::InvalidConfig {
2838            op,
2839            message: "view offset is negative".to_string(),
2840        })?;
2841        f(physical)?;
2842
2843        let mut advance_axis = None;
2844        for axis in 0..shape.len() {
2845            let next_index =
2846                index[axis]
2847                    .checked_add(1)
2848                    .ok_or_else(|| crate::Error::InvalidConfig {
2849                        op,
2850                        message: "logical index overflows".to_string(),
2851                    })?;
2852            if next_index < shape[axis] {
2853                advance_axis = Some((axis, next_index));
2854                break;
2855            }
2856        }
2857
2858        let Some((advance_axis, next_index)) = advance_axis else {
2859            return Ok(());
2860        };
2861
2862        for axis in 0..advance_axis {
2863            let steps = isize::try_from(index[axis]).map_err(|_| crate::Error::InvalidConfig {
2864                op,
2865                message: "logical index does not fit in isize".to_string(),
2866            })?;
2867            let rewind =
2868                strides[axis]
2869                    .checked_mul(steps)
2870                    .ok_or_else(|| crate::Error::InvalidConfig {
2871                        op,
2872                        message: "stride rewind overflows".to_string(),
2873                    })?;
2874            offset = offset
2875                .checked_sub(rewind)
2876                .ok_or_else(|| crate::Error::InvalidConfig {
2877                    op,
2878                    message: "view offset rewind overflows".to_string(),
2879                })?;
2880            index[axis] = 0;
2881        }
2882
2883        offset = offset.checked_add(strides[advance_axis]).ok_or_else(|| {
2884            crate::Error::InvalidConfig {
2885                op,
2886                message: "view offset overflows".to_string(),
2887            }
2888        })?;
2889        index[advance_axis] = next_index;
2890    }
2891}
2892
2893fn reachable_layout_span(
2894    shape: &[usize],
2895    strides: &[isize],
2896    offset: isize,
2897) -> crate::Result<Option<(usize, usize)>> {
2898    if shape.contains(&0) {
2899        return Ok(None);
2900    }
2901
2902    let mut min_offset = offset;
2903    let mut max_offset = offset;
2904    for (&extent, &stride) in shape.iter().zip(strides) {
2905        let steps =
2906            isize::try_from(extent.saturating_sub(1)).map_err(|_| crate::Error::InvalidConfig {
2907                op: "TypedTensorViewMut::try_multi_slice_mut",
2908                message: "shape extent does not fit in isize".to_string(),
2909            })?;
2910        let end = stride
2911            .checked_mul(steps)
2912            .ok_or_else(|| crate::Error::InvalidConfig {
2913                op: "TypedTensorViewMut::try_multi_slice_mut",
2914                message: "stride span overflows".to_string(),
2915            })?;
2916        let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
2917        min_offset =
2918            min_offset
2919                .checked_add(axis_min)
2920                .ok_or_else(|| crate::Error::InvalidConfig {
2921                    op: "TypedTensorViewMut::try_multi_slice_mut",
2922                    message: "minimum reachable offset overflows".to_string(),
2923                })?;
2924        max_offset =
2925            max_offset
2926                .checked_add(axis_max)
2927                .ok_or_else(|| crate::Error::InvalidConfig {
2928                    op: "TypedTensorViewMut::try_multi_slice_mut",
2929                    message: "maximum reachable offset overflows".to_string(),
2930                })?;
2931    }
2932
2933    let min_offset = usize::try_from(min_offset).map_err(|_| crate::Error::InvalidConfig {
2934        op: "TypedTensorViewMut::try_multi_slice_mut",
2935        message: "minimum reachable offset is negative".to_string(),
2936    })?;
2937    let max_offset = usize::try_from(max_offset).map_err(|_| crate::Error::InvalidConfig {
2938        op: "TypedTensorViewMut::try_multi_slice_mut",
2939        message: "maximum reachable offset is negative".to_string(),
2940    })?;
2941    Ok(Some((min_offset, max_offset)))
2942}
2943
2944fn split_two_mut_ranges<T>(
2945    data: &mut [T],
2946    first: (usize, usize),
2947    second: (usize, usize),
2948) -> Option<(&mut [T], &mut [T])> {
2949    if first.1 < second.0 {
2950        let (_, after_first_start) = data.split_at_mut(first.0);
2951        let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
2952        let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
2953        let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
2954        Some((first_slice, second_slice))
2955    } else if second.1 < first.0 {
2956        let (_, after_second_start) = data.split_at_mut(second.0);
2957        let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
2958        let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
2959        let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
2960        Some((first_slice, second_slice))
2961    } else {
2962        None
2963    }
2964}
2965
2966fn adjusted_view_offset(offset: isize, span_start: usize) -> Option<isize> {
2967    let span_start = isize::try_from(span_start).ok()?;
2968    offset.checked_sub(span_start)
2969}
2970
2971fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
2972    layout: &TensorLayout<R>,
2973    offset: isize,
2974    data: &'a mut [T],
2975    placement: Placement,
2976) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
2977    let shape = R::shape_from_vec(layout.shape().to_vec().into())
2978        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
2979    let strides = R::strides_from_vec(layout.strides().to_vec().into())
2980        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
2981    TypedTensorViewMut::from_buffer_ref_mut(
2982        shape,
2983        strides,
2984        offset,
2985        TensorBufferRefMut::Host(data),
2986        placement,
2987        "TypedTensorViewMut::try_multi_slice_mut",
2988    )
2989}
2990
2991fn contiguous_layout_slice<'a, T, R: TensorRank>(
2992    layout: &TensorLayout<R>,
2993    data: &'a [T],
2994    op: &'static str,
2995) -> crate::Result<&'a [T]> {
2996    if !layout.is_compact_col_major() {
2997        return Err(crate::Error::InvalidConfig {
2998            op,
2999            message: "view is not contiguous column-major".to_string(),
3000        });
3001    }
3002    let len = checked_view_element_count(layout.shape(), op)?;
3003    let start = usize::try_from(layout.offset()).map_err(|_| crate::Error::InvalidConfig {
3004        op,
3005        message: "view offset is negative".to_string(),
3006    })?;
3007    let end = start
3008        .checked_add(len)
3009        .ok_or_else(|| crate::Error::InvalidConfig {
3010            op,
3011            message: "contiguous view range overflows".to_string(),
3012        })?;
3013    data.get(start..end)
3014        .ok_or_else(|| crate::Error::InvalidConfig {
3015            op,
3016            message: "contiguous view range is outside host buffer".to_string(),
3017        })
3018}
3019
3020fn materialize_view_buffer_col_major<T: Clone>(
3021    shape: &[usize],
3022    strides: &[isize],
3023    offset: isize,
3024    buffer: &TensorBufferRef<'_, T>,
3025    op: &'static str,
3026) -> crate::Result<Vec<T>> {
3027    let source = match buffer {
3028        TensorBufferRef::Host(data) => *data,
3029        TensorBufferRef::Backend(_) => return Err(crate::Error::backend_failure(
3030            op,
3031            "backend buffers cannot be materialized through host memory; download explicitly first",
3032        )),
3033    };
3034
3035    let n_elements = checked_view_element_count(shape, op)?;
3036    let mut out = Vec::with_capacity(n_elements);
3037    for_each_layout_offset_col_major(shape, strides, offset, op, |physical| {
3038        let value = source
3039            .get(physical)
3040            .ok_or_else(|| crate::Error::InvalidConfig {
3041                op,
3042                message: "view offset is outside host buffer".to_string(),
3043            })?;
3044        out.push(value.clone());
3045        Ok(())
3046    })?;
3047    Ok(out)
3048}
3049
3050fn relaxed_col_major_contiguous(
3051    shape: &[usize],
3052    strides: &[isize],
3053    op: &'static str,
3054) -> crate::Result<bool> {
3055    let mut expected = 1isize;
3056    for (&extent, &stride) in shape.iter().zip(strides) {
3057        if extent <= 1 {
3058            continue;
3059        }
3060        if stride != expected {
3061            return Ok(false);
3062        }
3063        let extent = isize::try_from(extent).map_err(|_| crate::Error::InvalidConfig {
3064            op,
3065            message: "shape extent does not fit in isize".to_string(),
3066        })?;
3067        expected = expected
3068            .checked_mul(extent)
3069            .ok_or_else(|| crate::Error::InvalidConfig {
3070                op,
3071                message: "contiguous stride overflows".to_string(),
3072            })?;
3073    }
3074    Ok(true)
3075}
3076
3077fn reshape_layout_dyn<R: TensorRank>(
3078    layout: &TensorLayout<R>,
3079    shape: &[usize],
3080    buffer_len: usize,
3081    op: &'static str,
3082) -> crate::Result<TensorLayout<DynRank>> {
3083    match layout.reshape_view_as::<DynRank>(shape.to_vec().into(), buffer_len) {
3084        Ok(layout) => Ok(layout),
3085        Err(err) => {
3086            if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
3087                return Err(tensor_layout_error(op, err));
3088            }
3089            let from = checked_view_element_count(layout.shape(), op)?;
3090            let to = checked_view_element_count(shape, op)?;
3091            if from != to {
3092                return Err(tensor_layout_error(
3093                    op,
3094                    tenferro_tensor_core::Error::ReshapeElementCountMismatch { from, to },
3095                ));
3096            }
3097            TensorLayout::<DynRank>::compact(shape.to_vec().into())
3098                .and_then(|compact| {
3099                    TensorLayout::from_parts(
3100                        compact.shape().to_vec().into(),
3101                        compact.strides().to_vec().into(),
3102                        layout.offset(),
3103                        buffer_len,
3104                    )
3105                })
3106                .map_err(|err| tensor_layout_error(op, err))
3107        }
3108    }
3109}
3110
3111fn core_slice_specs(
3112    slices: &[StridedSliceSpec],
3113    shape: &[usize],
3114    op: &'static str,
3115) -> crate::Result<Vec<CoreSliceSpec>> {
3116    if slices.len() != shape.len() {
3117        return Err(crate::Error::RankMismatch {
3118            op,
3119            expected: shape.len(),
3120            actual: slices.len(),
3121        });
3122    }
3123
3124    let mut specs = Vec::with_capacity(slices.len());
3125    for (slice, &axis_len) in slices.iter().zip(shape) {
3126        specs.push(core_slice_spec(*slice, axis_len, op)?);
3127    }
3128    Ok(specs)
3129}
3130
3131fn core_slice_spec(
3132    slice: StridedSliceSpec,
3133    axis_len: usize,
3134    op: &'static str,
3135) -> crate::Result<CoreSliceSpec> {
3136    if slice.step() == 0 {
3137        return Err(crate::Error::InvalidConfig {
3138            op,
3139            message: "slice step must not be zero".to_string(),
3140        });
3141    }
3142
3143    let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
3144    let end = match slice.end() {
3145        Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
3146        None => isize::try_from(axis_len).map_err(|_| crate::Error::InvalidConfig {
3147            op,
3148            message: format!("axis length {axis_len} does not fit in isize"),
3149        })?,
3150    };
3151
3152    if slice.step() > 0 {
3153        return Ok(CoreSliceSpec {
3154            start,
3155            end,
3156            step: slice.step(),
3157        });
3158    }
3159
3160    if start >= end {
3161        return Ok(CoreSliceSpec {
3162            start,
3163            end: start,
3164            step: slice.step(),
3165        });
3166    }
3167
3168    Ok(CoreSliceSpec {
3169        start: end
3170            .checked_sub(1)
3171            .ok_or_else(|| crate::Error::InvalidConfig {
3172                op,
3173                message: "negative-step slice start overflows".to_string(),
3174            })?,
3175        end: start
3176            .checked_sub(1)
3177            .ok_or_else(|| crate::Error::InvalidConfig {
3178                op,
3179                message: "negative-step slice end overflows".to_string(),
3180            })?,
3181        step: slice.step(),
3182    })
3183}
3184
3185fn normalize_strided_bound(
3186    bound: isize,
3187    axis_len: usize,
3188    op: &'static str,
3189    role: &'static str,
3190) -> crate::Result<isize> {
3191    let axis_len = isize::try_from(axis_len).map_err(|_| crate::Error::InvalidConfig {
3192        op,
3193        message: format!("axis length {axis_len} does not fit in isize"),
3194    })?;
3195    let bound = if bound < 0 {
3196        axis_len
3197            .checked_add(bound)
3198            .ok_or_else(|| crate::Error::InvalidConfig {
3199                op,
3200                message: format!("{role} {bound} overflows"),
3201            })?
3202    } else {
3203        bound
3204    };
3205    if !(0..=axis_len).contains(&bound) {
3206        return Err(crate::Error::InvalidConfig {
3207            op,
3208            message: format!("{role} {bound} is outside 0..={axis_len}"),
3209        });
3210    }
3211    Ok(bound)
3212}
3213
3214fn slice_axis_specs(
3215    rank: usize,
3216    axis: usize,
3217    slice: StridedSliceSpec,
3218    op: &'static str,
3219) -> crate::Result<Vec<StridedSliceSpec>> {
3220    if axis >= rank {
3221        return Err(crate::Error::AxisOutOfBounds { op, axis, rank });
3222    }
3223
3224    let mut slices = vec![StridedSliceSpec::all(); rank];
3225    slices[axis] = slice;
3226    Ok(slices)
3227}
3228
3229pub(crate) fn materialize_typed_view_col_major<T: Clone + 'static, R: TensorRank>(
3230    view: &TypedTensorView<'_, T, R>,
3231    op: &'static str,
3232) -> crate::Result<TypedTensor<T>> {
3233    let data = materialize_view_buffer_col_major(
3234        view.shape(),
3235        view.strides(),
3236        view.offset(),
3237        &view.buffer,
3238        op,
3239    )?;
3240    TypedTensor::from_vec_col_major(view.shape().to_vec(), data)
3241}
3242
3243pub(crate) fn default_placement() -> Placement {
3244    Placement {
3245        memory_kind: MemoryKind::UnpinnedHost,
3246        device: None,
3247    }
3248}
3249
3250fn typed_tensor_from_vec_col_major<T, R: TensorRank>(
3251    shape: impl Into<R::Shape>,
3252    data: Vec<T>,
3253    op: &'static str,
3254) -> crate::Result<TypedTensor<T, R>> {
3255    try_typed_tensor_from_vec_col_major(shape, data, op)
3256}
3257
3258fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
3259    shape: impl Into<R::Shape>,
3260    data: Vec<T>,
3261    op: &'static str,
3262) -> crate::Result<TypedTensor<T, R>> {
3263    let layout = try_compact_layout(shape, op)?;
3264    try_checked_shape_len(layout.shape(), data.len(), op)?;
3265    Ok(TypedTensor {
3266        buffer: Buffer::Host(data),
3267        layout,
3268        placement: default_placement(),
3269    })
3270}
3271
3272fn typed_tensor_zeros<T: Clone + Zero, R: TensorRank>(
3273    shape: impl Into<R::Shape>,
3274) -> crate::Result<TypedTensor<T, R>> {
3275    try_typed_tensor_zeros(shape)
3276}
3277
3278fn try_typed_tensor_zeros<T: Clone + Zero, R: TensorRank>(
3279    shape: impl Into<R::Shape>,
3280) -> crate::Result<TypedTensor<T, R>> {
3281    let layout = try_compact_layout(shape, "zeros")?;
3282    let n = try_shape_product(layout.shape(), "zeros")?;
3283    Ok(TypedTensor {
3284        buffer: Buffer::Host(vec![T::zero(); n]),
3285        layout,
3286        placement: default_placement(),
3287    })
3288}
3289
3290fn typed_tensor_ones<T: Clone + One + Zero, R: TensorRank>(
3291    shape: impl Into<R::Shape>,
3292) -> crate::Result<TypedTensor<T, R>> {
3293    try_typed_tensor_ones(shape)
3294}
3295
3296fn try_typed_tensor_ones<T: Clone + One + Zero, R: TensorRank>(
3297    shape: impl Into<R::Shape>,
3298) -> crate::Result<TypedTensor<T, R>> {
3299    let layout = try_compact_layout(shape, "ones")?;
3300    let n = try_shape_product(layout.shape(), "ones")?;
3301    Ok(TypedTensor {
3302        buffer: Buffer::Host(vec![T::one(); n]),
3303        layout,
3304        placement: default_placement(),
3305    })
3306}
3307
3308fn typed_tensor_from_buffer_col_major<T: 'static, R: TensorRank>(
3309    shape: impl Into<R::Shape>,
3310    buffer: Buffer<T>,
3311    placement: Placement,
3312) -> crate::Result<TypedTensor<T, R>> {
3313    try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
3314}
3315
3316fn try_typed_tensor_from_buffer_col_major<T: 'static, R: TensorRank>(
3317    shape: impl Into<R::Shape>,
3318    buffer: Buffer<T>,
3319    placement: Placement,
3320) -> crate::Result<TypedTensor<T, R>> {
3321    let layout = try_compact_layout(shape, "from_buffer_col_major")?;
3322    let len = buffer.len();
3323    try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
3324    Ok(TypedTensor {
3325        buffer,
3326        layout,
3327        placement,
3328    })
3329}
3330
3331impl<T: Clone + Zero, R: TensorRank> TypedTensor<T, R> {
3332    /// Allocate a zero-filled tensor.
3333    ///
3334    /// # Examples
3335    ///
3336    /// ```rust
3337    /// use tenferro_tensor::TypedTensor;
3338    ///
3339    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
3340    /// assert_eq!(t.n_elements(), 6);
3341    /// ```
3342    pub fn zeros(shape: impl Into<R::Shape>) -> crate::Result<Self> {
3343        typed_tensor_zeros(shape)
3344    }
3345}
3346
3347impl<T: Clone + One + Zero, R: TensorRank> TypedTensor<T, R> {
3348    /// Allocate a one-filled tensor.
3349    ///
3350    /// # Examples
3351    ///
3352    /// ```rust
3353    /// use tenferro_tensor::TypedTensor;
3354    ///
3355    /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
3356    /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
3357    /// ```
3358    pub fn ones(shape: impl Into<R::Shape>) -> crate::Result<Self> {
3359        typed_tensor_ones(shape)
3360    }
3361}
3362
3363impl<T, R: TensorRank> TypedTensor<T, R> {
3364    /// Create a tensor from an existing buffer and compact column-major layout.
3365    ///
3366    /// This preserves the owned tensor invariant that layout metadata is
3367    /// compact column-major, including for backend-owned buffers.
3368    ///
3369    /// # Examples
3370    ///
3371    /// ```
3372    /// use tenferro_tensor::{Buffer, Placement, TypedTensor};
3373    ///
3374    /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
3375    ///     vec![2],
3376    ///     Buffer::Host(vec![1.0, 2.0]),
3377    ///     Placement {
3378    ///         memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
3379    ///         device: None,
3380    ///     },
3381    /// )
3382    /// .unwrap();
3383    /// assert_eq!(tensor.shape(), &[2]);
3384    /// ```
3385    pub fn from_buffer_col_major(
3386        shape: impl Into<R::Shape>,
3387        buffer: Buffer<T>,
3388        placement: Placement,
3389    ) -> crate::Result<Self>
3390    where
3391        T: 'static,
3392    {
3393        typed_tensor_from_buffer_col_major(shape, buffer, placement)
3394    }
3395
3396    /// Convert this tensor into static rank metadata after validating its rank.
3397    ///
3398    /// The buffer and placement are preserved. This method changes only the
3399    /// compile-time rank marker on the owned compact column-major tensor.
3400    ///
3401    /// # Examples
3402    ///
3403    /// ```rust
3404    /// use tenferro_tensor::{Rank, TypedTensor};
3405    ///
3406    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
3407    /// let ranked: TypedTensor<f64, Rank<2>> = tensor.try_into_rank::<2>()?;
3408    /// assert_eq!(ranked.shape(), &[2, 3]);
3409    /// # Ok::<(), tenferro_tensor::Error>(())
3410    /// ```
3411    pub fn try_into_rank<const N: usize>(self) -> crate::Result<TypedTensor<T, Rank<N>>> {
3412        let op = "TypedTensor::try_into_rank";
3413        let shape = <Rank<N> as TensorRank>::shape_from_vec(self.shape().to_vec().into())
3414            .map_err(|err| tensor_layout_error(op, err))?;
3415        let layout =
3416            TensorLayout::<Rank<N>>::compact(shape).map_err(|err| tensor_layout_error(op, err))?;
3417        Ok(TypedTensor {
3418            buffer: self.buffer,
3419            layout,
3420            placement: self.placement,
3421        })
3422    }
3423
3424    /// Number of elements in the tensor.
3425    ///
3426    /// # Examples
3427    ///
3428    /// ```rust
3429    /// use tenferro_tensor::TypedTensor;
3430    ///
3431    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
3432    /// assert_eq!(t.n_elements(), 6);
3433    /// ```
3434    pub fn n_elements(&self) -> usize {
3435        // Invariant: owned tensor constructors validate compact shape length against buffer length.
3436        try_shape_product(self.shape(), "TypedTensor::n_elements")
3437            .expect("TypedTensor compact shape was validated at construction")
3438    }
3439
3440    /// Tensor shape.
3441    ///
3442    /// # Examples
3443    ///
3444    /// ```
3445    /// use tenferro_tensor::TypedTensor;
3446    ///
3447    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3448    /// assert_eq!(t.shape(), &[2]);
3449    /// ```
3450    pub fn shape(&self) -> &[usize] {
3451        self.layout.shape()
3452    }
3453
3454    /// Tensor rank.
3455    ///
3456    /// # Examples
3457    ///
3458    /// ```
3459    /// use tenferro_tensor::TypedTensor;
3460    ///
3461    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
3462    /// assert_eq!(t.rank(), 2);
3463    /// ```
3464    pub fn rank(&self) -> usize {
3465        self.shape().len()
3466    }
3467
3468    /// Tensor layout metadata.
3469    ///
3470    /// Owned typed tensors are always compact column-major layouts.
3471    ///
3472    /// # Examples
3473    ///
3474    /// ```
3475    /// use tenferro_tensor::TypedTensor;
3476    ///
3477    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
3478    /// assert_eq!(t.layout().strides(), &[1, 2]);
3479    /// ```
3480    pub fn layout(&self) -> &TensorLayout<R> {
3481        &self.layout
3482    }
3483
3484    /// Return the storage backing this tensor.
3485    ///
3486    /// This is an explicit storage-inspection API for backend glue and tests.
3487    /// Host value inspection should prefer [`TypedTensor::host_data`] when the
3488    /// caller requires host storage.
3489    ///
3490    /// # Examples
3491    ///
3492    /// ```
3493    /// use tenferro_tensor::{Buffer, TypedTensor};
3494    ///
3495    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3496    /// assert!(matches!(t.buffer(), Buffer::Host(_)));
3497    /// ```
3498    pub fn buffer(&self) -> &Buffer<T> {
3499        &self.buffer
3500    }
3501
3502    /// Return placement metadata for this tensor.
3503    ///
3504    /// # Examples
3505    ///
3506    /// ```
3507    /// use tenferro_tensor::{MemoryKind, TypedTensor};
3508    ///
3509    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
3510    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
3511    /// ```
3512    pub fn placement(&self) -> &Placement {
3513        &self.placement
3514    }
3515
3516    /// Replace placement metadata without changing the storage buffer.
3517    ///
3518    /// # Examples
3519    ///
3520    /// ```
3521    /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
3522    ///
3523    /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
3524    /// t.set_placement(Placement {
3525    ///     memory_kind: MemoryKind::PinnedHost,
3526    ///     device: None,
3527    /// });
3528    /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
3529    /// ```
3530    pub fn set_placement(&mut self, placement: Placement) {
3531        self.placement = placement;
3532    }
3533
3534    /// Borrow this tensor as a typed view preserving rank and layout metadata.
3535    ///
3536    /// # Examples
3537    ///
3538    /// ```rust
3539    /// use tenferro_tensor::{Rank, TypedTensor};
3540    ///
3541    /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
3542    /// let view = tensor.as_view();
3543    /// assert_eq!(view.strides(), &[1, 2]);
3544    /// ```
3545    pub fn as_view(&self) -> TypedTensorView<'_, T, R>
3546    where
3547        T: 'static,
3548    {
3549        let buffer = match &self.buffer {
3550            Buffer::Host(data) => TensorBufferRef::Host(data),
3551            Buffer::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
3552        };
3553        TypedTensorView {
3554            buffer,
3555            layout: self.layout.clone(),
3556            placement: self.placement.clone(),
3557        }
3558    }
3559
3560    /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
3561    ///
3562    /// # Examples
3563    ///
3564    /// ```rust
3565    /// use tenferro_tensor::TypedTensor;
3566    ///
3567    /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
3568    /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
3569    /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
3570    /// ```
3571    pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
3572    where
3573        T: 'static,
3574    {
3575        let layout = self.layout.clone();
3576        let placement = self.placement.clone();
3577        let buffer = match &mut self.buffer {
3578            Buffer::Host(data) => TensorBufferRefMut::Host(data),
3579            Buffer::Backend(buffer) => TensorBufferRefMut::Backend(Arc::clone(buffer)),
3580        };
3581        TypedTensorViewMut {
3582            buffer,
3583            layout,
3584            placement,
3585        }
3586    }
3587
3588    /// Consume this tensor and return its layout metadata.
3589    ///
3590    /// # Examples
3591    ///
3592    /// ```
3593    /// use tenferro_tensor::TypedTensor;
3594    ///
3595    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3596    /// assert!(t.into_layout().is_compact_col_major());
3597    /// ```
3598    pub fn into_layout(self) -> TensorLayout<R> {
3599        self.layout
3600    }
3601
3602    /// Consume this tensor and return its storage, layout, and placement.
3603    ///
3604    /// # Examples
3605    ///
3606    /// ```
3607    /// use tenferro_tensor::{Buffer, TypedTensor};
3608    ///
3609    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3610    /// let (buffer, layout, placement) = t.into_parts();
3611    /// assert!(matches!(buffer, Buffer::Host(_)));
3612    /// assert_eq!(layout.shape(), &[2]);
3613    /// assert!(placement.device.is_none());
3614    /// ```
3615    pub fn into_parts(self) -> (Buffer<T>, TensorLayout<R>, Placement) {
3616        (self.buffer, self.layout, self.placement)
3617    }
3618}
3619
3620impl<T: Clone, R: TensorRank> TypedTensor<T, R> {
3621    /// Create a tensor from a column-major buffer.
3622    ///
3623    /// # Examples
3624    ///
3625    /// ```
3626    /// use tenferro_tensor::TypedTensor;
3627    ///
3628    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
3629    /// assert_eq!(t.get(&[1, 0])?, &2.0);
3630    /// # Ok::<(), tenferro_tensor::Error>(())
3631    /// ```
3632    pub fn from_vec_col_major(shape: impl Into<R::Shape>, data: Vec<T>) -> crate::Result<Self> {
3633        typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
3634    }
3635
3636    /// Consume this tensor and return its owned column-major host buffer.
3637    ///
3638    /// # Examples
3639    ///
3640    /// ```
3641    /// use tenferro_tensor::TypedTensor;
3642    ///
3643    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3644    /// let (shape, data) = t.into_vec_col_major().unwrap();
3645    /// assert_eq!(shape, vec![2]);
3646    /// assert_eq!(data, vec![1.0, 2.0]);
3647    /// ```
3648    pub fn into_vec_col_major(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
3649        let shape = self.shape().to_vec();
3650        match self.buffer {
3651            Buffer::Host(data) => Ok((shape, data)),
3652            Buffer::Backend(_) => Err(crate::Error::backend_failure(
3653                "into_vec_col_major",
3654                "backend buffers cannot be exported as host Vec",
3655            )),
3656        }
3657    }
3658
3659    /// Borrow the host buffer.
3660    ///
3661    /// # Examples
3662    ///
3663    /// ```rust
3664    /// use tenferro_tensor::TypedTensor;
3665    ///
3666    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3667    /// assert_eq!(t.host_data()?, &[1.0, 2.0]);
3668    /// # Ok::<(), tenferro_tensor::Error>(())
3669    /// ```
3670    pub fn host_data(&self) -> crate::Result<&[T]> {
3671        match &self.buffer {
3672            Buffer::Host(v) => Ok(v),
3673            Buffer::Backend(_) => Err(crate::Error::backend_failure(
3674                "TypedTensor::host_data",
3675                "backend buffers cannot be inspected as host slices; download explicitly first",
3676            )),
3677        }
3678    }
3679
3680    /// View the tensor data as a flat slice.
3681    ///
3682    /// This is an alias for `host_data()` for API consistency with
3683    /// `Tensor::as_slice`.
3684    ///
3685    /// # Examples
3686    ///
3687    /// ```
3688    /// use tenferro_tensor::TypedTensor;
3689    ///
3690    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3691    /// assert_eq!(t.as_slice()?, &[1.0, 2.0]);
3692    /// # Ok::<(), tenferro_tensor::Error>(())
3693    /// ```
3694    pub fn as_slice(&self) -> crate::Result<&[T]> {
3695        self.host_data()
3696    }
3697
3698    /// Mutably borrow the host buffer.
3699    ///
3700    /// # Examples
3701    ///
3702    /// ```rust
3703    /// use tenferro_tensor::TypedTensor;
3704    ///
3705    /// let mut t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
3706    /// t.host_data_mut()?[0] = 3.0;
3707    /// assert_eq!(t.host_data()?, &[3.0, 0.0]);
3708    /// # Ok::<(), tenferro_tensor::Error>(())
3709    /// ```
3710    pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
3711        match &mut self.buffer {
3712            Buffer::Host(v) => Ok(v),
3713            Buffer::Backend(_) => Err(crate::Error::backend_failure(
3714                "TypedTensor::host_data_mut",
3715                "backend buffers cannot be mutated as host slices; download explicitly first",
3716            )),
3717        }
3718    }
3719
3720    /// Compute the linear physical-buffer offset for a logical index.
3721    ///
3722    /// # Examples
3723    ///
3724    /// ```rust
3725    /// use tenferro_tensor::TypedTensor;
3726    ///
3727    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
3728    /// assert_eq!(t.linear_offset(&[1, 2])?, 5);
3729    /// # Ok::<(), tenferro_tensor::Error>(())
3730    /// ```
3731    pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
3732        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
3733    }
3734
3735    /// Borrow a single element by multi-index.
3736    ///
3737    /// # Examples
3738    ///
3739    /// ```rust
3740    /// use tenferro_tensor::TypedTensor;
3741    ///
3742    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3743    /// assert_eq!(t.get(&[1])?, &2.0);
3744    /// # Ok::<(), tenferro_tensor::Error>(())
3745    /// ```
3746    pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
3747        let off = self.linear_offset(indices)?;
3748        self.host_data()?
3749            .get(off)
3750            .ok_or_else(|| crate::Error::InvalidConfig {
3751                op: "TypedTensor::get",
3752                message: format!("linear offset {off} is outside host buffer"),
3753            })
3754    }
3755
3756    /// Mutably borrow a single element by multi-index.
3757    ///
3758    /// # Examples
3759    ///
3760    /// ```rust
3761    /// use tenferro_tensor::TypedTensor;
3762    ///
3763    /// let mut t = TypedTensor::<f64>::zeros(vec![1]).unwrap();
3764    /// *t.get_mut(&[0])? = 7.0;
3765    /// assert_eq!(t.host_data()?, &[7.0]);
3766    /// # Ok::<(), tenferro_tensor::Error>(())
3767    /// ```
3768    pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
3769        let off = self.linear_offset(indices)?;
3770        self.host_data_mut()?
3771            .get_mut(off)
3772            .ok_or_else(|| crate::Error::InvalidConfig {
3773                op: "TypedTensor::get_mut",
3774                message: format!("linear offset {off} is outside host buffer"),
3775            })
3776    }
3777}
3778
3779impl Tensor {
3780    /// Create a tensor from a shape and column-major flat data.
3781    ///
3782    /// This is the `Tensor`-level equivalent of
3783    /// `TypedTensor::<T>::from_vec_col_major`.
3784    ///
3785    /// # Examples
3786    ///
3787    /// ```
3788    /// use tenferro_tensor::Tensor;
3789    ///
3790    /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
3791    /// assert_eq!(t.shape(), &[2, 2]);
3792    /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
3793    /// ```
3794    pub fn from_vec_col_major<T: TensorScalar>(
3795        shape: Vec<usize>,
3796        data: Vec<T>,
3797    ) -> crate::Result<Self> {
3798        T::into_tensor(shape, data)
3799    }
3800
3801    /// Tensor shape.
3802    ///
3803    /// # Examples
3804    ///
3805    /// ```rust
3806    /// use tenferro_tensor::{Tensor, TypedTensor};
3807    ///
3808    /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
3809    /// assert_eq!(t.shape(), &[2]);
3810    /// ```
3811    pub fn shape(&self) -> &[usize] {
3812        match self {
3813            Tensor::F32(t) => t.shape(),
3814            Tensor::F64(t) => t.shape(),
3815            Tensor::I32(t) => t.shape(),
3816            Tensor::I64(t) => t.shape(),
3817            Tensor::Bool(t) => t.shape(),
3818            Tensor::C32(t) => t.shape(),
3819            Tensor::C64(t) => t.shape(),
3820        }
3821    }
3822
3823    /// Tensor dtype tag.
3824    ///
3825    /// # Examples
3826    ///
3827    /// ```rust
3828    /// use tenferro_tensor::{DType, Tensor, TypedTensor};
3829    ///
3830    /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
3831    /// assert_eq!(t.dtype(), DType::F64);
3832    /// ```
3833    pub fn dtype(&self) -> DType {
3834        match self {
3835            Tensor::F32(_) => DType::F32,
3836            Tensor::F64(_) => DType::F64,
3837            Tensor::I32(_) => DType::I32,
3838            Tensor::I64(_) => DType::I64,
3839            Tensor::Bool(_) => DType::Bool,
3840            Tensor::C32(_) => DType::C32,
3841            Tensor::C64(_) => DType::C64,
3842        }
3843    }
3844
3845    /// Return placement metadata for this dtype-erased tensor.
3846    ///
3847    /// # Examples
3848    ///
3849    /// ```rust
3850    /// use tenferro_tensor::{MemoryKind, Tensor};
3851    ///
3852    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
3853    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
3854    /// ```
3855    pub fn placement(&self) -> &Placement {
3856        match self {
3857            Tensor::F32(t) => t.placement(),
3858            Tensor::F64(t) => t.placement(),
3859            Tensor::I32(t) => t.placement(),
3860            Tensor::I64(t) => t.placement(),
3861            Tensor::Bool(t) => t.placement(),
3862            Tensor::C32(t) => t.placement(),
3863            Tensor::C64(t) => t.placement(),
3864        }
3865    }
3866
3867    /// Return whether this tensor is backed by backend-native storage.
3868    ///
3869    /// # Examples
3870    ///
3871    /// ```rust
3872    /// use tenferro_tensor::Tensor;
3873    ///
3874    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
3875    /// assert!(!t.is_backend_buffer());
3876    /// ```
3877    pub fn is_backend_buffer(&self) -> bool {
3878        match self {
3879            Tensor::F32(t) => t.buffer().is_backend(),
3880            Tensor::F64(t) => t.buffer().is_backend(),
3881            Tensor::I32(t) => t.buffer().is_backend(),
3882            Tensor::I64(t) => t.buffer().is_backend(),
3883            Tensor::Bool(t) => t.buffer().is_backend(),
3884            Tensor::C32(t) => t.buffer().is_backend(),
3885            Tensor::C64(t) => t.buffer().is_backend(),
3886        }
3887    }
3888
3889    /// Try to borrow the host data as a typed slice.
3890    ///
3891    /// Returns an error if the tensor dtype does not match `T`.
3892    ///
3893    /// # Examples
3894    ///
3895    /// ```
3896    /// use tenferro_tensor::{Tensor, TypedTensor};
3897    ///
3898    /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
3899    /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
3900    /// assert!(t.as_slice::<f32>().is_err());
3901    /// ```
3902    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
3903        T::as_slice(self)
3904    }
3905
3906    /// Consume this tensor and return its owned column-major buffer when the
3907    /// dtype matches.
3908    ///
3909    /// # Examples
3910    ///
3911    /// ```
3912    /// use tenferro_tensor::Tensor;
3913    ///
3914    /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
3915    /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
3916    /// ```
3917    pub fn into_vec_col_major<T: TensorScalar>(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
3918        let typed = T::into_typed(self)?;
3919        typed.into_vec_col_major()
3920    }
3921}
3922
3923// Kept for crate-local layout tests while tensor indexing helpers remain split
3924// across tensor and CPU crates.
3925#[allow(dead_code)]
3926pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
3927    for i in 0..shape.len() {
3928        out[i] = flat % shape[i];
3929        flat /= shape[i];
3930    }
3931}