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};
16use tinyvec::TinyVec;
17
18use crate::storage::{
19    AllocationGroup, AllocationSlot, BackendAllocation, DescriptorSlot, GroupError, GroupReadView,
20    GroupWriteView,
21};
22
23mod accessors;
24mod col_major;
25mod shape_packing;
26mod strided_view;
27#[cfg(test)]
28mod tests;
29
30pub use col_major::{ColMajorView, ColMajorViewMut};
31pub use strided_view::StridedSliceSpec;
32
33pub(crate) fn shape_vec(shape: &[usize]) -> ShapeVec {
34    shape.iter().copied().collect()
35}
36
37pub(crate) fn stride_vec(strides: &[isize]) -> StrideVec {
38    strides.iter().copied().collect()
39}
40
41pub(crate) fn shape_vec_zeros(len: usize) -> ShapeVec {
42    std::iter::repeat_n(0, len).collect()
43}
44
45fn representation_pair_error(
46    op: &'static str,
47    from: DType,
48    to: DType,
49    message: impl Into<String>,
50) -> crate::Error {
51    crate::Error::unsupported_dtype_conversion(op, from, to, message)
52}
53
54fn validate_representation_pair(op: &'static str, from: DType, to: DType) -> crate::Result<()> {
55    let valid = match (from, to) {
56        (DType::C32, DType::F32) | (DType::F32, DType::C32) => {
57            size_of::<Complex32>() == 2 * size_of::<f32>()
58                && align_of::<Complex32>() == align_of::<f32>()
59                && offset_of!(Complex32, re) == 0
60                && offset_of!(Complex32, im) == size_of::<f32>()
61                && !needs_drop::<Complex32>()
62                && !needs_drop::<f32>()
63        }
64        (DType::C64, DType::F64) | (DType::F64, DType::C64) => {
65            size_of::<Complex64>() == 2 * size_of::<f64>()
66                && align_of::<Complex64>() == align_of::<f64>()
67                && offset_of!(Complex64, re) == 0
68                && offset_of!(Complex64, im) == size_of::<f64>()
69                && !needs_drop::<Complex64>()
70                && !needs_drop::<f64>()
71        }
72        _ => false,
73    };
74    if valid {
75        Ok(())
76    } else {
77        Err(representation_pair_error(
78            op,
79            from,
80            to,
81            "only the sealed Complex<f32><->f32 and Complex<f64><->f64 representations are supported",
82        ))
83    }
84}
85
86fn reinterpret_complex_to_real_layout(
87    shape: &[usize],
88    strides: &[isize],
89    offset: isize,
90    complex_buffer_len: usize,
91    op: &'static str,
92) -> crate::Result<TensorLayout<DynRank>> {
93    let real_buffer_len = complex_buffer_len
94        .checked_mul(2)
95        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
96    let rank = shape
97        .len()
98        .checked_add(1)
99        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
100    let mut real_shape = ShapeVec::with_capacity(rank);
101    real_shape.push(2);
102    real_shape.extend_from_slice(shape);
103    let mut real_strides = StrideVec::with_capacity(rank);
104    real_strides.push(1);
105    for &stride in strides {
106        real_strides.push(
107            stride
108                .checked_mul(2)
109                .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
110        );
111    }
112    let real_offset = offset
113        .checked_mul(2)
114        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
115    TensorLayout::from_parts(real_shape, real_strides, real_offset, real_buffer_len)
116        .map_err(|err| tensor_layout_error(op, err))
117}
118
119fn reinterpret_real_to_complex_layout(
120    shape: &[usize],
121    strides: &[isize],
122    offset: isize,
123    real_buffer_len: usize,
124    op: &'static str,
125) -> crate::Result<TensorLayout<DynRank>> {
126    if shape.first().copied() != Some(2) {
127        return Err(crate::Error::invalid_argument(
128            op,
129            "shape",
130            "the leading extent must be 2 for a complex reinterpretation",
131        ));
132    }
133    if strides.first().copied() != Some(1) {
134        return Err(crate::Error::invalid_argument(
135            op,
136            "strides",
137            "the leading stride must be 1 for a complex reinterpretation",
138        ));
139    }
140    if offset % 2 != 0 {
141        return Err(crate::Error::invalid_argument(
142            op,
143            "offset",
144            "the offset must be divisible by 2 for a complex reinterpretation",
145        ));
146    }
147    let mut complex_strides = StrideVec::with_capacity(strides.len() - 1);
148    for &stride in &strides[1..] {
149        if stride % 2 != 0 {
150            return Err(crate::Error::invalid_argument(
151                op,
152                "strides",
153                "all non-leading strides must be divisible by 2",
154            ));
155        }
156        complex_strides.push(stride / 2);
157    }
158    let complex_buffer_len = real_buffer_len / 2;
159    TensorLayout::from_parts(
160        shape[1..].iter().copied().collect(),
161        complex_strides,
162        offset / 2,
163        complex_buffer_len,
164    )
165    .map_err(|err| tensor_layout_error(op, err))
166}
167
168fn reinterpret_host_slice<'a, T: TensorScalar, U: TensorScalar>(
169    data: &'a [T],
170    op: &'static str,
171) -> crate::Result<&'a [U]> {
172    let byte_len = data
173        .len()
174        .checked_mul(size_of::<T>())
175        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
176    if !byte_len.is_multiple_of(size_of::<U>()) {
177        return Err(crate::Error::validation(
178            op,
179            ValidationError::ViewOutOfBounds,
180        ));
181    }
182    if data.as_ptr().align_offset(align_of::<U>()) != 0 {
183        return Err(crate::Error::invalid_argument(
184            op,
185            "alignment",
186            "the source allocation is not aligned for the target representation",
187        ));
188    }
189    // SAFETY: `validate_representation_pair` seals the only supported pairs;
190    // sizes, alignment, field order, and drop properties are checked before
191    // exposing the borrowed target slice.
192    Ok(unsafe { std::slice::from_raw_parts(data.as_ptr().cast::<U>(), byte_len / size_of::<U>()) })
193}
194
195fn reinterpret_host_slice_mut<'a, T: TensorScalar, U: TensorScalar>(
196    data: &'a mut [T],
197    op: &'static str,
198) -> crate::Result<&'a mut [U]> {
199    let byte_len = data
200        .len()
201        .checked_mul(size_of::<T>())
202        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
203    if !byte_len.is_multiple_of(size_of::<U>()) {
204        return Err(crate::Error::validation(
205            op,
206            ValidationError::ViewOutOfBounds,
207        ));
208    }
209    if data.as_mut_ptr().align_offset(align_of::<U>()) != 0 {
210        return Err(crate::Error::invalid_argument(
211            op,
212            "alignment",
213            "the source allocation is not aligned for the target representation",
214        ));
215    }
216    // SAFETY: the mutable source borrow is unique and the sealed pair has no
217    // padding or drop glue, so the target slice covers the same bytes exactly.
218    Ok(unsafe {
219        std::slice::from_raw_parts_mut(data.as_mut_ptr().cast::<U>(), byte_len / size_of::<U>())
220    })
221}
222
223/// Memory location for tensor storage.
224///
225/// # Examples
226///
227/// ```rust
228/// use tenferro_tensor::MemoryKind;
229///
230/// let kind = MemoryKind::UnpinnedHost;
231/// ```
232#[derive(Clone, Debug, PartialEq, Eq, Hash)]
233pub enum MemoryKind {
234    Device,
235    PinnedHost,
236    UnpinnedHost,
237    Managed,
238    Other(String),
239}
240
241/// Compute device family.
242///
243/// # Examples
244///
245/// ```rust
246/// use tenferro_tensor::DeviceKind;
247///
248/// let kind = DeviceKind::Cpu;
249/// ```
250#[derive(Clone, Debug, PartialEq, Eq, Hash)]
251pub enum DeviceKind {
252    Cpu,
253    Gpu(GpuBackendKind),
254    Other(String),
255}
256
257/// GPU backend family used by placement metadata.
258///
259/// # Examples
260///
261/// ```rust
262/// use tenferro_tensor::GpuBackendKind;
263///
264/// let kind = GpuBackendKind::Cuda;
265/// let webgpu = GpuBackendKind::WebGpu;
266/// assert_ne!(kind, webgpu);
267/// ```
268#[derive(Clone, Debug, PartialEq, Eq, Hash)]
269pub enum GpuBackendKind {
270    Cuda,
271    WebGpu,
272    Rocm,
273    Other(String),
274}
275
276/// Concrete compute device identifier.
277///
278/// # Examples
279///
280/// ```rust
281/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind};
282///
283/// let device = DeviceId {
284///     kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
285///     ordinal: 0,
286/// };
287/// ```
288#[derive(Clone, Debug, PartialEq, Eq, Hash)]
289pub struct DeviceId {
290    pub kind: DeviceKind,
291    pub ordinal: usize,
292}
293
294/// Caller-stable identity for a CPU execution domain.
295///
296/// Domain IDs are metadata supplied by the caller or execution coordinator;
297/// creating an ID does not allocate a process-global identity.
298///
299/// # Examples
300///
301/// ```rust
302/// use tenferro_tensor::CpuDomainId;
303///
304/// let domain = CpuDomainId::new(17);
305/// assert_eq!(domain.as_u64(), 17);
306/// ```
307#[repr(transparent)]
308#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
309pub struct CpuDomainId(u64);
310
311impl CpuDomainId {
312    /// Create a caller-stable CPU domain identity.
313    ///
314    /// # Examples
315    ///
316    /// ```rust
317    /// use tenferro_tensor::CpuDomainId;
318    ///
319    /// assert_eq!(CpuDomainId::new(3), CpuDomainId::new(3));
320    /// ```
321    pub const fn new(id: u64) -> Self {
322        Self(id)
323    }
324
325    /// Return the caller-supplied integer identity.
326    ///
327    /// # Examples
328    ///
329    /// ```rust
330    /// use tenferro_tensor::CpuDomainId;
331    ///
332    /// assert_eq!(CpuDomainId::new(9).as_u64(), 9);
333    /// ```
334    pub const fn as_u64(self) -> u64 {
335        self.0
336    }
337}
338
339/// Placement metadata for a tensor buffer.
340///
341/// # Examples
342///
343/// ```rust
344/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind, MemoryKind, Placement};
345///
346/// let placement = Placement {
347///     memory_kind: MemoryKind::Device,
348///     device: Some(DeviceId {
349///         kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
350///         ordinal: 0,
351///     }),
352///     cpu_affinity: None,
353/// };
354/// assert!(placement.cpu_affinity.is_none());
355/// ```
356#[derive(Clone, Debug, PartialEq, Eq, Hash)]
357pub struct Placement {
358    /// Storage memory class, independent of execution routing metadata.
359    pub memory_kind: MemoryKind,
360    /// Device that owns or addresses the storage, when applicable.
361    pub device: Option<DeviceId>,
362    /// Preferred or producing CPU execution domain for routing and locality.
363    ///
364    /// This tag is not proof of allocation ownership, page residency, NUMA
365    /// pinning, or worker-affinity enforcement. Backend allocation-domain
366    /// metadata remains attached to the buffer independently.
367    pub cpu_affinity: Option<CpuDomainId>,
368}
369
370impl Default for Placement {
371    fn default() -> Self {
372        default_placement()
373    }
374}
375
376/// Backend-owned buffer handle.
377///
378/// `BackendStorageHandle::new` creates an empty opaque handle. Use
379/// [`BackendStorageHandle::new_with_len`] when test or adapter code needs to model a
380/// non-empty backend allocation.
381///
382/// # Examples
383///
384/// ```rust
385/// use tenferro_tensor::BackendStorageHandle;
386///
387/// let handle = BackendStorageHandle::<f64>::new(7);
388/// ```
389pub struct BackendStorageHandle<T> {
390    id: u64,
391    len: usize,
392    allocation_domain: AllocationDomainId,
393    _phantom: std::marker::PhantomData<T>,
394}
395
396/// Identity of a backend-owned allocation domain.
397///
398/// Domains let cooperating backends accept shared allocations without treating
399/// another context's physically similar buffer as compatible.
400///
401/// # Examples
402///
403/// ```rust
404/// use tenferro_tensor::AllocationDomainId;
405///
406/// assert_ne!(AllocationDomainId::fresh(), AllocationDomainId::fresh());
407/// ```
408#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
409pub struct AllocationDomainId(u64);
410
411impl AllocationDomainId {
412    /// Create a process-unique allocation-domain identity.
413    ///
414    /// # Examples
415    ///
416    /// ```rust
417    /// use tenferro_tensor::AllocationDomainId;
418    ///
419    /// let domain = AllocationDomainId::fresh();
420    /// assert_eq!(domain, domain);
421    /// ```
422    pub fn fresh() -> Self {
423        static NEXT_DOMAIN_ID: AtomicU64 = AtomicU64::new(1);
424        Self(NEXT_DOMAIN_ID.fetch_add(1, Ordering::Relaxed))
425    }
426}
427
428/// Stable physical identity of one backend allocation.
429///
430/// # Examples
431///
432/// ```rust
433/// use tenferro_tensor::AllocationId;
434///
435/// assert_eq!(AllocationId::from_backend_id(7), AllocationId::from_backend_id(7));
436/// ```
437#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
438pub struct AllocationId(u64);
439
440impl AllocationId {
441    /// Wrap an allocation identity supplied by the owning backend.
442    ///
443    /// # Examples
444    ///
445    /// ```rust
446    /// use tenferro_tensor::AllocationId;
447    ///
448    /// let id = AllocationId::from_backend_id(3);
449    /// assert_eq!(id, AllocationId::from_backend_id(3));
450    /// ```
451    pub const fn from_backend_id(id: u64) -> Self {
452        Self(id)
453    }
454}
455
456/// Typed failure returned by guarded backend host access.
457///
458/// # Examples
459///
460/// ```rust
461/// use tenferro_tensor::HostAccessError;
462///
463/// let error = HostAccessError::Unsupported { backend: "opaque" };
464/// assert!(error.to_string().contains("opaque"));
465/// ```
466#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
467#[non_exhaustive]
468pub enum HostAccessError {
469    /// The backend does not expose guarded host access for this allocation.
470    #[error("backend `{backend}` does not support guarded host access")]
471    Unsupported { backend: &'static str },
472    /// The allocation belongs to another shared-allocation domain.
473    #[error("allocation belongs to domain {actual:?}, expected {expected:?}")]
474    ForeignDomain {
475        expected: AllocationDomainId,
476        actual: AllocationDomainId,
477    },
478    /// Another host mapping overlaps this allocation.
479    #[error("the allocation already has an active host mapping")]
480    OverlappingHostMapping,
481    /// GPU work currently owns or has reserved the allocation.
482    #[error("GPU access is in progress for the allocation")]
483    GpuAccessInProgress,
484    /// Host mapping is active while GPU access was requested.
485    #[error("the allocation is mapped for host access")]
486    MappedForHost,
487    /// The backend failed to complete the map operation.
488    #[error("backend host mapping failed: {message}")]
489    BackendFailure { message: String },
490    /// The source did not cover the full write-only mapping.
491    #[error("host write length mismatch: expected {expected}, got {actual}")]
492    LengthMismatch { expected: usize, actual: usize },
493}
494
495/// Metadata sealed at the tensor/root boundary before a provider launch.
496///
497/// Providers receive this request exactly once for a prepared access. Binding
498/// code consumes the resulting opaque state and does not receive replacement
499/// storage, ranges, or raw pointers.
500#[doc(hidden)]
501#[derive(Clone, Copy, Debug)]
502pub struct DeviceAccessRequest<'a> {
503    allocation_domain: AllocationDomainId,
504    allocation_id: AllocationId,
505    byte_len: usize,
506    element_size: usize,
507    shape: &'a [usize],
508    strides: &'a [isize],
509    offset: isize,
510}
511
512impl<'a> DeviceAccessRequest<'a> {
513    pub(crate) fn new(
514        allocation_domain: AllocationDomainId,
515        allocation_id: AllocationId,
516        byte_len: usize,
517        element_size: usize,
518        shape: &'a [usize],
519        strides: &'a [isize],
520        offset: isize,
521    ) -> Self {
522        Self {
523            allocation_domain,
524            allocation_id,
525            byte_len,
526            element_size,
527            shape,
528            strides,
529            offset,
530        }
531    }
532
533    pub fn allocation_domain(&self) -> AllocationDomainId {
534        self.allocation_domain
535    }
536
537    pub fn allocation_id(&self) -> AllocationId {
538        self.allocation_id
539    }
540
541    pub fn byte_len(&self) -> usize {
542        self.byte_len
543    }
544
545    pub fn element_size(&self) -> usize {
546        self.element_size
547    }
548
549    pub fn shape(&self) -> &[usize] {
550        self.shape
551    }
552
553    pub fn strides(&self) -> &[isize] {
554        self.strides
555    }
556
557    pub fn offset(&self) -> isize {
558        self.offset
559    }
560}
561
562/// Typed failure returned while preparing a provider-native device access.
563#[doc(hidden)]
564#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
565pub enum DeviceAccessError {
566    #[error("backend `{backend}` does not support prepared device access")]
567    Unsupported { backend: &'static str },
568    #[error("prepared device access request is invalid: {message}")]
569    InvalidRequest { message: String },
570    #[error("provider device preparation failed: {message}")]
571    ProviderFailure { message: String },
572}
573
574/// Opaque provider-prepared state retained for one device binding.
575#[doc(hidden)]
576pub trait PreparedDeviceAccess: Debug {
577    fn as_any(&self) -> &dyn Any;
578
579    fn into_any(self: Box<Self>) -> Box<dyn Any>;
580}
581
582trait ReadGuardAccess<T> {
583    fn as_slice(&self) -> &[T];
584}
585
586impl<T, G> ReadGuardAccess<T> for G
587where
588    G: Deref,
589    G::Target: AsRef<[T]>,
590{
591    fn as_slice(&self) -> &[T] {
592        self.deref().as_ref()
593    }
594}
595
596/// Closure-scoped read mapping of a backend allocation.
597///
598/// # Examples
599///
600/// ```rust
601/// use tenferro_tensor::HostReadGuard;
602///
603/// let guard = HostReadGuard::new(vec![1_u32, 2]);
604/// assert_eq!(&*guard, &[1, 2]);
605/// ```
606pub struct HostReadGuard<'a, T> {
607    access: Box<dyn ReadGuardAccess<T> + 'a>,
608}
609
610impl<T> Debug for HostReadGuard<'_, T> {
611    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
612        formatter
613            .debug_struct("HostReadGuard")
614            .field("len", &self.len())
615            .finish_non_exhaustive()
616    }
617}
618
619impl<'a, T> HostReadGuard<'a, T> {
620    /// Wrap a backend-native read guard without exposing its concrete type.
621    ///
622    /// # Examples
623    ///
624    /// ```rust
625    /// use tenferro_tensor::HostReadGuard;
626    ///
627    /// let guard = HostReadGuard::new(vec![3_i32]);
628    /// assert_eq!(guard[0], 3);
629    /// ```
630    pub fn new<G>(guard: G) -> Self
631    where
632        G: Deref + 'a,
633        G::Target: AsRef<[T]>,
634        T: 'a,
635    {
636        Self {
637            access: Box::new(guard),
638        }
639    }
640
641    /// Borrow the mapped elements as a rank-1 host view.
642    ///
643    /// The view lends the mapping's shared borrow, so the guard stays alive for
644    /// as long as the view is used and no copy or transfer happens.
645    ///
646    /// # Examples
647    ///
648    /// ```rust
649    /// use tenferro_tensor::HostReadGuard;
650    ///
651    /// let guard = HostReadGuard::new(vec![3_i32, 4]);
652    /// let view = guard.as_view()?;
653    /// assert_eq!(view.shape(), &[2]);
654    /// assert_eq!(view.get(&[1]), Some(&4));
655    /// # Ok::<(), tenferro_tensor::Error>(())
656    /// ```
657    ///
658    /// # Errors
659    ///
660    /// Returns [`crate::Error::Validation`] with
661    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] or
662    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
663    /// mapped length cannot be represented as a rank-1 layout.
664    pub fn as_view(&self) -> crate::Result<TypedTensorView<'_, T, DynRank>>
665    where
666        T: 'static,
667    {
668        let data: &[T] = self;
669        TypedTensorView::from_slice(vec![data.len()], vec![1], 0, data)
670    }
671}
672
673impl<T> Deref for HostReadGuard<'_, T> {
674    type Target = [T];
675
676    fn deref(&self) -> &Self::Target {
677        self.access.as_slice()
678    }
679}
680
681/// Backend-neutral owner of one shared tensor allocation domain.
682///
683/// CPU operation crates use this object-safe boundary to allocate results in
684/// the same managed domain without depending on a GPU provider crate.
685///
686/// # Examples
687///
688/// ```rust
689/// use std::sync::Arc;
690/// use tenferro_tensor::SharedTensorAllocationDomain;
691///
692/// let _domain: Option<Arc<dyn SharedTensorAllocationDomain>> = None;
693/// ```
694pub trait SharedTensorAllocationDomain: Debug + Send + Sync + 'static {
695    /// Return the stable identity shared by every allocation from this owner.
696    fn id(&self) -> AllocationDomainId;
697
698    /// Allocate an uninitialized compact column-major tensor in this domain.
699    ///
700    /// # Errors
701    ///
702    /// Returns a typed validation, unsupported-dtype, or backend allocation error.
703    fn allocate(&self, dtype: DType, shape: &[usize]) -> crate::Result<Tensor>;
704}
705
706type HostWriteCopy<'a, T> = dyn FnMut(&[T]) -> Result<(), HostAccessError> + 'a;
707
708/// Closure-scoped write-only mapping of a backend allocation.
709///
710/// # Examples
711///
712/// ```rust
713/// use tenferro_tensor::{HostAccessError, HostWriteGuard};
714///
715/// let mut written = Vec::new();
716/// {
717///     let mut guard = HostWriteGuard::new(2, |source: &[u32]| {
718///         written.extend_from_slice(source);
719///         Ok::<(), HostAccessError>(())
720///     });
721///     guard.copy_from_slice(&[4, 5]).unwrap();
722/// }
723/// assert_eq!(written, [4, 5]);
724/// ```
725pub struct HostWriteGuard<'a, T> {
726    len: usize,
727    copy: Box<HostWriteCopy<'a, T>>,
728}
729
730impl<T> Debug for HostWriteGuard<'_, T> {
731    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
732        formatter
733            .debug_struct("HostWriteGuard")
734            .field("len", &self.len)
735            .finish_non_exhaustive()
736    }
737}
738
739impl<'a, T> HostWriteGuard<'a, T> {
740    /// Wrap a backend-native write guard without exposing its concrete type.
741    ///
742    /// # Examples
743    ///
744    /// ```rust
745    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
746    ///
747    /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
748    /// assert!(guard.is_empty());
749    /// ```
750    ///
751    /// # Errors
752    ///
753    /// Construction is infallible. A callback failure such as
754    /// [`HostAccessError::BackendFailure`] is returned later by
755    /// [`Self::copy_from_slice`].
756    pub fn new<F>(len: usize, copy: F) -> Self
757    where
758        F: FnMut(&[T]) -> Result<(), HostAccessError> + 'a,
759        T: 'a,
760    {
761        Self {
762            len,
763            copy: Box::new(copy),
764        }
765    }
766
767    /// Number of elements covered by this write-only mapping.
768    ///
769    /// # Examples
770    ///
771    /// ```rust
772    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
773    ///
774    /// let guard = HostWriteGuard::new(2, |_source: &[f32]| Ok::<(), HostAccessError>(()));
775    /// assert_eq!(guard.len(), 2);
776    /// ```
777    pub fn len(&self) -> usize {
778        self.len
779    }
780
781    /// Returns `true` when this mapping covers no elements.
782    ///
783    /// # Examples
784    ///
785    /// ```rust
786    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
787    ///
788    /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
789    /// assert!(guard.is_empty());
790    /// ```
791    pub fn is_empty(&self) -> bool {
792        self.len == 0
793    }
794
795    /// Replace the full mapped allocation contents.
796    ///
797    /// # Examples
798    ///
799    /// ```rust
800    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
801    ///
802    /// let mut guard = HostWriteGuard::new(1, |_source: &[f32]| Ok::<(), HostAccessError>(()));
803    /// guard.copy_from_slice(&[1.0]).unwrap();
804    /// ```
805    ///
806    /// # Errors
807    ///
808    /// Returns [`HostAccessError::LengthMismatch`] when `source` does not cover
809    /// the complete mapping, or the typed backend error returned by the owning
810    /// write guard.
811    pub fn copy_from_slice(&mut self, source: &[T]) -> Result<(), HostAccessError> {
812        if source.len() != self.len {
813            return Err(HostAccessError::LengthMismatch {
814                expected: self.len,
815                actual: source.len(),
816            });
817        }
818        (self.copy)(source)
819    }
820}
821
822impl<T> Debug for BackendStorageHandle<T> {
823    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
824        f.debug_struct("BackendStorageHandle")
825            .field("id", &self.id)
826            .finish()
827    }
828}
829
830impl<T> BackendStorageHandle<T> {
831    /// Create a new backend buffer handle.
832    ///
833    /// # Examples
834    ///
835    /// ```rust
836    /// use tenferro_tensor::BackendStorageHandle;
837    ///
838    /// let handle = BackendStorageHandle::<f64>::new(1);
839    /// assert_eq!(tenferro_tensor::BackendStorage::len(&handle), 0);
840    /// ```
841    pub fn new(id: u64) -> Self {
842        Self::new_with_len(id, 0)
843    }
844
845    /// Create a new backend buffer handle with a logical element count.
846    ///
847    /// # Examples
848    ///
849    /// ```rust
850    /// use tenferro_tensor::{BackendStorage, BackendStorageHandle};
851    ///
852    /// let handle = BackendStorageHandle::<f64>::new_with_len(1, 4);
853    /// assert_eq!(BackendStorage::len(&handle), 4);
854    /// ```
855    pub fn new_with_len(id: u64, len: usize) -> Self {
856        Self {
857            id,
858            len,
859            // Synthetic opaque handles are test/adapter allocations. Give
860            // each one an explicit domain so root import never fabricates
861            // identity from a missing provider field.
862            allocation_domain: AllocationDomainId::fresh(),
863            _phantom: std::marker::PhantomData,
864        }
865    }
866}
867
868/// Opaque backend-owned tensor buffer.
869///
870/// Tensor core never inspects backend-native allocations directly. Backend
871/// crates store their own concrete handle types behind this trait and
872/// downcast inside the owning backend only.
873///
874/// # Examples
875///
876/// ```rust
877/// use std::sync::Arc;
878/// use tenferro_tensor::{BackendStorage, BackendStorageHandle};
879///
880/// let buffer: Arc<dyn BackendStorage<f64>> = Arc::new(BackendStorageHandle::<f64>::new_with_len(7, 2));
881/// assert_eq!(buffer.backend_family(), "opaque");
882/// assert_eq!(buffer.len(), 2);
883/// ```
884pub trait BackendStorage<T>: Debug + Send + Sync + 'static {
885    /// Stable backend family identifier.
886    fn backend_family(&self) -> &'static str;
887
888    /// Number of logical elements in the backend allocation.
889    fn len(&self) -> usize;
890
891    /// Returns `true` when the backend allocation is empty.
892    fn is_empty(&self) -> bool {
893        self.len() == 0
894    }
895
896    /// Return the shared-allocation domain, when this buffer belongs to one.
897    fn allocation_domain(&self) -> Option<AllocationDomainId> {
898        None
899    }
900
901    /// Return the stable physical allocation identity, when available.
902    fn allocation_id(&self) -> Option<AllocationId> {
903        None
904    }
905
906    /// Prepare one provider-native device access from the root-owned buffer.
907    ///
908    /// The returned state is consumed by the provider binding path. Providers
909    /// that do not expose device launches return [`DeviceAccessError::Unsupported`].
910    #[doc(hidden)]
911    fn prepare_device_access(
912        &self,
913        _request: DeviceAccessRequest<'_>,
914    ) -> Result<Box<dyn PreparedDeviceAccess>, DeviceAccessError> {
915        Err(DeviceAccessError::Unsupported {
916            backend: self.backend_family(),
917        })
918    }
919
920    /// Map the allocation for closure-scoped host reads.
921    ///
922    /// # Errors
923    ///
924    /// The default returns [`HostAccessError::Unsupported`]. Host-visible
925    /// backends return typed overlap, pending-GPU, or backend mapping failures.
926    fn map_read(&self) -> Result<HostReadGuard<'_, T>, HostAccessError> {
927        Err(HostAccessError::Unsupported {
928            backend: self.backend_family(),
929        })
930    }
931
932    /// Map the allocation for closure-scoped host writes.
933    ///
934    /// The mutable receiver keeps write authority with the owning tensor or
935    /// provider object; borrowed views do not clone or share that authority.
936    ///
937    /// # Errors
938    ///
939    /// The default returns [`HostAccessError::Unsupported`]. Host-visible
940    /// backends return typed overlap, pending-GPU, or backend mapping failures.
941    fn map_write(&mut self) -> Result<HostWriteGuard<'_, T>, HostAccessError> {
942        Err(HostAccessError::Unsupported {
943            backend: self.backend_family(),
944        })
945    }
946
947    /// Type-erased access for the backend crate that owns the concrete handle.
948    fn as_any(&self) -> &dyn Any;
949}
950
951impl<T: Send + Sync + 'static> BackendStorage<T> for BackendStorageHandle<T> {
952    fn backend_family(&self) -> &'static str {
953        "opaque"
954    }
955
956    fn len(&self) -> usize {
957        self.len
958    }
959
960    fn allocation_domain(&self) -> Option<AllocationDomainId> {
961        Some(self.allocation_domain)
962    }
963
964    fn allocation_id(&self) -> Option<AllocationId> {
965        Some(AllocationId::from_backend_id(self.id))
966    }
967
968    fn as_any(&self) -> &dyn Any {
969        self
970    }
971}
972
973/// Tensor storage.
974///
975/// # Examples
976///
977/// ```rust
978/// use tenferro_tensor::StorageBuffer;
979///
980/// let host = StorageBuffer::Host(vec![1.0_f64, 2.0]);
981/// ```
982#[derive(Debug)]
983pub enum StorageBuffer<T> {
984    Host(Vec<T>),
985    Backend(Box<dyn BackendStorage<T>>),
986}
987
988impl<T: 'static> StorageBuffer<T> {
989    /// Return the physical element count in this buffer.
990    ///
991    /// # Examples
992    ///
993    /// ```rust
994    /// use tenferro_tensor::StorageBuffer;
995    ///
996    /// assert_eq!(StorageBuffer::Host(vec![1_i32, 2]).len(), 2);
997    /// ```
998    pub fn len(&self) -> usize {
999        match self {
1000            Self::Host(data) => data.len(),
1001            Self::Backend(buffer) => buffer.len(),
1002        }
1003    }
1004
1005    /// Return whether this buffer has no physical elements.
1006    ///
1007    /// # Examples
1008    ///
1009    /// ```rust
1010    /// use tenferro_tensor::StorageBuffer;
1011    ///
1012    /// assert!(StorageBuffer::<i32>::Host(Vec::new()).is_empty());
1013    /// ```
1014    pub fn is_empty(&self) -> bool {
1015        self.len() == 0
1016    }
1017
1018    /// Return whether the storage is backend-owned rather than host-owned.
1019    ///
1020    /// # Examples
1021    ///
1022    /// ```rust
1023    /// use tenferro_tensor::StorageBuffer;
1024    ///
1025    /// assert!(!StorageBuffer::Host(vec![1_i32]).is_backend());
1026    /// ```
1027    pub fn is_backend(&self) -> bool {
1028        matches!(self, Self::Backend(_))
1029    }
1030}
1031
1032/// Sealed marker for the plain and pooled host payload of a [`TypedTensor`].
1033///
1034/// `TypedTensor<T, R, Host>` owns host elements directly, so it offers
1035/// infallible host access (`as_slice`, `get`, `get_mut`, `Index`/`IndexMut`)
1036/// and [`Clone`] without a runtime device check. `T` needs no `Copy`,
1037/// [`TensorScalar`] or arithmetic bound for that.
1038///
1039/// # Examples
1040///
1041/// ```
1042/// use tenferro_tensor::{DynRank, Host, TypedTensor};
1043///
1044/// let tensor: TypedTensor<f64, DynRank, Host> =
1045///     TypedTensor::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1046/// assert_eq!(tensor.as_slice(), &[1.0, 2.0]);
1047/// assert_eq!(tensor[&[1]], 2.0);
1048/// # Ok::<(), tenferro_tensor::Error>(())
1049/// ```
1050#[derive(Debug)]
1051pub struct Host;
1052
1053/// Sealed marker for the group-backed payload of a [`TypedTensor`].
1054///
1055/// `TypedTensor<T, R, Gpu>` carries provider allocation authority, identity
1056/// and retirement state through its allocation group, so host access stays
1057/// fallible and must be prepared or mapped explicitly. The marker is not proof
1058/// of a particular device ordinal or provider.
1059///
1060/// # Examples
1061///
1062/// ```
1063/// use tenferro_tensor::{DynRank, Gpu, Host, TypedTensor};
1064///
1065/// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1066/// let gpu: TypedTensor<f64, DynRank, Gpu> = host.promote()?;
1067/// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
1068/// # Ok::<(), tenferro_tensor::Error>(())
1069/// ```
1070#[derive(Debug)]
1071pub struct Gpu;
1072
1073/// Sealed marker for the runtime union of the host and group-backed payloads.
1074///
1075/// This is the default representation: one type for ordinary tensors that may
1076/// be host-resident or backend-resident at runtime.
1077///
1078/// # Examples
1079///
1080/// ```
1081/// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1082///
1083/// let host = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1084/// let dynamic: TypedTensor<i32, DynRank, Dynamic> = host.into_dynamic();
1085/// assert_eq!(dynamic.host_data()?, &[1, 2]);
1086/// # Ok::<(), tenferro_tensor::Error>(())
1087/// ```
1088#[derive(Debug)]
1089pub struct Dynamic;
1090
1091mod representation_sealed {
1092    pub trait Sealed {}
1093    impl Sealed for super::Host {}
1094    impl Sealed for super::Gpu {}
1095    impl Sealed for super::Dynamic {}
1096}
1097
1098/// Sealed selector for the owned payload representation of a [`TypedTensor`].
1099///
1100/// The three markers are [`Host`], [`Gpu`] and [`Dynamic`]. The trait is
1101/// sealed: downstream crates select an existing representation, they do not
1102/// define one.
1103///
1104/// # Examples
1105///
1106/// ```
1107/// use tenferro_tensor::{Dynamic, DynRank, Host, Representation, TypedTensor};
1108///
1109/// fn extent<D: Representation>(tensor: &TypedTensor<f64, DynRank, D>) -> usize {
1110///     tensor.shape()[0]
1111/// }
1112/// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![3], vec![0.0; 3])?;
1113/// assert_eq!(extent(&host), 3);
1114/// let dynamic: TypedTensor<f64, DynRank, Dynamic> = host.into_dynamic();
1115/// assert_eq!(extent(&dynamic), 3);
1116/// # Ok::<(), tenferro_tensor::Error>(())
1117/// ```
1118pub trait Representation: representation_sealed::Sealed + 'static {
1119    /// Owned payload stored for this representation.
1120    #[doc(hidden)]
1121    type Storage<T, R: TensorRank>;
1122}
1123
1124impl Representation for Host {
1125    #[doc(hidden)]
1126    type Storage<T, R: TensorRank> = HostStorage<T>;
1127}
1128
1129impl Representation for Gpu {
1130    #[doc(hidden)]
1131    type Storage<T, R: TensorRank> = GroupStorage<R>;
1132}
1133
1134impl Representation for Dynamic {
1135    #[doc(hidden)]
1136    type Storage<T, R: TensorRank> = DynamicStorage<T, R>;
1137}
1138
1139/// Directly owned plain or pooled host elements.
1140#[doc(hidden)]
1141pub struct HostStorage<T> {
1142    data: HostData<T>,
1143}
1144
1145/// Group-backed payload: provider allocation authority, identity and retirement.
1146#[doc(hidden)]
1147pub struct GroupStorage<R: TensorRank> {
1148    group: Box<OwnedTensorGroup<R>>,
1149}
1150
1151/// Runtime union of the [`Host`] and [`Gpu`] owned payloads.
1152#[doc(hidden)]
1153pub enum DynamicStorage<T, R: TensorRank> {
1154    Host(HostStorage<T>),
1155    Group(GroupStorage<R>),
1156}
1157
1158impl<T> std::fmt::Debug for HostStorage<T> {
1159    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1160        self.data.fmt(formatter)
1161    }
1162}
1163
1164impl<R: TensorRank> std::fmt::Debug for GroupStorage<R> {
1165    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1166        formatter
1167            .debug_struct("GroupStorage")
1168            .field("group", &self.group)
1169            .finish()
1170    }
1171}
1172
1173impl<T, R: TensorRank> std::fmt::Debug for DynamicStorage<T, R> {
1174    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1175        match self {
1176            Self::Host(host) => formatter.debug_tuple("Host").field(host).finish(),
1177            Self::Group(group) => formatter.debug_tuple("Group").field(group).finish(),
1178        }
1179    }
1180}
1181
1182/// Owned compact column-major typed tensor.
1183///
1184/// `T` is the element type, `R` the rank metadata (default [`DynRank`]) and `D`
1185/// the owned representation (default [`Dynamic`]). Shape and placement live on
1186/// the tensor itself, once, never inside a storage arm.
1187///
1188/// No bound is imposed on `T` by the type: plain host adoption only needs the
1189/// constructor's own requirements, and `T: Clone` is required only by the
1190/// copying constructors. A preset [`TensorScalar`] appears only where a dtype
1191/// identity or a numerical capability is actually used, never for plain host
1192/// ownership.
1193///
1194/// Owned tensors are compact column-major. Arbitrary strides and metadata-only
1195/// layout changes are represented by [`TypedTensorView`] and
1196/// [`TypedTensorViewMut`].
1197///
1198/// # Examples
1199///
1200/// ```
1201/// use tenferro_tensor::{DynRank, Host, Rank, Tensor, TypedTensor};
1202///
1203/// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
1204/// assert_eq!(t.shape(), &[2, 2]);
1205///
1206/// let static_rank = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
1207/// assert_eq!(static_rank.rank(), 2);
1208///
1209/// let host: TypedTensor<i32, DynRank, Host> =
1210///     TypedTensor::from_host_vec_col_major(vec![2], vec![1, 2]).unwrap();
1211/// assert_eq!(host.get(&[1])?, &2);
1212///
1213/// let dynamic = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64; 4]).unwrap();
1214/// assert_eq!(dynamic.shape(), &[2, 2]);
1215/// # Ok::<(), tenferro_tensor::Error>(())
1216/// ```
1217pub struct TypedTensor<T, R: TensorRank = DynRank, D: Representation = Dynamic> {
1218    shape: R::Shape,
1219    placement: Placement,
1220    /// Column-major element strides, cached at construction so a strided view of
1221    /// this tensor borrows its layout instead of recomputing it per operation.
1222    layout: StrideVec,
1223    storage: D::Storage<T, R>,
1224}
1225
1226/// Validate and adopt a column-major host vector as a statically host-owned tensor.
1227fn typed_host_tensor_from_vec_col_major<T, R: TensorRank>(
1228    shape: impl tenferro_tensor_core::IntoRankShape<R>,
1229    data: Vec<T>,
1230    op: &'static str,
1231) -> crate::Result<TypedTensor<T, R, Host>> {
1232    let shape = shape
1233        .into_rank_shape()
1234        .map_err(|err| tensor_layout_error(op, err))?;
1235    let layout = tenferro_tensor_core::col_major_strides(shape.as_ref())
1236        .map_err(|err| tensor_layout_error(op, err))?;
1237    try_checked_shape_len(shape.as_ref(), data.len(), op)?;
1238    Ok(TypedTensor {
1239        shape,
1240        placement: default_placement(),
1241        layout,
1242        storage: HostStorage {
1243            data: HostData::new(data),
1244        },
1245    })
1246}
1247
1248/// Reorder explicit row-major host values into column-major order.
1249fn row_major_reorder<T: Clone, R: TensorRank>(
1250    shape: impl tenferro_tensor_core::IntoRankShape<R>,
1251    data: Vec<T>,
1252    op: &'static str,
1253) -> crate::Result<(R::Shape, Vec<T>)> {
1254    let shape = shape
1255        .into_rank_shape()
1256        .map_err(|err| tensor_layout_error(op, err))?;
1257    tenferro_tensor_core::col_major_strides(shape.as_ref())
1258        .map_err(|err| tensor_layout_error(op, err))?;
1259    try_checked_shape_len(shape.as_ref(), data.len(), op)?;
1260    let mut row_strides = shape_vec_zeros(shape.as_ref().len());
1261    let mut stride = 1usize;
1262    for axis in (0..row_strides.len()).rev() {
1263        row_strides[axis] = stride;
1264        stride = stride
1265            .checked_mul(shape.as_ref()[axis])
1266            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
1267    }
1268    let mut coordinates = shape_vec_zeros(row_strides.len());
1269    let mut source_offset = 0usize;
1270    let mut reordered = Vec::with_capacity(data.len());
1271    for _ in 0..data.len() {
1272        reordered.push(data[source_offset].clone());
1273        for axis in 0..coordinates.len() {
1274            coordinates[axis] += 1;
1275            if coordinates[axis] < shape.as_ref()[axis] {
1276                source_offset += row_strides[axis];
1277                break;
1278            }
1279            coordinates[axis] = 0;
1280            source_offset -= row_strides[axis] * (shape.as_ref()[axis] - 1);
1281        }
1282    }
1283    Ok((shape, reordered))
1284}
1285
1286impl<T, R: TensorRank> TypedTensor<T, R, Host> {
1287    /// Adopt a column-major host `Vec<T>` as a statically host-owned tensor.
1288    ///
1289    /// The payload is the caller's vector itself; `T` needs no `Copy`,
1290    /// [`TensorScalar`] or arithmetic bound, and no group, session or device is
1291    /// involved.
1292    ///
1293    /// # Examples
1294    /// ```
1295    /// use tenferro_tensor::{Host, Rank, TypedTensor};
1296    /// struct Custom(String);
1297    /// let tensor = TypedTensor::<Custom, Rank<2>, Host>::from_host_vec_col_major(
1298    ///     [1, 2], vec![Custom("a".into()), Custom("b".into())],
1299    /// )?;
1300    /// assert_eq!(tensor[&[0, 1]].0.as_str(), "b");
1301    /// # Ok::<(), tenferro_tensor::Error>(())
1302    /// ```
1303    /// # Errors
1304    /// Returns [`crate::Error::Validation`] for a rank mismatch, shape/data
1305    /// length mismatch or shape/stride arithmetic overflow.
1306    pub fn from_host_vec_col_major(
1307        shape: impl tenferro_tensor_core::IntoRankShape<R>,
1308        data: Vec<T>,
1309    ) -> crate::Result<Self> {
1310        typed_host_tensor_from_vec_col_major(shape, data, "from_host_vec_col_major")
1311    }
1312
1313    /// Explicitly import row-major host values into column-major storage.
1314    ///
1315    /// Clones each input element once.
1316    ///
1317    /// # Examples
1318    /// ```
1319    /// use tenferro_tensor::{Host, Rank, TypedTensor};
1320    /// let tensor = TypedTensor::<i32, Rank<2>, Host>::from_host_vec_row_major(
1321    ///     [2, 3], vec![1, 2, 3, 4, 5, 6],
1322    /// )?;
1323    /// assert_eq!(tensor[&[1, 0]], 4);
1324    /// assert_eq!(tensor[&[0, 2]], 3);
1325    /// # Ok::<(), tenferro_tensor::Error>(())
1326    /// ```
1327    /// # Errors
1328    /// Returns [`crate::Error::Validation`] for a rank, shape-length or stride
1329    /// overflow, or when the shape product disagrees with the input length.
1330    pub fn from_host_vec_row_major(
1331        shape: impl tenferro_tensor_core::IntoRankShape<R>,
1332        data: Vec<T>,
1333    ) -> crate::Result<Self>
1334    where
1335        T: Clone,
1336    {
1337        let (shape, data) = row_major_reorder(shape, data, "from_host_vec_row_major")?;
1338        typed_host_tensor_from_vec_col_major(shape, data, "from_host_vec_row_major")
1339    }
1340
1341    /// Borrow the owned host elements.
1342    ///
1343    /// # Examples
1344    /// ```
1345    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1346    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1347    /// assert_eq!(tensor.as_slice(), &[1, 2]);
1348    /// # Ok::<(), tenferro_tensor::Error>(())
1349    /// ```
1350    pub fn as_slice(&self) -> &[T] {
1351        self.storage.data.as_slice()
1352    }
1353
1354    /// Alias of [`Self::as_slice`].
1355    ///
1356    /// # Examples
1357    ///
1358    /// ```
1359    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1360    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1361    /// assert_eq!(tensor.host_data(), &[1, 2]);
1362    /// # Ok::<(), tenferro_tensor::Error>(())
1363    /// ```
1364    pub fn host_data(&self) -> &[T] {
1365        self.as_slice()
1366    }
1367
1368    /// Exclusively borrow the owned host elements.
1369    ///
1370    /// # Examples
1371    ///
1372    /// ```
1373    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1374    /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1375    /// tensor.host_data_mut()[0] = 5;
1376    /// assert_eq!(tensor.as_slice(), &[5, 2]);
1377    /// # Ok::<(), tenferro_tensor::Error>(())
1378    /// ```
1379    pub fn host_data_mut(&mut self) -> &mut [T] {
1380        self.storage.data.as_mut_slice()
1381    }
1382
1383    /// Borrow one element by checked column-major multi-index.
1384    ///
1385    /// # Examples
1386    ///
1387    /// ```
1388    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1389    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2, 2], vec![1, 2, 3, 4])?;
1390    /// assert_eq!(tensor.get(&[1, 1])?, &4);
1391    /// assert!(tensor.get(&[2, 0]).is_err());
1392    /// # Ok::<(), tenferro_tensor::Error>(())
1393    /// ```
1394    ///
1395    /// # Errors
1396    /// Returns [`crate::Error::Validation`] for a wrong rank, an out-of-range
1397    /// coordinate or offset arithmetic overflow.
1398    pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
1399        let offset = self.linear_offset(indices)?;
1400        self.as_slice().get(offset).ok_or_else(|| {
1401            crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
1402        })
1403    }
1404
1405    /// Exclusively borrow one element by checked column-major multi-index.
1406    ///
1407    /// # Examples
1408    ///
1409    /// ```
1410    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1411    /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1412    /// *tensor.get_mut(&[1])? = 9;
1413    /// assert_eq!(tensor.as_slice(), &[1, 9]);
1414    /// # Ok::<(), tenferro_tensor::Error>(())
1415    /// ```
1416    ///
1417    /// # Errors
1418    /// Returns [`crate::Error::Validation`] for a wrong rank, an out-of-range
1419    /// coordinate or offset arithmetic overflow.
1420    pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
1421        let offset = self.linear_offset(indices)?;
1422        self.host_data_mut().get_mut(offset).ok_or_else(|| {
1423            crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
1424        })
1425    }
1426
1427    /// Consume this tensor and return the original host vector.
1428    ///
1429    /// # Examples
1430    ///
1431    /// ```
1432    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1433    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1434    /// assert_eq!(tensor.into_host_vec(), vec![1, 2]);
1435    /// # Ok::<(), tenferro_tensor::Error>(())
1436    /// ```
1437    pub fn into_host_vec(self) -> Vec<T> {
1438        self.storage.data.into_vec()
1439    }
1440
1441    /// Consume this tensor and return its shape and column-major host vector.
1442    ///
1443    /// # Examples
1444    ///
1445    /// ```
1446    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1447    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2, 1], vec![1, 2])?;
1448    /// let (shape, data) = tensor.into_vec_col_major();
1449    /// assert_eq!(shape, vec![2, 1]);
1450    /// assert_eq!(data, vec![1, 2]);
1451    /// # Ok::<(), tenferro_tensor::Error>(())
1452    /// ```
1453    pub fn into_vec_col_major(self) -> (Vec<usize>, Vec<T>) {
1454        let shape = self.shape().to_vec();
1455        (shape, self.into_host_vec())
1456    }
1457
1458    /// Make an explicit independent host copy with the same placement.
1459    ///
1460    /// # Examples
1461    /// ```
1462    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1463    /// let tensor = TypedTensor::<String, DynRank, Host>::from_host_vec_col_major(vec![1], vec!["a".into()])?;
1464    /// let copy = tensor.clone();
1465    /// assert_eq!(copy[&[0]], "a");
1466    /// # Ok::<(), tenferro_tensor::Error>(())
1467    /// ```
1468    pub fn duplicate(&self) -> Self
1469    where
1470        T: Clone,
1471    {
1472        let mut copy = typed_host_tensor_from_vec_col_major::<T, R>(
1473            self.shape.clone(),
1474            self.as_slice().to_vec(),
1475            "TypedTensor::duplicate",
1476        )
1477        .unwrap_or_else(|err| unreachable!("a validated host owner re-copies: {err}"));
1478        copy.placement = self.placement.clone();
1479        copy
1480    }
1481
1482    /// Move this host owner into the runtime union without copying or regrouping.
1483    ///
1484    /// # Examples
1485    /// ```
1486    /// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1487    /// let host = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7])?;
1488    /// let dynamic: TypedTensor<i32, DynRank, Dynamic> = host.into_dynamic();
1489    /// assert_eq!(dynamic.host_data()?, &[7]);
1490    /// # Ok::<(), tenferro_tensor::Error>(())
1491    /// ```
1492    pub fn into_dynamic(self) -> TypedTensor<T, R, Dynamic> {
1493        TypedTensor {
1494            shape: self.shape,
1495            placement: self.placement,
1496            layout: self.layout,
1497            storage: DynamicStorage::Host(self.storage),
1498        }
1499    }
1500
1501    /// Borrow this host owner as a typed view.
1502    ///
1503    /// # Examples
1504    ///
1505    /// ```
1506    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1507    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1508    /// let view = tensor.as_view();
1509    /// assert_eq!(view.shape(), &[2]);
1510    /// assert_eq!(view.as_host_slice(), &[1, 2]);
1511    /// # Ok::<(), tenferro_tensor::Error>(())
1512    /// ```
1513    pub fn as_view(&self) -> TypedTensorView<'_, T, R, Host> {
1514        TypedTensorView {
1515            buffer: TensorStorageRef::Host(self.as_slice()),
1516            root: None,
1517            layout: self.layout(),
1518            placement: self.placement.clone(),
1519            _representation: std::marker::PhantomData,
1520        }
1521    }
1522
1523    /// Exclusively borrow this host owner as a mutable typed view.
1524    ///
1525    /// # Examples
1526    ///
1527    /// ```
1528    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1529    /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1530    /// tensor.as_view_mut().as_host_slice_mut()[1] = 4;
1531    /// assert_eq!(tensor.as_slice(), &[1, 4]);
1532    /// # Ok::<(), tenferro_tensor::Error>(())
1533    /// ```
1534    pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R, Host> {
1535        let layout = self.layout();
1536        let placement = self.placement.clone();
1537        TypedTensorViewMut {
1538            buffer: TensorStorageRefMut::Host(self.storage.data.as_mut_slice()),
1539            root: None,
1540            layout,
1541            placement,
1542            _representation: std::marker::PhantomData,
1543        }
1544    }
1545}
1546
1547impl<T: Clone, R: TensorRank> Clone for TypedTensor<T, R, Host> {
1548    fn clone(&self) -> Self {
1549        self.duplicate()
1550    }
1551}
1552
1553impl<T, R: TensorRank> std::ops::Index<&[usize]> for TypedTensor<T, R, Host> {
1554    type Output = T;
1555
1556    /// # Panics
1557    ///
1558    /// Panics when the index has the wrong rank or is out of bounds, like any
1559    /// other slice indexing. Use [`TypedTensor::get`] for the checked form.
1560    fn index(&self, indices: &[usize]) -> &T {
1561        match self.get(indices) {
1562            Ok(value) => value,
1563            Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1564        }
1565    }
1566}
1567
1568impl<T, R: TensorRank> std::ops::IndexMut<&[usize]> for TypedTensor<T, R, Host> {
1569    /// # Panics
1570    ///
1571    /// Panics when the index has the wrong rank or is out of bounds, like any
1572    /// other slice indexing. Use [`TypedTensor::get_mut`] for the checked form.
1573    fn index_mut(&mut self, indices: &[usize]) -> &mut T {
1574        match self.get_mut(indices) {
1575            Ok(value) => value,
1576            Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1577        }
1578    }
1579}
1580
1581impl<T, const N: usize> std::ops::Index<[usize; N]> for TypedTensor<T, Rank<N>, Host> {
1582    type Output = T;
1583
1584    /// # Panics
1585    ///
1586    /// Panics when an index is out of bounds, like any other slice indexing.
1587    /// Use [`TypedTensor::get`] for the checked form.
1588    ///
1589    /// # Examples
1590    ///
1591    /// ```rust
1592    /// use tenferro_tensor::{Host, Rank, TypedTensor};
1593    /// let tensor =
1594    ///     TypedTensor::<i32, Rank<2>, Host>::from_host_vec_col_major([2, 2], vec![1, 2, 3, 4])?;
1595    /// assert_eq!(tensor[[0, 1]], 3);
1596    /// # Ok::<(), tenferro_tensor::Error>(())
1597    /// ```
1598    fn index(&self, indices: [usize; N]) -> &T {
1599        match self.get(&indices) {
1600            Ok(value) => value,
1601            Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1602        }
1603    }
1604}
1605
1606impl<T, const N: usize> std::ops::Index<&[usize; N]> for TypedTensor<T, Rank<N>, Host> {
1607    type Output = T;
1608
1609    /// The `&[usize; N]` spelling, which keeps a bare `&[i, j]` literal
1610    /// resolving as an integer index rather than forcing `usize` inference to
1611    /// choose between the slice and array spellings.
1612    ///
1613    /// # Panics
1614    ///
1615    /// Panics when an index is out of bounds, like any other slice indexing.
1616    /// Use [`TypedTensor::get`] for the checked form.
1617    ///
1618    /// # Examples
1619    ///
1620    /// ```rust
1621    /// use tenferro_tensor::{Host, Rank, TypedTensor};
1622    /// let tensor =
1623    ///     TypedTensor::<i32, Rank<2>, Host>::from_host_vec_col_major([2, 2], vec![1, 2, 3, 4])?;
1624    /// assert_eq!(tensor[&[0, 1]], 3);
1625    /// # Ok::<(), tenferro_tensor::Error>(())
1626    /// ```
1627    fn index(&self, indices: &[usize; N]) -> &T {
1628        std::ops::Index::index(self, *indices)
1629    }
1630}
1631
1632impl<T, const N: usize> std::ops::IndexMut<[usize; N]> for TypedTensor<T, Rank<N>, Host> {
1633    /// # Panics
1634    ///
1635    /// Panics when an index is out of bounds, like any other slice indexing.
1636    /// Use [`TypedTensor::get_mut`] for the checked form.
1637    ///
1638    /// # Examples
1639    ///
1640    /// ```rust
1641    /// use tenferro_tensor::{Host, Rank, TypedTensor};
1642    /// let mut tensor =
1643    ///     TypedTensor::<i32, Rank<2>, Host>::from_host_vec_col_major([2, 2], vec![1, 2, 3, 4])?;
1644    /// tensor[[1, 0]] = 7;
1645    /// assert_eq!(tensor.as_slice(), &[1, 7, 3, 4]);
1646    /// # Ok::<(), tenferro_tensor::Error>(())
1647    /// ```
1648    fn index_mut(&mut self, indices: [usize; N]) -> &mut T {
1649        match self.get_mut(&indices) {
1650            Ok(value) => value,
1651            Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1652        }
1653    }
1654}
1655
1656impl<T, const N: usize> std::ops::IndexMut<&[usize; N]> for TypedTensor<T, Rank<N>, Host> {
1657    /// # Panics
1658    ///
1659    /// Panics when an index is out of bounds, like any other slice indexing.
1660    /// Use [`TypedTensor::get_mut`] for the checked form.
1661    ///
1662    /// # Examples
1663    ///
1664    /// ```rust
1665    /// use tenferro_tensor::{Host, Rank, TypedTensor};
1666    /// let mut tensor =
1667    ///     TypedTensor::<i32, Rank<2>, Host>::from_host_vec_col_major([2, 2], vec![1, 2, 3, 4])?;
1668    /// tensor[&[1, 0]] = 7;
1669    /// assert_eq!(tensor.as_slice(), &[1, 7, 3, 4]);
1670    /// # Ok::<(), tenferro_tensor::Error>(())
1671    /// ```
1672    fn index_mut(&mut self, indices: &[usize; N]) -> &mut T {
1673        std::ops::IndexMut::index_mut(self, *indices)
1674    }
1675}
1676
1677impl<T, const N: usize> std::ops::Index<[usize; N]> for TypedTensor<T, Rank<N>> {
1678    type Output = T;
1679
1680    /// # Panics
1681    ///
1682    /// Panics when an index is out of bounds or when the storage is not
1683    /// host-accessible, like any other slice indexing. Use
1684    /// [`TypedTensor::get`] for the checked form.
1685    ///
1686    /// # Examples
1687    ///
1688    /// ```rust
1689    /// use tenferro_tensor::{Rank, TypedTensor};
1690    /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?;
1691    /// assert_eq!(tensor[[0, 1]], 3.);
1692    /// # Ok::<(), tenferro_tensor::Error>(())
1693    /// ```
1694    fn index(&self, indices: [usize; N]) -> &T {
1695        match self.get(&indices) {
1696            Ok(value) => value,
1697            Err(err) => panic!("TypedTensor index {indices:?} is invalid: {err}"),
1698        }
1699    }
1700}
1701
1702impl<T, const N: usize> std::ops::IndexMut<[usize; N]> for TypedTensor<T, Rank<N>> {
1703    /// # Panics
1704    ///
1705    /// Panics when an index is out of bounds or when the storage is not
1706    /// host-accessible, like any other slice indexing. Use
1707    /// [`TypedTensor::get_mut`] for the checked form.
1708    ///
1709    /// # Examples
1710    ///
1711    /// ```rust
1712    /// use tenferro_tensor::{Rank, TypedTensor};
1713    /// let mut tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?;
1714    /// tensor[[1, 0]] = 7.;
1715    /// assert_eq!(tensor.as_slice()?, &[1., 7., 3., 4.]);
1716    /// # Ok::<(), tenferro_tensor::Error>(())
1717    /// ```
1718    fn index_mut(&mut self, indices: [usize; N]) -> &mut T {
1719        match self.get_mut(&indices) {
1720            Ok(value) => value,
1721            Err(err) => panic!("TypedTensor index {indices:?} is invalid: {err}"),
1722        }
1723    }
1724}
1725
1726/// Read mapping over a host slice, as one owning guard.
1727fn host_read_guard<T>(data: &[T]) -> HostReadGuard<'_, T> {
1728    HostReadGuard::new(data)
1729}
1730
1731/// Write mapping over an exclusive host slice, as one owning guard.
1732fn host_write_guard<T: Clone>(data: &mut [T]) -> HostWriteGuard<'_, T> {
1733    let len = data.len();
1734    HostWriteGuard::new(len, move |source| {
1735        data.clone_from_slice(source);
1736        Ok(())
1737    })
1738}
1739
1740/// Promote a plain host payload into a group-backed root without copying elements.
1741fn promote_host_group<T: TensorScalar, R: TensorRank>(
1742    shape: R::Shape,
1743    host: HostData<T>,
1744) -> crate::Result<OwnedTensorGroup<R>> {
1745    let recycler = host.recycler.clone();
1746    let mut group = OwnedTensorGroup::from_host_vec(shape, host.into_vec())?;
1747    if let Some(recycler) = recycler {
1748        group
1749            .group
1750            .set_host_recycler(group.allocation_index.index(), recycler)
1751            .map_err(|error| crate::Error::runtime_state_source("TypedTensor::promote", error))?;
1752    }
1753    Ok(group)
1754}
1755
1756impl<T, R: TensorRank> TypedTensor<T, R, Host> {
1757    /// Borrow the owned host elements through one owning read mapping.
1758    ///
1759    /// The guard retains the exclusive borrow of this owner, so no other access
1760    /// can overlap it while the mapping is alive.
1761    ///
1762    /// # Examples
1763    /// ```
1764    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1765    /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1766    /// let guard = tensor.map_read();
1767    /// assert_eq!(&guard[..], &[1, 2]);
1768    /// # Ok::<(), tenferro_tensor::Error>(())
1769    /// ```
1770    pub fn map_read(&self) -> HostReadGuard<'_, T> {
1771        host_read_guard(self.as_slice())
1772    }
1773
1774    /// Exclusively borrow the owned host elements through one owning write mapping.
1775    ///
1776    /// Publish data with [`HostWriteGuard::copy_from_slice`]; the mapping is
1777    /// released when the guard is dropped.
1778    ///
1779    /// # Examples
1780    /// ```
1781    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1782    /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1783    /// tensor.map_write().copy_from_slice(&[3, 4]).unwrap();
1784    /// assert_eq!(tensor.as_slice(), &[3, 4]);
1785    /// # Ok::<(), tenferro_tensor::Error>(())
1786    /// ```
1787    pub fn map_write(&mut self) -> HostWriteGuard<'_, T>
1788    where
1789        T: Clone,
1790    {
1791        host_write_guard(self.host_data_mut())
1792    }
1793}
1794
1795impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R, Host> {
1796    /// Promote this plain host owner into a group-backed owner.
1797    ///
1798    /// The payload is adopted as a provider root: no element is copied and a
1799    /// pooled return target survives the promotion.
1800    ///
1801    /// # Examples
1802    /// ```
1803    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1804    /// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1805    /// let gpu = host.promote()?;
1806    /// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
1807    /// # Ok::<(), tenferro_tensor::Error>(())
1808    /// ```
1809    /// # Errors
1810    /// Returns [`crate::Error::RuntimeState`] when the new allocation group
1811    /// rejects the promoted root or its recycler.
1812    pub fn promote(self) -> crate::Result<TypedTensor<T, R, Gpu>> {
1813        let TypedTensor {
1814            shape,
1815            placement,
1816            layout,
1817            storage,
1818        } = self;
1819        let group = promote_host_group(shape.clone(), storage.data)?;
1820        Ok(TypedTensor {
1821            shape,
1822            placement,
1823            layout,
1824            storage: GroupStorage {
1825                group: Box::new(group),
1826            },
1827        })
1828    }
1829}
1830
1831impl<T, R: TensorRank> TypedTensor<T, R, Dynamic> {
1832    /// Borrow host elements through one owning read mapping.
1833    ///
1834    /// # Examples
1835    ///
1836    /// ```
1837    /// use tenferro_tensor::TypedTensor;
1838    /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
1839    /// let guard = tensor.map_read()?;
1840    /// assert_eq!(&guard[..], &[1, 2]);
1841    /// # Ok::<(), tenferro_tensor::Error>(())
1842    /// ```
1843    ///
1844    /// # Errors
1845    /// Returns [`crate::Error::HostAccess`] with
1846    /// [`HostAccessError::Unsupported`] when the group's allocation is not
1847    /// host-accessible, or [`crate::Error::Validation`] with
1848    /// [`ValidationError::NonContiguousViewAsSlice`] or
1849    /// [`ValidationError::ViewOutOfBounds`] when the owner has no contiguous
1850    /// logical extent inside its backing storage.
1851    pub fn map_read(&self) -> crate::Result<HostReadGuard<'_, T>> {
1852        match &self.storage {
1853            DynamicStorage::Host(host) => Ok(host_read_guard(host.data.as_slice())),
1854            DynamicStorage::Group(core) => {
1855                // A root-preserving extraction exports only its descriptor
1856                // extent (#2023), so the mapping must not expose the root.
1857                let range = group_logical_range(&core.group, "TypedTensor::map_read")?;
1858                let data = core.group.host_slice::<T>()?;
1859                data.get(range).map(host_read_guard).ok_or_else(|| {
1860                    crate::Error::validation(
1861                        "TypedTensor::map_read",
1862                        ValidationError::ViewOutOfBounds,
1863                    )
1864                })
1865            }
1866        }
1867    }
1868
1869    /// Exclusively borrow host elements through one owning write mapping.
1870    ///
1871    /// Publish data with [`HostWriteGuard::copy_from_slice`].
1872    ///
1873    /// # Examples
1874    ///
1875    /// ```
1876    /// use tenferro_tensor::{Error, TypedTensor};
1877    /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
1878    /// tensor
1879    ///     .map_write()?
1880    ///     .copy_from_slice(&[3, 4])
1881    ///     .map_err(|err| Error::host_access("map_write", err))?;
1882    /// assert_eq!(tensor.host_data()?, &[3, 4]);
1883    /// # Ok::<(), tenferro_tensor::Error>(())
1884    /// ```
1885    ///
1886    /// # Errors
1887    /// Returns [`crate::Error::HostAccess`] with
1888    /// [`HostAccessError::Unsupported`] when the group's allocation is not
1889    /// host-writable, or [`crate::Error::Validation`] with
1890    /// [`ValidationError::NonContiguousViewAsSlice`] or
1891    /// [`ValidationError::ViewOutOfBounds`] when the owner has no contiguous
1892    /// logical extent inside its backing storage.
1893    pub fn map_write(&mut self) -> crate::Result<HostWriteGuard<'_, T>>
1894    where
1895        T: Clone,
1896    {
1897        match &mut self.storage {
1898            DynamicStorage::Host(host) => Ok(host_write_guard(host.data.as_mut_slice())),
1899            DynamicStorage::Group(core) => {
1900                let range = group_logical_range(&core.group, "TypedTensor::map_write")?;
1901                let data = core.group.host_slice_mut::<T>()?;
1902                data.get_mut(range).map(host_write_guard).ok_or_else(|| {
1903                    crate::Error::validation(
1904                        "TypedTensor::map_write",
1905                        ValidationError::ViewOutOfBounds,
1906                    )
1907                })
1908            }
1909        }
1910    }
1911}
1912
1913impl<T, R: TensorRank> TypedTensor<T, R, Gpu> {
1914    /// Adopt a backend-owned buffer as a statically group-backed owner.
1915    ///
1916    /// A host buffer belongs to the [`Host`] representation; pass it to
1917    /// `TypedTensor::<T, R, Host>` instead of silently regrouping it here.
1918    ///
1919    /// # Examples
1920    /// ```
1921    /// use tenferro_tensor::{BackendStorageHandle, Placement, StorageBuffer, TypedTensor};
1922    /// let handle = BackendStorageHandle::<f32>::new_with_len(1, 2);
1923    /// let gpu = TypedTensor::<f32, tenferro_tensor::DynRank, tenferro_tensor::Gpu>::from_backend_buffer_col_major(
1924    ///     vec![2],
1925    ///     StorageBuffer::Backend(Box::new(handle)),
1926    ///     Placement::default(),
1927    /// )?;
1928    /// assert!(gpu.is_backend_buffer());
1929    /// # Ok::<(), tenferro_tensor::Error>(())
1930    /// ```
1931    /// # Errors
1932    /// Returns [`crate::Error::Validation`] when the compact shape disagrees
1933    /// with the buffer length, [`crate::Error::RuntimeState`] for a host buffer,
1934    /// or a runtime-state error when the allocation group cannot be built.
1935    pub fn from_backend_buffer_col_major(
1936        shape: impl tenferro_tensor_core::IntoRankShape<R>,
1937        buffer: StorageBuffer<T>,
1938        placement: Placement,
1939    ) -> crate::Result<Self>
1940    where
1941        T: TensorScalar + Send + Sync + 'static,
1942    {
1943        let op = "TypedTensor::from_backend_buffer_col_major";
1944        let layout = try_compact_layout(shape, op)?;
1945        try_checked_shape_len(layout.shape(), buffer.len(), op)?;
1946        let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
1947            .map_err(|err| tensor_layout_error(op, err))?;
1948        match buffer {
1949            StorageBuffer::Host(_) => Err(crate::Error::runtime_state(
1950                op,
1951                "a host buffer is plain host storage; use TypedTensor::<T, R, Host>::from_vec_col_major",
1952            )),
1953            StorageBuffer::Backend(buffer) => {
1954                let group = OwnedTensorGroup::from_backend_buffer(
1955                    group_shape.clone(),
1956                    StorageBuffer::Backend(buffer),
1957                    placement.clone(),
1958                )?;
1959                Ok(TypedTensor {
1960                    shape: group_shape,
1961                    placement,
1962                    layout: stride_vec(layout.strides()),
1963                    storage: GroupStorage {
1964                        group: Box::new(group),
1965                    },
1966                })
1967            }
1968        }
1969    }
1970
1971    /// Move this group-backed owner into the runtime union without rewriting it.
1972    ///
1973    /// # Examples
1974    ///
1975    /// ```
1976    /// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1977    /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7.0])?.promote()?;
1978    /// let dynamic: TypedTensor<f64, DynRank, Dynamic> = gpu.into_dynamic();
1979    /// assert_eq!(dynamic.host_data()?, &[7.0]);
1980    /// # Ok::<(), tenferro_tensor::Error>(())
1981    /// ```
1982    pub fn into_dynamic(self) -> TypedTensor<T, R, Dynamic> {
1983        TypedTensor {
1984            shape: self.shape,
1985            placement: self.placement,
1986            layout: self.layout,
1987            storage: DynamicStorage::Group(self.storage),
1988        }
1989    }
1990
1991    /// Whether this group's descriptor names a non-CPU provider.
1992    ///
1993    /// # Examples
1994    ///
1995    /// ```
1996    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1997    /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7.0])?.promote()?;
1998    /// // A promoted host payload stays on the CPU provider.
1999    /// assert!(!gpu.is_backend_buffer());
2000    /// # Ok::<(), tenferro_tensor::Error>(())
2001    /// ```
2002    pub fn is_backend_buffer(&self) -> bool {
2003        self.storage.group.is_backend_buffer()
2004    }
2005
2006    /// Borrow host elements when the group's allocation is host-accessible.
2007    ///
2008    /// # Examples
2009    ///
2010    /// ```
2011    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
2012    /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?.promote()?;
2013    /// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
2014    /// # Ok::<(), tenferro_tensor::Error>(())
2015    /// ```
2016    ///
2017    /// # Errors
2018    /// Returns [`crate::Error::RuntimeState`] for a device-only allocation and
2019    /// the non-compact-descriptor validation error for a strided owner.
2020    pub fn host_data(&self) -> crate::Result<&[T]> {
2021        let data = self.storage.group.host_slice::<T>()?;
2022        let range = group_logical_range(&self.storage.group, "TypedTensor::host_data")?;
2023        data.get(range).ok_or_else(|| {
2024            crate::Error::validation("TypedTensor::host_data", ValidationError::ViewOutOfBounds)
2025        })
2026    }
2027
2028    /// Exclusively borrow host elements when the group's allocation is host-accessible.
2029    ///
2030    /// # Examples
2031    ///
2032    /// ```
2033    /// use tenferro_tensor::{DynRank, Host, TypedTensor};
2034    /// let mut gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?.promote()?;
2035    /// gpu.host_data_mut()?[0] = 3.0;
2036    /// assert_eq!(gpu.host_data()?, &[3.0, 2.0]);
2037    /// # Ok::<(), tenferro_tensor::Error>(())
2038    /// ```
2039    ///
2040    /// # Errors
2041    /// Returns [`crate::Error::RuntimeState`] for a device-only allocation and
2042    /// the non-compact-descriptor validation error for a strided owner.
2043    pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
2044        let range = group_logical_range(&self.storage.group, "TypedTensor::host_data_mut")?;
2045        let data = self.storage.group.host_slice_mut::<T>()?;
2046        data.get_mut(range).ok_or_else(|| {
2047            crate::Error::validation(
2048                "TypedTensor::host_data_mut",
2049                ValidationError::ViewOutOfBounds,
2050            )
2051        })
2052    }
2053
2054    /// Prepare this group-backed owner for one provider-native read binding.
2055    ///
2056    /// # Errors
2057    /// Returns a provider preparation error when the group has no device-read
2058    /// path for this layout.
2059    #[doc(hidden)]
2060    pub fn prepare_device_read(
2061        &self,
2062        op: &'static str,
2063    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
2064    where
2065        T: TensorScalar + 'static,
2066    {
2067        let layout = group_physical_layout(&self.storage.group);
2068        self.storage
2069            .group
2070            .prepare_device_read_for_layout::<T>(&layout)
2071            .map_err(|error| crate::Error::runtime_state_source(op, error))
2072    }
2073
2074    /// Prepare this group-backed owner for one provider-native write binding.
2075    ///
2076    /// # Errors
2077    /// Returns a provider preparation error when the group has no device-write
2078    /// path for this layout.
2079    #[doc(hidden)]
2080    pub fn prepare_device_write(
2081        &mut self,
2082        op: &'static str,
2083    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
2084    where
2085        T: TensorScalar + 'static,
2086    {
2087        let layout = group_physical_layout(&self.storage.group);
2088        self.storage
2089            .group
2090            .prepare_device_write_for_layout::<T>(&layout)
2091            .map_err(|error| crate::Error::runtime_state_source(op, error))
2092    }
2093}
2094
2095impl<T, R: TensorRank> TypedTensor<T, R, Dynamic> {
2096    /// Checked narrowing to the statically host-owned representation.
2097    ///
2098    /// Fails without consuming ownership of the source tensor.
2099    ///
2100    /// # Examples
2101    /// ```
2102    /// use tenferro_tensor::TypedTensor;
2103    /// let dynamic = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![7])?;
2104    /// let host = dynamic.into_host().unwrap();
2105    /// assert_eq!(host.as_slice(), &[7]);
2106    /// # Ok::<(), tenferro_tensor::Error>(())
2107    /// ```
2108    /// # Errors
2109    /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is
2110    /// group-backed rather than plain host storage.
2111    #[allow(clippy::result_large_err)]
2112    pub fn into_host(self) -> Result<TypedTensor<T, R, Host>, ReinterpretError<Self>> {
2113        let TypedTensor {
2114            shape,
2115            placement,
2116            layout,
2117            storage,
2118        } = self;
2119        match storage {
2120            DynamicStorage::Host(host) => Ok(TypedTensor {
2121                shape,
2122                placement,
2123                layout,
2124                storage: host,
2125            }),
2126            DynamicStorage::Group(group) => Err(ReinterpretError::new(
2127                TypedTensor {
2128                    shape,
2129                    placement,
2130                    layout,
2131                    storage: DynamicStorage::Group(group),
2132                },
2133                crate::Error::runtime_state(
2134                    "TypedTensor::into_host",
2135                    "the tensor is group-backed rather than plain host storage",
2136                ),
2137            )),
2138        }
2139    }
2140
2141    /// Checked narrowing to the group-backed representation.
2142    ///
2143    /// Fails without consuming ownership of the source tensor.
2144    ///
2145    /// # Examples
2146    ///
2147    /// ```
2148    /// use tenferro_tensor::{DynRank, Gpu, Host, TypedTensor};
2149    /// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![2.0])?;
2150    /// let dynamic = host.promote()?.into_dynamic();
2151    /// let gpu: TypedTensor<f64, DynRank, Gpu> = dynamic.into_gpu().map_err(|f| f.into_parts().1)?;
2152    /// assert_eq!(gpu.host_data()?, &[2.0]);
2153    /// let plain = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![2.0])?;
2154    /// assert!(plain.into_gpu().is_err());
2155    /// # Ok::<(), tenferro_tensor::Error>(())
2156    /// ```
2157    ///
2158    /// # Errors
2159    /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is a
2160    /// plain host owner rather than a group-backed one.
2161    #[allow(clippy::result_large_err)]
2162    pub fn into_gpu(self) -> Result<TypedTensor<T, R, Gpu>, ReinterpretError<Self>> {
2163        let TypedTensor {
2164            shape,
2165            placement,
2166            layout,
2167            storage,
2168        } = self;
2169        match storage {
2170            DynamicStorage::Group(group) => Ok(TypedTensor {
2171                shape,
2172                placement,
2173                layout,
2174                storage: group,
2175            }),
2176            DynamicStorage::Host(host) => Err(ReinterpretError::new(
2177                TypedTensor {
2178                    shape,
2179                    placement,
2180                    layout,
2181                    storage: DynamicStorage::Host(host),
2182                },
2183                crate::Error::runtime_state(
2184                    "TypedTensor::into_gpu",
2185                    "the tensor is a plain host owner rather than group-backed storage",
2186                ),
2187            )),
2188        }
2189    }
2190}
2191
2192impl<T, R: TensorRank> TypedTensor<T, R, Dynamic> {
2193    /// Layout that reflects the owned storage rather than the compact shape.
2194    ///
2195    /// [`Self::layout`] reports the compact construction layout. A
2196    /// root-preserving extraction may instead carry a nonzero descriptor offset
2197    /// and fewer logical elements than the backing root, so callers that
2198    /// canonicalize an owned tensor must use this accessor.
2199    pub(crate) fn physical_layout(&self) -> TensorLayout<R> {
2200        match &self.storage {
2201            DynamicStorage::Host(_) => TensorLayout::<R>::compact(self.shape.clone())
2202                .unwrap_or_else(|err| unreachable!("validated owned shape: {err}")),
2203            DynamicStorage::Group(core) => group_physical_layout(&core.group),
2204        }
2205    }
2206}
2207
2208/// Descriptor layout of a group-backed owner over its whole backing root.
2209///
2210/// The descriptor is the storage truth for a root-preserving extraction, so its
2211/// offset and backing length override the compact logical shape cached on the
2212/// tensor.
2213fn group_physical_layout<R: TensorRank>(core: &OwnedTensorGroup<R>) -> TensorLayout<R> {
2214    let descriptor = core
2215        .group
2216        .descriptor_layout(core.slot)
2217        .unwrap_or_else(|| unreachable!("a live group carries its descriptor layout"));
2218    let shape = R::shape_from_vec(shape_vec(descriptor.shape()))
2219        .unwrap_or_else(|err| unreachable!("validated descriptor shape: {err:?}"));
2220    let strides = R::strides_from_vec(stride_vec(descriptor.strides()))
2221        .unwrap_or_else(|err| unreachable!("validated descriptor strides: {err:?}"));
2222    let buffer_len = core
2223        .group
2224        .descriptor_backing_len(core.slot)
2225        .unwrap_or_else(|| unreachable!("a live group carries its root length"));
2226    TensorLayout::<R>::from_parts(shape, strides, descriptor.offset(), buffer_len)
2227        .unwrap_or_else(|err| unreachable!("validated descriptor layout: {err:?}"))
2228}
2229
2230/// Contiguous logical element range of a group-backed owner inside its root.
2231fn group_logical_range<R: TensorRank>(
2232    core: &OwnedTensorGroup<R>,
2233    op: &'static str,
2234) -> crate::Result<std::ops::Range<usize>> {
2235    core.group
2236        .descriptor_logical_range(core.slot)
2237        .ok_or_else(|| crate::Error::validation(op, ValidationError::NonContiguousViewAsSlice))
2238}
2239
2240impl<T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensor<T, R, D> {
2241    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2242        formatter
2243            .debug_struct("TypedTensor")
2244            .field("shape", &self.shape.as_ref())
2245            .field("placement", &self.placement)
2246            .finish_non_exhaustive()
2247    }
2248}
2249
2250impl<T, R: TensorRank, D: Representation> TypedTensor<T, R, D> {
2251    /// Number of elements in the tensor.
2252    ///
2253    /// # Examples
2254    ///
2255    /// ```rust
2256    /// use tenferro_tensor::TypedTensor;
2257    ///
2258    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2259    /// assert_eq!(t.n_elements(), 6);
2260    /// ```
2261    pub fn n_elements(&self) -> usize {
2262        // Invariant: owned tensor constructors validate compact shape length against buffer length.
2263        match try_shape_product(self.shape(), "TypedTensor::n_elements") {
2264            Ok(n) => n,
2265            Err(err) => {
2266                unreachable!("TypedTensor compact shape is validated at construction: {err}")
2267            }
2268        }
2269    }
2270
2271    /// Tensor shape.
2272    ///
2273    /// # Examples
2274    ///
2275    /// ```
2276    /// use tenferro_tensor::TypedTensor;
2277    ///
2278    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
2279    /// assert_eq!(t.shape(), &[2]);
2280    /// ```
2281    pub fn shape(&self) -> &[usize] {
2282        self.shape.as_ref()
2283    }
2284
2285    /// Column-major element strides of this compact tensor.
2286    ///
2287    /// The layout is cached at construction, so views of a tensor borrow it
2288    /// rather than computing (and allocating) it once per operation.
2289    ///
2290    /// # Examples
2291    ///
2292    /// ```
2293    /// use tenferro_tensor::TypedTensor;
2294    ///
2295    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2296    /// assert_eq!(t.strides(), &[1, 2]);
2297    /// ```
2298    pub fn strides(&self) -> &[isize] {
2299        &self.layout
2300    }
2301
2302    /// Tensor rank.
2303    ///
2304    /// # Examples
2305    ///
2306    /// ```
2307    /// use tenferro_tensor::TypedTensor;
2308    ///
2309    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2310    /// assert_eq!(t.rank(), 2);
2311    /// ```
2312    pub fn rank(&self) -> usize {
2313        self.shape().len()
2314    }
2315
2316    /// Tensor layout metadata.
2317    ///
2318    /// Owned typed tensors are always compact column-major layouts.
2319    ///
2320    /// # Examples
2321    ///
2322    /// ```
2323    /// use tenferro_tensor::TypedTensor;
2324    ///
2325    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2326    /// assert_eq!(t.layout().strides(), &[1, 2]);
2327    /// ```
2328    pub fn layout(&self) -> TensorLayout<R> {
2329        TensorLayout::compact(self.shape.clone())
2330            .unwrap_or_else(|err| unreachable!("validated owned shape: {err}"))
2331    }
2332
2333    /// Return placement metadata for this tensor.
2334    ///
2335    /// # Examples
2336    ///
2337    /// ```
2338    /// use tenferro_tensor::{MemoryKind, TypedTensor};
2339    ///
2340    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
2341    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
2342    /// ```
2343    pub fn placement(&self) -> &Placement {
2344        &self.placement
2345    }
2346
2347    /// Replace placement metadata without changing the storage buffer.
2348    ///
2349    /// # Examples
2350    ///
2351    /// ```
2352    /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
2353    ///
2354    /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
2355    /// t.set_placement(Placement {
2356    ///     memory_kind: MemoryKind::PinnedHost,
2357    ///     device: None,
2358    ///     cpu_affinity: None,
2359    /// });
2360    /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
2361    /// ```
2362    pub fn set_placement(&mut self, placement: Placement) {
2363        self.placement = placement;
2364    }
2365
2366    /// Replace only CPU routing/locality metadata without changing storage.
2367    ///
2368    /// Device, memory kind, backend allocation domain, and allocation identity
2369    /// remain unchanged.
2370    ///
2371    /// # Examples
2372    ///
2373    /// ```rust
2374    /// use tenferro_tensor::{CpuDomainId, TypedTensor};
2375    ///
2376    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0])?;
2377    /// tensor.set_cpu_affinity(Some(CpuDomainId::new(4)));
2378    /// assert_eq!(tensor.placement().cpu_affinity, Some(CpuDomainId::new(4)));
2379    /// # Ok::<(), tenferro_tensor::Error>(())
2380    /// ```
2381    pub fn set_cpu_affinity(&mut self, cpu_affinity: Option<CpuDomainId>) {
2382        self.placement.cpu_affinity = cpu_affinity;
2383    }
2384
2385    /// Compute the checked compact column-major offset of an element.
2386    ///
2387    /// # Examples
2388    /// ```
2389    /// use tenferro_tensor::TypedTensor;
2390    /// let tensor = TypedTensor::<String>::from_vec_col_major([2, 3], vec![String::new(); 6])?;
2391    /// assert_eq!(tensor.linear_offset(&[1, 2])?, 5);
2392    /// # Ok::<(), tenferro_tensor::Error>(())
2393    /// ```
2394    /// # Errors
2395    /// Wrong rank, out-of-range coordinates or arithmetic overflow return
2396    /// [`crate::Error::Validation`].
2397    pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2398        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
2399    }
2400
2401    /// Consume this tensor and return its layout metadata.
2402    ///
2403    /// # Examples
2404    ///
2405    /// ```
2406    /// use tenferro_tensor::TypedTensor;
2407    ///
2408    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
2409    /// assert!(t.into_layout().is_compact_col_major().unwrap());
2410    /// ```
2411    pub fn into_layout(self) -> TensorLayout<R> {
2412        self.layout()
2413    }
2414
2415    /// Compute the physical element offset for a logical index.
2416    ///
2417    /// # Examples
2418    ///
2419    /// ```rust
2420    /// use tenferro_tensor::TypedTensor;
2421    ///
2422    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
2423    /// assert_eq!(t.layout_linear_offset(&[1, 2])?, 5);
2424    /// # Ok::<(), tenferro_tensor::Error>(())
2425    /// ```
2426    /// # Errors
2427    ///
2428    /// Returns [`crate::Error::Validation`] with
2429    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
2430    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
2431    /// when an index is outside its axis extent, or
2432    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
2433    /// arithmetic overflows.
2434    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2435        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::layout_linear_offset")
2436    }
2437
2438    /// Return whether this owned tensor is compact column-major.
2439    ///
2440    /// # Examples
2441    ///
2442    /// ```rust
2443    /// use tenferro_tensor::TypedTensor;
2444    ///
2445    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2446    /// assert!(t.is_col_major_contiguous()?);
2447    /// # Ok::<(), tenferro_tensor::Error>(())
2448    /// ```
2449    /// # Errors
2450    ///
2451    /// Returns [`crate::Error::Validation`] with
2452    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
2453    /// compactness arithmetic overflows.
2454    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
2455        self.layout()
2456            .is_compact_col_major()
2457            .map_err(|err| tensor_layout_error("TypedTensor::is_col_major_contiguous", err))
2458    }
2459
2460    /// Return a compact string summary of this tensor's layout metadata.
2461    ///
2462    /// # Examples
2463    ///
2464    /// ```rust
2465    /// use tenferro_tensor::TypedTensor;
2466    ///
2467    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2468    /// assert!(t.layout_summary().contains("shape=[2]"));
2469    /// # Ok::<(), tenferro_tensor::Error>(())
2470    /// ```
2471    pub fn layout_summary(&self) -> String {
2472        let layout = self.layout();
2473        layout_summary(self.shape(), layout.strides(), layout.offset())
2474    }
2475
2476    /// Assert this tensor is compact column-major.
2477    ///
2478    /// # Examples
2479    ///
2480    /// ```rust
2481    /// use tenferro_tensor::TypedTensor;
2482    ///
2483    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2484    /// t.assert_col_major_contiguous()?;
2485    /// # Ok::<(), tenferro_tensor::Error>(())
2486    /// ```
2487    /// # Errors
2488    ///
2489    /// Returns [`crate::Error::Validation`] with
2490    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
2491    /// compactness arithmetic overflows, or
2492    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
2493    /// tensor is not compact column-major.
2494    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
2495        let layout = self.layout();
2496        assert_layout_col_major_contiguous(
2497            self.is_col_major_contiguous()?,
2498            self.shape(),
2499            layout.strides(),
2500            layout.offset(),
2501            "TypedTensor::assert_col_major_contiguous",
2502        )
2503    }
2504}
2505
2506struct HostData<T> {
2507    buffer: StorageBuffer<T>,
2508    recycler: Option<std::sync::Weak<dyn crate::HostBufferRecycler<T>>>,
2509}
2510
2511impl<T> std::fmt::Debug for HostData<T> {
2512    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2513        formatter
2514            .debug_struct("HostData")
2515            .field("pooled", &self.recycler.is_some())
2516            .finish_non_exhaustive()
2517    }
2518}
2519
2520impl<T> HostData<T> {
2521    fn new(data: Vec<T>) -> Self {
2522        Self {
2523            buffer: StorageBuffer::Host(data),
2524            recycler: None,
2525        }
2526    }
2527
2528    fn into_vec(mut self) -> Vec<T> {
2529        self.recycler = None;
2530        match std::mem::replace(&mut self.buffer, StorageBuffer::Host(Vec::new())) {
2531            StorageBuffer::Host(data) => data,
2532            StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2533        }
2534    }
2535
2536    fn as_slice(&self) -> &[T] {
2537        match &self.buffer {
2538            StorageBuffer::Host(data) => data,
2539            StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2540        }
2541    }
2542
2543    fn as_mut_slice(&mut self) -> &mut [T] {
2544        match &mut self.buffer {
2545            StorageBuffer::Host(data) => data,
2546            StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2547        }
2548    }
2549}
2550
2551impl<T> Drop for HostData<T> {
2552    fn drop(&mut self) {
2553        let Some(recycler) = self.recycler.take().and_then(|weak| weak.upgrade()) else {
2554            return;
2555        };
2556        if let StorageBuffer::Host(data) = &mut self.buffer {
2557            recycler.recycle(std::mem::take(data));
2558        }
2559    }
2560}
2561
2562/// The sole owner handle for host tensors. The allocation group owns the
2563/// provider root; the descriptor slot carries only the logical view metadata.
2564pub(crate) struct OwnedTensorGroup<R: TensorRank> {
2565    group: AllocationGroup,
2566    slot: DescriptorSlot,
2567    allocation_index: AllocationSlot,
2568    // INVARIANT: this non-owning address points into the group root, whose host
2569    // vector cannot resize while the owning tensor is borrowed. It is stored as
2570    // `NonZeroUsize` rather than `NonNull<u8>`: the niche keeps the field at one
2571    // word, and unlike `NonNull` it preserves the `Send`/`Sync` auto traits the
2572    // typed tensor contract requires.
2573    host_ptr: Option<NonZeroUsize>,
2574    host_byte_len: usize,
2575    _rank: PhantomData<R>,
2576}
2577
2578impl<R: TensorRank> Debug for OwnedTensorGroup<R> {
2579    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2580        formatter
2581            .debug_struct("OwnedTensorGroup")
2582            .field("slot", &self.slot)
2583            .finish_non_exhaustive()
2584    }
2585}
2586
2587impl<R: TensorRank> OwnedTensorGroup<R> {
2588    fn from_host_vec<T: TensorScalar>(shape: R::Shape, data: Vec<T>) -> crate::Result<Self> {
2589        let (group, slot) = AllocationGroup::from_host_vec::<T, R>(shape, data)
2590            .map_err(|error| group_error("TypedTensor::from_host_vec", error))?;
2591        let allocation_index = group
2592            .allocation_index(slot)
2593            .map_err(|error| group_error("TypedTensor::from_host_vec", error))?;
2594        let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
2595        Ok(Self {
2596            group,
2597            slot,
2598            allocation_index,
2599            host_ptr,
2600            host_byte_len,
2601            _rank: PhantomData,
2602        })
2603    }
2604
2605    fn from_backend_buffer<T: TensorScalar + Send + Sync + 'static>(
2606        shape: R::Shape,
2607        buffer: StorageBuffer<T>,
2608        placement: Placement,
2609    ) -> crate::Result<Self> {
2610        let (mut group, slot) = AllocationGroup::from_backend_buffer::<T, R>(shape, buffer)
2611            .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2612        group
2613            .set_descriptor_placement(slot, placement)
2614            .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2615        let allocation_index = group
2616            .allocation_index(slot)
2617            .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2618        let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
2619        Ok(Self {
2620            group,
2621            slot,
2622            allocation_index,
2623            host_ptr,
2624            host_byte_len,
2625            _rank: PhantomData,
2626        })
2627    }
2628
2629    fn view<T: TensorScalar>(&self) -> crate::Result<GroupReadView<'_, T, R>> {
2630        self.group
2631            .view(self.slot)
2632            .map_err(|error| group_error("TypedTensor::group_view", error))
2633    }
2634
2635    fn view_dyn<T: TensorScalar>(&self) -> crate::Result<GroupReadView<'_, T, DynRank>> {
2636        self.group
2637            .view(self.slot)
2638            .map_err(|error| group_error("TypedTensor::group_view", error))
2639    }
2640
2641    fn view_mut<T: TensorScalar>(&mut self) -> crate::Result<GroupWriteView<'_, T, R>> {
2642        self.group
2643            .view_mut(self.slot)
2644            .map_err(|error| group_error("TypedTensor::group_view_mut", error))
2645    }
2646
2647    fn view_mut_dyn<T: TensorScalar>(&mut self) -> crate::Result<GroupWriteView<'_, T, DynRank>> {
2648        self.group
2649            .view_mut(self.slot)
2650            .map_err(|error| group_error("TypedTensor::group_view_mut", error))
2651    }
2652
2653    fn prepare_device_read_for_layout<T: TensorScalar>(
2654        &self,
2655        layout: &TensorLayout<R>,
2656    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>> {
2657        self.group
2658            .prepare_device_read_for_layout::<T, R>(self.slot, layout)
2659            .map_err(|error| {
2660                crate::Error::runtime_state_source("TypedTensor::prepare_device_read", error)
2661            })
2662    }
2663
2664    fn prepare_device_write_for_layout<T: TensorScalar>(
2665        &mut self,
2666        layout: &TensorLayout<R>,
2667    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>> {
2668        self.group
2669            .prepare_device_write_for_layout::<T, R>(self.slot, layout)
2670            .map_err(|error| {
2671                crate::Error::runtime_state_source("TypedTensor::prepare_device_write", error)
2672            })
2673    }
2674
2675    fn host_buffer<T: 'static>(&self) -> Option<&StorageBuffer<T>> {
2676        self.group.host_buffer_at::<T>(self.allocation_index)
2677    }
2678
2679    fn host_slice<T>(&self) -> crate::Result<&[T]> {
2680        let Some(pointer) = self.host_ptr else {
2681            return Err(crate::Error::runtime_state(
2682                "TypedTensor::host_data",
2683                "backend storage cannot be borrowed as host data; download explicitly first",
2684            ));
2685        };
2686        let element_size = size_of::<T>();
2687        let Some(element_count) = self.host_byte_len.checked_div(element_size) else {
2688            return Err(crate::Error::runtime_state(
2689                "TypedTensor::host_data",
2690                "host allocation byte length is not aligned to the requested dtype",
2691            ));
2692        };
2693        // SAFETY: the pointer and byte length were captured from the unique
2694        // root's full host allocation; the root cannot resize while borrowed.
2695        Ok(unsafe { std::slice::from_raw_parts(pointer.get() as *const T, element_count) })
2696    }
2697
2698    fn host_slice_mut<T>(&mut self) -> crate::Result<&mut [T]> {
2699        let Some(pointer) = self.host_ptr else {
2700            return Err(crate::Error::runtime_state(
2701                "TypedTensor::host_data_mut",
2702                "backend storage cannot be borrowed as host data; download explicitly first",
2703            ));
2704        };
2705        let element_size = size_of::<T>();
2706        let Some(element_count) = self.host_byte_len.checked_div(element_size) else {
2707            return Err(crate::Error::runtime_state(
2708                "TypedTensor::host_data_mut",
2709                "host allocation byte length is not aligned to the requested dtype",
2710            ));
2711        };
2712        // SAFETY: the pointer and byte length were captured from the unique
2713        // root; this method has the only mutable borrow of that root.
2714        Ok(unsafe { std::slice::from_raw_parts_mut(pointer.get() as *mut T, element_count) })
2715    }
2716
2717    fn backend_buffer<T: 'static>(&self) -> Option<&StorageBuffer<T>> {
2718        self.group.backend_buffer::<T>(self.slot)
2719    }
2720
2721    fn backend_buffer_mut<T: 'static>(&mut self) -> Option<&mut StorageBuffer<T>> {
2722        self.group.backend_buffer_mut::<T>(self.slot)
2723    }
2724
2725    // INVARIANT: the failure carrier must return the unchanged group owner, so
2726    // the wide `(Self, Error)` pair is deliberate rather than a boxing bug.
2727    #[allow(clippy::result_large_err)]
2728    fn into_host_vec<T: 'static>(self) -> std::result::Result<Vec<T>, (Self, crate::Error)> {
2729        if self.backend_buffer::<T>().is_some() {
2730            return Err((
2731                self,
2732                crate::Error::runtime_state(
2733                    "TypedTensor::into_host_vec",
2734                    "backend buffers cannot be exported as host Vec; download the tensor first",
2735                ),
2736            ));
2737        }
2738        let OwnedTensorGroup {
2739            group,
2740            slot,
2741            allocation_index,
2742            host_ptr,
2743            host_byte_len,
2744            ..
2745        } = self;
2746        match group.into_host_vec::<T>(slot) {
2747            Ok(data) => Ok(data),
2748            Err((group, error)) => Err((
2749                OwnedTensorGroup {
2750                    group,
2751                    slot,
2752                    allocation_index,
2753                    host_ptr,
2754                    host_byte_len,
2755                    _rank: PhantomData,
2756                },
2757                crate::Error::runtime_state("TypedTensor::into_host_vec", error),
2758            )),
2759        }
2760    }
2761
2762    fn into_parts(self) -> (AllocationGroup, DescriptorSlot) {
2763        (self.group, self.slot)
2764    }
2765
2766    #[allow(clippy::result_large_err)]
2767    fn reinterpret<T: TensorScalar, U: TensorScalar>(
2768        self,
2769        shape: Vec<usize>,
2770        strides: Vec<isize>,
2771        offset: isize,
2772    ) -> Result<OwnedTensorGroup<DynRank>, (Self, crate::Error)> {
2773        let OwnedTensorGroup {
2774            group,
2775            slot,
2776            allocation_index,
2777            host_ptr,
2778            host_byte_len,
2779            _rank: _,
2780        } = self;
2781        match group.reinterpret_descriptor::<T, U>(slot, shape, strides, offset) {
2782            Ok(group) => Ok(OwnedTensorGroup {
2783                group,
2784                slot,
2785                allocation_index,
2786                host_ptr,
2787                host_byte_len,
2788                _rank: PhantomData,
2789            }),
2790            Err((group, error)) => Err((
2791                OwnedTensorGroup {
2792                    group,
2793                    slot,
2794                    allocation_index,
2795                    host_ptr,
2796                    host_byte_len,
2797                    _rank: PhantomData,
2798                },
2799                group_error("TypedTensor::reinterpret", error),
2800            )),
2801        }
2802    }
2803}
2804
2805fn host_metadata<T: TensorScalar>(
2806    group: &AllocationGroup,
2807    slot: DescriptorSlot,
2808) -> (Option<NonZeroUsize>, usize) {
2809    group
2810        .host_root_metadata::<T>(slot)
2811        .map_or((None, 0), |(pointer, byte_len)| {
2812            // INVARIANT: `host_root_metadata` reports the base address of the group's live
2813            // host allocation, which is non-null for any slice, empty included.
2814            (NonZeroUsize::new(pointer), byte_len)
2815        })
2816}
2817
2818fn group_error(op: &'static str, error: GroupError) -> crate::Error {
2819    crate::Error::runtime_state(op, error.to_string())
2820}
2821
2822/// Borrowed tensor buffer reference used by read-only typed views.
2823///
2824/// # Examples
2825///
2826/// ```rust
2827/// use tenferro_tensor::TensorStorageRef;
2828///
2829/// let data = [1_i32, 2];
2830/// let buffer = TensorStorageRef::Host(&data);
2831/// assert_eq!(buffer.len(), 2);
2832/// ```
2833#[derive(Debug)]
2834pub enum TensorStorageRef<'a, T> {
2835    Host(&'a [T]),
2836    Backend(&'a dyn BackendStorage<T>),
2837    #[doc(hidden)]
2838    Root(&'a dyn BackendAllocation),
2839}
2840
2841impl<T> Clone for TensorStorageRef<'_, T> {
2842    fn clone(&self) -> Self {
2843        match self {
2844            Self::Host(data) => Self::Host(data),
2845            Self::Backend(buffer) => Self::Backend(*buffer),
2846            Self::Root(allocation) => Self::Root(*allocation),
2847        }
2848    }
2849}
2850
2851impl<T: 'static> TensorStorageRef<'_, T> {
2852    /// Return the logical length of the backing allocation.
2853    ///
2854    /// # Examples
2855    ///
2856    /// ```rust
2857    /// use tenferro_tensor::TensorStorageRef;
2858    ///
2859    /// let data = [1_i32, 2, 3];
2860    /// assert_eq!(TensorStorageRef::Host(&data).len(), 3);
2861    /// ```
2862    pub fn len(&self) -> usize {
2863        match self {
2864            Self::Host(data) => data.len(),
2865            Self::Backend(buffer) => buffer.len(),
2866            Self::Root(allocation) => allocation
2867                .root_extent()
2868                .byte_len()
2869                .checked_div(std::mem::size_of::<T>())
2870                .unwrap_or(0),
2871        }
2872    }
2873
2874    /// Return whether the backing allocation is empty.
2875    ///
2876    /// # Examples
2877    ///
2878    /// ```rust
2879    /// use tenferro_tensor::TensorStorageRef;
2880    ///
2881    /// let data: [f64; 0] = [];
2882    /// assert!(TensorStorageRef::Host(&data).is_empty());
2883    /// ```
2884    pub fn is_empty(&self) -> bool {
2885        self.len() == 0
2886    }
2887}
2888
2889/// Borrowed tensor buffer reference used by mutable typed views.
2890///
2891/// Backend buffers can be represented for residency metadata, but this crate
2892/// does not expose host mutation for backend-native allocations.
2893///
2894/// # Examples
2895///
2896/// ```rust
2897/// use tenferro_tensor::TensorStorageRefMut;
2898///
2899/// let mut data = [1_i32, 2];
2900/// let buffer = TensorStorageRefMut::Host(&mut data);
2901/// assert_eq!(buffer.len(), 2);
2902/// ```
2903#[derive(Debug)]
2904pub enum TensorStorageRefMut<'a, T> {
2905    Host(&'a mut [T]),
2906    Backend(&'a mut dyn BackendStorage<T>),
2907}
2908
2909impl<T: 'static> TensorStorageRefMut<'_, T> {
2910    /// Return the logical length of the backing allocation.
2911    ///
2912    /// # Examples
2913    ///
2914    /// ```rust
2915    /// use tenferro_tensor::TensorStorageRefMut;
2916    ///
2917    /// let mut data = [1_i32, 2, 3];
2918    /// assert_eq!(TensorStorageRefMut::Host(&mut data).len(), 3);
2919    /// ```
2920    pub fn len(&self) -> usize {
2921        match self {
2922            Self::Host(data) => data.len(),
2923            Self::Backend(buffer) => buffer.len(),
2924        }
2925    }
2926
2927    /// Return whether the backing allocation is empty.
2928    ///
2929    /// # Examples
2930    ///
2931    /// ```rust
2932    /// use tenferro_tensor::TensorStorageRefMut;
2933    ///
2934    /// let mut data: [f64; 0] = [];
2935    /// assert!(TensorStorageRefMut::Host(&mut data).is_empty());
2936    /// ```
2937    pub fn is_empty(&self) -> bool {
2938        self.len() == 0
2939    }
2940}
2941
2942/// Read-only borrowed view of typed tensor storage with arbitrary strides.
2943///
2944/// `TypedTensorView` is the typed representation for layout-only tensor
2945/// transformations. It borrows an existing host or backend allocation and
2946/// carries a logical shape, strides, and an offset. Slicing, reshaping when
2947/// stride-compatible, and [`transpose_view`](TypedTensorView::transpose_view)
2948/// update only metadata and do not copy storage.
2949///
2950/// Materialize through [`TensorStructural::to_contiguous_read`](crate::TensorStructural::to_contiguous_read)
2951/// on the active backend session when a compact owned [`TypedTensor`] is
2952/// required. Use [`TypedTensorView::as_slice`] only when the current view is
2953/// contiguous in the requested layout.
2954///
2955/// # Examples
2956///
2957/// ```rust
2958/// use tenferro_tensor::{Rank, TypedTensorView};
2959///
2960/// let data = [1_i32, 2, 3, 4];
2961/// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
2962/// assert_eq!(view.get(&[1, 1]), Some(&4));
2963/// # Ok::<(), tenferro_tensor::Error>(())
2964/// ```
2965/// Borrowed typed view of one tensor representation.
2966///
2967/// The buffer stays concrete on purpose: a generic-associated buffer would make
2968/// the projection invariant in `'a`, and the borrowed-read surface relies on
2969/// `TensorRead<'long>` shortening to `TensorRead<'short>`. `D` therefore marks
2970/// which representation produced the view - `Host` is only ever built from host
2971/// slices, with no retained region.
2972pub struct TypedTensorView<'a, T, R: TensorRank = DynRank, D: Representation = Dynamic> {
2973    buffer: TensorStorageRef<'a, T>,
2974    root: Option<GroupReadView<'a, T, R>>,
2975    layout: TensorLayout<R>,
2976    placement: Placement,
2977    _representation: std::marker::PhantomData<D>,
2978}
2979
2980impl<'a, T, R: TensorRank, D: Representation> Clone for TypedTensorView<'a, T, R, D> {
2981    fn clone(&self) -> Self {
2982        Self {
2983            buffer: self.buffer.clone(),
2984            root: self.root.clone(),
2985            layout: self.layout.clone(),
2986            placement: self.placement.clone(),
2987            _representation: std::marker::PhantomData,
2988        }
2989    }
2990}
2991
2992impl<'a, T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensorView<'a, T, R, D> {
2993    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2994        formatter
2995            .debug_struct("TypedTensorView")
2996            .field("shape", &self.layout.shape())
2997            .field("placement", &self.placement)
2998            .finish_non_exhaustive()
2999    }
3000}
3001
3002impl<'a, T: 'static> TypedTensorView<'a, T, DynRank> {
3003    /// Create a borrowed dynamic-rank view over compact column-major host data.
3004    ///
3005    /// # Examples
3006    ///
3007    /// ```rust
3008    /// use tenferro_tensor::TypedTensorView;
3009    ///
3010    /// let data = [1_i32, 2, 3, 4];
3011    /// let view = TypedTensorView::from_col_major(&[2, 2], &data)?;
3012    /// assert_eq!(view.strides(), &[1, 2]);
3013    /// # Ok::<(), tenferro_tensor::Error>(())
3014    /// ```
3015    ///
3016    /// # Errors
3017    ///
3018    /// Returns [`crate::Error::Validation`] with
3019    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when compact
3020    /// strides or reachable bounds overflow, or
3021    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3022    /// requested shape reaches beyond `data`.
3023    pub fn from_col_major(shape: &[usize], data: &'a [T]) -> crate::Result<Self> {
3024        let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
3025            .map_err(|err| tensor_layout_error("TypedTensorView::from_col_major", err))?;
3026        Self::from_buffer_ref(
3027            shape_vec(layout.shape()),
3028            stride_vec(layout.strides()),
3029            layout.offset(),
3030            TensorStorageRef::Host(data),
3031            default_placement(),
3032            "TypedTensorView::from_col_major",
3033        )
3034    }
3035
3036    /// Create a borrowed host view from explicit layout metadata.
3037    ///
3038    /// # Examples
3039    ///
3040    /// ```rust
3041    /// use tenferro_tensor::TypedTensorView;
3042    ///
3043    /// let data = [1_i32, 2, 3];
3044    /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
3045    /// assert_eq!(view.get(&[2]), Some(&1));
3046    /// # Ok::<(), tenferro_tensor::Error>(())
3047    /// ```
3048    ///
3049    /// # Errors
3050    ///
3051    /// Returns [`crate::Error::Validation`] with
3052    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
3053    /// `strides` have different ranks,
3054    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3055    /// reachable layout exceeds `data`, or
3056    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
3057    /// arithmetic overflows.
3058    pub fn from_slice(
3059        shape: impl AsRef<[usize]>,
3060        strides: impl AsRef<[isize]>,
3061        offset: isize,
3062        data: &'a [T],
3063    ) -> crate::Result<Self> {
3064        Self::from_buffer_ref(
3065            shape_vec(shape.as_ref()),
3066            stride_vec(strides.as_ref()),
3067            offset,
3068            TensorStorageRef::Host(data),
3069            default_placement(),
3070            "TypedTensorView::from_slice",
3071        )
3072    }
3073}
3074
3075impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
3076    /// Create a rank-generic borrowed host view from explicit layout metadata.
3077    ///
3078    /// # Examples
3079    ///
3080    /// ```rust
3081    /// use tenferro_tensor::{Rank, TypedTensorView};
3082    ///
3083    /// let data = [1_i32, 2, 3, 4];
3084    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
3085    /// assert_eq!(view.get(&[1, 1]), Some(&4));
3086    /// # Ok::<(), tenferro_tensor::Error>(())
3087    /// ```
3088    ///
3089    /// # Errors
3090    ///
3091    /// Returns [`crate::Error::Validation`] with
3092    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
3093    /// rank does not match `shape` or `strides`,
3094    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3095    /// reachable layout exceeds `data`, or
3096    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
3097    /// arithmetic overflows.
3098    pub fn from_slice_ranked(
3099        shape: impl Into<R::Shape>,
3100        strides: impl Into<R::Strides>,
3101        offset: isize,
3102        data: &'a [T],
3103    ) -> crate::Result<Self> {
3104        Self::from_buffer_ref(
3105            shape,
3106            strides,
3107            offset,
3108            TensorStorageRef::Host(data),
3109            default_placement(),
3110            "TypedTensorView::from_slice_ranked",
3111        )
3112    }
3113}
3114
3115impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
3116    fn from_buffer_ref(
3117        shape: impl Into<R::Shape>,
3118        strides: impl Into<R::Strides>,
3119        offset: isize,
3120        buffer: TensorStorageRef<'a, T>,
3121        placement: Placement,
3122        op: &'static str,
3123    ) -> crate::Result<Self> {
3124        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
3125            .map_err(|err| tensor_layout_error(op, err))?;
3126        Ok(Self {
3127            buffer,
3128            root: None,
3129            layout,
3130            placement,
3131            _representation: std::marker::PhantomData,
3132        })
3133    }
3134}
3135
3136impl<'a, T: 'static, R: TensorRank> TypedTensorView<'a, T, R, Host> {
3137    /// Create a representation-marked view over an explicit host layout.
3138    ///
3139    /// A `Host`-marked view is only ever built from a host slice, so it carries
3140    /// no retained region and its host slice needs no runtime check.
3141    ///
3142    /// # Examples
3143    ///
3144    /// ```rust
3145    /// use tenferro_tensor::{DynRank, Host, TypedTensorView};
3146    ///
3147    /// let data = [1_i32, 2, 3, 4];
3148    /// let view: TypedTensorView<'_, i32, DynRank, Host> =
3149    ///     TypedTensorView::from_host_slice(vec![2, 2].as_slice(), vec![1, 2].as_slice(), 0, &data)?;
3150    /// assert_eq!(view.as_host_slice(), &[1, 2, 3, 4]);
3151    /// # Ok::<(), tenferro_tensor::Error>(())
3152    /// ```
3153    ///
3154    /// # Errors
3155    ///
3156    /// Returns [`crate::Error::Validation`] with
3157    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `strides`
3158    /// has a different rank, or
3159    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] /
3160    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
3161    /// reachable layout leaves `data` or overflows.
3162    pub fn from_host_slice(
3163        shape: impl Into<R::Shape>,
3164        strides: impl Into<R::Strides>,
3165        offset: isize,
3166        data: &'a [T],
3167    ) -> crate::Result<Self> {
3168        // Built here rather than through the shared helper so that the Host
3169        // marker can only ever be paired with a host slice.
3170        let buffer = TensorStorageRef::Host(data);
3171        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
3172            .map_err(|err| tensor_layout_error("TypedTensorView::from_host_slice", err))?;
3173        Ok(Self {
3174            buffer,
3175            root: None,
3176            layout,
3177            placement: default_placement(),
3178            _representation: std::marker::PhantomData,
3179        })
3180    }
3181
3182    /// Borrow the host elements without a runtime representation check.
3183    ///
3184    /// # Examples
3185    ///
3186    /// ```rust
3187    /// use tenferro_tensor::{DynRank, Host, TypedTensorView};
3188    ///
3189    /// let data = [1_i32, 2];
3190    /// let view: TypedTensorView<'_, i32, DynRank, Host> =
3191    ///     TypedTensorView::from_host_slice(vec![2].as_slice(), vec![1].as_slice(), 0, &data)?;
3192    /// assert_eq!(view.as_host_slice(), &[1, 2]);
3193    /// # Ok::<(), tenferro_tensor::Error>(())
3194    /// ```
3195    ///
3196    /// # Panics
3197    ///
3198    /// Panics only if a `Host`-marked view was built from non-host storage,
3199    /// which no constructor in this crate does.
3200    pub fn as_host_slice(&self) -> &'a [T] {
3201        match &self.buffer {
3202            TensorStorageRef::Host(data) => data,
3203            // INVARIANT: `Host`-marked views are constructed only by
3204            // `from_host_slice` and by `TypedTensor<_, _, Host>::as_view`.
3205            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3206                unreachable!("a Host-marked view always borrows host storage")
3207            }
3208        }
3209    }
3210}
3211
3212impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
3213    /// Erase a borrowed view's rank and dtype for session dispatch without
3214    /// allocating tensor storage or promoting it into an allocation group.
3215    /// Common ranks use the existing inline shape and stride metadata.
3216    ///
3217    /// # Examples
3218    ///
3219    /// ```rust
3220    /// use tenferro_tensor::{Rank, TypedTensorView};
3221    /// let data = [1.0_f64, 2.0];
3222    /// let view = TypedTensorView::<_, Rank<1>>::from_slice_ranked([2], [1], 0, &data)?;
3223    /// let read = view.into_tensor_read()?;
3224    /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
3225    /// # Ok::<(), tenferro_tensor::Error>(())
3226    /// ```
3227    /// # Errors
3228    /// Returns [`crate::Error::Validation`] with
3229    /// [`ValidationError::IntegerOverflow`] for unrepresentable metadata or
3230    /// [`ValidationError::ViewOutOfBounds`] if the layout leaves its allocation.
3231    pub fn into_tensor_read(self) -> crate::Result<TensorRead<'a>>
3232    where
3233        T: TensorScalar,
3234    {
3235        let layout = TensorLayout::<DynRank>::from_parts(
3236            shape_vec(self.layout.shape()),
3237            stride_vec(self.layout.strides()),
3238            self.layout.offset(),
3239            self.buffer.len(),
3240        )
3241        .map_err(|err| tensor_layout_error("TypedTensorView::into_tensor_read", err))?;
3242        let view = TypedTensorView {
3243            buffer: self.buffer,
3244            root: self.root.map(GroupReadView::into_dyn),
3245            layout,
3246            placement: self.placement,
3247            _representation: std::marker::PhantomData,
3248        };
3249        Ok(TensorRead::from_view(T::tensor_view(view)))
3250    }
3251
3252    /// Return the logical shape.
3253    ///
3254    /// # Examples
3255    ///
3256    /// ```rust
3257    /// use tenferro_tensor::TypedTensorView;
3258    ///
3259    /// let data = [0_i32; 2];
3260    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3261    /// assert_eq!(view.shape(), &[2]);
3262    /// # Ok::<(), tenferro_tensor::Error>(())
3263    /// ```
3264    pub fn shape(&self) -> &[usize] {
3265        self.layout.shape()
3266    }
3267
3268    /// Return the logical rank carried by this view.
3269    ///
3270    /// # Examples
3271    ///
3272    /// ```
3273    /// use tenferro_tensor::TypedTensorView;
3274    ///
3275    /// let data = [0_i32; 6];
3276    /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
3277    /// assert_eq!(view.rank(), 2);
3278    /// # Ok::<(), tenferro_tensor::Error>(())
3279    /// ```
3280    pub fn rank(&self) -> usize {
3281        self.shape().len()
3282    }
3283
3284    /// Return strides in element units.
3285    ///
3286    /// # Examples
3287    ///
3288    /// ```rust
3289    /// use tenferro_tensor::TypedTensorView;
3290    ///
3291    /// let data = [0_i32; 2];
3292    /// let view = TypedTensorView::from_slice(vec![2], vec![-1], 1, &data)?;
3293    /// assert_eq!(view.strides(), &[-1]);
3294    /// # Ok::<(), tenferro_tensor::Error>(())
3295    /// ```
3296    pub fn strides(&self) -> &[isize] {
3297        self.layout.strides()
3298    }
3299
3300    /// Return the physical element offset.
3301    ///
3302    /// # Examples
3303    ///
3304    /// ```rust
3305    /// use tenferro_tensor::TypedTensorView;
3306    ///
3307    /// let data = [1_i32, 2];
3308    /// let view = TypedTensorView::from_slice(vec![1], vec![1], 1, &data)?;
3309    /// assert_eq!(view.offset(), 1);
3310    /// # Ok::<(), tenferro_tensor::Error>(())
3311    /// ```
3312    pub fn offset(&self) -> isize {
3313        self.layout.offset()
3314    }
3315
3316    /// Return the borrowed host storage backing this view.
3317    ///
3318    /// This exposes the entire backing host allocation, not just the logical
3319    /// slice covered by this view. Use [`TypedTensorView::as_slice`] when the
3320    /// caller needs the contiguous logical region instead.
3321    ///
3322    /// # Examples
3323    ///
3324    /// ```rust
3325    /// use tenferro_tensor::TypedTensorView;
3326    ///
3327    /// let data = [1_i32, 2];
3328    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3329    /// assert_eq!(view.host_storage()?, &[1, 2]);
3330    /// # Ok::<(), tenferro_tensor::Error>(())
3331    /// ```
3332    ///
3333    /// # Errors
3334    ///
3335    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
3336    /// buffer; backend storage must be downloaded before host inspection.
3337    pub fn host_storage(&self) -> crate::Result<&'a [T]> {
3338        match &self.buffer {
3339            TensorStorageRef::Host(data) => Ok(data),
3340            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3341                Err(crate::Error::runtime_state(
3342                    "TypedTensorView::host_storage",
3343                    "backend buffers cannot expose host storage; download explicitly first",
3344                ))
3345            }
3346        }
3347    }
3348
3349    /// Return the number of logical elements in this view.
3350    ///
3351    /// # Examples
3352    ///
3353    /// ```rust
3354    /// use tenferro_tensor::TypedTensorView;
3355    ///
3356    /// let data = [0_i32; 6];
3357    /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
3358    /// assert_eq!(view.n_elements(), 6);
3359    /// # Ok::<(), tenferro_tensor::Error>(())
3360    /// ```
3361    pub fn n_elements(&self) -> usize {
3362        // Invariant: public view constructors validate logical element count.
3363        match checked_view_element_count(self.shape(), "TypedTensorView::n_elements") {
3364            Ok(n) => n,
3365            Err(err) => {
3366                unreachable!("TypedTensorView layout shape is validated at construction: {err}")
3367            }
3368        }
3369    }
3370
3371    /// Return layout metadata for this view.
3372    ///
3373    /// # Examples
3374    ///
3375    /// ```rust
3376    /// use tenferro_tensor::TypedTensorView;
3377    ///
3378    /// let data = [1_i32, 2];
3379    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3380    /// assert!(view.layout().is_compact_col_major().unwrap());
3381    /// # Ok::<(), tenferro_tensor::Error>(())
3382    /// ```
3383    pub fn layout(&self) -> &TensorLayout<R> {
3384        &self.layout
3385    }
3386
3387    /// Return placement metadata for this view.
3388    ///
3389    /// # Examples
3390    ///
3391    /// ```rust
3392    /// use tenferro_tensor::{MemoryKind, TypedTensorView};
3393    ///
3394    /// let data = [1_i32];
3395    /// let view = TypedTensorView::from_slice(vec![1], vec![1], 0, &data)?;
3396    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
3397    /// # Ok::<(), tenferro_tensor::Error>(())
3398    /// ```
3399    pub fn placement(&self) -> &Placement {
3400        &self.placement
3401    }
3402
3403    /// Return the backend allocation for backend integrations.
3404    #[doc(hidden)]
3405    pub fn backing_len(&self) -> usize {
3406        self.buffer.len()
3407    }
3408
3409    /// Return the backend allocation for backend integrations.
3410    #[doc(hidden)]
3411    pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>> {
3412        match &self.buffer {
3413            TensorStorageRef::Host(_) => None,
3414            TensorStorageRef::Backend(buffer) => Some(*buffer),
3415            TensorStorageRef::Root(_) => {
3416                self.root
3417                    .as_ref()?
3418                    .backend_buffer()
3419                    .and_then(|buffer| match buffer {
3420                        StorageBuffer::Host(_) => None,
3421                        StorageBuffer::Backend(buffer) => Some(buffer.as_ref()),
3422                    })
3423            }
3424        }
3425    }
3426
3427    /// Return the provider family for this view when it is backend-owned.
3428    #[doc(hidden)]
3429    pub fn backend_family(&self) -> Option<&'static str>
3430    where
3431        T: TensorScalar + 'static,
3432    {
3433        self.root
3434            .as_ref()
3435            .and_then(|root| {
3436                root.backend_allocation()
3437                    .map(|_| root.provider_kind().as_str())
3438            })
3439            .or_else(|| self.backend_buffer().map(|buffer| buffer.backend_family()))
3440    }
3441
3442    /// Return the shared allocation domain for this view when backend-owned.
3443    #[doc(hidden)]
3444    pub fn allocation_domain(&self) -> Option<AllocationDomainId>
3445    where
3446        T: TensorScalar + 'static,
3447    {
3448        self.root
3449            .as_ref()
3450            .and_then(|root| root.backend_identity().map(|(domain, _)| domain))
3451            .or_else(|| {
3452                self.backend_buffer()
3453                    .and_then(|buffer| buffer.allocation_domain())
3454            })
3455    }
3456
3457    /// Return the physical allocation identity for this view when backend-owned.
3458    #[doc(hidden)]
3459    pub fn allocation_id(&self) -> Option<AllocationId>
3460    where
3461        T: TensorScalar + 'static,
3462    {
3463        self.root
3464            .as_ref()
3465            .and_then(|root| root.backend_identity().map(|(_, id)| id))
3466            .or_else(|| {
3467                self.backend_buffer()
3468                    .and_then(|buffer| buffer.allocation_id())
3469            })
3470    }
3471
3472    /// Prepare this backend view for one provider-native read binding.
3473    #[doc(hidden)]
3474    pub fn prepare_device_read(
3475        &self,
3476        op: &'static str,
3477    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
3478    where
3479        T: TensorScalar + 'static,
3480    {
3481        if let Some(root) = &self.root {
3482            return root
3483                .prepare_device_read_for_layout(&self.layout)
3484                .map_err(|error| crate::Error::runtime_state_source(op, error));
3485        }
3486        let buffer = self.backend_buffer().ok_or_else(|| {
3487            crate::Error::runtime_state_source(
3488                op,
3489                crate::AccessError::Unsupported { backend: "host" },
3490            )
3491        })?;
3492        prepare_backend_access(buffer, &self.layout, op)
3493    }
3494
3495    /// Compute the physical element offset for a logical index.
3496    ///
3497    /// # Examples
3498    ///
3499    /// ```rust
3500    /// use tenferro_tensor::TypedTensorView;
3501    ///
3502    /// let data = [1_i32, 2, 3];
3503    /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
3504    /// assert_eq!(view.linear_offset(&[2]), Some(0));
3505    /// # Ok::<(), tenferro_tensor::Error>(())
3506    /// ```
3507    pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
3508        checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
3509    }
3510
3511    /// Compute the physical element offset for a logical index, returning a typed error.
3512    ///
3513    /// # Examples
3514    ///
3515    /// ```rust
3516    /// use tenferro_tensor::TypedTensorView;
3517    ///
3518    /// let data = [1_i32, 2, 3];
3519    /// let view = TypedTensorView::from_slice([3], [-1], 2, &data)?;
3520    /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
3521    /// # Ok::<(), tenferro_tensor::Error>(())
3522    /// ```
3523    ///
3524    /// # Errors
3525    ///
3526    /// Returns [`crate::Error::Validation`] with
3527    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
3528    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
3529    /// when an index is outside its axis extent, or
3530    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
3531    /// arithmetic overflows.
3532    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
3533        checked_view_offset_result(
3534            self.shape(),
3535            self.strides(),
3536            self.offset(),
3537            indices,
3538            "TypedTensorView::layout_linear_offset",
3539        )
3540    }
3541
3542    /// Return whether this view is compact column-major.
3543    ///
3544    /// # Examples
3545    ///
3546    /// ```rust
3547    /// use tenferro_tensor::TypedTensorView;
3548    ///
3549    /// let data = [1_i32, 2];
3550    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3551    /// assert!(view.is_col_major_contiguous()?);
3552    /// # Ok::<(), tenferro_tensor::Error>(())
3553    /// ```
3554    ///
3555    /// # Errors
3556    ///
3557    /// Returns [`crate::Error::Validation`] with
3558    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] if compactness
3559    /// arithmetic overflows.
3560    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
3561        self.layout
3562            .is_compact_col_major()
3563            .map_err(|err| tensor_layout_error("TypedTensorView::is_col_major_contiguous", err))
3564    }
3565
3566    /// Return a compact string summary of this view's layout metadata.
3567    ///
3568    /// # Examples
3569    ///
3570    /// ```rust
3571    /// use tenferro_tensor::TypedTensorView;
3572    ///
3573    /// let data = [1_i32, 2];
3574    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3575    /// assert!(view.layout_summary().contains("shape=[2]"));
3576    /// # Ok::<(), tenferro_tensor::Error>(())
3577    /// ```
3578    pub fn layout_summary(&self) -> String {
3579        layout_summary(self.shape(), self.strides(), self.offset())
3580    }
3581
3582    /// Assert this view is compact column-major.
3583    ///
3584    /// # Examples
3585    ///
3586    /// ```rust
3587    /// use tenferro_tensor::TypedTensorView;
3588    ///
3589    /// let data = [1_i32, 2];
3590    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3591    /// view.assert_col_major_contiguous()?;
3592    /// # Ok::<(), tenferro_tensor::Error>(())
3593    /// ```
3594    ///
3595    /// # Errors
3596    ///
3597    /// Returns [`crate::Error::Validation`] with
3598    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
3599    /// compactness arithmetic overflows, or
3600    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
3601    /// view is not compact column-major.
3602    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
3603        assert_layout_col_major_contiguous(
3604            self.is_col_major_contiguous()?,
3605            self.shape(),
3606            self.strides(),
3607            self.offset(),
3608            "TypedTensorView::assert_col_major_contiguous",
3609        )
3610    }
3611
3612    /// Borrow one host element by logical index.
3613    ///
3614    /// Returns `None` for out-of-bounds indices and backend buffers.
3615    ///
3616    /// # Examples
3617    ///
3618    /// ```rust
3619    /// use tenferro_tensor::TypedTensorView;
3620    ///
3621    /// let data = [1_i32, 2];
3622    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3623    /// assert_eq!(view.get(&[1]), Some(&2));
3624    /// # Ok::<(), tenferro_tensor::Error>(())
3625    /// ```
3626    pub fn get(&self, indices: &[usize]) -> Option<&T> {
3627        let offset = self.linear_offset(indices)?;
3628        match &self.buffer {
3629            TensorStorageRef::Host(data) => data.get(offset),
3630            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => None,
3631        }
3632    }
3633
3634    /// Borrow the contiguous host slice covered by this view.
3635    ///
3636    /// Returns an explicit error for backend buffers and for non-contiguous
3637    /// layouts. This method never downloads or materializes backend data.
3638    ///
3639    /// # Examples
3640    ///
3641    /// ```rust
3642    /// use tenferro_tensor::TypedTensorView;
3643    ///
3644    /// let data = [1_i32, 2, 3];
3645    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?;
3646    /// assert_eq!(view.as_slice()?, &[2, 3]);
3647    /// # Ok::<(), tenferro_tensor::Error>(())
3648    /// ```
3649    ///
3650    /// # Errors
3651    ///
3652    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
3653    /// buffer, [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]
3654    /// when the layout is not compact column-major,
3655    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the view
3656    /// offset is negative, or [`crate::Error::Validation`] with
3657    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] or
3658    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
3659    /// requested host range is invalid.
3660    pub fn as_slice(&self) -> crate::Result<&'a [T]> {
3661        let data = match &self.buffer {
3662            TensorStorageRef::Host(data) => data,
3663            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3664                return Err(crate::Error::runtime_state(
3665                    "TypedTensorView::as_slice",
3666                    "backend buffers cannot be inspected as host slices; download explicitly first",
3667                ))
3668            }
3669        };
3670        contiguous_layout_slice(self.layout(), data, "TypedTensorView::as_slice")
3671    }
3672
3673    /// Borrow the compact logical region through a scoped host mapping.
3674    ///
3675    /// Backend integrations must validate placement and allocation-domain ownership
3676    /// before mapping. The guard cannot escape the callback; no transfer is performed.
3677    ///
3678    /// # Errors
3679    ///
3680    /// Returns a host-access or provider-preparation error when mapping fails,
3681    /// or a validation/unsupported error for a non-contiguous or invalid layout.
3682    #[doc(hidden)]
3683    pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
3684    where
3685        T: TensorScalar + 'static,
3686    {
3687        const OP: &str = "TypedTensorView::with_host_read";
3688        if !self.is_col_major_contiguous()? {
3689            return Err(crate::Error::unsupported(
3690                OP,
3691                "host guard access requires a compact descriptor",
3692            ));
3693        }
3694        if let Some(buffer) = self.backend_buffer() {
3695            let guard = buffer
3696                .map_read()
3697                .map_err(|error| crate::Error::host_access(OP, error))?;
3698            return Ok(f(contiguous_layout_slice(self.layout(), &guard, OP)?));
3699        }
3700        if let Some(root) = &self.root {
3701            let prepared = root
3702                .prepare_host_read_for_layout(self.layout())
3703                .map_err(|error| crate::Error::runtime_state_source(OP, error))?;
3704            let slice = prepared.as_slice().ok_or_else(|| {
3705                crate::Error::unsupported(OP, "host guard access requires a compact descriptor")
3706            })?;
3707            return Ok(f(slice));
3708        }
3709        Ok(f(self.as_slice()?))
3710    }
3711
3712    /// Explicitly duplicate a compact host view into a new owner.
3713    ///
3714    /// Backend views require an explicit provider canonicalization or download
3715    /// boundary; this method never transfers or materializes them implicitly.
3716    ///
3717    /// # Examples
3718    ///
3719    /// ```
3720    /// use tenferro_tensor::TypedTensorView;
3721    ///
3722    /// let data = [1_i32, 2];
3723    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3724    /// let copy = view.duplicate()?;
3725    /// assert_eq!(copy.as_slice()?, &[1, 2]);
3726    /// # Ok::<(), tenferro_tensor::Error>(())
3727    /// ```
3728    ///
3729    /// # Errors
3730    ///
3731    /// Returns [`crate::Error::HostAccess`] or
3732    /// [`ValidationError::NonContiguousViewAsSlice`] when the view is backend
3733    /// owned or not contiguous, and [`ValidationError::InvalidArgument`] when
3734    /// the static-rank shape cannot be reconstructed.
3735    pub fn duplicate(&self) -> crate::Result<TypedTensor<T, R>>
3736    where
3737        T: Clone,
3738    {
3739        let data = self.as_slice()?.to_vec();
3740        let shape = R::shape_from_vec(shape_vec(self.shape()))
3741            .map_err(|err| tensor_layout_error("TypedTensorView::duplicate", err))?;
3742        let mut tensor = TypedTensor::from_vec_col_major(shape, data)?;
3743        tensor.set_placement(self.placement.clone());
3744        Ok(tensor)
3745    }
3746
3747    /// Explicitly copy a host view, including strided/offset views, to a compact owner.
3748    ///
3749    /// # Examples
3750    /// ```
3751    /// use tenferro_tensor::{Rank, TypedTensorView};
3752    /// let storage = [1, 4, 2, 5, 3, 6];
3753    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked(
3754    ///     [2, 3], [1, 2], 0, &storage,
3755    /// )?;
3756    /// let transposed = view.transpose_view([1, 0])?;
3757    /// assert_eq!(transposed.to_col_major()?.as_slice()?, &[1, 2, 3, 4, 5, 6]);
3758    /// # Ok::<(), tenferro_tensor::Error>(())
3759    /// ```
3760    /// # Errors
3761    /// Backend-only storage returns [`crate::Error::RuntimeState`]; invalid
3762    /// layout or rank conversion returns [`crate::Error::Validation`].
3763    pub fn to_col_major(&self) -> crate::Result<TypedTensor<T, R>>
3764    where
3765        T: Clone,
3766    {
3767        let data = self.host_storage()?;
3768        let mut coordinates = shape_vec_zeros(self.shape().len());
3769        let element_count = self.n_elements();
3770        let mut copied = Vec::with_capacity(element_count);
3771        for _ in 0..element_count {
3772            let offset = self.layout_linear_offset(&coordinates)?;
3773            let value = data.get(offset).ok_or_else(|| {
3774                crate::Error::validation(
3775                    "TypedTensorView::to_col_major",
3776                    ValidationError::ViewOutOfBounds,
3777                )
3778            })?;
3779            copied.push(value.clone());
3780            for axis in 0..coordinates.len() {
3781                coordinates[axis] += 1;
3782                if coordinates[axis] < self.shape()[axis] {
3783                    break;
3784                }
3785                coordinates[axis] = 0;
3786            }
3787        }
3788        let shape = R::shape_from_vec(shape_vec(self.shape()))
3789            .map_err(|err| tensor_layout_error("TypedTensorView::to_col_major", err))?;
3790        let mut tensor = TypedTensor::from_vec_col_major(shape, copied)?;
3791        tensor.set_placement(self.placement.clone());
3792        Ok(tensor)
3793    }
3794
3795    /// Return a metadata-only axis permutation.
3796    ///
3797    /// # Examples
3798    ///
3799    /// ```rust
3800    /// use tenferro_tensor::{Rank, TypedTensorView};
3801    ///
3802    /// let data = [1_i32, 2, 3, 4, 5, 6];
3803    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 3], [1, 2], 0, &data)?;
3804    /// let transposed = view.transpose_view([1, 0])?;
3805    /// assert_eq!(transposed.shape(), &[3, 2]);
3806    /// # Ok::<(), tenferro_tensor::Error>(())
3807    /// ```
3808    /// # Errors
3809    ///
3810    /// Returns [`crate::Error::Validation`] with
3811    /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
3812    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
3813    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
3814    /// not a valid permutation of the view rank.
3815    pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
3816        let layout = self
3817            .layout
3818            .transpose_view(axes)
3819            .map_err(|err| tensor_layout_error("TypedTensorView::transpose_view", err))?;
3820        Ok(Self {
3821            buffer: self.buffer.clone(),
3822            root: self.root.clone(),
3823            layout,
3824            placement: self.placement.clone(),
3825            _representation: std::marker::PhantomData,
3826        })
3827    }
3828
3829    /// Return a metadata-only slice using one [`StridedSliceSpec`] per axis.
3830    ///
3831    /// # Examples
3832    ///
3833    /// ```rust
3834    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
3835    ///
3836    /// let data = [1_i32, 2, 3];
3837    /// let view = TypedTensorView::from_slice(vec![3], vec![1], 0, &data)?;
3838    /// let reversed = view.slice_view(&[StridedSliceSpec::reverse()])?;
3839    /// assert_eq!(reversed.get(&[0]), Some(&3));
3840    /// # Ok::<(), tenferro_tensor::Error>(())
3841    /// ```
3842    /// # Errors
3843    ///
3844    /// Returns [`crate::Error::Validation`] with
3845    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
3846    /// count differs from the view rank,
3847    /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
3848    /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
3849    /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3850    /// for slice arithmetic overflow, or
3851    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3852    /// resulting layout exceeds the backing buffer.
3853    pub fn slice_view(&self, slices: &[StridedSliceSpec]) -> crate::Result<Self> {
3854        let specs = core_slice_specs(slices, self.shape(), "TypedTensorView::slice_view")?;
3855        let layout = self
3856            .layout
3857            .slice_view(specs, self.buffer.len())
3858            .map_err(|err| tensor_layout_error("TypedTensorView::slice_view", err))?;
3859        Ok(Self {
3860            buffer: self.buffer.clone(),
3861            root: self.root.clone(),
3862            layout,
3863            placement: self.placement.clone(),
3864            _representation: std::marker::PhantomData,
3865        })
3866    }
3867
3868    /// Return a metadata-only slice along one axis.
3869    ///
3870    /// # Examples
3871    ///
3872    /// ```rust
3873    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
3874    ///
3875    /// let data = [1_i32, 2, 3, 4];
3876    /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
3877    /// assert_eq!(view.slice_axis_view(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
3878    /// # Ok::<(), tenferro_tensor::Error>(())
3879    /// ```
3880    /// # Errors
3881    ///
3882    /// Returns [`crate::Error::Validation`] with
3883    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
3884    /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
3885    /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
3886    /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3887    /// for slice arithmetic overflow, or
3888    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3889    /// resulting layout exceeds the backing buffer.
3890    pub fn slice_axis_view(&self, axis: usize, slice: StridedSliceSpec) -> crate::Result<Self> {
3891        let slices = slice_axis_specs(
3892            self.shape().len(),
3893            axis,
3894            slice,
3895            "TypedTensorView::slice_axis_view",
3896        )?;
3897        self.slice_view(&slices)
3898    }
3899
3900    /// Return a metadata-only dynamic-rank reshape for contiguous column-major views.
3901    ///
3902    /// # Examples
3903    ///
3904    /// ```rust
3905    /// use tenferro_tensor::TypedTensorView;
3906    ///
3907    /// let data = [1_i32, 2, 3, 4];
3908    /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
3909    /// assert_eq!(view.reshape_view(&[4])?.shape(), &[4]);
3910    /// # Ok::<(), tenferro_tensor::Error>(())
3911    /// ```
3912    /// # Errors
3913    ///
3914    /// Returns [`crate::Error::Validation`] with
3915    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
3916    /// the source is not compact column-major,
3917    /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
3918    /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
3919    /// records the counts) when element counts differ,
3920    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
3921    /// arithmetic overflow, or
3922    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3923    /// reshaped view exceeds the backing buffer.
3924    pub fn reshape_view(
3925        &self,
3926        shape: &[usize],
3927    ) -> crate::Result<TypedTensorView<'a, T, DynRank, D>> {
3928        let layout = reshape_layout_dyn(
3929            &self.layout,
3930            shape,
3931            self.buffer.len(),
3932            "TypedTensorView::reshape_view",
3933        )?;
3934        Ok(TypedTensorView {
3935            buffer: self.buffer.clone(),
3936            root: self.root.as_ref().map(GroupReadView::clone_dyn),
3937            layout,
3938            placement: self.placement.clone(),
3939            _representation: std::marker::PhantomData,
3940        })
3941    }
3942}
3943
3944impl<'a, R: TensorRank> TypedTensorView<'a, Complex32, R> {
3945    /// Borrow this complex view as an interleaved real view without copying.
3946    ///
3947    /// The result has dynamic rank because reinterpretation prepends the
3948    /// component axis `[2, ...]`. Only `Complex32 <-> f32` is sealed in this
3949    /// API; this is representation reinterpretation, not numeric conversion.
3950    ///
3951    /// # Examples
3952    ///
3953    /// ```
3954    /// use tenferro_tensor::{Complex32, TypedTensorView};
3955    ///
3956    /// let data = [Complex32::new(1.0, 2.0)];
3957    /// let view = TypedTensorView::from_col_major(&[1], &data)?;
3958    /// let real = view.as_real_view()?;
3959    /// assert_eq!(real.shape(), &[2, 1]);
3960    /// assert_eq!(real.as_slice()?, &[1.0_f32, 2.0]);
3961    /// # Ok::<(), tenferro_tensor::Error>(())
3962    /// ```
3963    ///
3964    /// # Errors
3965    ///
3966    /// Returns an error when the view layout is not a valid sealed
3967    /// representation or when backend reinterpretation is unsupported.
3968    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'a, f32, DynRank>> {
3969        let op = "TypedTensorView::as_real_view";
3970        validate_representation_pair(op, DType::C32, DType::F32)?;
3971        let layout = reinterpret_complex_to_real_layout(
3972            self.shape(),
3973            self.strides(),
3974            self.offset(),
3975            self.buffer.len(),
3976            op,
3977        )?;
3978        let buffer = match &self.buffer {
3979            TensorStorageRef::Host(data) => {
3980                TensorStorageRef::Host(reinterpret_host_slice::<Complex32, f32>(data, op)?)
3981            }
3982            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3983                return Err(crate::Error::unsupported(
3984                    op,
3985                    "backend representation reinterpretation is enabled by the provider phases",
3986                ))
3987            }
3988        };
3989        Ok(TypedTensorView {
3990            buffer,
3991            root: None,
3992            layout,
3993            placement: self.placement.clone(),
3994            _representation: std::marker::PhantomData,
3995        })
3996    }
3997}
3998
3999impl<'a, R: TensorRank> TypedTensorView<'a, Complex64, R> {
4000    /// Borrow this complex view as an interleaved real view without copying.
4001    ///
4002    /// The result has dynamic rank because reinterpretation prepends the
4003    /// component axis `[2, ...]`. Only `Complex64 <-> f64` is sealed in this
4004    /// API; this is representation reinterpretation, not numeric conversion.
4005    ///
4006    /// # Examples
4007    ///
4008    /// ```
4009    /// use tenferro_tensor::{Complex64, TypedTensorView};
4010    ///
4011    /// let data = [Complex64::new(1.0, 2.0)];
4012    /// let view = TypedTensorView::from_col_major(&[1], &data)?;
4013    /// let real = view.as_real_view()?;
4014    /// assert_eq!(real.shape(), &[2, 1]);
4015    /// assert_eq!(real.as_slice()?, &[1.0_f64, 2.0]);
4016    /// # Ok::<(), tenferro_tensor::Error>(())
4017    /// ```
4018    ///
4019    /// # Errors
4020    ///
4021    /// Returns an error when the view layout is not a valid sealed
4022    /// representation or when backend reinterpretation is unsupported.
4023    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'a, f64, DynRank>> {
4024        let op = "TypedTensorView::as_real_view";
4025        validate_representation_pair(op, DType::C64, DType::F64)?;
4026        let layout = reinterpret_complex_to_real_layout(
4027            self.shape(),
4028            self.strides(),
4029            self.offset(),
4030            self.buffer.len(),
4031            op,
4032        )?;
4033        let buffer = match &self.buffer {
4034            TensorStorageRef::Host(data) => {
4035                TensorStorageRef::Host(reinterpret_host_slice::<Complex64, f64>(data, op)?)
4036            }
4037            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
4038                return Err(crate::Error::unsupported(
4039                    op,
4040                    "backend representation reinterpretation is enabled by the provider phases",
4041                ))
4042            }
4043        };
4044        Ok(TypedTensorView {
4045            buffer,
4046            root: None,
4047            layout,
4048            placement: self.placement.clone(),
4049            _representation: std::marker::PhantomData,
4050        })
4051    }
4052}
4053
4054impl<'a, R: TensorRank> TypedTensorView<'a, f32, R> {
4055    /// Borrow this interleaved real view as a complex view without copying.
4056    ///
4057    /// The source must have a leading extent and stride of `2` and `1`, and
4058    /// every remaining stride plus the offset must be divisible by `2`.
4059    ///
4060    /// # Examples
4061    ///
4062    /// ```
4063    /// use tenferro_tensor::{Complex32, TypedTensorView};
4064    ///
4065    /// let data = [1.0_f32, 2.0];
4066    /// let view = TypedTensorView::from_col_major(&[2, 1], &data)?;
4067    /// let complex = view.as_complex_view()?;
4068    /// assert_eq!(complex.shape(), &[1]);
4069    /// assert_eq!(complex.as_slice()?, &[Complex32::new(1.0, 2.0)]);
4070    /// # Ok::<(), tenferro_tensor::Error>(())
4071    /// ```
4072    ///
4073    /// # Errors
4074    ///
4075    /// Returns an error when the view layout is not a valid sealed
4076    /// representation or when backend reinterpretation is unsupported.
4077    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'a, Complex32, DynRank>> {
4078        let op = "TypedTensorView::as_complex_view";
4079        validate_representation_pair(op, DType::F32, DType::C32)?;
4080        let layout = reinterpret_real_to_complex_layout(
4081            self.shape(),
4082            self.strides(),
4083            self.offset(),
4084            self.buffer.len(),
4085            op,
4086        )?;
4087        let buffer = match &self.buffer {
4088            TensorStorageRef::Host(data) => {
4089                TensorStorageRef::Host(reinterpret_host_slice::<f32, Complex32>(data, op)?)
4090            }
4091            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
4092                return Err(crate::Error::unsupported(
4093                    op,
4094                    "backend representation reinterpretation is enabled by the provider phases",
4095                ))
4096            }
4097        };
4098        Ok(TypedTensorView {
4099            buffer,
4100            root: None,
4101            layout,
4102            placement: self.placement.clone(),
4103            _representation: std::marker::PhantomData,
4104        })
4105    }
4106}
4107
4108impl<'a, R: TensorRank> TypedTensorView<'a, f64, R> {
4109    /// Borrow this interleaved real view as a complex view without copying.
4110    ///
4111    /// The source must have a leading extent and stride of `2` and `1`, and
4112    /// every remaining stride plus the offset must be divisible by `2`.
4113    ///
4114    /// # Examples
4115    ///
4116    /// ```
4117    /// use tenferro_tensor::{Complex64, TypedTensorView};
4118    ///
4119    /// let data = [1.0_f64, 2.0];
4120    /// let view = TypedTensorView::from_col_major(&[2, 1], &data)?;
4121    /// let complex = view.as_complex_view()?;
4122    /// assert_eq!(complex.shape(), &[1]);
4123    /// assert_eq!(complex.as_slice()?, &[Complex64::new(1.0, 2.0)]);
4124    /// # Ok::<(), tenferro_tensor::Error>(())
4125    /// ```
4126    ///
4127    /// # Errors
4128    ///
4129    /// Returns an error when the view layout is not a valid sealed
4130    /// representation or when backend reinterpretation is unsupported.
4131    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'a, Complex64, DynRank>> {
4132        let op = "TypedTensorView::as_complex_view";
4133        validate_representation_pair(op, DType::F64, DType::C64)?;
4134        let layout = reinterpret_real_to_complex_layout(
4135            self.shape(),
4136            self.strides(),
4137            self.offset(),
4138            self.buffer.len(),
4139            op,
4140        )?;
4141        let buffer = match &self.buffer {
4142            TensorStorageRef::Host(data) => {
4143                TensorStorageRef::Host(reinterpret_host_slice::<f64, Complex64>(data, op)?)
4144            }
4145            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
4146                return Err(crate::Error::unsupported(
4147                    op,
4148                    "backend representation reinterpretation is enabled by the provider phases",
4149                ))
4150            }
4151        };
4152        Ok(TypedTensorView {
4153            buffer,
4154            root: None,
4155            layout,
4156            placement: self.placement.clone(),
4157            _representation: std::marker::PhantomData,
4158        })
4159    }
4160}
4161
4162/// Mutable borrowed view of typed tensor storage with arbitrary strides.
4163///
4164/// # Examples
4165///
4166/// ```rust
4167/// use tenferro_tensor::TypedTensorViewMut;
4168///
4169/// let mut data = [1_i32, 2, 3];
4170/// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
4171/// *view.get_mut(&[2]).unwrap() = 10;
4172/// assert_eq!(view.as_read_only().get(&[2]), Some(&10));
4173/// # Ok::<(), tenferro_tensor::Error>(())
4174/// ```
4175/// Exclusive typed view of one tensor representation.
4176///
4177/// As with [`TypedTensorView`], the buffer stays concrete so the view keeps its
4178/// lifetime covariance, and `D` marks which representation produced the view.
4179pub struct TypedTensorViewMut<'a, T, R: TensorRank = DynRank, D: Representation = Dynamic> {
4180    buffer: TensorStorageRefMut<'a, T>,
4181    root: Option<GroupWriteView<'a, T, R>>,
4182    layout: TensorLayout<R>,
4183    placement: Placement,
4184    _representation: std::marker::PhantomData<D>,
4185}
4186
4187impl<'a, T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensorViewMut<'a, T, R, D> {
4188    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4189        formatter
4190            .debug_struct("TypedTensorViewMut")
4191            .field("shape", &self.layout.shape())
4192            .field("placement", &self.placement)
4193            .finish_non_exhaustive()
4194    }
4195}
4196
4197/// Pair of mutable tensor views returned by disjoint multi-slice operations.
4198///
4199/// # Examples
4200///
4201/// ```rust
4202/// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut, TypedTensorViewMutSplit};
4203///
4204/// let mut data = [1_i32, 2, 3, 4];
4205/// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
4206/// let pair: TypedTensorViewMutSplit<'_, i32> = view
4207///     .try_multi_slice_mut(
4208///         &[StridedSliceSpec::new(0, Some(2), 1)],
4209///         &[StridedSliceSpec::new(2, Some(4), 1)],
4210///     )
4211///     ?
4212///     .unwrap();
4213/// assert_eq!(pair.0.shape(), &[2]);
4214/// assert_eq!(pair.1.shape(), &[2]);
4215/// # Ok::<(), tenferro_tensor::Error>(())
4216/// ```
4217pub type TypedTensorViewMutSplit<'a, T, R = DynRank> =
4218    (TypedTensorViewMut<'a, T, R>, TypedTensorViewMut<'a, T, R>);
4219
4220impl<'a, T: 'static> TypedTensorViewMut<'a, T, DynRank> {
4221    /// Create a mutable dynamic-rank view over compact column-major host data.
4222    ///
4223    /// # Examples
4224    ///
4225    /// ```rust
4226    /// use tenferro_tensor::TypedTensorViewMut;
4227    ///
4228    /// let mut data = [1_i32, 2, 3, 4];
4229    /// let view = TypedTensorViewMut::from_col_major(&[2, 2], &mut data)?;
4230    /// assert_eq!(view.strides(), &[1, 2]);
4231    /// # Ok::<(), tenferro_tensor::Error>(())
4232    /// ```
4233    /// # Errors
4234    ///
4235    /// Returns [`crate::Error::Validation`] with
4236    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
4237    /// shape or offset arithmetic overflow, or
4238    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4239    /// compact shape reaches beyond `data`.
4240    pub fn from_col_major(shape: &[usize], data: &'a mut [T]) -> crate::Result<Self> {
4241        let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
4242            .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_col_major", err))?;
4243        Self::from_buffer_ref_mut(
4244            shape_vec(layout.shape()),
4245            stride_vec(layout.strides()),
4246            layout.offset(),
4247            TensorStorageRefMut::Host(data),
4248            default_placement(),
4249            "TypedTensorViewMut::from_col_major",
4250        )
4251    }
4252
4253    /// Create a mutable host view from explicit layout metadata.
4254    ///
4255    /// Layouts where distinct logical elements can alias the same physical
4256    /// element are rejected.
4257    ///
4258    /// # Examples
4259    ///
4260    /// ```rust
4261    /// use tenferro_tensor::TypedTensorViewMut;
4262    ///
4263    /// let mut data = [1_i32, 2];
4264    /// assert!(TypedTensorViewMut::from_slice(vec![2], vec![0], 0, &mut data).is_err());
4265    /// ```
4266    /// # Errors
4267    ///
4268    /// Returns [`crate::Error::Validation`] with
4269    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
4270    /// `strides` have different ranks,
4271    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4272    /// layout reaches beyond `data`,
4273    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4274    /// logical elements alias, or
4275    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4276    /// arithmetic overflow.
4277    pub fn from_slice(
4278        shape: impl AsRef<[usize]>,
4279        strides: impl AsRef<[isize]>,
4280        offset: isize,
4281        data: &'a mut [T],
4282    ) -> crate::Result<Self> {
4283        Self::from_buffer_ref_mut(
4284            shape_vec(shape.as_ref()),
4285            stride_vec(strides.as_ref()),
4286            offset,
4287            TensorStorageRefMut::Host(data),
4288            default_placement(),
4289            "TypedTensorViewMut::from_slice",
4290        )
4291    }
4292}
4293
4294impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4295    /// Create a rank-generic mutable host view from explicit layout metadata.
4296    ///
4297    /// # Examples
4298    ///
4299    /// ```rust
4300    /// use tenferro_tensor::{Rank, TypedTensorViewMut};
4301    ///
4302    /// let mut data = [1_i32, 2, 3, 4];
4303    /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
4304    /// assert_eq!(view.shape(), &[2, 2]);
4305    /// # Ok::<(), tenferro_tensor::Error>(())
4306    /// ```
4307    /// # Errors
4308    ///
4309    /// Returns [`crate::Error::Validation`] with
4310    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
4311    /// rank does not match `shape` or `strides`,
4312    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4313    /// layout reaches beyond `data`,
4314    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4315    /// logical elements alias, or
4316    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4317    /// arithmetic overflow.
4318    pub fn from_slice_ranked(
4319        shape: impl Into<R::Shape>,
4320        strides: impl Into<R::Strides>,
4321        offset: isize,
4322        data: &'a mut [T],
4323    ) -> crate::Result<Self> {
4324        Self::from_buffer_ref_mut(
4325            shape,
4326            strides,
4327            offset,
4328            TensorStorageRefMut::Host(data),
4329            default_placement(),
4330            "TypedTensorViewMut::from_slice_ranked",
4331        )
4332    }
4333}
4334
4335impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4336    fn from_buffer_ref_mut(
4337        shape: impl Into<R::Shape>,
4338        strides: impl Into<R::Strides>,
4339        offset: isize,
4340        buffer: TensorStorageRefMut<'a, T>,
4341        placement: Placement,
4342        op: &'static str,
4343    ) -> crate::Result<Self> {
4344        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
4345            .map_err(|err| tensor_layout_error(op, err))?;
4346        layout
4347            .validate_mutable_no_overlap()
4348            .map_err(|err| tensor_layout_error(op, err))?;
4349        Ok(Self {
4350            buffer,
4351            root: None,
4352            layout,
4353            placement,
4354            _representation: std::marker::PhantomData,
4355        })
4356    }
4357}
4358
4359impl<'a, T: 'static, R: TensorRank> TypedTensorViewMut<'a, T, R, Host> {
4360    /// Create a representation-marked mutable view over an explicit host layout.
4361    ///
4362    /// # Examples
4363    ///
4364    /// ```rust
4365    /// use tenferro_tensor::{DynRank, Host, TypedTensorViewMut};
4366    ///
4367    /// let mut data = [1_i32, 2];
4368    /// let mut view: TypedTensorViewMut<'_, i32, DynRank, Host> =
4369    ///     TypedTensorViewMut::from_host_slice(vec![2].as_slice(), vec![1].as_slice(), 0, &mut data)?;
4370    /// view.slice_view(&[tenferro_tensor::StridedSliceSpec::all()])?;
4371    /// # Ok::<(), tenferro_tensor::Error>(())
4372    /// ```
4373    ///
4374    /// # Errors
4375    ///
4376    /// Returns [`crate::Error::Validation`] with
4377    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `strides`
4378    /// has a different rank, [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`]
4379    /// when logical elements alias, or
4380    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] /
4381    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
4382    /// reachable layout leaves `data` or overflows.
4383    pub fn from_host_slice(
4384        shape: impl Into<R::Shape>,
4385        strides: impl Into<R::Strides>,
4386        offset: isize,
4387        data: &'a mut [T],
4388    ) -> crate::Result<Self> {
4389        // Built here rather than through the shared helper so that the Host
4390        // marker can only ever be paired with a host slice.
4391        let buffer = TensorStorageRefMut::Host(data);
4392        let layout =
4393            TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
4394                .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_host_slice", err))?;
4395        layout
4396            .validate_mutable_no_overlap()
4397            .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_host_slice", err))?;
4398        Ok(Self {
4399            buffer,
4400            root: None,
4401            layout,
4402            placement: default_placement(),
4403            _representation: std::marker::PhantomData,
4404        })
4405    }
4406
4407    /// Exclusively borrow the host elements without a runtime check.
4408    ///
4409    /// # Examples
4410    ///
4411    /// ```
4412    /// use tenferro_tensor::{DynRank, Host, TypedTensorViewMut};
4413    ///
4414    /// let mut data = [1_i32, 2];
4415    /// let mut view: TypedTensorViewMut<'_, i32, DynRank, Host> =
4416    ///     TypedTensorViewMut::from_host_slice(vec![2].as_slice(), vec![1].as_slice(), 0, &mut data)?;
4417    /// view.as_host_slice_mut()[0] = 5;
4418    /// assert_eq!(data, [5, 2]);
4419    /// # Ok::<(), tenferro_tensor::Error>(())
4420    /// ```
4421    ///
4422    /// # Panics
4423    ///
4424    /// Panics only if a `Host`-marked view was built from non-host storage,
4425    /// which no constructor in this crate does.
4426    pub fn as_host_slice_mut(&mut self) -> &mut [T] {
4427        match &mut self.buffer {
4428            TensorStorageRefMut::Host(data) => data,
4429            // INVARIANT: `Host`-marked views are constructed only by
4430            // `from_host_slice` and by `TypedTensor<_, _, Host>::as_view_mut`.
4431            TensorStorageRefMut::Backend(_) => {
4432                unreachable!("a Host-marked view always borrows host storage")
4433            }
4434        }
4435    }
4436}
4437
4438impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4439    /// Return the logical shape.
4440    ///
4441    /// # Examples
4442    ///
4443    /// ```rust
4444    /// use tenferro_tensor::TypedTensorViewMut;
4445    ///
4446    /// let mut data = [0_i32; 2];
4447    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4448    /// assert_eq!(view.shape(), &[2]);
4449    /// # Ok::<(), tenferro_tensor::Error>(())
4450    /// ```
4451    pub fn shape(&self) -> &[usize] {
4452        self.layout.shape()
4453    }
4454
4455    /// Return the logical rank carried by this mutable view.
4456    ///
4457    /// # Examples
4458    ///
4459    /// ```
4460    /// use tenferro_tensor::TypedTensorViewMut;
4461    ///
4462    /// let mut data = [0_i32; 6];
4463    /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
4464    /// assert_eq!(view.rank(), 2);
4465    /// # Ok::<(), tenferro_tensor::Error>(())
4466    /// ```
4467    pub fn rank(&self) -> usize {
4468        self.shape().len()
4469    }
4470
4471    /// Return strides in element units.
4472    ///
4473    /// # Examples
4474    ///
4475    /// ```rust
4476    /// use tenferro_tensor::TypedTensorViewMut;
4477    ///
4478    /// let mut data = [0_i32; 2];
4479    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![-1], 1, &mut data)?;
4480    /// assert_eq!(view.strides(), &[-1]);
4481    /// # Ok::<(), tenferro_tensor::Error>(())
4482    /// ```
4483    pub fn strides(&self) -> &[isize] {
4484        self.layout.strides()
4485    }
4486
4487    /// Return the physical element offset.
4488    ///
4489    /// # Examples
4490    ///
4491    /// ```rust
4492    /// use tenferro_tensor::TypedTensorViewMut;
4493    ///
4494    /// let mut data = [1_i32, 2];
4495    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 1, &mut data)?;
4496    /// assert_eq!(view.offset(), 1);
4497    /// # Ok::<(), tenferro_tensor::Error>(())
4498    /// ```
4499    pub fn offset(&self) -> isize {
4500        self.layout.offset()
4501    }
4502
4503    /// Return the borrowed host storage backing this view.
4504    ///
4505    /// This exposes the entire backing host allocation, not just the logical
4506    /// slice covered by this view. Use [`TypedTensorViewMut::as_read_only`]
4507    /// with [`TypedTensorView::as_slice`] when the caller needs the contiguous
4508    /// logical region instead.
4509    ///
4510    /// # Examples
4511    ///
4512    /// ```rust
4513    /// use tenferro_tensor::TypedTensorViewMut;
4514    ///
4515    /// let mut data = [1_i32, 2];
4516    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4517    /// assert_eq!(view.host_storage()?, &[1, 2]);
4518    /// # Ok::<(), tenferro_tensor::Error>(())
4519    /// ```
4520    /// # Errors
4521    ///
4522    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
4523    /// buffer; backend storage must be downloaded before host inspection.
4524    pub fn host_storage(&self) -> crate::Result<&[T]> {
4525        match &self.buffer {
4526            TensorStorageRefMut::Host(data) => Ok(data),
4527            TensorStorageRefMut::Backend(_) => Err(crate::Error::runtime_state(
4528                "TypedTensorViewMut::host_storage",
4529                "backend buffers cannot expose host storage; download explicitly first",
4530            )),
4531        }
4532    }
4533
4534    /// Mutably borrow the host storage backing this view.
4535    ///
4536    /// This exposes the entire backing host allocation, not just the logical
4537    /// slice covered by this view. Prefer scalar element accessors when mutating
4538    /// a logical region; tensor-sized copies belong to an active backend.
4539    ///
4540    /// # Examples
4541    ///
4542    /// ```rust
4543    /// use tenferro_tensor::TypedTensorViewMut;
4544    ///
4545    /// let mut data = [1_i32, 2];
4546    /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4547    /// view.host_storage_mut()?[0] = 3;
4548    /// assert_eq!(view.get(&[0]), Some(&3));
4549    /// # Ok::<(), tenferro_tensor::Error>(())
4550    /// ```
4551    /// # Errors
4552    ///
4553    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
4554    /// buffer; backend storage must be downloaded before host inspection.
4555    pub fn host_storage_mut(&mut self) -> crate::Result<&mut [T]> {
4556        match &mut self.buffer {
4557            TensorStorageRefMut::Host(data) => Ok(data),
4558            TensorStorageRefMut::Backend(_) => Err(crate::Error::runtime_state(
4559                "TypedTensorViewMut::host_storage_mut",
4560                "backend buffers cannot expose mutable host storage; download explicitly first",
4561            )),
4562        }
4563    }
4564
4565    /// Return the number of logical elements in this view.
4566    ///
4567    /// # Examples
4568    ///
4569    /// ```rust
4570    /// use tenferro_tensor::TypedTensorViewMut;
4571    ///
4572    /// let mut data = [0_i32; 6];
4573    /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
4574    /// assert_eq!(view.n_elements(), 6);
4575    /// # Ok::<(), tenferro_tensor::Error>(())
4576    /// ```
4577    pub fn n_elements(&self) -> usize {
4578        // Invariant: public mutable view constructors validate logical element count.
4579        match checked_view_element_count(self.shape(), "TypedTensorViewMut::n_elements") {
4580            Ok(n) => n,
4581            Err(err) => {
4582                unreachable!("TypedTensorViewMut layout shape is validated at construction: {err}")
4583            }
4584        }
4585    }
4586
4587    /// Return layout metadata for this view.
4588    ///
4589    /// # Examples
4590    ///
4591    /// ```rust
4592    /// use tenferro_tensor::TypedTensorViewMut;
4593    ///
4594    /// let mut data = [1_i32, 2];
4595    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4596    /// assert!(view.layout().is_compact_col_major().unwrap());
4597    /// # Ok::<(), tenferro_tensor::Error>(())
4598    /// ```
4599    pub fn layout(&self) -> &TensorLayout<R> {
4600        &self.layout
4601    }
4602
4603    /// Return placement metadata for this view.
4604    ///
4605    /// # Examples
4606    ///
4607    /// ```rust
4608    /// use tenferro_tensor::{MemoryKind, TypedTensorViewMut};
4609    ///
4610    /// let mut data = [1_i32];
4611    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4612    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
4613    /// # Ok::<(), tenferro_tensor::Error>(())
4614    /// ```
4615    pub fn placement(&self) -> &Placement {
4616        &self.placement
4617    }
4618
4619    /// Return the backend allocation for backend integrations.
4620    #[doc(hidden)]
4621    pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>> {
4622        match &self.buffer {
4623            TensorStorageRefMut::Host(_) => None,
4624            TensorStorageRefMut::Backend(buffer) => Some(&**buffer),
4625        }
4626    }
4627
4628    /// Prepare this backend view for one provider-native write binding.
4629    #[doc(hidden)]
4630    pub fn prepare_device_write(
4631        &mut self,
4632        op: &'static str,
4633    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
4634    where
4635        T: TensorScalar + 'static,
4636    {
4637        let layout = self.layout.clone();
4638        if self.root.is_none() {
4639            let buffer = self.backend_buffer().ok_or_else(|| {
4640                crate::Error::runtime_state_source(
4641                    op,
4642                    crate::AccessError::Unsupported { backend: "host" },
4643                )
4644            })?;
4645            return prepare_backend_access(buffer, &self.layout, op);
4646        }
4647        let root = self
4648            .root
4649            .as_mut()
4650            .ok_or_else(|| crate::Error::runtime_state(op, "expected a root-backed tensor view"))?;
4651        root.prepare_device_write_for_layout(&layout)
4652            .map_err(|error| crate::Error::runtime_state_source(op, error))
4653    }
4654
4655    /// Compute the physical element offset for a logical index.
4656    ///
4657    /// # Examples
4658    ///
4659    /// ```rust
4660    /// use tenferro_tensor::TypedTensorViewMut;
4661    ///
4662    /// let mut data = [1_i32, 2, 3];
4663    /// let view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
4664    /// assert_eq!(view.linear_offset(&[2]), Some(0));
4665    /// # Ok::<(), tenferro_tensor::Error>(())
4666    /// ```
4667    pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
4668        checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
4669    }
4670
4671    /// Compute the physical element offset for a logical index, returning a typed error.
4672    ///
4673    /// # Examples
4674    ///
4675    /// ```rust
4676    /// use tenferro_tensor::TypedTensorViewMut;
4677    ///
4678    /// let mut data = [1_i32, 2, 3];
4679    /// let view = TypedTensorViewMut::from_slice([3], [-1], 2, &mut data)?;
4680    /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
4681    /// # Ok::<(), tenferro_tensor::Error>(())
4682    /// ```
4683    /// # Errors
4684    ///
4685    /// Returns [`crate::Error::Validation`] with
4686    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
4687    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
4688    /// when an index is outside its axis extent, or
4689    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
4690    /// arithmetic overflows.
4691    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
4692        checked_view_offset_result(
4693            self.shape(),
4694            self.strides(),
4695            self.offset(),
4696            indices,
4697            "TypedTensorViewMut::layout_linear_offset",
4698        )
4699    }
4700
4701    /// Return whether this mutable view is compact column-major.
4702    ///
4703    /// # Examples
4704    ///
4705    /// ```rust
4706    /// use tenferro_tensor::TypedTensorViewMut;
4707    ///
4708    /// let mut data = [1_i32, 2];
4709    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4710    /// assert!(view.is_col_major_contiguous()?);
4711    /// # Ok::<(), tenferro_tensor::Error>(())
4712    /// ```
4713    /// # Errors
4714    ///
4715    /// Returns [`crate::Error::Validation`] with
4716    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4717    /// compactness arithmetic overflows.
4718    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
4719        self.layout
4720            .is_compact_col_major()
4721            .map_err(|err| tensor_layout_error("TypedTensorViewMut::is_col_major_contiguous", err))
4722    }
4723
4724    /// Return a compact string summary of this mutable view's layout metadata.
4725    ///
4726    /// # Examples
4727    ///
4728    /// ```rust
4729    /// use tenferro_tensor::TypedTensorViewMut;
4730    ///
4731    /// let mut data = [1_i32, 2];
4732    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4733    /// assert!(view.layout_summary().contains("shape=[2]"));
4734    /// # Ok::<(), tenferro_tensor::Error>(())
4735    /// ```
4736    pub fn layout_summary(&self) -> String {
4737        layout_summary(self.shape(), self.strides(), self.offset())
4738    }
4739
4740    /// Assert this mutable view is compact column-major.
4741    ///
4742    /// # Examples
4743    ///
4744    /// ```rust
4745    /// use tenferro_tensor::TypedTensorViewMut;
4746    ///
4747    /// let mut data = [1_i32, 2];
4748    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4749    /// view.assert_col_major_contiguous()?;
4750    /// # Ok::<(), tenferro_tensor::Error>(())
4751    /// ```
4752    /// # Errors
4753    ///
4754    /// Returns [`crate::Error::Validation`] with
4755    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4756    /// compactness arithmetic overflows, or
4757    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
4758    /// view is not compact column-major.
4759    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
4760        assert_layout_col_major_contiguous(
4761            self.is_col_major_contiguous()?,
4762            self.shape(),
4763            self.strides(),
4764            self.offset(),
4765            "TypedTensorViewMut::assert_col_major_contiguous",
4766        )
4767    }
4768
4769    /// Borrow one host element by logical index.
4770    ///
4771    /// # Examples
4772    ///
4773    /// ```rust
4774    /// use tenferro_tensor::TypedTensorViewMut;
4775    ///
4776    /// let mut data = [1_i32, 2];
4777    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4778    /// assert_eq!(view.get(&[1]), Some(&2));
4779    /// # Ok::<(), tenferro_tensor::Error>(())
4780    /// ```
4781    pub fn get(&self, indices: &[usize]) -> Option<&T> {
4782        let offset = self.linear_offset(indices)?;
4783        match &self.buffer {
4784            TensorStorageRefMut::Host(data) => data.get(offset),
4785            TensorStorageRefMut::Backend(_) => None,
4786        }
4787    }
4788
4789    /// Mutably borrow one host element by logical index.
4790    ///
4791    /// # Examples
4792    ///
4793    /// ```rust
4794    /// use tenferro_tensor::TypedTensorViewMut;
4795    ///
4796    /// let mut data = [1_i32, 2];
4797    /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4798    /// *view.get_mut(&[1]).unwrap() = 20;
4799    /// assert_eq!(view.get(&[1]), Some(&20));
4800    /// # Ok::<(), tenferro_tensor::Error>(())
4801    /// ```
4802    pub fn get_mut(&mut self, indices: &[usize]) -> Option<&mut T> {
4803        let offset = self.linear_offset(indices)?;
4804        match &mut self.buffer {
4805            TensorStorageRefMut::Host(data) => data.get_mut(offset),
4806            TensorStorageRefMut::Backend(_) => None,
4807        }
4808    }
4809
4810    /// Borrow this mutable view as a read-only view.
4811    ///
4812    /// # Examples
4813    ///
4814    /// ```rust
4815    /// use tenferro_tensor::TypedTensorViewMut;
4816    ///
4817    /// let mut data = [1_i32];
4818    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4819    /// assert_eq!(view.as_read_only().get(&[0]), Some(&1));
4820    /// # Ok::<(), tenferro_tensor::Error>(())
4821    /// ```
4822    /// Explicitly duplicate the compact host data visible through this
4823    /// mutable view into a new owner.
4824    ///
4825    /// # Errors
4826    ///
4827    /// Returns [`crate::Error::HostAccess`] or
4828    /// [`ValidationError::NonContiguousViewAsSlice`] when the view is backend
4829    /// owned or not contiguous, and [`ValidationError::InvalidArgument`] when
4830    /// the static-rank shape cannot be reconstructed.
4831    ///
4832    /// # Examples
4833    ///
4834    /// ```
4835    /// use tenferro_tensor::TypedTensorViewMut;
4836    ///
4837    /// let mut data = [1_i32, 2];
4838    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4839    /// let copy = view.duplicate()?;
4840    /// assert_eq!(copy.as_slice()?, &[1, 2]);
4841    /// # Ok::<(), tenferro_tensor::Error>(())
4842    /// ```
4843    pub fn duplicate(&self) -> crate::Result<TypedTensor<T, R>>
4844    where
4845        T: Clone,
4846    {
4847        self.as_read_only().duplicate()
4848    }
4849
4850    /// Explicitly copy this host view to a compact column-major owner.
4851    ///
4852    /// # Examples
4853    /// ```
4854    /// use tenferro_tensor::TypedTensorViewMut;
4855    /// let mut values = ["a".to_owned(), "b".to_owned()];
4856    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut values)?;
4857    /// assert_eq!(view.to_col_major()?.as_slice()?, &["a", "b"]);
4858    /// # Ok::<(), tenferro_tensor::Error>(())
4859    /// ```
4860    /// # Errors
4861    /// Backend-only storage returns [`crate::Error::RuntimeState`]; invalid
4862    /// layout or rank conversion returns [`crate::Error::Validation`].
4863    pub fn to_col_major(&self) -> crate::Result<TypedTensor<T, R>>
4864    where
4865        T: Clone,
4866    {
4867        self.as_read_only().to_col_major()
4868    }
4869
4870    /// Borrow this mutable view as a read-only typed view over the same storage.
4871    ///
4872    /// # Examples
4873    ///
4874    /// ```
4875    /// use tenferro_tensor::TypedTensorViewMut;
4876    ///
4877    /// let mut data = [1_i32, 2];
4878    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4879    /// assert_eq!(view.as_read_only().as_slice()?, &[1, 2]);
4880    /// # Ok::<(), tenferro_tensor::Error>(())
4881    /// ```
4882    pub fn as_read_only(&self) -> TypedTensorView<'_, T, R, D> {
4883        let buffer = match &self.buffer {
4884            TensorStorageRefMut::Host(data) => TensorStorageRef::Host(data),
4885            TensorStorageRefMut::Backend(buffer) => TensorStorageRef::Backend(&**buffer),
4886        };
4887        TypedTensorView {
4888            buffer,
4889            root: None,
4890            layout: self.layout.clone(),
4891            placement: self.placement.clone(),
4892            _representation: std::marker::PhantomData,
4893        }
4894    }
4895
4896    /// Convert this mutable view into a read-only view.
4897    ///
4898    /// # Examples
4899    ///
4900    /// ```rust
4901    /// use tenferro_tensor::TypedTensorViewMut;
4902    ///
4903    /// let mut data = [1_i32];
4904    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4905    /// assert_eq!(view.into_read_only().get(&[0]), Some(&1));
4906    /// # Ok::<(), tenferro_tensor::Error>(())
4907    /// ```
4908    pub fn into_read_only(self) -> TypedTensorView<'a, T, R, D> {
4909        let buffer = match self.buffer {
4910            TensorStorageRefMut::Host(data) => TensorStorageRef::Host(data),
4911            TensorStorageRefMut::Backend(buffer) => TensorStorageRef::Backend(buffer),
4912        };
4913        TypedTensorView {
4914            buffer,
4915            root: None,
4916            layout: self.layout,
4917            placement: self.placement,
4918            _representation: std::marker::PhantomData,
4919        }
4920    }
4921
4922    /// Consume this mutable view and return a metadata-only axis permutation.
4923    ///
4924    /// # Examples
4925    ///
4926    /// ```rust
4927    /// use tenferro_tensor::{Rank, TypedTensorViewMut};
4928    ///
4929    /// let mut data = [1_i32, 2, 3, 4];
4930    /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
4931    /// let transposed = view.transpose_view([1, 0])?;
4932    /// assert_eq!(transposed.strides(), &[2, 1]);
4933    /// # Ok::<(), tenferro_tensor::Error>(())
4934    /// ```
4935    /// # Errors
4936    ///
4937    /// Returns [`crate::Error::Validation`] with
4938    /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
4939    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
4940    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
4941    /// not a valid permutation, [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`]
4942    /// when the permutation creates aliases, or
4943    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4944    /// arithmetic overflow.
4945    pub fn transpose_view(
4946        self,
4947        axes: impl AsRef<[usize]>,
4948    ) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
4949        let Self {
4950            buffer,
4951            root,
4952            layout,
4953            placement,
4954            _representation: std::marker::PhantomData,
4955        } = self;
4956        let layout = layout
4957            .transpose_view(axes)
4958            .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
4959        layout
4960            .validate_mutable_no_overlap()
4961            .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
4962        match buffer {
4963            TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
4964                buffer: TensorStorageRefMut::Host(data),
4965                root,
4966                layout,
4967                placement,
4968                _representation: std::marker::PhantomData,
4969            }),
4970            TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
4971                buffer: TensorStorageRefMut::Backend(buffer),
4972                root,
4973                layout,
4974                placement,
4975                _representation: std::marker::PhantomData,
4976            }),
4977        }
4978    }
4979
4980    /// Return a mutable metadata-only slice using one [`StridedSliceSpec`] per axis.
4981    ///
4982    /// # Examples
4983    ///
4984    /// ```rust
4985    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
4986    ///
4987    /// let mut data = [1_i32, 2, 3];
4988    /// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![1], 0, &mut data)?;
4989    /// *view.slice_view(&[StridedSliceSpec::reverse()])?.get_mut(&[0]).unwrap() = 30;
4990    /// assert_eq!(view.get(&[2]), Some(&30));
4991    /// # Ok::<(), tenferro_tensor::Error>(())
4992    /// ```
4993    /// # Errors
4994    ///
4995    /// Returns [`crate::Error::Validation`] with
4996    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
4997    /// count differs from the view rank,
4998    /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
4999    /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
5000    /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
5001    /// when the result exceeds the backing buffer,
5002    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
5003    /// logical elements alias, or
5004    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
5005    /// arithmetic overflow.
5006    pub fn slice_view(
5007        &mut self,
5008        slices: &[StridedSliceSpec],
5009    ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
5010        let specs = core_slice_specs(slices, self.shape(), "TypedTensorViewMut::slice_view")?;
5011        let layout = self
5012            .layout
5013            .slice_view(specs, self.buffer.len())
5014            .map_err(|err| tensor_layout_error("TypedTensorViewMut::slice_view", err))?;
5015        layout
5016            .validate_mutable_no_overlap()
5017            .map_err(|err| tensor_layout_error("TypedTensorViewMut::slice_view", err))?;
5018        let placement = self.placement.clone();
5019        match &mut self.buffer {
5020            TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
5021                buffer: TensorStorageRefMut::Host(data),
5022                root: None,
5023                layout,
5024                placement,
5025                _representation: std::marker::PhantomData,
5026            }),
5027            TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
5028                buffer: TensorStorageRefMut::Backend(*buffer),
5029                root: None,
5030                layout,
5031                placement,
5032                _representation: std::marker::PhantomData,
5033            }),
5034        }
5035    }
5036
5037    /// Return a mutable metadata-only slice along one axis.
5038    ///
5039    /// # Examples
5040    ///
5041    /// ```rust
5042    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
5043    ///
5044    /// let mut data = [1_i32, 2, 3, 4];
5045    /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
5046    /// assert_eq!(view.slice_axis_view(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
5047    /// # Ok::<(), tenferro_tensor::Error>(())
5048    /// ```
5049    /// # Errors
5050    ///
5051    /// Returns [`crate::Error::Validation`] with
5052    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
5053    /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
5054    /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
5055    /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
5056    /// when the result exceeds the backing buffer,
5057    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
5058    /// logical elements alias, or
5059    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
5060    /// arithmetic overflow.
5061    pub fn slice_axis_view(
5062        &mut self,
5063        axis: usize,
5064        slice: StridedSliceSpec,
5065    ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
5066        let slices = slice_axis_specs(
5067            self.shape().len(),
5068            axis,
5069            slice,
5070            "TypedTensorViewMut::slice_axis_view",
5071        )?;
5072        self.slice_view(&slices)
5073    }
5074
5075    /// Return two mutable metadata-only slices when their physical ranges are disjoint.
5076    ///
5077    /// # Examples
5078    ///
5079    /// ```rust
5080    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
5081    ///
5082    /// let mut data = [1_i32, 2, 3, 4];
5083    /// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
5084    /// let (left, right) = view
5085    ///     .try_multi_slice_mut(
5086    ///         &[StridedSliceSpec::new(0, Some(2), 1)],
5087    ///         &[StridedSliceSpec::new(2, Some(4), 1)],
5088    ///     )
5089    ///     ?
5090    ///     .unwrap();
5091    /// assert_eq!(left.shape(), &[2]);
5092    /// assert_eq!(right.shape(), &[2]);
5093    /// # Ok::<(), tenferro_tensor::Error>(())
5094    /// ```
5095    /// # Errors
5096    ///
5097    /// Returns [`crate::Error::Validation`] with
5098    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for either
5099    /// slice count, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
5100    /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for
5101    /// invalid parameters, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
5102    /// when a result exceeds the backing buffer,
5103    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
5104    /// a result aliases, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5105    /// for a negative reachable offset, or
5106    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
5107    /// arithmetic overflow. The method returns `Ok(None)` when the two ranges
5108    /// overlap or the view uses backend storage.
5109    pub fn try_multi_slice_mut(
5110        &mut self,
5111        first: &[StridedSliceSpec],
5112        second: &[StridedSliceSpec],
5113    ) -> crate::Result<Option<TypedTensorViewMutSplit<'_, T, R>>> {
5114        let op = "TypedTensorViewMut::try_multi_slice_mut";
5115        let first_specs = core_slice_specs(first, self.shape(), op)?;
5116        let second_specs = core_slice_specs(second, self.shape(), op)?;
5117        let buffer_len = self.buffer.len();
5118        let first_layout = self
5119            .layout
5120            .slice_view(first_specs, buffer_len)
5121            .map_err(|err| tensor_layout_error(op, err))?;
5122        let second_layout = self
5123            .layout
5124            .slice_view(second_specs, buffer_len)
5125            .map_err(|err| tensor_layout_error(op, err))?;
5126        first_layout
5127            .validate_mutable_no_overlap()
5128            .map_err(|err| tensor_layout_error(op, err))?;
5129        second_layout
5130            .validate_mutable_no_overlap()
5131            .map_err(|err| tensor_layout_error(op, err))?;
5132
5133        match (
5134            reachable_layout_span(
5135                first_layout.shape(),
5136                first_layout.strides(),
5137                first_layout.offset(),
5138            )?,
5139            reachable_layout_span(
5140                second_layout.shape(),
5141                second_layout.strides(),
5142                second_layout.offset(),
5143            )?,
5144        ) {
5145            (Some(first_span), Some(second_span)) => {
5146                let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
5147                let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
5148                let (first_data, second_data) = match &mut self.buffer {
5149                    TensorStorageRefMut::Host(data) => {
5150                        match split_two_mut_ranges(data, first_span, second_span) {
5151                            Some(ranges) => ranges,
5152                            None => return Ok(None),
5153                        }
5154                    }
5155                    TensorStorageRefMut::Backend(_) => return Ok(None),
5156                };
5157                let first_view = view_mut_from_layout_and_slice(
5158                    &first_layout,
5159                    first_offset,
5160                    first_data,
5161                    self.placement.clone(),
5162                )?;
5163                let second_view = view_mut_from_layout_and_slice(
5164                    &second_layout,
5165                    second_offset,
5166                    second_data,
5167                    self.placement.clone(),
5168                )?;
5169                Ok(Some((first_view, second_view)))
5170            }
5171            (None, Some(second_span)) => {
5172                let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
5173                let (_, after_start) = match &mut self.buffer {
5174                    TensorStorageRefMut::Host(data) => data.split_at_mut(second_span.0),
5175                    TensorStorageRefMut::Backend(_) => return Ok(None),
5176                };
5177                let (second_data, _) = after_start.split_at_mut(second_span.1 - second_span.0 + 1);
5178                let first_view = view_mut_from_layout_and_slice(
5179                    &first_layout,
5180                    0,
5181                    &mut [],
5182                    self.placement.clone(),
5183                )?;
5184                let second_view = view_mut_from_layout_and_slice(
5185                    &second_layout,
5186                    second_offset,
5187                    second_data,
5188                    self.placement.clone(),
5189                )?;
5190                Ok(Some((first_view, second_view)))
5191            }
5192            (Some(first_span), None) => {
5193                let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
5194                let (_, after_start) = match &mut self.buffer {
5195                    TensorStorageRefMut::Host(data) => data.split_at_mut(first_span.0),
5196                    TensorStorageRefMut::Backend(_) => return Ok(None),
5197                };
5198                let (first_data, _) = after_start.split_at_mut(first_span.1 - first_span.0 + 1);
5199                let first_view = view_mut_from_layout_and_slice(
5200                    &first_layout,
5201                    first_offset,
5202                    first_data,
5203                    self.placement.clone(),
5204                )?;
5205                let second_view = view_mut_from_layout_and_slice(
5206                    &second_layout,
5207                    0,
5208                    &mut [],
5209                    self.placement.clone(),
5210                )?;
5211                Ok(Some((first_view, second_view)))
5212            }
5213            (None, None) => {
5214                let first_view = view_mut_from_layout_and_slice(
5215                    &first_layout,
5216                    0,
5217                    &mut [],
5218                    self.placement.clone(),
5219                )?;
5220                let second_view = view_mut_from_layout_and_slice(
5221                    &second_layout,
5222                    0,
5223                    &mut [],
5224                    self.placement.clone(),
5225                )?;
5226                Ok(Some((first_view, second_view)))
5227            }
5228        }
5229    }
5230
5231    /// Return a mutable metadata-only dynamic-rank reshape for contiguous views.
5232    ///
5233    /// # Examples
5234    ///
5235    /// ```rust
5236    /// use tenferro_tensor::TypedTensorViewMut;
5237    ///
5238    /// let mut data = [1_i32, 2, 3, 4];
5239    /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
5240    /// assert_eq!(view.reshape_view(&[4])?.shape(), &[4]);
5241    /// # Ok::<(), tenferro_tensor::Error>(())
5242    /// ```
5243    /// # Errors
5244    ///
5245    /// Returns [`crate::Error::Validation`] with
5246    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
5247    /// the source is not compact column-major,
5248    /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
5249    /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
5250    /// records the counts) when element counts differ,
5251    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
5252    /// the reshaped layout aliases, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
5253    /// for shape or layout arithmetic overflow, or
5254    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5255    /// reshaped view exceeds the backing buffer.
5256    pub fn reshape_view(
5257        &mut self,
5258        shape: &[usize],
5259    ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank, D>> {
5260        let layout = reshape_layout_dyn(
5261            &self.layout,
5262            shape,
5263            self.buffer.len(),
5264            "TypedTensorViewMut::reshape_view",
5265        )?;
5266        layout
5267            .validate_mutable_no_overlap()
5268            .map_err(|err| tensor_layout_error("TypedTensorViewMut::reshape_view", err))?;
5269        let placement = self.placement.clone();
5270        match &mut self.buffer {
5271            TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
5272                buffer: TensorStorageRefMut::Host(data),
5273                root: None,
5274                layout,
5275                placement,
5276                _representation: std::marker::PhantomData,
5277            }),
5278            TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
5279                buffer: TensorStorageRefMut::Backend(*buffer),
5280                root: None,
5281                layout,
5282                placement,
5283                _representation: std::marker::PhantomData,
5284            }),
5285        }
5286    }
5287}
5288
5289impl<'a, R: TensorRank> TypedTensorViewMut<'a, Complex32, R> {
5290    /// Borrow this mutable complex view as an interleaved real view.
5291    ///
5292    /// This changes only the typed descriptor and borrows the same host
5293    /// allocation. The result has dynamic rank because the component axis is
5294    /// prepended. Backend-native buffers are rejected until their provider
5295    /// phase supplies the corresponding mapping capability.
5296    ///
5297    /// # Examples
5298    ///
5299    /// ```
5300    /// use tenferro_tensor::{Complex32, TypedTensorViewMut};
5301    ///
5302    /// let mut data = [Complex32::new(1.0, 2.0)];
5303    /// let mut view = TypedTensorViewMut::from_col_major(&[1], &mut data)?;
5304    /// let real = view.as_real_view_mut()?;
5305    /// assert_eq!(real.shape(), &[2, 1]);
5306    /// assert_eq!(real.as_read_only().as_slice()?, &[1.0_f32, 2.0]);
5307    /// # Ok::<(), tenferro_tensor::Error>(())
5308    /// ```
5309    ///
5310    /// # Errors
5311    ///
5312    /// Returns an error when the view layout is not injective, the sealed
5313    /// representation is invalid, or backend reinterpretation is unsupported.
5314    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
5315        let op = "TypedTensorViewMut::as_real_view_mut";
5316        validate_representation_pair(op, DType::C32, DType::F32)?;
5317        let layout = reinterpret_complex_to_real_layout(
5318            self.shape(),
5319            self.strides(),
5320            self.offset(),
5321            self.buffer.len(),
5322            op,
5323        )?;
5324        layout
5325            .validate_mutable_no_overlap()
5326            .map_err(|err| tensor_layout_error(op, err))?;
5327        let buffer = match &mut self.buffer {
5328            TensorStorageRefMut::Host(data) => {
5329                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(data, op)?)
5330            }
5331            TensorStorageRefMut::Backend(_) => {
5332                return Err(crate::Error::unsupported(
5333                    op,
5334                    "backend representation reinterpretation is enabled by the provider phases",
5335                ))
5336            }
5337        };
5338        Ok(TypedTensorViewMut {
5339            buffer,
5340            root: None,
5341            layout,
5342            placement: self.placement.clone(),
5343            _representation: std::marker::PhantomData,
5344        })
5345    }
5346}
5347
5348impl<'a, R: TensorRank> TypedTensorViewMut<'a, Complex64, R> {
5349    /// Borrow this mutable complex view as an interleaved real view.
5350    ///
5351    /// This changes only the typed descriptor and borrows the same host
5352    /// allocation. The result has dynamic rank because the component axis is
5353    /// prepended. Backend-native buffers are rejected until their provider
5354    /// phase supplies the corresponding mapping capability.
5355    ///
5356    /// # Examples
5357    ///
5358    /// ```
5359    /// use tenferro_tensor::{Complex64, TypedTensorViewMut};
5360    ///
5361    /// let mut data = [Complex64::new(1.0, 2.0)];
5362    /// let mut view = TypedTensorViewMut::from_col_major(&[1], &mut data)?;
5363    /// let real = view.as_real_view_mut()?;
5364    /// assert_eq!(real.shape(), &[2, 1]);
5365    /// assert_eq!(real.as_read_only().as_slice()?, &[1.0_f64, 2.0]);
5366    /// # Ok::<(), tenferro_tensor::Error>(())
5367    /// ```
5368    ///
5369    /// # Errors
5370    ///
5371    /// Returns an error when the view layout is not injective, the sealed
5372    /// representation is invalid, or backend reinterpretation is unsupported.
5373    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
5374        let op = "TypedTensorViewMut::as_real_view_mut";
5375        validate_representation_pair(op, DType::C64, DType::F64)?;
5376        let layout = reinterpret_complex_to_real_layout(
5377            self.shape(),
5378            self.strides(),
5379            self.offset(),
5380            self.buffer.len(),
5381            op,
5382        )?;
5383        layout
5384            .validate_mutable_no_overlap()
5385            .map_err(|err| tensor_layout_error(op, err))?;
5386        let buffer = match &mut self.buffer {
5387            TensorStorageRefMut::Host(data) => {
5388                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(data, op)?)
5389            }
5390            TensorStorageRefMut::Backend(_) => {
5391                return Err(crate::Error::unsupported(
5392                    op,
5393                    "backend representation reinterpretation is enabled by the provider phases",
5394                ))
5395            }
5396        };
5397        Ok(TypedTensorViewMut {
5398            buffer,
5399            root: None,
5400            layout,
5401            placement: self.placement.clone(),
5402            _representation: std::marker::PhantomData,
5403        })
5404    }
5405}
5406
5407impl<'a, R: TensorRank> TypedTensorViewMut<'a, f32, R> {
5408    /// Borrow this mutable interleaved real view as a complex view.
5409    ///
5410    /// The source must have a leading extent and stride of `2` and `1`, and
5411    /// all remaining strides plus the offset must be divisible by `2`.
5412    ///
5413    /// # Examples
5414    ///
5415    /// ```
5416    /// use tenferro_tensor::{Complex32, TypedTensorViewMut};
5417    ///
5418    /// let mut data = [1.0_f32, 2.0];
5419    /// let mut view = TypedTensorViewMut::from_col_major(&[2, 1], &mut data)?;
5420    /// let complex = view.as_complex_view_mut()?;
5421    /// assert_eq!(complex.shape(), &[1]);
5422    /// assert_eq!(complex.as_read_only().as_slice()?, &[Complex32::new(1.0, 2.0)]);
5423    /// # Ok::<(), tenferro_tensor::Error>(())
5424    /// ```
5425    ///
5426    /// # Errors
5427    ///
5428    /// Returns an error when the view layout is not injective, the sealed
5429    /// representation is invalid, or backend reinterpretation is unsupported.
5430    pub fn as_complex_view_mut(
5431        &mut self,
5432    ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
5433        let op = "TypedTensorViewMut::as_complex_view_mut";
5434        validate_representation_pair(op, DType::F32, DType::C32)?;
5435        let layout = reinterpret_real_to_complex_layout(
5436            self.shape(),
5437            self.strides(),
5438            self.offset(),
5439            self.buffer.len(),
5440            op,
5441        )?;
5442        layout
5443            .validate_mutable_no_overlap()
5444            .map_err(|err| tensor_layout_error(op, err))?;
5445        let buffer = match &mut self.buffer {
5446            TensorStorageRefMut::Host(data) => {
5447                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(data, op)?)
5448            }
5449            TensorStorageRefMut::Backend(_) => {
5450                return Err(crate::Error::unsupported(
5451                    op,
5452                    "backend representation reinterpretation is enabled by the provider phases",
5453                ))
5454            }
5455        };
5456        Ok(TypedTensorViewMut {
5457            buffer,
5458            root: None,
5459            layout,
5460            placement: self.placement.clone(),
5461            _representation: std::marker::PhantomData,
5462        })
5463    }
5464}
5465
5466impl<'a, R: TensorRank> TypedTensorViewMut<'a, f64, R> {
5467    /// Borrow this mutable interleaved real view as a complex view.
5468    ///
5469    /// The source must have a leading extent and stride of `2` and `1`, and
5470    /// all remaining strides plus the offset must be divisible by `2`.
5471    ///
5472    /// # Examples
5473    ///
5474    /// ```
5475    /// use tenferro_tensor::{Complex64, TypedTensorViewMut};
5476    ///
5477    /// let mut data = [1.0_f64, 2.0];
5478    /// let mut view = TypedTensorViewMut::from_col_major(&[2, 1], &mut data)?;
5479    /// let complex = view.as_complex_view_mut()?;
5480    /// assert_eq!(complex.shape(), &[1]);
5481    /// assert_eq!(complex.as_read_only().as_slice()?, &[Complex64::new(1.0, 2.0)]);
5482    /// # Ok::<(), tenferro_tensor::Error>(())
5483    /// ```
5484    ///
5485    /// # Errors
5486    ///
5487    /// Returns an error when the view layout is not injective, the sealed
5488    /// representation is invalid, or backend reinterpretation is unsupported.
5489    pub fn as_complex_view_mut(
5490        &mut self,
5491    ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
5492        let op = "TypedTensorViewMut::as_complex_view_mut";
5493        validate_representation_pair(op, DType::F64, DType::C64)?;
5494        let layout = reinterpret_real_to_complex_layout(
5495            self.shape(),
5496            self.strides(),
5497            self.offset(),
5498            self.buffer.len(),
5499            op,
5500        )?;
5501        layout
5502            .validate_mutable_no_overlap()
5503            .map_err(|err| tensor_layout_error(op, err))?;
5504        let buffer = match &mut self.buffer {
5505            TensorStorageRefMut::Host(data) => {
5506                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(data, op)?)
5507            }
5508            TensorStorageRefMut::Backend(_) => {
5509                return Err(crate::Error::unsupported(
5510                    op,
5511                    "backend representation reinterpretation is enabled by the provider phases",
5512                ))
5513            }
5514        };
5515        Ok(TypedTensorViewMut {
5516            buffer,
5517            root: None,
5518            layout,
5519            placement: self.placement.clone(),
5520            _representation: std::marker::PhantomData,
5521        })
5522    }
5523}
5524
5525/// Sealed trait for scalar types that can be stored in a [`Tensor`].
5526///
5527/// This trait is implemented for `f64`, `f32`, `i32`, `i64`, `bool`,
5528/// [`Complex64`], and [`Complex32`].
5529///
5530/// # Examples
5531///
5532/// ```
5533/// use tenferro_tensor::TensorScalar;
5534///
5535/// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
5536/// assert_eq!(tensor.as_slice::<f64>()?, [1.0, 2.0].as_slice());
5537/// # Ok::<(), tenferro_tensor::Error>(())
5538/// ```
5539pub trait TensorScalar: Copy + Clone + Send + Sync + 'static + private::Sealed {
5540    /// Real-valued counterpart of this scalar type.
5541    type Real: TensorScalar;
5542
5543    /// The [`DType`] tag corresponding to this scalar type.
5544    ///
5545    /// # Examples
5546    ///
5547    /// ```
5548    /// use tenferro_tensor::{DType, TensorScalar};
5549    ///
5550    /// assert_eq!(f64::dtype(), DType::F64);
5551    /// assert_eq!(f32::dtype(), DType::F32);
5552    /// ```
5553    fn dtype() -> DType;
5554
5555    /// Build the crate's default scalar set from validated column-major data.
5556    ///
5557    /// # Examples
5558    ///
5559    /// ```
5560    /// use tenferro_tensor::{DType, TensorScalar};
5561    ///
5562    /// let set = <f64 as TensorScalar>::into_default_scalars(vec![2], vec![1.0, 2.0])?;
5563    /// assert_eq!(set.dtype(), DType::F64);
5564    /// # Ok::<(), tenferro_tensor::Error>(())
5565    /// ```
5566    ///
5567    /// # Errors
5568    ///
5569    /// Returns a validation error carrying
5570    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when the shape product
5571    /// differs from `data.len()`, or [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
5572    /// when shape arithmetic overflows.
5573    fn into_default_scalars(
5574        shape: Vec<usize>,
5575        data: Vec<Self>,
5576    ) -> crate::Result<crate::DefaultScalars>;
5577
5578    /// Borrow the default scalar set's values when it holds this scalar type.
5579    ///
5580    /// Returns `None` when the set currently holds another member.
5581    ///
5582    /// # Examples
5583    ///
5584    /// ```
5585    /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5586    ///
5587    /// let set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5588    /// assert_eq!(<f64 as TensorScalar>::default_scalars_slice(&set), Some(&[1.0, 2.0][..]));
5589    /// assert!(<f32 as TensorScalar>::default_scalars_slice(&set).is_none());
5590    /// # Ok::<(), tenferro_tensor::Error>(())
5591    /// ```
5592    fn default_scalars_slice(set: &crate::DefaultScalars) -> Option<&[Self]>;
5593
5594    /// Exclusively borrow the default scalar set's values when it holds this scalar type.
5595    ///
5596    /// Returns `None` when the set currently holds another member.
5597    ///
5598    /// # Examples
5599    ///
5600    /// ```
5601    /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5602    ///
5603    /// let mut set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5604    /// if let Some(values) = <f64 as TensorScalar>::default_scalars_slice_mut(&mut set) {
5605    ///     values[0] = 5.0;
5606    /// }
5607    /// assert_eq!(set.as_slice::<f64>()?, &[5.0, 2.0]);
5608    /// # Ok::<(), tenferro_tensor::Error>(())
5609    /// ```
5610    fn default_scalars_slice_mut(set: &mut crate::DefaultScalars) -> Option<&mut [Self]>;
5611
5612    /// Move the host tensor out of the default scalar set when it holds this scalar type.
5613    ///
5614    /// Returns `None` when the set currently holds another member.
5615    ///
5616    /// # Examples
5617    ///
5618    /// ```
5619    /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5620    ///
5621    /// let set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5622    /// let host = <f64 as TensorScalar>::from_default_scalars(set);
5623    /// assert_eq!(host.as_ref().map(|t| t.shape()), Some(&[2][..]));
5624    /// assert_eq!(host.as_ref().map(|t| t.as_slice()), Some(&[1.0, 2.0][..]));
5625    /// # Ok::<(), tenferro_tensor::Error>(())
5626    /// ```
5627    fn from_default_scalars(set: crate::DefaultScalars)
5628        -> Option<TypedTensor<Self, DynRank, Host>>;
5629
5630    /// Wrap typed column-major data into a [`Tensor`] enum variant.
5631    ///
5632    /// # Examples
5633    ///
5634    /// ```
5635    /// use tenferro_tensor::{DType, TensorScalar};
5636    ///
5637    /// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
5638    /// assert_eq!(tensor.dtype(), DType::F64);
5639    /// assert_eq!(tensor.shape(), &[2]);
5640    /// assert!(<f64 as TensorScalar>::into_tensor(vec![3], vec![1.0]).is_err());
5641    /// # Ok::<(), tenferro_tensor::Error>(())
5642    /// ```
5643    ///
5644    /// # Errors
5645    ///
5646    /// Returns [`crate::Error::Validation`] with
5647    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
5648    /// the shape product differs from `data.len()`, or
5649    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
5650    /// arithmetic overflows.
5651    fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor>;
5652
5653    /// Wrap a typed tensor into its dynamic [`Tensor`] enum variant.
5654    ///
5655    /// # Examples
5656    ///
5657    /// ```
5658    /// use tenferro_tensor::{DType, Tensor, TensorScalar, TypedTensor};
5659    ///
5660    /// let typed = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![3.0])?;
5661    /// let tensor = <f64 as TensorScalar>::typed_tensor_into_tensor(typed);
5662    /// assert!(matches!(tensor.dtype(), DType::F64));
5663    /// # Ok::<(), tenferro_tensor::Error>(())
5664    /// ```
5665    fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor;
5666
5667    /// Borrow a typed tensor as a dtype-erased [`TensorRead`] view.
5668    ///
5669    /// This keeps the typed tensor borrowed instead of copying host data into
5670    /// a new dynamic tensor.
5671    ///
5672    /// # Examples
5673    ///
5674    /// ```
5675    /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
5676    ///
5677    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5678    /// let read = f64::tensor_read(&tensor);
5679    /// assert_eq!(read.dtype(), DType::F64);
5680    /// assert_eq!(read.shape(), &[2]);
5681    /// ```
5682    fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_>;
5683
5684    /// Wrap a typed borrowed view as a dtype-erased [`TensorView`].
5685    ///
5686    /// # Examples
5687    ///
5688    /// ```
5689    /// use tenferro_tensor::{DType, TensorScalar, TypedTensorView};
5690    ///
5691    /// let data = [1.0_f64];
5692    /// let view = TypedTensorView::from_col_major(&[1], &data)?;
5693    /// assert_eq!(f64::tensor_view(view).dtype(), DType::F64);
5694    /// # Ok::<(), tenferro_tensor::Error>(())
5695    /// ```
5696    fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a>;
5697
5698    /// Wrap a typed mutable borrowed view as a dtype-erased [`TensorViewMut`].
5699    ///
5700    /// # Examples
5701    ///
5702    /// ```
5703    /// use tenferro_tensor::{DType, TensorScalar, TypedTensorViewMut};
5704    ///
5705    /// let mut data = [1.0_f64, 2.0];
5706    /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
5707    /// let erased = f64::tensor_view_mut(view);
5708    /// assert_eq!(erased.dtype(), DType::F64);
5709    /// assert_eq!(erased.shape(), &[2]);
5710    /// # Ok::<(), tenferro_tensor::Error>(())
5711    /// ```
5712    fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a>;
5713
5714    /// Mutably borrow a typed tensor as a dtype-erased [`TensorWrite`] view.
5715    ///
5716    /// This keeps the typed output borrowed instead of wrapping it in a
5717    /// temporary dynamic tensor.
5718    ///
5719    /// # Examples
5720    ///
5721    /// ```
5722    /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
5723    ///
5724    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0]).unwrap();
5725    /// let write = f64::tensor_write(&mut tensor);
5726    /// assert_eq!(write.dtype(), DType::F64);
5727    /// ```
5728    fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_>;
5729
5730    /// Borrow the host data from a [`Tensor`].
5731    ///
5732    /// # Examples
5733    ///
5734    /// ```
5735    /// use tenferro_tensor::{Tensor, TensorScalar};
5736    ///
5737    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5738    /// assert_eq!(<f64 as TensorScalar>::as_slice(&tensor)?, &[1.0, 2.0]);
5739    /// assert!(<f32 as TensorScalar>::as_slice(&tensor).is_err());
5740    /// # Ok::<(), tenferro_tensor::Error>(())
5741    /// ```
5742    ///
5743    /// # Errors
5744    ///
5745    /// Returns [`crate::Error::Validation`] with
5746    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
5747    /// is not the scalar type represented by this implementation, or
5748    /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
5749    /// storage that has not been downloaded.
5750    fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]>;
5751
5752    /// Mutably borrow the host data from a [`Tensor`].
5753    ///
5754    /// # Examples
5755    ///
5756    /// ```
5757    /// use tenferro_tensor::{Tensor, TensorScalar};
5758    ///
5759    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
5760    /// <f64 as TensorScalar>::as_slice_mut(&mut tensor)?[0] = 3.0;
5761    ///
5762    /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
5763    /// # Ok::<(), tenferro_tensor::Error>(())
5764    /// ```
5765    /// # Errors
5766    ///
5767    /// Returns [`crate::Error::Validation`] with
5768    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
5769    /// is not the scalar type represented by this implementation, or
5770    /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
5771    /// storage that has not been downloaded.
5772    fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]>;
5773
5774    /// Extract a [`TypedTensor<Self>`] from a dynamic [`Tensor`].
5775    ///
5776    /// # Examples
5777    ///
5778    /// ```
5779    /// use tenferro_tensor::{Tensor, TensorScalar};
5780    ///
5781    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5782    /// let Ok(typed) = <f64 as TensorScalar>::into_typed(tensor) else {
5783    ///     panic!("the dtype matches by construction")
5784    /// };
5785    ///
5786    /// assert_eq!(typed.as_slice()?, &[1.0, 2.0]);
5787    /// # Ok::<(), tenferro_tensor::Error>(())
5788    /// ```
5789    /// # Errors
5790    ///
5791    /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is
5792    /// not the scalar type represented by this implementation, with
5793    /// [`crate::Error::Validation`] and
5794    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] as the cause.
5795    fn into_typed(
5796        tensor: Tensor,
5797    ) -> std::result::Result<TypedTensor<Self>, ReinterpretError<Tensor>>;
5798}
5799
5800mod private {
5801    pub trait Sealed {}
5802
5803    impl Sealed for f64 {}
5804    impl Sealed for f32 {}
5805    impl Sealed for i32 {}
5806    impl Sealed for i64 {}
5807    impl Sealed for bool {}
5808    impl Sealed for num_complex::Complex64 {}
5809    impl Sealed for num_complex::Complex32 {}
5810}
5811
5812macro_rules! impl_tensor_scalar {
5813    ($ty:ty, $real:ty, $dtype:ident, $variant:ident) => {
5814        impl TensorScalar for $ty {
5815            type Real = $real;
5816
5817            #[inline]
5818            fn dtype() -> DType {
5819                DType::$dtype
5820            }
5821
5822            fn into_default_scalars(
5823                shape: Vec<usize>,
5824                data: Vec<Self>,
5825            ) -> crate::Result<crate::DefaultScalars> {
5826                TypedTensor::<Self, DynRank, Host>::from_host_vec_col_major(shape, data).map(
5827                    |tensor| {
5828                        crate::DefaultScalars::from_payload(
5829                            crate::default_scalars::DefaultScalarsValue::$variant(tensor),
5830                        )
5831                    },
5832                )
5833            }
5834
5835            fn default_scalars_slice(set: &crate::DefaultScalars) -> Option<&[Self]> {
5836                match set.payload() {
5837                    crate::default_scalars::DefaultScalarsValue::$variant(tensor) => {
5838                        Some(tensor.as_slice())
5839                    }
5840                    _ => None,
5841                }
5842            }
5843
5844            fn default_scalars_slice_mut(set: &mut crate::DefaultScalars) -> Option<&mut [Self]> {
5845                match set.payload_mut() {
5846                    crate::default_scalars::DefaultScalarsValue::$variant(tensor) => {
5847                        Some(tensor.host_data_mut())
5848                    }
5849                    _ => None,
5850                }
5851            }
5852
5853            fn from_default_scalars(
5854                set: crate::DefaultScalars,
5855            ) -> Option<TypedTensor<Self, DynRank, Host>> {
5856                match set.into_payload() {
5857                    crate::default_scalars::DefaultScalarsValue::$variant(tensor) => Some(tensor),
5858                    _ => None,
5859                }
5860            }
5861
5862            fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor> {
5863                TypedTensor::from_vec_col_major(shape, data).map(Self::typed_tensor_into_tensor)
5864            }
5865
5866            fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor {
5867                Tensor {
5868                    payload: TensorPayload::Native(PresetTensor::$variant(tensor)),
5869                }
5870            }
5871
5872            fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_> {
5873                TensorRead::from_view(TensorView::$variant(tensor.as_view()))
5874            }
5875
5876            #[inline]
5877            fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a> {
5878                TensorView::$variant(view)
5879            }
5880
5881            #[inline]
5882            fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a> {
5883                TensorViewMut::$variant(view)
5884            }
5885
5886            fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_> {
5887                TensorWrite::from_view(TensorViewMut::$variant(tensor.as_view_mut()))
5888            }
5889
5890            fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]> {
5891                tensor
5892                    .as_typed::<Self>()
5893                    .ok_or_else(|| {
5894                        crate::Error::validation(
5895                            "Tensor::as_slice",
5896                            ValidationError::DTypeMismatch {
5897                                expected: Self::dtype(),
5898                                actual: tensor.dtype(),
5899                            },
5900                        )
5901                    })?
5902                    .host_data()
5903            }
5904
5905            fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]> {
5906                let actual = tensor.dtype();
5907                let typed = tensor.as_typed_mut::<Self>().ok_or_else(|| {
5908                    crate::Error::validation(
5909                        "Tensor::as_slice_mut",
5910                        ValidationError::DTypeMismatch {
5911                            expected: Self::dtype(),
5912                            actual,
5913                        },
5914                    )
5915                })?;
5916                typed.host_data_mut()
5917            }
5918
5919            fn into_typed(
5920                tensor: Tensor,
5921            ) -> std::result::Result<TypedTensor<Self>, ReinterpretError<Tensor>> {
5922                let actual = tensor.dtype();
5923                match tensor.payload {
5924                    TensorPayload::Native(PresetTensor::$variant(typed)) => Ok(typed),
5925                    payload => Err(ReinterpretError::new(
5926                        Tensor { payload },
5927                        crate::Error::validation(
5928                            "TensorScalar::into_typed",
5929                            ValidationError::DTypeMismatch {
5930                                expected: Self::dtype(),
5931                                actual,
5932                            },
5933                        ),
5934                    )),
5935                }
5936            }
5937        }
5938    };
5939}
5940
5941impl_tensor_scalar!(f64, f64, F64, F64);
5942impl_tensor_scalar!(f32, f32, F32, F32);
5943impl_tensor_scalar!(i64, i64, I64, I64);
5944impl_tensor_scalar!(i32, i32, I32, I32);
5945impl_tensor_scalar!(bool, bool, Bool, Bool);
5946impl_tensor_scalar!(Complex64, f64, C64, C64);
5947impl_tensor_scalar!(Complex32, f32, C32, C32);
5948
5949/// Dynamic tensor enum over the supported scalar types.
5950///
5951/// The enum keeps dtype dynamic and rank dynamic. Use
5952/// [`TypedTensor<T, R>`](TypedTensor) directly when the scalar type or rank
5953/// should be represented in Rust's type system.
5954///
5955/// # Examples
5956///
5957/// ```rust
5958/// use tenferro_tensor::{Tensor, TypedTensor};
5959///
5960/// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
5961/// assert_eq!(t.shape(), &[2]);
5962///
5963/// let erased = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 2.0]).unwrap();
5964/// assert_eq!(erased.shape().len(), 2);
5965/// ```
5966/// The owning payload behind the erased [`Tensor`].
5967///
5968/// `Native` stores a preset scalar owner directly; `External` retains its
5969/// existing explicit caller-owned host payload.
5970#[derive(Debug)]
5971enum TensorPayload {
5972    Native(PresetTensor),
5973    External(crate::ErasedHostTensor, Placement),
5974}
5975
5976#[derive(Debug)]
5977enum PresetTensor {
5978    F32(TypedTensor<f32>),
5979    F64(TypedTensor<f64>),
5980    I32(TypedTensor<i32>),
5981    I64(TypedTensor<i64>),
5982    Bool(TypedTensor<bool>),
5983    C32(TypedTensor<Complex32>),
5984    C64(TypedTensor<Complex64>),
5985}
5986
5987macro_rules! with_preset {
5988    ($preset:expr, |$value:ident| $body:expr) => {
5989        match $preset {
5990            PresetTensor::F32($value) => $body,
5991            PresetTensor::F64($value) => $body,
5992            PresetTensor::I32($value) => $body,
5993            PresetTensor::I64($value) => $body,
5994            PresetTensor::Bool($value) => $body,
5995            PresetTensor::C32($value) => $body,
5996            PresetTensor::C64($value) => $body,
5997        }
5998    };
5999}
6000
6001/// Dynamic tensor over the supported scalar types.
6002///
6003/// The erased tensor keeps dtype and rank dynamic: each preset scalar retains its
6004/// typed owner without constructing a group, while caller-owned scalars remain
6005/// `External` payloads recovered by their own type. Use [`TypedTensor<T, R>`](TypedTensor)
6006/// directly when the scalar type or rank should be represented in Rust's type system.
6007///
6008/// # Examples
6009///
6010/// ```
6011/// use tenferro_tensor::{DType, Tensor};
6012///
6013/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
6014/// assert_eq!(tensor.dtype(), DType::F64);
6015/// # Ok::<(), tenferro_tensor::Error>(())
6016/// ```
6017#[derive(Debug)]
6018pub struct Tensor {
6019    payload: TensorPayload,
6020}
6021
6022impl<R: TensorRank> OwnedTensorGroup<R> {
6023    /// Whether this group's descriptor names a non-CPU provider.
6024    fn is_backend_buffer(&self) -> bool {
6025        !matches!(
6026            self.group.provider_kind(self.slot),
6027            None | Some(crate::storage::ProviderKind::Cpu)
6028        )
6029    }
6030}
6031
6032impl Tensor {
6033    /// Carry an externally defined scalar as a caller-owned payload.
6034    ///
6035    /// The payload keeps its own element type and is recovered by that type, so no
6036    /// bytes are reinterpreted. Placement defaults to unpinned host memory, which
6037    /// is where a caller-owned payload lives.
6038    ///
6039    /// # Examples
6040    ///
6041    /// ```rust
6042    /// use tenferro_tensor::{DType, Tensor};
6043    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
6044    ///
6045    /// let payload = ErasedHostTensor::new(
6046    ///     TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
6047    /// );
6048    /// let element = payload.element_type_id();
6049    /// let tensor = Tensor::external(payload);
6050    /// assert_eq!(tensor.dtype(), DType::External(element));
6051    /// assert_eq!(tensor.shape(), &[1]);
6052    /// # Ok::<(), tenferro_tensor::Error>(())
6053    /// ```
6054    #[must_use]
6055    pub fn external(payload: crate::ErasedHostTensor) -> Self {
6056        Self {
6057            payload: TensorPayload::External(payload, Placement::default()),
6058        }
6059    }
6060
6061    /// Build a tensor from a typed one, without naming its variant.
6062    ///
6063    /// A call site that constructs a tensor from a typed tensor should use this rather than a variant, so
6064    /// that changing how the erased representation is stored changes this function and not its 1290 call
6065    /// sites. The variants remain until the removal's last step, so both forms currently produce the same
6066    /// value.
6067    ///
6068    /// # Examples
6069    ///
6070    /// ```
6071    /// use tenferro_tensor::Tensor;
6072    ///
6073    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
6074    /// let typed = tensor.into_typed::<f64>().unwrap();
6075    /// let rebuilt = Tensor::from_typed(typed);
6076    /// assert_eq!(rebuilt.as_typed::<f64>().unwrap().shape(), &[2]);
6077    /// # Ok::<(), tenferro_tensor::Error>(())
6078    /// ```
6079    #[must_use]
6080    pub fn from_typed<T: TensorScalar>(typed: TypedTensor<T>) -> Self {
6081        T::typed_tensor_into_tensor(typed)
6082    }
6083
6084    /// Borrow the erased payload of an externally defined tensor.
6085    ///
6086    /// This is the counterpart of [`Tensor::external`] for dispatch: a table that matches on
6087    /// [`Tensor::dtype`] reaches the externally defined tag and needs the payload that tag stands
6088    /// for, just as the typed tags reach theirs through [`Tensor::as_typed`]. Every other tag
6089    /// returns `None`.
6090    ///
6091    /// # Examples
6092    ///
6093    /// ```
6094    /// use tenferro_tensor::Tensor;
6095    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
6096    ///
6097    /// let payload = ErasedHostTensor::new(
6098    ///     TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
6099    /// );
6100    /// let tensor = Tensor::external(payload);
6101    /// assert!(tensor.external_payload().is_some());
6102    /// # Ok::<(), tenferro_tensor::Error>(())
6103    /// ```
6104    #[must_use]
6105    pub fn external_payload(&self) -> Option<&crate::ErasedHostTensor> {
6106        match &self.payload {
6107            TensorPayload::External(payload, _) => Some(payload),
6108            TensorPayload::Native(_) => None,
6109        }
6110    }
6111
6112    /// Carry an externally defined payload with an explicit placement.
6113    ///
6114    /// [`Tensor::external`] defaults the placement to unpinned host memory, which is where
6115    /// a caller-owned payload normally lives; this entry point is for a caller that knows
6116    /// the placement it wants.
6117    ///
6118    /// # Examples
6119    ///
6120    /// ```
6121    /// use tenferro_tensor::{Placement, Tensor};
6122    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
6123    ///
6124    /// let payload = ErasedHostTensor::new(
6125    ///     TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
6126    /// );
6127    /// let tensor = Tensor::external_with_placement(payload, Placement::default());
6128    /// assert!(tensor.external_payload().is_some());
6129    /// # Ok::<(), tenferro_tensor::Error>(())
6130    /// ```
6131    #[must_use]
6132    pub fn external_with_placement(payload: crate::ErasedHostTensor, placement: Placement) -> Self {
6133        Self {
6134            payload: TensorPayload::External(payload, placement),
6135        }
6136    }
6137
6138    /// Mutably borrow the erased payload of an externally defined tensor.
6139    ///
6140    /// The counterpart of [`Tensor::external_payload`] for callers that update the payload
6141    /// in place, such as a mutation test that checks the copy boundary.
6142    ///
6143    /// # Examples
6144    ///
6145    /// ```
6146    /// use tenferro_tensor::Tensor;
6147    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
6148    ///
6149    /// let mut tensor = Tensor::external(ErasedHostTensor::new(
6150    ///     TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
6151    /// ));
6152    /// assert!(tensor.external_payload_mut().is_some());
6153    /// # Ok::<(), tenferro_tensor::Error>(())
6154    /// ```
6155    #[must_use]
6156    pub fn external_payload_mut(&mut self) -> Option<&mut crate::ErasedHostTensor> {
6157        match &mut self.payload {
6158            TensorPayload::External(payload, _) => Some(payload),
6159            TensorPayload::Native(_) => None,
6160        }
6161    }
6162
6163    pub(crate) fn into_group_parts(self) -> (AllocationGroup, DescriptorSlot) {
6164        match self.payload {
6165            TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.into_group_parts()),
6166            // INVARIANT: a caller-owned payload has no allocation group.
6167            TensorPayload::External(..) => {
6168                unreachable!("an externally defined payload has no allocation group")
6169            }
6170        }
6171    }
6172}
6173
6174/// Dynamic read-only borrowed tensor view.
6175///
6176/// `TensorView` keeps dtype erased while borrowing typed view metadata and
6177/// storage. Use [`TypedTensorView`] directly when the scalar type is statically
6178/// known.
6179///
6180/// # Examples
6181///
6182/// ```
6183/// use tenferro_tensor::{DType, TensorView, TypedTensorView};
6184///
6185/// let data = [1_i32, 2, 3, 4];
6186/// let typed = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data)?;
6187/// let view = TensorView::I32(typed);
6188///
6189/// assert_eq!(view.dtype(), DType::I32);
6190/// assert_eq!(view.shape(), &[2, 2]);
6191/// # Ok::<(), tenferro_tensor::Error>(())
6192/// ```
6193#[derive(Clone, Debug)]
6194pub enum TensorView<'a> {
6195    F32(TypedTensorView<'a, f32>),
6196    F64(TypedTensorView<'a, f64>),
6197    I32(TypedTensorView<'a, i32>),
6198    I64(TypedTensorView<'a, i64>),
6199    Bool(TypedTensorView<'a, bool>),
6200    C32(TypedTensorView<'a, Complex<f32>>),
6201    C64(TypedTensorView<'a, Complex<f64>>),
6202}
6203
6204/// Dynamic mutable borrowed tensor view.
6205///
6206/// `TensorViewMut` is the mutable counterpart to [`TensorView`]. It keeps the
6207/// dtype erased while preserving the typed mutable view's shape, strides, and
6208/// offset metadata.
6209///
6210/// # Examples
6211///
6212/// ```
6213/// use tenferro_tensor::{DType, TensorViewMut, TypedTensorViewMut};
6214///
6215/// let mut data = [1.0_f64, 2.0];
6216/// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?);
6217/// assert_eq!(view.dtype(), DType::F64);
6218/// # Ok::<(), tenferro_tensor::Error>(())
6219/// ```
6220#[allow(clippy::large_enum_variant)]
6221#[derive(Debug)]
6222pub enum TensorViewMut<'a> {
6223    F32(TypedTensorViewMut<'a, f32>),
6224    F64(TypedTensorViewMut<'a, f64>),
6225    I32(TypedTensorViewMut<'a, i32>),
6226    I64(TypedTensorViewMut<'a, i64>),
6227    Bool(TypedTensorViewMut<'a, bool>),
6228    C32(TypedTensorViewMut<'a, Complex<f32>>),
6229    C64(TypedTensorViewMut<'a, Complex<f64>>),
6230}
6231
6232/// Read-only tensor input accepted by synchronous eager kernels.
6233///
6234/// `TensorRead` lets kernels accept either an owned tensor reference or a
6235/// borrowed [`TensorView`] without forcing callers to materialize first.
6236/// The `View` variant preserves arbitrary strides and offsets, so kernels that
6237/// support strided reads can consume transposes, slices, and broadcasts directly.
6238///
6239/// `TensorRead` is intentionally borrowed. It is an input-dispatch type, not an
6240/// owned lazy tensor value. APIs that need to store a lazy layout result should
6241/// keep an owned base tensor plus layout metadata, then expose a `TensorRead`
6242/// only for the duration of kernel dispatch.
6243///
6244/// # Examples
6245///
6246/// ```
6247/// use tenferro_tensor::{DType, Tensor, TensorRead};
6248///
6249/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
6250/// let read = TensorRead::from_tensor(&tensor);
6251///
6252/// assert_eq!(read.dtype(), DType::F64);
6253/// assert_eq!(read.shape(), &[2]);
6254/// ```
6255// Keep borrowed views inline to avoid allocation on read-only tensor dispatch paths.
6256#[allow(clippy::large_enum_variant)]
6257#[derive(Clone, Debug)]
6258pub enum TensorRead<'a> {
6259    Tensor(&'a Tensor),
6260    View(TensorView<'a>),
6261}
6262
6263/// Mutable typed tensor output accepted by synchronous eager kernels.
6264///
6265/// `TypedTensorWrite` is the typed counterpart to [`TensorWrite`]. It accepts
6266/// either an owned compact [`TypedTensor`] or an arbitrary-strided mutable
6267/// [`TypedTensorViewMut`] without erasing the scalar type at the public API
6268/// boundary.
6269///
6270/// # Examples
6271///
6272/// ```
6273/// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
6274///
6275/// let mut data = [0.0_f64, 1.0, 0.0, 2.0];
6276/// let view = TypedTensorViewMut::from_slice([2], [2], 1, &mut data)?;
6277/// let write = TypedTensorWrite::from_view(view).into_tensor_write();
6278/// assert_eq!(write.shape(), &[2]);
6279/// assert_eq!(write.strides()?, [2]);
6280/// # Ok::<(), tenferro_tensor::Error>(())
6281/// ```
6282#[allow(clippy::large_enum_variant)]
6283#[derive(Debug)]
6284pub enum TypedTensorWrite<'a, T> {
6285    /// An owned compact typed tensor borrowed mutably for the write.
6286    Tensor(&'a mut TypedTensor<T>),
6287    /// An arbitrary-strided mutable typed tensor view.
6288    View(TypedTensorViewMut<'a, T>),
6289}
6290
6291impl<'a, T> TypedTensorWrite<'a, T> {
6292    /// Create a writable target from an owned typed tensor.
6293    ///
6294    /// # Examples
6295    ///
6296    /// ```
6297    /// use tenferro_tensor::{TypedTensor, TypedTensorWrite};
6298    ///
6299    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0])?;
6300    /// let write = TypedTensorWrite::from_tensor(&mut tensor);
6301    /// assert!(matches!(write, TypedTensorWrite::Tensor(_)));
6302    /// # Ok::<(), tenferro_tensor::Error>(())
6303    /// ```
6304    pub fn from_tensor(tensor: &'a mut TypedTensor<T>) -> Self {
6305        Self::Tensor(tensor)
6306    }
6307
6308    /// Create a writable target from a mutable typed tensor view.
6309    ///
6310    /// # Examples
6311    ///
6312    /// ```
6313    /// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
6314    ///
6315    /// let mut data = [0.0_f64, 1.0];
6316    /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
6317    /// let write = TypedTensorWrite::from_view(view);
6318    /// assert!(matches!(write, TypedTensorWrite::View(_)));
6319    /// # Ok::<(), tenferro_tensor::Error>(())
6320    /// ```
6321    pub fn from_view(view: TypedTensorViewMut<'a, T>) -> Self {
6322        Self::View(view)
6323    }
6324}
6325
6326impl<'a, T: TensorScalar> TypedTensorWrite<'a, T> {
6327    /// Erase the scalar type while preserving the output layout.
6328    ///
6329    /// # Examples
6330    ///
6331    /// ```
6332    /// use tenferro_tensor::{DType, TypedTensor, TypedTensorWrite};
6333    ///
6334    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![0.0; 2])?;
6335    /// let write = TypedTensorWrite::from_tensor(&mut tensor).into_tensor_write();
6336    /// assert_eq!(write.dtype(), DType::F64);
6337    /// assert_eq!(write.shape(), &[2]);
6338    /// # Ok::<(), tenferro_tensor::Error>(())
6339    /// ```
6340    pub fn into_tensor_write(self) -> TensorWrite<'a> {
6341        match self {
6342            Self::Tensor(tensor) => T::tensor_write(tensor),
6343            Self::View(view) => TensorWrite::from_view(T::tensor_view_mut(view)),
6344        }
6345    }
6346}
6347
6348impl<'a, T> From<&'a mut TypedTensor<T>> for TypedTensorWrite<'a, T> {
6349    fn from(tensor: &'a mut TypedTensor<T>) -> Self {
6350        Self::from_tensor(tensor)
6351    }
6352}
6353
6354impl<'a, T> From<TypedTensorViewMut<'a, T>> for TypedTensorWrite<'a, T> {
6355    fn from(view: TypedTensorViewMut<'a, T>) -> Self {
6356        Self::from_view(view)
6357    }
6358}
6359
6360/// Mutable tensor output accepted by synchronous eager kernels.
6361///
6362/// `TensorWrite` mirrors [`TensorRead`] for output dispatch: it can target an
6363/// owned compact [`Tensor`] or a borrowed mutable [`TensorViewMut`]. The target
6364/// is never resized.
6365///
6366/// # Examples
6367///
6368/// ```
6369/// use tenferro_tensor::{Tensor, TensorWrite};
6370///
6371/// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
6372/// let write = TensorWrite::from_tensor(&mut tensor);
6373/// assert_eq!(write.shape(), &[1]);
6374/// # Ok::<(), tenferro_tensor::Error>(())
6375/// ```
6376#[allow(clippy::large_enum_variant)]
6377#[derive(Debug)]
6378pub enum TensorWrite<'a> {
6379    Tensor(&'a mut Tensor),
6380    View(TensorViewMut<'a>),
6381}
6382
6383/// Owned tensor value with one move-only physical owner and metadata-only layout.
6384///
6385/// `TensorValue` is intentionally not cloneable. View transformations consume
6386/// the value and move its existing owner; [`TensorValue::duplicate`] is the
6387/// explicit boundary for creating another physical allocation.
6388///
6389/// # Examples
6390///
6391/// ```
6392/// use tenferro_tensor::{Tensor, TensorValue};
6393/// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6394/// let view = value.transpose_view([1, 0])?;
6395/// assert_eq!(view.strides(), &[2, 1]);
6396/// assert!(view.is_view());
6397/// # Ok::<(), tenferro_tensor::Error>(())
6398/// ```
6399#[derive(Debug)]
6400pub struct TensorValue {
6401    owner: Tensor,
6402    layout: TensorLayout<DynRank>,
6403}
6404
6405/// A consuming view transformation failed while retaining its unchanged owner.
6406///
6407/// # Examples
6408///
6409/// ```
6410/// use tenferro_tensor::{Tensor, TensorValue};
6411/// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6412/// let failure = value.try_reshape_view([3]).unwrap_err();
6413/// let (recovered, cause) = failure.into_parts();
6414/// assert_eq!(recovered.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6415/// assert!(matches!(cause, tenferro_tensor::Error::Validation { .. }));
6416/// # Ok::<(), tenferro_tensor::Error>(())
6417/// ```
6418#[derive(Debug)]
6419pub struct TensorValueViewError {
6420    value: TensorValue,
6421    source: crate::Error,
6422}
6423
6424impl TensorValueViewError {
6425    /// Return the unchanged value and the typed validation/backend error.
6426    ///
6427    /// # Examples
6428    ///
6429    /// ```
6430    /// use tenferro_tensor::{Tensor, TensorValue};
6431    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([1], vec![7.])?);
6432    /// let (value, error) = value.try_reshape_view([2]).unwrap_err().into_parts();
6433    /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[7.]);
6434    /// assert!(matches!(error, tenferro_tensor::Error::Validation { .. }));
6435    /// # Ok::<(), tenferro_tensor::Error>(())
6436    /// ```
6437    pub fn into_parts(self) -> (TensorValue, crate::Error) {
6438        (self.value, self.source)
6439    }
6440
6441    fn new(value: TensorValue, source: crate::Error) -> Self {
6442        Self { value, source }
6443    }
6444}
6445
6446impl std::fmt::Display for TensorValueViewError {
6447    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6448        std::fmt::Display::fmt(&self.source, formatter)
6449    }
6450}
6451
6452impl std::error::Error for TensorValueViewError {
6453    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
6454        Some(&self.source)
6455    }
6456}
6457
6458impl TensorValue {
6459    /// Explicitly duplicate the physical owner represented by this value.
6460    ///
6461    /// # Examples
6462    ///
6463    /// ```
6464    /// use tenferro_tensor::{Tensor, TensorValue};
6465    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6466    /// let copy = value.duplicate()?.into_tensor()?;
6467    /// assert_eq!(copy.as_slice::<f64>()?, &[3., 4.]);
6468    /// assert_eq!(value.shape(), &[2]);
6469    /// # Ok::<(), tenferro_tensor::Error>(())
6470    /// ```
6471    ///
6472    /// # Errors
6473    ///
6474    /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
6475    /// when the backend/storage owner cannot be duplicated.
6476    pub fn duplicate(&self) -> crate::Result<Self> {
6477        let tensor = duplicate_backing_storage(&self.owner)?;
6478        Self::from_parts(
6479            tensor,
6480            self.shape().to_vec(),
6481            self.strides().to_vec(),
6482            self.offset(),
6483        )
6484    }
6485
6486    /// Retain a compact tensor as a move-only value.
6487    ///
6488    /// # Examples
6489    ///
6490    /// ```
6491    /// use tenferro_tensor::{Tensor, TensorValue};
6492    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6493    /// assert!(!value.is_view());
6494    /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6495    /// # Ok::<(), tenferro_tensor::Error>(())
6496    /// ```
6497    pub fn from_tensor(tensor: Tensor) -> Self {
6498        let layout = tensor_layout(&tensor);
6499        Self {
6500            owner: tensor,
6501            layout,
6502        }
6503    }
6504
6505    /// # Errors
6506    ///
6507    /// Returns [`ValidationError::InvalidArgument`] or
6508    /// [`ValidationError::IntegerOverflow`] when the supplied layout is
6509    /// invalid for the tensor's physical buffer.
6510    ///
6511    /// # Examples
6512    ///
6513    /// ```
6514    /// use tenferro_tensor::{Tensor, TensorValue};
6515    /// let tensor = Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?;
6516    /// let view = TensorValue::from_parts(tensor, vec![2], vec![1], 2)?;
6517    /// assert_eq!(view.shape(), &[2]);
6518    /// assert_eq!(view.offset(), 2);
6519    /// # Ok::<(), tenferro_tensor::Error>(())
6520    /// ```
6521    pub fn from_parts(
6522        tensor: Tensor,
6523        shape: Vec<usize>,
6524        strides: Vec<isize>,
6525        offset: isize,
6526    ) -> crate::Result<Self> {
6527        let layout = TensorLayout::from_parts(
6528            shape_vec(&shape),
6529            stride_vec(&strides),
6530            offset,
6531            tensor_buffer_len(&tensor),
6532        )
6533        .map_err(|err| tensor_layout_error("TensorValue::from_parts", err))?;
6534        Ok(Self {
6535            owner: tensor,
6536            layout,
6537        })
6538    }
6539
6540    // INVARIANT: unchanged-owner recovery is part of the consuming ownership
6541    // contract, so this intentionally carries the large move-only error.
6542    #[doc(hidden)]
6543    /// Move the value's sole physical owner into an allocation group.
6544    ///
6545    /// This preserves metadata-only views without copying. The consumed value
6546    /// always contains one unique owner, so no compatibility fallback exists.
6547    ///
6548    /// # Errors
6549    ///
6550    /// Returns the unchanged value when descriptor publication fails because
6551    /// of [`ValidationError::InvalidArgument`] or
6552    /// [`ValidationError::IntegerOverflow`].
6553    #[allow(clippy::result_large_err)]
6554    pub fn try_into_group_parts(
6555        self,
6556    ) -> std::result::Result<(AllocationGroup, DescriptorSlot, DType, Vec<usize>), Self> {
6557        let Self { owner, layout } = self;
6558        if owner.external_payload().is_some() {
6559            // A caller-owned payload owns no allocation group, so it is returned
6560            // unchanged instead of being forced into one.
6561            return Err(Self { owner, layout });
6562        }
6563        let dtype = owner.dtype();
6564        let shape = layout.shape().to_vec();
6565        let strides = layout.strides().to_vec();
6566        let offset = layout.offset();
6567        let (group, slot) = owner.into_group_parts();
6568        match group.update_descriptor_layout(slot, shape, strides, offset) {
6569            Ok(group) => Ok((group, slot, dtype, layout.shape().to_vec())),
6570            Err((_group, _error)) => {
6571                unreachable!("TensorValue layout was validated before group ownership transfer")
6572            }
6573        }
6574    }
6575
6576    /// Consume a value with its owner's original layout and return that owner.
6577    ///
6578    /// # Examples
6579    ///
6580    /// ```
6581    /// use tenferro_tensor::{Tensor, TensorValue};
6582    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6583    /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6584    /// # Ok::<(), tenferro_tensor::Error>(())
6585    /// ```
6586    ///
6587    /// # Errors
6588    ///
6589    /// Returns [`crate::Error::Unsupported`] when the value's metadata-only
6590    /// view differs from its physical owner's layout.
6591    pub fn into_tensor(self) -> crate::Result<Tensor> {
6592        if self.layout != tensor_layout(&self.owner) {
6593            return Err(crate::Error::unsupported(
6594                "TensorValue::into_tensor",
6595                "a metadata-only view has no compact tensor owner",
6596            ));
6597        }
6598        Ok(self.owner)
6599    }
6600
6601    ///
6602    /// # Examples
6603    ///
6604    /// ```
6605    /// use tenferro_tensor::{Tensor, TensorValue};
6606    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6607    /// assert_eq!(value.as_tensor().unwrap().as_slice::<f64>()?, &[1., 2., 3., 4.]);
6608    /// assert!(value.transpose_view([1, 0])?.as_tensor().is_none());
6609    /// # Ok::<(), tenferro_tensor::Error>(())
6610    /// ```
6611    pub fn as_tensor(&self) -> Option<&Tensor> {
6612        (self.layout == tensor_layout(&self.owner)).then_some(&self.owner)
6613    }
6614
6615    ///
6616    /// # Examples
6617    ///
6618    /// ```
6619    /// use tenferro_tensor::{Tensor, TensorValue};
6620    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6621    /// assert!(!value.is_view());
6622    /// assert!(value.transpose_view([1, 0])?.is_view());
6623    /// # Ok::<(), tenferro_tensor::Error>(())
6624    /// ```
6625    pub fn is_view(&self) -> bool {
6626        self.as_tensor().is_none()
6627    }
6628
6629    ///
6630    /// # Examples
6631    ///
6632    /// ```
6633    /// use tenferro_tensor::{Tensor, TensorValue};
6634    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6635    /// assert_eq!(value.dtype(), tenferro_tensor::DType::F64);
6636    /// # Ok::<(), tenferro_tensor::Error>(())
6637    /// ```
6638    pub fn dtype(&self) -> DType {
6639        self.owner.dtype()
6640    }
6641
6642    ///
6643    /// # Examples
6644    ///
6645    /// ```
6646    /// use tenferro_tensor::{Tensor, TensorValue};
6647    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6648    /// assert_eq!(value.reshape_view([4])?.shape(), &[4]);
6649    /// # Ok::<(), tenferro_tensor::Error>(())
6650    /// ```
6651    pub fn shape(&self) -> &[usize] {
6652        self.layout.shape()
6653    }
6654
6655    ///
6656    /// # Examples
6657    ///
6658    /// ```
6659    /// use tenferro_tensor::{Tensor, TensorValue};
6660    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6661    /// assert_eq!(value.transpose_view([1, 0])?.strides(), &[2, 1]);
6662    /// # Ok::<(), tenferro_tensor::Error>(())
6663    /// ```
6664    pub fn strides(&self) -> &[isize] {
6665        self.layout.strides()
6666    }
6667
6668    ///
6669    /// # Examples
6670    ///
6671    /// ```
6672    /// use tenferro_tensor::{Tensor, TensorValue};
6673    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6674    /// assert_eq!(value.offset(), 0);
6675    /// # Ok::<(), tenferro_tensor::Error>(())
6676    /// ```
6677    pub fn offset(&self) -> isize {
6678        self.layout.offset()
6679    }
6680
6681    ///
6682    /// # Examples
6683    ///
6684    /// ```
6685    /// use tenferro_tensor::{Tensor, TensorValue};
6686    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6687    /// let view = value.tensor_view();
6688    /// assert_eq!(view.shape(), &[2, 2]);
6689    /// assert_eq!(view.dtype(), tenferro_tensor::DType::F64);
6690    /// # Ok::<(), tenferro_tensor::Error>(())
6691    /// ```
6692    pub fn tensor_view(&self) -> TensorView<'_> {
6693        tensor_view_with_layout(&self.owner, self.layout.clone())
6694    }
6695
6696    ///
6697    /// # Examples
6698    ///
6699    /// ```
6700    /// use tenferro_tensor::{Tensor, TensorValue};
6701    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6702    /// let read = value.tensor_read();
6703    /// assert_eq!(read.shape(), &[2, 2]);
6704    /// assert_eq!(read.dtype(), tenferro_tensor::DType::F64);
6705    /// # Ok::<(), tenferro_tensor::Error>(())
6706    /// ```
6707    pub fn tensor_read(&self) -> TensorRead<'_> {
6708        self.as_tensor()
6709            .map(TensorRead::from_tensor)
6710            .unwrap_or_else(|| TensorRead::from_view(self.tensor_view()))
6711    }
6712
6713    /// # Errors
6714    ///
6715    /// Returns [`crate::Error::Validation`] with
6716    /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
6717    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
6718    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
6719    /// not a valid permutation of the value rank.
6720    ///
6721    /// # Examples
6722    ///
6723    /// ```
6724    /// use tenferro_tensor::{Tensor, TensorValue};
6725    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6726    /// let view = value.transpose_view([1, 0])?;
6727    /// assert_eq!(view.strides(), &[2, 1]);
6728    /// assert!(view.is_view());
6729    /// # Ok::<(), tenferro_tensor::Error>(())
6730    /// ```
6731    pub fn transpose_view(self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
6732        let layout = self
6733            .layout
6734            .transpose_view(axes)
6735            .map_err(|err| tensor_layout_error("TensorValue::transpose_view", err))?;
6736        Ok(Self {
6737            owner: self.owner,
6738            layout,
6739        })
6740    }
6741
6742    // INVARIANT: unchanged-owner recovery is part of the consuming view
6743    // contract, so this intentionally carries the large move-only error.
6744    /// # Errors
6745    ///
6746    /// Returns [`crate::Error::Validation`] with
6747    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
6748    /// the source is not compact column-major,
6749    /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
6750    /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
6751    /// records the counts) when element counts differ,
6752    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
6753    /// arithmetic overflow, or
6754    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6755    /// reshaped view exceeds the backing buffer.
6756    #[allow(clippy::result_large_err)]
6757    ///
6758    /// # Examples
6759    ///
6760    /// ```
6761    /// use tenferro_tensor::{Tensor, TensorValue};
6762    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6763    /// let (recovered, error) = value.try_reshape_view([3]).unwrap_err().into_parts();
6764    /// assert_eq!(recovered.into_tensor()?.as_slice::<f64>()?, &[1., 2., 3., 4.]);
6765    /// assert!(matches!(error, tenferro_tensor::Error::Validation { .. }));
6766    /// # Ok::<(), tenferro_tensor::Error>(())
6767    /// ```
6768    pub fn try_reshape_view(
6769        self,
6770        shape: impl tenferro_tensor_core::IntoShapeVec,
6771    ) -> std::result::Result<Self, TensorValueViewError> {
6772        let shape = shape.into_shape_vec();
6773        let layout = match reshape_layout_dyn(
6774            &self.layout,
6775            &shape,
6776            tensor_buffer_len(&self.owner),
6777            "TensorValue::reshape_view",
6778        ) {
6779            Ok(layout) => layout,
6780            Err(error) => return Err(TensorValueViewError::new(self, error)),
6781        };
6782        Ok(Self {
6783            owner: self.owner,
6784            layout,
6785        })
6786    }
6787
6788    /// # Errors
6789    ///
6790    /// Returns [`crate::Error::Validation`] with
6791    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]
6792    /// when the source is not compact, [`tenferro_tensor_core::ValidationError::ShapeMismatch`]
6793    /// when element counts differ, or [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
6794    /// / [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] for invalid
6795    /// target-shape arithmetic or bounds.
6796    ///
6797    /// # Examples
6798    ///
6799    /// ```
6800    /// use tenferro_tensor::{Tensor, TensorValue};
6801    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6802    /// let view = value.reshape_view([4])?;
6803    /// assert_eq!(view.shape(), &[4]);
6804    /// assert_eq!(view.strides(), &[1]);
6805    /// # Ok::<(), tenferro_tensor::Error>(())
6806    /// ```
6807    pub fn reshape_view(
6808        self,
6809        shape: impl tenferro_tensor_core::IntoShapeVec,
6810    ) -> crate::Result<Self> {
6811        self.try_reshape_view(shape).map_err(|error| error.source)
6812    }
6813
6814    /// # Errors
6815    ///
6816    /// Returns [`crate::Error::Validation`] with
6817    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a slice
6818    /// vector does not match the value rank,
6819    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when a bound
6820    /// or stride cannot be represented or is invalid,
6821    /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
6822    /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for slice
6823    /// parameters, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
6824    /// for slice arithmetic overflow, or
6825    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6826    /// result exceeds the backing buffer.
6827    ///
6828    /// # Examples
6829    ///
6830    /// ```
6831    /// use tenferro_tensor::{Tensor, TensorValue};
6832    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6833    /// let view = value.slice_view(&tenferro_tensor::SliceConfig {
6834    ///    starts: vec![0, 1], limits: vec![2, 2], strides: vec![1, 1],
6835    /// })?;
6836    /// assert_eq!(view.shape(), &[2, 1]);
6837    /// assert_eq!(view.offset(), 2);
6838    /// # Ok::<(), tenferro_tensor::Error>(())
6839    /// ```
6840    pub fn slice_view(self, config: &SliceConfig) -> crate::Result<Self> {
6841        let op = "TensorValue::slice_view";
6842        if config.starts.len() != self.shape().len()
6843            || config.limits.len() != self.shape().len()
6844            || config.strides.len() != self.shape().len()
6845        {
6846            return Err(crate::Error::validation(
6847                op,
6848                ValidationError::RankMismatch {
6849                    expected: self.shape().len(),
6850                    actual: config.starts.len(),
6851                },
6852            ));
6853        }
6854        // The per-axis specs stay inline: a metadata-only slice must not
6855        // allocate its own scratch at rank <= 8 (#2040).
6856        let mut specs = TinyVec::<[CoreSliceSpec; 8]>::with_capacity(self.shape().len());
6857        for (((&start, &limit), &stride), &axis_len) in config
6858            .starts
6859            .iter()
6860            .zip(config.limits.iter())
6861            .zip(config.strides.iter())
6862            .zip(self.shape())
6863        {
6864            let start = isize::try_from(start).map_err(|_| {
6865                crate::Error::invalid_argument(
6866                    op,
6867                    "slice start",
6868                    "slice start does not fit in isize",
6869                )
6870            })?;
6871            let limit = isize::try_from(limit).map_err(|_| {
6872                crate::Error::invalid_argument(
6873                    op,
6874                    "slice limit",
6875                    "slice limit does not fit in isize",
6876                )
6877            })?;
6878            let stride = isize::try_from(stride).map_err(|_| {
6879                crate::Error::invalid_argument(
6880                    op,
6881                    "slice stride",
6882                    "slice stride does not fit in isize",
6883                )
6884            })?;
6885            specs.push(core_slice_spec(
6886                StridedSliceSpec::new(start, Some(limit), stride),
6887                axis_len,
6888                op,
6889            )?);
6890        }
6891        let layout = self
6892            .layout
6893            .slice_view(&specs, tensor_buffer_len(&self.owner))
6894            .map_err(|err| tensor_layout_error(op, err))?;
6895        Ok(Self {
6896            owner: self.owner,
6897            layout,
6898        })
6899    }
6900
6901    /// # Errors
6902    ///
6903    /// Returns [`crate::Error::Validation`] with
6904    /// [`tenferro_tensor_core::ValidationError::RankMismatch`],
6905    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
6906    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] for invalid
6907    /// dimension mappings,
6908    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] for
6909    /// incompatible extents,
6910    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6911    /// result exceeds the backing buffer, or
6912    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
6913    /// arithmetic overflow.
6914    ///
6915    /// # Examples
6916    ///
6917    /// ```
6918    /// use tenferro_tensor::{Tensor, TensorValue};
6919    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6920    /// let view = value.broadcast_in_dim_view([2, 3], [0])?;
6921    /// assert_eq!(view.shape(), &[2, 3]);
6922    /// assert_eq!(view.strides(), &[1, 0]);
6923    /// # Ok::<(), tenferro_tensor::Error>(())
6924    /// ```
6925    pub fn broadcast_in_dim_view(
6926        self,
6927        shape: impl tenferro_tensor_core::IntoShapeVec,
6928        dims: impl AsRef<[usize]>,
6929    ) -> crate::Result<Self> {
6930        let shape = shape.into_shape_vec();
6931        let layout = self
6932            .layout
6933            .broadcast_in_dim_view::<DynRank>(shape, dims, tensor_buffer_len(&self.owner))
6934            .map_err(|err| tensor_layout_error("TensorValue::broadcast_in_dim_view", err))?;
6935        Ok(Self {
6936            owner: self.owner,
6937            layout,
6938        })
6939    }
6940}
6941
6942/// Copy a value's backing storage into a fresh owned tensor carrying `layout`.
6943///
6944/// A value layout is relative to its owner's backing root. For a plain compact
6945/// owner that root is the owner itself, and [`TypedTensor::duplicate`] is
6946/// enough. A root-preserving extraction (#2023) exports only its descriptor
6947/// extent over a wider root, so the copy has to carry the root and be rebased
6948/// onto the same descriptor layout.
6949fn duplicate_backing_storage(tensor: &Tensor) -> crate::Result<Tensor> {
6950    fn typed<T: TensorScalar>(tensor: &TypedTensor<T>) -> crate::Result<Tensor> {
6951        // The owner's descriptor, not the outer value layout: the copy is an
6952        // owner of the same kind, and a value layout may be a strided view over
6953        // it that the owner must not adopt.
6954        let layout = tensor.physical_layout();
6955        // `as_view().host_storage()` reports the whole root for a host owner
6956        // (including one produced by an owning reinterpretation) and refuses a
6957        // backend owner, which `buffer()` cannot express.
6958        let root = tensor.as_view().host_storage()?.to_vec();
6959        let op = "TensorValue::duplicate";
6960        let (group, slot) =
6961            AllocationGroup::from_host_vec::<T, DynRank>(shape_vec(&[root.len()]), root)
6962                .map_err(|error| crate::Error::runtime_state_source(op, error))?;
6963        let group = group
6964            .update_descriptor_layout(
6965                slot,
6966                layout.shape().to_vec(),
6967                layout.strides().to_vec(),
6968                layout.offset(),
6969            )
6970            .map_err(|(_, error)| crate::Error::runtime_state_source(op, error))?;
6971        let allocation = group
6972            .allocation_index(slot)
6973            .unwrap_or_else(|_| unreachable!("the duplicate group carries a live descriptor"));
6974        let descriptor_layout = group
6975            .descriptor_layout(slot)
6976            .unwrap_or_else(|| unreachable!("the duplicate group carries a live descriptor"));
6977        let placement = tensor.placement().clone();
6978        Ok(tensor_from_group(
6979            group,
6980            slot,
6981            allocation,
6982            T::dtype(),
6983            descriptor_layout,
6984            placement,
6985        ))
6986    }
6987
6988    match &tensor.payload {
6989        // A caller-owned payload keeps its existing duplication route: it has no
6990        // allocation group to rebuild.
6991        TensorPayload::External(..) => tensor.duplicate(),
6992        TensorPayload::Native(preset) => with_preset!(preset, |inner| typed(inner)),
6993    }
6994}
6995
6996fn tensor_layout(tensor: &Tensor) -> TensorLayout<DynRank> {
6997    match &tensor.payload {
6998        // A caller-owned payload carries its own view layout, so strides and offset
6999        // come from the payload rather than from a compact assumption.
7000        TensorPayload::External(payload, _) => TensorLayout::<DynRank>::from_parts(
7001            shape_vec(payload.shape()),
7002            stride_vec(payload.strides()),
7003            payload.offset(),
7004            payload.payload_element_count(),
7005        )
7006        .unwrap_or_else(|_| {
7007            // INVARIANT: a payload is built from a validated host tensor, and the
7008            // only layout change is a permutation, so its strides stay inside the
7009            // payload it was created from and this construction cannot fail.
7010            unreachable!("a validated payload yields a layout inside its own storage")
7011        }),
7012        TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.physical_layout()),
7013    }
7014}
7015
7016fn tensor_buffer_len(tensor: &Tensor) -> usize {
7017    match &tensor.payload {
7018        // A caller-owned payload stores its element count directly.
7019        TensorPayload::External(payload, _) => payload.element_count(),
7020        TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.buffer_len()),
7021    }
7022}
7023
7024fn prepare_backend_access<'a, T: 'static, R: TensorRank>(
7025    buffer: &'a dyn BackendStorage<T>,
7026    layout: &'a TensorLayout<R>,
7027    op: &'static str,
7028) -> crate::Result<Box<dyn PreparedDeviceAccess + 'a>> {
7029    let domain = buffer.allocation_domain().ok_or_else(|| {
7030        crate::Error::runtime_state(op, "backend buffer is missing an allocation domain")
7031    })?;
7032    let allocation_id = buffer.allocation_id().ok_or_else(|| {
7033        crate::Error::runtime_state(op, "backend buffer is missing an allocation identity")
7034    })?;
7035    let byte_len = buffer
7036        .len()
7037        .checked_mul(size_of::<T>())
7038        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
7039    let request = DeviceAccessRequest::new(
7040        domain,
7041        allocation_id,
7042        byte_len,
7043        size_of::<T>(),
7044        layout.shape(),
7045        layout.strides(),
7046        layout.offset(),
7047    );
7048    buffer
7049        .prepare_device_access(request)
7050        .map_err(|error| crate::Error::runtime_state_source(op, error))
7051}
7052
7053fn cast_view_slice<S: 'static, T: TensorScalar>(source: &[S]) -> crate::Result<&[T]> {
7054    if size_of::<S>() != size_of::<T>() || align_of::<S>() != align_of::<T>() {
7055        return Err(crate::Error::invalid_argument(
7056            "TensorView::as_slice",
7057            "dtype",
7058            "matching dtypes must have identical scalar layout",
7059        ));
7060    }
7061    // SAFETY: the dtype check above is exhaustive over the sealed scalar set;
7062    // equal size/alignment preserve the element boundaries and the source
7063    // slice remains borrowed for the returned lifetime.
7064    Ok(unsafe { std::slice::from_raw_parts(source.as_ptr().cast::<T>(), source.len()) })
7065}
7066
7067/// Re-layout a borrowed typed view while keeping its buffer, retained root and
7068/// placement.
7069///
7070/// #2143 item 1: an eager implicit broadcast is a metadata change, so the view
7071/// keeps reading the same storage through zero strides instead of copying the
7072/// broadcast tensor into a fresh allocation.
7073fn broadcast_typed_view<'a, T: 'static, R: TensorRank>(
7074    view: TypedTensorView<'a, T, R>,
7075    shape: &[usize],
7076    dims: &[usize],
7077) -> crate::Result<TypedTensorView<'a, T, R>> {
7078    let shape = R::shape_from_vec(shape_vec(shape))
7079        .map_err(|err| tensor_layout_error("TensorRead::broadcast_in_dim_view", err))?;
7080    let layout = view
7081        .layout()
7082        .broadcast_in_dim_view::<R>(shape, dims, view.backing_len())
7083        .map_err(|err| tensor_layout_error("TensorRead::broadcast_in_dim_view", err))?;
7084    Ok(TypedTensorView {
7085        buffer: view.buffer,
7086        root: view.root,
7087        layout,
7088        placement: view.placement,
7089        _representation: std::marker::PhantomData,
7090    })
7091}
7092
7093fn tensor_view_with_layout(tensor: &Tensor, layout: TensorLayout<DynRank>) -> TensorView<'_> {
7094    match tensor.dtype() {
7095        DType::F32 => TensorView::F32(typed_view_with_layout(
7096            tensor
7097                .as_typed::<f32>()
7098                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7099            layout,
7100        )),
7101        DType::F64 => TensorView::F64(typed_view_with_layout(
7102            tensor
7103                .as_typed::<f64>()
7104                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7105            layout,
7106        )),
7107        DType::I32 => TensorView::I32(typed_view_with_layout(
7108            tensor
7109                .as_typed::<i32>()
7110                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7111            layout,
7112        )),
7113        DType::I64 => TensorView::I64(typed_view_with_layout(
7114            tensor
7115                .as_typed::<i64>()
7116                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7117            layout,
7118        )),
7119        DType::Bool => TensorView::Bool(typed_view_with_layout(
7120            tensor
7121                .as_typed::<bool>()
7122                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7123            layout,
7124        )),
7125        DType::C32 => TensorView::C32(typed_view_with_layout(
7126            tensor
7127                .as_typed::<Complex<f32>>()
7128                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7129            layout,
7130        )),
7131        DType::C64 => TensorView::C64(typed_view_with_layout(
7132            tensor
7133                .as_typed::<Complex<f64>>()
7134                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7135            layout,
7136        )),
7137        // INVARIANT: `TensorView` has no externally defined variant, and a view is
7138        // never requested for a caller-owned payload.
7139        DType::External(_) => unreachable!("views cover the preset scalars"),
7140    }
7141}
7142
7143fn typed_view_with_layout<T: TensorScalar + 'static>(
7144    tensor: &TypedTensor<T>,
7145    layout: TensorLayout<DynRank>,
7146) -> TypedTensorView<'_, T> {
7147    match &tensor.storage {
7148        DynamicStorage::Host(HostStorage { data }) => TypedTensorView {
7149            buffer: TensorStorageRef::Host(data.as_slice()),
7150            root: None,
7151            layout,
7152            placement: tensor.placement.clone(),
7153            _representation: std::marker::PhantomData,
7154        },
7155        DynamicStorage::Group(core) => {
7156            let root = core.group.view::<T>().unwrap_or_else(|error| {
7157                unreachable!("typed tensor group descriptor mismatch: {error}")
7158            });
7159            let buffer = if let Some(allocation) = root.backend_allocation() {
7160                TensorStorageRef::Root(allocation)
7161            } else {
7162                TensorStorageRef::Host(tensor.group_host_slice())
7163            };
7164            TypedTensorView {
7165                buffer,
7166                root: Some(root),
7167                layout,
7168                placement: tensor.placement.clone(),
7169                _representation: std::marker::PhantomData,
7170            }
7171        }
7172    }
7173}
7174
7175pub(crate) fn tensor_view_from_group<'a, T: TensorScalar>(
7176    view: GroupReadView<'a, T, DynRank>,
7177) -> crate::Result<TensorView<'a>> {
7178    let buffer = if let Some(allocation) = view.backend_allocation() {
7179        TensorStorageRef::Root(allocation)
7180    } else {
7181        let storage = view.storage_buffer();
7182        match storage {
7183            Some(StorageBuffer::Host(data)) => TensorStorageRef::Host(data),
7184            Some(StorageBuffer::Backend(buffer)) => TensorStorageRef::Backend(buffer.as_ref()),
7185            // A sealed representation reinterpretation changes the descriptor's
7186            // dtype while the host root keeps its own, so the typed downcast
7187            // above cannot see it.
7188            None => match view.host_root_slice() {
7189                Some(data) => TensorStorageRef::Host(data),
7190                None => {
7191                    return Err(crate::Error::runtime_state(
7192                        "AllocationGroup::tensor_read",
7193                        "group descriptor has no backing storage",
7194                    ));
7195                }
7196            },
7197        }
7198    };
7199    let layout = view.descriptor().layout().clone();
7200    let placement = view.descriptor().placement().clone();
7201    let typed = TypedTensorView {
7202        buffer,
7203        root: Some(view.clone()),
7204        layout,
7205        placement,
7206        _representation: std::marker::PhantomData,
7207    };
7208    Ok(T::tensor_view(typed))
7209}
7210
7211pub(crate) fn tensor_from_group(
7212    group: AllocationGroup,
7213    slot: DescriptorSlot,
7214    allocation_index: AllocationSlot,
7215    dtype: DType,
7216    layout: TensorLayout<DynRank>,
7217    placement: Placement,
7218) -> Tensor {
7219    fn typed<T: TensorScalar>(
7220        group: AllocationGroup,
7221        slot: DescriptorSlot,
7222        allocation_index: AllocationSlot,
7223        layout: TensorLayout<DynRank>,
7224        placement: Placement,
7225    ) -> TypedTensor<T> {
7226        let descriptor_dtype = group
7227            .descriptor_dtype(slot)
7228            .unwrap_or_else(|| unreachable!("tensor_from_group requires a live typed descriptor"));
7229        assert_eq!(
7230            descriptor_dtype,
7231            T::dtype(),
7232            "managed typed owner must match its descriptor dtype",
7233        );
7234        let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
7235        TypedTensor {
7236            shape: shape_vec(layout.shape()),
7237            placement,
7238            layout: stride_vec(layout.strides()),
7239            storage: DynamicStorage::Group(GroupStorage {
7240                group: Box::new(OwnedTensorGroup {
7241                    group,
7242                    slot,
7243                    allocation_index,
7244                    host_ptr,
7245                    host_byte_len,
7246                    _rank: PhantomData,
7247                }),
7248            }),
7249        }
7250    }
7251
7252    match dtype {
7253        DType::F32 => <f32 as TensorScalar>::typed_tensor_into_tensor(typed::<f32>(
7254            group,
7255            slot,
7256            allocation_index,
7257            layout,
7258            placement,
7259        )),
7260        DType::F64 => <f64 as TensorScalar>::typed_tensor_into_tensor(typed::<f64>(
7261            group,
7262            slot,
7263            allocation_index,
7264            layout,
7265            placement,
7266        )),
7267        DType::I32 => <i32 as TensorScalar>::typed_tensor_into_tensor(typed::<i32>(
7268            group,
7269            slot,
7270            allocation_index,
7271            layout,
7272            placement,
7273        )),
7274        DType::I64 => <i64 as TensorScalar>::typed_tensor_into_tensor(typed::<i64>(
7275            group,
7276            slot,
7277            allocation_index,
7278            layout,
7279            placement,
7280        )),
7281        DType::Bool => <bool as TensorScalar>::typed_tensor_into_tensor(typed::<bool>(
7282            group,
7283            slot,
7284            allocation_index,
7285            layout,
7286            placement,
7287        )),
7288        DType::C32 => <Complex<f32> as TensorScalar>::typed_tensor_into_tensor(
7289            typed::<Complex<f32>>(group, slot, allocation_index, layout, placement),
7290        ),
7291        DType::C64 => <Complex<f64> as TensorScalar>::typed_tensor_into_tensor(
7292            typed::<Complex<f64>>(group, slot, allocation_index, layout, placement),
7293        ),
7294        // INVARIANT: an allocation group is created from a sealed preset scalar,
7295        // so it can never carry an externally defined one. External payloads are
7296        // caller-owned and do not enter a group.
7297        DType::External(_) => unreachable!("allocation groups are preset-typed"),
7298    }
7299}
7300
7301/// Wrap an `f64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7302///
7303/// # Examples
7304///
7305/// ```
7306/// use tenferro_tensor::{Tensor, TypedTensor};
7307///
7308/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
7309/// let tensor: Tensor = typed.into();
7310/// assert_eq!(tensor.shape(), &[2]);
7311/// ```
7312impl From<TypedTensor<f64>> for Tensor {
7313    fn from(t: TypedTensor<f64>) -> Self {
7314        Tensor::from_typed(t)
7315    }
7316}
7317
7318/// Wrap an `f32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7319///
7320/// # Examples
7321///
7322/// ```
7323/// use tenferro_tensor::{Tensor, TypedTensor};
7324///
7325/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
7326/// let tensor: Tensor = typed.into();
7327/// assert_eq!(tensor.shape(), &[2]);
7328/// ```
7329impl From<TypedTensor<f32>> for Tensor {
7330    fn from(t: TypedTensor<f32>) -> Self {
7331        Tensor::from_typed(t)
7332    }
7333}
7334
7335/// Wrap an `i64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7336///
7337/// # Examples
7338///
7339/// ```
7340/// use tenferro_tensor::{DType, Tensor, TypedTensor};
7341///
7342/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i64, 2]).unwrap();
7343/// let tensor: Tensor = typed.into();
7344/// assert_eq!(tensor.dtype(), DType::I64);
7345/// assert_eq!(tensor.shape(), &[2]);
7346/// ```
7347impl From<TypedTensor<i64>> for Tensor {
7348    fn from(t: TypedTensor<i64>) -> Self {
7349        Tensor::from_typed(t)
7350    }
7351}
7352
7353/// Wrap an `i32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7354///
7355/// # Examples
7356///
7357/// ```
7358/// use tenferro_tensor::{DType, Tensor, TypedTensor};
7359///
7360/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i32, 2]).unwrap();
7361/// let tensor: Tensor = typed.into();
7362/// assert_eq!(tensor.dtype(), DType::I32);
7363/// assert_eq!(tensor.shape(), &[2]);
7364/// ```
7365impl From<TypedTensor<i32>> for Tensor {
7366    fn from(t: TypedTensor<i32>) -> Self {
7367        Tensor::from_typed(t)
7368    }
7369}
7370
7371/// Wrap a `bool` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7372///
7373/// # Examples
7374///
7375/// ```
7376/// use tenferro_tensor::{DType, Tensor, TypedTensor};
7377///
7378/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![true, false]).unwrap();
7379/// let tensor: Tensor = typed.into();
7380/// assert_eq!(tensor.dtype(), DType::Bool);
7381/// assert_eq!(tensor.shape(), &[2]);
7382/// ```
7383impl From<TypedTensor<bool>> for Tensor {
7384    fn from(t: TypedTensor<bool>) -> Self {
7385        Tensor::from_typed(t)
7386    }
7387}
7388
7389/// Wrap a [`Complex64`] [`TypedTensor`] into the corresponding [`Tensor`]
7390/// variant.
7391///
7392/// # Examples
7393///
7394/// ```
7395/// use num_complex::Complex64;
7396/// use tenferro_tensor::{Tensor, TypedTensor};
7397///
7398/// let typed = TypedTensor::from_vec_col_major(
7399///     vec![1],
7400///     vec![Complex64::new(1.0, 2.0)],
7401/// ).unwrap();
7402/// let tensor: Tensor = typed.into();
7403/// assert_eq!(tensor.shape(), &[1]);
7404/// ```
7405impl From<TypedTensor<Complex<f64>>> for Tensor {
7406    fn from(t: TypedTensor<Complex<f64>>) -> Self {
7407        Tensor::from_typed(t)
7408    }
7409}
7410
7411/// Wrap a [`Complex32`] [`TypedTensor`] into the corresponding [`Tensor`]
7412/// variant.
7413///
7414/// # Examples
7415///
7416/// ```
7417/// use num_complex::Complex32;
7418/// use tenferro_tensor::{Tensor, TypedTensor};
7419///
7420/// let typed = TypedTensor::from_vec_col_major(
7421///     vec![1],
7422///     vec![Complex32::new(1.0, 2.0)],
7423/// ).unwrap();
7424/// let tensor: Tensor = typed.into();
7425/// assert_eq!(tensor.shape(), &[1]);
7426/// ```
7427impl From<TypedTensor<Complex<f32>>> for Tensor {
7428    fn from(t: TypedTensor<Complex<f32>>) -> Self {
7429        Tensor::from_typed(t)
7430    }
7431}
7432
7433impl<'a> TensorView<'a> {
7434    /// Create a dynamic `f32` view over compact column-major host data.
7435    ///
7436    /// # Examples
7437    ///
7438    /// ```
7439    /// use tenferro_tensor::{DType, TensorView};
7440    ///
7441    /// let data = [1.0_f32, 2.0];
7442    /// let view = TensorView::f32(&[2], &data)?;
7443    /// assert_eq!(view.dtype(), DType::F32);
7444    /// # Ok::<(), tenferro_tensor::Error>(())
7445    /// ```
7446    /// # Errors
7447    ///
7448    /// Returns [`crate::Error::Validation`] with
7449    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7450    /// shape or offset arithmetic overflow, or
7451    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7452    /// compact shape reaches beyond `data`.
7453    pub fn f32(shape: &'a [usize], data: &'a [f32]) -> crate::Result<Self> {
7454        Ok(Self::F32(TypedTensorView::from_col_major(shape, data)?))
7455    }
7456
7457    /// Create a dynamic `f64` view over compact column-major host data.
7458    ///
7459    /// # Examples
7460    ///
7461    /// ```
7462    /// use tenferro_tensor::{DType, TensorView};
7463    ///
7464    /// let data = [1.0_f64, 2.0];
7465    /// let view = TensorView::f64(&[2], &data)?;
7466    /// assert_eq!(view.dtype(), DType::F64);
7467    /// # Ok::<(), tenferro_tensor::Error>(())
7468    /// ```
7469    /// # Errors
7470    ///
7471    /// Returns [`crate::Error::Validation`] with
7472    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7473    /// shape or offset arithmetic overflow, or
7474    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7475    /// compact shape reaches beyond `data`.
7476    pub fn f64(shape: &'a [usize], data: &'a [f64]) -> crate::Result<Self> {
7477        Ok(Self::F64(TypedTensorView::from_col_major(shape, data)?))
7478    }
7479
7480    /// Create a dynamic `i64` view over compact column-major host data.
7481    ///
7482    /// # Examples
7483    ///
7484    /// ```
7485    /// use tenferro_tensor::{DType, TensorView};
7486    ///
7487    /// let data = [1_i64, 2];
7488    /// let view = TensorView::i64(&[2], &data)?;
7489    /// assert_eq!(view.dtype(), DType::I64);
7490    /// # Ok::<(), tenferro_tensor::Error>(())
7491    /// ```
7492    /// # Errors
7493    ///
7494    /// Returns [`crate::Error::Validation`] with
7495    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7496    /// shape or offset arithmetic overflow, or
7497    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7498    /// compact shape reaches beyond `data`.
7499    pub fn i64(shape: &'a [usize], data: &'a [i64]) -> crate::Result<Self> {
7500        Ok(Self::I64(TypedTensorView::from_col_major(shape, data)?))
7501    }
7502
7503    /// Create a dynamic `i32` view over compact column-major host data.
7504    ///
7505    /// # Examples
7506    ///
7507    /// ```
7508    /// use tenferro_tensor::{DType, TensorView};
7509    ///
7510    /// let data = [1_i32, 2];
7511    /// let view = TensorView::i32(&[2], &data)?;
7512    /// assert_eq!(view.dtype(), DType::I32);
7513    /// # Ok::<(), tenferro_tensor::Error>(())
7514    /// ```
7515    /// # Errors
7516    ///
7517    /// Returns [`crate::Error::Validation`] with
7518    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7519    /// shape or offset arithmetic overflow, or
7520    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7521    /// compact shape reaches beyond `data`.
7522    pub fn i32(shape: &'a [usize], data: &'a [i32]) -> crate::Result<Self> {
7523        Ok(Self::I32(TypedTensorView::from_col_major(shape, data)?))
7524    }
7525
7526    /// Create a dynamic `bool` view over compact column-major host data.
7527    ///
7528    /// # Examples
7529    ///
7530    /// ```
7531    /// use tenferro_tensor::{DType, TensorView};
7532    ///
7533    /// let data = [true, false];
7534    /// let view = TensorView::bool(&[2], &data)?;
7535    /// assert_eq!(view.dtype(), DType::Bool);
7536    /// # Ok::<(), tenferro_tensor::Error>(())
7537    /// ```
7538    /// # Errors
7539    ///
7540    /// Returns [`crate::Error::Validation`] with
7541    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7542    /// shape or offset arithmetic overflow, or
7543    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7544    /// compact shape reaches beyond `data`.
7545    pub fn bool(shape: &'a [usize], data: &'a [bool]) -> crate::Result<Self> {
7546        Ok(Self::Bool(TypedTensorView::from_col_major(shape, data)?))
7547    }
7548
7549    /// Create a dynamic `Complex32` view over compact column-major host data.
7550    ///
7551    /// # Examples
7552    ///
7553    /// ```
7554    /// use num_complex::Complex32;
7555    /// use tenferro_tensor::{DType, TensorView};
7556    ///
7557    /// let data = [Complex32::new(1.0, 2.0)];
7558    /// let view = TensorView::c32(&[1], &data)?;
7559    /// assert_eq!(view.dtype(), DType::C32);
7560    /// # Ok::<(), tenferro_tensor::Error>(())
7561    /// ```
7562    /// # Errors
7563    ///
7564    /// Returns [`crate::Error::Validation`] with
7565    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7566    /// shape or offset arithmetic overflow, or
7567    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7568    /// compact shape reaches beyond `data`.
7569    pub fn c32(shape: &'a [usize], data: &'a [Complex32]) -> crate::Result<Self> {
7570        Ok(Self::C32(TypedTensorView::from_col_major(shape, data)?))
7571    }
7572
7573    /// Create a dynamic `Complex64` view over compact column-major host data.
7574    ///
7575    /// # Examples
7576    ///
7577    /// ```
7578    /// use num_complex::Complex64;
7579    /// use tenferro_tensor::{DType, TensorView};
7580    ///
7581    /// let data = [Complex64::new(1.0, 2.0)];
7582    /// let view = TensorView::c64(&[1], &data)?;
7583    /// assert_eq!(view.dtype(), DType::C64);
7584    /// # Ok::<(), tenferro_tensor::Error>(())
7585    /// ```
7586    /// # Errors
7587    ///
7588    /// Returns [`crate::Error::Validation`] with
7589    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7590    /// shape or offset arithmetic overflow, or
7591    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7592    /// compact shape reaches beyond `data`.
7593    pub fn c64(shape: &'a [usize], data: &'a [Complex64]) -> crate::Result<Self> {
7594        Ok(Self::C64(TypedTensorView::from_col_major(shape, data)?))
7595    }
7596
7597    /// Return the element dtype of this borrowed view.
7598    ///
7599    /// # Examples
7600    ///
7601    /// ```
7602    /// use tenferro_tensor::{DType, TensorView};
7603    ///
7604    /// let view = TensorView::f64(&[2], &[1.0, 2.0])?;
7605    /// assert_eq!(view.dtype(), DType::F64);
7606    /// # Ok::<(), tenferro_tensor::Error>(())
7607    /// ```
7608    pub fn dtype(&self) -> DType {
7609        match self {
7610            Self::F32(_) => DType::F32,
7611            Self::F64(_) => DType::F64,
7612            Self::I32(_) => DType::I32,
7613            Self::I64(_) => DType::I64,
7614            Self::Bool(_) => DType::Bool,
7615            Self::C32(_) => DType::C32,
7616            Self::C64(_) => DType::C64,
7617        }
7618    }
7619
7620    /// Return the logical shape of this borrowed view.
7621    ///
7622    /// # Examples
7623    ///
7624    /// ```
7625    /// use tenferro_tensor::TensorView;
7626    ///
7627    /// let view = TensorView::i32(&[2, 1], &[1, 2])?;
7628    /// assert_eq!(view.shape(), &[2, 1]);
7629    /// # Ok::<(), tenferro_tensor::Error>(())
7630    /// ```
7631    pub fn shape(&self) -> &[usize] {
7632        match self {
7633            Self::F32(t) => t.shape(),
7634            Self::F64(t) => t.shape(),
7635            Self::I32(t) => t.shape(),
7636            Self::I64(t) => t.shape(),
7637            Self::Bool(t) => t.shape(),
7638            Self::C32(t) => t.shape(),
7639            Self::C64(t) => t.shape(),
7640        }
7641    }
7642
7643    /// Borrow a contiguous host slice when the requested scalar matches this view's dtype.
7644    ///
7645    /// Backend buffers and non-contiguous views return an explicit error. No
7646    /// download or materialization is performed.
7647    ///
7648    /// # Errors
7649    ///
7650    /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
7651    /// view dtype, [`ValidationError::NonContiguousViewAsSlice`] for a
7652    /// non-contiguous layout, or [`crate::Error::HostAccess`] for unavailable
7653    /// backend host access.
7654    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
7655        if self.dtype() != T::dtype() {
7656            return Err(crate::Error::validation(
7657                "TensorView::as_slice",
7658                ValidationError::DTypeMismatch {
7659                    expected: T::dtype(),
7660                    actual: self.dtype(),
7661                },
7662            ));
7663        }
7664        match self {
7665            Self::F32(view) => cast_view_slice(view.as_slice()?),
7666            Self::F64(view) => cast_view_slice(view.as_slice()?),
7667            Self::I32(view) => cast_view_slice(view.as_slice()?),
7668            Self::I64(view) => cast_view_slice(view.as_slice()?),
7669            Self::Bool(view) => cast_view_slice(view.as_slice()?),
7670            Self::C32(view) => cast_view_slice(view.as_slice()?),
7671            Self::C64(view) => cast_view_slice(view.as_slice()?),
7672        }
7673    }
7674
7675    /// Reinterpret a complex view as its sealed real representation.
7676    ///
7677    /// # Examples
7678    ///
7679    /// ```
7680    /// use tenferro_tensor::{Complex64, DType, TensorView};
7681    ///
7682    /// let data = [Complex64::new(1.0, 2.0)];
7683    /// let real = TensorView::c64(&[1], &data)?.as_real_view()?;
7684    /// assert_eq!(real.dtype(), DType::F64);
7685    /// assert_eq!(real.as_slice::<f64>()?, &[1.0, 2.0]);
7686    /// # Ok::<(), tenferro_tensor::Error>(())
7687    /// ```
7688    ///
7689    /// # Errors
7690    ///
7691    /// Returns [`crate::Error::Unsupported`] for the wrong dtype pair and
7692    /// [`ValidationError::InvalidArgument`] or
7693    /// [`ValidationError::ViewOutOfBounds`] for invalid layout metadata.
7694    pub fn as_real_view(&self) -> crate::Result<Self> {
7695        match self {
7696            Self::C32(t) => t.as_real_view().map(Self::F32),
7697            Self::C64(t) => t.as_real_view().map(Self::F64),
7698            _ => Err(crate::Error::unsupported(
7699                "TensorView::as_real_view",
7700                "only complex views have a sealed real representation",
7701            )),
7702        }
7703    }
7704
7705    /// Reinterpret a real view as its sealed complex representation.
7706    ///
7707    /// # Examples
7708    ///
7709    /// ```
7710    /// use tenferro_tensor::{Complex64, DType, TensorView};
7711    ///
7712    /// let data = [1.0_f64, 2.0];
7713    /// let complex = TensorView::f64(&[2, 1], &data)?.as_complex_view()?;
7714    /// assert_eq!(complex.dtype(), DType::C64);
7715    /// assert_eq!(complex.as_slice::<Complex64>()?, &[Complex64::new(1.0, 2.0)]);
7716    /// # Ok::<(), tenferro_tensor::Error>(())
7717    /// ```
7718    ///
7719    /// # Errors
7720    ///
7721    /// Returns [`crate::Error::Unsupported`] for the wrong dtype pair and
7722    /// [`ValidationError::InvalidArgument`] or
7723    /// [`ValidationError::ViewOutOfBounds`] for invalid layout metadata.
7724    pub fn as_complex_view(&self) -> crate::Result<Self> {
7725        match self {
7726            Self::F32(t) => t.as_complex_view().map(Self::C32),
7727            Self::F64(t) => t.as_complex_view().map(Self::C64),
7728            _ => Err(crate::Error::unsupported(
7729                "TensorView::as_complex_view",
7730                "only real views have a sealed complex representation",
7731            )),
7732        }
7733    }
7734
7735    /// Return the placement metadata carried by this borrowed view.
7736    ///
7737    /// # Examples
7738    ///
7739    /// ```
7740    /// use tenferro_tensor::{MemoryKind, TensorView};
7741    ///
7742    /// let view = TensorView::f64(&[1], &[1.0])?;
7743    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
7744    /// # Ok::<(), tenferro_tensor::Error>(())
7745    /// ```
7746    pub fn placement(&self) -> &Placement {
7747        match self {
7748            Self::F32(t) => t.placement(),
7749            Self::F64(t) => t.placement(),
7750            Self::I32(t) => t.placement(),
7751            Self::I64(t) => t.placement(),
7752            Self::Bool(t) => t.placement(),
7753            Self::C32(t) => t.placement(),
7754            Self::C64(t) => t.placement(),
7755        }
7756    }
7757
7758    /// Return the physical backend family, when this view is backend-owned.
7759    ///
7760    /// # Examples
7761    ///
7762    /// ```
7763    /// use tenferro_tensor::TensorView;
7764    ///
7765    /// let view = TensorView::f64(&[1], &[1.0])?;
7766    /// assert_eq!(view.backend_family(), None);
7767    /// # Ok::<(), tenferro_tensor::Error>(())
7768    /// ```
7769    pub fn backend_family(&self) -> Option<&'static str> {
7770        match self {
7771            Self::F32(t) => t.backend_family(),
7772            Self::F64(t) => t.backend_family(),
7773            Self::I32(t) => t.backend_family(),
7774            Self::I64(t) => t.backend_family(),
7775            Self::Bool(t) => t.backend_family(),
7776            Self::C32(t) => t.backend_family(),
7777            Self::C64(t) => t.backend_family(),
7778        }
7779    }
7780
7781    /// Return the shared allocation domain, when this view has one.
7782    ///
7783    /// # Examples
7784    ///
7785    /// ```
7786    /// use tenferro_tensor::TensorView;
7787    ///
7788    /// let view = TensorView::f64(&[1], &[1.0])?;
7789    /// assert_eq!(view.allocation_domain(), None);
7790    /// # Ok::<(), tenferro_tensor::Error>(())
7791    /// ```
7792    pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
7793        match self {
7794            Self::F32(t) => t.allocation_domain(),
7795            Self::F64(t) => t.allocation_domain(),
7796            Self::I32(t) => t.allocation_domain(),
7797            Self::I64(t) => t.allocation_domain(),
7798            Self::Bool(t) => t.allocation_domain(),
7799            Self::C32(t) => t.allocation_domain(),
7800            Self::C64(t) => t.allocation_domain(),
7801        }
7802    }
7803
7804    /// Return strides in element units.
7805    ///
7806    /// # Examples
7807    ///
7808    /// ```
7809    /// use tenferro_tensor::TensorView;
7810    ///
7811    /// let data = [0.0_f64; 6];
7812    /// let view = TensorView::f64(&[2, 3], &data)?;
7813    /// assert_eq!(view.strides(), &[1, 2]);
7814    /// # Ok::<(), tenferro_tensor::Error>(())
7815    /// ```
7816    pub fn strides(&self) -> &[isize] {
7817        match self {
7818            Self::F32(t) => t.strides(),
7819            Self::F64(t) => t.strides(),
7820            Self::I32(t) => t.strides(),
7821            Self::I64(t) => t.strides(),
7822            Self::Bool(t) => t.strides(),
7823            Self::C32(t) => t.strides(),
7824            Self::C64(t) => t.strides(),
7825        }
7826    }
7827
7828    /// Return the physical element offset.
7829    pub fn offset(&self) -> isize {
7830        match self {
7831            Self::F32(t) => t.offset(),
7832            Self::F64(t) => t.offset(),
7833            Self::I32(t) => t.offset(),
7834            Self::I64(t) => t.offset(),
7835            Self::Bool(t) => t.offset(),
7836            Self::C32(t) => t.offset(),
7837            Self::C64(t) => t.offset(),
7838        }
7839    }
7840
7841    /// Compute the physical element offset for a logical index.
7842    /// # Errors
7843    ///
7844    /// Returns [`crate::Error::Validation`] with
7845    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7846    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7847    /// when an index is outside its axis extent, or
7848    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7849    /// arithmetic overflows.
7850    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7851        match self {
7852            Self::F32(t) => t.layout_linear_offset(indices),
7853            Self::F64(t) => t.layout_linear_offset(indices),
7854            Self::I32(t) => t.layout_linear_offset(indices),
7855            Self::I64(t) => t.layout_linear_offset(indices),
7856            Self::Bool(t) => t.layout_linear_offset(indices),
7857            Self::C32(t) => t.layout_linear_offset(indices),
7858            Self::C64(t) => t.layout_linear_offset(indices),
7859        }
7860    }
7861
7862    /// Return whether this view is compact column-major.
7863    /// # Errors
7864    ///
7865    /// Returns [`crate::Error::Validation`] with
7866    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7867    /// compactness arithmetic overflows.
7868    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
7869        match self {
7870            Self::F32(t) => t.is_col_major_contiguous(),
7871            Self::F64(t) => t.is_col_major_contiguous(),
7872            Self::I32(t) => t.is_col_major_contiguous(),
7873            Self::I64(t) => t.is_col_major_contiguous(),
7874            Self::Bool(t) => t.is_col_major_contiguous(),
7875            Self::C32(t) => t.is_col_major_contiguous(),
7876            Self::C64(t) => t.is_col_major_contiguous(),
7877        }
7878    }
7879
7880    /// Return a compact string summary of this view's layout metadata.
7881    pub fn layout_summary(&self) -> String {
7882        layout_summary(self.shape(), self.strides(), self.offset())
7883    }
7884
7885    /// Assert this view is compact column-major.
7886    /// # Errors
7887    ///
7888    /// Returns [`crate::Error::Validation`] with
7889    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7890    /// compactness arithmetic overflows, or
7891    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7892    /// view is not compact column-major.
7893    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
7894        assert_layout_col_major_contiguous(
7895            self.is_col_major_contiguous()?,
7896            self.shape(),
7897            self.strides(),
7898            self.offset(),
7899            "TensorView::assert_col_major_contiguous",
7900        )
7901    }
7902
7903    /// Explicitly duplicate a compact host view into a fresh tensor.
7904    ///
7905    /// Backend views and non-contiguous layouts return a typed error; this
7906    /// operation never downloads or silently canonicalizes a view.
7907    ///
7908    /// # Examples
7909    ///
7910    /// ```
7911    /// use tenferro_tensor::{TensorView, TypedTensorView};
7912    ///
7913    /// let data = [1_i32, 2];
7914    /// let view = TensorView::I32(TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?);
7915    /// let copy = view.duplicate()?;
7916    /// assert_eq!(copy.shape(), &[2]);
7917    /// # Ok::<(), tenferro_tensor::Error>(())
7918    /// ```
7919    ///
7920    /// # Errors
7921    ///
7922    /// Returns [`crate::Error::HostAccess`] for backend-owned views,
7923    /// [`ValidationError::NonContiguousViewAsSlice`] for non-contiguous views,
7924    /// or [`ValidationError::InvalidArgument`] for invalid layout metadata.
7925    pub fn duplicate(&self) -> crate::Result<Tensor> {
7926        fn duplicate_typed<T: TensorScalar>(
7927            view: &TypedTensorView<'_, T>,
7928        ) -> crate::Result<TypedTensor<T>> {
7929            let mut tensor = TypedTensor::<T>::from_vec_col_major(
7930                view.shape().to_vec(),
7931                view.as_slice()?.to_vec(),
7932            )?;
7933            tensor.set_placement(view.placement().clone());
7934            Ok(tensor)
7935        }
7936
7937        match self {
7938            Self::F32(view) => duplicate_typed(view).map(Tensor::from_typed),
7939            Self::F64(view) => duplicate_typed(view).map(Tensor::from_typed),
7940            Self::I32(view) => duplicate_typed(view).map(Tensor::from_typed),
7941            Self::I64(view) => duplicate_typed(view).map(Tensor::from_typed),
7942            Self::Bool(view) => duplicate_typed(view).map(Tensor::from_typed),
7943            Self::C32(view) => duplicate_typed(view).map(Tensor::from_typed),
7944            Self::C64(view) => duplicate_typed(view).map(Tensor::from_typed),
7945        }
7946    }
7947}
7948
7949macro_rules! tensor_view_mut_constructor {
7950    ($name:ident, $variant:ident, $scalar:ty) => {
7951        /// Create a dynamic mutable view over compact column-major host data.
7952        ///
7953        /// # Errors
7954        ///
7955        /// Returns [`crate::Error::Validation`] with
7956        /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7957        /// compact layout arithmetic overflows, or
7958        /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7959        /// requested shape exceeds `data`.
7960        pub fn $name(shape: &'a [usize], data: &'a mut [$scalar]) -> crate::Result<Self> {
7961            Ok(Self::$variant(TypedTensorViewMut::from_col_major(
7962                shape, data,
7963            )?))
7964        }
7965    };
7966}
7967
7968impl<'a> TensorViewMut<'a> {
7969    tensor_view_mut_constructor!(f32, F32, f32);
7970    tensor_view_mut_constructor!(f64, F64, f64);
7971    tensor_view_mut_constructor!(i32, I32, i32);
7972    tensor_view_mut_constructor!(i64, I64, i64);
7973    tensor_view_mut_constructor!(bool, Bool, bool);
7974    tensor_view_mut_constructor!(c32, C32, Complex32);
7975    tensor_view_mut_constructor!(c64, C64, Complex64);
7976
7977    pub fn dtype(&self) -> DType {
7978        match self {
7979            Self::F32(_) => DType::F32,
7980            Self::F64(_) => DType::F64,
7981            Self::I32(_) => DType::I32,
7982            Self::I64(_) => DType::I64,
7983            Self::Bool(_) => DType::Bool,
7984            Self::C32(_) => DType::C32,
7985            Self::C64(_) => DType::C64,
7986        }
7987    }
7988
7989    pub fn shape(&self) -> &[usize] {
7990        match self {
7991            Self::F32(t) => t.shape(),
7992            Self::F64(t) => t.shape(),
7993            Self::I32(t) => t.shape(),
7994            Self::I64(t) => t.shape(),
7995            Self::Bool(t) => t.shape(),
7996            Self::C32(t) => t.shape(),
7997            Self::C64(t) => t.shape(),
7998        }
7999    }
8000
8001    /// Return strides in element units.
8002    ///
8003    /// # Examples
8004    ///
8005    /// ```
8006    /// use tenferro_tensor::TensorViewMut;
8007    ///
8008    /// let mut data = [0.0_f64; 6];
8009    /// let view = TensorViewMut::f64(&[2, 3], &mut data)?;
8010    /// assert_eq!(view.strides(), &[1, 2]);
8011    /// # Ok::<(), tenferro_tensor::Error>(())
8012    /// ```
8013    pub fn strides(&self) -> &[isize] {
8014        match self {
8015            Self::F32(t) => t.strides(),
8016            Self::F64(t) => t.strides(),
8017            Self::I32(t) => t.strides(),
8018            Self::I64(t) => t.strides(),
8019            Self::Bool(t) => t.strides(),
8020            Self::C32(t) => t.strides(),
8021            Self::C64(t) => t.strides(),
8022        }
8023    }
8024
8025    pub fn offset(&self) -> isize {
8026        match self {
8027            Self::F32(t) => t.offset(),
8028            Self::F64(t) => t.offset(),
8029            Self::I32(t) => t.offset(),
8030            Self::I64(t) => t.offset(),
8031            Self::Bool(t) => t.offset(),
8032            Self::C32(t) => t.offset(),
8033            Self::C64(t) => t.offset(),
8034        }
8035    }
8036
8037    /// Compute the physical element offset for a logical index.
8038    ///
8039    /// # Errors
8040    ///
8041    /// Returns [`crate::Error::Validation`] with
8042    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8043    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8044    /// when an index is outside its axis extent, or
8045    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8046    /// arithmetic overflows.
8047    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8048        match self {
8049            Self::F32(t) => t.layout_linear_offset(indices),
8050            Self::F64(t) => t.layout_linear_offset(indices),
8051            Self::I32(t) => t.layout_linear_offset(indices),
8052            Self::I64(t) => t.layout_linear_offset(indices),
8053            Self::Bool(t) => t.layout_linear_offset(indices),
8054            Self::C32(t) => t.layout_linear_offset(indices),
8055            Self::C64(t) => t.layout_linear_offset(indices),
8056        }
8057    }
8058
8059    /// Return whether this view is compact column-major.
8060    ///
8061    /// # Errors
8062    ///
8063    /// Returns [`crate::Error::Validation`] with
8064    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8065    /// compactness arithmetic overflows.
8066    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8067        match self {
8068            Self::F32(t) => t.is_col_major_contiguous(),
8069            Self::F64(t) => t.is_col_major_contiguous(),
8070            Self::I32(t) => t.is_col_major_contiguous(),
8071            Self::I64(t) => t.is_col_major_contiguous(),
8072            Self::Bool(t) => t.is_col_major_contiguous(),
8073            Self::C32(t) => t.is_col_major_contiguous(),
8074            Self::C64(t) => t.is_col_major_contiguous(),
8075        }
8076    }
8077
8078    pub fn layout_summary(&self) -> String {
8079        layout_summary(self.shape(), self.strides(), self.offset())
8080    }
8081
8082    /// Assert this view is compact column-major.
8083    ///
8084    /// # Errors
8085    ///
8086    /// Returns [`crate::Error::Validation`] with
8087    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8088    /// compactness arithmetic overflows, or
8089    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8090    /// view is not compact column-major.
8091    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8092        assert_layout_col_major_contiguous(
8093            self.is_col_major_contiguous()?,
8094            self.shape(),
8095            self.strides(),
8096            self.offset(),
8097            "TensorViewMut::assert_col_major_contiguous",
8098        )
8099    }
8100
8101    /// Explicitly duplicate the compact host data visible through this
8102    /// mutable view into a new tensor owner.
8103    ///
8104    /// # Examples
8105    ///
8106    /// ```
8107    /// use tenferro_tensor::{TensorViewMut, TypedTensorViewMut};
8108    ///
8109    /// let mut data = [1_i32, 2];
8110    /// let view = TensorViewMut::I32(TypedTensorViewMut::from_slice(
8111    ///     vec![2], vec![1], 0, &mut data,
8112    /// )?);
8113    /// let copy = view.duplicate()?;
8114    /// assert_eq!(copy.shape(), &[2]);
8115    /// # Ok::<(), tenferro_tensor::Error>(())
8116    /// ```
8117    ///
8118    /// # Errors
8119    ///
8120    /// Returns [`crate::Error::HostAccess`] for backend-owned views,
8121    /// [`ValidationError::NonContiguousViewAsSlice`] for non-contiguous views,
8122    /// or [`ValidationError::InvalidArgument`] for invalid layout metadata.
8123    pub fn duplicate(&self) -> crate::Result<Tensor> {
8124        self.as_read_only().duplicate()
8125    }
8126
8127    /// Borrow this mutable view as a read-only dtype-erased view.
8128    ///
8129    /// # Examples
8130    ///
8131    /// ```
8132    /// use tenferro_tensor::TensorViewMut;
8133    ///
8134    /// let mut data = [1.0_f64, 2.0];
8135    /// let view = TensorViewMut::f64(&[2], &mut data)?;
8136    /// assert_eq!(view.as_read_only().as_slice::<f64>()?, &[1.0, 2.0]);
8137    /// # Ok::<(), tenferro_tensor::Error>(())
8138    /// ```
8139    pub fn as_read_only(&self) -> TensorView<'_> {
8140        match self {
8141            Self::F32(t) => TensorView::F32(t.as_read_only()),
8142            Self::F64(t) => TensorView::F64(t.as_read_only()),
8143            Self::I32(t) => TensorView::I32(t.as_read_only()),
8144            Self::I64(t) => TensorView::I64(t.as_read_only()),
8145            Self::Bool(t) => TensorView::Bool(t.as_read_only()),
8146            Self::C32(t) => TensorView::C32(t.as_read_only()),
8147            Self::C64(t) => TensorView::C64(t.as_read_only()),
8148        }
8149    }
8150}
8151
8152impl<'a> TensorRead<'a> {
8153    /// Borrow an owned tensor as a read target without copying it.
8154    ///
8155    /// # Examples
8156    ///
8157    /// ```
8158    /// use tenferro_tensor::{Tensor, TensorRead};
8159    ///
8160    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8161    /// let read = TensorRead::from_tensor(&tensor);
8162    /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
8163    /// # Ok::<(), tenferro_tensor::Error>(())
8164    /// ```
8165    pub fn from_tensor(tensor: &'a Tensor) -> Self {
8166        Self::Tensor(tensor)
8167    }
8168
8169    /// Wrap a borrowed dtype-erased view as a read target.
8170    ///
8171    /// # Examples
8172    ///
8173    /// ```
8174    /// use tenferro_tensor::{TensorRead, TensorView};
8175    ///
8176    /// let read = TensorRead::from_view(TensorView::i32(&[2], &[3, 4])?);
8177    /// assert_eq!(read.as_slice::<i32>()?, &[3, 4]);
8178    /// assert!(read.as_tensor().is_none());
8179    /// # Ok::<(), tenferro_tensor::Error>(())
8180    /// ```
8181    #[inline]
8182    pub fn from_view(view: TensorView<'a>) -> Self {
8183        Self::View(view)
8184    }
8185
8186    /// Convert this read target into a dtype-erased tensor view.
8187    ///
8188    /// Owned tensors are borrowed without copying their storage. Existing
8189    /// views preserve their layout and placement metadata.
8190    /// # Examples
8191    ///
8192    /// ```rust
8193    /// # use tenferro_tensor::{Tensor, TensorRead};
8194    /// # let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8195    /// let view = TensorRead::from_tensor(&tensor).tensor_view();
8196    /// assert_eq!(view.shape(), &[2]);
8197    /// assert_eq!(view.as_slice::<f64>()?, &[1.0, 2.0]);
8198    /// # Ok::<(), tenferro_tensor::Error>(())
8199    /// ```
8200    pub fn tensor_view(self) -> TensorView<'a> {
8201        match self {
8202            Self::Tensor(tensor) => tensor_view_with_layout(tensor, tensor_layout(tensor)),
8203            Self::View(view) => view,
8204        }
8205    }
8206
8207    /// Build a metadata-only stride-0 broadcast of this read.
8208    ///
8209    /// An owned preset is borrowed and an existing view keeps its buffer,
8210    /// retained root and placement, so the result copies no elements and
8211    /// allocates no storage. A caller that can consume a read-only operand
8212    /// should use this instead of the materializing session
8213    /// `broadcast_in_dim_read`.
8214    ///
8215    /// # Examples
8216    ///
8217    /// ```
8218    /// use tenferro_tensor::{Tensor, TensorRead};
8219    ///
8220    /// let tensor = Tensor::from_vec_col_major(vec![3], vec![1.0_f64, 2.0, 3.0])?;
8221    /// let read = TensorRead::from_tensor(&tensor).broadcast_in_dim_view(&[2, 3], &[1])?;
8222    /// assert_eq!(read.shape(), &[2, 3]);
8223    /// assert_eq!(read.strides()?, vec![0, 1]);
8224    /// # Ok::<(), tenferro_tensor::Error>(())
8225    /// ```
8226    ///
8227    /// # Errors
8228    ///
8229    /// Returns [`crate::Error::UnsupportedDType`] for a caller-owned payload,
8230    /// which has no borrowed view, or a validation error when `dims` does not
8231    /// map this read's layout into `shape`.
8232    #[doc(hidden)]
8233    pub fn broadcast_in_dim_view(self, shape: &[usize], dims: &[usize]) -> crate::Result<Self> {
8234        let dtype = self.dtype();
8235        // INVARIANT: `tensor_view()` has no arm for a caller-owned payload, so
8236        // the refusal must happen before the read is converted into a view.
8237        if let DType::External(_) = dtype {
8238            return Err(crate::Error::unsupported_dtype(
8239                "TensorRead::broadcast_in_dim_view",
8240                dtype,
8241                "a caller-owned payload has no borrowed broadcast view",
8242            ));
8243        }
8244        let view = self.tensor_view();
8245        Ok(Self::View(match view {
8246            TensorView::F32(view) => TensorView::F32(broadcast_typed_view(view, shape, dims)?),
8247            TensorView::F64(view) => TensorView::F64(broadcast_typed_view(view, shape, dims)?),
8248            TensorView::I32(view) => TensorView::I32(broadcast_typed_view(view, shape, dims)?),
8249            TensorView::I64(view) => TensorView::I64(broadcast_typed_view(view, shape, dims)?),
8250            TensorView::Bool(view) => TensorView::Bool(broadcast_typed_view(view, shape, dims)?),
8251            TensorView::C32(view) => TensorView::C32(broadcast_typed_view(view, shape, dims)?),
8252            TensorView::C64(view) => TensorView::C64(broadcast_typed_view(view, shape, dims)?),
8253        }))
8254    }
8255
8256    /// Borrow this read target as a typed compact host slice without allocation.
8257    ///
8258    /// This delegates to [`TensorView::as_slice`]. Cloning a `TensorRead` is a
8259    /// shallow clone of its borrowed reference or view metadata, so the returned
8260    /// slice retains the original `'a` storage lifetime and never outlives the
8261    /// storage borrowed by this read target. This method does not materialize,
8262    /// transfer, or otherwise canonicalize the input.
8263    ///
8264    /// # Errors
8265    ///
8266    /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
8267    /// input dtype, [`ValidationError::NonContiguousViewAsSlice`] for a
8268    /// noncompact view, or a typed runtime-state host-access error for
8269    /// backend-owned storage.
8270    /// Backend-owned
8271    /// inputs are never downloaded implicitly.
8272    ///
8273    /// # Examples
8274    ///
8275    /// ```
8276    /// use tenferro_tensor::{Tensor, TensorRead};
8277    ///
8278    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8279    /// let read = TensorRead::from_tensor(&tensor);
8280    /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
8281    /// # Ok::<(), tenferro_tensor::Error>(())
8282    /// ```
8283    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
8284        self.clone().tensor_view().as_slice()
8285    }
8286
8287    /// Return the element dtype of this read target.
8288    ///
8289    /// # Examples
8290    ///
8291    /// ```
8292    /// use tenferro_tensor::{DType, Tensor, TensorRead};
8293    ///
8294    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1_i64])?;
8295    /// assert_eq!(TensorRead::from_tensor(&tensor).dtype(), DType::I64);
8296    /// # Ok::<(), tenferro_tensor::Error>(())
8297    /// ```
8298    pub fn dtype(&self) -> DType {
8299        match self {
8300            Self::Tensor(tensor) => tensor.dtype(),
8301            Self::View(view) => view.dtype(),
8302        }
8303    }
8304
8305    /// Return the logical shape of this read target.
8306    ///
8307    /// # Examples
8308    ///
8309    /// ```
8310    /// use tenferro_tensor::{Tensor, TensorRead};
8311    ///
8312    /// let tensor = Tensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
8313    /// assert_eq!(TensorRead::from_tensor(&tensor).shape(), &[2, 1]);
8314    /// # Ok::<(), tenferro_tensor::Error>(())
8315    /// ```
8316    pub fn shape(&self) -> &[usize] {
8317        match self {
8318            Self::Tensor(tensor) => tensor.shape(),
8319            Self::View(view) => view.shape(),
8320        }
8321    }
8322
8323    /// Return the placement metadata carried by this read target.
8324    ///
8325    /// # Examples
8326    ///
8327    /// ```
8328    /// use tenferro_tensor::{MemoryKind, Tensor, TensorRead};
8329    ///
8330    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
8331    /// let read = TensorRead::from_tensor(&tensor);
8332    /// assert_eq!(read.placement().memory_kind, MemoryKind::UnpinnedHost);
8333    /// # Ok::<(), tenferro_tensor::Error>(())
8334    /// ```
8335    pub fn placement(&self) -> &Placement {
8336        match self {
8337            Self::Tensor(tensor) => tensor.placement(),
8338            Self::View(view) => view.placement(),
8339        }
8340    }
8341
8342    /// Return the physical backend family of this read target, when backend-owned.
8343    ///
8344    /// # Examples
8345    ///
8346    /// ```
8347    /// use tenferro_tensor::{Tensor, TensorRead};
8348    ///
8349    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
8350    /// assert_eq!(TensorRead::from_tensor(&tensor).backend_family(), None);
8351    /// # Ok::<(), tenferro_tensor::Error>(())
8352    /// ```
8353    pub fn backend_family(&self) -> Option<&'static str> {
8354        match self {
8355            Self::Tensor(tensor) => match tensor.dtype() {
8356                // A caller-owned payload is host memory without backend family.
8357                DType::External(_) => None,
8358                DType::F32 => tensor.as_typed::<f32>().and_then(|t| t.backend_family()),
8359                DType::F64 => tensor.as_typed::<f64>().and_then(|t| t.backend_family()),
8360                DType::I32 => tensor.as_typed::<i32>().and_then(|t| t.backend_family()),
8361                DType::I64 => tensor.as_typed::<i64>().and_then(|t| t.backend_family()),
8362                DType::Bool => tensor.as_typed::<bool>().and_then(|t| t.backend_family()),
8363                DType::C32 => tensor
8364                    .as_typed::<Complex<f32>>()
8365                    .and_then(|t| t.backend_family()),
8366                DType::C64 => tensor
8367                    .as_typed::<Complex<f64>>()
8368                    .and_then(|t| t.backend_family()),
8369            },
8370            Self::View(view) => view.backend_family(),
8371        }
8372    }
8373
8374    /// Return the shared allocation domain of this read target, when present.
8375    ///
8376    /// # Examples
8377    ///
8378    /// ```
8379    /// use tenferro_tensor::{Tensor, TensorRead};
8380    ///
8381    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
8382    /// assert_eq!(TensorRead::from_tensor(&tensor).allocation_domain(), None);
8383    /// # Ok::<(), tenferro_tensor::Error>(())
8384    /// ```
8385    pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
8386        match self {
8387            Self::Tensor(tensor) => match tensor.dtype() {
8388                // A caller-owned payload has no allocation domain.
8389                DType::External(_) => None,
8390                DType::F32 => tensor.as_typed::<f32>().and_then(|t| t.allocation_domain()),
8391                DType::F64 => tensor.as_typed::<f64>().and_then(|t| t.allocation_domain()),
8392                DType::I32 => tensor.as_typed::<i32>().and_then(|t| t.allocation_domain()),
8393                DType::I64 => tensor.as_typed::<i64>().and_then(|t| t.allocation_domain()),
8394                DType::Bool => tensor
8395                    .as_typed::<bool>()
8396                    .and_then(|t| t.allocation_domain()),
8397                DType::C32 => tensor
8398                    .as_typed::<Complex<f32>>()
8399                    .and_then(|t| t.allocation_domain()),
8400                DType::C64 => tensor
8401                    .as_typed::<Complex<f64>>()
8402                    .and_then(|t| t.allocation_domain()),
8403            },
8404            Self::View(view) => view.allocation_domain(),
8405        }
8406    }
8407
8408    /// Return strides in element units; owned tensors report compact column-major strides.
8409    ///
8410    /// # Examples
8411    ///
8412    /// ```
8413    /// use tenferro_tensor::{Tensor, TensorRead};
8414    ///
8415    /// let tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8416    /// assert_eq!(TensorRead::from_tensor(&tensor).strides()?, vec![1, 2]);
8417    /// # Ok::<(), tenferro_tensor::Error>(())
8418    /// ```
8419    ///
8420    /// # Errors
8421    ///
8422    /// Returns [`crate::Error::Validation`] with
8423    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8424    /// column-major stride arithmetic overflows.
8425    pub fn strides(&self) -> crate::Result<Vec<isize>> {
8426        match self {
8427            Self::Tensor(tensor) => Ok(col_major_strides(tensor.shape())?.into_vec()),
8428            Self::View(view) => Ok(view.strides().to_vec()),
8429        }
8430    }
8431
8432    /// Return the physical element offset; owned tensors always start at `0`.
8433    ///
8434    /// # Examples
8435    ///
8436    /// ```
8437    /// use tenferro_tensor::{TensorRead, TensorView, TypedTensorView};
8438    ///
8439    /// let data = [1.0_f64, 2.0, 3.0];
8440    /// let view = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?);
8441    /// assert_eq!(TensorRead::from_view(view).offset(), 1);
8442    /// # Ok::<(), tenferro_tensor::Error>(())
8443    /// ```
8444    pub fn offset(&self) -> isize {
8445        match self {
8446            Self::Tensor(_) => 0,
8447            Self::View(view) => view.offset(),
8448        }
8449    }
8450
8451    /// Compute the physical element offset for a logical index.
8452    ///
8453    /// # Examples
8454    ///
8455    /// ```
8456    /// use tenferro_tensor::{Tensor, TensorRead};
8457    ///
8458    /// let tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8459    /// let read = TensorRead::from_tensor(&tensor);
8460    /// assert_eq!(read.layout_linear_offset(&[1, 2])?, 5);
8461    /// assert!(read.layout_linear_offset(&[2, 0]).is_err());
8462    /// # Ok::<(), tenferro_tensor::Error>(())
8463    /// ```
8464    ///
8465    /// # Errors
8466    ///
8467    /// Returns [`crate::Error::Validation`] with
8468    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8469    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8470    /// when an index is outside its axis extent, or
8471    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8472    /// arithmetic overflows.
8473    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8474        match self {
8475            Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
8476            Self::View(view) => view.layout_linear_offset(indices),
8477        }
8478    }
8479
8480    /// Return whether this read target is compact column-major.
8481    ///
8482    /// # Examples
8483    ///
8484    /// ```
8485    /// use tenferro_tensor::{Tensor, TensorRead, TensorView, TypedTensorView};
8486    ///
8487    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8488    /// assert!(TensorRead::from_tensor(&tensor).is_col_major_contiguous()?);
8489    /// let data = [1.0_f64, 2.0, 3.0];
8490    /// let strided = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![2], 0, &data)?);
8491    /// assert!(!TensorRead::from_view(strided).is_col_major_contiguous()?);
8492    /// # Ok::<(), tenferro_tensor::Error>(())
8493    /// ```
8494    ///
8495    /// # Errors
8496    ///
8497    /// Returns [`crate::Error::Validation`] with
8498    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8499    /// compactness arithmetic overflows.
8500    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8501        match self {
8502            Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
8503            Self::View(view) => view.is_col_major_contiguous(),
8504        }
8505    }
8506
8507    /// Return a compact string summary of this read target's layout metadata.
8508    ///
8509    /// # Examples
8510    ///
8511    /// ```
8512    /// use tenferro_tensor::{Tensor, TensorRead};
8513    ///
8514    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8515    /// assert_eq!(
8516    ///     TensorRead::from_tensor(&tensor).layout_summary(),
8517    ///     "shape=[2] strides=[1] offset=0"
8518    /// );
8519    /// # Ok::<(), tenferro_tensor::Error>(())
8520    /// ```
8521    pub fn layout_summary(&self) -> String {
8522        let strides = match self.strides() {
8523            Ok(strides) => strides,
8524            Err(err) => return format!("layout unavailable: {err}"),
8525        };
8526        layout_summary(self.shape(), &strides, self.offset())
8527    }
8528
8529    /// Assert this read target is compact column-major.
8530    ///
8531    /// # Examples
8532    ///
8533    /// ```
8534    /// use tenferro_tensor::{Tensor, TensorRead, TensorView, TypedTensorView};
8535    ///
8536    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8537    /// TensorRead::from_tensor(&tensor).assert_col_major_contiguous()?;
8538    /// let data = [1.0_f64, 2.0, 3.0];
8539    /// let strided = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![2], 0, &data)?);
8540    /// assert!(TensorRead::from_view(strided).assert_col_major_contiguous().is_err());
8541    /// # Ok::<(), tenferro_tensor::Error>(())
8542    /// ```
8543    ///
8544    /// # Errors
8545    ///
8546    /// Returns [`crate::Error::Validation`] with
8547    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8548    /// compactness arithmetic overflows, or
8549    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8550    /// view is not compact column-major.
8551    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8552        let strides = self.strides()?;
8553        assert_layout_col_major_contiguous(
8554            self.is_col_major_contiguous()?,
8555            self.shape(),
8556            &strides,
8557            self.offset(),
8558            "TensorRead::assert_col_major_contiguous",
8559        )
8560    }
8561
8562    /// Return the borrowed owned tensor, or `None` when this read target is a view.
8563    ///
8564    /// # Examples
8565    ///
8566    /// ```
8567    /// use tenferro_tensor::{Tensor, TensorRead, TensorView};
8568    ///
8569    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
8570    /// assert!(TensorRead::from_tensor(&tensor).as_tensor().is_some());
8571    /// assert!(TensorRead::from_view(TensorView::f64(&[1], &[1.0])?).as_tensor().is_none());
8572    /// # Ok::<(), tenferro_tensor::Error>(())
8573    /// ```
8574    pub fn as_tensor(&self) -> Option<&'a Tensor> {
8575        match self {
8576            Self::Tensor(tensor) => Some(*tensor),
8577            Self::View(_) => None,
8578        }
8579    }
8580}
8581
8582impl<'a> TensorWrite<'a> {
8583    /// Borrow an owned tensor as a writable target without copying it.
8584    ///
8585    /// # Examples
8586    ///
8587    /// ```
8588    /// use tenferro_tensor::{Tensor, TensorWrite};
8589    ///
8590    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8591    /// let write = TensorWrite::from_tensor(&mut tensor);
8592    /// assert_eq!(write.shape(), &[2]);
8593    /// # Ok::<(), tenferro_tensor::Error>(())
8594    /// ```
8595    pub fn from_tensor(tensor: &'a mut Tensor) -> Self {
8596        Self::Tensor(tensor)
8597    }
8598
8599    /// Wrap a borrowed mutable dtype-erased view as a writable target.
8600    ///
8601    /// # Examples
8602    ///
8603    /// ```
8604    /// use tenferro_tensor::{DType, TensorViewMut, TensorWrite};
8605    ///
8606    /// let mut data = [1.0_f64, 2.0];
8607    /// let write = TensorWrite::from_view(TensorViewMut::f64(&[2], &mut data)?);
8608    /// assert_eq!(write.dtype(), DType::F64);
8609    /// assert_eq!(write.as_read().as_slice::<f64>()?, &[1.0, 2.0]);
8610    /// # Ok::<(), tenferro_tensor::Error>(())
8611    /// ```
8612    pub fn from_view(view: TensorViewMut<'a>) -> Self {
8613        Self::View(view)
8614    }
8615
8616    /// Borrow this writable target as a read-only tensor input.
8617    ///
8618    /// This is useful for explicit read-modify-write kernels such as
8619    /// accumulation updates. The returned view borrows through `&self`, so it
8620    /// cannot outlive the current read-only borrow of the writable target.
8621    ///
8622    /// # Examples
8623    ///
8624    /// ```rust
8625    /// use tenferro_tensor::{DType, Tensor, TensorWrite};
8626    ///
8627    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
8628    /// let write = TensorWrite::from_tensor(&mut tensor);
8629    /// let read = write.as_read();
8630    /// assert_eq!(read.dtype(), DType::F64);
8631    /// # Ok::<(), tenferro_tensor::Error>(())
8632    /// ```
8633    pub fn as_read(&self) -> TensorRead<'_> {
8634        match self {
8635            Self::Tensor(tensor) => TensorRead::from_tensor(tensor),
8636            Self::View(view) => TensorRead::from_view(view.as_read_only()),
8637        }
8638    }
8639
8640    /// Return the element dtype of this writable target.
8641    ///
8642    /// # Examples
8643    ///
8644    /// ```
8645    /// use tenferro_tensor::{DType, Tensor, TensorWrite};
8646    ///
8647    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![1_i32])?;
8648    /// assert_eq!(TensorWrite::from_tensor(&mut tensor).dtype(), DType::I32);
8649    /// # Ok::<(), tenferro_tensor::Error>(())
8650    /// ```
8651    pub fn dtype(&self) -> DType {
8652        match self {
8653            Self::Tensor(tensor) => tensor.dtype(),
8654            Self::View(view) => view.dtype(),
8655        }
8656    }
8657
8658    /// Return the logical shape of this writable target.
8659    ///
8660    /// # Examples
8661    ///
8662    /// ```
8663    /// use tenferro_tensor::{Tensor, TensorWrite};
8664    ///
8665    /// let mut tensor = Tensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
8666    /// assert_eq!(TensorWrite::from_tensor(&mut tensor).shape(), &[2, 1]);
8667    /// # Ok::<(), tenferro_tensor::Error>(())
8668    /// ```
8669    pub fn shape(&self) -> &[usize] {
8670        match self {
8671            Self::Tensor(tensor) => tensor.shape(),
8672            Self::View(view) => view.shape(),
8673        }
8674    }
8675
8676    /// Return strides in element units; owned tensors report compact column-major strides.
8677    ///
8678    /// # Examples
8679    ///
8680    /// ```
8681    /// use tenferro_tensor::{Tensor, TensorWrite};
8682    ///
8683    /// let mut tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8684    /// assert_eq!(TensorWrite::from_tensor(&mut tensor).strides()?, vec![1, 2]);
8685    /// # Ok::<(), tenferro_tensor::Error>(())
8686    /// ```
8687    ///
8688    /// # Errors
8689    ///
8690    /// Returns [`crate::Error::Validation`] with
8691    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8692    /// column-major stride arithmetic overflows.
8693    pub fn strides(&self) -> crate::Result<Vec<isize>> {
8694        match self {
8695            Self::Tensor(tensor) => Ok(col_major_strides(tensor.shape())?.into_vec()),
8696            Self::View(view) => Ok(view.strides().to_vec()),
8697        }
8698    }
8699
8700    /// Return the physical element offset; owned tensors always start at `0`.
8701    ///
8702    /// # Examples
8703    ///
8704    /// ```
8705    /// use tenferro_tensor::{TensorViewMut, TensorWrite, TypedTensorViewMut};
8706    ///
8707    /// let mut data = [1.0_f64, 2.0, 3.0];
8708    /// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![1], 1, &mut data)?);
8709    /// assert_eq!(TensorWrite::from_view(view).offset(), 1);
8710    /// # Ok::<(), tenferro_tensor::Error>(())
8711    /// ```
8712    pub fn offset(&self) -> isize {
8713        match self {
8714            Self::Tensor(_) => 0,
8715            Self::View(view) => view.offset(),
8716        }
8717    }
8718
8719    /// Compute the physical element offset for a logical index.
8720    ///
8721    /// # Examples
8722    ///
8723    /// ```
8724    /// use tenferro_tensor::{Tensor, TensorWrite};
8725    ///
8726    /// let mut tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8727    /// let write = TensorWrite::from_tensor(&mut tensor);
8728    /// assert_eq!(write.layout_linear_offset(&[1, 2])?, 5);
8729    /// assert!(write.layout_linear_offset(&[0]).is_err());
8730    /// # Ok::<(), tenferro_tensor::Error>(())
8731    /// ```
8732    ///
8733    /// # Errors
8734    ///
8735    /// Returns [`crate::Error::Validation`] with
8736    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8737    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8738    /// when an index is outside its axis extent, or
8739    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8740    /// arithmetic overflows.
8741    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8742        match self {
8743            Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
8744            Self::View(view) => view.layout_linear_offset(indices),
8745        }
8746    }
8747
8748    /// Return whether this writable target is compact column-major.
8749    ///
8750    /// # Examples
8751    ///
8752    /// ```
8753    /// use tenferro_tensor::{Tensor, TensorViewMut, TensorWrite, TypedTensorViewMut};
8754    ///
8755    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8756    /// assert!(TensorWrite::from_tensor(&mut tensor).is_col_major_contiguous()?);
8757    /// let mut data = [1.0_f64, 2.0, 3.0];
8758    /// let strided = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![2], 0, &mut data)?);
8759    /// assert!(!TensorWrite::from_view(strided).is_col_major_contiguous()?);
8760    /// # Ok::<(), tenferro_tensor::Error>(())
8761    /// ```
8762    ///
8763    /// # Errors
8764    ///
8765    /// Returns [`crate::Error::Validation`] with
8766    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8767    /// compactness arithmetic overflows.
8768    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8769        match self {
8770            Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
8771            Self::View(view) => view.is_col_major_contiguous(),
8772        }
8773    }
8774
8775    /// Return a compact string summary of this writable target's layout metadata.
8776    ///
8777    /// # Examples
8778    ///
8779    /// ```
8780    /// use tenferro_tensor::{Tensor, TensorWrite};
8781    ///
8782    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8783    /// assert_eq!(
8784    ///     TensorWrite::from_tensor(&mut tensor).layout_summary(),
8785    ///     "shape=[2] strides=[1] offset=0"
8786    /// );
8787    /// # Ok::<(), tenferro_tensor::Error>(())
8788    /// ```
8789    pub fn layout_summary(&self) -> String {
8790        let strides = match self.strides() {
8791            Ok(strides) => strides,
8792            Err(err) => return format!("layout unavailable: {err}"),
8793        };
8794        layout_summary(self.shape(), &strides, self.offset())
8795    }
8796
8797    /// Assert this writable target is compact column-major.
8798    ///
8799    /// # Examples
8800    ///
8801    /// ```
8802    /// use tenferro_tensor::{Tensor, TensorViewMut, TensorWrite, TypedTensorViewMut};
8803    ///
8804    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8805    /// TensorWrite::from_tensor(&mut tensor).assert_col_major_contiguous()?;
8806    /// let mut data = [1.0_f64, 2.0, 3.0];
8807    /// let strided = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![2], 0, &mut data)?);
8808    /// assert!(TensorWrite::from_view(strided).assert_col_major_contiguous().is_err());
8809    /// # Ok::<(), tenferro_tensor::Error>(())
8810    /// ```
8811    ///
8812    /// # Errors
8813    ///
8814    /// Returns [`crate::Error::Validation`] with
8815    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8816    /// compactness arithmetic overflows, or
8817    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8818    /// view is not compact column-major.
8819    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8820        let strides = self.strides()?;
8821        assert_layout_col_major_contiguous(
8822            self.is_col_major_contiguous()?,
8823            self.shape(),
8824            &strides,
8825            self.offset(),
8826            "TensorWrite::assert_col_major_contiguous",
8827        )
8828    }
8829}
8830
8831/// Column-major strides derived from a shape.
8832///
8833/// # Examples
8834///
8835/// ```rust
8836/// use tenferro_tensor::col_major_strides;
8837///
8838/// assert_eq!(col_major_strides(&[2, 3])?.as_slice(), &[1, 2]);
8839/// # Ok::<(), tenferro_tensor::Error>(())
8840/// ```
8841/// # Errors
8842///
8843/// Returns [`crate::Error::Validation`] with
8844/// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when a
8845/// column-major stride product overflows.
8846pub fn col_major_strides(shape: &[usize]) -> crate::Result<StrideVec> {
8847    // Inline for common ranks: every compact destination on the CPU and CUDA
8848    // paths derives its strides here, so the heap is off this path entirely.
8849    let mut strides = StrideVec::with_capacity(shape.len());
8850    let mut stride = 1isize;
8851    for &extent in shape {
8852        strides.push(stride);
8853        let extent = isize::try_from(extent).map_err(|_| {
8854            crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
8855        })?;
8856        stride = stride.checked_mul(extent).ok_or_else(|| {
8857            crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
8858        })?;
8859    }
8860    Ok(strides)
8861}
8862
8863fn try_linear_offset_for_shape(
8864    shape: &[usize],
8865    indices: &[usize],
8866    op: &'static str,
8867) -> crate::Result<usize> {
8868    if indices.len() != shape.len() {
8869        return Err(crate::Error::validation(
8870            op,
8871            ValidationError::RankMismatch {
8872                expected: shape.len(),
8873                actual: indices.len(),
8874            },
8875        ));
8876    }
8877    let mut offset = 0usize;
8878    let mut stride = 1usize;
8879    for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
8880        if idx >= extent {
8881            return Err(crate::Error::invalid_argument(
8882                op,
8883                "index",
8884                format!("index {idx} out of bounds for axis {axis} extent {extent}"),
8885            ));
8886        }
8887        offset =
8888            offset
8889                .checked_add(idx.checked_mul(stride).ok_or_else(|| {
8890                    crate::Error::validation(op, ValidationError::IntegerOverflow)
8891                })?)
8892                .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8893        stride = stride
8894            .checked_mul(extent)
8895            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8896    }
8897    Ok(offset)
8898}
8899
8900fn checked_view_offset_result(
8901    shape: &[usize],
8902    strides: &[isize],
8903    base_offset: isize,
8904    indices: &[usize],
8905    op: &'static str,
8906) -> crate::Result<usize> {
8907    if indices.len() != shape.len() {
8908        return Err(crate::Error::validation(
8909            op,
8910            ValidationError::RankMismatch {
8911                expected: shape.len(),
8912                actual: indices.len(),
8913            },
8914        ));
8915    }
8916    for (axis, (&index, &extent)) in indices.iter().zip(shape).enumerate() {
8917        if index >= extent {
8918            return Err(crate::Error::invalid_argument(
8919                op,
8920                "index",
8921                format!("index {index} out of bounds for axis {axis} extent {extent}"),
8922            ));
8923        }
8924    }
8925    checked_view_offset(shape, strides, base_offset, indices)
8926        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
8927}
8928
8929fn layout_summary(shape: &[usize], strides: &[isize], offset: isize) -> String {
8930    format!("shape={shape:?} strides={strides:?} offset={offset}")
8931}
8932
8933fn assert_layout_col_major_contiguous(
8934    is_contiguous: bool,
8935    shape: &[usize],
8936    strides: &[isize],
8937    offset: isize,
8938    op: &'static str,
8939) -> crate::Result<()> {
8940    if is_contiguous {
8941        Ok(())
8942    } else {
8943        Err(crate::Error::invalid_argument(
8944            op,
8945            "layout",
8946            format!(
8947                "expected compact column-major layout, got {}",
8948                layout_summary(shape, strides, offset)
8949            ),
8950        ))
8951    }
8952}
8953
8954fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
8955    shape.iter().try_fold(1usize, |acc, &dim| {
8956        acc.checked_mul(dim)
8957            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
8958    })
8959}
8960
8961fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
8962    let n = try_shape_product(shape, op)?;
8963    if data_len != n {
8964        return Err(crate::Error::validation(
8965            op,
8966            ValidationError::ShapeDataLengthMismatch {
8967                expected: n,
8968                actual: data_len,
8969            },
8970        ));
8971    }
8972    Ok(())
8973}
8974
8975fn try_compact_layout<R: TensorRank>(
8976    shape: impl tenferro_tensor_core::IntoRankShape<R>,
8977    op: &'static str,
8978) -> crate::Result<TensorLayout<R>> {
8979    let shape = shape
8980        .into_rank_shape()
8981        .map_err(|err| tensor_layout_error(op, err))?;
8982    TensorLayout::compact(shape).map_err(|err| tensor_layout_error(op, err))
8983}
8984
8985fn tensor_layout_error(
8986    op: &'static str,
8987    err: tenferro_tensor_core::ValidationError,
8988) -> crate::Error {
8989    crate::Error::validation(op, err)
8990}
8991
8992fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
8993    if shape.contains(&0) {
8994        return Ok(0);
8995    }
8996    shape.iter().try_fold(1usize, |product, &dim| {
8997        product
8998            .checked_mul(dim)
8999            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
9000    })
9001}
9002
9003fn checked_view_offset(
9004    shape: &[usize],
9005    strides: &[isize],
9006    base_offset: isize,
9007    indices: &[usize],
9008) -> Option<usize> {
9009    if indices.len() != shape.len() {
9010        return None;
9011    }
9012
9013    let mut offset = base_offset;
9014    for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
9015        if index >= extent {
9016            return None;
9017        }
9018        let index = isize::try_from(index).ok()?;
9019        let delta = index.checked_mul(stride)?;
9020        offset = offset.checked_add(delta)?;
9021    }
9022
9023    usize::try_from(offset).ok()
9024}
9025
9026fn reachable_layout_span(
9027    shape: &[usize],
9028    strides: &[isize],
9029    offset: isize,
9030) -> crate::Result<Option<(usize, usize)>> {
9031    if shape.contains(&0) {
9032        return Ok(None);
9033    }
9034
9035    let mut min_offset = offset;
9036    let mut max_offset = offset;
9037    for (&extent, &stride) in shape.iter().zip(strides) {
9038        let steps = isize::try_from(extent.saturating_sub(1)).map_err(|_| {
9039            crate::Error::validation(
9040                "TypedTensorViewMut::try_multi_slice_mut",
9041                ValidationError::IntegerOverflow,
9042            )
9043        })?;
9044        let end = stride.checked_mul(steps).ok_or_else(|| {
9045            crate::Error::validation(
9046                "TypedTensorViewMut::try_multi_slice_mut",
9047                ValidationError::IntegerOverflow,
9048            )
9049        })?;
9050        let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
9051        min_offset = min_offset.checked_add(axis_min).ok_or_else(|| {
9052            crate::Error::validation(
9053                "TypedTensorViewMut::try_multi_slice_mut",
9054                ValidationError::IntegerOverflow,
9055            )
9056        })?;
9057        max_offset = max_offset.checked_add(axis_max).ok_or_else(|| {
9058            crate::Error::validation(
9059                "TypedTensorViewMut::try_multi_slice_mut",
9060                ValidationError::IntegerOverflow,
9061            )
9062        })?;
9063    }
9064
9065    let min_offset = usize::try_from(min_offset).map_err(|_| {
9066        crate::Error::invalid_argument(
9067            "TypedTensorViewMut::try_multi_slice_mut",
9068            "layout",
9069            "minimum reachable offset is negative",
9070        )
9071    })?;
9072    let max_offset = usize::try_from(max_offset).map_err(|_| {
9073        crate::Error::invalid_argument(
9074            "TypedTensorViewMut::try_multi_slice_mut",
9075            "layout",
9076            "maximum reachable offset is negative",
9077        )
9078    })?;
9079    Ok(Some((min_offset, max_offset)))
9080}
9081
9082fn split_two_mut_ranges<T>(
9083    data: &mut [T],
9084    first: (usize, usize),
9085    second: (usize, usize),
9086) -> Option<(&mut [T], &mut [T])> {
9087    if first.1 < second.0 {
9088        let (_, after_first_start) = data.split_at_mut(first.0);
9089        let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
9090        let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
9091        let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
9092        Some((first_slice, second_slice))
9093    } else if second.1 < first.0 {
9094        let (_, after_second_start) = data.split_at_mut(second.0);
9095        let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
9096        let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
9097        let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
9098        Some((first_slice, second_slice))
9099    } else {
9100        None
9101    }
9102}
9103
9104fn adjusted_view_offset(offset: isize, span_start: usize) -> crate::Result<isize> {
9105    let span_start = isize::try_from(span_start).map_err(|_| {
9106        crate::Error::validation(
9107            "TypedTensorViewMut::try_multi_slice_mut",
9108            ValidationError::IntegerOverflow,
9109        )
9110    })?;
9111    offset.checked_sub(span_start).ok_or_else(|| {
9112        crate::Error::validation(
9113            "TypedTensorViewMut::try_multi_slice_mut",
9114            ValidationError::IntegerOverflow,
9115        )
9116    })
9117}
9118
9119fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
9120    layout: &TensorLayout<R>,
9121    offset: isize,
9122    data: &'a mut [T],
9123    placement: Placement,
9124) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
9125    let shape = R::shape_from_vec(shape_vec(layout.shape()))
9126        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
9127    let strides = R::strides_from_vec(stride_vec(layout.strides()))
9128        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
9129    TypedTensorViewMut::from_buffer_ref_mut(
9130        shape,
9131        strides,
9132        offset,
9133        TensorStorageRefMut::Host(data),
9134        placement,
9135        "TypedTensorViewMut::try_multi_slice_mut",
9136    )
9137}
9138
9139fn contiguous_layout_slice<'a, T, R: TensorRank>(
9140    layout: &TensorLayout<R>,
9141    data: &'a [T],
9142    op: &'static str,
9143) -> crate::Result<&'a [T]> {
9144    if !layout
9145        .is_compact_col_major()
9146        .map_err(|err| tensor_layout_error(op, err))?
9147    {
9148        return Err(crate::Error::validation(
9149            op,
9150            ValidationError::NonContiguousViewAsSlice,
9151        ));
9152    }
9153    let len = checked_view_element_count(layout.shape(), op)?;
9154    let start = usize::try_from(layout.offset())
9155        .map_err(|_| crate::Error::invalid_argument(op, "layout", "view offset is negative"))?;
9156    let end = start
9157        .checked_add(len)
9158        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
9159    data.get(start..end)
9160        .ok_or_else(|| crate::Error::validation(op, ValidationError::ViewOutOfBounds))
9161}
9162
9163fn relaxed_col_major_contiguous(
9164    shape: &[usize],
9165    strides: &[isize],
9166    op: &'static str,
9167) -> crate::Result<bool> {
9168    if shape.contains(&0) {
9169        return Ok(true);
9170    }
9171
9172    let mut expected = 1isize;
9173    for (&extent, &stride) in shape.iter().zip(strides) {
9174        if extent <= 1 {
9175            continue;
9176        }
9177        if stride != expected {
9178            return Ok(false);
9179        }
9180        let extent = isize::try_from(extent)
9181            .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
9182        expected = expected
9183            .checked_mul(extent)
9184            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
9185    }
9186    Ok(true)
9187}
9188
9189fn reshape_layout_dyn<R: TensorRank>(
9190    layout: &TensorLayout<R>,
9191    shape: &[usize],
9192    buffer_len: usize,
9193    op: &'static str,
9194) -> crate::Result<TensorLayout<DynRank>> {
9195    match layout.reshape_view_as::<DynRank>(shape_vec(shape), buffer_len) {
9196        Ok(layout) => Ok(layout),
9197        Err(err) => {
9198            if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
9199                return Err(tensor_layout_error(op, err));
9200            }
9201            let from = checked_view_element_count(layout.shape(), op)?;
9202            let to = checked_view_element_count(shape, op)?;
9203            if from != to {
9204                return Err(tensor_layout_error(
9205                    op,
9206                    tenferro_tensor_core::ShapeMismatch::ReshapeElementCount { from, to }.into(),
9207                ));
9208            }
9209            TensorLayout::<DynRank>::compact(shape_vec(shape))
9210                .and_then(|compact| {
9211                    TensorLayout::from_parts(
9212                        shape_vec(compact.shape()),
9213                        stride_vec(compact.strides()),
9214                        layout.offset(),
9215                        buffer_len,
9216                    )
9217                })
9218                .map_err(|err| tensor_layout_error(op, err))
9219        }
9220    }
9221}
9222
9223fn core_slice_specs(
9224    slices: &[StridedSliceSpec],
9225    shape: &[usize],
9226    op: &'static str,
9227) -> crate::Result<TinyVec<[CoreSliceSpec; 8]>> {
9228    if slices.len() != shape.len() {
9229        return Err(crate::Error::validation(
9230            op,
9231            ValidationError::RankMismatch {
9232                expected: shape.len(),
9233                actual: slices.len(),
9234            },
9235        ));
9236    }
9237
9238    let mut specs = TinyVec::<[CoreSliceSpec; 8]>::with_capacity(slices.len());
9239    for (slice, &axis_len) in slices.iter().zip(shape) {
9240        specs.push(core_slice_spec(*slice, axis_len, op)?);
9241    }
9242    Ok(specs)
9243}
9244
9245fn core_slice_spec(
9246    slice: StridedSliceSpec,
9247    axis_len: usize,
9248    op: &'static str,
9249) -> crate::Result<CoreSliceSpec> {
9250    if slice.step() == 0 {
9251        return Err(crate::Error::validation(
9252            op,
9253            ValidationError::InvalidSliceStep { step: slice.step() },
9254        ));
9255    }
9256
9257    let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
9258    let end = match slice.end() {
9259        Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
9260        None => isize::try_from(axis_len)
9261            .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
9262    };
9263
9264    if slice.step() > 0 {
9265        return Ok(CoreSliceSpec {
9266            start,
9267            end,
9268            step: slice.step(),
9269        });
9270    }
9271
9272    if start >= end {
9273        // `core`'s negative-step contract requires the inclusive `start` to
9274        // point at an element (`start < axis_len`). An empty selection is
9275        // step-sign independent, so encode the `start == axis_len` boundary at
9276        // the last element instead of re-encoding it verbatim (which `core`
9277        // would reject as out of bounds).
9278        let empty_start = if axis_len == 0 {
9279            start
9280        } else {
9281            start.min(axis_len as isize - 1)
9282        };
9283        return Ok(CoreSliceSpec {
9284            start: empty_start,
9285            end: empty_start,
9286            step: slice.step(),
9287        });
9288    }
9289
9290    Ok(CoreSliceSpec {
9291        start: end
9292            .checked_sub(1)
9293            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
9294        end: start
9295            .checked_sub(1)
9296            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
9297        step: slice.step(),
9298    })
9299}
9300
9301fn normalize_strided_bound(
9302    bound: isize,
9303    axis_len: usize,
9304    op: &'static str,
9305    role: &'static str,
9306) -> crate::Result<isize> {
9307    let original_axis_len = axis_len;
9308    let axis_len = isize::try_from(axis_len)
9309        .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
9310    let bound = if bound < 0 {
9311        axis_len
9312            .checked_add(bound)
9313            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?
9314    } else {
9315        bound
9316    };
9317    if !(0..=axis_len).contains(&bound) {
9318        let (start, end) = if role == "slice start" {
9319            (bound, bound)
9320        } else {
9321            (0, bound)
9322        };
9323        return Err(crate::Error::validation(
9324            op,
9325            ValidationError::InvalidSliceBounds {
9326                start,
9327                end,
9328                axis_len: original_axis_len,
9329            },
9330        ));
9331    }
9332    Ok(bound)
9333}
9334
9335fn slice_axis_specs(
9336    rank: usize,
9337    axis: usize,
9338    slice: StridedSliceSpec,
9339    op: &'static str,
9340) -> crate::Result<Vec<StridedSliceSpec>> {
9341    if axis >= rank {
9342        return Err(crate::Error::validation(
9343            op,
9344            ValidationError::AxisOutOfBounds { axis, rank },
9345        ));
9346    }
9347
9348    let mut slices = vec![StridedSliceSpec::all(); rank];
9349    slices[axis] = slice;
9350    Ok(slices)
9351}
9352
9353pub(crate) fn default_placement() -> Placement {
9354    Placement {
9355        memory_kind: MemoryKind::UnpinnedHost,
9356        device: None,
9357        cpu_affinity: None,
9358    }
9359}
9360
9361fn typed_tensor_from_vec_col_major<T, R: TensorRank>(
9362    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9363    data: Vec<T>,
9364    op: &'static str,
9365) -> crate::Result<TypedTensor<T, R>> {
9366    try_typed_tensor_from_vec_col_major(shape, data, op)
9367}
9368
9369fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
9370    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9371    data: Vec<T>,
9372    op: &'static str,
9373) -> crate::Result<TypedTensor<T, R>> {
9374    let shape = shape
9375        .into_rank_shape()
9376        .map_err(|err| tensor_layout_error(op, err))?;
9377    let layout = tenferro_tensor_core::col_major_strides(shape.as_ref())
9378        .map_err(|err| tensor_layout_error(op, err))?;
9379    try_checked_shape_len(shape.as_ref(), data.len(), op)?;
9380    Ok(TypedTensor {
9381        shape,
9382        placement: default_placement(),
9383        layout,
9384        storage: DynamicStorage::Host(HostStorage {
9385            data: HostData::new(data),
9386        }),
9387    })
9388}
9389
9390fn typed_tensor_zeros<T: TensorScalar + Zero, R: TensorRank>(
9391    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9392) -> crate::Result<TypedTensor<T, R>> {
9393    try_typed_tensor_zeros(shape)
9394}
9395
9396fn try_typed_tensor_zeros<T: TensorScalar + Clone + Zero, R: TensorRank>(
9397    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9398) -> crate::Result<TypedTensor<T, R>> {
9399    let layout = try_compact_layout(shape, "zeros")?;
9400    let n = try_shape_product(layout.shape(), "zeros")?;
9401    typed_tensor_from_vec_col_major(
9402        R::shape_from_vec(shape_vec(layout.shape()))
9403            .map_err(|err| tensor_layout_error("zeros", err))?,
9404        vec![T::zero(); n],
9405        "zeros",
9406    )
9407}
9408
9409fn typed_tensor_ones<T: TensorScalar + One + Zero, R: TensorRank>(
9410    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9411) -> crate::Result<TypedTensor<T, R>> {
9412    try_typed_tensor_ones(shape)
9413}
9414
9415fn try_typed_tensor_ones<T: TensorScalar + Clone + One + Zero, R: TensorRank>(
9416    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9417) -> crate::Result<TypedTensor<T, R>> {
9418    let layout = try_compact_layout(shape, "ones")?;
9419    let n = try_shape_product(layout.shape(), "ones")?;
9420    typed_tensor_from_vec_col_major(
9421        R::shape_from_vec(shape_vec(layout.shape()))
9422            .map_err(|err| tensor_layout_error("ones", err))?,
9423        vec![T::one(); n],
9424        "ones",
9425    )
9426}
9427
9428fn typed_tensor_from_buffer_col_major<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
9429    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9430    buffer: StorageBuffer<T>,
9431    placement: Placement,
9432) -> crate::Result<TypedTensor<T, R>> {
9433    try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
9434}
9435
9436#[doc(hidden)]
9437fn typed_tensor_from_backend_allocation<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
9438    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9439    allocation: Box<dyn crate::BackendAllocation>,
9440    placement: Placement,
9441) -> crate::Result<TypedTensor<T, R>> {
9442    let layout = try_compact_layout(shape, "from_backend_allocation")?;
9443    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
9444        .map_err(|err| tensor_layout_error("from_backend_allocation", err))?;
9445    let shape = group_shape.clone();
9446    let (mut group, slot) =
9447        AllocationGroup::from_backend_allocation::<T, R>(group_shape, allocation)
9448            .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9449    group
9450        .set_descriptor_placement(slot, placement.clone())
9451        .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9452    let allocation_index = group
9453        .allocation_index(slot)
9454        .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9455    let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
9456    Ok(TypedTensor {
9457        shape,
9458        placement,
9459        layout: stride_vec(layout.strides()),
9460        storage: DynamicStorage::Group(GroupStorage {
9461            group: Box::new(OwnedTensorGroup {
9462                group,
9463                slot,
9464                allocation_index,
9465                host_ptr,
9466                host_byte_len,
9467                _rank: PhantomData,
9468            }),
9469        }),
9470    })
9471}
9472
9473fn try_typed_tensor_from_buffer_col_major<
9474    T: TensorScalar + Send + Sync + 'static,
9475    R: TensorRank,
9476>(
9477    shape: impl tenferro_tensor_core::IntoRankShape<R>,
9478    buffer: StorageBuffer<T>,
9479    placement: Placement,
9480) -> crate::Result<TypedTensor<T, R>> {
9481    let layout = try_compact_layout(shape, "from_buffer_col_major")?;
9482    let len = buffer.len();
9483    try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
9484    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
9485        .map_err(|err| tensor_layout_error("from_buffer_col_major", err))?;
9486    match buffer {
9487        StorageBuffer::Host(data) => Ok(TypedTensor {
9488            shape: group_shape,
9489            placement,
9490            layout: stride_vec(layout.strides()),
9491            storage: DynamicStorage::Host(HostStorage {
9492                data: HostData::new(data),
9493            }),
9494        }),
9495        StorageBuffer::Backend(buffer) => {
9496            let group = OwnedTensorGroup::from_backend_buffer(
9497                group_shape.clone(),
9498                StorageBuffer::Backend(buffer),
9499                placement.clone(),
9500            )?;
9501            Ok(TypedTensor {
9502                shape: group_shape,
9503                placement,
9504                layout: stride_vec(layout.strides()),
9505                storage: DynamicStorage::Group(GroupStorage {
9506                    group: Box::new(group),
9507                }),
9508            })
9509        }
9510    }
9511}
9512
9513impl<T: TensorScalar + Zero, R: TensorRank> TypedTensor<T, R> {
9514    /// Allocate a zero-filled tensor.
9515    ///
9516    /// # Examples
9517    ///
9518    /// ```rust
9519    /// use tenferro_tensor::TypedTensor;
9520    ///
9521    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
9522    /// assert_eq!(t.n_elements(), 6);
9523    /// ```
9524    /// # Errors
9525    ///
9526    /// Returns [`crate::Error::Validation`] with
9527    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
9528    /// product or compact-stride arithmetic overflows.
9529    /// A vector or slice with a different static rank returns
9530    /// [`tenferro_tensor_core::ValidationError::RankMismatch`].
9531    pub fn zeros(shape: impl tenferro_tensor_core::IntoRankShape<R>) -> crate::Result<Self> {
9532        typed_tensor_zeros(shape)
9533    }
9534}
9535
9536impl<T: TensorScalar + One + Zero, R: TensorRank> TypedTensor<T, R> {
9537    /// Allocate a one-filled tensor.
9538    ///
9539    /// # Examples
9540    ///
9541    /// ```rust
9542    /// use tenferro_tensor::TypedTensor;
9543    ///
9544    /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
9545    /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
9546    /// ```
9547    /// # Errors
9548    ///
9549    /// Returns [`crate::Error::Validation`] with
9550    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
9551    /// product or compact-stride arithmetic overflows.
9552    /// A vector or slice with a different static rank returns
9553    /// [`tenferro_tensor_core::ValidationError::RankMismatch`].
9554    pub fn ones(shape: impl tenferro_tensor_core::IntoRankShape<R>) -> crate::Result<Self> {
9555        typed_tensor_ones(shape)
9556    }
9557}
9558
9559impl<T, R: TensorRank> TypedTensor<T, R> {
9560    /// Adopt a column-major host `Vec<T>` with no scalar, copy or thread-safety bound.
9561    ///
9562    /// # Examples
9563    /// ```
9564    /// use tenferro_tensor::{Rank, TypedTensor};
9565    /// struct Custom(String);
9566    /// let tensor = TypedTensor::<Custom, Rank<2>>::from_vec_col_major(
9567    ///     [1, 2], vec![Custom("a".into()), Custom("b".into())],
9568    /// )?;
9569    /// assert_eq!(tensor.get(&[0, 1])?.0.as_str(), "b");
9570    /// # Ok::<(), tenferro_tensor::Error>(())
9571    /// ```
9572    /// # Errors
9573    /// Returns [`crate::Error::Validation`] for a rank mismatch, shape/data
9574    /// length mismatch or shape/stride arithmetic overflow.
9575    pub fn from_vec_col_major(
9576        shape: impl tenferro_tensor_core::IntoRankShape<R>,
9577        data: Vec<T>,
9578    ) -> crate::Result<Self> {
9579        typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
9580    }
9581
9582    /// Explicitly import row-major host values into column-major storage.
9583    ///
9584    /// Clones each input element once; no backend or scalar registration is used.
9585    ///
9586    /// # Examples
9587    /// ```
9588    /// use tenferro_tensor::{Rank, TypedTensor};
9589    /// let tensor = TypedTensor::<i32, Rank<2>>::from_vec_row_major(
9590    ///     [2, 3], vec![1, 2, 3, 4, 5, 6],
9591    /// )?;
9592    /// assert_eq!(tensor.get(&[1, 0])?, &4);
9593    /// assert_eq!(tensor.get(&[0, 2])?, &3);
9594    /// # Ok::<(), tenferro_tensor::Error>(())
9595    /// ```
9596    /// # Errors
9597    /// Returns [`crate::Error::Validation`] for a rank, shape-length or stride overflow,
9598    /// or when the shape product disagrees with the input length.
9599    pub fn from_vec_row_major(
9600        shape: impl tenferro_tensor_core::IntoRankShape<R>,
9601        data: Vec<T>,
9602    ) -> crate::Result<Self>
9603    where
9604        T: Clone,
9605    {
9606        let op = "from_vec_row_major";
9607        let (shape, reordered) = row_major_reorder(shape, data, op)?;
9608        typed_tensor_from_vec_col_major(shape, reordered, op)
9609    }
9610
9611    /// Consume this compact tensor and return the original host `Vec<T>`.
9612    ///
9613    /// # Examples
9614    ///
9615    /// ```
9616    /// use tenferro_tensor::TypedTensor;
9617    /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
9618    /// let data = tensor.into_host_vec().map_err(|failure| failure.into_parts().1)?;
9619    /// assert_eq!(data, vec![1, 2]);
9620    /// # Ok::<(), tenferro_tensor::Error>(())
9621    /// ```
9622    ///
9623    /// # Errors
9624    /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9625    /// storage is device-only, the managed root cannot export a host vector, or
9626    /// a group-backed descriptor is not compact column-major
9627    /// ([`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]).
9628    pub fn into_host_vec(self) -> std::result::Result<Vec<T>, ReinterpretError<Self>>
9629    where
9630        T: 'static,
9631    {
9632        let TypedTensor {
9633            shape,
9634            placement,
9635            layout,
9636            storage,
9637        } = self;
9638        match storage {
9639            DynamicStorage::Host(HostStorage { data }) => Ok(data.into_vec()),
9640            DynamicStorage::Group(GroupStorage { group }) => {
9641                let range = match group_logical_range(&group, "TypedTensor::into_host_vec") {
9642                    Ok(range) => range,
9643                    Err(error) => {
9644                        return Err(ReinterpretError::new(
9645                            TypedTensor {
9646                                shape,
9647                                placement,
9648                                layout,
9649                                storage: DynamicStorage::Group(GroupStorage { group }),
9650                            },
9651                            error,
9652                        ))
9653                    }
9654                };
9655                match group.into_host_vec::<T>() {
9656                    Ok(mut data) => {
9657                        // INVARIANT: the live descriptor's compact logical range
9658                        // was validated inside its root span, so the exported
9659                        // vector covers it.
9660                        debug_assert!(range.end <= data.len(), "descriptor range exceeds its root");
9661                        data.truncate(range.end);
9662                        data.drain(..range.start);
9663                        // The exported vector represents the logical extent only,
9664                        // so it must not retain the root's capacity.
9665                        data.shrink_to_fit();
9666                        Ok(data)
9667                    }
9668                    Err((group, error)) => Err(ReinterpretError::new(
9669                        TypedTensor {
9670                            shape,
9671                            placement,
9672                            layout,
9673                            storage: DynamicStorage::Group(GroupStorage {
9674                                group: Box::new(group),
9675                            }),
9676                        },
9677                        error,
9678                    )),
9679                }
9680            }
9681        }
9682    }
9683
9684    /// Consume the original host vector along with its column-major shape.
9685    ///
9686    /// # Examples
9687    ///
9688    /// ```
9689    /// use tenferro_tensor::TypedTensor;
9690    /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2, 1], vec![1, 2])?;
9691    /// let (shape, data) = tensor.into_vec_col_major().map_err(|failure| failure.into_parts().1)?;
9692    /// assert_eq!(shape, vec![2, 1]);
9693    /// assert_eq!(data, vec![1, 2]);
9694    /// # Ok::<(), tenferro_tensor::Error>(())
9695    /// ```
9696    ///
9697    /// # Errors
9698    /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9699    /// storage is device-only, the managed root cannot export a host vector, or
9700    /// a group-backed descriptor is not compact column-major
9701    /// ([`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]).
9702    pub fn into_vec_col_major(
9703        self,
9704    ) -> std::result::Result<(Vec<usize>, Vec<T>), ReinterpretError<Self>>
9705    where
9706        T: 'static,
9707    {
9708        let shape = self.shape().to_vec();
9709        match self.into_host_vec() {
9710            Ok(data) => Ok((shape, data)),
9711            Err(failure) => Err(failure),
9712        }
9713    }
9714
9715    /// Borrow the plain host values of this tensor.
9716    ///
9717    /// A group-backed owner extracted from a larger root exposes only its
9718    /// logical, compact values; [`Self::as_view`] retains the whole backing
9719    /// root when a caller needs to inspect elements outside the extent.
9720    ///
9721    /// # Examples
9722    ///
9723    /// ```
9724    /// use tenferro_tensor::TypedTensor;
9725    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
9726    /// assert_eq!(tensor.host_data()?, &[1.0, 2.0]);
9727    /// # Ok::<(), tenferro_tensor::Error>(())
9728    /// ```
9729    ///
9730    /// # Errors
9731    /// Returns [`crate::Error::RuntimeState`] for device-only storage and the
9732    /// non-compact-descriptor validation error for a strided owner.
9733    pub fn host_data(&self) -> crate::Result<&[T]> {
9734        match &self.storage {
9735            DynamicStorage::Host(HostStorage { data }) => Ok(data.as_slice()),
9736            DynamicStorage::Group(core) => {
9737                let data = core.group.host_slice::<T>()?;
9738                let range = group_logical_range(&core.group, "TypedTensor::host_data")?;
9739                data.get(range).ok_or_else(|| {
9740                    crate::Error::validation(
9741                        "TypedTensor::host_data",
9742                        ValidationError::ViewOutOfBounds,
9743                    )
9744                })
9745            }
9746        }
9747    }
9748
9749    /// Borrow the logical host values as a flat column-major slice without
9750    /// copying.
9751    ///
9752    /// This is the value view of [`Self::host_data`]: a root-preserving
9753    /// extraction exposes only its compact logical slice, never the root prefix
9754    /// and never elements outside its extent.
9755    ///
9756    /// # Examples
9757    /// ```
9758    /// use tenferro_tensor::TypedTensor;
9759    /// let tensor = TypedTensor::<String>::from_vec_col_major([2], vec!["a".into(), "b".into()])?;
9760    /// assert_eq!(tensor.as_slice()?, &["a", "b"]);
9761    /// # Ok::<(), tenferro_tensor::Error>(())
9762    /// ```
9763    /// # Errors
9764    /// Device-only storage returns [`crate::Error::RuntimeState`].
9765    pub fn as_slice(&self) -> crate::Result<&[T]> {
9766        self.host_data()
9767    }
9768
9769    /// Mutably borrow the plain host values of this tensor.
9770    ///
9771    /// The exclusive borrow covers exactly the logical, compact values of a
9772    /// root-preserving owner, so writes cannot reach elements outside its
9773    /// extent.
9774    ///
9775    /// # Examples
9776    ///
9777    /// ```
9778    /// use tenferro_tensor::TypedTensor;
9779    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
9780    /// tensor.host_data_mut()?[0] = 5.0;
9781    /// assert_eq!(tensor.host_data()?, &[5.0, 2.0]);
9782    /// # Ok::<(), tenferro_tensor::Error>(())
9783    /// ```
9784    ///
9785    /// # Errors
9786    /// Returns [`crate::Error::RuntimeState`] for device-only storage and the
9787    /// non-compact-descriptor validation error for a strided owner.
9788    pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
9789        match &mut self.storage {
9790            DynamicStorage::Host(HostStorage { data }) => Ok(data.as_mut_slice()),
9791            DynamicStorage::Group(core) => {
9792                let range = group_logical_range(&core.group, "TypedTensor::host_data_mut")?;
9793                let data = core.group.host_slice_mut::<T>()?;
9794                data.get_mut(range).ok_or_else(|| {
9795                    crate::Error::validation(
9796                        "TypedTensor::host_data_mut",
9797                        ValidationError::ViewOutOfBounds,
9798                    )
9799                })
9800            }
9801        }
9802    }
9803
9804    /// Borrow an element by checked column-major multi-index.
9805    ///
9806    /// # Examples
9807    /// ```
9808    /// use tenferro_tensor::TypedTensor;
9809    /// let tensor = TypedTensor::<String>::from_vec_col_major([2], vec!["a".into(), "b".into()])?;
9810    /// assert_eq!(tensor.get(&[1])?, "b");
9811    /// # Ok::<(), tenferro_tensor::Error>(())
9812    /// ```
9813    /// # Errors
9814    /// Invalid rank, coordinates or offset return [`crate::Error::Validation`];
9815    /// device-only storage returns [`crate::Error::RuntimeState`].
9816    pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
9817        let offset = self.linear_offset(indices)?;
9818        self.host_data()?.get(offset).ok_or_else(|| {
9819            crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
9820        })
9821    }
9822
9823    /// Exclusively borrow an element by checked column-major multi-index.
9824    ///
9825    /// # Examples
9826    /// ```
9827    /// use tenferro_tensor::TypedTensor;
9828    /// let mut tensor = TypedTensor::<String>::from_vec_col_major([1], vec!["a".into()])?;
9829    /// *tensor.get_mut(&[0])? = "b".into();
9830    /// assert_eq!(tensor.get(&[0])?, "b");
9831    /// # Ok::<(), tenferro_tensor::Error>(())
9832    /// ```
9833    /// # Errors
9834    /// Invalid rank, coordinates or offset return [`crate::Error::Validation`];
9835    /// device-only storage returns [`crate::Error::RuntimeState`].
9836    pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
9837        let offset = self.linear_offset(indices)?;
9838        self.host_data_mut()?.get_mut(offset).ok_or_else(|| {
9839            crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
9840        })
9841    }
9842
9843    /// Make an explicit independent host copy with the same shape and placement.
9844    ///
9845    /// The dynamic `TypedTensor` does not implement [`Clone`] (only the
9846    /// host-only representation does): the copy reads host data, rejects
9847    /// device-only storage, and creates a new owner, so it is fallible. See
9848    /// [`Tensor::duplicate`] for the sharing alternatives.
9849    ///
9850    /// # Examples
9851    /// ```
9852    /// use tenferro_tensor::TypedTensor;
9853    /// let tensor = TypedTensor::<String>::from_vec_col_major([1], vec!["a".into()])?;
9854    /// assert_eq!(tensor.duplicate()?.get(&[0])?, "a");
9855    /// # Ok::<(), tenferro_tensor::Error>(())
9856    /// ```
9857    /// # Errors
9858    /// Device-only storage returns [`crate::Error::RuntimeState`]; invalid
9859    /// host metadata returns [`crate::Error::Validation`].
9860    pub fn duplicate(&self) -> crate::Result<Self>
9861    where
9862        T: Clone,
9863    {
9864        let mut copy = Self::from_vec_col_major(
9865            R::shape_from_vec(shape_vec(self.shape()))
9866                .map_err(|err| tensor_layout_error("TypedTensor::duplicate", err))?,
9867            self.host_data()?.to_vec(),
9868        )?;
9869        copy.set_placement(self.placement().clone());
9870        Ok(copy)
9871    }
9872
9873    /// Create a tensor from an existing buffer and compact column-major layout.
9874    ///
9875    /// This preserves the owned tensor invariant that layout metadata is
9876    /// compact column-major, including for backend-owned buffers.
9877    ///
9878    /// # Examples
9879    ///
9880    /// ```
9881    /// use tenferro_tensor::{StorageBuffer, Placement, TypedTensor};
9882    ///
9883    /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
9884    ///     vec![2],
9885    ///     StorageBuffer::Host(vec![1.0, 2.0]),
9886    ///     Placement {
9887    ///         memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
9888    ///         device: None,
9889    ///         cpu_affinity: None,
9890    ///     },
9891    /// )
9892    /// .unwrap();
9893    /// assert_eq!(tensor.shape(), &[2]);
9894    /// ```
9895    /// # Errors
9896    ///
9897    /// Returns [`crate::Error::Validation`] with
9898    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
9899    /// the shape product differs from the buffer length,
9900    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape or
9901    /// stride arithmetic overflows, or
9902    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a supplied
9903    /// rank-specific shape cannot be represented.
9904    pub fn from_buffer_col_major(
9905        shape: impl tenferro_tensor_core::IntoRankShape<R>,
9906        buffer: StorageBuffer<T>,
9907        placement: Placement,
9908    ) -> crate::Result<Self>
9909    where
9910        T: TensorScalar + Send + Sync + 'static,
9911    {
9912        typed_tensor_from_buffer_col_major(shape, buffer, placement)
9913    }
9914
9915    /// Consume a scalar-independent provider root into one compact tensor.
9916    #[doc(hidden)]
9917    pub fn from_backend_allocation(
9918        shape: impl tenferro_tensor_core::IntoRankShape<R>,
9919        allocation: Box<dyn crate::BackendAllocation>,
9920        placement: Placement,
9921    ) -> crate::Result<Self>
9922    where
9923        T: TensorScalar + Send + Sync + 'static,
9924    {
9925        typed_tensor_from_backend_allocation(shape, allocation, placement)
9926    }
9927
9928    /// Convert this tensor into static rank metadata after validating its rank.
9929    ///
9930    /// The buffer and placement are preserved. This method changes only the
9931    /// compile-time rank marker on the owned compact column-major tensor.
9932    ///
9933    /// # Examples
9934    ///
9935    /// ```
9936    /// use tenferro_tensor::{Rank, TypedTensor};
9937    ///
9938    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
9939    /// let Ok(ranked) = tensor.try_into_rank::<2>() else {
9940    ///     panic!("a rank-2 tensor converts to two axes")
9941    /// };
9942    /// assert_eq!(ranked.shape(), &[2, 3]);
9943    /// # Ok::<(), tenferro_tensor::Error>(())
9944    /// ```
9945    /// # Errors
9946    ///
9947    /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9948    /// typed rank does not match the existing shape, with
9949    /// [`crate::Error::Validation`] and
9950    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] as the cause,
9951    /// or when the compact rank layout overflows.
9952    pub fn try_into_rank<const N: usize>(
9953        self,
9954    ) -> std::result::Result<TypedTensor<T, Rank<N>>, ReinterpretError<Self>> {
9955        let op = "TypedTensor::try_into_rank";
9956        let actual = self.shape().len();
9957        let shape: [usize; N] = match self.shape().try_into() {
9958            Ok(shape) => shape,
9959            Err(_) => {
9960                return Err(ReinterpretError::new(
9961                    self,
9962                    tensor_layout_error(
9963                        op,
9964                        ValidationError::RankMismatch {
9965                            expected: N,
9966                            actual,
9967                        },
9968                    ),
9969                ))
9970            }
9971        };
9972        let layout = match TensorLayout::<Rank<N>>::compact(shape) {
9973            Ok(layout) => layout,
9974            Err(error) => return Err(ReinterpretError::new(self, tensor_layout_error(op, error))),
9975        };
9976        let TypedTensor {
9977            placement, storage, ..
9978        } = self;
9979        let storage = match storage {
9980            DynamicStorage::Host(host) => DynamicStorage::Host(host),
9981            DynamicStorage::Group(GroupStorage { group }) => DynamicStorage::Group(GroupStorage {
9982                group: Box::new(OwnedTensorGroup {
9983                    group: group.group,
9984                    slot: group.slot,
9985                    allocation_index: group.allocation_index,
9986                    host_ptr: group.host_ptr,
9987                    host_byte_len: group.host_byte_len,
9988                    _rank: PhantomData,
9989                }),
9990            }),
9991        };
9992        Ok(TypedTensor {
9993            shape,
9994            placement,
9995            layout: stride_vec(layout.strides()),
9996            storage,
9997        })
9998    }
9999
10000    /// Return the storage backing this tensor.
10001    ///
10002    /// This is an explicit storage-inspection API for backend glue and tests.
10003    /// Host value inspection should prefer [`TypedTensor::host_data`] when the
10004    /// caller requires host storage.
10005    ///
10006    /// # Panics
10007    ///
10008    /// Panics only if the typed descriptor and its single group owner are
10009    /// internally inconsistent.
10010    ///
10011    /// # Examples
10012    ///
10013    /// ```
10014    /// use tenferro_tensor::{StorageBuffer, TypedTensor};
10015    ///
10016    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
10017    /// assert!(matches!(t.buffer(), StorageBuffer::Host(_)));
10018    /// ```
10019    pub fn buffer(&self) -> &StorageBuffer<T>
10020    where
10021        T: 'static,
10022    {
10023        match &self.storage {
10024            DynamicStorage::Host(HostStorage { data }) => &data.buffer,
10025            DynamicStorage::Group(core) => core
10026                .group
10027                .host_buffer::<T>()
10028                .or_else(|| core.group.backend_buffer::<T>())
10029                .unwrap_or_else(|| unreachable!("typed tensor group storage mismatch")),
10030        }
10031    }
10032
10033    /// Return the provider family for this tensor when backend-owned.
10034    #[doc(hidden)]
10035    pub fn backend_family(&self) -> Option<&'static str>
10036    where
10037        T: TensorScalar + 'static,
10038    {
10039        self.as_view().backend_family()
10040    }
10041
10042    /// Return the opaque backend buffer for backend-owned tensors.
10043    #[doc(hidden)]
10044    pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>>
10045    where
10046        T: 'static,
10047    {
10048        match &self.storage {
10049            DynamicStorage::Host(..) => None,
10050            DynamicStorage::Group(core) => match core.group.backend_buffer::<T>() {
10051                Some(StorageBuffer::Backend(buffer)) => Some(buffer.as_ref()),
10052                Some(StorageBuffer::Host(_)) | None => None,
10053            },
10054        }
10055    }
10056
10057    /// Return the mutable backend buffer for an exclusive owner borrow.
10058    #[doc(hidden)]
10059    pub fn backend_buffer_mut(&mut self) -> Option<&mut dyn BackendStorage<T>>
10060    where
10061        T: 'static,
10062    {
10063        let DynamicStorage::Group(core) = &mut self.storage else {
10064            return None;
10065        };
10066        match core.group.backend_buffer_mut::<T>()? {
10067            StorageBuffer::Host(_) => None,
10068            StorageBuffer::Backend(buffer) => Some(buffer.as_mut()),
10069        }
10070    }
10071
10072    /// Prepare this backend tensor for one provider-native read binding.
10073    #[doc(hidden)]
10074    pub fn prepare_device_read(
10075        &self,
10076        op: &'static str,
10077    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
10078    where
10079        T: TensorScalar + 'static,
10080    {
10081        let DynamicStorage::Group(core) = &self.storage else {
10082            return Err(crate::Error::runtime_state_source(
10083                op,
10084                crate::AccessError::Unsupported { backend: "host" },
10085            ));
10086        };
10087        let layout = self.physical_layout();
10088        core.group
10089            .prepare_device_read_for_layout::<T>(&layout)
10090            .map_err(|error| crate::Error::runtime_state_source(op, error))
10091    }
10092
10093    /// Prepare this backend tensor for one provider-native write binding.
10094    #[doc(hidden)]
10095    pub fn prepare_device_write(
10096        &mut self,
10097        op: &'static str,
10098    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
10099    where
10100        T: TensorScalar + 'static,
10101    {
10102        let layout = self.physical_layout();
10103        let DynamicStorage::Group(core) = &mut self.storage else {
10104            return Err(crate::Error::runtime_state_source(
10105                op,
10106                crate::AccessError::Unsupported { backend: "host" },
10107            ));
10108        };
10109        core.group
10110            .prepare_device_write_for_layout::<T>(&layout)
10111            .map_err(|error| crate::Error::runtime_state_source(op, error))
10112    }
10113
10114    /// Element count of the tensor's backing storage.
10115    ///
10116    /// A root-preserving owner reports its whole backing root, not just the
10117    /// logical view extent, so layout validation for views and reshapes bounds
10118    /// against the real allocation.
10119    pub(crate) fn buffer_len(&self) -> usize
10120    where
10121        T: 'static,
10122    {
10123        match &self.storage {
10124            DynamicStorage::Host(HostStorage { data }) => data.as_slice().len(),
10125            DynamicStorage::Group(core) => core
10126                .group
10127                .group
10128                .descriptor_backing_len(core.group.slot)
10129                .unwrap_or_else(|| unreachable!("typed tensor group descriptor mismatch")),
10130        }
10131    }
10132
10133    /// Return the shared-allocation domain carried by the backend buffer.
10134    ///
10135    /// # Examples
10136    ///
10137    /// ```rust
10138    /// use tenferro_tensor::TypedTensor;
10139    ///
10140    /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
10141    /// assert_eq!(tensor.allocation_domain(), None);
10142    /// # Ok::<(), tenferro_tensor::Error>(())
10143    /// ```
10144    pub fn allocation_domain(&self) -> Option<AllocationDomainId>
10145    where
10146        T: 'static,
10147    {
10148        match &self.storage {
10149            DynamicStorage::Host(..) => None,
10150            DynamicStorage::Group(core) => core
10151                .group
10152                .group
10153                .backend_identity(core.group.slot)
10154                .map(|(domain, _)| domain),
10155        }
10156    }
10157
10158    /// Return the stable physical backend allocation identity.
10159    ///
10160    /// # Examples
10161    ///
10162    /// ```rust
10163    /// use tenferro_tensor::TypedTensor;
10164    ///
10165    /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
10166    /// assert_eq!(tensor.allocation_id(), None);
10167    /// # Ok::<(), tenferro_tensor::Error>(())
10168    /// ```
10169    pub fn allocation_id(&self) -> Option<AllocationId>
10170    where
10171        T: 'static,
10172    {
10173        match &self.storage {
10174            DynamicStorage::Host(..) => None,
10175            DynamicStorage::Group(core) => core
10176                .group
10177                .group
10178                .backend_identity(core.group.slot)
10179                .map(|(_, allocation)| allocation),
10180        }
10181    }
10182
10183    /// Borrow this tensor as a typed view preserving rank and layout metadata.
10184    ///
10185    /// The view carries the descriptor's physical layout, so a root-preserving
10186    /// extraction exposes its full backing allocation with the logical slice at
10187    /// the descriptor offset.
10188    ///
10189    /// # Panics
10190    ///
10191    /// Panics only if the typed descriptor and its single group owner are
10192    /// internally inconsistent.
10193    ///
10194    /// # Examples
10195    ///
10196    /// ```rust
10197    /// use tenferro_tensor::{Rank, TypedTensor};
10198    ///
10199    /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
10200    /// let view = tensor.as_view();
10201    /// assert_eq!(view.strides(), &[1, 2]);
10202    /// ```
10203    pub fn as_view(&self) -> TypedTensorView<'_, T, R>
10204    where
10205        T: 'static,
10206    {
10207        let layout = self.physical_layout();
10208        let placement = self.placement.clone();
10209        match &self.storage {
10210            DynamicStorage::Host(HostStorage { data }) => TypedTensorView {
10211                buffer: TensorStorageRef::Host(data.as_slice()),
10212                root: None,
10213                layout,
10214                placement,
10215                _representation: std::marker::PhantomData,
10216            },
10217            DynamicStorage::Group(core) => {
10218                // Managed owners are constructed only with their matching preset T.
10219                let root = core
10220                    .group
10221                    .group
10222                    .view_raw::<T, R>(core.group.slot)
10223                    .unwrap_or_else(|error| {
10224                        unreachable!("typed tensor group descriptor mismatch: {error}")
10225                    });
10226                let buffer = if let Some(allocation) = root.backend_allocation() {
10227                    TensorStorageRef::Root(allocation)
10228                } else {
10229                    TensorStorageRef::Host(core.group.host_slice::<T>().unwrap_or_default())
10230                };
10231                TypedTensorView {
10232                    buffer,
10233                    root: Some(root),
10234                    layout,
10235                    placement,
10236                    _representation: std::marker::PhantomData,
10237                }
10238            }
10239        }
10240    }
10241
10242    /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
10243    ///
10244    /// # Panics
10245    ///
10246    /// Panics only if the typed descriptor and its single group owner are
10247    /// internally inconsistent.
10248    ///
10249    /// # Examples
10250    ///
10251    /// ```rust
10252    /// use tenferro_tensor::TypedTensor;
10253    ///
10254    /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
10255    /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
10256    /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
10257    /// ```
10258    pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
10259    where
10260        T: TensorScalar + 'static,
10261    {
10262        let layout = self.physical_layout();
10263        let placement = self.placement.clone();
10264        match &mut self.storage {
10265            DynamicStorage::Host(HostStorage { data }) => TypedTensorViewMut {
10266                buffer: TensorStorageRefMut::Host(data.as_mut_slice()),
10267                root: None,
10268                layout,
10269                placement,
10270                _representation: std::marker::PhantomData,
10271            },
10272            DynamicStorage::Group(core) => {
10273                let mut root = core.group.view_mut::<T>().unwrap_or_else(|error| {
10274                    unreachable!("typed tensor group descriptor mismatch: {error}")
10275                });
10276                let buffer = if let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut()
10277                {
10278                    TensorStorageRefMut::Backend(buffer.as_mut())
10279                } else {
10280                    TensorStorageRefMut::Host(root.host_slice_mut().unwrap_or_else(|error| {
10281                        unreachable!("typed tensor group descriptor is not host-backed: {error}")
10282                    }))
10283                };
10284                TypedTensorViewMut {
10285                    buffer,
10286                    root: Some(root),
10287                    layout,
10288                    placement,
10289                    _representation: std::marker::PhantomData,
10290                }
10291            }
10292        }
10293    }
10294
10295    /// Borrow a read-only strided region view over this tensor's backend
10296    /// (device) buffer from explicit layout metadata.
10297    ///
10298    /// This is a metadata-only view: no data is copied or transferred. The
10299    /// layout's reachable element span is validated against the backend
10300    /// buffer's physical length. Host-backed tensors are rejected with an
10301    /// explicit backend error; host regions are expressed with
10302    /// [`TypedTensorView::from_slice`] over host storage instead.
10303    ///
10304    /// # Examples
10305    ///
10306    /// ```rust
10307    /// use tenferro_tensor::TypedTensor;
10308    ///
10309    /// // Host tensors are rejected: this constructor is for backend buffers.
10310    /// let host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
10311    /// let err = host.backend_region_view(vec![2, 2], vec![1, 2], 0).unwrap_err();
10312    /// assert!(err.to_string().contains("backend"));
10313    /// ```
10314    /// # Errors
10315    ///
10316    /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
10317    /// backend region views require a backend buffer. It returns
10318    /// [`crate::Error::Validation`] with
10319    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
10320    /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
10321    /// when the region exceeds the backend buffer, or
10322    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
10323    /// arithmetic overflow.
10324    pub fn backend_region_view(
10325        &self,
10326        shape: Vec<usize>,
10327        strides: Vec<isize>,
10328        offset: isize,
10329    ) -> crate::Result<TypedTensorView<'_, T, DynRank>>
10330    where
10331        T: TensorScalar + 'static,
10332    {
10333        let op = "TypedTensor::backend_region_view";
10334        let DynamicStorage::Group(core) = &self.storage else {
10335            return Err(crate::Error::runtime_state(op, "expected a backend (device) allocation; host tensors use TypedTensorView::from_slice over host storage"));
10336        };
10337        let root = core.group.view_dyn::<T>()?;
10338        let Some(allocation) = root.backend_allocation() else {
10339            return Err(crate::Error::runtime_state(
10340                op,
10341                "expected a backend (device) allocation; host tensors use \
10342                 TypedTensorView::from_slice over host storage",
10343            ));
10344        };
10345        let element_len = allocation
10346            .root_extent()
10347            .byte_len()
10348            .checked_div(size_of::<T>())
10349            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
10350        let layout =
10351            TensorLayout::from_parts(shape_vec(&shape), stride_vec(&strides), offset, element_len)
10352                .map_err(|err| tensor_layout_error(op, err))?;
10353        Ok(TypedTensorView {
10354            buffer: TensorStorageRef::Root(allocation),
10355            root: Some(root),
10356            layout,
10357            placement: self.placement.clone(),
10358            _representation: std::marker::PhantomData,
10359        })
10360    }
10361
10362    /// Borrow a mutable strided region view over this tensor's backend
10363    /// (device) buffer from explicit layout metadata.
10364    ///
10365    /// This is the mutable counterpart of
10366    /// [`TypedTensor::backend_region_view`]. The layout's reachable element
10367    /// span is validated against the backend buffer's physical length, and
10368    /// layouts whose logical elements alias the same physical element are
10369    /// rejected. Host-backed tensors are rejected with an explicit backend
10370    /// error; mutable host regions must go through
10371    /// [`TypedTensorViewMut::try_multi_slice_mut`] or host constructors.
10372    ///
10373    /// The returned view borrows the tensor's backend owner exclusively for its
10374    /// lifetime. This keeps write authority tied to the owner; a second mutable
10375    /// region view must be created only after the first borrow ends.
10376    ///
10377    /// # Examples
10378    ///
10379    /// ```rust
10380    /// use tenferro_tensor::TypedTensor;
10381    ///
10382    /// // Host tensors are rejected: this constructor is for backend buffers.
10383    /// let mut host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
10384    /// let err = host.backend_region_view_mut(vec![2, 2], vec![1, 2], 0).unwrap_err();
10385    /// assert!(err.to_string().contains("backend"));
10386    /// ```
10387    /// # Errors
10388    ///
10389    /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
10390    /// mutable backend region views require a backend buffer. It returns
10391    /// [`crate::Error::Validation`] with
10392    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
10393    /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
10394    /// when the region exceeds the backend buffer,
10395    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
10396    /// logical elements alias, or
10397    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
10398    /// arithmetic overflow.
10399    pub fn backend_region_view_mut(
10400        &mut self,
10401        shape: Vec<usize>,
10402        strides: Vec<isize>,
10403        offset: isize,
10404    ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>>
10405    where
10406        T: TensorScalar + 'static,
10407    {
10408        let op = "TypedTensor::backend_region_view_mut";
10409        let placement = self.placement.clone();
10410        let DynamicStorage::Group(core) = &mut self.storage else {
10411            return Err(crate::Error::runtime_state(op, "expected a backend (device) buffer; mutable host regions use TypedTensorViewMut host constructors or try_multi_slice_mut"));
10412        };
10413        let mut root = core.group.view_mut_dyn::<T>()?;
10414        let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut() else {
10415            return Err(crate::Error::runtime_state(
10416                op,
10417                "expected a backend (device) buffer; mutable host regions use \
10418                 TypedTensorViewMut host constructors or try_multi_slice_mut",
10419            ));
10420        };
10421        let layout = TensorLayout::from_parts(
10422            shape_vec(&shape),
10423            stride_vec(&strides),
10424            offset,
10425            buffer.len(),
10426        )
10427        .map_err(|err| tensor_layout_error(op, err))?;
10428        layout
10429            .validate_mutable_no_overlap()
10430            .map_err(|err| tensor_layout_error(op, err))?;
10431        Ok(TypedTensorViewMut {
10432            buffer: TensorStorageRefMut::Backend(buffer.as_mut()),
10433            root: Some(root),
10434            layout,
10435            placement,
10436            _representation: std::marker::PhantomData,
10437        })
10438    }
10439
10440    /// Consume this tensor and return its logical host storage, layout, and
10441    /// placement.
10442    ///
10443    /// A root-preserving owner exports only its compact logical values, exactly
10444    /// like [`TypedTensor::into_host_vec`].
10445    ///
10446    /// # Examples
10447    ///
10448    /// ```
10449    /// use tenferro_tensor::{StorageBuffer, TypedTensor};
10450    ///
10451    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
10452    /// let Ok((buffer, layout, placement)) = t.into_parts() else {
10453    ///     panic!("a plain host owner extracts")
10454    /// };
10455    /// assert!(matches!(buffer, StorageBuffer::Host(_)));
10456    /// assert_eq!(layout.shape(), &[2]);
10457    /// assert!(placement.device.is_none());
10458    /// # Ok::<(), tenferro_tensor::Error>(())
10459    /// ```
10460    ///
10461    /// # Errors
10462    ///
10463    /// Returns [`ReinterpretError`] carrying the unchanged tensor when it uses
10464    /// backend storage (download it before extracting host storage) or when a
10465    /// group-backed descriptor is not compact column-major
10466    /// ([`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]).
10467    pub fn into_parts(
10468        self,
10469    ) -> std::result::Result<(StorageBuffer<T>, TensorLayout<R>, Placement), ReinterpretError<Self>>
10470    where
10471        T: TensorScalar,
10472    {
10473        let placement = self.placement().clone();
10474        let layout = TensorLayout::<R>::compact(
10475            R::shape_from_vec(shape_vec(self.shape()))
10476                .unwrap_or_else(|err| unreachable!("validated owned shape: {err:?}")),
10477        )
10478        .unwrap_or_else(|err| unreachable!("validated owned shape: {err}"));
10479        // The export contract is identical to `into_host_vec`; reuse it rather
10480        // than repeating the descriptor rebase and its failure recovery.
10481        match self.into_host_vec() {
10482            Ok(data) => Ok((StorageBuffer::Host(data), layout, placement)),
10483            Err(error) => Err(error),
10484        }
10485    }
10486}
10487
10488impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R> {
10489    fn into_managed(self) -> Self {
10490        let TypedTensor {
10491            shape,
10492            placement,
10493            layout,
10494            storage,
10495        } = self;
10496        match storage {
10497            DynamicStorage::Host(HostStorage { data }) => {
10498                let group = promote_host_group::<T, R>(shape.clone(), data)
10499                    .unwrap_or_else(|err| unreachable!("a validated host owner promotes: {err}"));
10500                Self {
10501                    shape,
10502                    placement,
10503                    layout,
10504                    storage: DynamicStorage::Group(GroupStorage {
10505                        group: Box::new(group),
10506                    }),
10507                }
10508            }
10509            DynamicStorage::Group(core) => Self {
10510                shape,
10511                placement,
10512                layout,
10513                storage: DynamicStorage::Group(core),
10514            },
10515        }
10516    }
10517
10518    fn into_group_parts(self) -> (AllocationGroup, DescriptorSlot) {
10519        let TypedTensor {
10520            placement, storage, ..
10521        } = self.into_managed();
10522        let DynamicStorage::Group(GroupStorage { group }) = storage else {
10523            unreachable!("explicit group promotion produces managed storage")
10524        };
10525        let (mut group, slot) = group.into_parts();
10526        group.publish_live_descriptor_placement(slot, placement);
10527        (group, slot)
10528    }
10529
10530    /// Construct a backend-pooled host tensor with weak final-owner reclamation.
10531    /// Explicit Vec extraction disarms reclamation; aliases and retained groups
10532    /// keep the original root alive. The recycler must not acquire session locks.
10533    ///
10534    /// # Errors
10535    /// Returns validation errors for invalid shape/length and runtime-state errors
10536    /// if the freshly created host root cannot attach its recycler.
10537    #[doc(hidden)]
10538    pub fn from_vec_col_major_with_recycler(
10539        shape: impl tenferro_tensor_core::IntoRankShape<R>,
10540        data: Vec<T>,
10541        recycler: std::sync::Weak<dyn crate::HostBufferRecycler<T>>,
10542    ) -> crate::Result<Self> {
10543        let mut tensor = Self::from_vec_col_major(shape, data)?;
10544        if let DynamicStorage::Host(HostStorage { data }) = &mut tensor.storage {
10545            data.recycler = Some(recycler);
10546        }
10547        Ok(tensor)
10548    }
10549
10550    /// Borrow compact host-visible storage through one synchronization guard.
10551    #[doc(hidden)]
10552    pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
10553    where
10554        T: TensorScalar + 'static,
10555    {
10556        self.as_view().with_host_read(f)
10557    }
10558
10559    /// Borrow compact host-visible storage through one exclusive write guard.
10560    #[doc(hidden)]
10561    pub fn with_host_write<U>(&mut self, f: impl FnOnce(&mut [T]) -> U) -> crate::Result<U>
10562    where
10563        T: TensorScalar + 'static,
10564    {
10565        match &mut self.storage {
10566            DynamicStorage::Host(HostStorage { data }) => Ok(f(data.as_mut_slice())),
10567            DynamicStorage::Group(core) => {
10568                let slot = core.group.slot;
10569                let mut view = core
10570                    .group
10571                    .group
10572                    .view_mut::<T, R>(slot)
10573                    .map_err(|error| group_error("TypedTensor::with_host_write", error))?;
10574                let mut prepared = view.prepare_host_write().map_err(|error| {
10575                    crate::Error::runtime_state("TypedTensor::with_host_write", error.to_string())
10576                })?;
10577                let slice = prepared.as_slice_mut().ok_or_else(|| {
10578                    crate::Error::unsupported(
10579                        "TypedTensor::with_host_write",
10580                        "host guard access requires a compact descriptor",
10581                    )
10582                })?;
10583                Ok(f(slice))
10584            }
10585        }
10586    }
10587
10588    /// Whole backing host buffer, including bytes outside a root-preserving
10589    /// extraction's logical extent (#2023).
10590    ///
10591    /// A mutable reinterpretation rebases the descriptor over the whole root,
10592    /// so it must borrow that root rather than the exported logical slice.
10593    fn backing_host_slice_mut(&mut self) -> crate::Result<&mut [T]> {
10594        match &mut self.storage {
10595            DynamicStorage::Host(HostStorage { data }) => Ok(data.as_mut_slice()),
10596            DynamicStorage::Group(core) => core.group.host_slice_mut::<T>(),
10597        }
10598    }
10599
10600    fn group_host_slice(&self) -> &[T] {
10601        let DynamicStorage::Group(core) = &self.storage else {
10602            return &[];
10603        };
10604        core.group
10605            .view::<T>()
10606            .ok()
10607            .and_then(|view| view.host_slice().ok())
10608            .unwrap_or_default()
10609    }
10610}
10611
10612/// Layout builder used by an owning representation reinterpretation.
10613type ReinterpretLayoutFn =
10614    fn(&[usize], &[isize], isize, usize, &'static str) -> crate::Result<TensorLayout<DynRank>>;
10615
10616fn reinterpret_owned<S: TensorScalar, U: TensorScalar, R: TensorRank>(
10617    owner: TypedTensor<S, R>,
10618    from: DType,
10619    to: DType,
10620    make_layout: ReinterpretLayoutFn,
10621    op: &'static str,
10622) -> Result<TypedTensor<U>, ReinterpretError<TypedTensor<S, R>>> {
10623    if let Err(error) = validate_representation_pair(op, from, to) {
10624        return Err(ReinterpretError::new(owner, error));
10625    }
10626    if matches!(to, DType::C32 | DType::C64) && !owner.buffer_len().is_multiple_of(2) {
10627        return Err(ReinterpretError::new(
10628            owner,
10629            crate::Error::invalid_argument(
10630                op,
10631                "buffer",
10632                "the owned real buffer must contain an even number of elements",
10633            ),
10634        ));
10635    }
10636    let source_layout = owner.physical_layout();
10637    let target_layout = match make_layout(
10638        source_layout.shape(),
10639        source_layout.strides(),
10640        source_layout.offset(),
10641        owner.buffer_len(),
10642        op,
10643    ) {
10644        Ok(layout) => layout,
10645        Err(error) => return Err(ReinterpretError::new(owner, error)),
10646    };
10647    // Only an explicit owning representation conversion promotes a plain owner;
10648    // borrowed session dispatch never passes through a group.
10649    let TypedTensor {
10650        shape,
10651        placement,
10652        layout,
10653        storage,
10654    } = owner.into_managed();
10655    let DynamicStorage::Group(GroupStorage { group }) = storage else {
10656        unreachable!("explicit group promotion produces managed storage")
10657    };
10658    match group.reinterpret::<S, U>(
10659        target_layout.shape().to_vec(),
10660        target_layout.strides().to_vec(),
10661        target_layout.offset(),
10662    ) {
10663        Ok(group) => Ok(TypedTensor {
10664            shape: shape_vec(target_layout.shape()),
10665            placement,
10666            layout: stride_vec(target_layout.strides()),
10667            storage: DynamicStorage::Group(GroupStorage {
10668                group: Box::new(group),
10669            }),
10670        }),
10671        Err((group, error)) => Err(ReinterpretError::new(
10672            TypedTensor {
10673                shape,
10674                placement,
10675                layout,
10676                storage: DynamicStorage::Group(GroupStorage {
10677                    group: Box::new(group),
10678                }),
10679            },
10680            error,
10681        )),
10682    }
10683}
10684
10685impl<R: TensorRank> TypedTensor<Complex32, R> {
10686    /// Borrow this tensor as an interleaved `f32` view without copying.
10687    ///
10688    /// # Errors
10689    ///
10690    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10691    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10692    /// invalid tensor layout.
10693    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f32, DynRank>> {
10694        self.as_view().as_real_view()
10695    }
10696
10697    /// Borrow this tensor mutably as an interleaved `f32` view without copying.
10698    ///
10699    /// # Errors
10700    ///
10701    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10702    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10703    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10704    /// invalid representation metadata.
10705    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
10706        let op = "TypedTensor::as_real_view_mut";
10707        validate_representation_pair(op, DType::C32, DType::F32)?;
10708        let source_layout = self.physical_layout();
10709        let layout = reinterpret_complex_to_real_layout(
10710            self.shape(),
10711            source_layout.strides(),
10712            source_layout.offset(),
10713            self.buffer_len(),
10714            op,
10715        )?;
10716        layout
10717            .validate_mutable_no_overlap()
10718            .map_err(|err| tensor_layout_error(op, err))?;
10719        if self.backend_buffer().is_some() {
10720            return Err(crate::Error::unsupported(
10721                op,
10722                "backend representation reinterpretation is enabled by the provider phases",
10723            ));
10724        }
10725        let placement = self.placement().clone();
10726        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(
10727            self.backing_host_slice_mut()?,
10728            op,
10729        )?);
10730        Ok(TypedTensorViewMut {
10731            buffer,
10732            root: None,
10733            layout,
10734            placement,
10735            _representation: std::marker::PhantomData,
10736        })
10737    }
10738
10739    /// Consume this tensor and reinterpret its owner as `f32` without copying.
10740    ///
10741    /// A failed operation returns the unchanged owner through
10742    /// [`ReinterpretError::into_owner`].
10743    ///
10744    /// # Errors
10745    ///
10746    /// Returns [`ReinterpretError::error`] containing
10747    /// [`ValidationError::InvalidArgument`] or
10748    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10749    /// owner.
10750    pub fn into_real(self) -> Result<TypedTensor<f32, DynRank>, ReinterpretError<Self>> {
10751        reinterpret_owned(
10752            self,
10753            DType::C32,
10754            DType::F32,
10755            reinterpret_complex_to_real_layout,
10756            "TypedTensor::into_real",
10757        )
10758    }
10759}
10760
10761impl<R: TensorRank> TypedTensor<Complex64, R> {
10762    /// Borrow this tensor as an interleaved `f64` view without copying.
10763    ///
10764    /// # Errors
10765    ///
10766    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10767    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10768    /// invalid tensor layout.
10769    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f64, DynRank>> {
10770        self.as_view().as_real_view()
10771    }
10772
10773    /// Borrow this tensor mutably as an interleaved `f64` view without copying.
10774    ///
10775    /// # Errors
10776    ///
10777    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10778    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10779    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10780    /// invalid representation metadata.
10781    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
10782        let op = "TypedTensor::as_real_view_mut";
10783        validate_representation_pair(op, DType::C64, DType::F64)?;
10784        let source_layout = self.physical_layout();
10785        let layout = reinterpret_complex_to_real_layout(
10786            self.shape(),
10787            source_layout.strides(),
10788            source_layout.offset(),
10789            self.buffer_len(),
10790            op,
10791        )?;
10792        layout
10793            .validate_mutable_no_overlap()
10794            .map_err(|err| tensor_layout_error(op, err))?;
10795        if self.backend_buffer().is_some() {
10796            return Err(crate::Error::unsupported(
10797                op,
10798                "backend representation reinterpretation is enabled by the provider phases",
10799            ));
10800        }
10801        let placement = self.placement().clone();
10802        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(
10803            self.backing_host_slice_mut()?,
10804            op,
10805        )?);
10806        Ok(TypedTensorViewMut {
10807            buffer,
10808            root: None,
10809            layout,
10810            placement,
10811            _representation: std::marker::PhantomData,
10812        })
10813    }
10814
10815    /// Consume this tensor and reinterpret its owner as `f64` without copying.
10816    ///
10817    /// A failed operation returns the unchanged owner through
10818    /// [`ReinterpretError::into_owner`].
10819    ///
10820    /// # Errors
10821    ///
10822    /// Returns [`ReinterpretError::error`] containing
10823    /// [`ValidationError::InvalidArgument`] or
10824    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10825    /// owner.
10826    pub fn into_real(self) -> Result<TypedTensor<f64, DynRank>, ReinterpretError<Self>> {
10827        reinterpret_owned(
10828            self,
10829            DType::C64,
10830            DType::F64,
10831            reinterpret_complex_to_real_layout,
10832            "TypedTensor::into_real",
10833        )
10834    }
10835}
10836
10837impl<R: TensorRank> TypedTensor<f32, R> {
10838    /// Borrow this tensor as a complex view without copying.
10839    ///
10840    /// # Errors
10841    ///
10842    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10843    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10844    /// invalid tensor layout.
10845    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex32, DynRank>> {
10846        self.as_view().as_complex_view()
10847    }
10848
10849    /// Borrow this tensor mutably as a complex view without copying.
10850    ///
10851    /// # Errors
10852    ///
10853    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10854    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10855    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10856    /// invalid representation metadata.
10857    pub fn as_complex_view_mut(
10858        &mut self,
10859    ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
10860        let op = "TypedTensor::as_complex_view_mut";
10861        validate_representation_pair(op, DType::F32, DType::C32)?;
10862        let source_layout = self.physical_layout();
10863        let layout = reinterpret_real_to_complex_layout(
10864            self.shape(),
10865            source_layout.strides(),
10866            source_layout.offset(),
10867            self.buffer_len(),
10868            op,
10869        )?;
10870        layout
10871            .validate_mutable_no_overlap()
10872            .map_err(|err| tensor_layout_error(op, err))?;
10873        if self.backend_buffer().is_some() {
10874            return Err(crate::Error::unsupported(
10875                op,
10876                "backend representation reinterpretation is enabled by the provider phases",
10877            ));
10878        }
10879        let placement = self.placement().clone();
10880        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(
10881            self.backing_host_slice_mut()?,
10882            op,
10883        )?);
10884        Ok(TypedTensorViewMut {
10885            buffer,
10886            root: None,
10887            layout,
10888            placement,
10889            _representation: std::marker::PhantomData,
10890        })
10891    }
10892
10893    /// Consume this tensor and reinterpret its owner as `Complex32` without copying.
10894    ///
10895    /// The compact source must have an even physical element count. A failed
10896    /// operation returns the unchanged owner through
10897    /// [`ReinterpretError::into_owner`].
10898    ///
10899    /// # Errors
10900    ///
10901    /// Returns [`ReinterpretError::error`] containing
10902    /// [`ValidationError::InvalidArgument`] or
10903    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10904    /// owner.
10905    pub fn into_complex(self) -> Result<TypedTensor<Complex32, DynRank>, ReinterpretError<Self>> {
10906        reinterpret_owned(
10907            self,
10908            DType::F32,
10909            DType::C32,
10910            reinterpret_real_to_complex_layout,
10911            "TypedTensor::into_complex",
10912        )
10913    }
10914}
10915
10916impl<R: TensorRank> TypedTensor<f64, R> {
10917    /// Borrow this tensor as a complex view without copying.
10918    ///
10919    /// # Errors
10920    ///
10921    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10922    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10923    /// invalid tensor layout.
10924    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex64, DynRank>> {
10925        self.as_view().as_complex_view()
10926    }
10927
10928    /// Borrow this tensor mutably as a complex view without copying.
10929    ///
10930    /// # Errors
10931    ///
10932    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10933    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10934    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10935    /// invalid representation metadata.
10936    pub fn as_complex_view_mut(
10937        &mut self,
10938    ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
10939        let op = "TypedTensor::as_complex_view_mut";
10940        validate_representation_pair(op, DType::F64, DType::C64)?;
10941        let source_layout = self.physical_layout();
10942        let layout = reinterpret_real_to_complex_layout(
10943            self.shape(),
10944            source_layout.strides(),
10945            source_layout.offset(),
10946            self.buffer_len(),
10947            op,
10948        )?;
10949        layout
10950            .validate_mutable_no_overlap()
10951            .map_err(|err| tensor_layout_error(op, err))?;
10952        if self.backend_buffer().is_some() {
10953            return Err(crate::Error::unsupported(
10954                op,
10955                "backend representation reinterpretation is enabled by the provider phases",
10956            ));
10957        }
10958        let placement = self.placement().clone();
10959        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(
10960            self.backing_host_slice_mut()?,
10961            op,
10962        )?);
10963        Ok(TypedTensorViewMut {
10964            buffer,
10965            root: None,
10966            layout,
10967            placement,
10968            _representation: std::marker::PhantomData,
10969        })
10970    }
10971
10972    /// Consume this tensor and reinterpret its owner as `Complex64` without copying.
10973    ///
10974    /// The compact source must have an even physical element count. A failed
10975    /// operation returns the unchanged owner through
10976    /// [`ReinterpretError::into_owner`].
10977    ///
10978    /// # Errors
10979    ///
10980    /// Returns [`ReinterpretError::error`] containing
10981    /// [`ValidationError::InvalidArgument`] or
10982    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10983    /// owner.
10984    pub fn into_complex(self) -> Result<TypedTensor<Complex64, DynRank>, ReinterpretError<Self>> {
10985        reinterpret_owned(
10986            self,
10987            DType::F64,
10988            DType::C64,
10989            reinterpret_real_to_complex_layout,
10990            "TypedTensor::into_complex",
10991        )
10992    }
10993}
10994
10995impl Tensor {
10996    /// Borrow a complex tensor as its sealed interleaved real representation.
10997    ///
10998    /// # Errors
10999    ///
11000    /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
11001    /// [`ValidationError::ViewOutOfBounds`] or
11002    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
11003    pub fn as_real_view(&self) -> crate::Result<TensorView<'_>> {
11004        match self.dtype() {
11005            DType::C32 => self
11006                .as_typed::<Complex<f32>>()
11007                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11008                .as_real_view()
11009                .map(TensorView::F32),
11010            DType::C64 => self
11011                .as_typed::<Complex<f64>>()
11012                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11013                .as_real_view()
11014                .map(TensorView::F64),
11015            other => Err(crate::Error::unsupported_dtype_conversion(
11016                "Tensor::as_real_view",
11017                other,
11018                DType::F32,
11019                "only complex tensors have a sealed real representation view",
11020            )),
11021        }
11022    }
11023
11024    /// Borrow a complex tensor mutably as its sealed interleaved real representation.
11025    ///
11026    /// # Errors
11027    ///
11028    /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
11029    /// [`ValidationError::ViewOutOfBounds`] or
11030    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
11031    pub fn as_real_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
11032        match self.dtype() {
11033            DType::C32 => self
11034                .as_typed_mut::<Complex<f32>>()
11035                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11036                .as_real_view_mut()
11037                .map(TensorViewMut::F32),
11038            DType::C64 => self
11039                .as_typed_mut::<Complex<f64>>()
11040                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11041                .as_real_view_mut()
11042                .map(TensorViewMut::F64),
11043            other => Err(crate::Error::unsupported_dtype_conversion(
11044                "Tensor::as_real_view_mut",
11045                other,
11046                DType::F32,
11047                "only complex tensors have a sealed real representation view",
11048            )),
11049        }
11050    }
11051
11052    /// Consume a complex tensor and reinterpret its owner as real without copying.
11053    ///
11054    /// # Errors
11055    ///
11056    /// Returns [`ReinterpretError::error`] containing
11057    /// [`ValidationError::InvalidArgument`] or
11058    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
11059    /// owner.
11060    pub fn into_real(self) -> Result<Self, ReinterpretError<Self>> {
11061        let dtype = self.dtype();
11062        match dtype {
11063            DType::C32 => {
11064                let tensor = <Complex<f32> as TensorScalar>::into_typed(self)
11065                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
11066                tensor
11067                    .into_real()
11068                    .map(Tensor::from_typed::<f32>)
11069                    .map_err(|error| {
11070                        let (owner, error) = error.into_parts();
11071                        ReinterpretError::new(Tensor::from_typed::<Complex<f32>>(owner), error)
11072                    })
11073            }
11074            DType::C64 => {
11075                let tensor = <Complex<f64> as TensorScalar>::into_typed(self)
11076                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
11077                tensor
11078                    .into_real()
11079                    .map(Tensor::from_typed::<f64>)
11080                    .map_err(|error| {
11081                        let (owner, error) = error.into_parts();
11082                        ReinterpretError::new(Tensor::from_typed::<Complex<f64>>(owner), error)
11083                    })
11084            }
11085            _ => Err(ReinterpretError::new(
11086                self,
11087                crate::Error::unsupported(
11088                    "Tensor::into_real",
11089                    "only complex tensors have a sealed real representation",
11090                ),
11091            )),
11092        }
11093    }
11094
11095    /// Borrow an interleaved real tensor as its sealed complex representation.
11096    ///
11097    /// # Errors
11098    ///
11099    /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
11100    /// [`ValidationError::ViewOutOfBounds`] or
11101    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
11102    pub fn as_complex_view(&self) -> crate::Result<TensorView<'_>> {
11103        match self.dtype() {
11104            DType::F32 => self
11105                .as_typed::<f32>()
11106                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11107                .as_complex_view()
11108                .map(TensorView::C32),
11109            DType::F64 => self
11110                .as_typed::<f64>()
11111                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11112                .as_complex_view()
11113                .map(TensorView::C64),
11114            other => Err(crate::Error::unsupported_dtype_conversion(
11115                "Tensor::as_complex_view",
11116                other,
11117                DType::C32,
11118                "only real tensors can have a sealed complex representation view",
11119            )),
11120        }
11121    }
11122
11123    /// Borrow an interleaved real tensor mutably as its sealed complex representation.
11124    ///
11125    /// # Errors
11126    ///
11127    /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
11128    /// [`ValidationError::ViewOutOfBounds`] or
11129    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
11130    pub fn as_complex_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
11131        match self.dtype() {
11132            DType::F32 => self
11133                .as_typed_mut::<f32>()
11134                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11135                .as_complex_view_mut()
11136                .map(TensorViewMut::C32),
11137            DType::F64 => self
11138                .as_typed_mut::<f64>()
11139                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11140                .as_complex_view_mut()
11141                .map(TensorViewMut::C64),
11142            other => Err(crate::Error::unsupported_dtype_conversion(
11143                "Tensor::as_complex_view_mut",
11144                other,
11145                DType::C32,
11146                "only real tensors can have a sealed complex representation view",
11147            )),
11148        }
11149    }
11150
11151    /// Consume a real tensor and reinterpret its owner as complex without copying.
11152    ///
11153    /// # Errors
11154    ///
11155    /// Returns [`ReinterpretError::error`] containing
11156    /// [`ValidationError::InvalidArgument`] or
11157    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
11158    /// owner.
11159    pub fn into_complex(self) -> Result<Self, ReinterpretError<Self>> {
11160        let dtype = self.dtype();
11161        match dtype {
11162            DType::F32 => {
11163                let tensor = <f32 as TensorScalar>::into_typed(self)
11164                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
11165                tensor
11166                    .into_complex()
11167                    .map(Tensor::from_typed::<Complex<f32>>)
11168                    .map_err(|error| {
11169                        let (owner, error) = error.into_parts();
11170                        ReinterpretError::new(Tensor::from_typed::<f32>(owner), error)
11171                    })
11172            }
11173            DType::F64 => {
11174                let tensor = <f64 as TensorScalar>::into_typed(self)
11175                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
11176                tensor
11177                    .into_complex()
11178                    .map(Tensor::from_typed::<Complex<f64>>)
11179                    .map_err(|error| {
11180                        let (owner, error) = error.into_parts();
11181                        ReinterpretError::new(Tensor::from_typed::<f64>(owner), error)
11182                    })
11183            }
11184            _ => Err(ReinterpretError::new(
11185                self,
11186                crate::Error::unsupported(
11187                    "Tensor::into_complex",
11188                    "only real tensors have a sealed complex representation",
11189                ),
11190            )),
11191        }
11192    }
11193
11194    /// Make an explicit owning copy of this dtype-erased tensor.
11195    ///
11196    /// `Tensor` deliberately does not implement [`Clone`]: copying can fail
11197    /// (device-only storage is rejected) and allocates a new, independent
11198    /// owner, so it is an explicit fallible call rather than an infallible
11199    /// `clone()`. The copy shares nothing with `self`; mutating one never
11200    /// affects the other. To share one tensor between several users without
11201    /// copying, wrap it in [`std::sync::Arc`] or borrow views of it.
11202    ///
11203    /// # Examples
11204    ///
11205    /// ```
11206    /// use std::sync::Arc;
11207    /// use tenferro_tensor::Tensor;
11208    ///
11209    /// let weights = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11210    /// // Two independent owners of the same values.
11211    /// let copy = weights.duplicate()?;
11212    /// assert_eq!(copy.as_slice::<f64>()?, weights.as_slice::<f64>()?);
11213    ///
11214    /// // A shared, read-only handle instead of a copy.
11215    /// let shared = Arc::new(weights);
11216    /// let other = Arc::clone(&shared);
11217    /// assert_eq!(other.shape(), &[2]);
11218    /// # Ok::<(), tenferro_tensor::Error>(())
11219    /// ```
11220    ///
11221    /// # Errors
11222    ///
11223    /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
11224    /// when the selected backend/storage owner cannot be duplicated.
11225    pub fn duplicate(&self) -> crate::Result<Self> {
11226        match &self.payload {
11227            // A caller-owned payload is copied through its own entry point, which
11228            // keeps its element type and its view; sharing the payload instead
11229            // would alias the caller's storage.
11230            TensorPayload::External(payload, placement) => Ok(Self {
11231                payload: TensorPayload::External(payload.duplicate(), placement.clone()),
11232            }),
11233            TensorPayload::Native(_) => match self.dtype() {
11234                DType::F32 => self.duplicate_typed::<f32>(),
11235                DType::F64 => self.duplicate_typed::<f64>(),
11236                DType::I32 => self.duplicate_typed::<i32>(),
11237                DType::I64 => self.duplicate_typed::<i64>(),
11238                DType::Bool => self.duplicate_typed::<bool>(),
11239                DType::C32 => self.duplicate_typed::<Complex<f32>>(),
11240                DType::C64 => self.duplicate_typed::<Complex<f64>>(),
11241                DType::External(_) => unreachable!("a native payload carries a preset dtype"),
11242            },
11243        }
11244    }
11245
11246    /// Duplicate a preset tensor through its typed owner.
11247    fn duplicate_typed<T: TensorScalar>(&self) -> crate::Result<Self> {
11248        self.as_typed::<T>()
11249            .unwrap_or_else(|| unreachable!("the caller selected this arm from the dtype"))
11250            .duplicate()
11251            .map(Self::from_typed)
11252    }
11253
11254    /// Create a tensor from a shape and column-major flat data.
11255    ///
11256    /// This is the `Tensor`-level equivalent of
11257    /// `TypedTensor::<T>::from_vec_col_major`.
11258    ///
11259    /// # Examples
11260    ///
11261    /// ```
11262    /// use tenferro_tensor::Tensor;
11263    ///
11264    /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
11265    /// assert_eq!(t.shape(), &[2, 2]);
11266    /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
11267    /// ```
11268    /// # Errors
11269    ///
11270    /// Returns [`crate::Error::Validation`] with
11271    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
11272    /// the shape product differs from `data.len()`, or
11273    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
11274    /// arithmetic overflows.
11275    pub fn from_vec_col_major<T: TensorScalar>(
11276        shape: impl tenferro_tensor_core::IntoShapeVec,
11277        data: Vec<T>,
11278    ) -> crate::Result<Self> {
11279        // Build the typed owner once and wrap it: routing the shape through
11280        // `T::into_tensor(Vec<usize>, _)` costs an exact-size `Vec` per construction
11281        // that the destination cannot keep, because the erased payload stores the
11282        // inline shape the typed constructor already produces (#2177).
11283        Ok(Self::from_typed(TypedTensor::<T>::from_vec_col_major(
11284            shape, data,
11285        )?))
11286    }
11287
11288    /// Create a tensor from a shape and row-major (C-order) flat data.
11289    ///
11290    /// The values are reordered once into tenferro's column-major storage;
11291    /// no row-major owner is created. Use this for buffers authored in
11292    /// PyTorch/NumPy/C order instead of passing them to
11293    /// [`Self::from_vec_col_major`], which would reinterpret them silently.
11294    /// This is the `Tensor`-level equivalent of
11295    /// `TypedTensor::<T>::from_vec_row_major`.
11296    ///
11297    /// # Examples
11298    ///
11299    /// ```
11300    /// use tenferro_tensor::Tensor;
11301    ///
11302    /// // Row-major [[1, 2, 3], [4, 5, 6]].
11303    /// let t = Tensor::from_vec_row_major(vec![2, 3], vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0])?;
11304    /// assert_eq!(t.shape(), &[2, 3]);
11305    /// assert_eq!(t.as_slice::<f64>()?, &[1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
11306    /// # Ok::<(), tenferro_tensor::Error>(())
11307    /// ```
11308    /// # Errors
11309    ///
11310    /// Returns [`crate::Error::Validation`] with
11311    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
11312    /// the shape product differs from `data.len()`, or
11313    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
11314    /// arithmetic overflows.
11315    pub fn from_vec_row_major<T: TensorScalar>(
11316        shape: impl tenferro_tensor_core::IntoShapeVec,
11317        data: Vec<T>,
11318    ) -> crate::Result<Self> {
11319        // The reorder keeps its own `op` name, and the shape comes back as the inline
11320        // vector the typed constructor takes, so neither `.to_vec()` in the erased
11321        // route is needed (#2177).
11322        let (shape, reordered) = row_major_reorder::<T, tenferro_tensor_core::DynRank>(
11323            shape,
11324            data,
11325            "Tensor::from_vec_row_major",
11326        )?;
11327        Ok(Self::from_typed(TypedTensor::<T>::from_vec_col_major(
11328            shape, reordered,
11329        )?))
11330    }
11331
11332    /// Tensor shape.
11333    ///
11334    /// # Examples
11335    ///
11336    /// ```rust
11337    /// use tenferro_tensor::{Tensor, TypedTensor};
11338    ///
11339    /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
11340    /// assert_eq!(t.shape(), &[2]);
11341    /// ```
11342    pub fn shape(&self) -> &[usize] {
11343        match &self.payload {
11344            TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.shape()),
11345            // The payload keeps its shape from construction.
11346            TensorPayload::External(payload, _) => payload.shape(),
11347        }
11348    }
11349
11350    /// Tensor dtype tag.
11351    ///
11352    /// # Examples
11353    ///
11354    /// ```rust
11355    /// use tenferro_tensor::{DType, Tensor, TypedTensor};
11356    ///
11357    /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
11358    /// assert_eq!(t.dtype(), DType::F64);
11359    /// ```
11360    pub fn dtype(&self) -> DType {
11361        match &self.payload {
11362            TensorPayload::Native(preset) => match preset {
11363                PresetTensor::F32(_) => DType::F32,
11364                PresetTensor::F64(_) => DType::F64,
11365                PresetTensor::I32(_) => DType::I32,
11366                PresetTensor::I64(_) => DType::I64,
11367                PresetTensor::Bool(_) => DType::Bool,
11368                PresetTensor::C32(_) => DType::C32,
11369                PresetTensor::C64(_) => DType::C64,
11370            },
11371            // The payload carries its own element identity.
11372            TensorPayload::External(payload, _) => DType::External(payload.element_type_id()),
11373        }
11374    }
11375
11376    /// Return placement metadata for this dtype-erased tensor.
11377    ///
11378    /// # Examples
11379    ///
11380    /// ```rust
11381    /// use tenferro_tensor::{MemoryKind, Tensor};
11382    ///
11383    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
11384    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
11385    /// ```
11386    pub fn placement(&self) -> &Placement {
11387        match &self.payload {
11388            TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.placement()),
11389            // The placement is stored with the payload.
11390            TensorPayload::External(_, placement) => placement,
11391        }
11392    }
11393
11394    /// Return whether this tensor is backed by backend-native storage.
11395    ///
11396    /// # Examples
11397    ///
11398    /// ```rust
11399    /// use tenferro_tensor::Tensor;
11400    ///
11401    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
11402    /// assert!(!t.is_backend_buffer());
11403    /// ```
11404    pub fn is_backend_buffer(&self) -> bool {
11405        match &self.payload {
11406            TensorPayload::Native(preset) => with_preset!(preset, |typed| {
11407                matches!(&typed.storage, DynamicStorage::Group(core) if core.group.is_backend_buffer())
11408            }),
11409            // A caller-owned payload is host memory, never a backend buffer.
11410            TensorPayload::External(..) => false,
11411        }
11412    }
11413
11414    /// Compute the physical element offset for a logical index.
11415    ///
11416    /// # Examples
11417    ///
11418    /// ```rust
11419    /// use tenferro_tensor::Tensor;
11420    ///
11421    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11422    /// assert_eq!(t.layout_linear_offset(&[1])?, 1);
11423    /// # Ok::<(), tenferro_tensor::Error>(())
11424    /// ```
11425    /// # Errors
11426    ///
11427    /// Returns [`crate::Error::Validation`] with
11428    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
11429    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
11430    /// when an index is outside its axis extent, or
11431    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
11432    /// arithmetic overflows.
11433    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
11434        match &self.payload {
11435            TensorPayload::Native(_) => {
11436                let layout = tensor_layout(self);
11437                checked_view_offset_result(
11438                    layout.shape(),
11439                    layout.strides(),
11440                    layout.offset(),
11441                    indices,
11442                    "Tensor::layout_linear_offset",
11443                )
11444            }
11445            // A caller-owned payload's owner resolves its own offsets.
11446            TensorPayload::External(..) => Err(crate::Error::unsupported_dtype(
11447                "layout_linear_offset",
11448                self.dtype(),
11449                "an externally defined payload resolves its own offsets",
11450            )),
11451        }
11452    }
11453
11454    /// Return whether this tensor is compact column-major.
11455    ///
11456    /// # Examples
11457    ///
11458    /// ```rust
11459    /// use tenferro_tensor::Tensor;
11460    ///
11461    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11462    /// assert!(t.is_col_major_contiguous()?);
11463    /// # Ok::<(), tenferro_tensor::Error>(())
11464    /// ```
11465    /// # Errors
11466    ///
11467    /// Returns [`crate::Error::Validation`] with
11468    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
11469    /// compactness arithmetic overflows.
11470    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
11471        match &self.payload {
11472            TensorPayload::Native(_) => tensor_layout(self)
11473                .is_compact_col_major()
11474                .map_err(|err| tensor_layout_error("Tensor::is_col_major_contiguous", err)),
11475            // The payload is a compact column-major host tensor by construction.
11476            TensorPayload::External(..) => Ok(true),
11477        }
11478    }
11479
11480    /// Return a compact string summary of this tensor's layout metadata.
11481    ///
11482    /// # Examples
11483    ///
11484    /// ```rust
11485    /// use tenferro_tensor::Tensor;
11486    ///
11487    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11488    /// assert!(t.layout_summary().contains("shape=[2]"));
11489    /// # Ok::<(), tenferro_tensor::Error>(())
11490    /// ```
11491    pub fn layout_summary(&self) -> String {
11492        let layout = tensor_layout(self);
11493        layout_summary(layout.shape(), layout.strides(), layout.offset())
11494    }
11495
11496    /// Assert this tensor is compact column-major.
11497    ///
11498    /// # Examples
11499    ///
11500    /// ```rust
11501    /// use tenferro_tensor::Tensor;
11502    ///
11503    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11504    /// t.assert_col_major_contiguous()?;
11505    /// # Ok::<(), tenferro_tensor::Error>(())
11506    /// ```
11507    /// # Errors
11508    ///
11509    /// Returns [`crate::Error::Validation`] with
11510    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
11511    /// compactness arithmetic overflows, or
11512    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
11513    /// tensor is not compact column-major.
11514    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
11515        let layout = tensor_layout(self);
11516        assert_layout_col_major_contiguous(
11517            self.is_col_major_contiguous()?,
11518            layout.shape(),
11519            layout.strides(),
11520            layout.offset(),
11521            "Tensor::assert_col_major_contiguous",
11522        )
11523    }
11524
11525    /// Try to borrow the host data as a typed slice.
11526    ///
11527    /// Returns an error if the tensor dtype does not match `T`.
11528    ///
11529    /// # Examples
11530    ///
11531    /// ```
11532    /// use tenferro_tensor::{Tensor, TypedTensor};
11533    ///
11534    /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
11535    /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
11536    /// assert!(t.as_slice::<f32>().is_err());
11537    /// ```
11538    /// # Errors
11539    ///
11540    /// Returns [`crate::Error::Validation`] with
11541    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
11542    /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
11543    /// matching tensor uses backend storage that has not been downloaded.
11544    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
11545        T::as_slice(self)
11546    }
11547
11548    /// Borrow the typed tensor when the requested scalar matches this tensor's dtype.
11549    ///
11550    /// This is the accessor tag-based dispatch needs: it recovers the typed tensor — and with it the
11551    /// device buffer — from a value whose element type is only known at run time, so a caller can
11552    /// dispatch on [`Tensor::dtype`] instead of matching every variant. An externally defined scalar
11553    /// is not a typed tensor, so it returns `None` rather than guessing a representation.
11554    ///
11555    /// # Examples
11556    ///
11557    /// ```
11558    /// use tenferro_tensor::Tensor;
11559    ///
11560    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11561    /// assert!(tensor.as_typed::<f64>().is_some());
11562    /// assert!(tensor.as_typed::<f32>().is_none());
11563    /// assert_eq!(tensor.as_typed::<f64>().unwrap().shape(), &[2]);
11564    /// # Ok::<(), tenferro_tensor::Error>(())
11565    /// ```
11566    #[must_use]
11567    pub fn as_typed<T: TensorScalar>(&self) -> Option<&TypedTensor<T>> {
11568        if self.dtype() != T::dtype() {
11569            return None;
11570        }
11571        match &self.payload {
11572            TensorPayload::Native(preset) => with_preset!(preset, |typed| {
11573                (typed as &dyn Any).downcast_ref::<TypedTensor<T>>()
11574            }),
11575            TensorPayload::External(..) => None,
11576        }
11577    }
11578
11579    /// Mutably borrow the typed tensor when the requested scalar matches this tensor's dtype.
11580    ///
11581    /// This is the mutable half of [`Tensor::as_typed`], for the tables whose arm calls a method that
11582    /// needs `&mut`, such as marking a freshly allocated output with its placement. An externally
11583    /// defined scalar is not a typed tensor, so it returns `None` for the same reason.
11584    ///
11585    /// # Examples
11586    ///
11587    /// ```
11588    /// use tenferro_tensor::Tensor;
11589    ///
11590    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11591    /// assert!(tensor.as_typed_mut::<f64>().is_some());
11592    /// assert!(tensor.as_typed_mut::<f32>().is_none());
11593    /// # Ok::<(), tenferro_tensor::Error>(())
11594    /// ```
11595    #[must_use]
11596    pub fn as_typed_mut<T: TensorScalar>(&mut self) -> Option<&mut TypedTensor<T>> {
11597        if self.dtype() != T::dtype() {
11598            return None;
11599        }
11600        match &mut self.payload {
11601            TensorPayload::Native(preset) => with_preset!(preset, |typed| {
11602                (typed as &mut dyn Any).downcast_mut::<TypedTensor<T>>()
11603            }),
11604            TensorPayload::External(..) => None,
11605        }
11606    }
11607
11608    /// Consume this tensor and return the owned typed tensor when the dtype matches.
11609    ///
11610    /// This is the consuming counterpart of [`Tensor::as_typed`], for the tables whose arm hands the typed
11611    /// tensor to a function that takes it by value — reusing its buffer rather than copying it.
11612    ///
11613    /// # Examples
11614    ///
11615    /// ```
11616    /// use tenferro_tensor::Tensor;
11617    ///
11618    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11619    /// assert!(tensor.into_typed::<f64>().is_ok());
11620    ///
11621    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0])?;
11622    /// assert!(tensor.into_typed::<f64>().is_err());
11623    /// # Ok::<(), tenferro_tensor::Error>(())
11624    /// ```
11625    /// # Errors
11626    ///
11627    /// Returns [`ReinterpretError`] carrying the unchanged tensor when `T` is
11628    /// not this tensor's dtype, with [`crate::Error::Validation`] and
11629    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] as the cause.
11630    /// A matching tensor is handed over as it is, including one whose storage
11631    /// lives in a backend buffer.
11632    pub fn into_typed<T: TensorScalar>(
11633        self,
11634    ) -> std::result::Result<TypedTensor<T>, ReinterpretError<Self>> {
11635        T::into_typed(self)
11636    }
11637
11638    /// Consume this tensor and return its owned column-major buffer when the
11639    /// dtype matches.
11640    ///
11641    /// # Examples
11642    ///
11643    /// ```
11644    /// use tenferro_tensor::Tensor;
11645    ///
11646    /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
11647    /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
11648    /// ```
11649    /// # Errors
11650    ///
11651    /// Returns [`ReinterpretError`] carrying the unchanged tensor when `T` does
11652    /// not match the tensor dtype or when the matching tensor uses backend
11653    /// storage that has not been downloaded.
11654    pub fn into_vec_col_major<T: TensorScalar>(
11655        self,
11656    ) -> std::result::Result<(Vec<usize>, Vec<T>), ReinterpretError<Self>> {
11657        let typed = T::into_typed(self)?;
11658        match typed.into_vec_col_major() {
11659            Ok(parts) => Ok(parts),
11660            Err(failure) => {
11661                let (owner, error) = failure.into_parts();
11662                Err(ReinterpretError::new(Tensor::from_typed(owner), error))
11663            }
11664        }
11665    }
11666}
11667
11668// INVARIANT: retained for crate-local layout tests while tensor indexing
11669// helpers remain split across the tensor and CPU crates.
11670#[allow(dead_code)]
11671pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
11672    for i in 0..shape.len() {
11673        if shape[i] == 0 {
11674            out[i] = 0;
11675        } else {
11676            out[i] = flat % shape[i];
11677            flat /= shape[i];
11678        }
11679    }
11680}