Skip to main content

tenferro_tensor/
types.rs

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