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::marker::PhantomData;
6use std::mem::{align_of, needs_drop, offset_of, size_of};
7use std::num::NonZeroUsize;
8use std::ops::Deref;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11use crate::config::SliceConfig;
12use crate::error::ReinterpretError;
13pub use tenferro_tensor_core::{DType, DynRank, Rank, TensorLayout, TensorRank};
14use tenferro_tensor_core::{ShapeVec, StrideVec};
15use tenferro_tensor_core::{SliceSpec as CoreSliceSpec, ValidationError};
16
17use crate::storage::{
18    AllocationGroup, AllocationSlot, BackendAllocation, DescriptorSlot, GroupError, GroupReadView,
19    GroupWriteView,
20};
21
22mod accessors;
23mod col_major;
24mod shape_packing;
25mod strided_view;
26#[cfg(test)]
27mod tests;
28
29pub use col_major::{ColMajorView, ColMajorViewMut};
30pub use strided_view::StridedSliceSpec;
31
32fn shape_vec(shape: &[usize]) -> ShapeVec {
33    shape.iter().copied().collect()
34}
35
36fn stride_vec(strides: &[isize]) -> StrideVec {
37    strides.iter().copied().collect()
38}
39
40fn representation_pair_error(
41    op: &'static str,
42    from: DType,
43    to: DType,
44    message: impl Into<String>,
45) -> crate::Error {
46    crate::Error::unsupported_dtype_conversion(op, from, to, message)
47}
48
49fn validate_representation_pair(op: &'static str, from: DType, to: DType) -> crate::Result<()> {
50    let valid = match (from, to) {
51        (DType::C32, DType::F32) | (DType::F32, DType::C32) => {
52            size_of::<Complex32>() == 2 * size_of::<f32>()
53                && align_of::<Complex32>() == align_of::<f32>()
54                && offset_of!(Complex32, re) == 0
55                && offset_of!(Complex32, im) == size_of::<f32>()
56                && !needs_drop::<Complex32>()
57                && !needs_drop::<f32>()
58        }
59        (DType::C64, DType::F64) | (DType::F64, DType::C64) => {
60            size_of::<Complex64>() == 2 * size_of::<f64>()
61                && align_of::<Complex64>() == align_of::<f64>()
62                && offset_of!(Complex64, re) == 0
63                && offset_of!(Complex64, im) == size_of::<f64>()
64                && !needs_drop::<Complex64>()
65                && !needs_drop::<f64>()
66        }
67        _ => false,
68    };
69    if valid {
70        Ok(())
71    } else {
72        Err(representation_pair_error(
73            op,
74            from,
75            to,
76            "only the sealed Complex<f32><->f32 and Complex<f64><->f64 representations are supported",
77        ))
78    }
79}
80
81fn reinterpret_complex_to_real_layout(
82    shape: &[usize],
83    strides: &[isize],
84    offset: isize,
85    complex_buffer_len: usize,
86    op: &'static str,
87) -> crate::Result<TensorLayout<DynRank>> {
88    let real_buffer_len = complex_buffer_len
89        .checked_mul(2)
90        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
91    let rank = shape
92        .len()
93        .checked_add(1)
94        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
95    let mut real_shape = ShapeVec::with_capacity(rank);
96    real_shape.push(2);
97    real_shape.extend_from_slice(shape);
98    let mut real_strides = StrideVec::with_capacity(rank);
99    real_strides.push(1);
100    for &stride in strides {
101        real_strides.push(
102            stride
103                .checked_mul(2)
104                .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
105        );
106    }
107    let real_offset = offset
108        .checked_mul(2)
109        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
110    TensorLayout::from_parts(real_shape, real_strides, real_offset, real_buffer_len)
111        .map_err(|err| tensor_layout_error(op, err))
112}
113
114fn reinterpret_real_to_complex_layout(
115    shape: &[usize],
116    strides: &[isize],
117    offset: isize,
118    real_buffer_len: usize,
119    op: &'static str,
120) -> crate::Result<TensorLayout<DynRank>> {
121    if shape.first().copied() != Some(2) {
122        return Err(crate::Error::invalid_argument(
123            op,
124            "shape",
125            "the leading extent must be 2 for a complex reinterpretation",
126        ));
127    }
128    if strides.first().copied() != Some(1) {
129        return Err(crate::Error::invalid_argument(
130            op,
131            "strides",
132            "the leading stride must be 1 for a complex reinterpretation",
133        ));
134    }
135    if offset % 2 != 0 {
136        return Err(crate::Error::invalid_argument(
137            op,
138            "offset",
139            "the offset must be divisible by 2 for a complex reinterpretation",
140        ));
141    }
142    let mut complex_strides = StrideVec::with_capacity(strides.len() - 1);
143    for &stride in &strides[1..] {
144        if stride % 2 != 0 {
145            return Err(crate::Error::invalid_argument(
146                op,
147                "strides",
148                "all non-leading strides must be divisible by 2",
149            ));
150        }
151        complex_strides.push(stride / 2);
152    }
153    let complex_buffer_len = real_buffer_len / 2;
154    TensorLayout::from_parts(
155        shape[1..].iter().copied().collect(),
156        complex_strides,
157        offset / 2,
158        complex_buffer_len,
159    )
160    .map_err(|err| tensor_layout_error(op, err))
161}
162
163fn reinterpret_host_slice<'a, T: TensorScalar, U: TensorScalar>(
164    data: &'a [T],
165    op: &'static str,
166) -> crate::Result<&'a [U]> {
167    let byte_len = data
168        .len()
169        .checked_mul(size_of::<T>())
170        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
171    if !byte_len.is_multiple_of(size_of::<U>()) {
172        return Err(crate::Error::validation(
173            op,
174            ValidationError::ViewOutOfBounds,
175        ));
176    }
177    if data.as_ptr().align_offset(align_of::<U>()) != 0 {
178        return Err(crate::Error::invalid_argument(
179            op,
180            "alignment",
181            "the source allocation is not aligned for the target representation",
182        ));
183    }
184    // SAFETY: `validate_representation_pair` seals the only supported pairs;
185    // sizes, alignment, field order, and drop properties are checked before
186    // exposing the borrowed target slice.
187    Ok(unsafe { std::slice::from_raw_parts(data.as_ptr().cast::<U>(), byte_len / size_of::<U>()) })
188}
189
190fn reinterpret_host_slice_mut<'a, T: TensorScalar, U: TensorScalar>(
191    data: &'a mut [T],
192    op: &'static str,
193) -> crate::Result<&'a mut [U]> {
194    let byte_len = data
195        .len()
196        .checked_mul(size_of::<T>())
197        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
198    if !byte_len.is_multiple_of(size_of::<U>()) {
199        return Err(crate::Error::validation(
200            op,
201            ValidationError::ViewOutOfBounds,
202        ));
203    }
204    if data.as_mut_ptr().align_offset(align_of::<U>()) != 0 {
205        return Err(crate::Error::invalid_argument(
206            op,
207            "alignment",
208            "the source allocation is not aligned for the target representation",
209        ));
210    }
211    // SAFETY: the mutable source borrow is unique and the sealed pair has no
212    // padding or drop glue, so the target slice covers the same bytes exactly.
213    Ok(unsafe {
214        std::slice::from_raw_parts_mut(data.as_mut_ptr().cast::<U>(), byte_len / size_of::<U>())
215    })
216}
217
218/// Memory location for tensor storage.
219///
220/// # Examples
221///
222/// ```rust
223/// use tenferro_tensor::MemoryKind;
224///
225/// let kind = MemoryKind::UnpinnedHost;
226/// ```
227#[derive(Clone, Debug, PartialEq, Eq, Hash)]
228pub enum MemoryKind {
229    Device,
230    PinnedHost,
231    UnpinnedHost,
232    Managed,
233    Other(String),
234}
235
236/// Compute device family.
237///
238/// # Examples
239///
240/// ```rust
241/// use tenferro_tensor::DeviceKind;
242///
243/// let kind = DeviceKind::Cpu;
244/// ```
245#[derive(Clone, Debug, PartialEq, Eq, Hash)]
246pub enum DeviceKind {
247    Cpu,
248    Gpu(GpuBackendKind),
249    Other(String),
250}
251
252/// GPU backend family used by placement metadata.
253///
254/// # Examples
255///
256/// ```rust
257/// use tenferro_tensor::GpuBackendKind;
258///
259/// let kind = GpuBackendKind::Cuda;
260/// let webgpu = GpuBackendKind::WebGpu;
261/// assert_ne!(kind, webgpu);
262/// ```
263#[derive(Clone, Debug, PartialEq, Eq, Hash)]
264pub enum GpuBackendKind {
265    Cuda,
266    WebGpu,
267    Rocm,
268    Other(String),
269}
270
271/// Concrete compute device identifier.
272///
273/// # Examples
274///
275/// ```rust
276/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind};
277///
278/// let device = DeviceId {
279///     kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
280///     ordinal: 0,
281/// };
282/// ```
283#[derive(Clone, Debug, PartialEq, Eq, Hash)]
284pub struct DeviceId {
285    pub kind: DeviceKind,
286    pub ordinal: usize,
287}
288
289/// Caller-stable identity for a CPU execution domain.
290///
291/// Domain IDs are metadata supplied by the caller or execution coordinator;
292/// creating an ID does not allocate a process-global identity.
293///
294/// # Examples
295///
296/// ```rust
297/// use tenferro_tensor::CpuDomainId;
298///
299/// let domain = CpuDomainId::new(17);
300/// assert_eq!(domain.as_u64(), 17);
301/// ```
302#[repr(transparent)]
303#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
304pub struct CpuDomainId(u64);
305
306impl CpuDomainId {
307    /// Create a caller-stable CPU domain identity.
308    ///
309    /// # Examples
310    ///
311    /// ```rust
312    /// use tenferro_tensor::CpuDomainId;
313    ///
314    /// assert_eq!(CpuDomainId::new(3), CpuDomainId::new(3));
315    /// ```
316    pub const fn new(id: u64) -> Self {
317        Self(id)
318    }
319
320    /// Return the caller-supplied integer identity.
321    ///
322    /// # Examples
323    ///
324    /// ```rust
325    /// use tenferro_tensor::CpuDomainId;
326    ///
327    /// assert_eq!(CpuDomainId::new(9).as_u64(), 9);
328    /// ```
329    pub const fn as_u64(self) -> u64 {
330        self.0
331    }
332}
333
334/// Placement metadata for a tensor buffer.
335///
336/// # Examples
337///
338/// ```rust
339/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind, MemoryKind, Placement};
340///
341/// let placement = Placement {
342///     memory_kind: MemoryKind::Device,
343///     device: Some(DeviceId {
344///         kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
345///         ordinal: 0,
346///     }),
347///     cpu_affinity: None,
348/// };
349/// assert!(placement.cpu_affinity.is_none());
350/// ```
351#[derive(Clone, Debug, PartialEq, Eq, Hash)]
352pub struct Placement {
353    /// Storage memory class, independent of execution routing metadata.
354    pub memory_kind: MemoryKind,
355    /// Device that owns or addresses the storage, when applicable.
356    pub device: Option<DeviceId>,
357    /// Preferred or producing CPU execution domain for routing and locality.
358    ///
359    /// This tag is not proof of allocation ownership, page residency, NUMA
360    /// pinning, or worker-affinity enforcement. Backend allocation-domain
361    /// metadata remains attached to the buffer independently.
362    pub cpu_affinity: Option<CpuDomainId>,
363}
364
365impl Default for Placement {
366    fn default() -> Self {
367        default_placement()
368    }
369}
370
371/// Backend-owned buffer handle.
372///
373/// `BackendStorageHandle::new` creates an empty opaque handle. Use
374/// [`BackendStorageHandle::new_with_len`] when test or adapter code needs to model a
375/// non-empty backend allocation.
376///
377/// # Examples
378///
379/// ```rust
380/// use tenferro_tensor::BackendStorageHandle;
381///
382/// let handle = BackendStorageHandle::<f64>::new(7);
383/// ```
384pub struct BackendStorageHandle<T> {
385    id: u64,
386    len: usize,
387    allocation_domain: AllocationDomainId,
388    _phantom: std::marker::PhantomData<T>,
389}
390
391/// Identity of a backend-owned allocation domain.
392///
393/// Domains let cooperating backends accept shared allocations without treating
394/// another context's physically similar buffer as compatible.
395///
396/// # Examples
397///
398/// ```rust
399/// use tenferro_tensor::AllocationDomainId;
400///
401/// assert_ne!(AllocationDomainId::fresh(), AllocationDomainId::fresh());
402/// ```
403#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
404pub struct AllocationDomainId(u64);
405
406impl AllocationDomainId {
407    /// Create a process-unique allocation-domain identity.
408    ///
409    /// # Examples
410    ///
411    /// ```rust
412    /// use tenferro_tensor::AllocationDomainId;
413    ///
414    /// let domain = AllocationDomainId::fresh();
415    /// assert_eq!(domain, domain);
416    /// ```
417    pub fn fresh() -> Self {
418        static NEXT_DOMAIN_ID: AtomicU64 = AtomicU64::new(1);
419        Self(NEXT_DOMAIN_ID.fetch_add(1, Ordering::Relaxed))
420    }
421}
422
423/// Stable physical identity of one backend allocation.
424///
425/// # Examples
426///
427/// ```rust
428/// use tenferro_tensor::AllocationId;
429///
430/// assert_eq!(AllocationId::from_backend_id(7), AllocationId::from_backend_id(7));
431/// ```
432#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
433pub struct AllocationId(u64);
434
435impl AllocationId {
436    /// Wrap an allocation identity supplied by the owning backend.
437    ///
438    /// # Examples
439    ///
440    /// ```rust
441    /// use tenferro_tensor::AllocationId;
442    ///
443    /// let id = AllocationId::from_backend_id(3);
444    /// assert_eq!(id, AllocationId::from_backend_id(3));
445    /// ```
446    pub const fn from_backend_id(id: u64) -> Self {
447        Self(id)
448    }
449}
450
451/// Typed failure returned by guarded backend host access.
452///
453/// # Examples
454///
455/// ```rust
456/// use tenferro_tensor::HostAccessError;
457///
458/// let error = HostAccessError::Unsupported { backend: "opaque" };
459/// assert!(error.to_string().contains("opaque"));
460/// ```
461#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
462#[non_exhaustive]
463pub enum HostAccessError {
464    /// The backend does not expose guarded host access for this allocation.
465    #[error("backend `{backend}` does not support guarded host access")]
466    Unsupported { backend: &'static str },
467    /// The allocation belongs to another shared-allocation domain.
468    #[error("allocation belongs to domain {actual:?}, expected {expected:?}")]
469    ForeignDomain {
470        expected: AllocationDomainId,
471        actual: AllocationDomainId,
472    },
473    /// Another host mapping overlaps this allocation.
474    #[error("the allocation already has an active host mapping")]
475    OverlappingHostMapping,
476    /// GPU work currently owns or has reserved the allocation.
477    #[error("GPU access is in progress for the allocation")]
478    GpuAccessInProgress,
479    /// Host mapping is active while GPU access was requested.
480    #[error("the allocation is mapped for host access")]
481    MappedForHost,
482    /// The backend failed to complete the map operation.
483    #[error("backend host mapping failed: {message}")]
484    BackendFailure { message: String },
485    /// The source did not cover the full write-only mapping.
486    #[error("host write length mismatch: expected {expected}, got {actual}")]
487    LengthMismatch { expected: usize, actual: usize },
488}
489
490/// Metadata sealed at the tensor/root boundary before a provider launch.
491///
492/// Providers receive this request exactly once for a prepared access. Binding
493/// code consumes the resulting opaque state and does not receive replacement
494/// storage, ranges, or raw pointers.
495#[doc(hidden)]
496#[derive(Clone, Copy, Debug)]
497pub struct DeviceAccessRequest<'a> {
498    allocation_domain: AllocationDomainId,
499    allocation_id: AllocationId,
500    byte_len: usize,
501    element_size: usize,
502    shape: &'a [usize],
503    strides: &'a [isize],
504    offset: isize,
505}
506
507impl<'a> DeviceAccessRequest<'a> {
508    pub(crate) fn new(
509        allocation_domain: AllocationDomainId,
510        allocation_id: AllocationId,
511        byte_len: usize,
512        element_size: usize,
513        shape: &'a [usize],
514        strides: &'a [isize],
515        offset: isize,
516    ) -> Self {
517        Self {
518            allocation_domain,
519            allocation_id,
520            byte_len,
521            element_size,
522            shape,
523            strides,
524            offset,
525        }
526    }
527
528    pub fn allocation_domain(&self) -> AllocationDomainId {
529        self.allocation_domain
530    }
531
532    pub fn allocation_id(&self) -> AllocationId {
533        self.allocation_id
534    }
535
536    pub fn byte_len(&self) -> usize {
537        self.byte_len
538    }
539
540    pub fn element_size(&self) -> usize {
541        self.element_size
542    }
543
544    pub fn shape(&self) -> &[usize] {
545        self.shape
546    }
547
548    pub fn strides(&self) -> &[isize] {
549        self.strides
550    }
551
552    pub fn offset(&self) -> isize {
553        self.offset
554    }
555}
556
557/// Typed failure returned while preparing a provider-native device access.
558#[doc(hidden)]
559#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
560pub enum DeviceAccessError {
561    #[error("backend `{backend}` does not support prepared device access")]
562    Unsupported { backend: &'static str },
563    #[error("prepared device access request is invalid: {message}")]
564    InvalidRequest { message: String },
565    #[error("provider device preparation failed: {message}")]
566    ProviderFailure { message: String },
567}
568
569/// Opaque provider-prepared state retained for one device binding.
570#[doc(hidden)]
571pub trait PreparedDeviceAccess: Debug {
572    fn as_any(&self) -> &dyn Any;
573
574    fn into_any(self: Box<Self>) -> Box<dyn Any>;
575}
576
577trait ReadGuardAccess<T> {
578    fn as_slice(&self) -> &[T];
579}
580
581impl<T, G> ReadGuardAccess<T> for G
582where
583    G: Deref,
584    G::Target: AsRef<[T]>,
585{
586    fn as_slice(&self) -> &[T] {
587        self.deref().as_ref()
588    }
589}
590
591/// Closure-scoped read mapping of a backend allocation.
592///
593/// # Examples
594///
595/// ```rust
596/// use tenferro_tensor::HostReadGuard;
597///
598/// let guard = HostReadGuard::new(vec![1_u32, 2]);
599/// assert_eq!(&*guard, &[1, 2]);
600/// ```
601pub struct HostReadGuard<'a, T> {
602    access: Box<dyn ReadGuardAccess<T> + 'a>,
603}
604
605impl<T> Debug for HostReadGuard<'_, T> {
606    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
607        formatter
608            .debug_struct("HostReadGuard")
609            .field("len", &self.len())
610            .finish_non_exhaustive()
611    }
612}
613
614impl<'a, T> HostReadGuard<'a, T> {
615    /// Wrap a backend-native read guard without exposing its concrete type.
616    ///
617    /// # Examples
618    ///
619    /// ```rust
620    /// use tenferro_tensor::HostReadGuard;
621    ///
622    /// let guard = HostReadGuard::new(vec![3_i32]);
623    /// assert_eq!(guard[0], 3);
624    /// ```
625    pub fn new<G>(guard: G) -> Self
626    where
627        G: Deref + 'a,
628        G::Target: AsRef<[T]>,
629        T: 'a,
630    {
631        Self {
632            access: Box::new(guard),
633        }
634    }
635
636    /// Borrow the mapped elements as a rank-1 host view.
637    ///
638    /// The view lends the mapping's shared borrow, so the guard stays alive for
639    /// as long as the view is used and no copy or transfer happens.
640    ///
641    /// # Examples
642    ///
643    /// ```rust
644    /// use tenferro_tensor::HostReadGuard;
645    ///
646    /// let guard = HostReadGuard::new(vec![3_i32, 4]);
647    /// let view = guard.as_view()?;
648    /// assert_eq!(view.shape(), &[2]);
649    /// assert_eq!(view.get(&[1]), Some(&4));
650    /// # Ok::<(), tenferro_tensor::Error>(())
651    /// ```
652    ///
653    /// # Errors
654    ///
655    /// Returns [`crate::Error::Validation`] with
656    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] or
657    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
658    /// mapped length cannot be represented as a rank-1 layout.
659    pub fn as_view(&self) -> crate::Result<TypedTensorView<'_, T, DynRank>>
660    where
661        T: 'static,
662    {
663        let data: &[T] = self;
664        TypedTensorView::from_slice(vec![data.len()], vec![1], 0, data)
665    }
666}
667
668impl<T> Deref for HostReadGuard<'_, T> {
669    type Target = [T];
670
671    fn deref(&self) -> &Self::Target {
672        self.access.as_slice()
673    }
674}
675
676/// Backend-neutral owner of one shared tensor allocation domain.
677///
678/// CPU operation crates use this object-safe boundary to allocate results in
679/// the same managed domain without depending on a GPU provider crate.
680///
681/// # Examples
682///
683/// ```rust
684/// use std::sync::Arc;
685/// use tenferro_tensor::SharedTensorAllocationDomain;
686///
687/// let _domain: Option<Arc<dyn SharedTensorAllocationDomain>> = None;
688/// ```
689pub trait SharedTensorAllocationDomain: Debug + Send + Sync + 'static {
690    /// Return the stable identity shared by every allocation from this owner.
691    fn id(&self) -> AllocationDomainId;
692
693    /// Allocate an uninitialized compact column-major tensor in this domain.
694    ///
695    /// # Errors
696    ///
697    /// Returns a typed validation, unsupported-dtype, or backend allocation error.
698    fn allocate(&self, dtype: DType, shape: &[usize]) -> crate::Result<Tensor>;
699}
700
701type HostWriteCopy<'a, T> = dyn FnMut(&[T]) -> Result<(), HostAccessError> + 'a;
702
703/// Closure-scoped write-only mapping of a backend allocation.
704///
705/// # Examples
706///
707/// ```rust
708/// use tenferro_tensor::{HostAccessError, HostWriteGuard};
709///
710/// let mut written = Vec::new();
711/// {
712///     let mut guard = HostWriteGuard::new(2, |source: &[u32]| {
713///         written.extend_from_slice(source);
714///         Ok::<(), HostAccessError>(())
715///     });
716///     guard.copy_from_slice(&[4, 5]).unwrap();
717/// }
718/// assert_eq!(written, [4, 5]);
719/// ```
720pub struct HostWriteGuard<'a, T> {
721    len: usize,
722    copy: Box<HostWriteCopy<'a, T>>,
723}
724
725impl<T> Debug for HostWriteGuard<'_, T> {
726    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
727        formatter
728            .debug_struct("HostWriteGuard")
729            .field("len", &self.len)
730            .finish_non_exhaustive()
731    }
732}
733
734impl<'a, T> HostWriteGuard<'a, T> {
735    /// Wrap a backend-native write guard without exposing its concrete type.
736    ///
737    /// # Examples
738    ///
739    /// ```rust
740    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
741    ///
742    /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
743    /// assert!(guard.is_empty());
744    /// ```
745    ///
746    /// # Errors
747    ///
748    /// Construction is infallible. A callback failure such as
749    /// [`HostAccessError::BackendFailure`] is returned later by
750    /// [`Self::copy_from_slice`].
751    pub fn new<F>(len: usize, copy: F) -> Self
752    where
753        F: FnMut(&[T]) -> Result<(), HostAccessError> + 'a,
754        T: 'a,
755    {
756        Self {
757            len,
758            copy: Box::new(copy),
759        }
760    }
761
762    /// Number of elements covered by this write-only mapping.
763    ///
764    /// # Examples
765    ///
766    /// ```rust
767    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
768    ///
769    /// let guard = HostWriteGuard::new(2, |_source: &[f32]| Ok::<(), HostAccessError>(()));
770    /// assert_eq!(guard.len(), 2);
771    /// ```
772    pub fn len(&self) -> usize {
773        self.len
774    }
775
776    /// Returns `true` when this mapping covers no elements.
777    ///
778    /// # Examples
779    ///
780    /// ```rust
781    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
782    ///
783    /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
784    /// assert!(guard.is_empty());
785    /// ```
786    pub fn is_empty(&self) -> bool {
787        self.len == 0
788    }
789
790    /// Replace the full mapped allocation contents.
791    ///
792    /// # Examples
793    ///
794    /// ```rust
795    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
796    ///
797    /// let mut guard = HostWriteGuard::new(1, |_source: &[f32]| Ok::<(), HostAccessError>(()));
798    /// guard.copy_from_slice(&[1.0]).unwrap();
799    /// ```
800    ///
801    /// # Errors
802    ///
803    /// Returns [`HostAccessError::LengthMismatch`] when `source` does not cover
804    /// the complete mapping, or the typed backend error returned by the owning
805    /// write guard.
806    pub fn copy_from_slice(&mut self, source: &[T]) -> Result<(), HostAccessError> {
807        if source.len() != self.len {
808            return Err(HostAccessError::LengthMismatch {
809                expected: self.len,
810                actual: source.len(),
811            });
812        }
813        (self.copy)(source)
814    }
815}
816
817impl<T> Debug for BackendStorageHandle<T> {
818    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
819        f.debug_struct("BackendStorageHandle")
820            .field("id", &self.id)
821            .finish()
822    }
823}
824
825impl<T> BackendStorageHandle<T> {
826    /// Create a new backend buffer handle.
827    ///
828    /// # Examples
829    ///
830    /// ```rust
831    /// use tenferro_tensor::BackendStorageHandle;
832    ///
833    /// let handle = BackendStorageHandle::<f64>::new(1);
834    /// assert_eq!(tenferro_tensor::BackendStorage::len(&handle), 0);
835    /// ```
836    pub fn new(id: u64) -> Self {
837        Self::new_with_len(id, 0)
838    }
839
840    /// Create a new backend buffer handle with a logical element count.
841    ///
842    /// # Examples
843    ///
844    /// ```rust
845    /// use tenferro_tensor::{BackendStorage, BackendStorageHandle};
846    ///
847    /// let handle = BackendStorageHandle::<f64>::new_with_len(1, 4);
848    /// assert_eq!(BackendStorage::len(&handle), 4);
849    /// ```
850    pub fn new_with_len(id: u64, len: usize) -> Self {
851        Self {
852            id,
853            len,
854            // Synthetic opaque handles are test/adapter allocations. Give
855            // each one an explicit domain so root import never fabricates
856            // identity from a missing provider field.
857            allocation_domain: AllocationDomainId::fresh(),
858            _phantom: std::marker::PhantomData,
859        }
860    }
861}
862
863/// Opaque backend-owned tensor buffer.
864///
865/// Tensor core never inspects backend-native allocations directly. Backend
866/// crates store their own concrete handle types behind this trait and
867/// downcast inside the owning backend only.
868///
869/// # Examples
870///
871/// ```rust
872/// use std::sync::Arc;
873/// use tenferro_tensor::{BackendStorage, BackendStorageHandle};
874///
875/// let buffer: Arc<dyn BackendStorage<f64>> = Arc::new(BackendStorageHandle::<f64>::new_with_len(7, 2));
876/// assert_eq!(buffer.backend_family(), "opaque");
877/// assert_eq!(buffer.len(), 2);
878/// ```
879pub trait BackendStorage<T>: Debug + Send + Sync + 'static {
880    /// Stable backend family identifier.
881    fn backend_family(&self) -> &'static str;
882
883    /// Number of logical elements in the backend allocation.
884    fn len(&self) -> usize;
885
886    /// Returns `true` when the backend allocation is empty.
887    fn is_empty(&self) -> bool {
888        self.len() == 0
889    }
890
891    /// Return the shared-allocation domain, when this buffer belongs to one.
892    fn allocation_domain(&self) -> Option<AllocationDomainId> {
893        None
894    }
895
896    /// Return the stable physical allocation identity, when available.
897    fn allocation_id(&self) -> Option<AllocationId> {
898        None
899    }
900
901    /// Prepare one provider-native device access from the root-owned buffer.
902    ///
903    /// The returned state is consumed by the provider binding path. Providers
904    /// that do not expose device launches return [`DeviceAccessError::Unsupported`].
905    #[doc(hidden)]
906    fn prepare_device_access(
907        &self,
908        _request: DeviceAccessRequest<'_>,
909    ) -> Result<Box<dyn PreparedDeviceAccess>, DeviceAccessError> {
910        Err(DeviceAccessError::Unsupported {
911            backend: self.backend_family(),
912        })
913    }
914
915    /// Map the allocation for closure-scoped host reads.
916    ///
917    /// # Errors
918    ///
919    /// The default returns [`HostAccessError::Unsupported`]. Host-visible
920    /// backends return typed overlap, pending-GPU, or backend mapping failures.
921    fn map_read(&self) -> Result<HostReadGuard<'_, T>, HostAccessError> {
922        Err(HostAccessError::Unsupported {
923            backend: self.backend_family(),
924        })
925    }
926
927    /// Map the allocation for closure-scoped host writes.
928    ///
929    /// The mutable receiver keeps write authority with the owning tensor or
930    /// provider object; borrowed views do not clone or share that authority.
931    ///
932    /// # Errors
933    ///
934    /// The default returns [`HostAccessError::Unsupported`]. Host-visible
935    /// backends return typed overlap, pending-GPU, or backend mapping failures.
936    fn map_write(&mut self) -> Result<HostWriteGuard<'_, T>, HostAccessError> {
937        Err(HostAccessError::Unsupported {
938            backend: self.backend_family(),
939        })
940    }
941
942    /// Type-erased access for the backend crate that owns the concrete handle.
943    fn as_any(&self) -> &dyn Any;
944}
945
946impl<T: Send + Sync + 'static> BackendStorage<T> for BackendStorageHandle<T> {
947    fn backend_family(&self) -> &'static str {
948        "opaque"
949    }
950
951    fn len(&self) -> usize {
952        self.len
953    }
954
955    fn allocation_domain(&self) -> Option<AllocationDomainId> {
956        Some(self.allocation_domain)
957    }
958
959    fn allocation_id(&self) -> Option<AllocationId> {
960        Some(AllocationId::from_backend_id(self.id))
961    }
962
963    fn as_any(&self) -> &dyn Any {
964        self
965    }
966}
967
968/// Tensor storage.
969///
970/// # Examples
971///
972/// ```rust
973/// use tenferro_tensor::StorageBuffer;
974///
975/// let host = StorageBuffer::Host(vec![1.0_f64, 2.0]);
976/// ```
977#[derive(Debug)]
978pub enum StorageBuffer<T> {
979    Host(Vec<T>),
980    Backend(Box<dyn BackendStorage<T>>),
981}
982
983impl<T: 'static> StorageBuffer<T> {
984    /// Return the physical element count in this buffer.
985    ///
986    /// # Examples
987    ///
988    /// ```rust
989    /// use tenferro_tensor::StorageBuffer;
990    ///
991    /// assert_eq!(StorageBuffer::Host(vec![1_i32, 2]).len(), 2);
992    /// ```
993    pub fn len(&self) -> usize {
994        match self {
995            Self::Host(data) => data.len(),
996            Self::Backend(buffer) => buffer.len(),
997        }
998    }
999
1000    /// Return whether this buffer has no physical elements.
1001    ///
1002    /// # Examples
1003    ///
1004    /// ```rust
1005    /// use tenferro_tensor::StorageBuffer;
1006    ///
1007    /// assert!(StorageBuffer::<i32>::Host(Vec::new()).is_empty());
1008    /// ```
1009    pub fn is_empty(&self) -> bool {
1010        self.len() == 0
1011    }
1012
1013    /// Return whether the storage is backend-owned rather than host-owned.
1014    ///
1015    /// # Examples
1016    ///
1017    /// ```rust
1018    /// use tenferro_tensor::StorageBuffer;
1019    ///
1020    /// assert!(!StorageBuffer::Host(vec![1_i32]).is_backend());
1021    /// ```
1022    pub fn is_backend(&self) -> bool {
1023        matches!(self, Self::Backend(_))
1024    }
1025}
1026
1027/// Sealed marker for the plain and pooled host payload of a [`TypedTensor`].
1028///
1029/// `TypedTensor<T, R, Host>` owns host elements directly, so it offers
1030/// infallible host access (`as_slice`, `get`, `get_mut`, `Index`/`IndexMut`)
1031/// and [`Clone`] without a runtime device check. `T` needs no `Copy`,
1032/// [`TensorScalar`] or arithmetic bound for that.
1033///
1034/// # Examples
1035///
1036/// ```
1037/// use tenferro_tensor::{DynRank, Host, TypedTensor};
1038///
1039/// let tensor: TypedTensor<f64, DynRank, Host> =
1040///     TypedTensor::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1041/// assert_eq!(tensor.as_slice(), &[1.0, 2.0]);
1042/// assert_eq!(tensor[&[1]], 2.0);
1043/// # Ok::<(), tenferro_tensor::Error>(())
1044/// ```
1045#[derive(Debug)]
1046pub struct Host;
1047
1048/// Sealed marker for the group-backed payload of a [`TypedTensor`].
1049///
1050/// `TypedTensor<T, R, Gpu>` carries provider allocation authority, identity
1051/// and retirement state through its allocation group, so host access stays
1052/// fallible and must be prepared or mapped explicitly. The marker is not proof
1053/// of a particular device ordinal or provider.
1054///
1055/// # Examples
1056///
1057/// ```
1058/// use tenferro_tensor::{DynRank, Gpu, Host, TypedTensor};
1059///
1060/// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1061/// let gpu: TypedTensor<f64, DynRank, Gpu> = host.promote()?;
1062/// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
1063/// # Ok::<(), tenferro_tensor::Error>(())
1064/// ```
1065#[derive(Debug)]
1066pub struct Gpu;
1067
1068/// Sealed marker for the runtime union of the host and group-backed payloads.
1069///
1070/// This is the default representation: one type for ordinary tensors that may
1071/// be host-resident or backend-resident at runtime.
1072///
1073/// # Examples
1074///
1075/// ```
1076/// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1077///
1078/// let host = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1079/// let dynamic: TypedTensor<i32, DynRank, Dynamic> = host.into_dynamic();
1080/// assert_eq!(dynamic.host_data()?, &[1, 2]);
1081/// # Ok::<(), tenferro_tensor::Error>(())
1082/// ```
1083#[derive(Debug)]
1084pub struct Dynamic;
1085
1086mod representation_sealed {
1087    pub trait Sealed {}
1088    impl Sealed for super::Host {}
1089    impl Sealed for super::Gpu {}
1090    impl Sealed for super::Dynamic {}
1091}
1092
1093/// Sealed selector for the owned payload representation of a [`TypedTensor`].
1094///
1095/// The three markers are [`Host`], [`Gpu`] and [`Dynamic`]. The trait is
1096/// sealed: downstream crates select an existing representation, they do not
1097/// define one.
1098///
1099/// # Examples
1100///
1101/// ```
1102/// use tenferro_tensor::{Dynamic, DynRank, Host, Representation, TypedTensor};
1103///
1104/// fn extent<D: Representation>(tensor: &TypedTensor<f64, DynRank, D>) -> usize {
1105///     tensor.shape()[0]
1106/// }
1107/// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![3], vec![0.0; 3])?;
1108/// assert_eq!(extent(&host), 3);
1109/// let dynamic: TypedTensor<f64, DynRank, Dynamic> = host.into_dynamic();
1110/// assert_eq!(extent(&dynamic), 3);
1111/// # Ok::<(), tenferro_tensor::Error>(())
1112/// ```
1113pub trait Representation: representation_sealed::Sealed + 'static {
1114    /// Owned payload stored for this representation.
1115    #[doc(hidden)]
1116    type Storage<T, R: TensorRank>;
1117}
1118
1119impl Representation for Host {
1120    #[doc(hidden)]
1121    type Storage<T, R: TensorRank> = HostStorage<T>;
1122}
1123
1124impl Representation for Gpu {
1125    #[doc(hidden)]
1126    type Storage<T, R: TensorRank> = GroupStorage<R>;
1127}
1128
1129impl Representation for Dynamic {
1130    #[doc(hidden)]
1131    type Storage<T, R: TensorRank> = DynamicStorage<T, R>;
1132}
1133
1134/// Directly owned plain or pooled host elements.
1135#[doc(hidden)]
1136pub struct HostStorage<T> {
1137    data: HostData<T>,
1138}
1139
1140/// Group-backed payload: provider allocation authority, identity and retirement.
1141#[doc(hidden)]
1142pub struct GroupStorage<R: TensorRank> {
1143    group: Box<OwnedTensorGroup<R>>,
1144}
1145
1146/// Runtime union of the [`Host`] and [`Gpu`] owned payloads.
1147#[doc(hidden)]
1148pub enum DynamicStorage<T, R: TensorRank> {
1149    Host(HostStorage<T>),
1150    Group(GroupStorage<R>),
1151}
1152
1153impl<T> std::fmt::Debug for HostStorage<T> {
1154    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1155        self.data.fmt(formatter)
1156    }
1157}
1158
1159impl<R: TensorRank> std::fmt::Debug for GroupStorage<R> {
1160    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1161        formatter
1162            .debug_struct("GroupStorage")
1163            .field("group", &self.group)
1164            .finish()
1165    }
1166}
1167
1168impl<T, R: TensorRank> std::fmt::Debug for DynamicStorage<T, R> {
1169    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1170        match self {
1171            Self::Host(host) => formatter.debug_tuple("Host").field(host).finish(),
1172            Self::Group(group) => formatter.debug_tuple("Group").field(group).finish(),
1173        }
1174    }
1175}
1176
1177/// Owned compact column-major typed tensor.
1178///
1179/// `T` is the element type, `R` the rank metadata (default [`DynRank`]) and `D`
1180/// the owned representation (default [`Dynamic`]). Shape and placement live on
1181/// the tensor itself, once, never inside a storage arm.
1182///
1183/// No bound is imposed on `T` by the type: plain host adoption only needs the
1184/// constructor's own requirements, and `T: Clone` is required only by the
1185/// copying constructors. A preset [`TensorScalar`] appears only where a dtype
1186/// identity or a numerical capability is actually used, never for plain host
1187/// ownership.
1188///
1189/// Owned tensors are compact column-major. Arbitrary strides and metadata-only
1190/// layout changes are represented by [`TypedTensorView`] and
1191/// [`TypedTensorViewMut`].
1192///
1193/// # Examples
1194///
1195/// ```
1196/// use tenferro_tensor::{DynRank, Host, Rank, Tensor, TypedTensor};
1197///
1198/// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
1199/// assert_eq!(t.shape(), &[2, 2]);
1200///
1201/// let static_rank = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
1202/// assert_eq!(static_rank.rank(), 2);
1203///
1204/// let host: TypedTensor<i32, DynRank, Host> =
1205///     TypedTensor::from_host_vec_col_major(vec![2], vec![1, 2]).unwrap();
1206/// assert_eq!(host.get(&[1])?, &2);
1207///
1208/// let dynamic = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64; 4]).unwrap();
1209/// assert_eq!(dynamic.shape(), &[2, 2]);
1210/// # Ok::<(), tenferro_tensor::Error>(())
1211/// ```
1212pub struct TypedTensor<T, R: TensorRank = DynRank, D: Representation = Dynamic> {
1213    shape: R::Shape,
1214    placement: Placement,
1215    storage: D::Storage<T, R>,
1216}
1217
1218/// Validate and adopt a column-major host vector as a statically host-owned tensor.
1219fn typed_host_tensor_from_vec_col_major<T, R: TensorRank>(
1220    shape: impl tenferro_tensor_core::IntoRankShape<R>,
1221    data: Vec<T>,
1222    op: &'static str,
1223) -> crate::Result<TypedTensor<T, R, Host>> {
1224    let shape = shape
1225        .into_rank_shape()
1226        .map_err(|err| tensor_layout_error(op, err))?;
1227    tenferro_tensor_core::col_major_strides(shape.as_ref())
1228        .map_err(|err| tensor_layout_error(op, err))?;
1229    try_checked_shape_len(shape.as_ref(), data.len(), op)?;
1230    Ok(TypedTensor {
1231        shape,
1232        placement: default_placement(),
1233        storage: HostStorage {
1234            data: HostData::new(data),
1235        },
1236    })
1237}
1238
1239/// Reorder explicit row-major host values into column-major order.
1240fn row_major_reorder<T: Clone, R: TensorRank>(
1241    shape: impl tenferro_tensor_core::IntoRankShape<R>,
1242    data: Vec<T>,
1243    op: &'static str,
1244) -> crate::Result<(R::Shape, Vec<T>)> {
1245    let shape = shape
1246        .into_rank_shape()
1247        .map_err(|err| tensor_layout_error(op, err))?;
1248    tenferro_tensor_core::col_major_strides(shape.as_ref())
1249        .map_err(|err| tensor_layout_error(op, err))?;
1250    try_checked_shape_len(shape.as_ref(), data.len(), op)?;
1251    let mut row_strides = ShapeVec::from_elem(0, shape.as_ref().len());
1252    let mut stride = 1usize;
1253    for axis in (0..row_strides.len()).rev() {
1254        row_strides[axis] = stride;
1255        stride = stride
1256            .checked_mul(shape.as_ref()[axis])
1257            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
1258    }
1259    let mut coordinates = ShapeVec::from_elem(0, row_strides.len());
1260    let mut source_offset = 0usize;
1261    let mut reordered = Vec::with_capacity(data.len());
1262    for _ in 0..data.len() {
1263        reordered.push(data[source_offset].clone());
1264        for axis in 0..coordinates.len() {
1265            coordinates[axis] += 1;
1266            if coordinates[axis] < shape.as_ref()[axis] {
1267                source_offset += row_strides[axis];
1268                break;
1269            }
1270            coordinates[axis] = 0;
1271            source_offset -= row_strides[axis] * (shape.as_ref()[axis] - 1);
1272        }
1273    }
1274    Ok((shape, reordered))
1275}
1276
1277impl<T, R: TensorRank> TypedTensor<T, R, Host> {
1278    /// Adopt a column-major host `Vec<T>` as a statically host-owned tensor.
1279    ///
1280    /// The payload is the caller's vector itself; `T` needs no `Copy`,
1281    /// [`TensorScalar`] or arithmetic bound, and no group, session or device is
1282    /// involved.
1283    ///
1284    /// # Examples
1285    /// ```
1286    /// use tenferro_tensor::{Host, Rank, TypedTensor};
1287    /// struct Custom(String);
1288    /// let tensor = TypedTensor::<Custom, Rank<2>, Host>::from_host_vec_col_major(
1289    ///     [1, 2], vec![Custom("a".into()), Custom("b".into())],
1290    /// )?;
1291    /// assert_eq!(tensor[&[0, 1]].0.as_str(), "b");
1292    /// # Ok::<(), tenferro_tensor::Error>(())
1293    /// ```
1294    /// # Errors
1295    /// Returns [`crate::Error::Validation`] for a rank mismatch, shape/data
1296    /// length mismatch or shape/stride arithmetic overflow.
1297    pub fn from_host_vec_col_major(
1298        shape: impl tenferro_tensor_core::IntoRankShape<R>,
1299        data: Vec<T>,
1300    ) -> crate::Result<Self> {
1301        typed_host_tensor_from_vec_col_major(shape, data, "from_host_vec_col_major")
1302    }
1303
1304    /// Explicitly import row-major host values into column-major storage.
1305    ///
1306    /// Clones each input element once.
1307    ///
1308    /// # Examples
1309    /// ```
1310    /// use tenferro_tensor::{Host, Rank, TypedTensor};
1311    /// let tensor = TypedTensor::<i32, Rank<2>, Host>::from_host_vec_row_major(
1312    ///     [2, 3], vec![1, 2, 3, 4, 5, 6],
1313    /// )?;
1314    /// assert_eq!(tensor[&[1, 0]], 4);
1315    /// assert_eq!(tensor[&[0, 2]], 3);
1316    /// # Ok::<(), tenferro_tensor::Error>(())
1317    /// ```
1318    /// # Errors
1319    /// Returns [`crate::Error::Validation`] for a rank, shape-length or stride
1320    /// overflow, or when the shape product disagrees with the input length.
1321    pub fn from_host_vec_row_major(
1322        shape: impl tenferro_tensor_core::IntoRankShape<R>,
1323        data: Vec<T>,
1324    ) -> crate::Result<Self>
1325    where
1326        T: Clone,
1327    {
1328        let (shape, data) = row_major_reorder(shape, data, "from_host_vec_row_major")?;
1329        typed_host_tensor_from_vec_col_major(shape, data, "from_host_vec_row_major")
1330    }
1331
1332    /// Borrow the owned host elements.
1333    ///
1334    /// # Examples
1335    /// ```
1336    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1337    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1338    /// assert_eq!(tensor.as_slice(), &[1, 2]);
1339    /// # Ok::<(), tenferro_tensor::Error>(())
1340    /// ```
1341    pub fn as_slice(&self) -> &[T] {
1342        self.storage.data.as_slice()
1343    }
1344
1345    /// Alias of [`Self::as_slice`].
1346    ///
1347    /// # Examples
1348    ///
1349    /// ```
1350    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1351    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1352    /// assert_eq!(tensor.host_data(), &[1, 2]);
1353    /// # Ok::<(), tenferro_tensor::Error>(())
1354    /// ```
1355    pub fn host_data(&self) -> &[T] {
1356        self.as_slice()
1357    }
1358
1359    /// Exclusively borrow the owned host elements.
1360    ///
1361    /// # Examples
1362    ///
1363    /// ```
1364    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1365    /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1366    /// tensor.host_data_mut()[0] = 5;
1367    /// assert_eq!(tensor.as_slice(), &[5, 2]);
1368    /// # Ok::<(), tenferro_tensor::Error>(())
1369    /// ```
1370    pub fn host_data_mut(&mut self) -> &mut [T] {
1371        self.storage.data.as_mut_slice()
1372    }
1373
1374    /// Borrow one element by checked column-major multi-index.
1375    ///
1376    /// # Examples
1377    ///
1378    /// ```
1379    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1380    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2, 2], vec![1, 2, 3, 4])?;
1381    /// assert_eq!(tensor.get(&[1, 1])?, &4);
1382    /// assert!(tensor.get(&[2, 0]).is_err());
1383    /// # Ok::<(), tenferro_tensor::Error>(())
1384    /// ```
1385    ///
1386    /// # Errors
1387    /// Returns [`crate::Error::Validation`] for a wrong rank, an out-of-range
1388    /// coordinate or offset arithmetic overflow.
1389    pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
1390        let offset = self.linear_offset(indices)?;
1391        self.as_slice().get(offset).ok_or_else(|| {
1392            crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
1393        })
1394    }
1395
1396    /// Exclusively borrow one element by checked column-major multi-index.
1397    ///
1398    /// # Examples
1399    ///
1400    /// ```
1401    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1402    /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1403    /// *tensor.get_mut(&[1])? = 9;
1404    /// assert_eq!(tensor.as_slice(), &[1, 9]);
1405    /// # Ok::<(), tenferro_tensor::Error>(())
1406    /// ```
1407    ///
1408    /// # Errors
1409    /// Returns [`crate::Error::Validation`] for a wrong rank, an out-of-range
1410    /// coordinate or offset arithmetic overflow.
1411    pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
1412        let offset = self.linear_offset(indices)?;
1413        self.host_data_mut().get_mut(offset).ok_or_else(|| {
1414            crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
1415        })
1416    }
1417
1418    /// Consume this tensor and return the original host vector.
1419    ///
1420    /// # Examples
1421    ///
1422    /// ```
1423    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1424    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1425    /// assert_eq!(tensor.into_host_vec(), vec![1, 2]);
1426    /// # Ok::<(), tenferro_tensor::Error>(())
1427    /// ```
1428    pub fn into_host_vec(self) -> Vec<T> {
1429        self.storage.data.into_vec()
1430    }
1431
1432    /// Consume this tensor and return its shape and column-major host vector.
1433    ///
1434    /// # Examples
1435    ///
1436    /// ```
1437    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1438    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2, 1], vec![1, 2])?;
1439    /// let (shape, data) = tensor.into_vec_col_major();
1440    /// assert_eq!(shape, vec![2, 1]);
1441    /// assert_eq!(data, vec![1, 2]);
1442    /// # Ok::<(), tenferro_tensor::Error>(())
1443    /// ```
1444    pub fn into_vec_col_major(self) -> (Vec<usize>, Vec<T>) {
1445        let shape = self.shape().to_vec();
1446        (shape, self.into_host_vec())
1447    }
1448
1449    /// Make an explicit independent host copy with the same placement.
1450    ///
1451    /// # Examples
1452    /// ```
1453    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1454    /// let tensor = TypedTensor::<String, DynRank, Host>::from_host_vec_col_major(vec![1], vec!["a".into()])?;
1455    /// let copy = tensor.clone();
1456    /// assert_eq!(copy[&[0]], "a");
1457    /// # Ok::<(), tenferro_tensor::Error>(())
1458    /// ```
1459    pub fn duplicate(&self) -> Self
1460    where
1461        T: Clone,
1462    {
1463        let mut copy = typed_host_tensor_from_vec_col_major::<T, R>(
1464            self.shape.clone(),
1465            self.as_slice().to_vec(),
1466            "TypedTensor::duplicate",
1467        )
1468        .unwrap_or_else(|err| unreachable!("a validated host owner re-copies: {err}"));
1469        copy.placement = self.placement.clone();
1470        copy
1471    }
1472
1473    /// Move this host owner into the runtime union without copying or regrouping.
1474    ///
1475    /// # Examples
1476    /// ```
1477    /// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1478    /// let host = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7])?;
1479    /// let dynamic: TypedTensor<i32, DynRank, Dynamic> = host.into_dynamic();
1480    /// assert_eq!(dynamic.host_data()?, &[7]);
1481    /// # Ok::<(), tenferro_tensor::Error>(())
1482    /// ```
1483    pub fn into_dynamic(self) -> TypedTensor<T, R, Dynamic> {
1484        TypedTensor {
1485            shape: self.shape,
1486            placement: self.placement,
1487            storage: DynamicStorage::Host(self.storage),
1488        }
1489    }
1490
1491    /// Borrow this host owner as a typed view.
1492    ///
1493    /// # Examples
1494    ///
1495    /// ```
1496    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1497    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1498    /// let view = tensor.as_view();
1499    /// assert_eq!(view.shape(), &[2]);
1500    /// assert_eq!(view.as_host_slice(), &[1, 2]);
1501    /// # Ok::<(), tenferro_tensor::Error>(())
1502    /// ```
1503    pub fn as_view(&self) -> TypedTensorView<'_, T, R, Host> {
1504        TypedTensorView {
1505            buffer: TensorStorageRef::Host(self.as_slice()),
1506            root: None,
1507            layout: self.layout(),
1508            placement: self.placement.clone(),
1509            _representation: std::marker::PhantomData,
1510        }
1511    }
1512
1513    /// Exclusively borrow this host owner as a mutable typed view.
1514    ///
1515    /// # Examples
1516    ///
1517    /// ```
1518    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1519    /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1520    /// tensor.as_view_mut().as_host_slice_mut()[1] = 4;
1521    /// assert_eq!(tensor.as_slice(), &[1, 4]);
1522    /// # Ok::<(), tenferro_tensor::Error>(())
1523    /// ```
1524    pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R, Host> {
1525        let layout = self.layout();
1526        let placement = self.placement.clone();
1527        TypedTensorViewMut {
1528            buffer: TensorStorageRefMut::Host(self.storage.data.as_mut_slice()),
1529            root: None,
1530            layout,
1531            placement,
1532            _representation: std::marker::PhantomData,
1533        }
1534    }
1535}
1536
1537impl<T: Clone, R: TensorRank> Clone for TypedTensor<T, R, Host> {
1538    fn clone(&self) -> Self {
1539        self.duplicate()
1540    }
1541}
1542
1543impl<T, R: TensorRank> std::ops::Index<&[usize]> for TypedTensor<T, R, Host> {
1544    type Output = T;
1545
1546    /// # Panics
1547    ///
1548    /// Panics when the index has the wrong rank or is out of bounds, like any
1549    /// other slice indexing. Use [`TypedTensor::get`] for the checked form.
1550    fn index(&self, indices: &[usize]) -> &T {
1551        match self.get(indices) {
1552            Ok(value) => value,
1553            Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1554        }
1555    }
1556}
1557
1558impl<T, R: TensorRank> std::ops::IndexMut<&[usize]> for TypedTensor<T, R, Host> {
1559    /// # Panics
1560    ///
1561    /// Panics when the index has the wrong rank or is out of bounds, like any
1562    /// other slice indexing. Use [`TypedTensor::get_mut`] for the checked form.
1563    fn index_mut(&mut self, indices: &[usize]) -> &mut T {
1564        match self.get_mut(indices) {
1565            Ok(value) => value,
1566            Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1567        }
1568    }
1569}
1570
1571/// Read mapping over a host slice, as one owning guard.
1572fn host_read_guard<T>(data: &[T]) -> HostReadGuard<'_, T> {
1573    HostReadGuard::new(data)
1574}
1575
1576/// Write mapping over an exclusive host slice, as one owning guard.
1577fn host_write_guard<T: Clone>(data: &mut [T]) -> HostWriteGuard<'_, T> {
1578    let len = data.len();
1579    HostWriteGuard::new(len, move |source| {
1580        data.clone_from_slice(source);
1581        Ok(())
1582    })
1583}
1584
1585/// Promote a plain host payload into a group-backed root without copying elements.
1586fn promote_host_group<T: TensorScalar, R: TensorRank>(
1587    shape: R::Shape,
1588    host: HostData<T>,
1589) -> crate::Result<OwnedTensorGroup<R>> {
1590    let recycler = host.recycler.clone();
1591    let mut group = OwnedTensorGroup::from_host_vec(shape, host.into_vec())?;
1592    if let Some(recycler) = recycler {
1593        group
1594            .group
1595            .set_host_recycler(group.allocation_index.index(), recycler)
1596            .map_err(|error| crate::Error::runtime_state_source("TypedTensor::promote", error))?;
1597    }
1598    Ok(group)
1599}
1600
1601impl<T, R: TensorRank> TypedTensor<T, R, Host> {
1602    /// Borrow the owned host elements through one owning read mapping.
1603    ///
1604    /// The guard retains the exclusive borrow of this owner, so no other access
1605    /// can overlap it while the mapping is alive.
1606    ///
1607    /// # Examples
1608    /// ```
1609    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1610    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1611    /// let guard = tensor.map_read();
1612    /// assert_eq!(&guard[..], &[1, 2]);
1613    /// # Ok::<(), tenferro_tensor::Error>(())
1614    /// ```
1615    pub fn map_read(&self) -> HostReadGuard<'_, T> {
1616        host_read_guard(self.as_slice())
1617    }
1618
1619    /// Exclusively borrow the owned host elements through one owning write mapping.
1620    ///
1621    /// Publish data with [`HostWriteGuard::copy_from_slice`]; the mapping is
1622    /// released when the guard is dropped.
1623    ///
1624    /// # Examples
1625    /// ```
1626    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1627    /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1628    /// tensor.map_write().copy_from_slice(&[3, 4]).unwrap();
1629    /// assert_eq!(tensor.as_slice(), &[3, 4]);
1630    /// # Ok::<(), tenferro_tensor::Error>(())
1631    /// ```
1632    pub fn map_write(&mut self) -> HostWriteGuard<'_, T>
1633    where
1634        T: Clone,
1635    {
1636        host_write_guard(self.host_data_mut())
1637    }
1638}
1639
1640impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R, Host> {
1641    /// Promote this plain host owner into a group-backed owner.
1642    ///
1643    /// The payload is adopted as a provider root: no element is copied and a
1644    /// pooled return target survives the promotion.
1645    ///
1646    /// # Examples
1647    /// ```
1648    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1649    /// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1650    /// let gpu = host.promote()?;
1651    /// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
1652    /// # Ok::<(), tenferro_tensor::Error>(())
1653    /// ```
1654    /// # Errors
1655    /// Returns [`crate::Error::RuntimeState`] when the new allocation group
1656    /// rejects the promoted root or its recycler.
1657    pub fn promote(self) -> crate::Result<TypedTensor<T, R, Gpu>> {
1658        let TypedTensor {
1659            shape,
1660            placement,
1661            storage,
1662        } = self;
1663        let group = promote_host_group(shape.clone(), storage.data)?;
1664        Ok(TypedTensor {
1665            shape,
1666            placement,
1667            storage: GroupStorage {
1668                group: Box::new(group),
1669            },
1670        })
1671    }
1672}
1673
1674impl<T, R: TensorRank> TypedTensor<T, R, Dynamic> {
1675    /// Borrow host elements through one owning read mapping.
1676    ///
1677    /// # Examples
1678    ///
1679    /// ```
1680    /// use tenferro_tensor::TypedTensor;
1681    /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
1682    /// let guard = tensor.map_read()?;
1683    /// assert_eq!(&guard[..], &[1, 2]);
1684    /// # Ok::<(), tenferro_tensor::Error>(())
1685    /// ```
1686    ///
1687    /// # Errors
1688    /// Returns [`crate::Error::HostAccess`] with
1689    /// [`HostAccessError::Unsupported`] when the group's allocation is not
1690    /// host-accessible.
1691    pub fn map_read(&self) -> crate::Result<HostReadGuard<'_, T>> {
1692        match &self.storage {
1693            DynamicStorage::Host(host) => Ok(host_read_guard(host.data.as_slice())),
1694            DynamicStorage::Group(core) => core.group.host_slice::<T>().map(host_read_guard),
1695        }
1696    }
1697
1698    /// Exclusively borrow host elements through one owning write mapping.
1699    ///
1700    /// Publish data with [`HostWriteGuard::copy_from_slice`].
1701    ///
1702    /// # Examples
1703    ///
1704    /// ```
1705    /// use tenferro_tensor::{Error, TypedTensor};
1706    /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
1707    /// tensor
1708    ///     .map_write()?
1709    ///     .copy_from_slice(&[3, 4])
1710    ///     .map_err(|err| Error::host_access("map_write", err))?;
1711    /// assert_eq!(tensor.host_data()?, &[3, 4]);
1712    /// # Ok::<(), tenferro_tensor::Error>(())
1713    /// ```
1714    ///
1715    /// # Errors
1716    /// Returns [`crate::Error::HostAccess`] with
1717    /// [`HostAccessError::Unsupported`] when the group's allocation is not
1718    /// host-writable.
1719    pub fn map_write(&mut self) -> crate::Result<HostWriteGuard<'_, T>>
1720    where
1721        T: Clone,
1722    {
1723        match &mut self.storage {
1724            DynamicStorage::Host(host) => Ok(host_write_guard(host.data.as_mut_slice())),
1725            DynamicStorage::Group(core) => core.group.host_slice_mut::<T>().map(host_write_guard),
1726        }
1727    }
1728}
1729
1730impl<T, R: TensorRank> TypedTensor<T, R, Gpu> {
1731    /// Adopt a backend-owned buffer as a statically group-backed owner.
1732    ///
1733    /// A host buffer belongs to the [`Host`] representation; pass it to
1734    /// `TypedTensor::<T, R, Host>` instead of silently regrouping it here.
1735    ///
1736    /// # Examples
1737    /// ```
1738    /// use tenferro_tensor::{BackendStorageHandle, Placement, StorageBuffer, TypedTensor};
1739    /// let handle = BackendStorageHandle::<f32>::new_with_len(1, 2);
1740    /// let gpu = TypedTensor::<f32, tenferro_tensor::DynRank, tenferro_tensor::Gpu>::from_backend_buffer_col_major(
1741    ///     vec![2],
1742    ///     StorageBuffer::Backend(Box::new(handle)),
1743    ///     Placement::default(),
1744    /// )?;
1745    /// assert!(gpu.is_backend_buffer());
1746    /// # Ok::<(), tenferro_tensor::Error>(())
1747    /// ```
1748    /// # Errors
1749    /// Returns [`crate::Error::Validation`] when the compact shape disagrees
1750    /// with the buffer length, [`crate::Error::RuntimeState`] for a host buffer,
1751    /// or a runtime-state error when the allocation group cannot be built.
1752    pub fn from_backend_buffer_col_major(
1753        shape: impl tenferro_tensor_core::IntoRankShape<R>,
1754        buffer: StorageBuffer<T>,
1755        placement: Placement,
1756    ) -> crate::Result<Self>
1757    where
1758        T: TensorScalar + Send + Sync + 'static,
1759    {
1760        let op = "TypedTensor::from_backend_buffer_col_major";
1761        let layout = try_compact_layout(shape, op)?;
1762        try_checked_shape_len(layout.shape(), buffer.len(), op)?;
1763        let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
1764            .map_err(|err| tensor_layout_error(op, err))?;
1765        match buffer {
1766            StorageBuffer::Host(_) => Err(crate::Error::runtime_state(
1767                op,
1768                "a host buffer is plain host storage; use TypedTensor::<T, R, Host>::from_vec_col_major",
1769            )),
1770            StorageBuffer::Backend(buffer) => {
1771                let group = OwnedTensorGroup::from_backend_buffer(
1772                    group_shape.clone(),
1773                    StorageBuffer::Backend(buffer),
1774                    placement.clone(),
1775                )?;
1776                Ok(TypedTensor {
1777                    shape: group_shape,
1778                    placement,
1779                    storage: GroupStorage {
1780                        group: Box::new(group),
1781                    },
1782                })
1783            }
1784        }
1785    }
1786
1787    /// Move this group-backed owner into the runtime union without rewriting it.
1788    ///
1789    /// # Examples
1790    ///
1791    /// ```
1792    /// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1793    /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7.0])?.promote()?;
1794    /// let dynamic: TypedTensor<f64, DynRank, Dynamic> = gpu.into_dynamic();
1795    /// assert_eq!(dynamic.host_data()?, &[7.0]);
1796    /// # Ok::<(), tenferro_tensor::Error>(())
1797    /// ```
1798    pub fn into_dynamic(self) -> TypedTensor<T, R, Dynamic> {
1799        TypedTensor {
1800            shape: self.shape,
1801            placement: self.placement,
1802            storage: DynamicStorage::Group(self.storage),
1803        }
1804    }
1805
1806    /// Whether this group's descriptor names a non-CPU provider.
1807    ///
1808    /// # Examples
1809    ///
1810    /// ```
1811    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1812    /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7.0])?.promote()?;
1813    /// // A promoted host payload stays on the CPU provider.
1814    /// assert!(!gpu.is_backend_buffer());
1815    /// # Ok::<(), tenferro_tensor::Error>(())
1816    /// ```
1817    pub fn is_backend_buffer(&self) -> bool {
1818        self.storage.group.is_backend_buffer()
1819    }
1820
1821    /// Borrow host elements when the group's allocation is host-accessible.
1822    ///
1823    /// # Examples
1824    ///
1825    /// ```
1826    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1827    /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?.promote()?;
1828    /// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
1829    /// # Ok::<(), tenferro_tensor::Error>(())
1830    /// ```
1831    ///
1832    /// # Errors
1833    /// Returns [`crate::Error::RuntimeState`] for a device-only allocation.
1834    pub fn host_data(&self) -> crate::Result<&[T]> {
1835        self.storage.group.host_slice::<T>()
1836    }
1837
1838    /// Exclusively borrow host elements when the group's allocation is host-accessible.
1839    ///
1840    /// # Examples
1841    ///
1842    /// ```
1843    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1844    /// let mut gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?.promote()?;
1845    /// gpu.host_data_mut()?[0] = 3.0;
1846    /// assert_eq!(gpu.host_data()?, &[3.0, 2.0]);
1847    /// # Ok::<(), tenferro_tensor::Error>(())
1848    /// ```
1849    ///
1850    /// # Errors
1851    /// Returns [`crate::Error::RuntimeState`] for a device-only allocation.
1852    pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
1853        self.storage.group.host_slice_mut::<T>()
1854    }
1855
1856    /// Prepare this group-backed owner for one provider-native read binding.
1857    ///
1858    /// # Errors
1859    /// Returns a provider preparation error when the group has no device-read
1860    /// path for this layout.
1861    #[doc(hidden)]
1862    pub fn prepare_device_read(
1863        &self,
1864        op: &'static str,
1865    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
1866    where
1867        T: TensorScalar + 'static,
1868    {
1869        let layout = self.layout();
1870        self.storage
1871            .group
1872            .prepare_device_read_for_layout::<T>(&layout)
1873            .map_err(|error| crate::Error::runtime_state_source(op, error))
1874    }
1875
1876    /// Prepare this group-backed owner for one provider-native write binding.
1877    ///
1878    /// # Errors
1879    /// Returns a provider preparation error when the group has no device-write
1880    /// path for this layout.
1881    #[doc(hidden)]
1882    pub fn prepare_device_write(
1883        &mut self,
1884        op: &'static str,
1885    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
1886    where
1887        T: TensorScalar + 'static,
1888    {
1889        let layout = self.layout();
1890        self.storage
1891            .group
1892            .prepare_device_write_for_layout::<T>(&layout)
1893            .map_err(|error| crate::Error::runtime_state_source(op, error))
1894    }
1895}
1896
1897impl<T, R: TensorRank> TypedTensor<T, R, Dynamic> {
1898    /// Checked narrowing to the statically host-owned representation.
1899    ///
1900    /// Fails without consuming ownership of the source tensor.
1901    ///
1902    /// # Examples
1903    /// ```
1904    /// use tenferro_tensor::TypedTensor;
1905    /// let dynamic = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![7])?;
1906    /// let host = dynamic.into_host().unwrap();
1907    /// assert_eq!(host.as_slice(), &[7]);
1908    /// # Ok::<(), tenferro_tensor::Error>(())
1909    /// ```
1910    /// # Errors
1911    /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is
1912    /// group-backed rather than plain host storage.
1913    #[allow(clippy::result_large_err)]
1914    pub fn into_host(self) -> Result<TypedTensor<T, R, Host>, ReinterpretError<Self>> {
1915        let TypedTensor {
1916            shape,
1917            placement,
1918            storage,
1919        } = self;
1920        match storage {
1921            DynamicStorage::Host(host) => Ok(TypedTensor {
1922                shape,
1923                placement,
1924                storage: host,
1925            }),
1926            DynamicStorage::Group(group) => Err(ReinterpretError::new(
1927                TypedTensor {
1928                    shape,
1929                    placement,
1930                    storage: DynamicStorage::Group(group),
1931                },
1932                crate::Error::runtime_state(
1933                    "TypedTensor::into_host",
1934                    "the tensor is group-backed rather than plain host storage",
1935                ),
1936            )),
1937        }
1938    }
1939
1940    /// Checked narrowing to the group-backed representation.
1941    ///
1942    /// Fails without consuming ownership of the source tensor.
1943    ///
1944    /// # Examples
1945    ///
1946    /// ```
1947    /// use tenferro_tensor::{DynRank, Gpu, Host, TypedTensor};
1948    /// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![2.0])?;
1949    /// let dynamic = host.promote()?.into_dynamic();
1950    /// let gpu: TypedTensor<f64, DynRank, Gpu> = dynamic.into_gpu().map_err(|f| f.into_parts().1)?;
1951    /// assert_eq!(gpu.host_data()?, &[2.0]);
1952    /// let plain = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![2.0])?;
1953    /// assert!(plain.into_gpu().is_err());
1954    /// # Ok::<(), tenferro_tensor::Error>(())
1955    /// ```
1956    ///
1957    /// # Errors
1958    /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is a
1959    /// plain host owner rather than a group-backed one.
1960    #[allow(clippy::result_large_err)]
1961    pub fn into_gpu(self) -> Result<TypedTensor<T, R, Gpu>, ReinterpretError<Self>> {
1962        let TypedTensor {
1963            shape,
1964            placement,
1965            storage,
1966        } = self;
1967        match storage {
1968            DynamicStorage::Group(group) => Ok(TypedTensor {
1969                shape,
1970                placement,
1971                storage: group,
1972            }),
1973            DynamicStorage::Host(host) => Err(ReinterpretError::new(
1974                TypedTensor {
1975                    shape,
1976                    placement,
1977                    storage: DynamicStorage::Host(host),
1978                },
1979                crate::Error::runtime_state(
1980                    "TypedTensor::into_gpu",
1981                    "the tensor is a plain host owner rather than group-backed storage",
1982                ),
1983            )),
1984        }
1985    }
1986}
1987
1988impl<T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensor<T, R, D> {
1989    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1990        formatter
1991            .debug_struct("TypedTensor")
1992            .field("shape", &self.shape.as_ref())
1993            .field("placement", &self.placement)
1994            .finish_non_exhaustive()
1995    }
1996}
1997
1998impl<T, R: TensorRank, D: Representation> TypedTensor<T, R, D> {
1999    /// Number of elements in the tensor.
2000    ///
2001    /// # Examples
2002    ///
2003    /// ```rust
2004    /// use tenferro_tensor::TypedTensor;
2005    ///
2006    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2007    /// assert_eq!(t.n_elements(), 6);
2008    /// ```
2009    pub fn n_elements(&self) -> usize {
2010        // Invariant: owned tensor constructors validate compact shape length against buffer length.
2011        match try_shape_product(self.shape(), "TypedTensor::n_elements") {
2012            Ok(n) => n,
2013            Err(err) => {
2014                unreachable!("TypedTensor compact shape is validated at construction: {err}")
2015            }
2016        }
2017    }
2018
2019    /// Tensor shape.
2020    ///
2021    /// # Examples
2022    ///
2023    /// ```
2024    /// use tenferro_tensor::TypedTensor;
2025    ///
2026    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
2027    /// assert_eq!(t.shape(), &[2]);
2028    /// ```
2029    pub fn shape(&self) -> &[usize] {
2030        self.shape.as_ref()
2031    }
2032
2033    /// Tensor rank.
2034    ///
2035    /// # Examples
2036    ///
2037    /// ```
2038    /// use tenferro_tensor::TypedTensor;
2039    ///
2040    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2041    /// assert_eq!(t.rank(), 2);
2042    /// ```
2043    pub fn rank(&self) -> usize {
2044        self.shape().len()
2045    }
2046
2047    /// Tensor layout metadata.
2048    ///
2049    /// Owned typed tensors are always compact column-major layouts.
2050    ///
2051    /// # Examples
2052    ///
2053    /// ```
2054    /// use tenferro_tensor::TypedTensor;
2055    ///
2056    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2057    /// assert_eq!(t.layout().strides(), &[1, 2]);
2058    /// ```
2059    pub fn layout(&self) -> TensorLayout<R> {
2060        TensorLayout::compact(self.shape.clone())
2061            .unwrap_or_else(|err| unreachable!("validated owned shape: {err}"))
2062    }
2063
2064    /// Return placement metadata for this tensor.
2065    ///
2066    /// # Examples
2067    ///
2068    /// ```
2069    /// use tenferro_tensor::{MemoryKind, TypedTensor};
2070    ///
2071    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
2072    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
2073    /// ```
2074    pub fn placement(&self) -> &Placement {
2075        &self.placement
2076    }
2077
2078    /// Replace placement metadata without changing the storage buffer.
2079    ///
2080    /// # Examples
2081    ///
2082    /// ```
2083    /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
2084    ///
2085    /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
2086    /// t.set_placement(Placement {
2087    ///     memory_kind: MemoryKind::PinnedHost,
2088    ///     device: None,
2089    ///     cpu_affinity: None,
2090    /// });
2091    /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
2092    /// ```
2093    pub fn set_placement(&mut self, placement: Placement) {
2094        self.placement = placement;
2095    }
2096
2097    /// Replace only CPU routing/locality metadata without changing storage.
2098    ///
2099    /// Device, memory kind, backend allocation domain, and allocation identity
2100    /// remain unchanged.
2101    ///
2102    /// # Examples
2103    ///
2104    /// ```rust
2105    /// use tenferro_tensor::{CpuDomainId, TypedTensor};
2106    ///
2107    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0])?;
2108    /// tensor.set_cpu_affinity(Some(CpuDomainId::new(4)));
2109    /// assert_eq!(tensor.placement().cpu_affinity, Some(CpuDomainId::new(4)));
2110    /// # Ok::<(), tenferro_tensor::Error>(())
2111    /// ```
2112    pub fn set_cpu_affinity(&mut self, cpu_affinity: Option<CpuDomainId>) {
2113        self.placement.cpu_affinity = cpu_affinity;
2114    }
2115
2116    /// Compute the checked compact column-major offset of an element.
2117    ///
2118    /// # Examples
2119    /// ```
2120    /// use tenferro_tensor::TypedTensor;
2121    /// let tensor = TypedTensor::<String>::from_vec_col_major([2, 3], vec![String::new(); 6])?;
2122    /// assert_eq!(tensor.linear_offset(&[1, 2])?, 5);
2123    /// # Ok::<(), tenferro_tensor::Error>(())
2124    /// ```
2125    /// # Errors
2126    /// Wrong rank, out-of-range coordinates or arithmetic overflow return
2127    /// [`crate::Error::Validation`].
2128    pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2129        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
2130    }
2131
2132    /// Consume this tensor and return its layout metadata.
2133    ///
2134    /// # Examples
2135    ///
2136    /// ```
2137    /// use tenferro_tensor::TypedTensor;
2138    ///
2139    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
2140    /// assert!(t.into_layout().is_compact_col_major().unwrap());
2141    /// ```
2142    pub fn into_layout(self) -> TensorLayout<R> {
2143        self.layout()
2144    }
2145
2146    /// Compute the physical element offset for a logical index.
2147    ///
2148    /// # Examples
2149    ///
2150    /// ```rust
2151    /// use tenferro_tensor::TypedTensor;
2152    ///
2153    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
2154    /// assert_eq!(t.layout_linear_offset(&[1, 2])?, 5);
2155    /// # Ok::<(), tenferro_tensor::Error>(())
2156    /// ```
2157    /// # Errors
2158    ///
2159    /// Returns [`crate::Error::Validation`] with
2160    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
2161    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
2162    /// when an index is outside its axis extent, or
2163    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
2164    /// arithmetic overflows.
2165    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2166        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::layout_linear_offset")
2167    }
2168
2169    /// Return whether this owned tensor is compact column-major.
2170    ///
2171    /// # Examples
2172    ///
2173    /// ```rust
2174    /// use tenferro_tensor::TypedTensor;
2175    ///
2176    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2177    /// assert!(t.is_col_major_contiguous()?);
2178    /// # Ok::<(), tenferro_tensor::Error>(())
2179    /// ```
2180    /// # Errors
2181    ///
2182    /// Returns [`crate::Error::Validation`] with
2183    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
2184    /// compactness arithmetic overflows.
2185    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
2186        self.layout()
2187            .is_compact_col_major()
2188            .map_err(|err| tensor_layout_error("TypedTensor::is_col_major_contiguous", err))
2189    }
2190
2191    /// Return a compact string summary of this tensor's layout metadata.
2192    ///
2193    /// # Examples
2194    ///
2195    /// ```rust
2196    /// use tenferro_tensor::TypedTensor;
2197    ///
2198    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2199    /// assert!(t.layout_summary().contains("shape=[2]"));
2200    /// # Ok::<(), tenferro_tensor::Error>(())
2201    /// ```
2202    pub fn layout_summary(&self) -> String {
2203        let layout = self.layout();
2204        layout_summary(self.shape(), layout.strides(), layout.offset())
2205    }
2206
2207    /// Assert this tensor is compact column-major.
2208    ///
2209    /// # Examples
2210    ///
2211    /// ```rust
2212    /// use tenferro_tensor::TypedTensor;
2213    ///
2214    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2215    /// t.assert_col_major_contiguous()?;
2216    /// # Ok::<(), tenferro_tensor::Error>(())
2217    /// ```
2218    /// # Errors
2219    ///
2220    /// Returns [`crate::Error::Validation`] with
2221    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
2222    /// compactness arithmetic overflows, or
2223    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
2224    /// tensor is not compact column-major.
2225    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
2226        let layout = self.layout();
2227        assert_layout_col_major_contiguous(
2228            self.is_col_major_contiguous()?,
2229            self.shape(),
2230            layout.strides(),
2231            layout.offset(),
2232            "TypedTensor::assert_col_major_contiguous",
2233        )
2234    }
2235}
2236
2237struct HostData<T> {
2238    buffer: StorageBuffer<T>,
2239    recycler: Option<std::sync::Weak<dyn crate::HostBufferRecycler<T>>>,
2240}
2241
2242impl<T> std::fmt::Debug for HostData<T> {
2243    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2244        formatter
2245            .debug_struct("HostData")
2246            .field("pooled", &self.recycler.is_some())
2247            .finish_non_exhaustive()
2248    }
2249}
2250
2251impl<T> HostData<T> {
2252    fn new(data: Vec<T>) -> Self {
2253        Self {
2254            buffer: StorageBuffer::Host(data),
2255            recycler: None,
2256        }
2257    }
2258
2259    fn into_vec(mut self) -> Vec<T> {
2260        self.recycler = None;
2261        match std::mem::replace(&mut self.buffer, StorageBuffer::Host(Vec::new())) {
2262            StorageBuffer::Host(data) => data,
2263            StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2264        }
2265    }
2266
2267    fn as_slice(&self) -> &[T] {
2268        match &self.buffer {
2269            StorageBuffer::Host(data) => data,
2270            StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2271        }
2272    }
2273
2274    fn as_mut_slice(&mut self) -> &mut [T] {
2275        match &mut self.buffer {
2276            StorageBuffer::Host(data) => data,
2277            StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2278        }
2279    }
2280}
2281
2282impl<T> Drop for HostData<T> {
2283    fn drop(&mut self) {
2284        let Some(recycler) = self.recycler.take().and_then(|weak| weak.upgrade()) else {
2285            return;
2286        };
2287        if let StorageBuffer::Host(data) = &mut self.buffer {
2288            recycler.recycle(std::mem::take(data));
2289        }
2290    }
2291}
2292
2293/// The sole owner handle for host tensors. The allocation group owns the
2294/// provider root; the descriptor slot carries only the logical view metadata.
2295pub(crate) struct OwnedTensorGroup<R: TensorRank> {
2296    group: AllocationGroup,
2297    slot: DescriptorSlot,
2298    allocation_index: AllocationSlot,
2299    // INVARIANT: this non-owning address points into the group root, whose host
2300    // vector cannot resize while the owning tensor is borrowed. It is stored as
2301    // `NonZeroUsize` rather than `NonNull<u8>`: the niche keeps the field at one
2302    // word, and unlike `NonNull` it preserves the `Send`/`Sync` auto traits the
2303    // typed tensor contract requires.
2304    host_ptr: Option<NonZeroUsize>,
2305    host_byte_len: usize,
2306    _rank: PhantomData<R>,
2307}
2308
2309impl<R: TensorRank> Debug for OwnedTensorGroup<R> {
2310    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2311        formatter
2312            .debug_struct("OwnedTensorGroup")
2313            .field("slot", &self.slot)
2314            .finish_non_exhaustive()
2315    }
2316}
2317
2318impl<R: TensorRank> OwnedTensorGroup<R> {
2319    fn from_host_vec<T: TensorScalar>(shape: R::Shape, data: Vec<T>) -> crate::Result<Self> {
2320        let (group, slot) = AllocationGroup::from_host_vec::<T, R>(shape, data)
2321            .map_err(|error| group_error("TypedTensor::from_host_vec", error))?;
2322        let allocation_index = group
2323            .allocation_index(slot)
2324            .map_err(|error| group_error("TypedTensor::from_host_vec", error))?;
2325        let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
2326        Ok(Self {
2327            group,
2328            slot,
2329            allocation_index,
2330            host_ptr,
2331            host_byte_len,
2332            _rank: PhantomData,
2333        })
2334    }
2335
2336    fn from_backend_buffer<T: TensorScalar + Send + Sync + 'static>(
2337        shape: R::Shape,
2338        buffer: StorageBuffer<T>,
2339        placement: Placement,
2340    ) -> crate::Result<Self> {
2341        let (mut group, slot) = AllocationGroup::from_backend_buffer::<T, R>(shape, buffer)
2342            .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2343        group
2344            .set_descriptor_placement(slot, placement)
2345            .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2346        let allocation_index = group
2347            .allocation_index(slot)
2348            .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2349        let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
2350        Ok(Self {
2351            group,
2352            slot,
2353            allocation_index,
2354            host_ptr,
2355            host_byte_len,
2356            _rank: PhantomData,
2357        })
2358    }
2359
2360    fn view<T: TensorScalar>(&self) -> crate::Result<GroupReadView<'_, T, R>> {
2361        self.group
2362            .view(self.slot)
2363            .map_err(|error| group_error("TypedTensor::group_view", error))
2364    }
2365
2366    fn view_dyn<T: TensorScalar>(&self) -> crate::Result<GroupReadView<'_, T, DynRank>> {
2367        self.group
2368            .view(self.slot)
2369            .map_err(|error| group_error("TypedTensor::group_view", error))
2370    }
2371
2372    fn view_mut<T: TensorScalar>(&mut self) -> crate::Result<GroupWriteView<'_, T, R>> {
2373        self.group
2374            .view_mut(self.slot)
2375            .map_err(|error| group_error("TypedTensor::group_view_mut", error))
2376    }
2377
2378    fn view_mut_dyn<T: TensorScalar>(&mut self) -> crate::Result<GroupWriteView<'_, T, DynRank>> {
2379        self.group
2380            .view_mut(self.slot)
2381            .map_err(|error| group_error("TypedTensor::group_view_mut", error))
2382    }
2383
2384    fn prepare_device_read_for_layout<T: TensorScalar>(
2385        &self,
2386        layout: &TensorLayout<R>,
2387    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>> {
2388        self.group
2389            .prepare_device_read_for_layout::<T, R>(self.slot, layout)
2390            .map_err(|error| {
2391                crate::Error::runtime_state_source("TypedTensor::prepare_device_read", error)
2392            })
2393    }
2394
2395    fn prepare_device_write_for_layout<T: TensorScalar>(
2396        &mut self,
2397        layout: &TensorLayout<R>,
2398    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>> {
2399        self.group
2400            .prepare_device_write_for_layout::<T, R>(self.slot, layout)
2401            .map_err(|error| {
2402                crate::Error::runtime_state_source("TypedTensor::prepare_device_write", error)
2403            })
2404    }
2405
2406    fn host_buffer<T: 'static>(&self) -> Option<&StorageBuffer<T>> {
2407        self.group.host_buffer_at::<T>(self.allocation_index)
2408    }
2409
2410    fn host_slice<T>(&self) -> crate::Result<&[T]> {
2411        let Some(pointer) = self.host_ptr else {
2412            return Err(crate::Error::runtime_state(
2413                "TypedTensor::host_data",
2414                "backend storage cannot be borrowed as host data; download explicitly first",
2415            ));
2416        };
2417        let element_size = size_of::<T>();
2418        let Some(element_count) = self.host_byte_len.checked_div(element_size) else {
2419            return Err(crate::Error::runtime_state(
2420                "TypedTensor::host_data",
2421                "host allocation byte length is not aligned to the requested dtype",
2422            ));
2423        };
2424        // SAFETY: the pointer and byte length were captured from the unique
2425        // root's full host allocation; the root cannot resize while borrowed.
2426        Ok(unsafe { std::slice::from_raw_parts(pointer.get() as *const T, element_count) })
2427    }
2428
2429    fn host_slice_mut<T>(&mut self) -> crate::Result<&mut [T]> {
2430        let Some(pointer) = self.host_ptr else {
2431            return Err(crate::Error::runtime_state(
2432                "TypedTensor::host_data_mut",
2433                "backend storage cannot be borrowed as host data; download explicitly first",
2434            ));
2435        };
2436        let element_size = size_of::<T>();
2437        let Some(element_count) = self.host_byte_len.checked_div(element_size) else {
2438            return Err(crate::Error::runtime_state(
2439                "TypedTensor::host_data_mut",
2440                "host allocation byte length is not aligned to the requested dtype",
2441            ));
2442        };
2443        // SAFETY: the pointer and byte length were captured from the unique
2444        // root; this method has the only mutable borrow of that root.
2445        Ok(unsafe { std::slice::from_raw_parts_mut(pointer.get() as *mut T, element_count) })
2446    }
2447
2448    fn backend_buffer<T: 'static>(&self) -> Option<&StorageBuffer<T>> {
2449        self.group.backend_buffer::<T>(self.slot)
2450    }
2451
2452    fn backend_buffer_mut<T: 'static>(&mut self) -> Option<&mut StorageBuffer<T>> {
2453        self.group.backend_buffer_mut::<T>(self.slot)
2454    }
2455
2456    // INVARIANT: the failure carrier must return the unchanged group owner, so
2457    // the wide `(Self, Error)` pair is deliberate rather than a boxing bug.
2458    #[allow(clippy::result_large_err)]
2459    fn into_host_vec<T: 'static>(self) -> std::result::Result<Vec<T>, (Self, crate::Error)> {
2460        if self.backend_buffer::<T>().is_some() {
2461            return Err((
2462                self,
2463                crate::Error::runtime_state(
2464                    "TypedTensor::into_host_vec",
2465                    "backend buffers cannot be exported as host Vec; download the tensor first",
2466                ),
2467            ));
2468        }
2469        let OwnedTensorGroup {
2470            group,
2471            slot,
2472            allocation_index,
2473            host_ptr,
2474            host_byte_len,
2475            ..
2476        } = self;
2477        match group.into_host_vec::<T>(slot) {
2478            Ok(data) => Ok(data),
2479            Err((group, error)) => Err((
2480                OwnedTensorGroup {
2481                    group,
2482                    slot,
2483                    allocation_index,
2484                    host_ptr,
2485                    host_byte_len,
2486                    _rank: PhantomData,
2487                },
2488                crate::Error::runtime_state("TypedTensor::into_host_vec", error),
2489            )),
2490        }
2491    }
2492
2493    fn into_parts(self) -> (AllocationGroup, DescriptorSlot) {
2494        (self.group, self.slot)
2495    }
2496
2497    #[allow(clippy::result_large_err)]
2498    fn reinterpret<T: TensorScalar, U: TensorScalar>(
2499        self,
2500        shape: Vec<usize>,
2501        strides: Vec<isize>,
2502        offset: isize,
2503    ) -> Result<OwnedTensorGroup<DynRank>, (Self, crate::Error)> {
2504        let OwnedTensorGroup {
2505            group,
2506            slot,
2507            allocation_index,
2508            host_ptr,
2509            host_byte_len,
2510            _rank: _,
2511        } = self;
2512        match group.reinterpret_descriptor::<T, U>(slot, shape, strides, offset) {
2513            Ok(group) => Ok(OwnedTensorGroup {
2514                group,
2515                slot,
2516                allocation_index,
2517                host_ptr,
2518                host_byte_len,
2519                _rank: PhantomData,
2520            }),
2521            Err((group, error)) => Err((
2522                OwnedTensorGroup {
2523                    group,
2524                    slot,
2525                    allocation_index,
2526                    host_ptr,
2527                    host_byte_len,
2528                    _rank: PhantomData,
2529                },
2530                group_error("TypedTensor::reinterpret", error),
2531            )),
2532        }
2533    }
2534}
2535
2536fn host_metadata<T: 'static>(
2537    group: &AllocationGroup,
2538    slot: DescriptorSlot,
2539) -> (Option<NonZeroUsize>, usize) {
2540    group
2541        .host_root_metadata::<T>(slot)
2542        .map_or((None, 0), |(pointer, byte_len)| {
2543            // INVARIANT: `host_root_metadata` reports the base address of the group's live
2544            // host allocation, which is non-null for any slice, empty included.
2545            (NonZeroUsize::new(pointer), byte_len)
2546        })
2547}
2548
2549fn group_error(op: &'static str, error: GroupError) -> crate::Error {
2550    crate::Error::runtime_state(op, error.to_string())
2551}
2552
2553/// Borrowed tensor buffer reference used by read-only typed views.
2554///
2555/// # Examples
2556///
2557/// ```rust
2558/// use tenferro_tensor::TensorStorageRef;
2559///
2560/// let data = [1_i32, 2];
2561/// let buffer = TensorStorageRef::Host(&data);
2562/// assert_eq!(buffer.len(), 2);
2563/// ```
2564#[derive(Debug)]
2565pub enum TensorStorageRef<'a, T> {
2566    Host(&'a [T]),
2567    Backend(&'a dyn BackendStorage<T>),
2568    #[doc(hidden)]
2569    Root(&'a dyn BackendAllocation),
2570}
2571
2572impl<T> Clone for TensorStorageRef<'_, T> {
2573    fn clone(&self) -> Self {
2574        match self {
2575            Self::Host(data) => Self::Host(data),
2576            Self::Backend(buffer) => Self::Backend(*buffer),
2577            Self::Root(allocation) => Self::Root(*allocation),
2578        }
2579    }
2580}
2581
2582impl<T: 'static> TensorStorageRef<'_, T> {
2583    /// Return the logical length of the backing allocation.
2584    ///
2585    /// # Examples
2586    ///
2587    /// ```rust
2588    /// use tenferro_tensor::TensorStorageRef;
2589    ///
2590    /// let data = [1_i32, 2, 3];
2591    /// assert_eq!(TensorStorageRef::Host(&data).len(), 3);
2592    /// ```
2593    pub fn len(&self) -> usize {
2594        match self {
2595            Self::Host(data) => data.len(),
2596            Self::Backend(buffer) => buffer.len(),
2597            Self::Root(allocation) => allocation
2598                .root_extent()
2599                .byte_len()
2600                .checked_div(std::mem::size_of::<T>())
2601                .unwrap_or(0),
2602        }
2603    }
2604
2605    /// Return whether the backing allocation is empty.
2606    ///
2607    /// # Examples
2608    ///
2609    /// ```rust
2610    /// use tenferro_tensor::TensorStorageRef;
2611    ///
2612    /// let data: [f64; 0] = [];
2613    /// assert!(TensorStorageRef::Host(&data).is_empty());
2614    /// ```
2615    pub fn is_empty(&self) -> bool {
2616        self.len() == 0
2617    }
2618}
2619
2620/// Borrowed tensor buffer reference used by mutable typed views.
2621///
2622/// Backend buffers can be represented for residency metadata, but this crate
2623/// does not expose host mutation for backend-native allocations.
2624///
2625/// # Examples
2626///
2627/// ```rust
2628/// use tenferro_tensor::TensorStorageRefMut;
2629///
2630/// let mut data = [1_i32, 2];
2631/// let buffer = TensorStorageRefMut::Host(&mut data);
2632/// assert_eq!(buffer.len(), 2);
2633/// ```
2634#[derive(Debug)]
2635pub enum TensorStorageRefMut<'a, T> {
2636    Host(&'a mut [T]),
2637    Backend(&'a mut dyn BackendStorage<T>),
2638}
2639
2640impl<T: 'static> TensorStorageRefMut<'_, T> {
2641    /// Return the logical length of the backing allocation.
2642    ///
2643    /// # Examples
2644    ///
2645    /// ```rust
2646    /// use tenferro_tensor::TensorStorageRefMut;
2647    ///
2648    /// let mut data = [1_i32, 2, 3];
2649    /// assert_eq!(TensorStorageRefMut::Host(&mut data).len(), 3);
2650    /// ```
2651    pub fn len(&self) -> usize {
2652        match self {
2653            Self::Host(data) => data.len(),
2654            Self::Backend(buffer) => buffer.len(),
2655        }
2656    }
2657
2658    /// Return whether the backing allocation is empty.
2659    ///
2660    /// # Examples
2661    ///
2662    /// ```rust
2663    /// use tenferro_tensor::TensorStorageRefMut;
2664    ///
2665    /// let mut data: [f64; 0] = [];
2666    /// assert!(TensorStorageRefMut::Host(&mut data).is_empty());
2667    /// ```
2668    pub fn is_empty(&self) -> bool {
2669        self.len() == 0
2670    }
2671}
2672
2673/// Read-only borrowed view of typed tensor storage with arbitrary strides.
2674///
2675/// `TypedTensorView` is the typed representation for layout-only tensor
2676/// transformations. It borrows an existing host or backend allocation and
2677/// carries a logical shape, strides, and an offset. Slicing, reshaping when
2678/// stride-compatible, and [`transpose_view`](TypedTensorView::transpose_view)
2679/// update only metadata and do not copy storage.
2680///
2681/// Materialize through [`TensorStructural::to_contiguous_read`](crate::TensorStructural::to_contiguous_read)
2682/// on the active backend session when a compact owned [`TypedTensor`] is
2683/// required. Use [`TypedTensorView::as_slice`] only when the current view is
2684/// contiguous in the requested layout.
2685///
2686/// # Examples
2687///
2688/// ```rust
2689/// use tenferro_tensor::{Rank, TypedTensorView};
2690///
2691/// let data = [1_i32, 2, 3, 4];
2692/// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
2693/// assert_eq!(view.get(&[1, 1]), Some(&4));
2694/// # Ok::<(), tenferro_tensor::Error>(())
2695/// ```
2696/// Borrowed typed view of one tensor representation.
2697///
2698/// The buffer stays concrete on purpose: a generic-associated buffer would make
2699/// the projection invariant in `'a`, and the borrowed-read surface relies on
2700/// `TensorRead<'long>` shortening to `TensorRead<'short>`. `D` therefore marks
2701/// which representation produced the view - `Host` is only ever built from host
2702/// slices, with no retained region.
2703pub struct TypedTensorView<'a, T, R: TensorRank = DynRank, D: Representation = Dynamic> {
2704    buffer: TensorStorageRef<'a, T>,
2705    root: Option<GroupReadView<'a, T, R>>,
2706    layout: TensorLayout<R>,
2707    placement: Placement,
2708    _representation: std::marker::PhantomData<D>,
2709}
2710
2711impl<'a, T, R: TensorRank, D: Representation> Clone for TypedTensorView<'a, T, R, D> {
2712    fn clone(&self) -> Self {
2713        Self {
2714            buffer: self.buffer.clone(),
2715            root: self.root.clone(),
2716            layout: self.layout.clone(),
2717            placement: self.placement.clone(),
2718            _representation: std::marker::PhantomData,
2719        }
2720    }
2721}
2722
2723impl<'a, T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensorView<'a, T, R, D> {
2724    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2725        formatter
2726            .debug_struct("TypedTensorView")
2727            .field("shape", &self.layout.shape())
2728            .field("placement", &self.placement)
2729            .finish_non_exhaustive()
2730    }
2731}
2732
2733impl<'a, T: 'static> TypedTensorView<'a, T, DynRank> {
2734    /// Create a borrowed dynamic-rank view over compact column-major host data.
2735    ///
2736    /// # Examples
2737    ///
2738    /// ```rust
2739    /// use tenferro_tensor::TypedTensorView;
2740    ///
2741    /// let data = [1_i32, 2, 3, 4];
2742    /// let view = TypedTensorView::from_col_major(&[2, 2], &data)?;
2743    /// assert_eq!(view.strides(), &[1, 2]);
2744    /// # Ok::<(), tenferro_tensor::Error>(())
2745    /// ```
2746    ///
2747    /// # Errors
2748    ///
2749    /// Returns [`crate::Error::Validation`] with
2750    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when compact
2751    /// strides or reachable bounds overflow, or
2752    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2753    /// requested shape reaches beyond `data`.
2754    pub fn from_col_major(shape: &[usize], data: &'a [T]) -> crate::Result<Self> {
2755        let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
2756            .map_err(|err| tensor_layout_error("TypedTensorView::from_col_major", err))?;
2757        Self::from_buffer_ref(
2758            shape_vec(layout.shape()),
2759            stride_vec(layout.strides()),
2760            layout.offset(),
2761            TensorStorageRef::Host(data),
2762            default_placement(),
2763            "TypedTensorView::from_col_major",
2764        )
2765    }
2766
2767    /// Create a borrowed host view from explicit layout metadata.
2768    ///
2769    /// # Examples
2770    ///
2771    /// ```rust
2772    /// use tenferro_tensor::TypedTensorView;
2773    ///
2774    /// let data = [1_i32, 2, 3];
2775    /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
2776    /// assert_eq!(view.get(&[2]), Some(&1));
2777    /// # Ok::<(), tenferro_tensor::Error>(())
2778    /// ```
2779    ///
2780    /// # Errors
2781    ///
2782    /// Returns [`crate::Error::Validation`] with
2783    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
2784    /// `strides` have different ranks,
2785    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2786    /// reachable layout exceeds `data`, or
2787    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
2788    /// arithmetic overflows.
2789    pub fn from_slice(
2790        shape: impl AsRef<[usize]>,
2791        strides: impl AsRef<[isize]>,
2792        offset: isize,
2793        data: &'a [T],
2794    ) -> crate::Result<Self> {
2795        Self::from_buffer_ref(
2796            shape_vec(shape.as_ref()),
2797            stride_vec(strides.as_ref()),
2798            offset,
2799            TensorStorageRef::Host(data),
2800            default_placement(),
2801            "TypedTensorView::from_slice",
2802        )
2803    }
2804}
2805
2806impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
2807    /// Create a rank-generic borrowed host view from explicit layout metadata.
2808    ///
2809    /// # Examples
2810    ///
2811    /// ```rust
2812    /// use tenferro_tensor::{Rank, TypedTensorView};
2813    ///
2814    /// let data = [1_i32, 2, 3, 4];
2815    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
2816    /// assert_eq!(view.get(&[1, 1]), Some(&4));
2817    /// # Ok::<(), tenferro_tensor::Error>(())
2818    /// ```
2819    ///
2820    /// # Errors
2821    ///
2822    /// Returns [`crate::Error::Validation`] with
2823    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
2824    /// rank does not match `shape` or `strides`,
2825    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2826    /// reachable layout exceeds `data`, or
2827    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
2828    /// arithmetic overflows.
2829    pub fn from_slice_ranked(
2830        shape: impl Into<R::Shape>,
2831        strides: impl Into<R::Strides>,
2832        offset: isize,
2833        data: &'a [T],
2834    ) -> crate::Result<Self> {
2835        Self::from_buffer_ref(
2836            shape,
2837            strides,
2838            offset,
2839            TensorStorageRef::Host(data),
2840            default_placement(),
2841            "TypedTensorView::from_slice_ranked",
2842        )
2843    }
2844}
2845
2846impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
2847    fn from_buffer_ref(
2848        shape: impl Into<R::Shape>,
2849        strides: impl Into<R::Strides>,
2850        offset: isize,
2851        buffer: TensorStorageRef<'a, T>,
2852        placement: Placement,
2853        op: &'static str,
2854    ) -> crate::Result<Self> {
2855        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
2856            .map_err(|err| tensor_layout_error(op, err))?;
2857        Ok(Self {
2858            buffer,
2859            root: None,
2860            layout,
2861            placement,
2862            _representation: std::marker::PhantomData,
2863        })
2864    }
2865}
2866
2867impl<'a, T: 'static, R: TensorRank> TypedTensorView<'a, T, R, Host> {
2868    /// Create a representation-marked view over an explicit host layout.
2869    ///
2870    /// A `Host`-marked view is only ever built from a host slice, so it carries
2871    /// no retained region and its host slice needs no runtime check.
2872    ///
2873    /// # Examples
2874    ///
2875    /// ```rust
2876    /// use tenferro_tensor::{DynRank, Host, TypedTensorView};
2877    ///
2878    /// let data = [1_i32, 2, 3, 4];
2879    /// let view: TypedTensorView<'_, i32, DynRank, Host> =
2880    ///     TypedTensorView::from_host_slice(vec![2, 2], vec![1, 2], 0, &data)?;
2881    /// assert_eq!(view.as_host_slice(), &[1, 2, 3, 4]);
2882    /// # Ok::<(), tenferro_tensor::Error>(())
2883    /// ```
2884    ///
2885    /// # Errors
2886    ///
2887    /// Returns [`crate::Error::Validation`] with
2888    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `strides`
2889    /// has a different rank, or
2890    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] /
2891    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
2892    /// reachable layout leaves `data` or overflows.
2893    pub fn from_host_slice(
2894        shape: impl Into<R::Shape>,
2895        strides: impl Into<R::Strides>,
2896        offset: isize,
2897        data: &'a [T],
2898    ) -> crate::Result<Self> {
2899        // Built here rather than through the shared helper so that the Host
2900        // marker can only ever be paired with a host slice.
2901        let buffer = TensorStorageRef::Host(data);
2902        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
2903            .map_err(|err| tensor_layout_error("TypedTensorView::from_host_slice", err))?;
2904        Ok(Self {
2905            buffer,
2906            root: None,
2907            layout,
2908            placement: default_placement(),
2909            _representation: std::marker::PhantomData,
2910        })
2911    }
2912
2913    /// Borrow the host elements without a runtime representation check.
2914    ///
2915    /// # Examples
2916    ///
2917    /// ```rust
2918    /// use tenferro_tensor::{DynRank, Host, TypedTensorView};
2919    ///
2920    /// let data = [1_i32, 2];
2921    /// let view: TypedTensorView<'_, i32, DynRank, Host> =
2922    ///     TypedTensorView::from_host_slice(vec![2], vec![1], 0, &data)?;
2923    /// assert_eq!(view.as_host_slice(), &[1, 2]);
2924    /// # Ok::<(), tenferro_tensor::Error>(())
2925    /// ```
2926    ///
2927    /// # Panics
2928    ///
2929    /// Panics only if a `Host`-marked view was built from non-host storage,
2930    /// which no constructor in this crate does.
2931    pub fn as_host_slice(&self) -> &'a [T] {
2932        match &self.buffer {
2933            TensorStorageRef::Host(data) => data,
2934            // INVARIANT: `Host`-marked views are constructed only by
2935            // `from_host_slice` and by `TypedTensor<_, _, Host>::as_view`.
2936            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
2937                unreachable!("a Host-marked view always borrows host storage")
2938            }
2939        }
2940    }
2941}
2942
2943impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
2944    /// Erase a borrowed view's rank and dtype for session dispatch without
2945    /// allocating tensor storage or promoting it into an allocation group.
2946    /// Common ranks use the existing inline shape and stride metadata.
2947    ///
2948    /// # Examples
2949    ///
2950    /// ```rust
2951    /// use tenferro_tensor::{Rank, TypedTensorView};
2952    /// let data = [1.0_f64, 2.0];
2953    /// let view = TypedTensorView::<_, Rank<1>>::from_slice_ranked([2], [1], 0, &data)?;
2954    /// let read = view.into_tensor_read()?;
2955    /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
2956    /// # Ok::<(), tenferro_tensor::Error>(())
2957    /// ```
2958    /// # Errors
2959    /// Returns [`crate::Error::Validation`] with
2960    /// [`ValidationError::IntegerOverflow`] for unrepresentable metadata or
2961    /// [`ValidationError::ViewOutOfBounds`] if the layout leaves its allocation.
2962    pub fn into_tensor_read(self) -> crate::Result<TensorRead<'a>>
2963    where
2964        T: TensorScalar,
2965    {
2966        let layout = TensorLayout::<DynRank>::from_parts(
2967            shape_vec(self.layout.shape()),
2968            stride_vec(self.layout.strides()),
2969            self.layout.offset(),
2970            self.buffer.len(),
2971        )
2972        .map_err(|err| tensor_layout_error("TypedTensorView::into_tensor_read", err))?;
2973        let view = TypedTensorView {
2974            buffer: self.buffer,
2975            root: self.root.map(GroupReadView::into_dyn),
2976            layout,
2977            placement: self.placement,
2978            _representation: std::marker::PhantomData,
2979        };
2980        Ok(TensorRead::from_view(T::tensor_view(view)))
2981    }
2982
2983    /// Return the logical shape.
2984    ///
2985    /// # Examples
2986    ///
2987    /// ```rust
2988    /// use tenferro_tensor::TypedTensorView;
2989    ///
2990    /// let data = [0_i32; 2];
2991    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
2992    /// assert_eq!(view.shape(), &[2]);
2993    /// # Ok::<(), tenferro_tensor::Error>(())
2994    /// ```
2995    pub fn shape(&self) -> &[usize] {
2996        self.layout.shape()
2997    }
2998
2999    /// Return the logical rank carried by this view.
3000    ///
3001    /// # Examples
3002    ///
3003    /// ```
3004    /// use tenferro_tensor::TypedTensorView;
3005    ///
3006    /// let data = [0_i32; 6];
3007    /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
3008    /// assert_eq!(view.rank(), 2);
3009    /// # Ok::<(), tenferro_tensor::Error>(())
3010    /// ```
3011    pub fn rank(&self) -> usize {
3012        self.shape().len()
3013    }
3014
3015    /// Return strides in element units.
3016    ///
3017    /// # Examples
3018    ///
3019    /// ```rust
3020    /// use tenferro_tensor::TypedTensorView;
3021    ///
3022    /// let data = [0_i32; 2];
3023    /// let view = TypedTensorView::from_slice(vec![2], vec![-1], 1, &data)?;
3024    /// assert_eq!(view.strides(), &[-1]);
3025    /// # Ok::<(), tenferro_tensor::Error>(())
3026    /// ```
3027    pub fn strides(&self) -> &[isize] {
3028        self.layout.strides()
3029    }
3030
3031    /// Return the physical element offset.
3032    ///
3033    /// # Examples
3034    ///
3035    /// ```rust
3036    /// use tenferro_tensor::TypedTensorView;
3037    ///
3038    /// let data = [1_i32, 2];
3039    /// let view = TypedTensorView::from_slice(vec![1], vec![1], 1, &data)?;
3040    /// assert_eq!(view.offset(), 1);
3041    /// # Ok::<(), tenferro_tensor::Error>(())
3042    /// ```
3043    pub fn offset(&self) -> isize {
3044        self.layout.offset()
3045    }
3046
3047    /// Return the borrowed host storage backing this view.
3048    ///
3049    /// This exposes the entire backing host allocation, not just the logical
3050    /// slice covered by this view. Use [`TypedTensorView::as_slice`] when the
3051    /// caller needs the contiguous logical region instead.
3052    ///
3053    /// # Examples
3054    ///
3055    /// ```rust
3056    /// use tenferro_tensor::TypedTensorView;
3057    ///
3058    /// let data = [1_i32, 2];
3059    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3060    /// assert_eq!(view.host_storage()?, &[1, 2]);
3061    /// # Ok::<(), tenferro_tensor::Error>(())
3062    /// ```
3063    ///
3064    /// # Errors
3065    ///
3066    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
3067    /// buffer; backend storage must be downloaded before host inspection.
3068    pub fn host_storage(&self) -> crate::Result<&'a [T]> {
3069        match &self.buffer {
3070            TensorStorageRef::Host(data) => Ok(data),
3071            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3072                Err(crate::Error::runtime_state(
3073                    "TypedTensorView::host_storage",
3074                    "backend buffers cannot expose host storage; download explicitly first",
3075                ))
3076            }
3077        }
3078    }
3079
3080    /// Return the number of logical elements in this view.
3081    ///
3082    /// # Examples
3083    ///
3084    /// ```rust
3085    /// use tenferro_tensor::TypedTensorView;
3086    ///
3087    /// let data = [0_i32; 6];
3088    /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
3089    /// assert_eq!(view.n_elements(), 6);
3090    /// # Ok::<(), tenferro_tensor::Error>(())
3091    /// ```
3092    pub fn n_elements(&self) -> usize {
3093        // Invariant: public view constructors validate logical element count.
3094        match checked_view_element_count(self.shape(), "TypedTensorView::n_elements") {
3095            Ok(n) => n,
3096            Err(err) => {
3097                unreachable!("TypedTensorView layout shape is validated at construction: {err}")
3098            }
3099        }
3100    }
3101
3102    /// Return layout metadata for this view.
3103    ///
3104    /// # Examples
3105    ///
3106    /// ```rust
3107    /// use tenferro_tensor::TypedTensorView;
3108    ///
3109    /// let data = [1_i32, 2];
3110    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3111    /// assert!(view.layout().is_compact_col_major().unwrap());
3112    /// # Ok::<(), tenferro_tensor::Error>(())
3113    /// ```
3114    pub fn layout(&self) -> &TensorLayout<R> {
3115        &self.layout
3116    }
3117
3118    /// Return placement metadata for this view.
3119    ///
3120    /// # Examples
3121    ///
3122    /// ```rust
3123    /// use tenferro_tensor::{MemoryKind, TypedTensorView};
3124    ///
3125    /// let data = [1_i32];
3126    /// let view = TypedTensorView::from_slice(vec![1], vec![1], 0, &data)?;
3127    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
3128    /// # Ok::<(), tenferro_tensor::Error>(())
3129    /// ```
3130    pub fn placement(&self) -> &Placement {
3131        &self.placement
3132    }
3133
3134    /// Return the backend allocation for backend integrations.
3135    #[doc(hidden)]
3136    pub fn backing_len(&self) -> usize {
3137        self.buffer.len()
3138    }
3139
3140    /// Return the backend allocation for backend integrations.
3141    #[doc(hidden)]
3142    pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>> {
3143        match &self.buffer {
3144            TensorStorageRef::Host(_) => None,
3145            TensorStorageRef::Backend(buffer) => Some(*buffer),
3146            TensorStorageRef::Root(_) => {
3147                self.root
3148                    .as_ref()?
3149                    .backend_buffer()
3150                    .and_then(|buffer| match buffer {
3151                        StorageBuffer::Host(_) => None,
3152                        StorageBuffer::Backend(buffer) => Some(buffer.as_ref()),
3153                    })
3154            }
3155        }
3156    }
3157
3158    /// Return the provider family for this view when it is backend-owned.
3159    #[doc(hidden)]
3160    pub fn backend_family(&self) -> Option<&'static str>
3161    where
3162        T: TensorScalar + 'static,
3163    {
3164        self.root
3165            .as_ref()
3166            .and_then(|root| {
3167                root.backend_allocation()
3168                    .map(|_| root.provider_kind().as_str())
3169            })
3170            .or_else(|| self.backend_buffer().map(|buffer| buffer.backend_family()))
3171    }
3172
3173    /// Return the shared allocation domain for this view when backend-owned.
3174    #[doc(hidden)]
3175    pub fn allocation_domain(&self) -> Option<AllocationDomainId>
3176    where
3177        T: TensorScalar + 'static,
3178    {
3179        self.root
3180            .as_ref()
3181            .and_then(|root| root.backend_identity().map(|(domain, _)| domain))
3182            .or_else(|| {
3183                self.backend_buffer()
3184                    .and_then(|buffer| buffer.allocation_domain())
3185            })
3186    }
3187
3188    /// Return the physical allocation identity for this view when backend-owned.
3189    #[doc(hidden)]
3190    pub fn allocation_id(&self) -> Option<AllocationId>
3191    where
3192        T: TensorScalar + 'static,
3193    {
3194        self.root
3195            .as_ref()
3196            .and_then(|root| root.backend_identity().map(|(_, id)| id))
3197            .or_else(|| {
3198                self.backend_buffer()
3199                    .and_then(|buffer| buffer.allocation_id())
3200            })
3201    }
3202
3203    /// Prepare this backend view for one provider-native read binding.
3204    #[doc(hidden)]
3205    pub fn prepare_device_read(
3206        &self,
3207        op: &'static str,
3208    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
3209    where
3210        T: TensorScalar + 'static,
3211    {
3212        if let Some(root) = &self.root {
3213            return root
3214                .prepare_device_read_for_layout(&self.layout)
3215                .map_err(|error| crate::Error::runtime_state_source(op, error));
3216        }
3217        let buffer = self.backend_buffer().ok_or_else(|| {
3218            crate::Error::runtime_state_source(
3219                op,
3220                crate::AccessError::Unsupported { backend: "host" },
3221            )
3222        })?;
3223        prepare_backend_access(buffer, &self.layout, op)
3224    }
3225
3226    /// Compute the physical element offset for a logical index.
3227    ///
3228    /// # Examples
3229    ///
3230    /// ```rust
3231    /// use tenferro_tensor::TypedTensorView;
3232    ///
3233    /// let data = [1_i32, 2, 3];
3234    /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
3235    /// assert_eq!(view.linear_offset(&[2]), Some(0));
3236    /// # Ok::<(), tenferro_tensor::Error>(())
3237    /// ```
3238    pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
3239        checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
3240    }
3241
3242    /// Compute the physical element offset for a logical index, returning a typed error.
3243    ///
3244    /// # Examples
3245    ///
3246    /// ```rust
3247    /// use tenferro_tensor::TypedTensorView;
3248    ///
3249    /// let data = [1_i32, 2, 3];
3250    /// let view = TypedTensorView::from_slice([3], [-1], 2, &data)?;
3251    /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
3252    /// # Ok::<(), tenferro_tensor::Error>(())
3253    /// ```
3254    ///
3255    /// # Errors
3256    ///
3257    /// Returns [`crate::Error::Validation`] with
3258    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
3259    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
3260    /// when an index is outside its axis extent, or
3261    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
3262    /// arithmetic overflows.
3263    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
3264        checked_view_offset_result(
3265            self.shape(),
3266            self.strides(),
3267            self.offset(),
3268            indices,
3269            "TypedTensorView::layout_linear_offset",
3270        )
3271    }
3272
3273    /// Return whether this view is compact column-major.
3274    ///
3275    /// # Examples
3276    ///
3277    /// ```rust
3278    /// use tenferro_tensor::TypedTensorView;
3279    ///
3280    /// let data = [1_i32, 2];
3281    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3282    /// assert!(view.is_col_major_contiguous()?);
3283    /// # Ok::<(), tenferro_tensor::Error>(())
3284    /// ```
3285    ///
3286    /// # Errors
3287    ///
3288    /// Returns [`crate::Error::Validation`] with
3289    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] if compactness
3290    /// arithmetic overflows.
3291    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
3292        self.layout
3293            .is_compact_col_major()
3294            .map_err(|err| tensor_layout_error("TypedTensorView::is_col_major_contiguous", err))
3295    }
3296
3297    /// Return a compact string summary of this view's layout metadata.
3298    ///
3299    /// # Examples
3300    ///
3301    /// ```rust
3302    /// use tenferro_tensor::TypedTensorView;
3303    ///
3304    /// let data = [1_i32, 2];
3305    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3306    /// assert!(view.layout_summary().contains("shape=[2]"));
3307    /// # Ok::<(), tenferro_tensor::Error>(())
3308    /// ```
3309    pub fn layout_summary(&self) -> String {
3310        layout_summary(self.shape(), self.strides(), self.offset())
3311    }
3312
3313    /// Assert this view is compact column-major.
3314    ///
3315    /// # Examples
3316    ///
3317    /// ```rust
3318    /// use tenferro_tensor::TypedTensorView;
3319    ///
3320    /// let data = [1_i32, 2];
3321    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3322    /// view.assert_col_major_contiguous()?;
3323    /// # Ok::<(), tenferro_tensor::Error>(())
3324    /// ```
3325    ///
3326    /// # Errors
3327    ///
3328    /// Returns [`crate::Error::Validation`] with
3329    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
3330    /// compactness arithmetic overflows, or
3331    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
3332    /// view is not compact column-major.
3333    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
3334        assert_layout_col_major_contiguous(
3335            self.is_col_major_contiguous()?,
3336            self.shape(),
3337            self.strides(),
3338            self.offset(),
3339            "TypedTensorView::assert_col_major_contiguous",
3340        )
3341    }
3342
3343    /// Borrow one host element by logical index.
3344    ///
3345    /// Returns `None` for out-of-bounds indices and backend buffers.
3346    ///
3347    /// # Examples
3348    ///
3349    /// ```rust
3350    /// use tenferro_tensor::TypedTensorView;
3351    ///
3352    /// let data = [1_i32, 2];
3353    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3354    /// assert_eq!(view.get(&[1]), Some(&2));
3355    /// # Ok::<(), tenferro_tensor::Error>(())
3356    /// ```
3357    pub fn get(&self, indices: &[usize]) -> Option<&T> {
3358        let offset = self.linear_offset(indices)?;
3359        match &self.buffer {
3360            TensorStorageRef::Host(data) => data.get(offset),
3361            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => None,
3362        }
3363    }
3364
3365    /// Borrow the contiguous host slice covered by this view.
3366    ///
3367    /// Returns an explicit error for backend buffers and for non-contiguous
3368    /// layouts. This method never downloads or materializes backend data.
3369    ///
3370    /// # Examples
3371    ///
3372    /// ```rust
3373    /// use tenferro_tensor::TypedTensorView;
3374    ///
3375    /// let data = [1_i32, 2, 3];
3376    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?;
3377    /// assert_eq!(view.as_slice()?, &[2, 3]);
3378    /// # Ok::<(), tenferro_tensor::Error>(())
3379    /// ```
3380    ///
3381    /// # Errors
3382    ///
3383    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
3384    /// buffer, [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]
3385    /// when the layout is not compact column-major,
3386    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the view
3387    /// offset is negative, or [`crate::Error::Validation`] with
3388    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] or
3389    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
3390    /// requested host range is invalid.
3391    pub fn as_slice(&self) -> crate::Result<&'a [T]> {
3392        let data = match &self.buffer {
3393            TensorStorageRef::Host(data) => data,
3394            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3395                return Err(crate::Error::runtime_state(
3396                    "TypedTensorView::as_slice",
3397                    "backend buffers cannot be inspected as host slices; download explicitly first",
3398                ))
3399            }
3400        };
3401        contiguous_layout_slice(self.layout(), data, "TypedTensorView::as_slice")
3402    }
3403
3404    /// Borrow the compact logical region through a scoped host mapping.
3405    ///
3406    /// Backend integrations must validate placement and allocation-domain ownership
3407    /// before mapping. The guard cannot escape the callback; no transfer is performed.
3408    ///
3409    /// # Errors
3410    ///
3411    /// Returns a host-access or provider-preparation error when mapping fails,
3412    /// or a validation/unsupported error for a non-contiguous or invalid layout.
3413    #[doc(hidden)]
3414    pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
3415    where
3416        T: TensorScalar + 'static,
3417    {
3418        const OP: &str = "TypedTensorView::with_host_read";
3419        if !self.is_col_major_contiguous()? {
3420            return Err(crate::Error::unsupported(
3421                OP,
3422                "host guard access requires a compact descriptor",
3423            ));
3424        }
3425        if let Some(buffer) = self.backend_buffer() {
3426            let guard = buffer
3427                .map_read()
3428                .map_err(|error| crate::Error::host_access(OP, error))?;
3429            return Ok(f(contiguous_layout_slice(self.layout(), &guard, OP)?));
3430        }
3431        if let Some(root) = &self.root {
3432            let prepared = root
3433                .prepare_host_read_for_layout(self.layout())
3434                .map_err(|error| crate::Error::runtime_state_source(OP, error))?;
3435            let slice = prepared.as_slice().ok_or_else(|| {
3436                crate::Error::unsupported(OP, "host guard access requires a compact descriptor")
3437            })?;
3438            return Ok(f(slice));
3439        }
3440        Ok(f(self.as_slice()?))
3441    }
3442
3443    /// Explicitly duplicate a compact host view into a new owner.
3444    ///
3445    /// Backend views require an explicit provider canonicalization or download
3446    /// boundary; this method never transfers or materializes them implicitly.
3447    ///
3448    /// # Examples
3449    ///
3450    /// ```
3451    /// use tenferro_tensor::TypedTensorView;
3452    ///
3453    /// let data = [1_i32, 2];
3454    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3455    /// let copy = view.duplicate()?;
3456    /// assert_eq!(copy.as_slice()?, &[1, 2]);
3457    /// # Ok::<(), tenferro_tensor::Error>(())
3458    /// ```
3459    ///
3460    /// # Errors
3461    ///
3462    /// Returns [`crate::Error::HostAccess`] or
3463    /// [`ValidationError::NonContiguousViewAsSlice`] when the view is backend
3464    /// owned or not contiguous, and [`ValidationError::InvalidArgument`] when
3465    /// the static-rank shape cannot be reconstructed.
3466    pub fn duplicate(&self) -> crate::Result<TypedTensor<T, R>>
3467    where
3468        T: Clone,
3469    {
3470        let data = self.as_slice()?.to_vec();
3471        let shape = R::shape_from_vec(shape_vec(self.shape()))
3472            .map_err(|err| tensor_layout_error("TypedTensorView::duplicate", err))?;
3473        let mut tensor = TypedTensor::from_vec_col_major(shape, data)?;
3474        tensor.set_placement(self.placement.clone());
3475        Ok(tensor)
3476    }
3477
3478    /// Explicitly copy a host view, including strided/offset views, to a compact owner.
3479    ///
3480    /// # Examples
3481    /// ```
3482    /// use tenferro_tensor::{Rank, TypedTensorView};
3483    /// let storage = [1, 4, 2, 5, 3, 6];
3484    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked(
3485    ///     [2, 3], [1, 2], 0, &storage,
3486    /// )?;
3487    /// let transposed = view.transpose_view([1, 0])?;
3488    /// assert_eq!(transposed.to_col_major()?.as_slice()?, &[1, 2, 3, 4, 5, 6]);
3489    /// # Ok::<(), tenferro_tensor::Error>(())
3490    /// ```
3491    /// # Errors
3492    /// Backend-only storage returns [`crate::Error::RuntimeState`]; invalid
3493    /// layout or rank conversion returns [`crate::Error::Validation`].
3494    pub fn to_col_major(&self) -> crate::Result<TypedTensor<T, R>>
3495    where
3496        T: Clone,
3497    {
3498        let data = self.host_storage()?;
3499        let mut coordinates = ShapeVec::from_elem(0, self.shape().len());
3500        let element_count = self.n_elements();
3501        let mut copied = Vec::with_capacity(element_count);
3502        for _ in 0..element_count {
3503            let offset = self.layout_linear_offset(&coordinates)?;
3504            let value = data.get(offset).ok_or_else(|| {
3505                crate::Error::validation(
3506                    "TypedTensorView::to_col_major",
3507                    ValidationError::ViewOutOfBounds,
3508                )
3509            })?;
3510            copied.push(value.clone());
3511            for axis in 0..coordinates.len() {
3512                coordinates[axis] += 1;
3513                if coordinates[axis] < self.shape()[axis] {
3514                    break;
3515                }
3516                coordinates[axis] = 0;
3517            }
3518        }
3519        let shape = R::shape_from_vec(shape_vec(self.shape()))
3520            .map_err(|err| tensor_layout_error("TypedTensorView::to_col_major", err))?;
3521        let mut tensor = TypedTensor::from_vec_col_major(shape, copied)?;
3522        tensor.set_placement(self.placement.clone());
3523        Ok(tensor)
3524    }
3525
3526    /// Return a metadata-only axis permutation.
3527    ///
3528    /// # Examples
3529    ///
3530    /// ```rust
3531    /// use tenferro_tensor::{Rank, TypedTensorView};
3532    ///
3533    /// let data = [1_i32, 2, 3, 4, 5, 6];
3534    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 3], [1, 2], 0, &data)?;
3535    /// let transposed = view.transpose_view([1, 0])?;
3536    /// assert_eq!(transposed.shape(), &[3, 2]);
3537    /// # Ok::<(), tenferro_tensor::Error>(())
3538    /// ```
3539    /// # Errors
3540    ///
3541    /// Returns [`crate::Error::Validation`] with
3542    /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
3543    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
3544    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
3545    /// not a valid permutation of the view rank.
3546    pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
3547        let layout = self
3548            .layout
3549            .transpose_view(axes)
3550            .map_err(|err| tensor_layout_error("TypedTensorView::transpose_view", err))?;
3551        Ok(Self {
3552            buffer: self.buffer.clone(),
3553            root: self.root.clone(),
3554            layout,
3555            placement: self.placement.clone(),
3556            _representation: std::marker::PhantomData,
3557        })
3558    }
3559
3560    /// Return a metadata-only slice using one [`StridedSliceSpec`] per axis.
3561    ///
3562    /// # Examples
3563    ///
3564    /// ```rust
3565    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
3566    ///
3567    /// let data = [1_i32, 2, 3];
3568    /// let view = TypedTensorView::from_slice(vec![3], vec![1], 0, &data)?;
3569    /// let reversed = view.slice_view(&[StridedSliceSpec::reverse()])?;
3570    /// assert_eq!(reversed.get(&[0]), Some(&3));
3571    /// # Ok::<(), tenferro_tensor::Error>(())
3572    /// ```
3573    /// # Errors
3574    ///
3575    /// Returns [`crate::Error::Validation`] with
3576    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
3577    /// count differs from the view rank,
3578    /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
3579    /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
3580    /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3581    /// for slice arithmetic overflow, or
3582    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3583    /// resulting layout exceeds the backing buffer.
3584    pub fn slice_view(&self, slices: &[StridedSliceSpec]) -> crate::Result<Self> {
3585        let specs = core_slice_specs(slices, self.shape(), "TypedTensorView::slice_view")?;
3586        let layout = self
3587            .layout
3588            .slice_view(specs, self.buffer.len())
3589            .map_err(|err| tensor_layout_error("TypedTensorView::slice_view", err))?;
3590        Ok(Self {
3591            buffer: self.buffer.clone(),
3592            root: self.root.clone(),
3593            layout,
3594            placement: self.placement.clone(),
3595            _representation: std::marker::PhantomData,
3596        })
3597    }
3598
3599    /// Return a metadata-only slice along one axis.
3600    ///
3601    /// # Examples
3602    ///
3603    /// ```rust
3604    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
3605    ///
3606    /// let data = [1_i32, 2, 3, 4];
3607    /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
3608    /// assert_eq!(view.slice_axis_view(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
3609    /// # Ok::<(), tenferro_tensor::Error>(())
3610    /// ```
3611    /// # Errors
3612    ///
3613    /// Returns [`crate::Error::Validation`] with
3614    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
3615    /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
3616    /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
3617    /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3618    /// for slice arithmetic overflow, or
3619    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3620    /// resulting layout exceeds the backing buffer.
3621    pub fn slice_axis_view(&self, axis: usize, slice: StridedSliceSpec) -> crate::Result<Self> {
3622        let slices = slice_axis_specs(
3623            self.shape().len(),
3624            axis,
3625            slice,
3626            "TypedTensorView::slice_axis_view",
3627        )?;
3628        self.slice_view(&slices)
3629    }
3630
3631    /// Return a metadata-only dynamic-rank reshape for contiguous column-major views.
3632    ///
3633    /// # Examples
3634    ///
3635    /// ```rust
3636    /// use tenferro_tensor::TypedTensorView;
3637    ///
3638    /// let data = [1_i32, 2, 3, 4];
3639    /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
3640    /// assert_eq!(view.reshape_view(&[4])?.shape(), &[4]);
3641    /// # Ok::<(), tenferro_tensor::Error>(())
3642    /// ```
3643    /// # Errors
3644    ///
3645    /// Returns [`crate::Error::Validation`] with
3646    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
3647    /// the source is not compact column-major,
3648    /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
3649    /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
3650    /// records the counts) when element counts differ,
3651    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
3652    /// arithmetic overflow, or
3653    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3654    /// reshaped view exceeds the backing buffer.
3655    pub fn reshape_view(
3656        &self,
3657        shape: &[usize],
3658    ) -> crate::Result<TypedTensorView<'a, T, DynRank, D>> {
3659        let layout = reshape_layout_dyn(
3660            &self.layout,
3661            shape,
3662            self.buffer.len(),
3663            "TypedTensorView::reshape_view",
3664        )?;
3665        Ok(TypedTensorView {
3666            buffer: self.buffer.clone(),
3667            root: self.root.as_ref().map(GroupReadView::clone_dyn),
3668            layout,
3669            placement: self.placement.clone(),
3670            _representation: std::marker::PhantomData,
3671        })
3672    }
3673}
3674
3675impl<'a, R: TensorRank> TypedTensorView<'a, Complex32, R> {
3676    /// Borrow this complex view as an interleaved real view without copying.
3677    ///
3678    /// The result has dynamic rank because reinterpretation prepends the
3679    /// component axis `[2, ...]`. Only `Complex32 <-> f32` is sealed in this
3680    /// API; this is representation reinterpretation, not numeric conversion.
3681    ///
3682    /// # Examples
3683    ///
3684    /// ```
3685    /// use tenferro_tensor::{Complex32, TypedTensorView};
3686    ///
3687    /// let data = [Complex32::new(1.0, 2.0)];
3688    /// let view = TypedTensorView::from_col_major(&[1], &data)?;
3689    /// let real = view.as_real_view()?;
3690    /// assert_eq!(real.shape(), &[2, 1]);
3691    /// assert_eq!(real.as_slice()?, &[1.0_f32, 2.0]);
3692    /// # Ok::<(), tenferro_tensor::Error>(())
3693    /// ```
3694    ///
3695    /// # Errors
3696    ///
3697    /// Returns an error when the view layout is not a valid sealed
3698    /// representation or when backend reinterpretation is unsupported.
3699    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'a, f32, DynRank>> {
3700        let op = "TypedTensorView::as_real_view";
3701        validate_representation_pair(op, DType::C32, DType::F32)?;
3702        let layout = reinterpret_complex_to_real_layout(
3703            self.shape(),
3704            self.strides(),
3705            self.offset(),
3706            self.buffer.len(),
3707            op,
3708        )?;
3709        let buffer = match &self.buffer {
3710            TensorStorageRef::Host(data) => {
3711                TensorStorageRef::Host(reinterpret_host_slice::<Complex32, f32>(data, op)?)
3712            }
3713            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3714                return Err(crate::Error::unsupported(
3715                    op,
3716                    "backend representation reinterpretation is enabled by the provider phases",
3717                ))
3718            }
3719        };
3720        Ok(TypedTensorView {
3721            buffer,
3722            root: None,
3723            layout,
3724            placement: self.placement.clone(),
3725            _representation: std::marker::PhantomData,
3726        })
3727    }
3728}
3729
3730impl<'a, R: TensorRank> TypedTensorView<'a, Complex64, R> {
3731    /// Borrow this complex view as an interleaved real view without copying.
3732    ///
3733    /// The result has dynamic rank because reinterpretation prepends the
3734    /// component axis `[2, ...]`. Only `Complex64 <-> f64` is sealed in this
3735    /// API; this is representation reinterpretation, not numeric conversion.
3736    ///
3737    /// # Examples
3738    ///
3739    /// ```
3740    /// use tenferro_tensor::{Complex64, TypedTensorView};
3741    ///
3742    /// let data = [Complex64::new(1.0, 2.0)];
3743    /// let view = TypedTensorView::from_col_major(&[1], &data)?;
3744    /// let real = view.as_real_view()?;
3745    /// assert_eq!(real.shape(), &[2, 1]);
3746    /// assert_eq!(real.as_slice()?, &[1.0_f64, 2.0]);
3747    /// # Ok::<(), tenferro_tensor::Error>(())
3748    /// ```
3749    ///
3750    /// # Errors
3751    ///
3752    /// Returns an error when the view layout is not a valid sealed
3753    /// representation or when backend reinterpretation is unsupported.
3754    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'a, f64, DynRank>> {
3755        let op = "TypedTensorView::as_real_view";
3756        validate_representation_pair(op, DType::C64, DType::F64)?;
3757        let layout = reinterpret_complex_to_real_layout(
3758            self.shape(),
3759            self.strides(),
3760            self.offset(),
3761            self.buffer.len(),
3762            op,
3763        )?;
3764        let buffer = match &self.buffer {
3765            TensorStorageRef::Host(data) => {
3766                TensorStorageRef::Host(reinterpret_host_slice::<Complex64, f64>(data, op)?)
3767            }
3768            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3769                return Err(crate::Error::unsupported(
3770                    op,
3771                    "backend representation reinterpretation is enabled by the provider phases",
3772                ))
3773            }
3774        };
3775        Ok(TypedTensorView {
3776            buffer,
3777            root: None,
3778            layout,
3779            placement: self.placement.clone(),
3780            _representation: std::marker::PhantomData,
3781        })
3782    }
3783}
3784
3785impl<'a, R: TensorRank> TypedTensorView<'a, f32, R> {
3786    /// Borrow this interleaved real view as a complex view without copying.
3787    ///
3788    /// The source must have a leading extent and stride of `2` and `1`, and
3789    /// every remaining stride plus the offset must be divisible by `2`.
3790    ///
3791    /// # Examples
3792    ///
3793    /// ```
3794    /// use tenferro_tensor::{Complex32, TypedTensorView};
3795    ///
3796    /// let data = [1.0_f32, 2.0];
3797    /// let view = TypedTensorView::from_col_major(&[2, 1], &data)?;
3798    /// let complex = view.as_complex_view()?;
3799    /// assert_eq!(complex.shape(), &[1]);
3800    /// assert_eq!(complex.as_slice()?, &[Complex32::new(1.0, 2.0)]);
3801    /// # Ok::<(), tenferro_tensor::Error>(())
3802    /// ```
3803    ///
3804    /// # Errors
3805    ///
3806    /// Returns an error when the view layout is not a valid sealed
3807    /// representation or when backend reinterpretation is unsupported.
3808    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'a, Complex32, DynRank>> {
3809        let op = "TypedTensorView::as_complex_view";
3810        validate_representation_pair(op, DType::F32, DType::C32)?;
3811        let layout = reinterpret_real_to_complex_layout(
3812            self.shape(),
3813            self.strides(),
3814            self.offset(),
3815            self.buffer.len(),
3816            op,
3817        )?;
3818        let buffer = match &self.buffer {
3819            TensorStorageRef::Host(data) => {
3820                TensorStorageRef::Host(reinterpret_host_slice::<f32, Complex32>(data, op)?)
3821            }
3822            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3823                return Err(crate::Error::unsupported(
3824                    op,
3825                    "backend representation reinterpretation is enabled by the provider phases",
3826                ))
3827            }
3828        };
3829        Ok(TypedTensorView {
3830            buffer,
3831            root: None,
3832            layout,
3833            placement: self.placement.clone(),
3834            _representation: std::marker::PhantomData,
3835        })
3836    }
3837}
3838
3839impl<'a, R: TensorRank> TypedTensorView<'a, f64, R> {
3840    /// Borrow this interleaved real view as a complex view without copying.
3841    ///
3842    /// The source must have a leading extent and stride of `2` and `1`, and
3843    /// every remaining stride plus the offset must be divisible by `2`.
3844    ///
3845    /// # Examples
3846    ///
3847    /// ```
3848    /// use tenferro_tensor::{Complex64, TypedTensorView};
3849    ///
3850    /// let data = [1.0_f64, 2.0];
3851    /// let view = TypedTensorView::from_col_major(&[2, 1], &data)?;
3852    /// let complex = view.as_complex_view()?;
3853    /// assert_eq!(complex.shape(), &[1]);
3854    /// assert_eq!(complex.as_slice()?, &[Complex64::new(1.0, 2.0)]);
3855    /// # Ok::<(), tenferro_tensor::Error>(())
3856    /// ```
3857    ///
3858    /// # Errors
3859    ///
3860    /// Returns an error when the view layout is not a valid sealed
3861    /// representation or when backend reinterpretation is unsupported.
3862    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'a, Complex64, DynRank>> {
3863        let op = "TypedTensorView::as_complex_view";
3864        validate_representation_pair(op, DType::F64, DType::C64)?;
3865        let layout = reinterpret_real_to_complex_layout(
3866            self.shape(),
3867            self.strides(),
3868            self.offset(),
3869            self.buffer.len(),
3870            op,
3871        )?;
3872        let buffer = match &self.buffer {
3873            TensorStorageRef::Host(data) => {
3874                TensorStorageRef::Host(reinterpret_host_slice::<f64, Complex64>(data, op)?)
3875            }
3876            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3877                return Err(crate::Error::unsupported(
3878                    op,
3879                    "backend representation reinterpretation is enabled by the provider phases",
3880                ))
3881            }
3882        };
3883        Ok(TypedTensorView {
3884            buffer,
3885            root: None,
3886            layout,
3887            placement: self.placement.clone(),
3888            _representation: std::marker::PhantomData,
3889        })
3890    }
3891}
3892
3893/// Mutable borrowed view of typed tensor storage with arbitrary strides.
3894///
3895/// # Examples
3896///
3897/// ```rust
3898/// use tenferro_tensor::TypedTensorViewMut;
3899///
3900/// let mut data = [1_i32, 2, 3];
3901/// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
3902/// *view.get_mut(&[2]).unwrap() = 10;
3903/// assert_eq!(view.as_read_only().get(&[2]), Some(&10));
3904/// # Ok::<(), tenferro_tensor::Error>(())
3905/// ```
3906/// Exclusive typed view of one tensor representation.
3907///
3908/// As with [`TypedTensorView`], the buffer stays concrete so the view keeps its
3909/// lifetime covariance, and `D` marks which representation produced the view.
3910pub struct TypedTensorViewMut<'a, T, R: TensorRank = DynRank, D: Representation = Dynamic> {
3911    buffer: TensorStorageRefMut<'a, T>,
3912    root: Option<GroupWriteView<'a, T, R>>,
3913    layout: TensorLayout<R>,
3914    placement: Placement,
3915    _representation: std::marker::PhantomData<D>,
3916}
3917
3918impl<'a, T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensorViewMut<'a, T, R, D> {
3919    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3920        formatter
3921            .debug_struct("TypedTensorViewMut")
3922            .field("shape", &self.layout.shape())
3923            .field("placement", &self.placement)
3924            .finish_non_exhaustive()
3925    }
3926}
3927
3928/// Pair of mutable tensor views returned by disjoint multi-slice operations.
3929///
3930/// # Examples
3931///
3932/// ```rust
3933/// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut, TypedTensorViewMutSplit};
3934///
3935/// let mut data = [1_i32, 2, 3, 4];
3936/// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
3937/// let pair: TypedTensorViewMutSplit<'_, i32> = view
3938///     .try_multi_slice_mut(
3939///         &[StridedSliceSpec::new(0, Some(2), 1)],
3940///         &[StridedSliceSpec::new(2, Some(4), 1)],
3941///     )
3942///     ?
3943///     .unwrap();
3944/// assert_eq!(pair.0.shape(), &[2]);
3945/// assert_eq!(pair.1.shape(), &[2]);
3946/// # Ok::<(), tenferro_tensor::Error>(())
3947/// ```
3948pub type TypedTensorViewMutSplit<'a, T, R = DynRank> =
3949    (TypedTensorViewMut<'a, T, R>, TypedTensorViewMut<'a, T, R>);
3950
3951impl<'a, T: 'static> TypedTensorViewMut<'a, T, DynRank> {
3952    /// Create a mutable dynamic-rank view over compact column-major host data.
3953    ///
3954    /// # Examples
3955    ///
3956    /// ```rust
3957    /// use tenferro_tensor::TypedTensorViewMut;
3958    ///
3959    /// let mut data = [1_i32, 2, 3, 4];
3960    /// let view = TypedTensorViewMut::from_col_major(&[2, 2], &mut data)?;
3961    /// assert_eq!(view.strides(), &[1, 2]);
3962    /// # Ok::<(), tenferro_tensor::Error>(())
3963    /// ```
3964    /// # Errors
3965    ///
3966    /// Returns [`crate::Error::Validation`] with
3967    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
3968    /// shape or offset arithmetic overflow, or
3969    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3970    /// compact shape reaches beyond `data`.
3971    pub fn from_col_major(shape: &[usize], data: &'a mut [T]) -> crate::Result<Self> {
3972        let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
3973            .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_col_major", err))?;
3974        Self::from_buffer_ref_mut(
3975            shape_vec(layout.shape()),
3976            stride_vec(layout.strides()),
3977            layout.offset(),
3978            TensorStorageRefMut::Host(data),
3979            default_placement(),
3980            "TypedTensorViewMut::from_col_major",
3981        )
3982    }
3983
3984    /// Create a mutable host view from explicit layout metadata.
3985    ///
3986    /// Layouts where distinct logical elements can alias the same physical
3987    /// element are rejected.
3988    ///
3989    /// # Examples
3990    ///
3991    /// ```rust
3992    /// use tenferro_tensor::TypedTensorViewMut;
3993    ///
3994    /// let mut data = [1_i32, 2];
3995    /// assert!(TypedTensorViewMut::from_slice(vec![2], vec![0], 0, &mut data).is_err());
3996    /// ```
3997    /// # Errors
3998    ///
3999    /// Returns [`crate::Error::Validation`] with
4000    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
4001    /// `strides` have different ranks,
4002    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4003    /// layout reaches beyond `data`,
4004    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4005    /// logical elements alias, or
4006    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4007    /// arithmetic overflow.
4008    pub fn from_slice(
4009        shape: impl AsRef<[usize]>,
4010        strides: impl AsRef<[isize]>,
4011        offset: isize,
4012        data: &'a mut [T],
4013    ) -> crate::Result<Self> {
4014        Self::from_buffer_ref_mut(
4015            shape_vec(shape.as_ref()),
4016            stride_vec(strides.as_ref()),
4017            offset,
4018            TensorStorageRefMut::Host(data),
4019            default_placement(),
4020            "TypedTensorViewMut::from_slice",
4021        )
4022    }
4023}
4024
4025impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4026    /// Create a rank-generic mutable host view from explicit layout metadata.
4027    ///
4028    /// # Examples
4029    ///
4030    /// ```rust
4031    /// use tenferro_tensor::{Rank, TypedTensorViewMut};
4032    ///
4033    /// let mut data = [1_i32, 2, 3, 4];
4034    /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
4035    /// assert_eq!(view.shape(), &[2, 2]);
4036    /// # Ok::<(), tenferro_tensor::Error>(())
4037    /// ```
4038    /// # Errors
4039    ///
4040    /// Returns [`crate::Error::Validation`] with
4041    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
4042    /// rank does not match `shape` or `strides`,
4043    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4044    /// layout reaches beyond `data`,
4045    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4046    /// logical elements alias, or
4047    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4048    /// arithmetic overflow.
4049    pub fn from_slice_ranked(
4050        shape: impl Into<R::Shape>,
4051        strides: impl Into<R::Strides>,
4052        offset: isize,
4053        data: &'a mut [T],
4054    ) -> crate::Result<Self> {
4055        Self::from_buffer_ref_mut(
4056            shape,
4057            strides,
4058            offset,
4059            TensorStorageRefMut::Host(data),
4060            default_placement(),
4061            "TypedTensorViewMut::from_slice_ranked",
4062        )
4063    }
4064}
4065
4066impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4067    fn from_buffer_ref_mut(
4068        shape: impl Into<R::Shape>,
4069        strides: impl Into<R::Strides>,
4070        offset: isize,
4071        buffer: TensorStorageRefMut<'a, T>,
4072        placement: Placement,
4073        op: &'static str,
4074    ) -> crate::Result<Self> {
4075        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
4076            .map_err(|err| tensor_layout_error(op, err))?;
4077        layout
4078            .validate_mutable_no_overlap()
4079            .map_err(|err| tensor_layout_error(op, err))?;
4080        Ok(Self {
4081            buffer,
4082            root: None,
4083            layout,
4084            placement,
4085            _representation: std::marker::PhantomData,
4086        })
4087    }
4088}
4089
4090impl<'a, T: 'static, R: TensorRank> TypedTensorViewMut<'a, T, R, Host> {
4091    /// Create a representation-marked mutable view over an explicit host layout.
4092    ///
4093    /// # Examples
4094    ///
4095    /// ```rust
4096    /// use tenferro_tensor::{DynRank, Host, TypedTensorViewMut};
4097    ///
4098    /// let mut data = [1_i32, 2];
4099    /// let mut view: TypedTensorViewMut<'_, i32, DynRank, Host> =
4100    ///     TypedTensorViewMut::from_host_slice(vec![2], vec![1], 0, &mut data)?;
4101    /// view.slice_view(&[tenferro_tensor::StridedSliceSpec::all()])?;
4102    /// # Ok::<(), tenferro_tensor::Error>(())
4103    /// ```
4104    ///
4105    /// # Errors
4106    ///
4107    /// Returns [`crate::Error::Validation`] with
4108    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `strides`
4109    /// has a different rank, [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`]
4110    /// when logical elements alias, or
4111    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] /
4112    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
4113    /// reachable layout leaves `data` or overflows.
4114    pub fn from_host_slice(
4115        shape: impl Into<R::Shape>,
4116        strides: impl Into<R::Strides>,
4117        offset: isize,
4118        data: &'a mut [T],
4119    ) -> crate::Result<Self> {
4120        // Built here rather than through the shared helper so that the Host
4121        // marker can only ever be paired with a host slice.
4122        let buffer = TensorStorageRefMut::Host(data);
4123        let layout =
4124            TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
4125                .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_host_slice", err))?;
4126        layout
4127            .validate_mutable_no_overlap()
4128            .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_host_slice", err))?;
4129        Ok(Self {
4130            buffer,
4131            root: None,
4132            layout,
4133            placement: default_placement(),
4134            _representation: std::marker::PhantomData,
4135        })
4136    }
4137
4138    /// Exclusively borrow the host elements without a runtime check.
4139    ///
4140    /// # Examples
4141    ///
4142    /// ```
4143    /// use tenferro_tensor::{DynRank, Host, TypedTensorViewMut};
4144    ///
4145    /// let mut data = [1_i32, 2];
4146    /// let mut view: TypedTensorViewMut<'_, i32, DynRank, Host> =
4147    ///     TypedTensorViewMut::from_host_slice(vec![2], vec![1], 0, &mut data)?;
4148    /// view.as_host_slice_mut()[0] = 5;
4149    /// assert_eq!(data, [5, 2]);
4150    /// # Ok::<(), tenferro_tensor::Error>(())
4151    /// ```
4152    ///
4153    /// # Panics
4154    ///
4155    /// Panics only if a `Host`-marked view was built from non-host storage,
4156    /// which no constructor in this crate does.
4157    pub fn as_host_slice_mut(&mut self) -> &mut [T] {
4158        match &mut self.buffer {
4159            TensorStorageRefMut::Host(data) => data,
4160            // INVARIANT: `Host`-marked views are constructed only by
4161            // `from_host_slice` and by `TypedTensor<_, _, Host>::as_view_mut`.
4162            TensorStorageRefMut::Backend(_) => {
4163                unreachable!("a Host-marked view always borrows host storage")
4164            }
4165        }
4166    }
4167}
4168
4169impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4170    /// Return the logical shape.
4171    ///
4172    /// # Examples
4173    ///
4174    /// ```rust
4175    /// use tenferro_tensor::TypedTensorViewMut;
4176    ///
4177    /// let mut data = [0_i32; 2];
4178    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4179    /// assert_eq!(view.shape(), &[2]);
4180    /// # Ok::<(), tenferro_tensor::Error>(())
4181    /// ```
4182    pub fn shape(&self) -> &[usize] {
4183        self.layout.shape()
4184    }
4185
4186    /// Return the logical rank carried by this mutable view.
4187    ///
4188    /// # Examples
4189    ///
4190    /// ```
4191    /// use tenferro_tensor::TypedTensorViewMut;
4192    ///
4193    /// let mut data = [0_i32; 6];
4194    /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
4195    /// assert_eq!(view.rank(), 2);
4196    /// # Ok::<(), tenferro_tensor::Error>(())
4197    /// ```
4198    pub fn rank(&self) -> usize {
4199        self.shape().len()
4200    }
4201
4202    /// Return strides in element units.
4203    ///
4204    /// # Examples
4205    ///
4206    /// ```rust
4207    /// use tenferro_tensor::TypedTensorViewMut;
4208    ///
4209    /// let mut data = [0_i32; 2];
4210    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![-1], 1, &mut data)?;
4211    /// assert_eq!(view.strides(), &[-1]);
4212    /// # Ok::<(), tenferro_tensor::Error>(())
4213    /// ```
4214    pub fn strides(&self) -> &[isize] {
4215        self.layout.strides()
4216    }
4217
4218    /// Return the physical element offset.
4219    ///
4220    /// # Examples
4221    ///
4222    /// ```rust
4223    /// use tenferro_tensor::TypedTensorViewMut;
4224    ///
4225    /// let mut data = [1_i32, 2];
4226    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 1, &mut data)?;
4227    /// assert_eq!(view.offset(), 1);
4228    /// # Ok::<(), tenferro_tensor::Error>(())
4229    /// ```
4230    pub fn offset(&self) -> isize {
4231        self.layout.offset()
4232    }
4233
4234    /// Return the borrowed host storage backing this view.
4235    ///
4236    /// This exposes the entire backing host allocation, not just the logical
4237    /// slice covered by this view. Use [`TypedTensorViewMut::as_read_only`]
4238    /// with [`TypedTensorView::as_slice`] when the caller needs the contiguous
4239    /// logical region instead.
4240    ///
4241    /// # Examples
4242    ///
4243    /// ```rust
4244    /// use tenferro_tensor::TypedTensorViewMut;
4245    ///
4246    /// let mut data = [1_i32, 2];
4247    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4248    /// assert_eq!(view.host_storage()?, &[1, 2]);
4249    /// # Ok::<(), tenferro_tensor::Error>(())
4250    /// ```
4251    /// # Errors
4252    ///
4253    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
4254    /// buffer; backend storage must be downloaded before host inspection.
4255    pub fn host_storage(&self) -> crate::Result<&[T]> {
4256        match &self.buffer {
4257            TensorStorageRefMut::Host(data) => Ok(data),
4258            TensorStorageRefMut::Backend(_) => Err(crate::Error::runtime_state(
4259                "TypedTensorViewMut::host_storage",
4260                "backend buffers cannot expose host storage; download explicitly first",
4261            )),
4262        }
4263    }
4264
4265    /// Mutably borrow the host storage backing this view.
4266    ///
4267    /// This exposes the entire backing host allocation, not just the logical
4268    /// slice covered by this view. Prefer scalar element accessors when mutating
4269    /// a logical region; tensor-sized copies belong to an active backend.
4270    ///
4271    /// # Examples
4272    ///
4273    /// ```rust
4274    /// use tenferro_tensor::TypedTensorViewMut;
4275    ///
4276    /// let mut data = [1_i32, 2];
4277    /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4278    /// view.host_storage_mut()?[0] = 3;
4279    /// assert_eq!(view.get(&[0]), Some(&3));
4280    /// # Ok::<(), tenferro_tensor::Error>(())
4281    /// ```
4282    /// # Errors
4283    ///
4284    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
4285    /// buffer; backend storage must be downloaded before host inspection.
4286    pub fn host_storage_mut(&mut self) -> crate::Result<&mut [T]> {
4287        match &mut self.buffer {
4288            TensorStorageRefMut::Host(data) => Ok(data),
4289            TensorStorageRefMut::Backend(_) => Err(crate::Error::runtime_state(
4290                "TypedTensorViewMut::host_storage_mut",
4291                "backend buffers cannot expose mutable host storage; download explicitly first",
4292            )),
4293        }
4294    }
4295
4296    /// Return the number of logical elements in this view.
4297    ///
4298    /// # Examples
4299    ///
4300    /// ```rust
4301    /// use tenferro_tensor::TypedTensorViewMut;
4302    ///
4303    /// let mut data = [0_i32; 6];
4304    /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
4305    /// assert_eq!(view.n_elements(), 6);
4306    /// # Ok::<(), tenferro_tensor::Error>(())
4307    /// ```
4308    pub fn n_elements(&self) -> usize {
4309        // Invariant: public mutable view constructors validate logical element count.
4310        match checked_view_element_count(self.shape(), "TypedTensorViewMut::n_elements") {
4311            Ok(n) => n,
4312            Err(err) => {
4313                unreachable!("TypedTensorViewMut layout shape is validated at construction: {err}")
4314            }
4315        }
4316    }
4317
4318    /// Return layout metadata for this view.
4319    ///
4320    /// # Examples
4321    ///
4322    /// ```rust
4323    /// use tenferro_tensor::TypedTensorViewMut;
4324    ///
4325    /// let mut data = [1_i32, 2];
4326    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4327    /// assert!(view.layout().is_compact_col_major().unwrap());
4328    /// # Ok::<(), tenferro_tensor::Error>(())
4329    /// ```
4330    pub fn layout(&self) -> &TensorLayout<R> {
4331        &self.layout
4332    }
4333
4334    /// Return placement metadata for this view.
4335    ///
4336    /// # Examples
4337    ///
4338    /// ```rust
4339    /// use tenferro_tensor::{MemoryKind, TypedTensorViewMut};
4340    ///
4341    /// let mut data = [1_i32];
4342    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4343    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
4344    /// # Ok::<(), tenferro_tensor::Error>(())
4345    /// ```
4346    pub fn placement(&self) -> &Placement {
4347        &self.placement
4348    }
4349
4350    /// Return the backend allocation for backend integrations.
4351    #[doc(hidden)]
4352    pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>> {
4353        match &self.buffer {
4354            TensorStorageRefMut::Host(_) => None,
4355            TensorStorageRefMut::Backend(buffer) => Some(&**buffer),
4356        }
4357    }
4358
4359    /// Prepare this backend view for one provider-native write binding.
4360    #[doc(hidden)]
4361    pub fn prepare_device_write(
4362        &mut self,
4363        op: &'static str,
4364    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
4365    where
4366        T: TensorScalar + 'static,
4367    {
4368        let layout = self.layout.clone();
4369        if self.root.is_none() {
4370            let buffer = self.backend_buffer().ok_or_else(|| {
4371                crate::Error::runtime_state_source(
4372                    op,
4373                    crate::AccessError::Unsupported { backend: "host" },
4374                )
4375            })?;
4376            return prepare_backend_access(buffer, &self.layout, op);
4377        }
4378        let root = self
4379            .root
4380            .as_mut()
4381            .ok_or_else(|| crate::Error::runtime_state(op, "expected a root-backed tensor view"))?;
4382        root.prepare_device_write_for_layout(&layout)
4383            .map_err(|error| crate::Error::runtime_state_source(op, error))
4384    }
4385
4386    /// Compute the physical element offset for a logical index.
4387    ///
4388    /// # Examples
4389    ///
4390    /// ```rust
4391    /// use tenferro_tensor::TypedTensorViewMut;
4392    ///
4393    /// let mut data = [1_i32, 2, 3];
4394    /// let view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
4395    /// assert_eq!(view.linear_offset(&[2]), Some(0));
4396    /// # Ok::<(), tenferro_tensor::Error>(())
4397    /// ```
4398    pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
4399        checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
4400    }
4401
4402    /// Compute the physical element offset for a logical index, returning a typed error.
4403    ///
4404    /// # Examples
4405    ///
4406    /// ```rust
4407    /// use tenferro_tensor::TypedTensorViewMut;
4408    ///
4409    /// let mut data = [1_i32, 2, 3];
4410    /// let view = TypedTensorViewMut::from_slice([3], [-1], 2, &mut data)?;
4411    /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
4412    /// # Ok::<(), tenferro_tensor::Error>(())
4413    /// ```
4414    /// # Errors
4415    ///
4416    /// Returns [`crate::Error::Validation`] with
4417    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
4418    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
4419    /// when an index is outside its axis extent, or
4420    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
4421    /// arithmetic overflows.
4422    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
4423        checked_view_offset_result(
4424            self.shape(),
4425            self.strides(),
4426            self.offset(),
4427            indices,
4428            "TypedTensorViewMut::layout_linear_offset",
4429        )
4430    }
4431
4432    /// Return whether this mutable view is compact column-major.
4433    ///
4434    /// # Examples
4435    ///
4436    /// ```rust
4437    /// use tenferro_tensor::TypedTensorViewMut;
4438    ///
4439    /// let mut data = [1_i32, 2];
4440    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4441    /// assert!(view.is_col_major_contiguous()?);
4442    /// # Ok::<(), tenferro_tensor::Error>(())
4443    /// ```
4444    /// # Errors
4445    ///
4446    /// Returns [`crate::Error::Validation`] with
4447    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4448    /// compactness arithmetic overflows.
4449    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
4450        self.layout
4451            .is_compact_col_major()
4452            .map_err(|err| tensor_layout_error("TypedTensorViewMut::is_col_major_contiguous", err))
4453    }
4454
4455    /// Return a compact string summary of this mutable view's layout metadata.
4456    ///
4457    /// # Examples
4458    ///
4459    /// ```rust
4460    /// use tenferro_tensor::TypedTensorViewMut;
4461    ///
4462    /// let mut data = [1_i32, 2];
4463    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4464    /// assert!(view.layout_summary().contains("shape=[2]"));
4465    /// # Ok::<(), tenferro_tensor::Error>(())
4466    /// ```
4467    pub fn layout_summary(&self) -> String {
4468        layout_summary(self.shape(), self.strides(), self.offset())
4469    }
4470
4471    /// Assert this mutable view is compact column-major.
4472    ///
4473    /// # Examples
4474    ///
4475    /// ```rust
4476    /// use tenferro_tensor::TypedTensorViewMut;
4477    ///
4478    /// let mut data = [1_i32, 2];
4479    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4480    /// view.assert_col_major_contiguous()?;
4481    /// # Ok::<(), tenferro_tensor::Error>(())
4482    /// ```
4483    /// # Errors
4484    ///
4485    /// Returns [`crate::Error::Validation`] with
4486    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4487    /// compactness arithmetic overflows, or
4488    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
4489    /// view is not compact column-major.
4490    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
4491        assert_layout_col_major_contiguous(
4492            self.is_col_major_contiguous()?,
4493            self.shape(),
4494            self.strides(),
4495            self.offset(),
4496            "TypedTensorViewMut::assert_col_major_contiguous",
4497        )
4498    }
4499
4500    /// Borrow one host element by logical index.
4501    ///
4502    /// # Examples
4503    ///
4504    /// ```rust
4505    /// use tenferro_tensor::TypedTensorViewMut;
4506    ///
4507    /// let mut data = [1_i32, 2];
4508    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4509    /// assert_eq!(view.get(&[1]), Some(&2));
4510    /// # Ok::<(), tenferro_tensor::Error>(())
4511    /// ```
4512    pub fn get(&self, indices: &[usize]) -> Option<&T> {
4513        let offset = self.linear_offset(indices)?;
4514        match &self.buffer {
4515            TensorStorageRefMut::Host(data) => data.get(offset),
4516            TensorStorageRefMut::Backend(_) => None,
4517        }
4518    }
4519
4520    /// Mutably borrow one host element by logical index.
4521    ///
4522    /// # Examples
4523    ///
4524    /// ```rust
4525    /// use tenferro_tensor::TypedTensorViewMut;
4526    ///
4527    /// let mut data = [1_i32, 2];
4528    /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4529    /// *view.get_mut(&[1]).unwrap() = 20;
4530    /// assert_eq!(view.get(&[1]), Some(&20));
4531    /// # Ok::<(), tenferro_tensor::Error>(())
4532    /// ```
4533    pub fn get_mut(&mut self, indices: &[usize]) -> Option<&mut T> {
4534        let offset = self.linear_offset(indices)?;
4535        match &mut self.buffer {
4536            TensorStorageRefMut::Host(data) => data.get_mut(offset),
4537            TensorStorageRefMut::Backend(_) => None,
4538        }
4539    }
4540
4541    /// Borrow this mutable view as a read-only view.
4542    ///
4543    /// # Examples
4544    ///
4545    /// ```rust
4546    /// use tenferro_tensor::TypedTensorViewMut;
4547    ///
4548    /// let mut data = [1_i32];
4549    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4550    /// assert_eq!(view.as_read_only().get(&[0]), Some(&1));
4551    /// # Ok::<(), tenferro_tensor::Error>(())
4552    /// ```
4553    /// Explicitly duplicate the compact host data visible through this
4554    /// mutable view into a new owner.
4555    ///
4556    /// # Errors
4557    ///
4558    /// Returns [`crate::Error::HostAccess`] or
4559    /// [`ValidationError::NonContiguousViewAsSlice`] when the view is backend
4560    /// owned or not contiguous, and [`ValidationError::InvalidArgument`] when
4561    /// the static-rank shape cannot be reconstructed.
4562    ///
4563    /// # Examples
4564    ///
4565    /// ```
4566    /// use tenferro_tensor::TypedTensorViewMut;
4567    ///
4568    /// let mut data = [1_i32, 2];
4569    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4570    /// let copy = view.duplicate()?;
4571    /// assert_eq!(copy.as_slice()?, &[1, 2]);
4572    /// # Ok::<(), tenferro_tensor::Error>(())
4573    /// ```
4574    pub fn duplicate(&self) -> crate::Result<TypedTensor<T, R>>
4575    where
4576        T: Clone,
4577    {
4578        self.as_read_only().duplicate()
4579    }
4580
4581    /// Explicitly copy this host view to a compact column-major owner.
4582    ///
4583    /// # Examples
4584    /// ```
4585    /// use tenferro_tensor::TypedTensorViewMut;
4586    /// let mut values = ["a".to_owned(), "b".to_owned()];
4587    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut values)?;
4588    /// assert_eq!(view.to_col_major()?.as_slice()?, &["a", "b"]);
4589    /// # Ok::<(), tenferro_tensor::Error>(())
4590    /// ```
4591    /// # Errors
4592    /// Backend-only storage returns [`crate::Error::RuntimeState`]; invalid
4593    /// layout or rank conversion returns [`crate::Error::Validation`].
4594    pub fn to_col_major(&self) -> crate::Result<TypedTensor<T, R>>
4595    where
4596        T: Clone,
4597    {
4598        self.as_read_only().to_col_major()
4599    }
4600
4601    /// Borrow this mutable view as a read-only typed view over the same storage.
4602    ///
4603    /// # Examples
4604    ///
4605    /// ```
4606    /// use tenferro_tensor::TypedTensorViewMut;
4607    ///
4608    /// let mut data = [1_i32, 2];
4609    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4610    /// assert_eq!(view.as_read_only().as_slice()?, &[1, 2]);
4611    /// # Ok::<(), tenferro_tensor::Error>(())
4612    /// ```
4613    pub fn as_read_only(&self) -> TypedTensorView<'_, T, R, D> {
4614        let buffer = match &self.buffer {
4615            TensorStorageRefMut::Host(data) => TensorStorageRef::Host(data),
4616            TensorStorageRefMut::Backend(buffer) => TensorStorageRef::Backend(&**buffer),
4617        };
4618        TypedTensorView {
4619            buffer,
4620            root: None,
4621            layout: self.layout.clone(),
4622            placement: self.placement.clone(),
4623            _representation: std::marker::PhantomData,
4624        }
4625    }
4626
4627    /// Convert this mutable view into a read-only view.
4628    ///
4629    /// # Examples
4630    ///
4631    /// ```rust
4632    /// use tenferro_tensor::TypedTensorViewMut;
4633    ///
4634    /// let mut data = [1_i32];
4635    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4636    /// assert_eq!(view.into_read_only().get(&[0]), Some(&1));
4637    /// # Ok::<(), tenferro_tensor::Error>(())
4638    /// ```
4639    pub fn into_read_only(self) -> TypedTensorView<'a, T, R, D> {
4640        let buffer = match self.buffer {
4641            TensorStorageRefMut::Host(data) => TensorStorageRef::Host(data),
4642            TensorStorageRefMut::Backend(buffer) => TensorStorageRef::Backend(buffer),
4643        };
4644        TypedTensorView {
4645            buffer,
4646            root: None,
4647            layout: self.layout,
4648            placement: self.placement,
4649            _representation: std::marker::PhantomData,
4650        }
4651    }
4652
4653    /// Consume this mutable view and return a metadata-only axis permutation.
4654    ///
4655    /// # Examples
4656    ///
4657    /// ```rust
4658    /// use tenferro_tensor::{Rank, TypedTensorViewMut};
4659    ///
4660    /// let mut data = [1_i32, 2, 3, 4];
4661    /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
4662    /// let transposed = view.transpose_view([1, 0])?;
4663    /// assert_eq!(transposed.strides(), &[2, 1]);
4664    /// # Ok::<(), tenferro_tensor::Error>(())
4665    /// ```
4666    /// # Errors
4667    ///
4668    /// Returns [`crate::Error::Validation`] with
4669    /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
4670    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
4671    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
4672    /// not a valid permutation, [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`]
4673    /// when the permutation creates aliases, or
4674    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4675    /// arithmetic overflow.
4676    pub fn transpose_view(
4677        self,
4678        axes: impl AsRef<[usize]>,
4679    ) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
4680        let Self {
4681            buffer,
4682            root,
4683            layout,
4684            placement,
4685            _representation: std::marker::PhantomData,
4686        } = self;
4687        let layout = layout
4688            .transpose_view(axes)
4689            .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
4690        layout
4691            .validate_mutable_no_overlap()
4692            .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
4693        match buffer {
4694            TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
4695                buffer: TensorStorageRefMut::Host(data),
4696                root,
4697                layout,
4698                placement,
4699                _representation: std::marker::PhantomData,
4700            }),
4701            TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
4702                buffer: TensorStorageRefMut::Backend(buffer),
4703                root,
4704                layout,
4705                placement,
4706                _representation: std::marker::PhantomData,
4707            }),
4708        }
4709    }
4710
4711    /// Return a mutable metadata-only slice using one [`StridedSliceSpec`] per axis.
4712    ///
4713    /// # Examples
4714    ///
4715    /// ```rust
4716    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
4717    ///
4718    /// let mut data = [1_i32, 2, 3];
4719    /// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![1], 0, &mut data)?;
4720    /// *view.slice_view(&[StridedSliceSpec::reverse()])?.get_mut(&[0]).unwrap() = 30;
4721    /// assert_eq!(view.get(&[2]), Some(&30));
4722    /// # Ok::<(), tenferro_tensor::Error>(())
4723    /// ```
4724    /// # Errors
4725    ///
4726    /// Returns [`crate::Error::Validation`] with
4727    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
4728    /// count differs from the view rank,
4729    /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
4730    /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
4731    /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
4732    /// when the result exceeds the backing buffer,
4733    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4734    /// logical elements alias, or
4735    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4736    /// arithmetic overflow.
4737    pub fn slice_view(
4738        &mut self,
4739        slices: &[StridedSliceSpec],
4740    ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
4741        let specs = core_slice_specs(slices, self.shape(), "TypedTensorViewMut::slice_view")?;
4742        let layout = self
4743            .layout
4744            .slice_view(specs, self.buffer.len())
4745            .map_err(|err| tensor_layout_error("TypedTensorViewMut::slice_view", err))?;
4746        layout
4747            .validate_mutable_no_overlap()
4748            .map_err(|err| tensor_layout_error("TypedTensorViewMut::slice_view", err))?;
4749        let placement = self.placement.clone();
4750        match &mut self.buffer {
4751            TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
4752                buffer: TensorStorageRefMut::Host(data),
4753                root: None,
4754                layout,
4755                placement,
4756                _representation: std::marker::PhantomData,
4757            }),
4758            TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
4759                buffer: TensorStorageRefMut::Backend(*buffer),
4760                root: None,
4761                layout,
4762                placement,
4763                _representation: std::marker::PhantomData,
4764            }),
4765        }
4766    }
4767
4768    /// Return a mutable metadata-only slice along one axis.
4769    ///
4770    /// # Examples
4771    ///
4772    /// ```rust
4773    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
4774    ///
4775    /// let mut data = [1_i32, 2, 3, 4];
4776    /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
4777    /// assert_eq!(view.slice_axis_view(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
4778    /// # Ok::<(), tenferro_tensor::Error>(())
4779    /// ```
4780    /// # Errors
4781    ///
4782    /// Returns [`crate::Error::Validation`] with
4783    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
4784    /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
4785    /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
4786    /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
4787    /// when the result exceeds the backing buffer,
4788    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4789    /// logical elements alias, or
4790    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4791    /// arithmetic overflow.
4792    pub fn slice_axis_view(
4793        &mut self,
4794        axis: usize,
4795        slice: StridedSliceSpec,
4796    ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
4797        let slices = slice_axis_specs(
4798            self.shape().len(),
4799            axis,
4800            slice,
4801            "TypedTensorViewMut::slice_axis_view",
4802        )?;
4803        self.slice_view(&slices)
4804    }
4805
4806    /// Return two mutable metadata-only slices when their physical ranges are disjoint.
4807    ///
4808    /// # Examples
4809    ///
4810    /// ```rust
4811    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
4812    ///
4813    /// let mut data = [1_i32, 2, 3, 4];
4814    /// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
4815    /// let (left, right) = view
4816    ///     .try_multi_slice_mut(
4817    ///         &[StridedSliceSpec::new(0, Some(2), 1)],
4818    ///         &[StridedSliceSpec::new(2, Some(4), 1)],
4819    ///     )
4820    ///     ?
4821    ///     .unwrap();
4822    /// assert_eq!(left.shape(), &[2]);
4823    /// assert_eq!(right.shape(), &[2]);
4824    /// # Ok::<(), tenferro_tensor::Error>(())
4825    /// ```
4826    /// # Errors
4827    ///
4828    /// Returns [`crate::Error::Validation`] with
4829    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for either
4830    /// slice count, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
4831    /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for
4832    /// invalid parameters, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
4833    /// when a result exceeds the backing buffer,
4834    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4835    /// a result aliases, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
4836    /// for a negative reachable offset, or
4837    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4838    /// arithmetic overflow. The method returns `Ok(None)` when the two ranges
4839    /// overlap or the view uses backend storage.
4840    pub fn try_multi_slice_mut(
4841        &mut self,
4842        first: &[StridedSliceSpec],
4843        second: &[StridedSliceSpec],
4844    ) -> crate::Result<Option<TypedTensorViewMutSplit<'_, T, R>>> {
4845        let op = "TypedTensorViewMut::try_multi_slice_mut";
4846        let first_specs = core_slice_specs(first, self.shape(), op)?;
4847        let second_specs = core_slice_specs(second, self.shape(), op)?;
4848        let buffer_len = self.buffer.len();
4849        let first_layout = self
4850            .layout
4851            .slice_view(first_specs, buffer_len)
4852            .map_err(|err| tensor_layout_error(op, err))?;
4853        let second_layout = self
4854            .layout
4855            .slice_view(second_specs, buffer_len)
4856            .map_err(|err| tensor_layout_error(op, err))?;
4857        first_layout
4858            .validate_mutable_no_overlap()
4859            .map_err(|err| tensor_layout_error(op, err))?;
4860        second_layout
4861            .validate_mutable_no_overlap()
4862            .map_err(|err| tensor_layout_error(op, err))?;
4863
4864        match (
4865            reachable_layout_span(
4866                first_layout.shape(),
4867                first_layout.strides(),
4868                first_layout.offset(),
4869            )?,
4870            reachable_layout_span(
4871                second_layout.shape(),
4872                second_layout.strides(),
4873                second_layout.offset(),
4874            )?,
4875        ) {
4876            (Some(first_span), Some(second_span)) => {
4877                let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
4878                let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
4879                let (first_data, second_data) = match &mut self.buffer {
4880                    TensorStorageRefMut::Host(data) => {
4881                        match split_two_mut_ranges(data, first_span, second_span) {
4882                            Some(ranges) => ranges,
4883                            None => return Ok(None),
4884                        }
4885                    }
4886                    TensorStorageRefMut::Backend(_) => return Ok(None),
4887                };
4888                let first_view = view_mut_from_layout_and_slice(
4889                    &first_layout,
4890                    first_offset,
4891                    first_data,
4892                    self.placement.clone(),
4893                )?;
4894                let second_view = view_mut_from_layout_and_slice(
4895                    &second_layout,
4896                    second_offset,
4897                    second_data,
4898                    self.placement.clone(),
4899                )?;
4900                Ok(Some((first_view, second_view)))
4901            }
4902            (None, Some(second_span)) => {
4903                let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
4904                let (_, after_start) = match &mut self.buffer {
4905                    TensorStorageRefMut::Host(data) => data.split_at_mut(second_span.0),
4906                    TensorStorageRefMut::Backend(_) => return Ok(None),
4907                };
4908                let (second_data, _) = after_start.split_at_mut(second_span.1 - second_span.0 + 1);
4909                let first_view = view_mut_from_layout_and_slice(
4910                    &first_layout,
4911                    0,
4912                    &mut [],
4913                    self.placement.clone(),
4914                )?;
4915                let second_view = view_mut_from_layout_and_slice(
4916                    &second_layout,
4917                    second_offset,
4918                    second_data,
4919                    self.placement.clone(),
4920                )?;
4921                Ok(Some((first_view, second_view)))
4922            }
4923            (Some(first_span), None) => {
4924                let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
4925                let (_, after_start) = match &mut self.buffer {
4926                    TensorStorageRefMut::Host(data) => data.split_at_mut(first_span.0),
4927                    TensorStorageRefMut::Backend(_) => return Ok(None),
4928                };
4929                let (first_data, _) = after_start.split_at_mut(first_span.1 - first_span.0 + 1);
4930                let first_view = view_mut_from_layout_and_slice(
4931                    &first_layout,
4932                    first_offset,
4933                    first_data,
4934                    self.placement.clone(),
4935                )?;
4936                let second_view = view_mut_from_layout_and_slice(
4937                    &second_layout,
4938                    0,
4939                    &mut [],
4940                    self.placement.clone(),
4941                )?;
4942                Ok(Some((first_view, second_view)))
4943            }
4944            (None, None) => {
4945                let first_view = view_mut_from_layout_and_slice(
4946                    &first_layout,
4947                    0,
4948                    &mut [],
4949                    self.placement.clone(),
4950                )?;
4951                let second_view = view_mut_from_layout_and_slice(
4952                    &second_layout,
4953                    0,
4954                    &mut [],
4955                    self.placement.clone(),
4956                )?;
4957                Ok(Some((first_view, second_view)))
4958            }
4959        }
4960    }
4961
4962    /// Return a mutable metadata-only dynamic-rank reshape for contiguous views.
4963    ///
4964    /// # Examples
4965    ///
4966    /// ```rust
4967    /// use tenferro_tensor::TypedTensorViewMut;
4968    ///
4969    /// let mut data = [1_i32, 2, 3, 4];
4970    /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
4971    /// assert_eq!(view.reshape_view(&[4])?.shape(), &[4]);
4972    /// # Ok::<(), tenferro_tensor::Error>(())
4973    /// ```
4974    /// # Errors
4975    ///
4976    /// Returns [`crate::Error::Validation`] with
4977    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
4978    /// the source is not compact column-major,
4979    /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
4980    /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
4981    /// records the counts) when element counts differ,
4982    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4983    /// the reshaped layout aliases, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
4984    /// for shape or layout arithmetic overflow, or
4985    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4986    /// reshaped view exceeds the backing buffer.
4987    pub fn reshape_view(
4988        &mut self,
4989        shape: &[usize],
4990    ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank, D>> {
4991        let layout = reshape_layout_dyn(
4992            &self.layout,
4993            shape,
4994            self.buffer.len(),
4995            "TypedTensorViewMut::reshape_view",
4996        )?;
4997        layout
4998            .validate_mutable_no_overlap()
4999            .map_err(|err| tensor_layout_error("TypedTensorViewMut::reshape_view", err))?;
5000        let placement = self.placement.clone();
5001        match &mut self.buffer {
5002            TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
5003                buffer: TensorStorageRefMut::Host(data),
5004                root: None,
5005                layout,
5006                placement,
5007                _representation: std::marker::PhantomData,
5008            }),
5009            TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
5010                buffer: TensorStorageRefMut::Backend(*buffer),
5011                root: None,
5012                layout,
5013                placement,
5014                _representation: std::marker::PhantomData,
5015            }),
5016        }
5017    }
5018}
5019
5020impl<'a, R: TensorRank> TypedTensorViewMut<'a, Complex32, R> {
5021    /// Borrow this mutable complex view as an interleaved real view.
5022    ///
5023    /// This changes only the typed descriptor and borrows the same host
5024    /// allocation. The result has dynamic rank because the component axis is
5025    /// prepended. Backend-native buffers are rejected until their provider
5026    /// phase supplies the corresponding mapping capability.
5027    ///
5028    /// # Examples
5029    ///
5030    /// ```
5031    /// use tenferro_tensor::{Complex32, TypedTensorViewMut};
5032    ///
5033    /// let mut data = [Complex32::new(1.0, 2.0)];
5034    /// let mut view = TypedTensorViewMut::from_col_major(&[1], &mut data)?;
5035    /// let real = view.as_real_view_mut()?;
5036    /// assert_eq!(real.shape(), &[2, 1]);
5037    /// assert_eq!(real.as_read_only().as_slice()?, &[1.0_f32, 2.0]);
5038    /// # Ok::<(), tenferro_tensor::Error>(())
5039    /// ```
5040    ///
5041    /// # Errors
5042    ///
5043    /// Returns an error when the view layout is not injective, the sealed
5044    /// representation is invalid, or backend reinterpretation is unsupported.
5045    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
5046        let op = "TypedTensorViewMut::as_real_view_mut";
5047        validate_representation_pair(op, DType::C32, DType::F32)?;
5048        let layout = reinterpret_complex_to_real_layout(
5049            self.shape(),
5050            self.strides(),
5051            self.offset(),
5052            self.buffer.len(),
5053            op,
5054        )?;
5055        layout
5056            .validate_mutable_no_overlap()
5057            .map_err(|err| tensor_layout_error(op, err))?;
5058        let buffer = match &mut self.buffer {
5059            TensorStorageRefMut::Host(data) => {
5060                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(data, op)?)
5061            }
5062            TensorStorageRefMut::Backend(_) => {
5063                return Err(crate::Error::unsupported(
5064                    op,
5065                    "backend representation reinterpretation is enabled by the provider phases",
5066                ))
5067            }
5068        };
5069        Ok(TypedTensorViewMut {
5070            buffer,
5071            root: None,
5072            layout,
5073            placement: self.placement.clone(),
5074            _representation: std::marker::PhantomData,
5075        })
5076    }
5077}
5078
5079impl<'a, R: TensorRank> TypedTensorViewMut<'a, Complex64, R> {
5080    /// Borrow this mutable complex view as an interleaved real view.
5081    ///
5082    /// This changes only the typed descriptor and borrows the same host
5083    /// allocation. The result has dynamic rank because the component axis is
5084    /// prepended. Backend-native buffers are rejected until their provider
5085    /// phase supplies the corresponding mapping capability.
5086    ///
5087    /// # Examples
5088    ///
5089    /// ```
5090    /// use tenferro_tensor::{Complex64, TypedTensorViewMut};
5091    ///
5092    /// let mut data = [Complex64::new(1.0, 2.0)];
5093    /// let mut view = TypedTensorViewMut::from_col_major(&[1], &mut data)?;
5094    /// let real = view.as_real_view_mut()?;
5095    /// assert_eq!(real.shape(), &[2, 1]);
5096    /// assert_eq!(real.as_read_only().as_slice()?, &[1.0_f64, 2.0]);
5097    /// # Ok::<(), tenferro_tensor::Error>(())
5098    /// ```
5099    ///
5100    /// # Errors
5101    ///
5102    /// Returns an error when the view layout is not injective, the sealed
5103    /// representation is invalid, or backend reinterpretation is unsupported.
5104    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
5105        let op = "TypedTensorViewMut::as_real_view_mut";
5106        validate_representation_pair(op, DType::C64, DType::F64)?;
5107        let layout = reinterpret_complex_to_real_layout(
5108            self.shape(),
5109            self.strides(),
5110            self.offset(),
5111            self.buffer.len(),
5112            op,
5113        )?;
5114        layout
5115            .validate_mutable_no_overlap()
5116            .map_err(|err| tensor_layout_error(op, err))?;
5117        let buffer = match &mut self.buffer {
5118            TensorStorageRefMut::Host(data) => {
5119                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(data, op)?)
5120            }
5121            TensorStorageRefMut::Backend(_) => {
5122                return Err(crate::Error::unsupported(
5123                    op,
5124                    "backend representation reinterpretation is enabled by the provider phases",
5125                ))
5126            }
5127        };
5128        Ok(TypedTensorViewMut {
5129            buffer,
5130            root: None,
5131            layout,
5132            placement: self.placement.clone(),
5133            _representation: std::marker::PhantomData,
5134        })
5135    }
5136}
5137
5138impl<'a, R: TensorRank> TypedTensorViewMut<'a, f32, R> {
5139    /// Borrow this mutable interleaved real view as a complex view.
5140    ///
5141    /// The source must have a leading extent and stride of `2` and `1`, and
5142    /// all remaining strides plus the offset must be divisible by `2`.
5143    ///
5144    /// # Examples
5145    ///
5146    /// ```
5147    /// use tenferro_tensor::{Complex32, TypedTensorViewMut};
5148    ///
5149    /// let mut data = [1.0_f32, 2.0];
5150    /// let mut view = TypedTensorViewMut::from_col_major(&[2, 1], &mut data)?;
5151    /// let complex = view.as_complex_view_mut()?;
5152    /// assert_eq!(complex.shape(), &[1]);
5153    /// assert_eq!(complex.as_read_only().as_slice()?, &[Complex32::new(1.0, 2.0)]);
5154    /// # Ok::<(), tenferro_tensor::Error>(())
5155    /// ```
5156    ///
5157    /// # Errors
5158    ///
5159    /// Returns an error when the view layout is not injective, the sealed
5160    /// representation is invalid, or backend reinterpretation is unsupported.
5161    pub fn as_complex_view_mut(
5162        &mut self,
5163    ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
5164        let op = "TypedTensorViewMut::as_complex_view_mut";
5165        validate_representation_pair(op, DType::F32, DType::C32)?;
5166        let layout = reinterpret_real_to_complex_layout(
5167            self.shape(),
5168            self.strides(),
5169            self.offset(),
5170            self.buffer.len(),
5171            op,
5172        )?;
5173        layout
5174            .validate_mutable_no_overlap()
5175            .map_err(|err| tensor_layout_error(op, err))?;
5176        let buffer = match &mut self.buffer {
5177            TensorStorageRefMut::Host(data) => {
5178                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(data, op)?)
5179            }
5180            TensorStorageRefMut::Backend(_) => {
5181                return Err(crate::Error::unsupported(
5182                    op,
5183                    "backend representation reinterpretation is enabled by the provider phases",
5184                ))
5185            }
5186        };
5187        Ok(TypedTensorViewMut {
5188            buffer,
5189            root: None,
5190            layout,
5191            placement: self.placement.clone(),
5192            _representation: std::marker::PhantomData,
5193        })
5194    }
5195}
5196
5197impl<'a, R: TensorRank> TypedTensorViewMut<'a, f64, R> {
5198    /// Borrow this mutable interleaved real view as a complex view.
5199    ///
5200    /// The source must have a leading extent and stride of `2` and `1`, and
5201    /// all remaining strides plus the offset must be divisible by `2`.
5202    ///
5203    /// # Examples
5204    ///
5205    /// ```
5206    /// use tenferro_tensor::{Complex64, TypedTensorViewMut};
5207    ///
5208    /// let mut data = [1.0_f64, 2.0];
5209    /// let mut view = TypedTensorViewMut::from_col_major(&[2, 1], &mut data)?;
5210    /// let complex = view.as_complex_view_mut()?;
5211    /// assert_eq!(complex.shape(), &[1]);
5212    /// assert_eq!(complex.as_read_only().as_slice()?, &[Complex64::new(1.0, 2.0)]);
5213    /// # Ok::<(), tenferro_tensor::Error>(())
5214    /// ```
5215    ///
5216    /// # Errors
5217    ///
5218    /// Returns an error when the view layout is not injective, the sealed
5219    /// representation is invalid, or backend reinterpretation is unsupported.
5220    pub fn as_complex_view_mut(
5221        &mut self,
5222    ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
5223        let op = "TypedTensorViewMut::as_complex_view_mut";
5224        validate_representation_pair(op, DType::F64, DType::C64)?;
5225        let layout = reinterpret_real_to_complex_layout(
5226            self.shape(),
5227            self.strides(),
5228            self.offset(),
5229            self.buffer.len(),
5230            op,
5231        )?;
5232        layout
5233            .validate_mutable_no_overlap()
5234            .map_err(|err| tensor_layout_error(op, err))?;
5235        let buffer = match &mut self.buffer {
5236            TensorStorageRefMut::Host(data) => {
5237                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(data, op)?)
5238            }
5239            TensorStorageRefMut::Backend(_) => {
5240                return Err(crate::Error::unsupported(
5241                    op,
5242                    "backend representation reinterpretation is enabled by the provider phases",
5243                ))
5244            }
5245        };
5246        Ok(TypedTensorViewMut {
5247            buffer,
5248            root: None,
5249            layout,
5250            placement: self.placement.clone(),
5251            _representation: std::marker::PhantomData,
5252        })
5253    }
5254}
5255
5256/// Sealed trait for scalar types that can be stored in a [`Tensor`].
5257///
5258/// This trait is implemented for `f64`, `f32`, `i32`, `i64`, `bool`,
5259/// [`Complex64`], and [`Complex32`].
5260///
5261/// # Examples
5262///
5263/// ```
5264/// use tenferro_tensor::TensorScalar;
5265///
5266/// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
5267/// assert_eq!(tensor.as_slice::<f64>()?, [1.0, 2.0].as_slice());
5268/// # Ok::<(), tenferro_tensor::Error>(())
5269/// ```
5270pub trait TensorScalar: Copy + Clone + Send + Sync + 'static + private::Sealed {
5271    /// Real-valued counterpart of this scalar type.
5272    type Real: TensorScalar;
5273
5274    /// The [`DType`] tag corresponding to this scalar type.
5275    ///
5276    /// # Examples
5277    ///
5278    /// ```
5279    /// use tenferro_tensor::{DType, TensorScalar};
5280    ///
5281    /// assert_eq!(f64::dtype(), DType::F64);
5282    /// assert_eq!(f32::dtype(), DType::F32);
5283    /// ```
5284    fn dtype() -> DType;
5285
5286    /// Build the crate's default scalar set from validated column-major data.
5287    ///
5288    /// # Examples
5289    ///
5290    /// ```
5291    /// use tenferro_tensor::{DType, TensorScalar};
5292    ///
5293    /// let set = <f64 as TensorScalar>::into_default_scalars(vec![2], vec![1.0, 2.0])?;
5294    /// assert_eq!(set.dtype(), DType::F64);
5295    /// # Ok::<(), tenferro_tensor::Error>(())
5296    /// ```
5297    ///
5298    /// # Errors
5299    ///
5300    /// Returns a validation error carrying
5301    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when the shape product
5302    /// differs from `data.len()`, or [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
5303    /// when shape arithmetic overflows.
5304    fn into_default_scalars(
5305        shape: Vec<usize>,
5306        data: Vec<Self>,
5307    ) -> crate::Result<crate::DefaultScalars>;
5308
5309    /// Borrow the default scalar set's values when it holds this scalar type.
5310    ///
5311    /// Returns `None` when the set currently holds another member.
5312    ///
5313    /// # Examples
5314    ///
5315    /// ```
5316    /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5317    ///
5318    /// let set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5319    /// assert_eq!(<f64 as TensorScalar>::default_scalars_slice(&set), Some(&[1.0, 2.0][..]));
5320    /// assert!(<f32 as TensorScalar>::default_scalars_slice(&set).is_none());
5321    /// # Ok::<(), tenferro_tensor::Error>(())
5322    /// ```
5323    fn default_scalars_slice(set: &crate::DefaultScalars) -> Option<&[Self]>;
5324
5325    /// Exclusively borrow the default scalar set's values when it holds this scalar type.
5326    ///
5327    /// Returns `None` when the set currently holds another member.
5328    ///
5329    /// # Examples
5330    ///
5331    /// ```
5332    /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5333    ///
5334    /// let mut set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5335    /// if let Some(values) = <f64 as TensorScalar>::default_scalars_slice_mut(&mut set) {
5336    ///     values[0] = 5.0;
5337    /// }
5338    /// assert_eq!(set.as_slice::<f64>()?, &[5.0, 2.0]);
5339    /// # Ok::<(), tenferro_tensor::Error>(())
5340    /// ```
5341    fn default_scalars_slice_mut(set: &mut crate::DefaultScalars) -> Option<&mut [Self]>;
5342
5343    /// Move the host tensor out of the default scalar set when it holds this scalar type.
5344    ///
5345    /// Returns `None` when the set currently holds another member.
5346    ///
5347    /// # Examples
5348    ///
5349    /// ```
5350    /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5351    ///
5352    /// let set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5353    /// let host = <f64 as TensorScalar>::from_default_scalars(set);
5354    /// assert_eq!(host.as_ref().map(|t| t.shape()), Some(&[2][..]));
5355    /// assert_eq!(host.as_ref().map(|t| t.as_slice()), Some(&[1.0, 2.0][..]));
5356    /// # Ok::<(), tenferro_tensor::Error>(())
5357    /// ```
5358    fn from_default_scalars(set: crate::DefaultScalars)
5359        -> Option<TypedTensor<Self, DynRank, Host>>;
5360
5361    /// Wrap typed column-major data into a [`Tensor`] enum variant.
5362    ///
5363    /// # Examples
5364    ///
5365    /// ```
5366    /// use tenferro_tensor::{DType, TensorScalar};
5367    ///
5368    /// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
5369    /// assert_eq!(tensor.dtype(), DType::F64);
5370    /// assert_eq!(tensor.shape(), &[2]);
5371    /// assert!(<f64 as TensorScalar>::into_tensor(vec![3], vec![1.0]).is_err());
5372    /// # Ok::<(), tenferro_tensor::Error>(())
5373    /// ```
5374    ///
5375    /// # Errors
5376    ///
5377    /// Returns [`crate::Error::Validation`] with
5378    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
5379    /// the shape product differs from `data.len()`, or
5380    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
5381    /// arithmetic overflows.
5382    fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor>;
5383
5384    /// Wrap a typed tensor into its dynamic [`Tensor`] enum variant.
5385    ///
5386    /// # Examples
5387    ///
5388    /// ```
5389    /// use tenferro_tensor::{DType, Tensor, TensorScalar, TypedTensor};
5390    ///
5391    /// let typed = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![3.0])?;
5392    /// let tensor = <f64 as TensorScalar>::typed_tensor_into_tensor(typed);
5393    /// assert!(matches!(tensor.dtype(), DType::F64));
5394    /// # Ok::<(), tenferro_tensor::Error>(())
5395    /// ```
5396    fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor;
5397
5398    /// Borrow a typed tensor as a dtype-erased [`TensorRead`] view.
5399    ///
5400    /// This keeps the typed tensor borrowed instead of copying host data into
5401    /// a new dynamic tensor.
5402    ///
5403    /// # Examples
5404    ///
5405    /// ```
5406    /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
5407    ///
5408    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5409    /// let read = f64::tensor_read(&tensor);
5410    /// assert_eq!(read.dtype(), DType::F64);
5411    /// assert_eq!(read.shape(), &[2]);
5412    /// ```
5413    fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_>;
5414
5415    /// Wrap a typed borrowed view as a dtype-erased [`TensorView`].
5416    ///
5417    /// # Examples
5418    ///
5419    /// ```
5420    /// use tenferro_tensor::{DType, TensorScalar, TypedTensorView};
5421    ///
5422    /// let data = [1.0_f64];
5423    /// let view = TypedTensorView::from_col_major(&[1], &data)?;
5424    /// assert_eq!(f64::tensor_view(view).dtype(), DType::F64);
5425    /// # Ok::<(), tenferro_tensor::Error>(())
5426    /// ```
5427    fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a>;
5428
5429    /// Wrap a typed mutable borrowed view as a dtype-erased [`TensorViewMut`].
5430    ///
5431    /// # Examples
5432    ///
5433    /// ```
5434    /// use tenferro_tensor::{DType, TensorScalar, TypedTensorViewMut};
5435    ///
5436    /// let mut data = [1.0_f64, 2.0];
5437    /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
5438    /// let erased = f64::tensor_view_mut(view);
5439    /// assert_eq!(erased.dtype(), DType::F64);
5440    /// assert_eq!(erased.shape(), &[2]);
5441    /// # Ok::<(), tenferro_tensor::Error>(())
5442    /// ```
5443    fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a>;
5444
5445    /// Mutably borrow a typed tensor as a dtype-erased [`TensorWrite`] view.
5446    ///
5447    /// This keeps the typed output borrowed instead of wrapping it in a
5448    /// temporary dynamic tensor.
5449    ///
5450    /// # Examples
5451    ///
5452    /// ```
5453    /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
5454    ///
5455    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0]).unwrap();
5456    /// let write = f64::tensor_write(&mut tensor);
5457    /// assert_eq!(write.dtype(), DType::F64);
5458    /// ```
5459    fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_>;
5460
5461    /// Borrow the host data from a [`Tensor`].
5462    ///
5463    /// # Examples
5464    ///
5465    /// ```
5466    /// use tenferro_tensor::{Tensor, TensorScalar};
5467    ///
5468    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5469    /// assert_eq!(<f64 as TensorScalar>::as_slice(&tensor)?, &[1.0, 2.0]);
5470    /// assert!(<f32 as TensorScalar>::as_slice(&tensor).is_err());
5471    /// # Ok::<(), tenferro_tensor::Error>(())
5472    /// ```
5473    ///
5474    /// # Errors
5475    ///
5476    /// Returns [`crate::Error::Validation`] with
5477    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
5478    /// is not the scalar type represented by this implementation, or
5479    /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
5480    /// storage that has not been downloaded.
5481    fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]>;
5482
5483    /// Mutably borrow the host data from a [`Tensor`].
5484    ///
5485    /// # Examples
5486    ///
5487    /// ```
5488    /// use tenferro_tensor::{Tensor, TensorScalar};
5489    ///
5490    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
5491    /// <f64 as TensorScalar>::as_slice_mut(&mut tensor)?[0] = 3.0;
5492    ///
5493    /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
5494    /// # Ok::<(), tenferro_tensor::Error>(())
5495    /// ```
5496    /// # Errors
5497    ///
5498    /// Returns [`crate::Error::Validation`] with
5499    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
5500    /// is not the scalar type represented by this implementation, or
5501    /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
5502    /// storage that has not been downloaded.
5503    fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]>;
5504
5505    /// Extract a [`TypedTensor<Self>`] from a dynamic [`Tensor`].
5506    ///
5507    /// # Examples
5508    ///
5509    /// ```
5510    /// use tenferro_tensor::{Tensor, TensorScalar};
5511    ///
5512    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5513    /// let Ok(typed) = <f64 as TensorScalar>::into_typed(tensor) else {
5514    ///     panic!("the dtype matches by construction")
5515    /// };
5516    ///
5517    /// assert_eq!(typed.as_slice()?, &[1.0, 2.0]);
5518    /// # Ok::<(), tenferro_tensor::Error>(())
5519    /// ```
5520    /// # Errors
5521    ///
5522    /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is
5523    /// not the scalar type represented by this implementation, with
5524    /// [`crate::Error::Validation`] and
5525    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] as the cause.
5526    fn into_typed(
5527        tensor: Tensor,
5528    ) -> std::result::Result<TypedTensor<Self>, ReinterpretError<Tensor>>;
5529}
5530
5531mod private {
5532    pub trait Sealed {}
5533
5534    impl Sealed for f64 {}
5535    impl Sealed for f32 {}
5536    impl Sealed for i32 {}
5537    impl Sealed for i64 {}
5538    impl Sealed for bool {}
5539    impl Sealed for num_complex::Complex64 {}
5540    impl Sealed for num_complex::Complex32 {}
5541}
5542
5543macro_rules! impl_tensor_scalar {
5544    ($ty:ty, $real:ty, $dtype:ident, $variant:ident) => {
5545        impl TensorScalar for $ty {
5546            type Real = $real;
5547
5548            #[inline]
5549            fn dtype() -> DType {
5550                DType::$dtype
5551            }
5552
5553            fn into_default_scalars(
5554                shape: Vec<usize>,
5555                data: Vec<Self>,
5556            ) -> crate::Result<crate::DefaultScalars> {
5557                TypedTensor::<Self, DynRank, Host>::from_host_vec_col_major(shape, data).map(
5558                    |tensor| {
5559                        crate::DefaultScalars::from_payload(
5560                            crate::default_scalars::DefaultScalarsValue::$variant(tensor),
5561                        )
5562                    },
5563                )
5564            }
5565
5566            fn default_scalars_slice(set: &crate::DefaultScalars) -> Option<&[Self]> {
5567                match set.payload() {
5568                    crate::default_scalars::DefaultScalarsValue::$variant(tensor) => {
5569                        Some(tensor.as_slice())
5570                    }
5571                    _ => None,
5572                }
5573            }
5574
5575            fn default_scalars_slice_mut(set: &mut crate::DefaultScalars) -> Option<&mut [Self]> {
5576                match set.payload_mut() {
5577                    crate::default_scalars::DefaultScalarsValue::$variant(tensor) => {
5578                        Some(tensor.host_data_mut())
5579                    }
5580                    _ => None,
5581                }
5582            }
5583
5584            fn from_default_scalars(
5585                set: crate::DefaultScalars,
5586            ) -> Option<TypedTensor<Self, DynRank, Host>> {
5587                match set.into_payload() {
5588                    crate::default_scalars::DefaultScalarsValue::$variant(tensor) => Some(tensor),
5589                    _ => None,
5590                }
5591            }
5592
5593            fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor> {
5594                TypedTensor::from_vec_col_major(shape, data).map(Self::typed_tensor_into_tensor)
5595            }
5596
5597            fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor {
5598                Tensor {
5599                    payload: TensorPayload::Native(PresetTensor::$variant(tensor)),
5600                }
5601            }
5602
5603            fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_> {
5604                TensorRead::from_view(TensorView::$variant(tensor.as_view()))
5605            }
5606
5607            #[inline]
5608            fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a> {
5609                TensorView::$variant(view)
5610            }
5611
5612            #[inline]
5613            fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a> {
5614                TensorViewMut::$variant(view)
5615            }
5616
5617            fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_> {
5618                TensorWrite::from_view(TensorViewMut::$variant(tensor.as_view_mut()))
5619            }
5620
5621            fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]> {
5622                tensor
5623                    .as_typed::<Self>()
5624                    .ok_or_else(|| {
5625                        crate::Error::validation(
5626                            "Tensor::as_slice",
5627                            ValidationError::DTypeMismatch {
5628                                expected: Self::dtype(),
5629                                actual: tensor.dtype(),
5630                            },
5631                        )
5632                    })?
5633                    .host_data()
5634            }
5635
5636            fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]> {
5637                let actual = tensor.dtype();
5638                let typed = tensor.as_typed_mut::<Self>().ok_or_else(|| {
5639                    crate::Error::validation(
5640                        "Tensor::as_slice_mut",
5641                        ValidationError::DTypeMismatch {
5642                            expected: Self::dtype(),
5643                            actual,
5644                        },
5645                    )
5646                })?;
5647                typed.host_data_mut()
5648            }
5649
5650            fn into_typed(
5651                tensor: Tensor,
5652            ) -> std::result::Result<TypedTensor<Self>, ReinterpretError<Tensor>> {
5653                let actual = tensor.dtype();
5654                match tensor.payload {
5655                    TensorPayload::Native(PresetTensor::$variant(typed)) => Ok(typed),
5656                    payload => Err(ReinterpretError::new(
5657                        Tensor { payload },
5658                        crate::Error::validation(
5659                            "TensorScalar::into_typed",
5660                            ValidationError::DTypeMismatch {
5661                                expected: Self::dtype(),
5662                                actual,
5663                            },
5664                        ),
5665                    )),
5666                }
5667            }
5668        }
5669    };
5670}
5671
5672impl_tensor_scalar!(f64, f64, F64, F64);
5673impl_tensor_scalar!(f32, f32, F32, F32);
5674impl_tensor_scalar!(i64, i64, I64, I64);
5675impl_tensor_scalar!(i32, i32, I32, I32);
5676impl_tensor_scalar!(bool, bool, Bool, Bool);
5677impl_tensor_scalar!(Complex64, f64, C64, C64);
5678impl_tensor_scalar!(Complex32, f32, C32, C32);
5679
5680/// Dynamic tensor enum over the supported scalar types.
5681///
5682/// The enum keeps dtype dynamic and rank dynamic. Use
5683/// [`TypedTensor<T, R>`](TypedTensor) directly when the scalar type or rank
5684/// should be represented in Rust's type system.
5685///
5686/// # Examples
5687///
5688/// ```rust
5689/// use tenferro_tensor::{Tensor, TypedTensor};
5690///
5691/// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
5692/// assert_eq!(t.shape(), &[2]);
5693///
5694/// let erased = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 2.0]).unwrap();
5695/// assert_eq!(erased.shape().len(), 2);
5696/// ```
5697/// The owning payload behind the erased [`Tensor`].
5698///
5699/// `Native` stores a preset scalar owner directly; `External` retains its
5700/// existing explicit caller-owned host payload.
5701#[derive(Debug)]
5702enum TensorPayload {
5703    Native(PresetTensor),
5704    External(crate::ErasedHostTensor, Placement),
5705}
5706
5707#[derive(Debug)]
5708enum PresetTensor {
5709    F32(TypedTensor<f32>),
5710    F64(TypedTensor<f64>),
5711    I32(TypedTensor<i32>),
5712    I64(TypedTensor<i64>),
5713    Bool(TypedTensor<bool>),
5714    C32(TypedTensor<Complex32>),
5715    C64(TypedTensor<Complex64>),
5716}
5717
5718macro_rules! with_preset {
5719    ($preset:expr, |$value:ident| $body:expr) => {
5720        match $preset {
5721            PresetTensor::F32($value) => $body,
5722            PresetTensor::F64($value) => $body,
5723            PresetTensor::I32($value) => $body,
5724            PresetTensor::I64($value) => $body,
5725            PresetTensor::Bool($value) => $body,
5726            PresetTensor::C32($value) => $body,
5727            PresetTensor::C64($value) => $body,
5728        }
5729    };
5730}
5731
5732/// Dynamic tensor over the supported scalar types.
5733///
5734/// The erased tensor keeps dtype and rank dynamic: each preset scalar retains its
5735/// typed owner without constructing a group, while caller-owned scalars remain
5736/// `External` payloads recovered by their own type. Use [`TypedTensor<T, R>`](TypedTensor)
5737/// directly when the scalar type or rank should be represented in Rust's type system.
5738///
5739/// # Examples
5740///
5741/// ```
5742/// use tenferro_tensor::{DType, Tensor};
5743///
5744/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5745/// assert_eq!(tensor.dtype(), DType::F64);
5746/// # Ok::<(), tenferro_tensor::Error>(())
5747/// ```
5748#[derive(Debug)]
5749pub struct Tensor {
5750    payload: TensorPayload,
5751}
5752
5753impl<R: TensorRank> OwnedTensorGroup<R> {
5754    /// Whether this group's descriptor names a non-CPU provider.
5755    fn is_backend_buffer(&self) -> bool {
5756        !matches!(
5757            self.group.provider_kind(self.slot),
5758            None | Some(crate::storage::ProviderKind::Cpu)
5759        )
5760    }
5761}
5762
5763impl Tensor {
5764    /// Carry an externally defined scalar as a caller-owned payload.
5765    ///
5766    /// The payload keeps its own element type and is recovered by that type, so no
5767    /// bytes are reinterpreted. Placement defaults to unpinned host memory, which
5768    /// is where a caller-owned payload lives.
5769    ///
5770    /// # Examples
5771    ///
5772    /// ```rust
5773    /// use tenferro_tensor::{DType, Tensor};
5774    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
5775    ///
5776    /// let payload = ErasedHostTensor::new(
5777    ///     TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
5778    /// );
5779    /// let element = payload.element_type_id();
5780    /// let tensor = Tensor::external(payload);
5781    /// assert_eq!(tensor.dtype(), DType::External(element));
5782    /// assert_eq!(tensor.shape(), &[1]);
5783    /// # Ok::<(), tenferro_tensor::Error>(())
5784    /// ```
5785    #[must_use]
5786    pub fn external(payload: crate::ErasedHostTensor) -> Self {
5787        Self {
5788            payload: TensorPayload::External(payload, Placement::default()),
5789        }
5790    }
5791
5792    /// Build a tensor from a typed one, without naming its variant.
5793    ///
5794    /// A call site that constructs a tensor from a typed tensor should use this rather than a variant, so
5795    /// that changing how the erased representation is stored changes this function and not its 1290 call
5796    /// sites. The variants remain until the removal's last step, so both forms currently produce the same
5797    /// value.
5798    ///
5799    /// # Examples
5800    ///
5801    /// ```
5802    /// use tenferro_tensor::Tensor;
5803    ///
5804    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5805    /// let typed = tensor.into_typed::<f64>().unwrap();
5806    /// let rebuilt = Tensor::from_typed(typed);
5807    /// assert_eq!(rebuilt.as_typed::<f64>().unwrap().shape(), &[2]);
5808    /// # Ok::<(), tenferro_tensor::Error>(())
5809    /// ```
5810    #[must_use]
5811    pub fn from_typed<T: TensorScalar>(typed: TypedTensor<T>) -> Self {
5812        T::typed_tensor_into_tensor(typed)
5813    }
5814
5815    /// Borrow the erased payload of an externally defined tensor.
5816    ///
5817    /// This is the counterpart of [`Tensor::external`] for dispatch: a table that matches on
5818    /// [`Tensor::dtype`] reaches the externally defined tag and needs the payload that tag stands
5819    /// for, just as the typed tags reach theirs through [`Tensor::as_typed`]. Every other tag
5820    /// returns `None`.
5821    ///
5822    /// # Examples
5823    ///
5824    /// ```
5825    /// use tenferro_tensor::Tensor;
5826    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
5827    ///
5828    /// let payload = ErasedHostTensor::new(
5829    ///     TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
5830    /// );
5831    /// let tensor = Tensor::external(payload);
5832    /// assert!(tensor.external_payload().is_some());
5833    /// # Ok::<(), tenferro_tensor::Error>(())
5834    /// ```
5835    #[must_use]
5836    pub fn external_payload(&self) -> Option<&crate::ErasedHostTensor> {
5837        match &self.payload {
5838            TensorPayload::External(payload, _) => Some(payload),
5839            TensorPayload::Native(_) => None,
5840        }
5841    }
5842
5843    /// Carry an externally defined payload with an explicit placement.
5844    ///
5845    /// [`Tensor::external`] defaults the placement to unpinned host memory, which is where
5846    /// a caller-owned payload normally lives; this entry point is for a caller that knows
5847    /// the placement it wants.
5848    ///
5849    /// # Examples
5850    ///
5851    /// ```
5852    /// use tenferro_tensor::{Placement, Tensor};
5853    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
5854    ///
5855    /// let payload = ErasedHostTensor::new(
5856    ///     TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
5857    /// );
5858    /// let tensor = Tensor::external_with_placement(payload, Placement::default());
5859    /// assert!(tensor.external_payload().is_some());
5860    /// # Ok::<(), tenferro_tensor::Error>(())
5861    /// ```
5862    #[must_use]
5863    pub fn external_with_placement(payload: crate::ErasedHostTensor, placement: Placement) -> Self {
5864        Self {
5865            payload: TensorPayload::External(payload, placement),
5866        }
5867    }
5868
5869    /// Mutably borrow the erased payload of an externally defined tensor.
5870    ///
5871    /// The counterpart of [`Tensor::external_payload`] for callers that update the payload
5872    /// in place, such as a mutation test that checks the copy boundary.
5873    ///
5874    /// # Examples
5875    ///
5876    /// ```
5877    /// use tenferro_tensor::Tensor;
5878    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
5879    ///
5880    /// let mut tensor = Tensor::external(ErasedHostTensor::new(
5881    ///     TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
5882    /// ));
5883    /// assert!(tensor.external_payload_mut().is_some());
5884    /// # Ok::<(), tenferro_tensor::Error>(())
5885    /// ```
5886    #[must_use]
5887    pub fn external_payload_mut(&mut self) -> Option<&mut crate::ErasedHostTensor> {
5888        match &mut self.payload {
5889            TensorPayload::External(payload, _) => Some(payload),
5890            TensorPayload::Native(_) => None,
5891        }
5892    }
5893
5894    pub(crate) fn into_group_parts(self) -> (AllocationGroup, DescriptorSlot) {
5895        match self.payload {
5896            TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.into_group_parts()),
5897            // INVARIANT: a caller-owned payload has no allocation group.
5898            TensorPayload::External(..) => {
5899                unreachable!("an externally defined payload has no allocation group")
5900            }
5901        }
5902    }
5903}
5904
5905/// Dynamic read-only borrowed tensor view.
5906///
5907/// `TensorView` keeps dtype erased while borrowing typed view metadata and
5908/// storage. Use [`TypedTensorView`] directly when the scalar type is statically
5909/// known.
5910///
5911/// # Examples
5912///
5913/// ```
5914/// use tenferro_tensor::{DType, TensorView, TypedTensorView};
5915///
5916/// let data = [1_i32, 2, 3, 4];
5917/// let typed = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data)?;
5918/// let view = TensorView::I32(typed);
5919///
5920/// assert_eq!(view.dtype(), DType::I32);
5921/// assert_eq!(view.shape(), &[2, 2]);
5922/// # Ok::<(), tenferro_tensor::Error>(())
5923/// ```
5924#[derive(Clone, Debug)]
5925pub enum TensorView<'a> {
5926    F32(TypedTensorView<'a, f32>),
5927    F64(TypedTensorView<'a, f64>),
5928    I32(TypedTensorView<'a, i32>),
5929    I64(TypedTensorView<'a, i64>),
5930    Bool(TypedTensorView<'a, bool>),
5931    C32(TypedTensorView<'a, Complex<f32>>),
5932    C64(TypedTensorView<'a, Complex<f64>>),
5933}
5934
5935/// Dynamic mutable borrowed tensor view.
5936///
5937/// `TensorViewMut` is the mutable counterpart to [`TensorView`]. It keeps the
5938/// dtype erased while preserving the typed mutable view's shape, strides, and
5939/// offset metadata.
5940///
5941/// # Examples
5942///
5943/// ```
5944/// use tenferro_tensor::{DType, TensorViewMut, TypedTensorViewMut};
5945///
5946/// let mut data = [1.0_f64, 2.0];
5947/// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?);
5948/// assert_eq!(view.dtype(), DType::F64);
5949/// # Ok::<(), tenferro_tensor::Error>(())
5950/// ```
5951#[allow(clippy::large_enum_variant)]
5952#[derive(Debug)]
5953pub enum TensorViewMut<'a> {
5954    F32(TypedTensorViewMut<'a, f32>),
5955    F64(TypedTensorViewMut<'a, f64>),
5956    I32(TypedTensorViewMut<'a, i32>),
5957    I64(TypedTensorViewMut<'a, i64>),
5958    Bool(TypedTensorViewMut<'a, bool>),
5959    C32(TypedTensorViewMut<'a, Complex<f32>>),
5960    C64(TypedTensorViewMut<'a, Complex<f64>>),
5961}
5962
5963/// Read-only tensor input accepted by synchronous eager kernels.
5964///
5965/// `TensorRead` lets kernels accept either an owned tensor reference or a
5966/// borrowed [`TensorView`] without forcing callers to materialize first.
5967/// The `View` variant preserves arbitrary strides and offsets, so kernels that
5968/// support strided reads can consume transposes, slices, and broadcasts directly.
5969///
5970/// `TensorRead` is intentionally borrowed. It is an input-dispatch type, not an
5971/// owned lazy tensor value. APIs that need to store a lazy layout result should
5972/// keep an owned base tensor plus layout metadata, then expose a `TensorRead`
5973/// only for the duration of kernel dispatch.
5974///
5975/// # Examples
5976///
5977/// ```
5978/// use tenferro_tensor::{DType, Tensor, TensorRead};
5979///
5980/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
5981/// let read = TensorRead::from_tensor(&tensor);
5982///
5983/// assert_eq!(read.dtype(), DType::F64);
5984/// assert_eq!(read.shape(), &[2]);
5985/// ```
5986// Keep borrowed views inline to avoid allocation on read-only tensor dispatch paths.
5987#[allow(clippy::large_enum_variant)]
5988#[derive(Clone, Debug)]
5989pub enum TensorRead<'a> {
5990    Tensor(&'a Tensor),
5991    View(TensorView<'a>),
5992}
5993
5994/// Mutable typed tensor output accepted by synchronous eager kernels.
5995///
5996/// `TypedTensorWrite` is the typed counterpart to [`TensorWrite`]. It accepts
5997/// either an owned compact [`TypedTensor`] or an arbitrary-strided mutable
5998/// [`TypedTensorViewMut`] without erasing the scalar type at the public API
5999/// boundary.
6000///
6001/// # Examples
6002///
6003/// ```
6004/// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
6005///
6006/// let mut data = [0.0_f64, 1.0, 0.0, 2.0];
6007/// let view = TypedTensorViewMut::from_slice([2], [2], 1, &mut data)?;
6008/// let write = TypedTensorWrite::from_view(view).into_tensor_write();
6009/// assert_eq!(write.shape(), &[2]);
6010/// assert_eq!(write.strides()?, [2]);
6011/// # Ok::<(), tenferro_tensor::Error>(())
6012/// ```
6013#[allow(clippy::large_enum_variant)]
6014#[derive(Debug)]
6015pub enum TypedTensorWrite<'a, T> {
6016    /// An owned compact typed tensor borrowed mutably for the write.
6017    Tensor(&'a mut TypedTensor<T>),
6018    /// An arbitrary-strided mutable typed tensor view.
6019    View(TypedTensorViewMut<'a, T>),
6020}
6021
6022impl<'a, T> TypedTensorWrite<'a, T> {
6023    /// Create a writable target from an owned typed tensor.
6024    ///
6025    /// # Examples
6026    ///
6027    /// ```
6028    /// use tenferro_tensor::{TypedTensor, TypedTensorWrite};
6029    ///
6030    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0])?;
6031    /// let write = TypedTensorWrite::from_tensor(&mut tensor);
6032    /// assert!(matches!(write, TypedTensorWrite::Tensor(_)));
6033    /// # Ok::<(), tenferro_tensor::Error>(())
6034    /// ```
6035    pub fn from_tensor(tensor: &'a mut TypedTensor<T>) -> Self {
6036        Self::Tensor(tensor)
6037    }
6038
6039    /// Create a writable target from a mutable typed tensor view.
6040    ///
6041    /// # Examples
6042    ///
6043    /// ```
6044    /// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
6045    ///
6046    /// let mut data = [0.0_f64, 1.0];
6047    /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
6048    /// let write = TypedTensorWrite::from_view(view);
6049    /// assert!(matches!(write, TypedTensorWrite::View(_)));
6050    /// # Ok::<(), tenferro_tensor::Error>(())
6051    /// ```
6052    pub fn from_view(view: TypedTensorViewMut<'a, T>) -> Self {
6053        Self::View(view)
6054    }
6055}
6056
6057impl<'a, T: TensorScalar> TypedTensorWrite<'a, T> {
6058    /// Erase the scalar type while preserving the output layout.
6059    ///
6060    /// # Examples
6061    ///
6062    /// ```
6063    /// use tenferro_tensor::{DType, TypedTensor, TypedTensorWrite};
6064    ///
6065    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![0.0; 2])?;
6066    /// let write = TypedTensorWrite::from_tensor(&mut tensor).into_tensor_write();
6067    /// assert_eq!(write.dtype(), DType::F64);
6068    /// assert_eq!(write.shape(), &[2]);
6069    /// # Ok::<(), tenferro_tensor::Error>(())
6070    /// ```
6071    pub fn into_tensor_write(self) -> TensorWrite<'a> {
6072        match self {
6073            Self::Tensor(tensor) => T::tensor_write(tensor),
6074            Self::View(view) => TensorWrite::from_view(T::tensor_view_mut(view)),
6075        }
6076    }
6077}
6078
6079impl<'a, T> From<&'a mut TypedTensor<T>> for TypedTensorWrite<'a, T> {
6080    fn from(tensor: &'a mut TypedTensor<T>) -> Self {
6081        Self::from_tensor(tensor)
6082    }
6083}
6084
6085impl<'a, T> From<TypedTensorViewMut<'a, T>> for TypedTensorWrite<'a, T> {
6086    fn from(view: TypedTensorViewMut<'a, T>) -> Self {
6087        Self::from_view(view)
6088    }
6089}
6090
6091/// Mutable tensor output accepted by synchronous eager kernels.
6092///
6093/// `TensorWrite` mirrors [`TensorRead`] for output dispatch: it can target an
6094/// owned compact [`Tensor`] or a borrowed mutable [`TensorViewMut`]. The target
6095/// is never resized.
6096///
6097/// # Examples
6098///
6099/// ```
6100/// use tenferro_tensor::{Tensor, TensorWrite};
6101///
6102/// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
6103/// let write = TensorWrite::from_tensor(&mut tensor);
6104/// assert_eq!(write.shape(), &[1]);
6105/// # Ok::<(), tenferro_tensor::Error>(())
6106/// ```
6107#[allow(clippy::large_enum_variant)]
6108#[derive(Debug)]
6109pub enum TensorWrite<'a> {
6110    Tensor(&'a mut Tensor),
6111    View(TensorViewMut<'a>),
6112}
6113
6114/// Owned tensor value with one move-only physical owner and metadata-only layout.
6115///
6116/// `TensorValue` is intentionally not cloneable. View transformations consume
6117/// the value and move its existing owner; [`TensorValue::duplicate`] is the
6118/// explicit boundary for creating another physical allocation.
6119///
6120/// # Examples
6121///
6122/// ```
6123/// use tenferro_tensor::{Tensor, TensorValue};
6124/// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6125/// let view = value.transpose_view([1, 0])?;
6126/// assert_eq!(view.strides(), &[2, 1]);
6127/// assert!(view.is_view());
6128/// # Ok::<(), tenferro_tensor::Error>(())
6129/// ```
6130#[derive(Debug)]
6131pub struct TensorValue {
6132    owner: Tensor,
6133    layout: TensorLayout<DynRank>,
6134}
6135
6136/// A consuming view transformation failed while retaining its unchanged owner.
6137///
6138/// # Examples
6139///
6140/// ```
6141/// use tenferro_tensor::{Tensor, TensorValue};
6142/// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6143/// let failure = value.try_reshape_view([3]).unwrap_err();
6144/// let (recovered, cause) = failure.into_parts();
6145/// assert_eq!(recovered.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6146/// assert!(matches!(cause, tenferro_tensor::Error::Validation { .. }));
6147/// # Ok::<(), tenferro_tensor::Error>(())
6148/// ```
6149#[derive(Debug)]
6150pub struct TensorValueViewError {
6151    value: TensorValue,
6152    source: crate::Error,
6153}
6154
6155impl TensorValueViewError {
6156    /// Return the unchanged value and the typed validation/backend error.
6157    ///
6158    /// # Examples
6159    ///
6160    /// ```
6161    /// use tenferro_tensor::{Tensor, TensorValue};
6162    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([1], vec![7.])?);
6163    /// let (value, error) = value.try_reshape_view([2]).unwrap_err().into_parts();
6164    /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[7.]);
6165    /// assert!(matches!(error, tenferro_tensor::Error::Validation { .. }));
6166    /// # Ok::<(), tenferro_tensor::Error>(())
6167    /// ```
6168    pub fn into_parts(self) -> (TensorValue, crate::Error) {
6169        (self.value, self.source)
6170    }
6171
6172    fn new(value: TensorValue, source: crate::Error) -> Self {
6173        Self { value, source }
6174    }
6175}
6176
6177impl std::fmt::Display for TensorValueViewError {
6178    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6179        std::fmt::Display::fmt(&self.source, formatter)
6180    }
6181}
6182
6183impl std::error::Error for TensorValueViewError {
6184    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
6185        Some(&self.source)
6186    }
6187}
6188
6189impl TensorValue {
6190    /// Explicitly duplicate the physical owner represented by this value.
6191    ///
6192    /// # Examples
6193    ///
6194    /// ```
6195    /// use tenferro_tensor::{Tensor, TensorValue};
6196    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6197    /// let copy = value.duplicate()?.into_tensor()?;
6198    /// assert_eq!(copy.as_slice::<f64>()?, &[3., 4.]);
6199    /// assert_eq!(value.shape(), &[2]);
6200    /// # Ok::<(), tenferro_tensor::Error>(())
6201    /// ```
6202    ///
6203    /// # Errors
6204    ///
6205    /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
6206    /// when the backend/storage owner cannot be duplicated.
6207    pub fn duplicate(&self) -> crate::Result<Self> {
6208        let tensor = self.owner.duplicate()?;
6209        Self::from_parts(
6210            tensor,
6211            self.shape().to_vec(),
6212            self.strides().to_vec(),
6213            self.offset(),
6214        )
6215    }
6216
6217    /// Retain a compact tensor as a move-only value.
6218    ///
6219    /// # Examples
6220    ///
6221    /// ```
6222    /// use tenferro_tensor::{Tensor, TensorValue};
6223    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6224    /// assert!(!value.is_view());
6225    /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6226    /// # Ok::<(), tenferro_tensor::Error>(())
6227    /// ```
6228    pub fn from_tensor(tensor: Tensor) -> Self {
6229        let layout = tensor_layout(&tensor);
6230        Self {
6231            owner: tensor,
6232            layout,
6233        }
6234    }
6235
6236    /// # Errors
6237    ///
6238    /// Returns [`ValidationError::InvalidArgument`] or
6239    /// [`ValidationError::IntegerOverflow`] when the supplied layout is
6240    /// invalid for the tensor's physical buffer.
6241    ///
6242    /// # Examples
6243    ///
6244    /// ```
6245    /// use tenferro_tensor::{Tensor, TensorValue};
6246    /// let tensor = Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?;
6247    /// let view = TensorValue::from_parts(tensor, vec![2], vec![1], 2)?;
6248    /// assert_eq!(view.shape(), &[2]);
6249    /// assert_eq!(view.offset(), 2);
6250    /// # Ok::<(), tenferro_tensor::Error>(())
6251    /// ```
6252    pub fn from_parts(
6253        tensor: Tensor,
6254        shape: Vec<usize>,
6255        strides: Vec<isize>,
6256        offset: isize,
6257    ) -> crate::Result<Self> {
6258        let layout = TensorLayout::from_parts(
6259            shape.into(),
6260            strides.into(),
6261            offset,
6262            tensor_buffer_len(&tensor),
6263        )
6264        .map_err(|err| tensor_layout_error("TensorValue::from_parts", err))?;
6265        Ok(Self {
6266            owner: tensor,
6267            layout,
6268        })
6269    }
6270
6271    // INVARIANT: unchanged-owner recovery is part of the consuming ownership
6272    // contract, so this intentionally carries the large move-only error.
6273    #[doc(hidden)]
6274    /// Move the value's sole physical owner into an allocation group.
6275    ///
6276    /// This preserves metadata-only views without copying. The consumed value
6277    /// always contains one unique owner, so no compatibility fallback exists.
6278    ///
6279    /// # Errors
6280    ///
6281    /// Returns the unchanged value when descriptor publication fails because
6282    /// of [`ValidationError::InvalidArgument`] or
6283    /// [`ValidationError::IntegerOverflow`].
6284    #[allow(clippy::result_large_err)]
6285    pub fn try_into_group_parts(
6286        self,
6287    ) -> std::result::Result<(AllocationGroup, DescriptorSlot, DType, Vec<usize>), Self> {
6288        let Self { owner, layout } = self;
6289        if owner.external_payload().is_some() {
6290            // A caller-owned payload owns no allocation group, so it is returned
6291            // unchanged instead of being forced into one.
6292            return Err(Self { owner, layout });
6293        }
6294        let dtype = owner.dtype();
6295        let shape = layout.shape().to_vec();
6296        let strides = layout.strides().to_vec();
6297        let offset = layout.offset();
6298        let (group, slot) = owner.into_group_parts();
6299        match group.update_descriptor_layout(slot, shape, strides, offset) {
6300            Ok(group) => Ok((group, slot, dtype, layout.shape().to_vec())),
6301            Err((_group, _error)) => {
6302                unreachable!("TensorValue layout was validated before group ownership transfer")
6303            }
6304        }
6305    }
6306
6307    /// Consume a value with its owner's original layout and return that owner.
6308    ///
6309    /// # Examples
6310    ///
6311    /// ```
6312    /// use tenferro_tensor::{Tensor, TensorValue};
6313    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6314    /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6315    /// # Ok::<(), tenferro_tensor::Error>(())
6316    /// ```
6317    ///
6318    /// # Errors
6319    ///
6320    /// Returns [`crate::Error::Unsupported`] when the value's metadata-only
6321    /// view differs from its physical owner's layout.
6322    pub fn into_tensor(self) -> crate::Result<Tensor> {
6323        if self.layout != tensor_layout(&self.owner) {
6324            return Err(crate::Error::unsupported(
6325                "TensorValue::into_tensor",
6326                "a metadata-only view has no compact tensor owner",
6327            ));
6328        }
6329        Ok(self.owner)
6330    }
6331
6332    ///
6333    /// # Examples
6334    ///
6335    /// ```
6336    /// use tenferro_tensor::{Tensor, TensorValue};
6337    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6338    /// assert_eq!(value.as_tensor().unwrap().as_slice::<f64>()?, &[1., 2., 3., 4.]);
6339    /// assert!(value.transpose_view([1, 0])?.as_tensor().is_none());
6340    /// # Ok::<(), tenferro_tensor::Error>(())
6341    /// ```
6342    pub fn as_tensor(&self) -> Option<&Tensor> {
6343        (self.layout == tensor_layout(&self.owner)).then_some(&self.owner)
6344    }
6345
6346    ///
6347    /// # Examples
6348    ///
6349    /// ```
6350    /// use tenferro_tensor::{Tensor, TensorValue};
6351    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6352    /// assert!(!value.is_view());
6353    /// assert!(value.transpose_view([1, 0])?.is_view());
6354    /// # Ok::<(), tenferro_tensor::Error>(())
6355    /// ```
6356    pub fn is_view(&self) -> bool {
6357        self.as_tensor().is_none()
6358    }
6359
6360    ///
6361    /// # Examples
6362    ///
6363    /// ```
6364    /// use tenferro_tensor::{Tensor, TensorValue};
6365    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6366    /// assert_eq!(value.dtype(), tenferro_tensor::DType::F64);
6367    /// # Ok::<(), tenferro_tensor::Error>(())
6368    /// ```
6369    pub fn dtype(&self) -> DType {
6370        self.owner.dtype()
6371    }
6372
6373    ///
6374    /// # Examples
6375    ///
6376    /// ```
6377    /// use tenferro_tensor::{Tensor, TensorValue};
6378    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6379    /// assert_eq!(value.reshape_view([4])?.shape(), &[4]);
6380    /// # Ok::<(), tenferro_tensor::Error>(())
6381    /// ```
6382    pub fn shape(&self) -> &[usize] {
6383        self.layout.shape()
6384    }
6385
6386    ///
6387    /// # Examples
6388    ///
6389    /// ```
6390    /// use tenferro_tensor::{Tensor, TensorValue};
6391    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6392    /// assert_eq!(value.transpose_view([1, 0])?.strides(), &[2, 1]);
6393    /// # Ok::<(), tenferro_tensor::Error>(())
6394    /// ```
6395    pub fn strides(&self) -> &[isize] {
6396        self.layout.strides()
6397    }
6398
6399    ///
6400    /// # Examples
6401    ///
6402    /// ```
6403    /// use tenferro_tensor::{Tensor, TensorValue};
6404    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6405    /// assert_eq!(value.offset(), 0);
6406    /// # Ok::<(), tenferro_tensor::Error>(())
6407    /// ```
6408    pub fn offset(&self) -> isize {
6409        self.layout.offset()
6410    }
6411
6412    ///
6413    /// # Examples
6414    ///
6415    /// ```
6416    /// use tenferro_tensor::{Tensor, TensorValue};
6417    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6418    /// let view = value.tensor_view();
6419    /// assert_eq!(view.shape(), &[2, 2]);
6420    /// assert_eq!(view.dtype(), tenferro_tensor::DType::F64);
6421    /// # Ok::<(), tenferro_tensor::Error>(())
6422    /// ```
6423    pub fn tensor_view(&self) -> TensorView<'_> {
6424        tensor_view_with_layout(&self.owner, self.layout.clone())
6425    }
6426
6427    ///
6428    /// # Examples
6429    ///
6430    /// ```
6431    /// use tenferro_tensor::{Tensor, TensorValue};
6432    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6433    /// let read = value.tensor_read();
6434    /// assert_eq!(read.shape(), &[2, 2]);
6435    /// assert_eq!(read.dtype(), tenferro_tensor::DType::F64);
6436    /// # Ok::<(), tenferro_tensor::Error>(())
6437    /// ```
6438    pub fn tensor_read(&self) -> TensorRead<'_> {
6439        self.as_tensor()
6440            .map(TensorRead::from_tensor)
6441            .unwrap_or_else(|| TensorRead::from_view(self.tensor_view()))
6442    }
6443
6444    /// # Errors
6445    ///
6446    /// Returns [`crate::Error::Validation`] with
6447    /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
6448    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
6449    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
6450    /// not a valid permutation of the value rank.
6451    ///
6452    /// # Examples
6453    ///
6454    /// ```
6455    /// use tenferro_tensor::{Tensor, TensorValue};
6456    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6457    /// let view = value.transpose_view([1, 0])?;
6458    /// assert_eq!(view.strides(), &[2, 1]);
6459    /// assert!(view.is_view());
6460    /// # Ok::<(), tenferro_tensor::Error>(())
6461    /// ```
6462    pub fn transpose_view(self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
6463        let layout = self
6464            .layout
6465            .transpose_view(axes)
6466            .map_err(|err| tensor_layout_error("TensorValue::transpose_view", err))?;
6467        Ok(Self {
6468            owner: self.owner,
6469            layout,
6470        })
6471    }
6472
6473    // INVARIANT: unchanged-owner recovery is part of the consuming view
6474    // contract, so this intentionally carries the large move-only error.
6475    /// # Errors
6476    ///
6477    /// Returns [`crate::Error::Validation`] with
6478    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
6479    /// the source is not compact column-major,
6480    /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
6481    /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
6482    /// records the counts) when element counts differ,
6483    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
6484    /// arithmetic overflow, or
6485    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6486    /// reshaped view exceeds the backing buffer.
6487    #[allow(clippy::result_large_err)]
6488    ///
6489    /// # Examples
6490    ///
6491    /// ```
6492    /// use tenferro_tensor::{Tensor, TensorValue};
6493    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6494    /// let (recovered, error) = value.try_reshape_view([3]).unwrap_err().into_parts();
6495    /// assert_eq!(recovered.into_tensor()?.as_slice::<f64>()?, &[1., 2., 3., 4.]);
6496    /// assert!(matches!(error, tenferro_tensor::Error::Validation { .. }));
6497    /// # Ok::<(), tenferro_tensor::Error>(())
6498    /// ```
6499    pub fn try_reshape_view(
6500        self,
6501        shape: impl tenferro_tensor_core::IntoShapeVec,
6502    ) -> std::result::Result<Self, TensorValueViewError> {
6503        let shape = shape.into_shape_vec();
6504        let layout = match reshape_layout_dyn(
6505            &self.layout,
6506            &shape,
6507            tensor_buffer_len(&self.owner),
6508            "TensorValue::reshape_view",
6509        ) {
6510            Ok(layout) => layout,
6511            Err(error) => return Err(TensorValueViewError::new(self, error)),
6512        };
6513        Ok(Self {
6514            owner: self.owner,
6515            layout,
6516        })
6517    }
6518
6519    /// # Errors
6520    ///
6521    /// Returns [`crate::Error::Validation`] with
6522    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]
6523    /// when the source is not compact, [`tenferro_tensor_core::ValidationError::ShapeMismatch`]
6524    /// when element counts differ, or [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
6525    /// / [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] for invalid
6526    /// target-shape arithmetic or bounds.
6527    ///
6528    /// # Examples
6529    ///
6530    /// ```
6531    /// use tenferro_tensor::{Tensor, TensorValue};
6532    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6533    /// let view = value.reshape_view([4])?;
6534    /// assert_eq!(view.shape(), &[4]);
6535    /// assert_eq!(view.strides(), &[1]);
6536    /// # Ok::<(), tenferro_tensor::Error>(())
6537    /// ```
6538    pub fn reshape_view(
6539        self,
6540        shape: impl tenferro_tensor_core::IntoShapeVec,
6541    ) -> crate::Result<Self> {
6542        self.try_reshape_view(shape).map_err(|error| error.source)
6543    }
6544
6545    /// # Errors
6546    ///
6547    /// Returns [`crate::Error::Validation`] with
6548    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a slice
6549    /// vector does not match the value rank,
6550    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when a bound
6551    /// or stride cannot be represented or is invalid,
6552    /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
6553    /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for slice
6554    /// parameters, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
6555    /// for slice arithmetic overflow, or
6556    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6557    /// result exceeds the backing buffer.
6558    ///
6559    /// # Examples
6560    ///
6561    /// ```
6562    /// use tenferro_tensor::{Tensor, TensorValue};
6563    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6564    /// let view = value.slice_view(&tenferro_tensor::SliceConfig {
6565    ///    starts: vec![0, 1], limits: vec![2, 2], strides: vec![1, 1],
6566    /// })?;
6567    /// assert_eq!(view.shape(), &[2, 1]);
6568    /// assert_eq!(view.offset(), 2);
6569    /// # Ok::<(), tenferro_tensor::Error>(())
6570    /// ```
6571    pub fn slice_view(self, config: &SliceConfig) -> crate::Result<Self> {
6572        let op = "TensorValue::slice_view";
6573        if config.starts.len() != self.shape().len()
6574            || config.limits.len() != self.shape().len()
6575            || config.strides.len() != self.shape().len()
6576        {
6577            return Err(crate::Error::validation(
6578                op,
6579                ValidationError::RankMismatch {
6580                    expected: self.shape().len(),
6581                    actual: config.starts.len(),
6582                },
6583            ));
6584        }
6585        let mut slices = Vec::with_capacity(self.shape().len());
6586        for ((&start, &limit), &stride) in config
6587            .starts
6588            .iter()
6589            .zip(config.limits.iter())
6590            .zip(config.strides.iter())
6591        {
6592            let start = isize::try_from(start).map_err(|_| {
6593                crate::Error::invalid_argument(
6594                    op,
6595                    "slice start",
6596                    "slice start does not fit in isize",
6597                )
6598            })?;
6599            let limit = isize::try_from(limit).map_err(|_| {
6600                crate::Error::invalid_argument(
6601                    op,
6602                    "slice limit",
6603                    "slice limit does not fit in isize",
6604                )
6605            })?;
6606            let stride = isize::try_from(stride).map_err(|_| {
6607                crate::Error::invalid_argument(
6608                    op,
6609                    "slice stride",
6610                    "slice stride does not fit in isize",
6611                )
6612            })?;
6613            slices.push(StridedSliceSpec::new(start, Some(limit), stride));
6614        }
6615        let specs = core_slice_specs(&slices, self.shape(), op)?;
6616        let layout = self
6617            .layout
6618            .slice_view(&specs, tensor_buffer_len(&self.owner))
6619            .map_err(|err| tensor_layout_error(op, err))?;
6620        Ok(Self {
6621            owner: self.owner,
6622            layout,
6623        })
6624    }
6625
6626    /// # Errors
6627    ///
6628    /// Returns [`crate::Error::Validation`] with
6629    /// [`tenferro_tensor_core::ValidationError::RankMismatch`],
6630    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
6631    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] for invalid
6632    /// dimension mappings,
6633    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] for
6634    /// incompatible extents,
6635    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6636    /// result exceeds the backing buffer, or
6637    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
6638    /// arithmetic overflow.
6639    ///
6640    /// # Examples
6641    ///
6642    /// ```
6643    /// use tenferro_tensor::{Tensor, TensorValue};
6644    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6645    /// let view = value.broadcast_in_dim_view([2, 3], [0])?;
6646    /// assert_eq!(view.shape(), &[2, 3]);
6647    /// assert_eq!(view.strides(), &[1, 0]);
6648    /// # Ok::<(), tenferro_tensor::Error>(())
6649    /// ```
6650    pub fn broadcast_in_dim_view(
6651        self,
6652        shape: impl tenferro_tensor_core::IntoShapeVec,
6653        dims: impl AsRef<[usize]>,
6654    ) -> crate::Result<Self> {
6655        let shape = shape.into_shape_vec();
6656        let layout = self
6657            .layout
6658            .broadcast_in_dim_view::<DynRank>(shape, dims, tensor_buffer_len(&self.owner))
6659            .map_err(|err| tensor_layout_error("TensorValue::broadcast_in_dim_view", err))?;
6660        Ok(Self {
6661            owner: self.owner,
6662            layout,
6663        })
6664    }
6665}
6666
6667fn tensor_layout(tensor: &Tensor) -> TensorLayout<DynRank> {
6668    match &tensor.payload {
6669        // A caller-owned payload carries its own view layout, so strides and offset
6670        // come from the payload rather than from a compact assumption.
6671        TensorPayload::External(payload, _) => TensorLayout::<DynRank>::from_parts(
6672            tenferro_tensor_core::ShapeVec::from_slice(payload.shape()),
6673            tenferro_tensor_core::StrideVec::from_slice(payload.strides()),
6674            payload.offset(),
6675            payload.payload_element_count(),
6676        )
6677        .unwrap_or_else(|_| {
6678            // INVARIANT: a payload is built from a validated host tensor, and the
6679            // only layout change is a permutation, so its strides stay inside the
6680            // payload it was created from and this construction cannot fail.
6681            unreachable!("a validated payload yields a layout inside its own storage")
6682        }),
6683        TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.layout()),
6684    }
6685}
6686
6687fn tensor_buffer_len(tensor: &Tensor) -> usize {
6688    match &tensor.payload {
6689        // A caller-owned payload stores its element count directly.
6690        TensorPayload::External(payload, _) => payload.element_count(),
6691        TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.buffer_len()),
6692    }
6693}
6694
6695fn prepare_backend_access<'a, T: 'static, R: TensorRank>(
6696    buffer: &'a dyn BackendStorage<T>,
6697    layout: &'a TensorLayout<R>,
6698    op: &'static str,
6699) -> crate::Result<Box<dyn PreparedDeviceAccess + 'a>> {
6700    let domain = buffer.allocation_domain().ok_or_else(|| {
6701        crate::Error::runtime_state(op, "backend buffer is missing an allocation domain")
6702    })?;
6703    let allocation_id = buffer.allocation_id().ok_or_else(|| {
6704        crate::Error::runtime_state(op, "backend buffer is missing an allocation identity")
6705    })?;
6706    let byte_len = buffer
6707        .len()
6708        .checked_mul(size_of::<T>())
6709        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6710    let request = DeviceAccessRequest::new(
6711        domain,
6712        allocation_id,
6713        byte_len,
6714        size_of::<T>(),
6715        layout.shape(),
6716        layout.strides(),
6717        layout.offset(),
6718    );
6719    buffer
6720        .prepare_device_access(request)
6721        .map_err(|error| crate::Error::runtime_state_source(op, error))
6722}
6723
6724fn cast_view_slice<S: 'static, T: TensorScalar>(source: &[S]) -> crate::Result<&[T]> {
6725    if size_of::<S>() != size_of::<T>() || align_of::<S>() != align_of::<T>() {
6726        return Err(crate::Error::invalid_argument(
6727            "TensorView::as_slice",
6728            "dtype",
6729            "matching dtypes must have identical scalar layout",
6730        ));
6731    }
6732    // SAFETY: the dtype check above is exhaustive over the sealed scalar set;
6733    // equal size/alignment preserve the element boundaries and the source
6734    // slice remains borrowed for the returned lifetime.
6735    Ok(unsafe { std::slice::from_raw_parts(source.as_ptr().cast::<T>(), source.len()) })
6736}
6737
6738fn tensor_view_with_layout(tensor: &Tensor, layout: TensorLayout<DynRank>) -> TensorView<'_> {
6739    match tensor.dtype() {
6740        DType::F32 => TensorView::F32(typed_view_with_layout(
6741            tensor
6742                .as_typed::<f32>()
6743                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6744            layout,
6745        )),
6746        DType::F64 => TensorView::F64(typed_view_with_layout(
6747            tensor
6748                .as_typed::<f64>()
6749                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6750            layout,
6751        )),
6752        DType::I32 => TensorView::I32(typed_view_with_layout(
6753            tensor
6754                .as_typed::<i32>()
6755                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6756            layout,
6757        )),
6758        DType::I64 => TensorView::I64(typed_view_with_layout(
6759            tensor
6760                .as_typed::<i64>()
6761                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6762            layout,
6763        )),
6764        DType::Bool => TensorView::Bool(typed_view_with_layout(
6765            tensor
6766                .as_typed::<bool>()
6767                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6768            layout,
6769        )),
6770        DType::C32 => TensorView::C32(typed_view_with_layout(
6771            tensor
6772                .as_typed::<Complex<f32>>()
6773                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6774            layout,
6775        )),
6776        DType::C64 => TensorView::C64(typed_view_with_layout(
6777            tensor
6778                .as_typed::<Complex<f64>>()
6779                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6780            layout,
6781        )),
6782        // INVARIANT: `TensorView` has no externally defined variant, and a view is
6783        // never requested for a caller-owned payload.
6784        DType::External(_) => unreachable!("views cover the preset scalars"),
6785    }
6786}
6787
6788fn typed_view_with_layout<T: TensorScalar + 'static>(
6789    tensor: &TypedTensor<T>,
6790    layout: TensorLayout<DynRank>,
6791) -> TypedTensorView<'_, T> {
6792    match &tensor.storage {
6793        DynamicStorage::Host(HostStorage { data }) => TypedTensorView {
6794            buffer: TensorStorageRef::Host(data.as_slice()),
6795            root: None,
6796            layout,
6797            placement: tensor.placement.clone(),
6798            _representation: std::marker::PhantomData,
6799        },
6800        DynamicStorage::Group(core) => {
6801            let root = core.group.view::<T>().unwrap_or_else(|error| {
6802                unreachable!("typed tensor group descriptor mismatch: {error}")
6803            });
6804            let buffer = if let Some(allocation) = root.backend_allocation() {
6805                TensorStorageRef::Root(allocation)
6806            } else {
6807                TensorStorageRef::Host(tensor.group_host_slice())
6808            };
6809            TypedTensorView {
6810                buffer,
6811                root: Some(root),
6812                layout,
6813                placement: tensor.placement.clone(),
6814                _representation: std::marker::PhantomData,
6815            }
6816        }
6817    }
6818}
6819
6820pub(crate) fn tensor_view_from_group<'a, T: TensorScalar>(
6821    view: GroupReadView<'a, T, DynRank>,
6822) -> crate::Result<TensorView<'a>> {
6823    let buffer = if let Some(allocation) = view.backend_allocation() {
6824        TensorStorageRef::Root(allocation)
6825    } else {
6826        let storage = view.storage_buffer().ok_or_else(|| {
6827            crate::Error::runtime_state(
6828                "AllocationGroup::tensor_read",
6829                "group descriptor has no backing storage",
6830            )
6831        })?;
6832        match storage {
6833            StorageBuffer::Host(data) => TensorStorageRef::Host(data),
6834            StorageBuffer::Backend(buffer) => TensorStorageRef::Backend(buffer.as_ref()),
6835        }
6836    };
6837    let layout = view.descriptor().layout().clone();
6838    let placement = view.descriptor().placement().clone();
6839    let typed = TypedTensorView {
6840        buffer,
6841        root: Some(view.clone()),
6842        layout,
6843        placement,
6844        _representation: std::marker::PhantomData,
6845    };
6846    Ok(T::tensor_view(typed))
6847}
6848
6849pub(crate) fn tensor_from_group(
6850    group: AllocationGroup,
6851    slot: DescriptorSlot,
6852    allocation_index: AllocationSlot,
6853    dtype: DType,
6854    layout: TensorLayout<DynRank>,
6855    placement: Placement,
6856) -> Tensor {
6857    fn typed<T: TensorScalar>(
6858        group: AllocationGroup,
6859        slot: DescriptorSlot,
6860        allocation_index: AllocationSlot,
6861        layout: TensorLayout<DynRank>,
6862        placement: Placement,
6863    ) -> TypedTensor<T> {
6864        let descriptor_dtype = group
6865            .descriptor_dtype(slot)
6866            .unwrap_or_else(|| unreachable!("tensor_from_group requires a live typed descriptor"));
6867        assert_eq!(
6868            descriptor_dtype,
6869            T::dtype(),
6870            "managed typed owner must match its descriptor dtype",
6871        );
6872        let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
6873        TypedTensor {
6874            shape: shape_vec(layout.shape()),
6875            placement,
6876            storage: DynamicStorage::Group(GroupStorage {
6877                group: Box::new(OwnedTensorGroup {
6878                    group,
6879                    slot,
6880                    allocation_index,
6881                    host_ptr,
6882                    host_byte_len,
6883                    _rank: PhantomData,
6884                }),
6885            }),
6886        }
6887    }
6888
6889    match dtype {
6890        DType::F32 => <f32 as TensorScalar>::typed_tensor_into_tensor(typed::<f32>(
6891            group,
6892            slot,
6893            allocation_index,
6894            layout,
6895            placement,
6896        )),
6897        DType::F64 => <f64 as TensorScalar>::typed_tensor_into_tensor(typed::<f64>(
6898            group,
6899            slot,
6900            allocation_index,
6901            layout,
6902            placement,
6903        )),
6904        DType::I32 => <i32 as TensorScalar>::typed_tensor_into_tensor(typed::<i32>(
6905            group,
6906            slot,
6907            allocation_index,
6908            layout,
6909            placement,
6910        )),
6911        DType::I64 => <i64 as TensorScalar>::typed_tensor_into_tensor(typed::<i64>(
6912            group,
6913            slot,
6914            allocation_index,
6915            layout,
6916            placement,
6917        )),
6918        DType::Bool => <bool as TensorScalar>::typed_tensor_into_tensor(typed::<bool>(
6919            group,
6920            slot,
6921            allocation_index,
6922            layout,
6923            placement,
6924        )),
6925        DType::C32 => <Complex<f32> as TensorScalar>::typed_tensor_into_tensor(
6926            typed::<Complex<f32>>(group, slot, allocation_index, layout, placement),
6927        ),
6928        DType::C64 => <Complex<f64> as TensorScalar>::typed_tensor_into_tensor(
6929            typed::<Complex<f64>>(group, slot, allocation_index, layout, placement),
6930        ),
6931        // INVARIANT: an allocation group is created from a sealed preset scalar,
6932        // so it can never carry an externally defined one. External payloads are
6933        // caller-owned and do not enter a group.
6934        DType::External(_) => unreachable!("allocation groups are preset-typed"),
6935    }
6936}
6937
6938/// Wrap an `f64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
6939///
6940/// # Examples
6941///
6942/// ```
6943/// use tenferro_tensor::{Tensor, TypedTensor};
6944///
6945/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
6946/// let tensor: Tensor = typed.into();
6947/// assert_eq!(tensor.shape(), &[2]);
6948/// ```
6949impl From<TypedTensor<f64>> for Tensor {
6950    fn from(t: TypedTensor<f64>) -> Self {
6951        Tensor::from_typed(t)
6952    }
6953}
6954
6955/// Wrap an `f32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
6956///
6957/// # Examples
6958///
6959/// ```
6960/// use tenferro_tensor::{Tensor, TypedTensor};
6961///
6962/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
6963/// let tensor: Tensor = typed.into();
6964/// assert_eq!(tensor.shape(), &[2]);
6965/// ```
6966impl From<TypedTensor<f32>> for Tensor {
6967    fn from(t: TypedTensor<f32>) -> Self {
6968        Tensor::from_typed(t)
6969    }
6970}
6971
6972/// Wrap an `i64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
6973///
6974/// # Examples
6975///
6976/// ```
6977/// use tenferro_tensor::{DType, Tensor, TypedTensor};
6978///
6979/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i64, 2]).unwrap();
6980/// let tensor: Tensor = typed.into();
6981/// assert_eq!(tensor.dtype(), DType::I64);
6982/// assert_eq!(tensor.shape(), &[2]);
6983/// ```
6984impl From<TypedTensor<i64>> for Tensor {
6985    fn from(t: TypedTensor<i64>) -> Self {
6986        Tensor::from_typed(t)
6987    }
6988}
6989
6990/// Wrap an `i32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
6991///
6992/// # Examples
6993///
6994/// ```
6995/// use tenferro_tensor::{DType, Tensor, TypedTensor};
6996///
6997/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i32, 2]).unwrap();
6998/// let tensor: Tensor = typed.into();
6999/// assert_eq!(tensor.dtype(), DType::I32);
7000/// assert_eq!(tensor.shape(), &[2]);
7001/// ```
7002impl From<TypedTensor<i32>> for Tensor {
7003    fn from(t: TypedTensor<i32>) -> Self {
7004        Tensor::from_typed(t)
7005    }
7006}
7007
7008/// Wrap a `bool` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7009///
7010/// # Examples
7011///
7012/// ```
7013/// use tenferro_tensor::{DType, Tensor, TypedTensor};
7014///
7015/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![true, false]).unwrap();
7016/// let tensor: Tensor = typed.into();
7017/// assert_eq!(tensor.dtype(), DType::Bool);
7018/// assert_eq!(tensor.shape(), &[2]);
7019/// ```
7020impl From<TypedTensor<bool>> for Tensor {
7021    fn from(t: TypedTensor<bool>) -> Self {
7022        Tensor::from_typed(t)
7023    }
7024}
7025
7026/// Wrap a [`Complex64`] [`TypedTensor`] into the corresponding [`Tensor`]
7027/// variant.
7028///
7029/// # Examples
7030///
7031/// ```
7032/// use num_complex::Complex64;
7033/// use tenferro_tensor::{Tensor, TypedTensor};
7034///
7035/// let typed = TypedTensor::from_vec_col_major(
7036///     vec![1],
7037///     vec![Complex64::new(1.0, 2.0)],
7038/// ).unwrap();
7039/// let tensor: Tensor = typed.into();
7040/// assert_eq!(tensor.shape(), &[1]);
7041/// ```
7042impl From<TypedTensor<Complex<f64>>> for Tensor {
7043    fn from(t: TypedTensor<Complex<f64>>) -> Self {
7044        Tensor::from_typed(t)
7045    }
7046}
7047
7048/// Wrap a [`Complex32`] [`TypedTensor`] into the corresponding [`Tensor`]
7049/// variant.
7050///
7051/// # Examples
7052///
7053/// ```
7054/// use num_complex::Complex32;
7055/// use tenferro_tensor::{Tensor, TypedTensor};
7056///
7057/// let typed = TypedTensor::from_vec_col_major(
7058///     vec![1],
7059///     vec![Complex32::new(1.0, 2.0)],
7060/// ).unwrap();
7061/// let tensor: Tensor = typed.into();
7062/// assert_eq!(tensor.shape(), &[1]);
7063/// ```
7064impl From<TypedTensor<Complex<f32>>> for Tensor {
7065    fn from(t: TypedTensor<Complex<f32>>) -> Self {
7066        Tensor::from_typed(t)
7067    }
7068}
7069
7070impl<'a> TensorView<'a> {
7071    /// Create a dynamic `f32` view over compact column-major host data.
7072    ///
7073    /// # Examples
7074    ///
7075    /// ```
7076    /// use tenferro_tensor::{DType, TensorView};
7077    ///
7078    /// let data = [1.0_f32, 2.0];
7079    /// let view = TensorView::f32(&[2], &data)?;
7080    /// assert_eq!(view.dtype(), DType::F32);
7081    /// # Ok::<(), tenferro_tensor::Error>(())
7082    /// ```
7083    /// # Errors
7084    ///
7085    /// Returns [`crate::Error::Validation`] with
7086    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7087    /// shape or offset arithmetic overflow, or
7088    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7089    /// compact shape reaches beyond `data`.
7090    pub fn f32(shape: &'a [usize], data: &'a [f32]) -> crate::Result<Self> {
7091        Ok(Self::F32(TypedTensorView::from_col_major(shape, data)?))
7092    }
7093
7094    /// Create a dynamic `f64` view over compact column-major host data.
7095    ///
7096    /// # Examples
7097    ///
7098    /// ```
7099    /// use tenferro_tensor::{DType, TensorView};
7100    ///
7101    /// let data = [1.0_f64, 2.0];
7102    /// let view = TensorView::f64(&[2], &data)?;
7103    /// assert_eq!(view.dtype(), DType::F64);
7104    /// # Ok::<(), tenferro_tensor::Error>(())
7105    /// ```
7106    /// # Errors
7107    ///
7108    /// Returns [`crate::Error::Validation`] with
7109    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7110    /// shape or offset arithmetic overflow, or
7111    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7112    /// compact shape reaches beyond `data`.
7113    pub fn f64(shape: &'a [usize], data: &'a [f64]) -> crate::Result<Self> {
7114        Ok(Self::F64(TypedTensorView::from_col_major(shape, data)?))
7115    }
7116
7117    /// Create a dynamic `i64` view over compact column-major host data.
7118    ///
7119    /// # Examples
7120    ///
7121    /// ```
7122    /// use tenferro_tensor::{DType, TensorView};
7123    ///
7124    /// let data = [1_i64, 2];
7125    /// let view = TensorView::i64(&[2], &data)?;
7126    /// assert_eq!(view.dtype(), DType::I64);
7127    /// # Ok::<(), tenferro_tensor::Error>(())
7128    /// ```
7129    /// # Errors
7130    ///
7131    /// Returns [`crate::Error::Validation`] with
7132    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7133    /// shape or offset arithmetic overflow, or
7134    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7135    /// compact shape reaches beyond `data`.
7136    pub fn i64(shape: &'a [usize], data: &'a [i64]) -> crate::Result<Self> {
7137        Ok(Self::I64(TypedTensorView::from_col_major(shape, data)?))
7138    }
7139
7140    /// Create a dynamic `i32` view over compact column-major host data.
7141    ///
7142    /// # Examples
7143    ///
7144    /// ```
7145    /// use tenferro_tensor::{DType, TensorView};
7146    ///
7147    /// let data = [1_i32, 2];
7148    /// let view = TensorView::i32(&[2], &data)?;
7149    /// assert_eq!(view.dtype(), DType::I32);
7150    /// # Ok::<(), tenferro_tensor::Error>(())
7151    /// ```
7152    /// # Errors
7153    ///
7154    /// Returns [`crate::Error::Validation`] with
7155    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7156    /// shape or offset arithmetic overflow, or
7157    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7158    /// compact shape reaches beyond `data`.
7159    pub fn i32(shape: &'a [usize], data: &'a [i32]) -> crate::Result<Self> {
7160        Ok(Self::I32(TypedTensorView::from_col_major(shape, data)?))
7161    }
7162
7163    /// Create a dynamic `bool` view over compact column-major host data.
7164    ///
7165    /// # Examples
7166    ///
7167    /// ```
7168    /// use tenferro_tensor::{DType, TensorView};
7169    ///
7170    /// let data = [true, false];
7171    /// let view = TensorView::bool(&[2], &data)?;
7172    /// assert_eq!(view.dtype(), DType::Bool);
7173    /// # Ok::<(), tenferro_tensor::Error>(())
7174    /// ```
7175    /// # Errors
7176    ///
7177    /// Returns [`crate::Error::Validation`] with
7178    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7179    /// shape or offset arithmetic overflow, or
7180    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7181    /// compact shape reaches beyond `data`.
7182    pub fn bool(shape: &'a [usize], data: &'a [bool]) -> crate::Result<Self> {
7183        Ok(Self::Bool(TypedTensorView::from_col_major(shape, data)?))
7184    }
7185
7186    /// Create a dynamic `Complex32` view over compact column-major host data.
7187    ///
7188    /// # Examples
7189    ///
7190    /// ```
7191    /// use num_complex::Complex32;
7192    /// use tenferro_tensor::{DType, TensorView};
7193    ///
7194    /// let data = [Complex32::new(1.0, 2.0)];
7195    /// let view = TensorView::c32(&[1], &data)?;
7196    /// assert_eq!(view.dtype(), DType::C32);
7197    /// # Ok::<(), tenferro_tensor::Error>(())
7198    /// ```
7199    /// # Errors
7200    ///
7201    /// Returns [`crate::Error::Validation`] with
7202    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7203    /// shape or offset arithmetic overflow, or
7204    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7205    /// compact shape reaches beyond `data`.
7206    pub fn c32(shape: &'a [usize], data: &'a [Complex32]) -> crate::Result<Self> {
7207        Ok(Self::C32(TypedTensorView::from_col_major(shape, data)?))
7208    }
7209
7210    /// Create a dynamic `Complex64` view over compact column-major host data.
7211    ///
7212    /// # Examples
7213    ///
7214    /// ```
7215    /// use num_complex::Complex64;
7216    /// use tenferro_tensor::{DType, TensorView};
7217    ///
7218    /// let data = [Complex64::new(1.0, 2.0)];
7219    /// let view = TensorView::c64(&[1], &data)?;
7220    /// assert_eq!(view.dtype(), DType::C64);
7221    /// # Ok::<(), tenferro_tensor::Error>(())
7222    /// ```
7223    /// # Errors
7224    ///
7225    /// Returns [`crate::Error::Validation`] with
7226    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7227    /// shape or offset arithmetic overflow, or
7228    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7229    /// compact shape reaches beyond `data`.
7230    pub fn c64(shape: &'a [usize], data: &'a [Complex64]) -> crate::Result<Self> {
7231        Ok(Self::C64(TypedTensorView::from_col_major(shape, data)?))
7232    }
7233
7234    /// Return the element dtype of this borrowed view.
7235    ///
7236    /// # Examples
7237    ///
7238    /// ```
7239    /// use tenferro_tensor::{DType, TensorView};
7240    ///
7241    /// let view = TensorView::f64(&[2], &[1.0, 2.0])?;
7242    /// assert_eq!(view.dtype(), DType::F64);
7243    /// # Ok::<(), tenferro_tensor::Error>(())
7244    /// ```
7245    pub fn dtype(&self) -> DType {
7246        match self {
7247            Self::F32(_) => DType::F32,
7248            Self::F64(_) => DType::F64,
7249            Self::I32(_) => DType::I32,
7250            Self::I64(_) => DType::I64,
7251            Self::Bool(_) => DType::Bool,
7252            Self::C32(_) => DType::C32,
7253            Self::C64(_) => DType::C64,
7254        }
7255    }
7256
7257    /// Return the logical shape of this borrowed view.
7258    ///
7259    /// # Examples
7260    ///
7261    /// ```
7262    /// use tenferro_tensor::TensorView;
7263    ///
7264    /// let view = TensorView::i32(&[2, 1], &[1, 2])?;
7265    /// assert_eq!(view.shape(), &[2, 1]);
7266    /// # Ok::<(), tenferro_tensor::Error>(())
7267    /// ```
7268    pub fn shape(&self) -> &[usize] {
7269        match self {
7270            Self::F32(t) => t.shape(),
7271            Self::F64(t) => t.shape(),
7272            Self::I32(t) => t.shape(),
7273            Self::I64(t) => t.shape(),
7274            Self::Bool(t) => t.shape(),
7275            Self::C32(t) => t.shape(),
7276            Self::C64(t) => t.shape(),
7277        }
7278    }
7279
7280    /// Borrow a contiguous host slice when the requested scalar matches this view's dtype.
7281    ///
7282    /// Backend buffers and non-contiguous views return an explicit error. No
7283    /// download or materialization is performed.
7284    ///
7285    /// # Errors
7286    ///
7287    /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
7288    /// view dtype, [`ValidationError::NonContiguousViewAsSlice`] for a
7289    /// non-contiguous layout, or [`crate::Error::HostAccess`] for unavailable
7290    /// backend host access.
7291    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
7292        if self.dtype() != T::dtype() {
7293            return Err(crate::Error::validation(
7294                "TensorView::as_slice",
7295                ValidationError::DTypeMismatch {
7296                    expected: T::dtype(),
7297                    actual: self.dtype(),
7298                },
7299            ));
7300        }
7301        match self {
7302            Self::F32(view) => cast_view_slice(view.as_slice()?),
7303            Self::F64(view) => cast_view_slice(view.as_slice()?),
7304            Self::I32(view) => cast_view_slice(view.as_slice()?),
7305            Self::I64(view) => cast_view_slice(view.as_slice()?),
7306            Self::Bool(view) => cast_view_slice(view.as_slice()?),
7307            Self::C32(view) => cast_view_slice(view.as_slice()?),
7308            Self::C64(view) => cast_view_slice(view.as_slice()?),
7309        }
7310    }
7311
7312    /// Reinterpret a complex view as its sealed real representation.
7313    ///
7314    /// # Examples
7315    ///
7316    /// ```
7317    /// use tenferro_tensor::{Complex64, DType, TensorView};
7318    ///
7319    /// let data = [Complex64::new(1.0, 2.0)];
7320    /// let real = TensorView::c64(&[1], &data)?.as_real_view()?;
7321    /// assert_eq!(real.dtype(), DType::F64);
7322    /// assert_eq!(real.as_slice::<f64>()?, &[1.0, 2.0]);
7323    /// # Ok::<(), tenferro_tensor::Error>(())
7324    /// ```
7325    ///
7326    /// # Errors
7327    ///
7328    /// Returns [`crate::Error::Unsupported`] for the wrong dtype pair and
7329    /// [`ValidationError::InvalidArgument`] or
7330    /// [`ValidationError::ViewOutOfBounds`] for invalid layout metadata.
7331    pub fn as_real_view(&self) -> crate::Result<Self> {
7332        match self {
7333            Self::C32(t) => t.as_real_view().map(Self::F32),
7334            Self::C64(t) => t.as_real_view().map(Self::F64),
7335            _ => Err(crate::Error::unsupported(
7336                "TensorView::as_real_view",
7337                "only complex views have a sealed real representation",
7338            )),
7339        }
7340    }
7341
7342    /// Reinterpret a real view as its sealed complex representation.
7343    ///
7344    /// # Examples
7345    ///
7346    /// ```
7347    /// use tenferro_tensor::{Complex64, DType, TensorView};
7348    ///
7349    /// let data = [1.0_f64, 2.0];
7350    /// let complex = TensorView::f64(&[2, 1], &data)?.as_complex_view()?;
7351    /// assert_eq!(complex.dtype(), DType::C64);
7352    /// assert_eq!(complex.as_slice::<Complex64>()?, &[Complex64::new(1.0, 2.0)]);
7353    /// # Ok::<(), tenferro_tensor::Error>(())
7354    /// ```
7355    ///
7356    /// # Errors
7357    ///
7358    /// Returns [`crate::Error::Unsupported`] for the wrong dtype pair and
7359    /// [`ValidationError::InvalidArgument`] or
7360    /// [`ValidationError::ViewOutOfBounds`] for invalid layout metadata.
7361    pub fn as_complex_view(&self) -> crate::Result<Self> {
7362        match self {
7363            Self::F32(t) => t.as_complex_view().map(Self::C32),
7364            Self::F64(t) => t.as_complex_view().map(Self::C64),
7365            _ => Err(crate::Error::unsupported(
7366                "TensorView::as_complex_view",
7367                "only real views have a sealed complex representation",
7368            )),
7369        }
7370    }
7371
7372    /// Return the placement metadata carried by this borrowed view.
7373    ///
7374    /// # Examples
7375    ///
7376    /// ```
7377    /// use tenferro_tensor::{MemoryKind, TensorView};
7378    ///
7379    /// let view = TensorView::f64(&[1], &[1.0])?;
7380    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
7381    /// # Ok::<(), tenferro_tensor::Error>(())
7382    /// ```
7383    pub fn placement(&self) -> &Placement {
7384        match self {
7385            Self::F32(t) => t.placement(),
7386            Self::F64(t) => t.placement(),
7387            Self::I32(t) => t.placement(),
7388            Self::I64(t) => t.placement(),
7389            Self::Bool(t) => t.placement(),
7390            Self::C32(t) => t.placement(),
7391            Self::C64(t) => t.placement(),
7392        }
7393    }
7394
7395    /// Return the physical backend family, when this view is backend-owned.
7396    ///
7397    /// # Examples
7398    ///
7399    /// ```
7400    /// use tenferro_tensor::TensorView;
7401    ///
7402    /// let view = TensorView::f64(&[1], &[1.0])?;
7403    /// assert_eq!(view.backend_family(), None);
7404    /// # Ok::<(), tenferro_tensor::Error>(())
7405    /// ```
7406    pub fn backend_family(&self) -> Option<&'static str> {
7407        match self {
7408            Self::F32(t) => t.backend_family(),
7409            Self::F64(t) => t.backend_family(),
7410            Self::I32(t) => t.backend_family(),
7411            Self::I64(t) => t.backend_family(),
7412            Self::Bool(t) => t.backend_family(),
7413            Self::C32(t) => t.backend_family(),
7414            Self::C64(t) => t.backend_family(),
7415        }
7416    }
7417
7418    /// Return the shared allocation domain, when this view has one.
7419    ///
7420    /// # Examples
7421    ///
7422    /// ```
7423    /// use tenferro_tensor::TensorView;
7424    ///
7425    /// let view = TensorView::f64(&[1], &[1.0])?;
7426    /// assert_eq!(view.allocation_domain(), None);
7427    /// # Ok::<(), tenferro_tensor::Error>(())
7428    /// ```
7429    pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
7430        match self {
7431            Self::F32(t) => t.allocation_domain(),
7432            Self::F64(t) => t.allocation_domain(),
7433            Self::I32(t) => t.allocation_domain(),
7434            Self::I64(t) => t.allocation_domain(),
7435            Self::Bool(t) => t.allocation_domain(),
7436            Self::C32(t) => t.allocation_domain(),
7437            Self::C64(t) => t.allocation_domain(),
7438        }
7439    }
7440
7441    /// Return strides in element units.
7442    ///
7443    /// # Examples
7444    ///
7445    /// ```
7446    /// use tenferro_tensor::TensorView;
7447    ///
7448    /// let data = [0.0_f64; 6];
7449    /// let view = TensorView::f64(&[2, 3], &data)?;
7450    /// assert_eq!(view.strides(), &[1, 2]);
7451    /// # Ok::<(), tenferro_tensor::Error>(())
7452    /// ```
7453    pub fn strides(&self) -> &[isize] {
7454        match self {
7455            Self::F32(t) => t.strides(),
7456            Self::F64(t) => t.strides(),
7457            Self::I32(t) => t.strides(),
7458            Self::I64(t) => t.strides(),
7459            Self::Bool(t) => t.strides(),
7460            Self::C32(t) => t.strides(),
7461            Self::C64(t) => t.strides(),
7462        }
7463    }
7464
7465    /// Return the physical element offset.
7466    pub fn offset(&self) -> isize {
7467        match self {
7468            Self::F32(t) => t.offset(),
7469            Self::F64(t) => t.offset(),
7470            Self::I32(t) => t.offset(),
7471            Self::I64(t) => t.offset(),
7472            Self::Bool(t) => t.offset(),
7473            Self::C32(t) => t.offset(),
7474            Self::C64(t) => t.offset(),
7475        }
7476    }
7477
7478    /// Compute the physical element offset for a logical index.
7479    /// # Errors
7480    ///
7481    /// Returns [`crate::Error::Validation`] with
7482    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7483    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7484    /// when an index is outside its axis extent, or
7485    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7486    /// arithmetic overflows.
7487    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7488        match self {
7489            Self::F32(t) => t.layout_linear_offset(indices),
7490            Self::F64(t) => t.layout_linear_offset(indices),
7491            Self::I32(t) => t.layout_linear_offset(indices),
7492            Self::I64(t) => t.layout_linear_offset(indices),
7493            Self::Bool(t) => t.layout_linear_offset(indices),
7494            Self::C32(t) => t.layout_linear_offset(indices),
7495            Self::C64(t) => t.layout_linear_offset(indices),
7496        }
7497    }
7498
7499    /// Return whether this view is compact column-major.
7500    /// # Errors
7501    ///
7502    /// Returns [`crate::Error::Validation`] with
7503    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7504    /// compactness arithmetic overflows.
7505    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
7506        match self {
7507            Self::F32(t) => t.is_col_major_contiguous(),
7508            Self::F64(t) => t.is_col_major_contiguous(),
7509            Self::I32(t) => t.is_col_major_contiguous(),
7510            Self::I64(t) => t.is_col_major_contiguous(),
7511            Self::Bool(t) => t.is_col_major_contiguous(),
7512            Self::C32(t) => t.is_col_major_contiguous(),
7513            Self::C64(t) => t.is_col_major_contiguous(),
7514        }
7515    }
7516
7517    /// Return a compact string summary of this view's layout metadata.
7518    pub fn layout_summary(&self) -> String {
7519        layout_summary(self.shape(), self.strides(), self.offset())
7520    }
7521
7522    /// Assert this view is compact column-major.
7523    /// # Errors
7524    ///
7525    /// Returns [`crate::Error::Validation`] with
7526    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7527    /// compactness arithmetic overflows, or
7528    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7529    /// view is not compact column-major.
7530    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
7531        assert_layout_col_major_contiguous(
7532            self.is_col_major_contiguous()?,
7533            self.shape(),
7534            self.strides(),
7535            self.offset(),
7536            "TensorView::assert_col_major_contiguous",
7537        )
7538    }
7539
7540    /// Explicitly duplicate a compact host view into a fresh tensor.
7541    ///
7542    /// Backend views and non-contiguous layouts return a typed error; this
7543    /// operation never downloads or silently canonicalizes a view.
7544    ///
7545    /// # Examples
7546    ///
7547    /// ```
7548    /// use tenferro_tensor::{TensorView, TypedTensorView};
7549    ///
7550    /// let data = [1_i32, 2];
7551    /// let view = TensorView::I32(TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?);
7552    /// let copy = view.duplicate()?;
7553    /// assert_eq!(copy.shape(), &[2]);
7554    /// # Ok::<(), tenferro_tensor::Error>(())
7555    /// ```
7556    ///
7557    /// # Errors
7558    ///
7559    /// Returns [`crate::Error::HostAccess`] for backend-owned views,
7560    /// [`ValidationError::NonContiguousViewAsSlice`] for non-contiguous views,
7561    /// or [`ValidationError::InvalidArgument`] for invalid layout metadata.
7562    pub fn duplicate(&self) -> crate::Result<Tensor> {
7563        fn duplicate_typed<T: TensorScalar>(
7564            view: &TypedTensorView<'_, T>,
7565        ) -> crate::Result<TypedTensor<T>> {
7566            let mut tensor = TypedTensor::<T>::from_vec_col_major(
7567                view.shape().to_vec(),
7568                view.as_slice()?.to_vec(),
7569            )?;
7570            tensor.set_placement(view.placement().clone());
7571            Ok(tensor)
7572        }
7573
7574        match self {
7575            Self::F32(view) => duplicate_typed(view).map(Tensor::from_typed),
7576            Self::F64(view) => duplicate_typed(view).map(Tensor::from_typed),
7577            Self::I32(view) => duplicate_typed(view).map(Tensor::from_typed),
7578            Self::I64(view) => duplicate_typed(view).map(Tensor::from_typed),
7579            Self::Bool(view) => duplicate_typed(view).map(Tensor::from_typed),
7580            Self::C32(view) => duplicate_typed(view).map(Tensor::from_typed),
7581            Self::C64(view) => duplicate_typed(view).map(Tensor::from_typed),
7582        }
7583    }
7584}
7585
7586macro_rules! tensor_view_mut_constructor {
7587    ($name:ident, $variant:ident, $scalar:ty) => {
7588        /// Create a dynamic mutable view over compact column-major host data.
7589        ///
7590        /// # Errors
7591        ///
7592        /// Returns [`crate::Error::Validation`] with
7593        /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7594        /// compact layout arithmetic overflows, or
7595        /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7596        /// requested shape exceeds `data`.
7597        pub fn $name(shape: &'a [usize], data: &'a mut [$scalar]) -> crate::Result<Self> {
7598            Ok(Self::$variant(TypedTensorViewMut::from_col_major(
7599                shape, data,
7600            )?))
7601        }
7602    };
7603}
7604
7605impl<'a> TensorViewMut<'a> {
7606    tensor_view_mut_constructor!(f32, F32, f32);
7607    tensor_view_mut_constructor!(f64, F64, f64);
7608    tensor_view_mut_constructor!(i32, I32, i32);
7609    tensor_view_mut_constructor!(i64, I64, i64);
7610    tensor_view_mut_constructor!(bool, Bool, bool);
7611    tensor_view_mut_constructor!(c32, C32, Complex32);
7612    tensor_view_mut_constructor!(c64, C64, Complex64);
7613
7614    pub fn dtype(&self) -> DType {
7615        match self {
7616            Self::F32(_) => DType::F32,
7617            Self::F64(_) => DType::F64,
7618            Self::I32(_) => DType::I32,
7619            Self::I64(_) => DType::I64,
7620            Self::Bool(_) => DType::Bool,
7621            Self::C32(_) => DType::C32,
7622            Self::C64(_) => DType::C64,
7623        }
7624    }
7625
7626    pub fn shape(&self) -> &[usize] {
7627        match self {
7628            Self::F32(t) => t.shape(),
7629            Self::F64(t) => t.shape(),
7630            Self::I32(t) => t.shape(),
7631            Self::I64(t) => t.shape(),
7632            Self::Bool(t) => t.shape(),
7633            Self::C32(t) => t.shape(),
7634            Self::C64(t) => t.shape(),
7635        }
7636    }
7637
7638    /// Return strides in element units.
7639    ///
7640    /// # Examples
7641    ///
7642    /// ```
7643    /// use tenferro_tensor::TensorViewMut;
7644    ///
7645    /// let mut data = [0.0_f64; 6];
7646    /// let view = TensorViewMut::f64(&[2, 3], &mut data)?;
7647    /// assert_eq!(view.strides(), &[1, 2]);
7648    /// # Ok::<(), tenferro_tensor::Error>(())
7649    /// ```
7650    pub fn strides(&self) -> &[isize] {
7651        match self {
7652            Self::F32(t) => t.strides(),
7653            Self::F64(t) => t.strides(),
7654            Self::I32(t) => t.strides(),
7655            Self::I64(t) => t.strides(),
7656            Self::Bool(t) => t.strides(),
7657            Self::C32(t) => t.strides(),
7658            Self::C64(t) => t.strides(),
7659        }
7660    }
7661
7662    pub fn offset(&self) -> isize {
7663        match self {
7664            Self::F32(t) => t.offset(),
7665            Self::F64(t) => t.offset(),
7666            Self::I32(t) => t.offset(),
7667            Self::I64(t) => t.offset(),
7668            Self::Bool(t) => t.offset(),
7669            Self::C32(t) => t.offset(),
7670            Self::C64(t) => t.offset(),
7671        }
7672    }
7673
7674    /// Compute the physical element offset for a logical index.
7675    ///
7676    /// # Errors
7677    ///
7678    /// Returns [`crate::Error::Validation`] with
7679    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7680    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7681    /// when an index is outside its axis extent, or
7682    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7683    /// arithmetic overflows.
7684    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7685        match self {
7686            Self::F32(t) => t.layout_linear_offset(indices),
7687            Self::F64(t) => t.layout_linear_offset(indices),
7688            Self::I32(t) => t.layout_linear_offset(indices),
7689            Self::I64(t) => t.layout_linear_offset(indices),
7690            Self::Bool(t) => t.layout_linear_offset(indices),
7691            Self::C32(t) => t.layout_linear_offset(indices),
7692            Self::C64(t) => t.layout_linear_offset(indices),
7693        }
7694    }
7695
7696    /// Return whether this view is compact column-major.
7697    ///
7698    /// # Errors
7699    ///
7700    /// Returns [`crate::Error::Validation`] with
7701    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7702    /// compactness arithmetic overflows.
7703    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
7704        match self {
7705            Self::F32(t) => t.is_col_major_contiguous(),
7706            Self::F64(t) => t.is_col_major_contiguous(),
7707            Self::I32(t) => t.is_col_major_contiguous(),
7708            Self::I64(t) => t.is_col_major_contiguous(),
7709            Self::Bool(t) => t.is_col_major_contiguous(),
7710            Self::C32(t) => t.is_col_major_contiguous(),
7711            Self::C64(t) => t.is_col_major_contiguous(),
7712        }
7713    }
7714
7715    pub fn layout_summary(&self) -> String {
7716        layout_summary(self.shape(), self.strides(), self.offset())
7717    }
7718
7719    /// Assert this view is compact column-major.
7720    ///
7721    /// # Errors
7722    ///
7723    /// Returns [`crate::Error::Validation`] with
7724    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7725    /// compactness arithmetic overflows, or
7726    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7727    /// view is not compact column-major.
7728    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
7729        assert_layout_col_major_contiguous(
7730            self.is_col_major_contiguous()?,
7731            self.shape(),
7732            self.strides(),
7733            self.offset(),
7734            "TensorViewMut::assert_col_major_contiguous",
7735        )
7736    }
7737
7738    /// Explicitly duplicate the compact host data visible through this
7739    /// mutable view into a new tensor owner.
7740    ///
7741    /// # Examples
7742    ///
7743    /// ```
7744    /// use tenferro_tensor::{TensorViewMut, TypedTensorViewMut};
7745    ///
7746    /// let mut data = [1_i32, 2];
7747    /// let view = TensorViewMut::I32(TypedTensorViewMut::from_slice(
7748    ///     vec![2], vec![1], 0, &mut data,
7749    /// )?);
7750    /// let copy = view.duplicate()?;
7751    /// assert_eq!(copy.shape(), &[2]);
7752    /// # Ok::<(), tenferro_tensor::Error>(())
7753    /// ```
7754    ///
7755    /// # Errors
7756    ///
7757    /// Returns [`crate::Error::HostAccess`] for backend-owned views,
7758    /// [`ValidationError::NonContiguousViewAsSlice`] for non-contiguous views,
7759    /// or [`ValidationError::InvalidArgument`] for invalid layout metadata.
7760    pub fn duplicate(&self) -> crate::Result<Tensor> {
7761        self.as_read_only().duplicate()
7762    }
7763
7764    /// Borrow this mutable view as a read-only dtype-erased view.
7765    ///
7766    /// # Examples
7767    ///
7768    /// ```
7769    /// use tenferro_tensor::TensorViewMut;
7770    ///
7771    /// let mut data = [1.0_f64, 2.0];
7772    /// let view = TensorViewMut::f64(&[2], &mut data)?;
7773    /// assert_eq!(view.as_read_only().as_slice::<f64>()?, &[1.0, 2.0]);
7774    /// # Ok::<(), tenferro_tensor::Error>(())
7775    /// ```
7776    pub fn as_read_only(&self) -> TensorView<'_> {
7777        match self {
7778            Self::F32(t) => TensorView::F32(t.as_read_only()),
7779            Self::F64(t) => TensorView::F64(t.as_read_only()),
7780            Self::I32(t) => TensorView::I32(t.as_read_only()),
7781            Self::I64(t) => TensorView::I64(t.as_read_only()),
7782            Self::Bool(t) => TensorView::Bool(t.as_read_only()),
7783            Self::C32(t) => TensorView::C32(t.as_read_only()),
7784            Self::C64(t) => TensorView::C64(t.as_read_only()),
7785        }
7786    }
7787}
7788
7789impl<'a> TensorRead<'a> {
7790    /// Borrow an owned tensor as a read target without copying it.
7791    ///
7792    /// # Examples
7793    ///
7794    /// ```
7795    /// use tenferro_tensor::{Tensor, TensorRead};
7796    ///
7797    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
7798    /// let read = TensorRead::from_tensor(&tensor);
7799    /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
7800    /// # Ok::<(), tenferro_tensor::Error>(())
7801    /// ```
7802    pub fn from_tensor(tensor: &'a Tensor) -> Self {
7803        Self::Tensor(tensor)
7804    }
7805
7806    /// Wrap a borrowed dtype-erased view as a read target.
7807    ///
7808    /// # Examples
7809    ///
7810    /// ```
7811    /// use tenferro_tensor::{TensorRead, TensorView};
7812    ///
7813    /// let read = TensorRead::from_view(TensorView::i32(&[2], &[3, 4])?);
7814    /// assert_eq!(read.as_slice::<i32>()?, &[3, 4]);
7815    /// assert!(read.as_tensor().is_none());
7816    /// # Ok::<(), tenferro_tensor::Error>(())
7817    /// ```
7818    #[inline]
7819    pub fn from_view(view: TensorView<'a>) -> Self {
7820        Self::View(view)
7821    }
7822
7823    /// Convert this read target into a dtype-erased tensor view.
7824    ///
7825    /// Owned tensors are borrowed without copying their storage. Existing
7826    /// views preserve their layout and placement metadata.
7827    /// # Examples
7828    ///
7829    /// ```rust
7830    /// # use tenferro_tensor::{Tensor, TensorRead};
7831    /// # let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
7832    /// let view = TensorRead::from_tensor(&tensor).tensor_view();
7833    /// assert_eq!(view.shape(), &[2]);
7834    /// assert_eq!(view.as_slice::<f64>()?, &[1.0, 2.0]);
7835    /// # Ok::<(), tenferro_tensor::Error>(())
7836    /// ```
7837    pub fn tensor_view(self) -> TensorView<'a> {
7838        match self {
7839            Self::Tensor(tensor) => tensor_view_with_layout(tensor, tensor_layout(tensor)),
7840            Self::View(view) => view,
7841        }
7842    }
7843
7844    /// Borrow this read target as a typed compact host slice without allocation.
7845    ///
7846    /// This delegates to [`TensorView::as_slice`]. Cloning a `TensorRead` is a
7847    /// shallow clone of its borrowed reference or view metadata, so the returned
7848    /// slice retains the original `'a` storage lifetime and never outlives the
7849    /// storage borrowed by this read target. This method does not materialize,
7850    /// transfer, or otherwise canonicalize the input.
7851    ///
7852    /// # Errors
7853    ///
7854    /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
7855    /// input dtype, [`ValidationError::NonContiguousViewAsSlice`] for a
7856    /// noncompact view, or a typed runtime-state host-access error for
7857    /// backend-owned storage.
7858    /// Backend-owned
7859    /// inputs are never downloaded implicitly.
7860    ///
7861    /// # Examples
7862    ///
7863    /// ```
7864    /// use tenferro_tensor::{Tensor, TensorRead};
7865    ///
7866    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
7867    /// let read = TensorRead::from_tensor(&tensor);
7868    /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
7869    /// # Ok::<(), tenferro_tensor::Error>(())
7870    /// ```
7871    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
7872        self.clone().tensor_view().as_slice()
7873    }
7874
7875    /// Return the element dtype of this read target.
7876    ///
7877    /// # Examples
7878    ///
7879    /// ```
7880    /// use tenferro_tensor::{DType, Tensor, TensorRead};
7881    ///
7882    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1_i64])?;
7883    /// assert_eq!(TensorRead::from_tensor(&tensor).dtype(), DType::I64);
7884    /// # Ok::<(), tenferro_tensor::Error>(())
7885    /// ```
7886    pub fn dtype(&self) -> DType {
7887        match self {
7888            Self::Tensor(tensor) => tensor.dtype(),
7889            Self::View(view) => view.dtype(),
7890        }
7891    }
7892
7893    /// Return the logical shape of this read target.
7894    ///
7895    /// # Examples
7896    ///
7897    /// ```
7898    /// use tenferro_tensor::{Tensor, TensorRead};
7899    ///
7900    /// let tensor = Tensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
7901    /// assert_eq!(TensorRead::from_tensor(&tensor).shape(), &[2, 1]);
7902    /// # Ok::<(), tenferro_tensor::Error>(())
7903    /// ```
7904    pub fn shape(&self) -> &[usize] {
7905        match self {
7906            Self::Tensor(tensor) => tensor.shape(),
7907            Self::View(view) => view.shape(),
7908        }
7909    }
7910
7911    /// Return the placement metadata carried by this read target.
7912    ///
7913    /// # Examples
7914    ///
7915    /// ```
7916    /// use tenferro_tensor::{MemoryKind, Tensor, TensorRead};
7917    ///
7918    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
7919    /// let read = TensorRead::from_tensor(&tensor);
7920    /// assert_eq!(read.placement().memory_kind, MemoryKind::UnpinnedHost);
7921    /// # Ok::<(), tenferro_tensor::Error>(())
7922    /// ```
7923    pub fn placement(&self) -> &Placement {
7924        match self {
7925            Self::Tensor(tensor) => tensor.placement(),
7926            Self::View(view) => view.placement(),
7927        }
7928    }
7929
7930    /// Return the physical backend family of this read target, when backend-owned.
7931    ///
7932    /// # Examples
7933    ///
7934    /// ```
7935    /// use tenferro_tensor::{Tensor, TensorRead};
7936    ///
7937    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
7938    /// assert_eq!(TensorRead::from_tensor(&tensor).backend_family(), None);
7939    /// # Ok::<(), tenferro_tensor::Error>(())
7940    /// ```
7941    pub fn backend_family(&self) -> Option<&'static str> {
7942        match self {
7943            Self::Tensor(tensor) => match tensor.dtype() {
7944                // A caller-owned payload is host memory without backend family.
7945                DType::External(_) => None,
7946                DType::F32 => tensor.as_typed::<f32>().and_then(|t| t.backend_family()),
7947                DType::F64 => tensor.as_typed::<f64>().and_then(|t| t.backend_family()),
7948                DType::I32 => tensor.as_typed::<i32>().and_then(|t| t.backend_family()),
7949                DType::I64 => tensor.as_typed::<i64>().and_then(|t| t.backend_family()),
7950                DType::Bool => tensor.as_typed::<bool>().and_then(|t| t.backend_family()),
7951                DType::C32 => tensor
7952                    .as_typed::<Complex<f32>>()
7953                    .and_then(|t| t.backend_family()),
7954                DType::C64 => tensor
7955                    .as_typed::<Complex<f64>>()
7956                    .and_then(|t| t.backend_family()),
7957            },
7958            Self::View(view) => view.backend_family(),
7959        }
7960    }
7961
7962    /// Return the shared allocation domain of this read target, when present.
7963    ///
7964    /// # Examples
7965    ///
7966    /// ```
7967    /// use tenferro_tensor::{Tensor, TensorRead};
7968    ///
7969    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
7970    /// assert_eq!(TensorRead::from_tensor(&tensor).allocation_domain(), None);
7971    /// # Ok::<(), tenferro_tensor::Error>(())
7972    /// ```
7973    pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
7974        match self {
7975            Self::Tensor(tensor) => match tensor.dtype() {
7976                // A caller-owned payload has no allocation domain.
7977                DType::External(_) => None,
7978                DType::F32 => tensor.as_typed::<f32>().and_then(|t| t.allocation_domain()),
7979                DType::F64 => tensor.as_typed::<f64>().and_then(|t| t.allocation_domain()),
7980                DType::I32 => tensor.as_typed::<i32>().and_then(|t| t.allocation_domain()),
7981                DType::I64 => tensor.as_typed::<i64>().and_then(|t| t.allocation_domain()),
7982                DType::Bool => tensor
7983                    .as_typed::<bool>()
7984                    .and_then(|t| t.allocation_domain()),
7985                DType::C32 => tensor
7986                    .as_typed::<Complex<f32>>()
7987                    .and_then(|t| t.allocation_domain()),
7988                DType::C64 => tensor
7989                    .as_typed::<Complex<f64>>()
7990                    .and_then(|t| t.allocation_domain()),
7991            },
7992            Self::View(view) => view.allocation_domain(),
7993        }
7994    }
7995
7996    /// Return strides in element units; owned tensors report compact column-major strides.
7997    ///
7998    /// # Examples
7999    ///
8000    /// ```
8001    /// use tenferro_tensor::{Tensor, TensorRead};
8002    ///
8003    /// let tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8004    /// assert_eq!(TensorRead::from_tensor(&tensor).strides()?, vec![1, 2]);
8005    /// # Ok::<(), tenferro_tensor::Error>(())
8006    /// ```
8007    ///
8008    /// # Errors
8009    ///
8010    /// Returns [`crate::Error::Validation`] with
8011    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8012    /// column-major stride arithmetic overflows.
8013    pub fn strides(&self) -> crate::Result<Vec<isize>> {
8014        match self {
8015            Self::Tensor(tensor) => col_major_strides(tensor.shape()),
8016            Self::View(view) => Ok(view.strides().to_vec()),
8017        }
8018    }
8019
8020    /// Return the physical element offset; owned tensors always start at `0`.
8021    ///
8022    /// # Examples
8023    ///
8024    /// ```
8025    /// use tenferro_tensor::{TensorRead, TensorView, TypedTensorView};
8026    ///
8027    /// let data = [1.0_f64, 2.0, 3.0];
8028    /// let view = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?);
8029    /// assert_eq!(TensorRead::from_view(view).offset(), 1);
8030    /// # Ok::<(), tenferro_tensor::Error>(())
8031    /// ```
8032    pub fn offset(&self) -> isize {
8033        match self {
8034            Self::Tensor(_) => 0,
8035            Self::View(view) => view.offset(),
8036        }
8037    }
8038
8039    /// Compute the physical element offset for a logical index.
8040    ///
8041    /// # Examples
8042    ///
8043    /// ```
8044    /// use tenferro_tensor::{Tensor, TensorRead};
8045    ///
8046    /// let tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8047    /// let read = TensorRead::from_tensor(&tensor);
8048    /// assert_eq!(read.layout_linear_offset(&[1, 2])?, 5);
8049    /// assert!(read.layout_linear_offset(&[2, 0]).is_err());
8050    /// # Ok::<(), tenferro_tensor::Error>(())
8051    /// ```
8052    ///
8053    /// # Errors
8054    ///
8055    /// Returns [`crate::Error::Validation`] with
8056    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8057    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8058    /// when an index is outside its axis extent, or
8059    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8060    /// arithmetic overflows.
8061    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8062        match self {
8063            Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
8064            Self::View(view) => view.layout_linear_offset(indices),
8065        }
8066    }
8067
8068    /// Return whether this read target is compact column-major.
8069    ///
8070    /// # Examples
8071    ///
8072    /// ```
8073    /// use tenferro_tensor::{Tensor, TensorRead, TensorView, TypedTensorView};
8074    ///
8075    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8076    /// assert!(TensorRead::from_tensor(&tensor).is_col_major_contiguous()?);
8077    /// let data = [1.0_f64, 2.0, 3.0];
8078    /// let strided = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![2], 0, &data)?);
8079    /// assert!(!TensorRead::from_view(strided).is_col_major_contiguous()?);
8080    /// # Ok::<(), tenferro_tensor::Error>(())
8081    /// ```
8082    ///
8083    /// # Errors
8084    ///
8085    /// Returns [`crate::Error::Validation`] with
8086    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8087    /// compactness arithmetic overflows.
8088    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8089        match self {
8090            Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
8091            Self::View(view) => view.is_col_major_contiguous(),
8092        }
8093    }
8094
8095    /// Return a compact string summary of this read target's layout metadata.
8096    ///
8097    /// # Examples
8098    ///
8099    /// ```
8100    /// use tenferro_tensor::{Tensor, TensorRead};
8101    ///
8102    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8103    /// assert_eq!(
8104    ///     TensorRead::from_tensor(&tensor).layout_summary(),
8105    ///     "shape=[2] strides=[1] offset=0"
8106    /// );
8107    /// # Ok::<(), tenferro_tensor::Error>(())
8108    /// ```
8109    pub fn layout_summary(&self) -> String {
8110        let strides = match self.strides() {
8111            Ok(strides) => strides,
8112            Err(err) => return format!("layout unavailable: {err}"),
8113        };
8114        layout_summary(self.shape(), &strides, self.offset())
8115    }
8116
8117    /// Assert this read target is compact column-major.
8118    ///
8119    /// # Examples
8120    ///
8121    /// ```
8122    /// use tenferro_tensor::{Tensor, TensorRead, TensorView, TypedTensorView};
8123    ///
8124    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8125    /// TensorRead::from_tensor(&tensor).assert_col_major_contiguous()?;
8126    /// let data = [1.0_f64, 2.0, 3.0];
8127    /// let strided = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![2], 0, &data)?);
8128    /// assert!(TensorRead::from_view(strided).assert_col_major_contiguous().is_err());
8129    /// # Ok::<(), tenferro_tensor::Error>(())
8130    /// ```
8131    ///
8132    /// # Errors
8133    ///
8134    /// Returns [`crate::Error::Validation`] with
8135    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8136    /// compactness arithmetic overflows, or
8137    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8138    /// view is not compact column-major.
8139    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8140        let strides = self.strides()?;
8141        assert_layout_col_major_contiguous(
8142            self.is_col_major_contiguous()?,
8143            self.shape(),
8144            &strides,
8145            self.offset(),
8146            "TensorRead::assert_col_major_contiguous",
8147        )
8148    }
8149
8150    /// Return the borrowed owned tensor, or `None` when this read target is a view.
8151    ///
8152    /// # Examples
8153    ///
8154    /// ```
8155    /// use tenferro_tensor::{Tensor, TensorRead, TensorView};
8156    ///
8157    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
8158    /// assert!(TensorRead::from_tensor(&tensor).as_tensor().is_some());
8159    /// assert!(TensorRead::from_view(TensorView::f64(&[1], &[1.0])?).as_tensor().is_none());
8160    /// # Ok::<(), tenferro_tensor::Error>(())
8161    /// ```
8162    pub fn as_tensor(&self) -> Option<&'a Tensor> {
8163        match self {
8164            Self::Tensor(tensor) => Some(*tensor),
8165            Self::View(_) => None,
8166        }
8167    }
8168}
8169
8170impl<'a> TensorWrite<'a> {
8171    /// Borrow an owned tensor as a writable target without copying it.
8172    ///
8173    /// # Examples
8174    ///
8175    /// ```
8176    /// use tenferro_tensor::{Tensor, TensorWrite};
8177    ///
8178    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8179    /// let write = TensorWrite::from_tensor(&mut tensor);
8180    /// assert_eq!(write.shape(), &[2]);
8181    /// # Ok::<(), tenferro_tensor::Error>(())
8182    /// ```
8183    pub fn from_tensor(tensor: &'a mut Tensor) -> Self {
8184        Self::Tensor(tensor)
8185    }
8186
8187    /// Wrap a borrowed mutable dtype-erased view as a writable target.
8188    ///
8189    /// # Examples
8190    ///
8191    /// ```
8192    /// use tenferro_tensor::{DType, TensorViewMut, TensorWrite};
8193    ///
8194    /// let mut data = [1.0_f64, 2.0];
8195    /// let write = TensorWrite::from_view(TensorViewMut::f64(&[2], &mut data)?);
8196    /// assert_eq!(write.dtype(), DType::F64);
8197    /// assert_eq!(write.as_read().as_slice::<f64>()?, &[1.0, 2.0]);
8198    /// # Ok::<(), tenferro_tensor::Error>(())
8199    /// ```
8200    pub fn from_view(view: TensorViewMut<'a>) -> Self {
8201        Self::View(view)
8202    }
8203
8204    /// Borrow this writable target as a read-only tensor input.
8205    ///
8206    /// This is useful for explicit read-modify-write kernels such as
8207    /// accumulation updates. The returned view borrows through `&self`, so it
8208    /// cannot outlive the current read-only borrow of the writable target.
8209    ///
8210    /// # Examples
8211    ///
8212    /// ```rust
8213    /// use tenferro_tensor::{DType, Tensor, TensorWrite};
8214    ///
8215    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
8216    /// let write = TensorWrite::from_tensor(&mut tensor);
8217    /// let read = write.as_read();
8218    /// assert_eq!(read.dtype(), DType::F64);
8219    /// # Ok::<(), tenferro_tensor::Error>(())
8220    /// ```
8221    pub fn as_read(&self) -> TensorRead<'_> {
8222        match self {
8223            Self::Tensor(tensor) => TensorRead::from_tensor(tensor),
8224            Self::View(view) => TensorRead::from_view(view.as_read_only()),
8225        }
8226    }
8227
8228    /// Return the element dtype of this writable target.
8229    ///
8230    /// # Examples
8231    ///
8232    /// ```
8233    /// use tenferro_tensor::{DType, Tensor, TensorWrite};
8234    ///
8235    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![1_i32])?;
8236    /// assert_eq!(TensorWrite::from_tensor(&mut tensor).dtype(), DType::I32);
8237    /// # Ok::<(), tenferro_tensor::Error>(())
8238    /// ```
8239    pub fn dtype(&self) -> DType {
8240        match self {
8241            Self::Tensor(tensor) => tensor.dtype(),
8242            Self::View(view) => view.dtype(),
8243        }
8244    }
8245
8246    /// Return the logical shape of this writable target.
8247    ///
8248    /// # Examples
8249    ///
8250    /// ```
8251    /// use tenferro_tensor::{Tensor, TensorWrite};
8252    ///
8253    /// let mut tensor = Tensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
8254    /// assert_eq!(TensorWrite::from_tensor(&mut tensor).shape(), &[2, 1]);
8255    /// # Ok::<(), tenferro_tensor::Error>(())
8256    /// ```
8257    pub fn shape(&self) -> &[usize] {
8258        match self {
8259            Self::Tensor(tensor) => tensor.shape(),
8260            Self::View(view) => view.shape(),
8261        }
8262    }
8263
8264    /// Return strides in element units; owned tensors report compact column-major strides.
8265    ///
8266    /// # Examples
8267    ///
8268    /// ```
8269    /// use tenferro_tensor::{Tensor, TensorWrite};
8270    ///
8271    /// let mut tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8272    /// assert_eq!(TensorWrite::from_tensor(&mut tensor).strides()?, vec![1, 2]);
8273    /// # Ok::<(), tenferro_tensor::Error>(())
8274    /// ```
8275    ///
8276    /// # Errors
8277    ///
8278    /// Returns [`crate::Error::Validation`] with
8279    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8280    /// column-major stride arithmetic overflows.
8281    pub fn strides(&self) -> crate::Result<Vec<isize>> {
8282        match self {
8283            Self::Tensor(tensor) => col_major_strides(tensor.shape()),
8284            Self::View(view) => Ok(view.strides().to_vec()),
8285        }
8286    }
8287
8288    /// Return the physical element offset; owned tensors always start at `0`.
8289    ///
8290    /// # Examples
8291    ///
8292    /// ```
8293    /// use tenferro_tensor::{TensorViewMut, TensorWrite, TypedTensorViewMut};
8294    ///
8295    /// let mut data = [1.0_f64, 2.0, 3.0];
8296    /// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![1], 1, &mut data)?);
8297    /// assert_eq!(TensorWrite::from_view(view).offset(), 1);
8298    /// # Ok::<(), tenferro_tensor::Error>(())
8299    /// ```
8300    pub fn offset(&self) -> isize {
8301        match self {
8302            Self::Tensor(_) => 0,
8303            Self::View(view) => view.offset(),
8304        }
8305    }
8306
8307    /// Compute the physical element offset for a logical index.
8308    ///
8309    /// # Examples
8310    ///
8311    /// ```
8312    /// use tenferro_tensor::{Tensor, TensorWrite};
8313    ///
8314    /// let mut tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8315    /// let write = TensorWrite::from_tensor(&mut tensor);
8316    /// assert_eq!(write.layout_linear_offset(&[1, 2])?, 5);
8317    /// assert!(write.layout_linear_offset(&[0]).is_err());
8318    /// # Ok::<(), tenferro_tensor::Error>(())
8319    /// ```
8320    ///
8321    /// # Errors
8322    ///
8323    /// Returns [`crate::Error::Validation`] with
8324    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8325    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8326    /// when an index is outside its axis extent, or
8327    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8328    /// arithmetic overflows.
8329    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8330        match self {
8331            Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
8332            Self::View(view) => view.layout_linear_offset(indices),
8333        }
8334    }
8335
8336    /// Return whether this writable target is compact column-major.
8337    ///
8338    /// # Examples
8339    ///
8340    /// ```
8341    /// use tenferro_tensor::{Tensor, TensorViewMut, TensorWrite, TypedTensorViewMut};
8342    ///
8343    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8344    /// assert!(TensorWrite::from_tensor(&mut tensor).is_col_major_contiguous()?);
8345    /// let mut data = [1.0_f64, 2.0, 3.0];
8346    /// let strided = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![2], 0, &mut data)?);
8347    /// assert!(!TensorWrite::from_view(strided).is_col_major_contiguous()?);
8348    /// # Ok::<(), tenferro_tensor::Error>(())
8349    /// ```
8350    ///
8351    /// # Errors
8352    ///
8353    /// Returns [`crate::Error::Validation`] with
8354    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8355    /// compactness arithmetic overflows.
8356    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8357        match self {
8358            Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
8359            Self::View(view) => view.is_col_major_contiguous(),
8360        }
8361    }
8362
8363    /// Return a compact string summary of this writable target's layout metadata.
8364    ///
8365    /// # Examples
8366    ///
8367    /// ```
8368    /// use tenferro_tensor::{Tensor, TensorWrite};
8369    ///
8370    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8371    /// assert_eq!(
8372    ///     TensorWrite::from_tensor(&mut tensor).layout_summary(),
8373    ///     "shape=[2] strides=[1] offset=0"
8374    /// );
8375    /// # Ok::<(), tenferro_tensor::Error>(())
8376    /// ```
8377    pub fn layout_summary(&self) -> String {
8378        let strides = match self.strides() {
8379            Ok(strides) => strides,
8380            Err(err) => return format!("layout unavailable: {err}"),
8381        };
8382        layout_summary(self.shape(), &strides, self.offset())
8383    }
8384
8385    /// Assert this writable target is compact column-major.
8386    ///
8387    /// # Examples
8388    ///
8389    /// ```
8390    /// use tenferro_tensor::{Tensor, TensorViewMut, TensorWrite, TypedTensorViewMut};
8391    ///
8392    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8393    /// TensorWrite::from_tensor(&mut tensor).assert_col_major_contiguous()?;
8394    /// let mut data = [1.0_f64, 2.0, 3.0];
8395    /// let strided = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![2], 0, &mut data)?);
8396    /// assert!(TensorWrite::from_view(strided).assert_col_major_contiguous().is_err());
8397    /// # Ok::<(), tenferro_tensor::Error>(())
8398    /// ```
8399    ///
8400    /// # Errors
8401    ///
8402    /// Returns [`crate::Error::Validation`] with
8403    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8404    /// compactness arithmetic overflows, or
8405    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8406    /// view is not compact column-major.
8407    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8408        let strides = self.strides()?;
8409        assert_layout_col_major_contiguous(
8410            self.is_col_major_contiguous()?,
8411            self.shape(),
8412            &strides,
8413            self.offset(),
8414            "TensorWrite::assert_col_major_contiguous",
8415        )
8416    }
8417}
8418
8419/// Column-major strides derived from a shape.
8420///
8421/// # Examples
8422///
8423/// ```rust
8424/// use tenferro_tensor::col_major_strides;
8425///
8426/// assert_eq!(col_major_strides(&[2, 3])?, vec![1, 2]);
8427/// # Ok::<(), tenferro_tensor::Error>(())
8428/// ```
8429/// # Errors
8430///
8431/// Returns [`crate::Error::Validation`] with
8432/// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when a
8433/// column-major stride product overflows.
8434pub fn col_major_strides(shape: &[usize]) -> crate::Result<Vec<isize>> {
8435    let mut strides = Vec::with_capacity(shape.len());
8436    let mut stride = 1isize;
8437    for &extent in shape {
8438        strides.push(stride);
8439        let extent = isize::try_from(extent).map_err(|_| {
8440            crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
8441        })?;
8442        stride = stride.checked_mul(extent).ok_or_else(|| {
8443            crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
8444        })?;
8445    }
8446    Ok(strides)
8447}
8448
8449fn try_linear_offset_for_shape(
8450    shape: &[usize],
8451    indices: &[usize],
8452    op: &'static str,
8453) -> crate::Result<usize> {
8454    if indices.len() != shape.len() {
8455        return Err(crate::Error::validation(
8456            op,
8457            ValidationError::RankMismatch {
8458                expected: shape.len(),
8459                actual: indices.len(),
8460            },
8461        ));
8462    }
8463    let mut offset = 0usize;
8464    let mut stride = 1usize;
8465    for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
8466        if idx >= extent {
8467            return Err(crate::Error::invalid_argument(
8468                op,
8469                "index",
8470                format!("index {idx} out of bounds for axis {axis} extent {extent}"),
8471            ));
8472        }
8473        offset =
8474            offset
8475                .checked_add(idx.checked_mul(stride).ok_or_else(|| {
8476                    crate::Error::validation(op, ValidationError::IntegerOverflow)
8477                })?)
8478                .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8479        stride = stride
8480            .checked_mul(extent)
8481            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8482    }
8483    Ok(offset)
8484}
8485
8486fn checked_view_offset_result(
8487    shape: &[usize],
8488    strides: &[isize],
8489    base_offset: isize,
8490    indices: &[usize],
8491    op: &'static str,
8492) -> crate::Result<usize> {
8493    if indices.len() != shape.len() {
8494        return Err(crate::Error::validation(
8495            op,
8496            ValidationError::RankMismatch {
8497                expected: shape.len(),
8498                actual: indices.len(),
8499            },
8500        ));
8501    }
8502    for (axis, (&index, &extent)) in indices.iter().zip(shape).enumerate() {
8503        if index >= extent {
8504            return Err(crate::Error::invalid_argument(
8505                op,
8506                "index",
8507                format!("index {index} out of bounds for axis {axis} extent {extent}"),
8508            ));
8509        }
8510    }
8511    checked_view_offset(shape, strides, base_offset, indices)
8512        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
8513}
8514
8515fn layout_summary(shape: &[usize], strides: &[isize], offset: isize) -> String {
8516    format!("shape={shape:?} strides={strides:?} offset={offset}")
8517}
8518
8519fn assert_layout_col_major_contiguous(
8520    is_contiguous: bool,
8521    shape: &[usize],
8522    strides: &[isize],
8523    offset: isize,
8524    op: &'static str,
8525) -> crate::Result<()> {
8526    if is_contiguous {
8527        Ok(())
8528    } else {
8529        Err(crate::Error::invalid_argument(
8530            op,
8531            "layout",
8532            format!(
8533                "expected compact column-major layout, got {}",
8534                layout_summary(shape, strides, offset)
8535            ),
8536        ))
8537    }
8538}
8539
8540fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
8541    shape.iter().try_fold(1usize, |acc, &dim| {
8542        acc.checked_mul(dim)
8543            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
8544    })
8545}
8546
8547fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
8548    let n = try_shape_product(shape, op)?;
8549    if data_len != n {
8550        return Err(crate::Error::validation(
8551            op,
8552            ValidationError::ShapeDataLengthMismatch {
8553                expected: n,
8554                actual: data_len,
8555            },
8556        ));
8557    }
8558    Ok(())
8559}
8560
8561fn try_compact_layout<R: TensorRank>(
8562    shape: impl tenferro_tensor_core::IntoRankShape<R>,
8563    op: &'static str,
8564) -> crate::Result<TensorLayout<R>> {
8565    let shape = shape
8566        .into_rank_shape()
8567        .map_err(|err| tensor_layout_error(op, err))?;
8568    TensorLayout::compact(shape).map_err(|err| tensor_layout_error(op, err))
8569}
8570
8571fn tensor_layout_error(
8572    op: &'static str,
8573    err: tenferro_tensor_core::ValidationError,
8574) -> crate::Error {
8575    crate::Error::validation(op, err)
8576}
8577
8578fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
8579    if shape.contains(&0) {
8580        return Ok(0);
8581    }
8582    shape.iter().try_fold(1usize, |product, &dim| {
8583        product
8584            .checked_mul(dim)
8585            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
8586    })
8587}
8588
8589fn checked_view_offset(
8590    shape: &[usize],
8591    strides: &[isize],
8592    base_offset: isize,
8593    indices: &[usize],
8594) -> Option<usize> {
8595    if indices.len() != shape.len() {
8596        return None;
8597    }
8598
8599    let mut offset = base_offset;
8600    for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
8601        if index >= extent {
8602            return None;
8603        }
8604        let index = isize::try_from(index).ok()?;
8605        let delta = index.checked_mul(stride)?;
8606        offset = offset.checked_add(delta)?;
8607    }
8608
8609    usize::try_from(offset).ok()
8610}
8611
8612fn reachable_layout_span(
8613    shape: &[usize],
8614    strides: &[isize],
8615    offset: isize,
8616) -> crate::Result<Option<(usize, usize)>> {
8617    if shape.contains(&0) {
8618        return Ok(None);
8619    }
8620
8621    let mut min_offset = offset;
8622    let mut max_offset = offset;
8623    for (&extent, &stride) in shape.iter().zip(strides) {
8624        let steps = isize::try_from(extent.saturating_sub(1)).map_err(|_| {
8625            crate::Error::validation(
8626                "TypedTensorViewMut::try_multi_slice_mut",
8627                ValidationError::IntegerOverflow,
8628            )
8629        })?;
8630        let end = stride.checked_mul(steps).ok_or_else(|| {
8631            crate::Error::validation(
8632                "TypedTensorViewMut::try_multi_slice_mut",
8633                ValidationError::IntegerOverflow,
8634            )
8635        })?;
8636        let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
8637        min_offset = min_offset.checked_add(axis_min).ok_or_else(|| {
8638            crate::Error::validation(
8639                "TypedTensorViewMut::try_multi_slice_mut",
8640                ValidationError::IntegerOverflow,
8641            )
8642        })?;
8643        max_offset = max_offset.checked_add(axis_max).ok_or_else(|| {
8644            crate::Error::validation(
8645                "TypedTensorViewMut::try_multi_slice_mut",
8646                ValidationError::IntegerOverflow,
8647            )
8648        })?;
8649    }
8650
8651    let min_offset = usize::try_from(min_offset).map_err(|_| {
8652        crate::Error::invalid_argument(
8653            "TypedTensorViewMut::try_multi_slice_mut",
8654            "layout",
8655            "minimum reachable offset is negative",
8656        )
8657    })?;
8658    let max_offset = usize::try_from(max_offset).map_err(|_| {
8659        crate::Error::invalid_argument(
8660            "TypedTensorViewMut::try_multi_slice_mut",
8661            "layout",
8662            "maximum reachable offset is negative",
8663        )
8664    })?;
8665    Ok(Some((min_offset, max_offset)))
8666}
8667
8668fn split_two_mut_ranges<T>(
8669    data: &mut [T],
8670    first: (usize, usize),
8671    second: (usize, usize),
8672) -> Option<(&mut [T], &mut [T])> {
8673    if first.1 < second.0 {
8674        let (_, after_first_start) = data.split_at_mut(first.0);
8675        let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
8676        let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
8677        let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
8678        Some((first_slice, second_slice))
8679    } else if second.1 < first.0 {
8680        let (_, after_second_start) = data.split_at_mut(second.0);
8681        let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
8682        let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
8683        let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
8684        Some((first_slice, second_slice))
8685    } else {
8686        None
8687    }
8688}
8689
8690fn adjusted_view_offset(offset: isize, span_start: usize) -> crate::Result<isize> {
8691    let span_start = isize::try_from(span_start).map_err(|_| {
8692        crate::Error::validation(
8693            "TypedTensorViewMut::try_multi_slice_mut",
8694            ValidationError::IntegerOverflow,
8695        )
8696    })?;
8697    offset.checked_sub(span_start).ok_or_else(|| {
8698        crate::Error::validation(
8699            "TypedTensorViewMut::try_multi_slice_mut",
8700            ValidationError::IntegerOverflow,
8701        )
8702    })
8703}
8704
8705fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
8706    layout: &TensorLayout<R>,
8707    offset: isize,
8708    data: &'a mut [T],
8709    placement: Placement,
8710) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
8711    let shape = R::shape_from_vec(shape_vec(layout.shape()))
8712        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
8713    let strides = R::strides_from_vec(stride_vec(layout.strides()))
8714        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
8715    TypedTensorViewMut::from_buffer_ref_mut(
8716        shape,
8717        strides,
8718        offset,
8719        TensorStorageRefMut::Host(data),
8720        placement,
8721        "TypedTensorViewMut::try_multi_slice_mut",
8722    )
8723}
8724
8725fn contiguous_layout_slice<'a, T, R: TensorRank>(
8726    layout: &TensorLayout<R>,
8727    data: &'a [T],
8728    op: &'static str,
8729) -> crate::Result<&'a [T]> {
8730    if !layout
8731        .is_compact_col_major()
8732        .map_err(|err| tensor_layout_error(op, err))?
8733    {
8734        return Err(crate::Error::validation(
8735            op,
8736            ValidationError::NonContiguousViewAsSlice,
8737        ));
8738    }
8739    let len = checked_view_element_count(layout.shape(), op)?;
8740    let start = usize::try_from(layout.offset())
8741        .map_err(|_| crate::Error::invalid_argument(op, "layout", "view offset is negative"))?;
8742    let end = start
8743        .checked_add(len)
8744        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8745    data.get(start..end)
8746        .ok_or_else(|| crate::Error::validation(op, ValidationError::ViewOutOfBounds))
8747}
8748
8749fn relaxed_col_major_contiguous(
8750    shape: &[usize],
8751    strides: &[isize],
8752    op: &'static str,
8753) -> crate::Result<bool> {
8754    if shape.contains(&0) {
8755        return Ok(true);
8756    }
8757
8758    let mut expected = 1isize;
8759    for (&extent, &stride) in shape.iter().zip(strides) {
8760        if extent <= 1 {
8761            continue;
8762        }
8763        if stride != expected {
8764            return Ok(false);
8765        }
8766        let extent = isize::try_from(extent)
8767            .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8768        expected = expected
8769            .checked_mul(extent)
8770            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8771    }
8772    Ok(true)
8773}
8774
8775fn reshape_layout_dyn<R: TensorRank>(
8776    layout: &TensorLayout<R>,
8777    shape: &[usize],
8778    buffer_len: usize,
8779    op: &'static str,
8780) -> crate::Result<TensorLayout<DynRank>> {
8781    match layout.reshape_view_as::<DynRank>(shape_vec(shape), buffer_len) {
8782        Ok(layout) => Ok(layout),
8783        Err(err) => {
8784            if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
8785                return Err(tensor_layout_error(op, err));
8786            }
8787            let from = checked_view_element_count(layout.shape(), op)?;
8788            let to = checked_view_element_count(shape, op)?;
8789            if from != to {
8790                return Err(tensor_layout_error(
8791                    op,
8792                    tenferro_tensor_core::ShapeMismatch::ReshapeElementCount { from, to }.into(),
8793                ));
8794            }
8795            TensorLayout::<DynRank>::compact(shape_vec(shape))
8796                .and_then(|compact| {
8797                    TensorLayout::from_parts(
8798                        shape_vec(compact.shape()),
8799                        stride_vec(compact.strides()),
8800                        layout.offset(),
8801                        buffer_len,
8802                    )
8803                })
8804                .map_err(|err| tensor_layout_error(op, err))
8805        }
8806    }
8807}
8808
8809fn core_slice_specs(
8810    slices: &[StridedSliceSpec],
8811    shape: &[usize],
8812    op: &'static str,
8813) -> crate::Result<Vec<CoreSliceSpec>> {
8814    if slices.len() != shape.len() {
8815        return Err(crate::Error::validation(
8816            op,
8817            ValidationError::RankMismatch {
8818                expected: shape.len(),
8819                actual: slices.len(),
8820            },
8821        ));
8822    }
8823
8824    let mut specs = Vec::with_capacity(slices.len());
8825    for (slice, &axis_len) in slices.iter().zip(shape) {
8826        specs.push(core_slice_spec(*slice, axis_len, op)?);
8827    }
8828    Ok(specs)
8829}
8830
8831fn core_slice_spec(
8832    slice: StridedSliceSpec,
8833    axis_len: usize,
8834    op: &'static str,
8835) -> crate::Result<CoreSliceSpec> {
8836    if slice.step() == 0 {
8837        return Err(crate::Error::validation(
8838            op,
8839            ValidationError::InvalidSliceStep { step: slice.step() },
8840        ));
8841    }
8842
8843    let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
8844    let end = match slice.end() {
8845        Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
8846        None => isize::try_from(axis_len)
8847            .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
8848    };
8849
8850    if slice.step() > 0 {
8851        return Ok(CoreSliceSpec {
8852            start,
8853            end,
8854            step: slice.step(),
8855        });
8856    }
8857
8858    if start >= end {
8859        return Ok(CoreSliceSpec {
8860            start,
8861            end: start,
8862            step: slice.step(),
8863        });
8864    }
8865
8866    Ok(CoreSliceSpec {
8867        start: end
8868            .checked_sub(1)
8869            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
8870        end: start
8871            .checked_sub(1)
8872            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
8873        step: slice.step(),
8874    })
8875}
8876
8877fn normalize_strided_bound(
8878    bound: isize,
8879    axis_len: usize,
8880    op: &'static str,
8881    role: &'static str,
8882) -> crate::Result<isize> {
8883    let original_axis_len = axis_len;
8884    let axis_len = isize::try_from(axis_len)
8885        .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8886    let bound = if bound < 0 {
8887        axis_len
8888            .checked_add(bound)
8889            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?
8890    } else {
8891        bound
8892    };
8893    if !(0..=axis_len).contains(&bound) {
8894        let (start, end) = if role == "slice start" {
8895            (bound, bound)
8896        } else {
8897            (0, bound)
8898        };
8899        return Err(crate::Error::validation(
8900            op,
8901            ValidationError::InvalidSliceBounds {
8902                start,
8903                end,
8904                axis_len: original_axis_len,
8905            },
8906        ));
8907    }
8908    Ok(bound)
8909}
8910
8911fn slice_axis_specs(
8912    rank: usize,
8913    axis: usize,
8914    slice: StridedSliceSpec,
8915    op: &'static str,
8916) -> crate::Result<Vec<StridedSliceSpec>> {
8917    if axis >= rank {
8918        return Err(crate::Error::validation(
8919            op,
8920            ValidationError::AxisOutOfBounds { axis, rank },
8921        ));
8922    }
8923
8924    let mut slices = vec![StridedSliceSpec::all(); rank];
8925    slices[axis] = slice;
8926    Ok(slices)
8927}
8928
8929pub(crate) fn default_placement() -> Placement {
8930    Placement {
8931        memory_kind: MemoryKind::UnpinnedHost,
8932        device: None,
8933        cpu_affinity: None,
8934    }
8935}
8936
8937fn typed_tensor_from_vec_col_major<T, R: TensorRank>(
8938    shape: impl tenferro_tensor_core::IntoRankShape<R>,
8939    data: Vec<T>,
8940    op: &'static str,
8941) -> crate::Result<TypedTensor<T, R>> {
8942    try_typed_tensor_from_vec_col_major(shape, data, op)
8943}
8944
8945fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
8946    shape: impl tenferro_tensor_core::IntoRankShape<R>,
8947    data: Vec<T>,
8948    op: &'static str,
8949) -> crate::Result<TypedTensor<T, R>> {
8950    let shape = shape
8951        .into_rank_shape()
8952        .map_err(|err| tensor_layout_error(op, err))?;
8953    tenferro_tensor_core::col_major_strides(shape.as_ref())
8954        .map_err(|err| tensor_layout_error(op, err))?;
8955    try_checked_shape_len(shape.as_ref(), data.len(), op)?;
8956    Ok(TypedTensor {
8957        shape,
8958        placement: default_placement(),
8959        storage: DynamicStorage::Host(HostStorage {
8960            data: HostData::new(data),
8961        }),
8962    })
8963}
8964
8965fn typed_tensor_zeros<T: TensorScalar + Zero, R: TensorRank>(
8966    shape: impl tenferro_tensor_core::IntoRankShape<R>,
8967) -> crate::Result<TypedTensor<T, R>> {
8968    try_typed_tensor_zeros(shape)
8969}
8970
8971fn try_typed_tensor_zeros<T: TensorScalar + Clone + Zero, R: TensorRank>(
8972    shape: impl tenferro_tensor_core::IntoRankShape<R>,
8973) -> crate::Result<TypedTensor<T, R>> {
8974    let layout = try_compact_layout(shape, "zeros")?;
8975    let n = try_shape_product(layout.shape(), "zeros")?;
8976    typed_tensor_from_vec_col_major(
8977        R::shape_from_vec(shape_vec(layout.shape()))
8978            .map_err(|err| tensor_layout_error("zeros", err))?,
8979        vec![T::zero(); n],
8980        "zeros",
8981    )
8982}
8983
8984fn typed_tensor_ones<T: TensorScalar + One + Zero, R: TensorRank>(
8985    shape: impl tenferro_tensor_core::IntoRankShape<R>,
8986) -> crate::Result<TypedTensor<T, R>> {
8987    try_typed_tensor_ones(shape)
8988}
8989
8990fn try_typed_tensor_ones<T: TensorScalar + Clone + One + Zero, R: TensorRank>(
8991    shape: impl tenferro_tensor_core::IntoRankShape<R>,
8992) -> crate::Result<TypedTensor<T, R>> {
8993    let layout = try_compact_layout(shape, "ones")?;
8994    let n = try_shape_product(layout.shape(), "ones")?;
8995    typed_tensor_from_vec_col_major(
8996        R::shape_from_vec(shape_vec(layout.shape()))
8997            .map_err(|err| tensor_layout_error("ones", err))?,
8998        vec![T::one(); n],
8999        "ones",
9000    )
9001}
9002
9003fn typed_tensor_from_buffer_col_major<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
9004    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9005    buffer: StorageBuffer<T>,
9006    placement: Placement,
9007) -> crate::Result<TypedTensor<T, R>> {
9008    try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
9009}
9010
9011#[doc(hidden)]
9012fn typed_tensor_from_backend_allocation<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
9013    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9014    allocation: Box<dyn crate::BackendAllocation>,
9015    placement: Placement,
9016) -> crate::Result<TypedTensor<T, R>> {
9017    let layout = try_compact_layout(shape, "from_backend_allocation")?;
9018    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
9019        .map_err(|err| tensor_layout_error("from_backend_allocation", err))?;
9020    let shape = group_shape.clone();
9021    let (mut group, slot) =
9022        AllocationGroup::from_backend_allocation::<T, R>(group_shape, allocation)
9023            .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9024    group
9025        .set_descriptor_placement(slot, placement.clone())
9026        .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9027    let allocation_index = group
9028        .allocation_index(slot)
9029        .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9030    let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
9031    Ok(TypedTensor {
9032        shape,
9033        placement,
9034        storage: DynamicStorage::Group(GroupStorage {
9035            group: Box::new(OwnedTensorGroup {
9036                group,
9037                slot,
9038                allocation_index,
9039                host_ptr,
9040                host_byte_len,
9041                _rank: PhantomData,
9042            }),
9043        }),
9044    })
9045}
9046
9047fn try_typed_tensor_from_buffer_col_major<
9048    T: TensorScalar + Send + Sync + 'static,
9049    R: TensorRank,
9050>(
9051    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9052    buffer: StorageBuffer<T>,
9053    placement: Placement,
9054) -> crate::Result<TypedTensor<T, R>> {
9055    let layout = try_compact_layout(shape, "from_buffer_col_major")?;
9056    let len = buffer.len();
9057    try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
9058    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
9059        .map_err(|err| tensor_layout_error("from_buffer_col_major", err))?;
9060    match buffer {
9061        StorageBuffer::Host(data) => Ok(TypedTensor {
9062            shape: group_shape,
9063            placement,
9064            storage: DynamicStorage::Host(HostStorage {
9065                data: HostData::new(data),
9066            }),
9067        }),
9068        StorageBuffer::Backend(buffer) => {
9069            let group = OwnedTensorGroup::from_backend_buffer(
9070                group_shape.clone(),
9071                StorageBuffer::Backend(buffer),
9072                placement.clone(),
9073            )?;
9074            Ok(TypedTensor {
9075                shape: group_shape,
9076                placement,
9077                storage: DynamicStorage::Group(GroupStorage {
9078                    group: Box::new(group),
9079                }),
9080            })
9081        }
9082    }
9083}
9084
9085impl<T: TensorScalar + Zero, R: TensorRank> TypedTensor<T, R> {
9086    /// Allocate a zero-filled tensor.
9087    ///
9088    /// # Examples
9089    ///
9090    /// ```rust
9091    /// use tenferro_tensor::TypedTensor;
9092    ///
9093    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
9094    /// assert_eq!(t.n_elements(), 6);
9095    /// ```
9096    /// # Errors
9097    ///
9098    /// Returns [`crate::Error::Validation`] with
9099    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
9100    /// product or compact-stride arithmetic overflows.
9101    /// A vector or slice with a different static rank returns
9102    /// [`tenferro_tensor_core::ValidationError::RankMismatch`].
9103    pub fn zeros(shape: impl tenferro_tensor_core::IntoRankShape<R>) -> crate::Result<Self> {
9104        typed_tensor_zeros(shape)
9105    }
9106}
9107
9108impl<T: TensorScalar + One + Zero, R: TensorRank> TypedTensor<T, R> {
9109    /// Allocate a one-filled tensor.
9110    ///
9111    /// # Examples
9112    ///
9113    /// ```rust
9114    /// use tenferro_tensor::TypedTensor;
9115    ///
9116    /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
9117    /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
9118    /// ```
9119    /// # Errors
9120    ///
9121    /// Returns [`crate::Error::Validation`] with
9122    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
9123    /// product or compact-stride arithmetic overflows.
9124    /// A vector or slice with a different static rank returns
9125    /// [`tenferro_tensor_core::ValidationError::RankMismatch`].
9126    pub fn ones(shape: impl tenferro_tensor_core::IntoRankShape<R>) -> crate::Result<Self> {
9127        typed_tensor_ones(shape)
9128    }
9129}
9130
9131impl<T, R: TensorRank> TypedTensor<T, R> {
9132    /// Adopt a column-major host `Vec<T>` with no scalar, copy or thread-safety bound.
9133    ///
9134    /// # Examples
9135    /// ```
9136    /// use tenferro_tensor::{Rank, TypedTensor};
9137    /// struct Custom(String);
9138    /// let tensor = TypedTensor::<Custom, Rank<2>>::from_vec_col_major(
9139    ///     [1, 2], vec![Custom("a".into()), Custom("b".into())],
9140    /// )?;
9141    /// assert_eq!(tensor.get(&[0, 1])?.0.as_str(), "b");
9142    /// # Ok::<(), tenferro_tensor::Error>(())
9143    /// ```
9144    /// # Errors
9145    /// Returns [`crate::Error::Validation`] for a rank mismatch, shape/data
9146    /// length mismatch or shape/stride arithmetic overflow.
9147    pub fn from_vec_col_major(
9148        shape: impl tenferro_tensor_core::IntoRankShape<R>,
9149        data: Vec<T>,
9150    ) -> crate::Result<Self> {
9151        typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
9152    }
9153
9154    /// Explicitly import row-major host values into column-major storage.
9155    ///
9156    /// Clones each input element once; no backend or scalar registration is used.
9157    ///
9158    /// # Examples
9159    /// ```
9160    /// use tenferro_tensor::{Rank, TypedTensor};
9161    /// let tensor = TypedTensor::<i32, Rank<2>>::from_vec_row_major(
9162    ///     [2, 3], vec![1, 2, 3, 4, 5, 6],
9163    /// )?;
9164    /// assert_eq!(tensor.get(&[1, 0])?, &4);
9165    /// assert_eq!(tensor.get(&[0, 2])?, &3);
9166    /// # Ok::<(), tenferro_tensor::Error>(())
9167    /// ```
9168    /// # Errors
9169    /// Returns [`crate::Error::Validation`] for a rank, shape-length or stride overflow,
9170    /// or when the shape product disagrees with the input length.
9171    pub fn from_vec_row_major(
9172        shape: impl tenferro_tensor_core::IntoRankShape<R>,
9173        data: Vec<T>,
9174    ) -> crate::Result<Self>
9175    where
9176        T: Clone,
9177    {
9178        let op = "from_vec_row_major";
9179        let (shape, reordered) = row_major_reorder(shape, data, op)?;
9180        typed_tensor_from_vec_col_major(shape, reordered, op)
9181    }
9182
9183    /// Consume this compact tensor and return the original host `Vec<T>`.
9184    ///
9185    /// # Examples
9186    ///
9187    /// ```
9188    /// use tenferro_tensor::TypedTensor;
9189    /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
9190    /// let data = tensor.into_host_vec().map_err(|failure| failure.into_parts().1)?;
9191    /// assert_eq!(data, vec![1, 2]);
9192    /// # Ok::<(), tenferro_tensor::Error>(())
9193    /// ```
9194    ///
9195    /// # Errors
9196    /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9197    /// storage is device-only or the managed root cannot export a host vector.
9198    pub fn into_host_vec(self) -> std::result::Result<Vec<T>, ReinterpretError<Self>>
9199    where
9200        T: 'static,
9201    {
9202        let TypedTensor {
9203            shape,
9204            placement,
9205            storage,
9206        } = self;
9207        match storage {
9208            DynamicStorage::Host(HostStorage { data }) => Ok(data.into_vec()),
9209            DynamicStorage::Group(GroupStorage { group }) => match group.into_host_vec::<T>() {
9210                Ok(data) => Ok(data),
9211                Err((group, error)) => Err(ReinterpretError::new(
9212                    TypedTensor {
9213                        shape,
9214                        placement,
9215                        storage: DynamicStorage::Group(GroupStorage {
9216                            group: Box::new(group),
9217                        }),
9218                    },
9219                    error,
9220                )),
9221            },
9222        }
9223    }
9224
9225    /// Consume the original host vector along with its column-major shape.
9226    ///
9227    /// # Examples
9228    ///
9229    /// ```
9230    /// use tenferro_tensor::TypedTensor;
9231    /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2, 1], vec![1, 2])?;
9232    /// let (shape, data) = tensor.into_vec_col_major().map_err(|failure| failure.into_parts().1)?;
9233    /// assert_eq!(shape, vec![2, 1]);
9234    /// assert_eq!(data, vec![1, 2]);
9235    /// # Ok::<(), tenferro_tensor::Error>(())
9236    /// ```
9237    ///
9238    /// # Errors
9239    /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9240    /// storage is device-only or the managed root cannot export a host vector.
9241    pub fn into_vec_col_major(
9242        self,
9243    ) -> std::result::Result<(Vec<usize>, Vec<T>), ReinterpretError<Self>>
9244    where
9245        T: 'static,
9246    {
9247        let shape = self.shape().to_vec();
9248        match self.into_host_vec() {
9249            Ok(data) => Ok((shape, data)),
9250            Err(failure) => Err(failure),
9251        }
9252    }
9253
9254    /// Borrow the plain host values (or an existing managed host root).
9255    ///
9256    /// # Examples
9257    ///
9258    /// ```
9259    /// use tenferro_tensor::TypedTensor;
9260    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
9261    /// assert_eq!(tensor.host_data()?, &[1.0, 2.0]);
9262    /// # Ok::<(), tenferro_tensor::Error>(())
9263    /// ```
9264    ///
9265    /// # Errors
9266    /// Returns [`crate::Error::RuntimeState`] for device-only storage.
9267    pub fn host_data(&self) -> crate::Result<&[T]> {
9268        match &self.storage {
9269            DynamicStorage::Host(HostStorage { data }) => Ok(data.as_slice()),
9270            DynamicStorage::Group(core) => core.group.host_slice::<T>(),
9271        }
9272    }
9273
9274    /// Borrow compact host storage as a flat column-major slice without copying.
9275    ///
9276    /// # Examples
9277    /// ```
9278    /// use tenferro_tensor::TypedTensor;
9279    /// let tensor = TypedTensor::<String>::from_vec_col_major([2], vec!["a".into(), "b".into()])?;
9280    /// assert_eq!(tensor.as_slice()?, &["a", "b"]);
9281    /// # Ok::<(), tenferro_tensor::Error>(())
9282    /// ```
9283    /// # Errors
9284    /// Device-only storage returns [`crate::Error::RuntimeState`].
9285    pub fn as_slice(&self) -> crate::Result<&[T]> {
9286        self.host_data()
9287    }
9288
9289    /// Mutably borrow the plain host values (or an existing managed host root).
9290    ///
9291    /// # Examples
9292    ///
9293    /// ```
9294    /// use tenferro_tensor::TypedTensor;
9295    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
9296    /// tensor.host_data_mut()?[0] = 5.0;
9297    /// assert_eq!(tensor.host_data()?, &[5.0, 2.0]);
9298    /// # Ok::<(), tenferro_tensor::Error>(())
9299    /// ```
9300    ///
9301    /// # Errors
9302    /// Returns [`crate::Error::RuntimeState`] for device-only storage.
9303    pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
9304        match &mut self.storage {
9305            DynamicStorage::Host(HostStorage { data }) => Ok(data.as_mut_slice()),
9306            DynamicStorage::Group(core) => core.group.host_slice_mut::<T>(),
9307        }
9308    }
9309
9310    /// Borrow an element by checked column-major multi-index.
9311    ///
9312    /// # Examples
9313    /// ```
9314    /// use tenferro_tensor::TypedTensor;
9315    /// let tensor = TypedTensor::<String>::from_vec_col_major([2], vec!["a".into(), "b".into()])?;
9316    /// assert_eq!(tensor.get(&[1])?, "b");
9317    /// # Ok::<(), tenferro_tensor::Error>(())
9318    /// ```
9319    /// # Errors
9320    /// Invalid rank, coordinates or offset return [`crate::Error::Validation`];
9321    /// device-only storage returns [`crate::Error::RuntimeState`].
9322    pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
9323        let offset = self.linear_offset(indices)?;
9324        self.host_data()?.get(offset).ok_or_else(|| {
9325            crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
9326        })
9327    }
9328
9329    /// Exclusively borrow an element by checked column-major multi-index.
9330    ///
9331    /// # Examples
9332    /// ```
9333    /// use tenferro_tensor::TypedTensor;
9334    /// let mut tensor = TypedTensor::<String>::from_vec_col_major([1], vec!["a".into()])?;
9335    /// *tensor.get_mut(&[0])? = "b".into();
9336    /// assert_eq!(tensor.get(&[0])?, "b");
9337    /// # Ok::<(), tenferro_tensor::Error>(())
9338    /// ```
9339    /// # Errors
9340    /// Invalid rank, coordinates or offset return [`crate::Error::Validation`];
9341    /// device-only storage returns [`crate::Error::RuntimeState`].
9342    pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
9343        let offset = self.linear_offset(indices)?;
9344        self.host_data_mut()?.get_mut(offset).ok_or_else(|| {
9345            crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
9346        })
9347    }
9348
9349    /// Make an explicit independent host copy with the same shape and placement.
9350    ///
9351    /// The dynamic `TypedTensor` does not implement [`Clone`] (only the
9352    /// host-only representation does): the copy reads host data, rejects
9353    /// device-only storage, and creates a new owner, so it is fallible. See
9354    /// [`Tensor::duplicate`] for the sharing alternatives.
9355    ///
9356    /// # Examples
9357    /// ```
9358    /// use tenferro_tensor::TypedTensor;
9359    /// let tensor = TypedTensor::<String>::from_vec_col_major([1], vec!["a".into()])?;
9360    /// assert_eq!(tensor.duplicate()?.get(&[0])?, "a");
9361    /// # Ok::<(), tenferro_tensor::Error>(())
9362    /// ```
9363    /// # Errors
9364    /// Device-only storage returns [`crate::Error::RuntimeState`]; invalid
9365    /// host metadata returns [`crate::Error::Validation`].
9366    pub fn duplicate(&self) -> crate::Result<Self>
9367    where
9368        T: Clone,
9369    {
9370        let mut copy = Self::from_vec_col_major(
9371            R::shape_from_vec(shape_vec(self.shape()))
9372                .map_err(|err| tensor_layout_error("TypedTensor::duplicate", err))?,
9373            self.host_data()?.to_vec(),
9374        )?;
9375        copy.set_placement(self.placement().clone());
9376        Ok(copy)
9377    }
9378
9379    /// Create a tensor from an existing buffer and compact column-major layout.
9380    ///
9381    /// This preserves the owned tensor invariant that layout metadata is
9382    /// compact column-major, including for backend-owned buffers.
9383    ///
9384    /// # Examples
9385    ///
9386    /// ```
9387    /// use tenferro_tensor::{StorageBuffer, Placement, TypedTensor};
9388    ///
9389    /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
9390    ///     vec![2],
9391    ///     StorageBuffer::Host(vec![1.0, 2.0]),
9392    ///     Placement {
9393    ///         memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
9394    ///         device: None,
9395    ///         cpu_affinity: None,
9396    ///     },
9397    /// )
9398    /// .unwrap();
9399    /// assert_eq!(tensor.shape(), &[2]);
9400    /// ```
9401    /// # Errors
9402    ///
9403    /// Returns [`crate::Error::Validation`] with
9404    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
9405    /// the shape product differs from the buffer length,
9406    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape or
9407    /// stride arithmetic overflows, or
9408    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a supplied
9409    /// rank-specific shape cannot be represented.
9410    pub fn from_buffer_col_major(
9411        shape: impl tenferro_tensor_core::IntoRankShape<R>,
9412        buffer: StorageBuffer<T>,
9413        placement: Placement,
9414    ) -> crate::Result<Self>
9415    where
9416        T: TensorScalar + Send + Sync + 'static,
9417    {
9418        typed_tensor_from_buffer_col_major(shape, buffer, placement)
9419    }
9420
9421    /// Consume a scalar-independent provider root into one compact tensor.
9422    #[doc(hidden)]
9423    pub fn from_backend_allocation(
9424        shape: impl tenferro_tensor_core::IntoRankShape<R>,
9425        allocation: Box<dyn crate::BackendAllocation>,
9426        placement: Placement,
9427    ) -> crate::Result<Self>
9428    where
9429        T: TensorScalar + Send + Sync + 'static,
9430    {
9431        typed_tensor_from_backend_allocation(shape, allocation, placement)
9432    }
9433
9434    /// Convert this tensor into static rank metadata after validating its rank.
9435    ///
9436    /// The buffer and placement are preserved. This method changes only the
9437    /// compile-time rank marker on the owned compact column-major tensor.
9438    ///
9439    /// # Examples
9440    ///
9441    /// ```
9442    /// use tenferro_tensor::{Rank, TypedTensor};
9443    ///
9444    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
9445    /// let Ok(ranked) = tensor.try_into_rank::<2>() else {
9446    ///     panic!("a rank-2 tensor converts to two axes")
9447    /// };
9448    /// assert_eq!(ranked.shape(), &[2, 3]);
9449    /// # Ok::<(), tenferro_tensor::Error>(())
9450    /// ```
9451    /// # Errors
9452    ///
9453    /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9454    /// typed rank does not match the existing shape, with
9455    /// [`crate::Error::Validation`] and
9456    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] as the cause,
9457    /// or when the compact rank layout overflows.
9458    pub fn try_into_rank<const N: usize>(
9459        self,
9460    ) -> std::result::Result<TypedTensor<T, Rank<N>>, ReinterpretError<Self>> {
9461        let op = "TypedTensor::try_into_rank";
9462        let actual = self.shape().len();
9463        let shape: [usize; N] = match self.shape().try_into() {
9464            Ok(shape) => shape,
9465            Err(_) => {
9466                return Err(ReinterpretError::new(
9467                    self,
9468                    tensor_layout_error(
9469                        op,
9470                        ValidationError::RankMismatch {
9471                            expected: N,
9472                            actual,
9473                        },
9474                    ),
9475                ))
9476            }
9477        };
9478        if let Err(error) =
9479            TensorLayout::<Rank<N>>::compact(shape).map_err(|err| tensor_layout_error(op, err))
9480        {
9481            return Err(ReinterpretError::new(self, error));
9482        }
9483        let TypedTensor {
9484            placement, storage, ..
9485        } = self;
9486        let storage = match storage {
9487            DynamicStorage::Host(host) => DynamicStorage::Host(host),
9488            DynamicStorage::Group(GroupStorage { group }) => DynamicStorage::Group(GroupStorage {
9489                group: Box::new(OwnedTensorGroup {
9490                    group: group.group,
9491                    slot: group.slot,
9492                    allocation_index: group.allocation_index,
9493                    host_ptr: group.host_ptr,
9494                    host_byte_len: group.host_byte_len,
9495                    _rank: PhantomData,
9496                }),
9497            }),
9498        };
9499        Ok(TypedTensor {
9500            shape,
9501            placement,
9502            storage,
9503        })
9504    }
9505
9506    /// Return the storage backing this tensor.
9507    ///
9508    /// This is an explicit storage-inspection API for backend glue and tests.
9509    /// Host value inspection should prefer [`TypedTensor::host_data`] when the
9510    /// caller requires host storage.
9511    ///
9512    /// # Panics
9513    ///
9514    /// Panics only if the typed descriptor and its single group owner are
9515    /// internally inconsistent.
9516    ///
9517    /// # Examples
9518    ///
9519    /// ```
9520    /// use tenferro_tensor::{StorageBuffer, TypedTensor};
9521    ///
9522    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
9523    /// assert!(matches!(t.buffer(), StorageBuffer::Host(_)));
9524    /// ```
9525    pub fn buffer(&self) -> &StorageBuffer<T>
9526    where
9527        T: 'static,
9528    {
9529        match &self.storage {
9530            DynamicStorage::Host(HostStorage { data }) => &data.buffer,
9531            DynamicStorage::Group(core) => core
9532                .group
9533                .host_buffer::<T>()
9534                .or_else(|| core.group.backend_buffer::<T>())
9535                .unwrap_or_else(|| unreachable!("typed tensor group storage mismatch")),
9536        }
9537    }
9538
9539    /// Return the provider family for this tensor when backend-owned.
9540    #[doc(hidden)]
9541    pub fn backend_family(&self) -> Option<&'static str>
9542    where
9543        T: TensorScalar + 'static,
9544    {
9545        self.as_view().backend_family()
9546    }
9547
9548    /// Return the opaque backend buffer for backend-owned tensors.
9549    #[doc(hidden)]
9550    pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>>
9551    where
9552        T: 'static,
9553    {
9554        match &self.storage {
9555            DynamicStorage::Host(..) => None,
9556            DynamicStorage::Group(core) => match core.group.backend_buffer::<T>() {
9557                Some(StorageBuffer::Backend(buffer)) => Some(buffer.as_ref()),
9558                Some(StorageBuffer::Host(_)) | None => None,
9559            },
9560        }
9561    }
9562
9563    /// Return the mutable backend buffer for an exclusive owner borrow.
9564    #[doc(hidden)]
9565    pub fn backend_buffer_mut(&mut self) -> Option<&mut dyn BackendStorage<T>>
9566    where
9567        T: 'static,
9568    {
9569        let DynamicStorage::Group(core) = &mut self.storage else {
9570            return None;
9571        };
9572        match core.group.backend_buffer_mut::<T>()? {
9573            StorageBuffer::Host(_) => None,
9574            StorageBuffer::Backend(buffer) => Some(buffer.as_mut()),
9575        }
9576    }
9577
9578    /// Prepare this backend tensor for one provider-native read binding.
9579    #[doc(hidden)]
9580    pub fn prepare_device_read(
9581        &self,
9582        op: &'static str,
9583    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
9584    where
9585        T: TensorScalar + 'static,
9586    {
9587        let DynamicStorage::Group(core) = &self.storage else {
9588            return Err(crate::Error::runtime_state_source(
9589                op,
9590                crate::AccessError::Unsupported { backend: "host" },
9591            ));
9592        };
9593        let layout = self.layout();
9594        core.group
9595            .prepare_device_read_for_layout::<T>(&layout)
9596            .map_err(|error| crate::Error::runtime_state_source(op, error))
9597    }
9598
9599    /// Prepare this backend tensor for one provider-native write binding.
9600    #[doc(hidden)]
9601    pub fn prepare_device_write(
9602        &mut self,
9603        op: &'static str,
9604    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
9605    where
9606        T: TensorScalar + 'static,
9607    {
9608        let layout = self.layout();
9609        let DynamicStorage::Group(core) = &mut self.storage else {
9610            return Err(crate::Error::runtime_state_source(
9611                op,
9612                crate::AccessError::Unsupported { backend: "host" },
9613            ));
9614        };
9615        core.group
9616            .prepare_device_write_for_layout::<T>(&layout)
9617            .map_err(|error| crate::Error::runtime_state_source(op, error))
9618    }
9619
9620    pub(crate) fn buffer_len(&self) -> usize
9621    where
9622        T: 'static,
9623    {
9624        match &self.storage {
9625            DynamicStorage::Host(HostStorage { data }) => data.as_slice().len(),
9626            DynamicStorage::Group(core) => core
9627                .group
9628                .group
9629                .descriptor_len(core.group.slot)
9630                .unwrap_or_else(|| unreachable!("typed tensor group descriptor mismatch")),
9631        }
9632    }
9633
9634    /// Return the shared-allocation domain carried by the backend buffer.
9635    ///
9636    /// # Examples
9637    ///
9638    /// ```rust
9639    /// use tenferro_tensor::TypedTensor;
9640    ///
9641    /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
9642    /// assert_eq!(tensor.allocation_domain(), None);
9643    /// # Ok::<(), tenferro_tensor::Error>(())
9644    /// ```
9645    pub fn allocation_domain(&self) -> Option<AllocationDomainId>
9646    where
9647        T: 'static,
9648    {
9649        match &self.storage {
9650            DynamicStorage::Host(..) => None,
9651            DynamicStorage::Group(core) => core
9652                .group
9653                .group
9654                .backend_identity(core.group.slot)
9655                .map(|(domain, _)| domain),
9656        }
9657    }
9658
9659    /// Return the stable physical backend allocation identity.
9660    ///
9661    /// # Examples
9662    ///
9663    /// ```rust
9664    /// use tenferro_tensor::TypedTensor;
9665    ///
9666    /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
9667    /// assert_eq!(tensor.allocation_id(), None);
9668    /// # Ok::<(), tenferro_tensor::Error>(())
9669    /// ```
9670    pub fn allocation_id(&self) -> Option<AllocationId>
9671    where
9672        T: 'static,
9673    {
9674        match &self.storage {
9675            DynamicStorage::Host(..) => None,
9676            DynamicStorage::Group(core) => core
9677                .group
9678                .group
9679                .backend_identity(core.group.slot)
9680                .map(|(_, allocation)| allocation),
9681        }
9682    }
9683
9684    /// Borrow this tensor as a typed view preserving rank and layout metadata.
9685    ///
9686    /// # Panics
9687    ///
9688    /// Panics only if the typed descriptor and its single group owner are
9689    /// internally inconsistent.
9690    ///
9691    /// # Examples
9692    ///
9693    /// ```rust
9694    /// use tenferro_tensor::{Rank, TypedTensor};
9695    ///
9696    /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
9697    /// let view = tensor.as_view();
9698    /// assert_eq!(view.strides(), &[1, 2]);
9699    /// ```
9700    pub fn as_view(&self) -> TypedTensorView<'_, T, R>
9701    where
9702        T: 'static,
9703    {
9704        let layout = self.layout();
9705        let placement = self.placement.clone();
9706        match &self.storage {
9707            DynamicStorage::Host(HostStorage { data }) => TypedTensorView {
9708                buffer: TensorStorageRef::Host(data.as_slice()),
9709                root: None,
9710                layout,
9711                placement,
9712                _representation: std::marker::PhantomData,
9713            },
9714            DynamicStorage::Group(core) => {
9715                // Managed owners are constructed only with their matching preset T.
9716                let root = core
9717                    .group
9718                    .group
9719                    .view_raw::<T, R>(core.group.slot)
9720                    .unwrap_or_else(|error| {
9721                        unreachable!("typed tensor group descriptor mismatch: {error}")
9722                    });
9723                let buffer = if let Some(allocation) = root.backend_allocation() {
9724                    TensorStorageRef::Root(allocation)
9725                } else {
9726                    TensorStorageRef::Host(core.group.host_slice::<T>().unwrap_or_default())
9727                };
9728                TypedTensorView {
9729                    buffer,
9730                    root: Some(root),
9731                    layout,
9732                    placement,
9733                    _representation: std::marker::PhantomData,
9734                }
9735            }
9736        }
9737    }
9738
9739    /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
9740    ///
9741    /// # Panics
9742    ///
9743    /// Panics only if the typed descriptor and its single group owner are
9744    /// internally inconsistent.
9745    ///
9746    /// # Examples
9747    ///
9748    /// ```rust
9749    /// use tenferro_tensor::TypedTensor;
9750    ///
9751    /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
9752    /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
9753    /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
9754    /// ```
9755    pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
9756    where
9757        T: TensorScalar + 'static,
9758    {
9759        let layout = self.layout();
9760        let placement = self.placement.clone();
9761        match &mut self.storage {
9762            DynamicStorage::Host(HostStorage { data }) => TypedTensorViewMut {
9763                buffer: TensorStorageRefMut::Host(data.as_mut_slice()),
9764                root: None,
9765                layout,
9766                placement,
9767                _representation: std::marker::PhantomData,
9768            },
9769            DynamicStorage::Group(core) => {
9770                let mut root = core.group.view_mut::<T>().unwrap_or_else(|error| {
9771                    unreachable!("typed tensor group descriptor mismatch: {error}")
9772                });
9773                let buffer = if let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut()
9774                {
9775                    TensorStorageRefMut::Backend(buffer.as_mut())
9776                } else {
9777                    TensorStorageRefMut::Host(root.host_slice_mut().unwrap_or_else(|error| {
9778                        unreachable!("typed tensor group descriptor is not host-backed: {error}")
9779                    }))
9780                };
9781                TypedTensorViewMut {
9782                    buffer,
9783                    root: Some(root),
9784                    layout,
9785                    placement,
9786                    _representation: std::marker::PhantomData,
9787                }
9788            }
9789        }
9790    }
9791
9792    /// Borrow a read-only strided region view over this tensor's backend
9793    /// (device) buffer from explicit layout metadata.
9794    ///
9795    /// This is a metadata-only view: no data is copied or transferred. The
9796    /// layout's reachable element span is validated against the backend
9797    /// buffer's physical length. Host-backed tensors are rejected with an
9798    /// explicit backend error; host regions are expressed with
9799    /// [`TypedTensorView::from_slice`] over host storage instead.
9800    ///
9801    /// # Examples
9802    ///
9803    /// ```rust
9804    /// use tenferro_tensor::TypedTensor;
9805    ///
9806    /// // Host tensors are rejected: this constructor is for backend buffers.
9807    /// let host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
9808    /// let err = host.backend_region_view(vec![2, 2], vec![1, 2], 0).unwrap_err();
9809    /// assert!(err.to_string().contains("backend"));
9810    /// ```
9811    /// # Errors
9812    ///
9813    /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
9814    /// backend region views require a backend buffer. It returns
9815    /// [`crate::Error::Validation`] with
9816    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
9817    /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
9818    /// when the region exceeds the backend buffer, or
9819    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
9820    /// arithmetic overflow.
9821    pub fn backend_region_view(
9822        &self,
9823        shape: Vec<usize>,
9824        strides: Vec<isize>,
9825        offset: isize,
9826    ) -> crate::Result<TypedTensorView<'_, T, DynRank>>
9827    where
9828        T: TensorScalar + 'static,
9829    {
9830        let op = "TypedTensor::backend_region_view";
9831        let DynamicStorage::Group(core) = &self.storage else {
9832            return Err(crate::Error::runtime_state(op, "expected a backend (device) allocation; host tensors use TypedTensorView::from_slice over host storage"));
9833        };
9834        let root = core.group.view_dyn::<T>()?;
9835        let Some(allocation) = root.backend_allocation() else {
9836            return Err(crate::Error::runtime_state(
9837                op,
9838                "expected a backend (device) allocation; host tensors use \
9839                 TypedTensorView::from_slice over host storage",
9840            ));
9841        };
9842        let element_len = allocation
9843            .root_extent()
9844            .byte_len()
9845            .checked_div(size_of::<T>())
9846            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
9847        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, element_len)
9848            .map_err(|err| tensor_layout_error(op, err))?;
9849        Ok(TypedTensorView {
9850            buffer: TensorStorageRef::Root(allocation),
9851            root: Some(root),
9852            layout,
9853            placement: self.placement.clone(),
9854            _representation: std::marker::PhantomData,
9855        })
9856    }
9857
9858    /// Borrow a mutable strided region view over this tensor's backend
9859    /// (device) buffer from explicit layout metadata.
9860    ///
9861    /// This is the mutable counterpart of
9862    /// [`TypedTensor::backend_region_view`]. The layout's reachable element
9863    /// span is validated against the backend buffer's physical length, and
9864    /// layouts whose logical elements alias the same physical element are
9865    /// rejected. Host-backed tensors are rejected with an explicit backend
9866    /// error; mutable host regions must go through
9867    /// [`TypedTensorViewMut::try_multi_slice_mut`] or host constructors.
9868    ///
9869    /// The returned view borrows the tensor's backend owner exclusively for its
9870    /// lifetime. This keeps write authority tied to the owner; a second mutable
9871    /// region view must be created only after the first borrow ends.
9872    ///
9873    /// # Examples
9874    ///
9875    /// ```rust
9876    /// use tenferro_tensor::TypedTensor;
9877    ///
9878    /// // Host tensors are rejected: this constructor is for backend buffers.
9879    /// let mut host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
9880    /// let err = host.backend_region_view_mut(vec![2, 2], vec![1, 2], 0).unwrap_err();
9881    /// assert!(err.to_string().contains("backend"));
9882    /// ```
9883    /// # Errors
9884    ///
9885    /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
9886    /// mutable backend region views require a backend buffer. It returns
9887    /// [`crate::Error::Validation`] with
9888    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
9889    /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
9890    /// when the region exceeds the backend buffer,
9891    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
9892    /// logical elements alias, or
9893    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
9894    /// arithmetic overflow.
9895    pub fn backend_region_view_mut(
9896        &mut self,
9897        shape: Vec<usize>,
9898        strides: Vec<isize>,
9899        offset: isize,
9900    ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>>
9901    where
9902        T: TensorScalar + 'static,
9903    {
9904        let op = "TypedTensor::backend_region_view_mut";
9905        let placement = self.placement.clone();
9906        let DynamicStorage::Group(core) = &mut self.storage else {
9907            return Err(crate::Error::runtime_state(op, "expected a backend (device) buffer; mutable host regions use TypedTensorViewMut host constructors or try_multi_slice_mut"));
9908        };
9909        let mut root = core.group.view_mut_dyn::<T>()?;
9910        let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut() else {
9911            return Err(crate::Error::runtime_state(
9912                op,
9913                "expected a backend (device) buffer; mutable host regions use \
9914                 TypedTensorViewMut host constructors or try_multi_slice_mut",
9915            ));
9916        };
9917        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
9918            .map_err(|err| tensor_layout_error(op, err))?;
9919        layout
9920            .validate_mutable_no_overlap()
9921            .map_err(|err| tensor_layout_error(op, err))?;
9922        Ok(TypedTensorViewMut {
9923            buffer: TensorStorageRefMut::Backend(buffer.as_mut()),
9924            root: Some(root),
9925            layout,
9926            placement,
9927            _representation: std::marker::PhantomData,
9928        })
9929    }
9930
9931    /// Consume this tensor and return its storage, layout, and placement.
9932    ///
9933    /// # Examples
9934    ///
9935    /// ```
9936    /// use tenferro_tensor::{StorageBuffer, TypedTensor};
9937    ///
9938    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
9939    /// let Ok((buffer, layout, placement)) = t.into_parts() else {
9940    ///     panic!("a plain host owner extracts")
9941    /// };
9942    /// assert!(matches!(buffer, StorageBuffer::Host(_)));
9943    /// assert_eq!(layout.shape(), &[2]);
9944    /// assert!(placement.device.is_none());
9945    /// # Ok::<(), tenferro_tensor::Error>(())
9946    /// ```
9947    ///
9948    /// # Errors
9949    ///
9950    /// Returns [`ReinterpretError`] carrying the unchanged tensor when it uses
9951    /// backend storage; download it before extracting host storage.
9952    pub fn into_parts(
9953        self,
9954    ) -> std::result::Result<(StorageBuffer<T>, TensorLayout<R>, Placement), ReinterpretError<Self>>
9955    where
9956        T: TensorScalar,
9957    {
9958        let TypedTensor {
9959            shape,
9960            placement,
9961            storage,
9962        } = self;
9963        let layout = TensorLayout::compact(shape.clone())
9964            .unwrap_or_else(|err| unreachable!("validated owned shape: {err}"));
9965        match storage {
9966            DynamicStorage::Host(HostStorage { data }) => {
9967                Ok((StorageBuffer::Host(data.into_vec()), layout, placement))
9968            }
9969            DynamicStorage::Group(GroupStorage { group }) => match group.into_host_vec::<T>() {
9970                Ok(data) => Ok((StorageBuffer::Host(data), layout, placement)),
9971                Err((group, error)) => Err(ReinterpretError::new(
9972                    TypedTensor {
9973                        shape,
9974                        placement,
9975                        storage: DynamicStorage::Group(GroupStorage {
9976                            group: Box::new(group),
9977                        }),
9978                    },
9979                    error,
9980                )),
9981            },
9982        }
9983    }
9984}
9985
9986impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R> {
9987    fn into_managed(self) -> Self {
9988        let TypedTensor {
9989            shape,
9990            placement,
9991            storage,
9992        } = self;
9993        match storage {
9994            DynamicStorage::Host(HostStorage { data }) => {
9995                let group = promote_host_group::<T, R>(shape.clone(), data)
9996                    .unwrap_or_else(|err| unreachable!("a validated host owner promotes: {err}"));
9997                Self {
9998                    shape,
9999                    placement,
10000                    storage: DynamicStorage::Group(GroupStorage {
10001                        group: Box::new(group),
10002                    }),
10003                }
10004            }
10005            DynamicStorage::Group(core) => Self {
10006                shape,
10007                placement,
10008                storage: DynamicStorage::Group(core),
10009            },
10010        }
10011    }
10012
10013    fn into_group_parts(self) -> (AllocationGroup, DescriptorSlot) {
10014        let TypedTensor {
10015            placement, storage, ..
10016        } = self.into_managed();
10017        let DynamicStorage::Group(GroupStorage { group }) = storage else {
10018            unreachable!("explicit group promotion produces managed storage")
10019        };
10020        let (mut group, slot) = group.into_parts();
10021        group.publish_live_descriptor_placement(slot, placement);
10022        (group, slot)
10023    }
10024
10025    /// Construct a backend-pooled host tensor with weak final-owner reclamation.
10026    /// Explicit Vec extraction disarms reclamation; aliases and retained groups
10027    /// keep the original root alive. The recycler must not acquire session locks.
10028    ///
10029    /// # Errors
10030    /// Returns validation errors for invalid shape/length and runtime-state errors
10031    /// if the freshly created host root cannot attach its recycler.
10032    #[doc(hidden)]
10033    pub fn from_vec_col_major_with_recycler(
10034        shape: impl tenferro_tensor_core::IntoRankShape<R>,
10035        data: Vec<T>,
10036        recycler: std::sync::Weak<dyn crate::HostBufferRecycler<T>>,
10037    ) -> crate::Result<Self> {
10038        let mut tensor = Self::from_vec_col_major(shape, data)?;
10039        if let DynamicStorage::Host(HostStorage { data }) = &mut tensor.storage {
10040            data.recycler = Some(recycler);
10041        }
10042        Ok(tensor)
10043    }
10044
10045    /// Borrow compact host-visible storage through one synchronization guard.
10046    #[doc(hidden)]
10047    pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
10048    where
10049        T: TensorScalar + 'static,
10050    {
10051        self.as_view().with_host_read(f)
10052    }
10053
10054    /// Borrow compact host-visible storage through one exclusive write guard.
10055    #[doc(hidden)]
10056    pub fn with_host_write<U>(&mut self, f: impl FnOnce(&mut [T]) -> U) -> crate::Result<U>
10057    where
10058        T: TensorScalar + 'static,
10059    {
10060        match &mut self.storage {
10061            DynamicStorage::Host(HostStorage { data }) => Ok(f(data.as_mut_slice())),
10062            DynamicStorage::Group(core) => {
10063                let slot = core.group.slot;
10064                let mut view = core
10065                    .group
10066                    .group
10067                    .view_mut::<T, R>(slot)
10068                    .map_err(|error| group_error("TypedTensor::with_host_write", error))?;
10069                let mut prepared = view.prepare_host_write().map_err(|error| {
10070                    crate::Error::runtime_state("TypedTensor::with_host_write", error.to_string())
10071                })?;
10072                let slice = prepared.as_slice_mut().ok_or_else(|| {
10073                    crate::Error::unsupported(
10074                        "TypedTensor::with_host_write",
10075                        "host guard access requires a compact descriptor",
10076                    )
10077                })?;
10078                Ok(f(slice))
10079            }
10080        }
10081    }
10082
10083    fn group_host_slice(&self) -> &[T] {
10084        let DynamicStorage::Group(core) = &self.storage else {
10085            return &[];
10086        };
10087        core.group
10088            .view::<T>()
10089            .ok()
10090            .and_then(|view| view.host_slice().ok())
10091            .unwrap_or_default()
10092    }
10093}
10094
10095/// Layout builder used by an owning representation reinterpretation.
10096type ReinterpretLayoutFn =
10097    fn(&[usize], &[isize], isize, usize, &'static str) -> crate::Result<TensorLayout<DynRank>>;
10098
10099fn reinterpret_owned<S: TensorScalar, U: TensorScalar, R: TensorRank>(
10100    owner: TypedTensor<S, R>,
10101    from: DType,
10102    to: DType,
10103    make_layout: ReinterpretLayoutFn,
10104    op: &'static str,
10105) -> Result<TypedTensor<U>, ReinterpretError<TypedTensor<S, R>>> {
10106    if let Err(error) = validate_representation_pair(op, from, to) {
10107        return Err(ReinterpretError::new(owner, error));
10108    }
10109    if matches!(to, DType::C32 | DType::C64) && !owner.buffer_len().is_multiple_of(2) {
10110        return Err(ReinterpretError::new(
10111            owner,
10112            crate::Error::invalid_argument(
10113                op,
10114                "buffer",
10115                "the owned real buffer must contain an even number of elements",
10116            ),
10117        ));
10118    }
10119    let source_layout = owner.layout();
10120    let target_layout = match make_layout(
10121        source_layout.shape(),
10122        source_layout.strides(),
10123        source_layout.offset(),
10124        owner.buffer_len(),
10125        op,
10126    ) {
10127        Ok(layout) => layout,
10128        Err(error) => return Err(ReinterpretError::new(owner, error)),
10129    };
10130    // Only an explicit owning representation conversion promotes a plain owner;
10131    // borrowed session dispatch never passes through a group.
10132    let TypedTensor {
10133        shape,
10134        placement,
10135        storage,
10136    } = owner.into_managed();
10137    let DynamicStorage::Group(GroupStorage { group }) = storage else {
10138        unreachable!("explicit group promotion produces managed storage")
10139    };
10140    match group.reinterpret::<S, U>(
10141        target_layout.shape().to_vec(),
10142        target_layout.strides().to_vec(),
10143        target_layout.offset(),
10144    ) {
10145        Ok(group) => Ok(TypedTensor {
10146            shape: shape_vec(target_layout.shape()),
10147            placement,
10148            storage: DynamicStorage::Group(GroupStorage {
10149                group: Box::new(group),
10150            }),
10151        }),
10152        Err((group, error)) => Err(ReinterpretError::new(
10153            TypedTensor {
10154                shape,
10155                placement,
10156                storage: DynamicStorage::Group(GroupStorage {
10157                    group: Box::new(group),
10158                }),
10159            },
10160            error,
10161        )),
10162    }
10163}
10164
10165impl<R: TensorRank> TypedTensor<Complex32, R> {
10166    /// Borrow this tensor as an interleaved `f32` view without copying.
10167    ///
10168    /// # Errors
10169    ///
10170    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10171    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10172    /// invalid tensor layout.
10173    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f32, DynRank>> {
10174        self.as_view().as_real_view()
10175    }
10176
10177    /// Borrow this tensor mutably as an interleaved `f32` view without copying.
10178    ///
10179    /// # Errors
10180    ///
10181    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10182    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10183    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10184    /// invalid representation metadata.
10185    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
10186        let op = "TypedTensor::as_real_view_mut";
10187        validate_representation_pair(op, DType::C32, DType::F32)?;
10188        let source_layout = self.layout();
10189        let layout = reinterpret_complex_to_real_layout(
10190            self.shape(),
10191            source_layout.strides(),
10192            source_layout.offset(),
10193            self.buffer_len(),
10194            op,
10195        )?;
10196        layout
10197            .validate_mutable_no_overlap()
10198            .map_err(|err| tensor_layout_error(op, err))?;
10199        if self.backend_buffer().is_some() {
10200            return Err(crate::Error::unsupported(
10201                op,
10202                "backend representation reinterpretation is enabled by the provider phases",
10203            ));
10204        }
10205        let placement = self.placement().clone();
10206        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(
10207            self.host_data_mut()?,
10208            op,
10209        )?);
10210        Ok(TypedTensorViewMut {
10211            buffer,
10212            root: None,
10213            layout,
10214            placement,
10215            _representation: std::marker::PhantomData,
10216        })
10217    }
10218
10219    /// Consume this tensor and reinterpret its owner as `f32` without copying.
10220    ///
10221    /// A failed operation returns the unchanged owner through
10222    /// [`ReinterpretError::into_owner`].
10223    ///
10224    /// # Errors
10225    ///
10226    /// Returns [`ReinterpretError::error`] containing
10227    /// [`ValidationError::InvalidArgument`] or
10228    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10229    /// owner.
10230    pub fn into_real(self) -> Result<TypedTensor<f32, DynRank>, ReinterpretError<Self>> {
10231        reinterpret_owned(
10232            self,
10233            DType::C32,
10234            DType::F32,
10235            reinterpret_complex_to_real_layout,
10236            "TypedTensor::into_real",
10237        )
10238    }
10239}
10240
10241impl<R: TensorRank> TypedTensor<Complex64, R> {
10242    /// Borrow this tensor as an interleaved `f64` view without copying.
10243    ///
10244    /// # Errors
10245    ///
10246    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10247    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10248    /// invalid tensor layout.
10249    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f64, DynRank>> {
10250        self.as_view().as_real_view()
10251    }
10252
10253    /// Borrow this tensor mutably as an interleaved `f64` view without copying.
10254    ///
10255    /// # Errors
10256    ///
10257    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10258    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10259    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10260    /// invalid representation metadata.
10261    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
10262        let op = "TypedTensor::as_real_view_mut";
10263        validate_representation_pair(op, DType::C64, DType::F64)?;
10264        let source_layout = self.layout();
10265        let layout = reinterpret_complex_to_real_layout(
10266            self.shape(),
10267            source_layout.strides(),
10268            source_layout.offset(),
10269            self.buffer_len(),
10270            op,
10271        )?;
10272        layout
10273            .validate_mutable_no_overlap()
10274            .map_err(|err| tensor_layout_error(op, err))?;
10275        if self.backend_buffer().is_some() {
10276            return Err(crate::Error::unsupported(
10277                op,
10278                "backend representation reinterpretation is enabled by the provider phases",
10279            ));
10280        }
10281        let placement = self.placement().clone();
10282        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(
10283            self.host_data_mut()?,
10284            op,
10285        )?);
10286        Ok(TypedTensorViewMut {
10287            buffer,
10288            root: None,
10289            layout,
10290            placement,
10291            _representation: std::marker::PhantomData,
10292        })
10293    }
10294
10295    /// Consume this tensor and reinterpret its owner as `f64` without copying.
10296    ///
10297    /// A failed operation returns the unchanged owner through
10298    /// [`ReinterpretError::into_owner`].
10299    ///
10300    /// # Errors
10301    ///
10302    /// Returns [`ReinterpretError::error`] containing
10303    /// [`ValidationError::InvalidArgument`] or
10304    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10305    /// owner.
10306    pub fn into_real(self) -> Result<TypedTensor<f64, DynRank>, ReinterpretError<Self>> {
10307        reinterpret_owned(
10308            self,
10309            DType::C64,
10310            DType::F64,
10311            reinterpret_complex_to_real_layout,
10312            "TypedTensor::into_real",
10313        )
10314    }
10315}
10316
10317impl<R: TensorRank> TypedTensor<f32, R> {
10318    /// Borrow this tensor as a complex view without copying.
10319    ///
10320    /// # Errors
10321    ///
10322    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10323    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10324    /// invalid tensor layout.
10325    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex32, DynRank>> {
10326        self.as_view().as_complex_view()
10327    }
10328
10329    /// Borrow this tensor mutably as a complex view without copying.
10330    ///
10331    /// # Errors
10332    ///
10333    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10334    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10335    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10336    /// invalid representation metadata.
10337    pub fn as_complex_view_mut(
10338        &mut self,
10339    ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
10340        let op = "TypedTensor::as_complex_view_mut";
10341        validate_representation_pair(op, DType::F32, DType::C32)?;
10342        let source_layout = self.layout();
10343        let layout = reinterpret_real_to_complex_layout(
10344            self.shape(),
10345            source_layout.strides(),
10346            source_layout.offset(),
10347            self.buffer_len(),
10348            op,
10349        )?;
10350        layout
10351            .validate_mutable_no_overlap()
10352            .map_err(|err| tensor_layout_error(op, err))?;
10353        if self.backend_buffer().is_some() {
10354            return Err(crate::Error::unsupported(
10355                op,
10356                "backend representation reinterpretation is enabled by the provider phases",
10357            ));
10358        }
10359        let placement = self.placement().clone();
10360        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(
10361            self.host_data_mut()?,
10362            op,
10363        )?);
10364        Ok(TypedTensorViewMut {
10365            buffer,
10366            root: None,
10367            layout,
10368            placement,
10369            _representation: std::marker::PhantomData,
10370        })
10371    }
10372
10373    /// Consume this tensor and reinterpret its owner as `Complex32` without copying.
10374    ///
10375    /// The compact source must have an even physical element count. A failed
10376    /// operation returns the unchanged owner through
10377    /// [`ReinterpretError::into_owner`].
10378    ///
10379    /// # Errors
10380    ///
10381    /// Returns [`ReinterpretError::error`] containing
10382    /// [`ValidationError::InvalidArgument`] or
10383    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10384    /// owner.
10385    pub fn into_complex(self) -> Result<TypedTensor<Complex32, DynRank>, ReinterpretError<Self>> {
10386        reinterpret_owned(
10387            self,
10388            DType::F32,
10389            DType::C32,
10390            reinterpret_real_to_complex_layout,
10391            "TypedTensor::into_complex",
10392        )
10393    }
10394}
10395
10396impl<R: TensorRank> TypedTensor<f64, R> {
10397    /// Borrow this tensor as a complex view without copying.
10398    ///
10399    /// # Errors
10400    ///
10401    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10402    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10403    /// invalid tensor layout.
10404    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex64, DynRank>> {
10405        self.as_view().as_complex_view()
10406    }
10407
10408    /// Borrow this tensor mutably as a complex view without copying.
10409    ///
10410    /// # Errors
10411    ///
10412    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10413    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10414    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10415    /// invalid representation metadata.
10416    pub fn as_complex_view_mut(
10417        &mut self,
10418    ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
10419        let op = "TypedTensor::as_complex_view_mut";
10420        validate_representation_pair(op, DType::F64, DType::C64)?;
10421        let source_layout = self.layout();
10422        let layout = reinterpret_real_to_complex_layout(
10423            self.shape(),
10424            source_layout.strides(),
10425            source_layout.offset(),
10426            self.buffer_len(),
10427            op,
10428        )?;
10429        layout
10430            .validate_mutable_no_overlap()
10431            .map_err(|err| tensor_layout_error(op, err))?;
10432        if self.backend_buffer().is_some() {
10433            return Err(crate::Error::unsupported(
10434                op,
10435                "backend representation reinterpretation is enabled by the provider phases",
10436            ));
10437        }
10438        let placement = self.placement().clone();
10439        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(
10440            self.host_data_mut()?,
10441            op,
10442        )?);
10443        Ok(TypedTensorViewMut {
10444            buffer,
10445            root: None,
10446            layout,
10447            placement,
10448            _representation: std::marker::PhantomData,
10449        })
10450    }
10451
10452    /// Consume this tensor and reinterpret its owner as `Complex64` without copying.
10453    ///
10454    /// The compact source must have an even physical element count. A failed
10455    /// operation returns the unchanged owner through
10456    /// [`ReinterpretError::into_owner`].
10457    ///
10458    /// # Errors
10459    ///
10460    /// Returns [`ReinterpretError::error`] containing
10461    /// [`ValidationError::InvalidArgument`] or
10462    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10463    /// owner.
10464    pub fn into_complex(self) -> Result<TypedTensor<Complex64, DynRank>, ReinterpretError<Self>> {
10465        reinterpret_owned(
10466            self,
10467            DType::F64,
10468            DType::C64,
10469            reinterpret_real_to_complex_layout,
10470            "TypedTensor::into_complex",
10471        )
10472    }
10473}
10474
10475impl Tensor {
10476    /// Borrow a complex tensor as its sealed interleaved real representation.
10477    ///
10478    /// # Errors
10479    ///
10480    /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
10481    /// [`ValidationError::ViewOutOfBounds`] or
10482    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
10483    pub fn as_real_view(&self) -> crate::Result<TensorView<'_>> {
10484        match self.dtype() {
10485            DType::C32 => self
10486                .as_typed::<Complex<f32>>()
10487                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10488                .as_real_view()
10489                .map(TensorView::F32),
10490            DType::C64 => self
10491                .as_typed::<Complex<f64>>()
10492                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10493                .as_real_view()
10494                .map(TensorView::F64),
10495            other => Err(crate::Error::unsupported_dtype_conversion(
10496                "Tensor::as_real_view",
10497                other,
10498                DType::F32,
10499                "only complex tensors have a sealed real representation view",
10500            )),
10501        }
10502    }
10503
10504    /// Borrow a complex tensor mutably as its sealed interleaved real representation.
10505    ///
10506    /// # Errors
10507    ///
10508    /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
10509    /// [`ValidationError::ViewOutOfBounds`] or
10510    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
10511    pub fn as_real_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
10512        match self.dtype() {
10513            DType::C32 => self
10514                .as_typed_mut::<Complex<f32>>()
10515                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10516                .as_real_view_mut()
10517                .map(TensorViewMut::F32),
10518            DType::C64 => self
10519                .as_typed_mut::<Complex<f64>>()
10520                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10521                .as_real_view_mut()
10522                .map(TensorViewMut::F64),
10523            other => Err(crate::Error::unsupported_dtype_conversion(
10524                "Tensor::as_real_view_mut",
10525                other,
10526                DType::F32,
10527                "only complex tensors have a sealed real representation view",
10528            )),
10529        }
10530    }
10531
10532    /// Consume a complex tensor and reinterpret its owner as real without copying.
10533    ///
10534    /// # Errors
10535    ///
10536    /// Returns [`ReinterpretError::error`] containing
10537    /// [`ValidationError::InvalidArgument`] or
10538    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10539    /// owner.
10540    pub fn into_real(self) -> Result<Self, ReinterpretError<Self>> {
10541        let dtype = self.dtype();
10542        match dtype {
10543            DType::C32 => {
10544                let tensor = <Complex<f32> as TensorScalar>::into_typed(self)
10545                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
10546                tensor
10547                    .into_real()
10548                    .map(Tensor::from_typed::<f32>)
10549                    .map_err(|error| {
10550                        let (owner, error) = error.into_parts();
10551                        ReinterpretError::new(Tensor::from_typed::<Complex<f32>>(owner), error)
10552                    })
10553            }
10554            DType::C64 => {
10555                let tensor = <Complex<f64> as TensorScalar>::into_typed(self)
10556                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
10557                tensor
10558                    .into_real()
10559                    .map(Tensor::from_typed::<f64>)
10560                    .map_err(|error| {
10561                        let (owner, error) = error.into_parts();
10562                        ReinterpretError::new(Tensor::from_typed::<Complex<f64>>(owner), error)
10563                    })
10564            }
10565            _ => Err(ReinterpretError::new(
10566                self,
10567                crate::Error::unsupported(
10568                    "Tensor::into_real",
10569                    "only complex tensors have a sealed real representation",
10570                ),
10571            )),
10572        }
10573    }
10574
10575    /// Borrow an interleaved real tensor as its sealed complex representation.
10576    ///
10577    /// # Errors
10578    ///
10579    /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
10580    /// [`ValidationError::ViewOutOfBounds`] or
10581    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
10582    pub fn as_complex_view(&self) -> crate::Result<TensorView<'_>> {
10583        match self.dtype() {
10584            DType::F32 => self
10585                .as_typed::<f32>()
10586                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10587                .as_complex_view()
10588                .map(TensorView::C32),
10589            DType::F64 => self
10590                .as_typed::<f64>()
10591                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10592                .as_complex_view()
10593                .map(TensorView::C64),
10594            other => Err(crate::Error::unsupported_dtype_conversion(
10595                "Tensor::as_complex_view",
10596                other,
10597                DType::C32,
10598                "only real tensors can have a sealed complex representation view",
10599            )),
10600        }
10601    }
10602
10603    /// Borrow an interleaved real tensor mutably as its sealed complex representation.
10604    ///
10605    /// # Errors
10606    ///
10607    /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
10608    /// [`ValidationError::ViewOutOfBounds`] or
10609    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
10610    pub fn as_complex_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
10611        match self.dtype() {
10612            DType::F32 => self
10613                .as_typed_mut::<f32>()
10614                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10615                .as_complex_view_mut()
10616                .map(TensorViewMut::C32),
10617            DType::F64 => self
10618                .as_typed_mut::<f64>()
10619                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10620                .as_complex_view_mut()
10621                .map(TensorViewMut::C64),
10622            other => Err(crate::Error::unsupported_dtype_conversion(
10623                "Tensor::as_complex_view_mut",
10624                other,
10625                DType::C32,
10626                "only real tensors can have a sealed complex representation view",
10627            )),
10628        }
10629    }
10630
10631    /// Consume a real tensor and reinterpret its owner as complex without copying.
10632    ///
10633    /// # Errors
10634    ///
10635    /// Returns [`ReinterpretError::error`] containing
10636    /// [`ValidationError::InvalidArgument`] or
10637    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10638    /// owner.
10639    pub fn into_complex(self) -> Result<Self, ReinterpretError<Self>> {
10640        let dtype = self.dtype();
10641        match dtype {
10642            DType::F32 => {
10643                let tensor = <f32 as TensorScalar>::into_typed(self)
10644                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
10645                tensor
10646                    .into_complex()
10647                    .map(Tensor::from_typed::<Complex<f32>>)
10648                    .map_err(|error| {
10649                        let (owner, error) = error.into_parts();
10650                        ReinterpretError::new(Tensor::from_typed::<f32>(owner), error)
10651                    })
10652            }
10653            DType::F64 => {
10654                let tensor = <f64 as TensorScalar>::into_typed(self)
10655                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
10656                tensor
10657                    .into_complex()
10658                    .map(Tensor::from_typed::<Complex<f64>>)
10659                    .map_err(|error| {
10660                        let (owner, error) = error.into_parts();
10661                        ReinterpretError::new(Tensor::from_typed::<f64>(owner), error)
10662                    })
10663            }
10664            _ => Err(ReinterpretError::new(
10665                self,
10666                crate::Error::unsupported(
10667                    "Tensor::into_complex",
10668                    "only real tensors have a sealed complex representation",
10669                ),
10670            )),
10671        }
10672    }
10673
10674    /// Make an explicit owning copy of this dtype-erased tensor.
10675    ///
10676    /// `Tensor` deliberately does not implement [`Clone`]: copying can fail
10677    /// (device-only storage is rejected) and allocates a new, independent
10678    /// owner, so it is an explicit fallible call rather than an infallible
10679    /// `clone()`. The copy shares nothing with `self`; mutating one never
10680    /// affects the other. To share one tensor between several users without
10681    /// copying, wrap it in [`std::sync::Arc`] or borrow views of it.
10682    ///
10683    /// # Examples
10684    ///
10685    /// ```
10686    /// use std::sync::Arc;
10687    /// use tenferro_tensor::Tensor;
10688    ///
10689    /// let weights = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
10690    /// // Two independent owners of the same values.
10691    /// let copy = weights.duplicate()?;
10692    /// assert_eq!(copy.as_slice::<f64>()?, weights.as_slice::<f64>()?);
10693    ///
10694    /// // A shared, read-only handle instead of a copy.
10695    /// let shared = Arc::new(weights);
10696    /// let other = Arc::clone(&shared);
10697    /// assert_eq!(other.shape(), &[2]);
10698    /// # Ok::<(), tenferro_tensor::Error>(())
10699    /// ```
10700    ///
10701    /// # Errors
10702    ///
10703    /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
10704    /// when the selected backend/storage owner cannot be duplicated.
10705    pub fn duplicate(&self) -> crate::Result<Self> {
10706        match &self.payload {
10707            // A caller-owned payload is copied through its own entry point, which
10708            // keeps its element type and its view; sharing the payload instead
10709            // would alias the caller's storage.
10710            TensorPayload::External(payload, placement) => Ok(Self {
10711                payload: TensorPayload::External(payload.duplicate(), placement.clone()),
10712            }),
10713            TensorPayload::Native(_) => match self.dtype() {
10714                DType::F32 => self.duplicate_typed::<f32>(),
10715                DType::F64 => self.duplicate_typed::<f64>(),
10716                DType::I32 => self.duplicate_typed::<i32>(),
10717                DType::I64 => self.duplicate_typed::<i64>(),
10718                DType::Bool => self.duplicate_typed::<bool>(),
10719                DType::C32 => self.duplicate_typed::<Complex<f32>>(),
10720                DType::C64 => self.duplicate_typed::<Complex<f64>>(),
10721                DType::External(_) => unreachable!("a native payload carries a preset dtype"),
10722            },
10723        }
10724    }
10725
10726    /// Duplicate a preset tensor through its typed owner.
10727    fn duplicate_typed<T: TensorScalar>(&self) -> crate::Result<Self> {
10728        self.as_typed::<T>()
10729            .unwrap_or_else(|| unreachable!("the caller selected this arm from the dtype"))
10730            .duplicate()
10731            .map(Self::from_typed)
10732    }
10733
10734    /// Create a tensor from a shape and column-major flat data.
10735    ///
10736    /// This is the `Tensor`-level equivalent of
10737    /// `TypedTensor::<T>::from_vec_col_major`.
10738    ///
10739    /// # Examples
10740    ///
10741    /// ```
10742    /// use tenferro_tensor::Tensor;
10743    ///
10744    /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
10745    /// assert_eq!(t.shape(), &[2, 2]);
10746    /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
10747    /// ```
10748    /// # Errors
10749    ///
10750    /// Returns [`crate::Error::Validation`] with
10751    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
10752    /// the shape product differs from `data.len()`, or
10753    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
10754    /// arithmetic overflows.
10755    pub fn from_vec_col_major<T: TensorScalar>(
10756        shape: impl tenferro_tensor_core::IntoShapeVec,
10757        data: Vec<T>,
10758    ) -> crate::Result<Self> {
10759        T::into_tensor(shape.into_shape_vec().to_vec(), data)
10760    }
10761
10762    /// Create a tensor from a shape and row-major (C-order) flat data.
10763    ///
10764    /// The values are reordered once into tenferro's column-major storage;
10765    /// no row-major owner is created. Use this for buffers authored in
10766    /// PyTorch/NumPy/C order instead of passing them to
10767    /// [`Self::from_vec_col_major`], which would reinterpret them silently.
10768    /// This is the `Tensor`-level equivalent of
10769    /// `TypedTensor::<T>::from_vec_row_major`.
10770    ///
10771    /// # Examples
10772    ///
10773    /// ```
10774    /// use tenferro_tensor::Tensor;
10775    ///
10776    /// // Row-major [[1, 2, 3], [4, 5, 6]].
10777    /// let t = Tensor::from_vec_row_major(vec![2, 3], vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0])?;
10778    /// assert_eq!(t.shape(), &[2, 3]);
10779    /// assert_eq!(t.as_slice::<f64>()?, &[1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
10780    /// # Ok::<(), tenferro_tensor::Error>(())
10781    /// ```
10782    /// # Errors
10783    ///
10784    /// Returns [`crate::Error::Validation`] with
10785    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
10786    /// the shape product differs from `data.len()`, or
10787    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
10788    /// arithmetic overflows.
10789    pub fn from_vec_row_major<T: TensorScalar>(
10790        shape: impl tenferro_tensor_core::IntoShapeVec,
10791        data: Vec<T>,
10792    ) -> crate::Result<Self> {
10793        let (shape, reordered) = row_major_reorder::<T, tenferro_tensor_core::DynRank>(
10794            shape.into_shape_vec().to_vec(),
10795            data,
10796            "Tensor::from_vec_row_major",
10797        )?;
10798        T::into_tensor(shape.as_ref().to_vec(), reordered)
10799    }
10800
10801    /// Tensor shape.
10802    ///
10803    /// # Examples
10804    ///
10805    /// ```rust
10806    /// use tenferro_tensor::{Tensor, TypedTensor};
10807    ///
10808    /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
10809    /// assert_eq!(t.shape(), &[2]);
10810    /// ```
10811    pub fn shape(&self) -> &[usize] {
10812        match &self.payload {
10813            TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.shape()),
10814            // The payload keeps its shape from construction.
10815            TensorPayload::External(payload, _) => payload.shape(),
10816        }
10817    }
10818
10819    /// Tensor dtype tag.
10820    ///
10821    /// # Examples
10822    ///
10823    /// ```rust
10824    /// use tenferro_tensor::{DType, Tensor, TypedTensor};
10825    ///
10826    /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
10827    /// assert_eq!(t.dtype(), DType::F64);
10828    /// ```
10829    pub fn dtype(&self) -> DType {
10830        match &self.payload {
10831            TensorPayload::Native(preset) => match preset {
10832                PresetTensor::F32(_) => DType::F32,
10833                PresetTensor::F64(_) => DType::F64,
10834                PresetTensor::I32(_) => DType::I32,
10835                PresetTensor::I64(_) => DType::I64,
10836                PresetTensor::Bool(_) => DType::Bool,
10837                PresetTensor::C32(_) => DType::C32,
10838                PresetTensor::C64(_) => DType::C64,
10839            },
10840            // The payload carries its own element identity.
10841            TensorPayload::External(payload, _) => DType::External(payload.element_type_id()),
10842        }
10843    }
10844
10845    /// Return placement metadata for this dtype-erased tensor.
10846    ///
10847    /// # Examples
10848    ///
10849    /// ```rust
10850    /// use tenferro_tensor::{MemoryKind, Tensor};
10851    ///
10852    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
10853    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
10854    /// ```
10855    pub fn placement(&self) -> &Placement {
10856        match &self.payload {
10857            TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.placement()),
10858            // The placement is stored with the payload.
10859            TensorPayload::External(_, placement) => placement,
10860        }
10861    }
10862
10863    /// Return whether this tensor is backed by backend-native storage.
10864    ///
10865    /// # Examples
10866    ///
10867    /// ```rust
10868    /// use tenferro_tensor::Tensor;
10869    ///
10870    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
10871    /// assert!(!t.is_backend_buffer());
10872    /// ```
10873    pub fn is_backend_buffer(&self) -> bool {
10874        match &self.payload {
10875            TensorPayload::Native(preset) => with_preset!(preset, |typed| {
10876                matches!(&typed.storage, DynamicStorage::Group(core) if core.group.is_backend_buffer())
10877            }),
10878            // A caller-owned payload is host memory, never a backend buffer.
10879            TensorPayload::External(..) => false,
10880        }
10881    }
10882
10883    /// Compute the physical element offset for a logical index.
10884    ///
10885    /// # Examples
10886    ///
10887    /// ```rust
10888    /// use tenferro_tensor::Tensor;
10889    ///
10890    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
10891    /// assert_eq!(t.layout_linear_offset(&[1])?, 1);
10892    /// # Ok::<(), tenferro_tensor::Error>(())
10893    /// ```
10894    /// # Errors
10895    ///
10896    /// Returns [`crate::Error::Validation`] with
10897    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
10898    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
10899    /// when an index is outside its axis extent, or
10900    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
10901    /// arithmetic overflows.
10902    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
10903        match &self.payload {
10904            TensorPayload::Native(_) => {
10905                let layout = tensor_layout(self);
10906                checked_view_offset_result(
10907                    layout.shape(),
10908                    layout.strides(),
10909                    layout.offset(),
10910                    indices,
10911                    "Tensor::layout_linear_offset",
10912                )
10913            }
10914            // A caller-owned payload's owner resolves its own offsets.
10915            TensorPayload::External(..) => Err(crate::Error::unsupported_dtype(
10916                "layout_linear_offset",
10917                self.dtype(),
10918                "an externally defined payload resolves its own offsets",
10919            )),
10920        }
10921    }
10922
10923    /// Return whether this tensor is compact column-major.
10924    ///
10925    /// # Examples
10926    ///
10927    /// ```rust
10928    /// use tenferro_tensor::Tensor;
10929    ///
10930    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
10931    /// assert!(t.is_col_major_contiguous()?);
10932    /// # Ok::<(), tenferro_tensor::Error>(())
10933    /// ```
10934    /// # Errors
10935    ///
10936    /// Returns [`crate::Error::Validation`] with
10937    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
10938    /// compactness arithmetic overflows.
10939    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
10940        match &self.payload {
10941            TensorPayload::Native(_) => tensor_layout(self)
10942                .is_compact_col_major()
10943                .map_err(|err| tensor_layout_error("Tensor::is_col_major_contiguous", err)),
10944            // The payload is a compact column-major host tensor by construction.
10945            TensorPayload::External(..) => Ok(true),
10946        }
10947    }
10948
10949    /// Return a compact string summary of this tensor's layout metadata.
10950    ///
10951    /// # Examples
10952    ///
10953    /// ```rust
10954    /// use tenferro_tensor::Tensor;
10955    ///
10956    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
10957    /// assert!(t.layout_summary().contains("shape=[2]"));
10958    /// # Ok::<(), tenferro_tensor::Error>(())
10959    /// ```
10960    pub fn layout_summary(&self) -> String {
10961        let layout = tensor_layout(self);
10962        layout_summary(layout.shape(), layout.strides(), layout.offset())
10963    }
10964
10965    /// Assert this tensor is compact column-major.
10966    ///
10967    /// # Examples
10968    ///
10969    /// ```rust
10970    /// use tenferro_tensor::Tensor;
10971    ///
10972    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
10973    /// t.assert_col_major_contiguous()?;
10974    /// # Ok::<(), tenferro_tensor::Error>(())
10975    /// ```
10976    /// # Errors
10977    ///
10978    /// Returns [`crate::Error::Validation`] with
10979    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
10980    /// compactness arithmetic overflows, or
10981    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
10982    /// tensor is not compact column-major.
10983    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
10984        let layout = tensor_layout(self);
10985        assert_layout_col_major_contiguous(
10986            self.is_col_major_contiguous()?,
10987            layout.shape(),
10988            layout.strides(),
10989            layout.offset(),
10990            "Tensor::assert_col_major_contiguous",
10991        )
10992    }
10993
10994    /// Try to borrow the host data as a typed slice.
10995    ///
10996    /// Returns an error if the tensor dtype does not match `T`.
10997    ///
10998    /// # Examples
10999    ///
11000    /// ```
11001    /// use tenferro_tensor::{Tensor, TypedTensor};
11002    ///
11003    /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
11004    /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
11005    /// assert!(t.as_slice::<f32>().is_err());
11006    /// ```
11007    /// # Errors
11008    ///
11009    /// Returns [`crate::Error::Validation`] with
11010    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
11011    /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
11012    /// matching tensor uses backend storage that has not been downloaded.
11013    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
11014        T::as_slice(self)
11015    }
11016
11017    /// Borrow the typed tensor when the requested scalar matches this tensor's dtype.
11018    ///
11019    /// This is the accessor tag-based dispatch needs: it recovers the typed tensor — and with it the
11020    /// device buffer — from a value whose element type is only known at run time, so a caller can
11021    /// dispatch on [`Tensor::dtype`] instead of matching every variant. An externally defined scalar
11022    /// is not a typed tensor, so it returns `None` rather than guessing a representation.
11023    ///
11024    /// # Examples
11025    ///
11026    /// ```
11027    /// use tenferro_tensor::Tensor;
11028    ///
11029    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11030    /// assert!(tensor.as_typed::<f64>().is_some());
11031    /// assert!(tensor.as_typed::<f32>().is_none());
11032    /// assert_eq!(tensor.as_typed::<f64>().unwrap().shape(), &[2]);
11033    /// # Ok::<(), tenferro_tensor::Error>(())
11034    /// ```
11035    #[must_use]
11036    pub fn as_typed<T: TensorScalar>(&self) -> Option<&TypedTensor<T>> {
11037        if self.dtype() != T::dtype() {
11038            return None;
11039        }
11040        match &self.payload {
11041            TensorPayload::Native(preset) => with_preset!(preset, |typed| {
11042                (typed as &dyn Any).downcast_ref::<TypedTensor<T>>()
11043            }),
11044            TensorPayload::External(..) => None,
11045        }
11046    }
11047
11048    /// Mutably borrow the typed tensor when the requested scalar matches this tensor's dtype.
11049    ///
11050    /// This is the mutable half of [`Tensor::as_typed`], for the tables whose arm calls a method that
11051    /// needs `&mut`, such as marking a freshly allocated output with its placement. An externally
11052    /// defined scalar is not a typed tensor, so it returns `None` for the same reason.
11053    ///
11054    /// # Examples
11055    ///
11056    /// ```
11057    /// use tenferro_tensor::Tensor;
11058    ///
11059    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11060    /// assert!(tensor.as_typed_mut::<f64>().is_some());
11061    /// assert!(tensor.as_typed_mut::<f32>().is_none());
11062    /// # Ok::<(), tenferro_tensor::Error>(())
11063    /// ```
11064    #[must_use]
11065    pub fn as_typed_mut<T: TensorScalar>(&mut self) -> Option<&mut TypedTensor<T>> {
11066        if self.dtype() != T::dtype() {
11067            return None;
11068        }
11069        match &mut self.payload {
11070            TensorPayload::Native(preset) => with_preset!(preset, |typed| {
11071                (typed as &mut dyn Any).downcast_mut::<TypedTensor<T>>()
11072            }),
11073            TensorPayload::External(..) => None,
11074        }
11075    }
11076
11077    /// Consume this tensor and return the owned typed tensor when the dtype matches.
11078    ///
11079    /// This is the consuming counterpart of [`Tensor::as_typed`], for the tables whose arm hands the typed
11080    /// tensor to a function that takes it by value — reusing its buffer rather than copying it.
11081    ///
11082    /// # Examples
11083    ///
11084    /// ```
11085    /// use tenferro_tensor::Tensor;
11086    ///
11087    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11088    /// assert!(tensor.into_typed::<f64>().is_ok());
11089    ///
11090    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0])?;
11091    /// assert!(tensor.into_typed::<f64>().is_err());
11092    /// # Ok::<(), tenferro_tensor::Error>(())
11093    /// ```
11094    /// # Errors
11095    ///
11096    /// Returns [`ReinterpretError`] carrying the unchanged tensor when `T` is
11097    /// not this tensor's dtype, with [`crate::Error::Validation`] and
11098    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] as the cause.
11099    /// A matching tensor is handed over as it is, including one whose storage
11100    /// lives in a backend buffer.
11101    pub fn into_typed<T: TensorScalar>(
11102        self,
11103    ) -> std::result::Result<TypedTensor<T>, ReinterpretError<Self>> {
11104        T::into_typed(self)
11105    }
11106
11107    /// Consume this tensor and return its owned column-major buffer when the
11108    /// dtype matches.
11109    ///
11110    /// # Examples
11111    ///
11112    /// ```
11113    /// use tenferro_tensor::Tensor;
11114    ///
11115    /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
11116    /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
11117    /// ```
11118    /// # Errors
11119    ///
11120    /// Returns [`ReinterpretError`] carrying the unchanged tensor when `T` does
11121    /// not match the tensor dtype or when the matching tensor uses backend
11122    /// storage that has not been downloaded.
11123    pub fn into_vec_col_major<T: TensorScalar>(
11124        self,
11125    ) -> std::result::Result<(Vec<usize>, Vec<T>), ReinterpretError<Self>> {
11126        let typed = T::into_typed(self)?;
11127        match typed.into_vec_col_major() {
11128            Ok(parts) => Ok(parts),
11129            Err(failure) => {
11130                let (owner, error) = failure.into_parts();
11131                Err(ReinterpretError::new(Tensor::from_typed(owner), error))
11132            }
11133        }
11134    }
11135}
11136
11137// INVARIANT: retained for crate-local layout tests while tensor indexing
11138// helpers remain split across the tensor and CPU crates.
11139#[allow(dead_code)]
11140pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
11141    for i in 0..shape.len() {
11142        if shape[i] == 0 {
11143            out[i] = 0;
11144        } else {
11145            out[i] = flat % shape[i];
11146            flat /= shape[i];
11147        }
11148    }
11149}