Skip to main content

tenferro_tensor/
types.rs

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