Skip to main content

tenferro_tensor/
types.rs

1use num_complex::{Complex, Complex32, Complex64};
2use num_traits::{One, Zero};
3use std::any::Any;
4use std::fmt::Debug;
5use std::marker::PhantomData;
6use std::mem::{align_of, needs_drop, offset_of, size_of};
7use std::num::NonZeroUsize;
8use std::ops::Deref;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11use crate::config::SliceConfig;
12use crate::error::ReinterpretError;
13pub use tenferro_tensor_core::{DType, DynRank, Rank, TensorLayout, TensorRank};
14use tenferro_tensor_core::{ShapeVec, StrideVec};
15use tenferro_tensor_core::{SliceSpec as CoreSliceSpec, ValidationError};
16
17use crate::storage::{
18    AllocationGroup, AllocationSlot, BackendAllocation, DescriptorSlot, GroupError, GroupReadView,
19    GroupWriteView,
20};
21
22mod accessors;
23mod col_major;
24mod shape_packing;
25mod strided_view;
26#[cfg(test)]
27mod tests;
28
29pub use col_major::{ColMajorView, ColMajorViewMut};
30pub use strided_view::StridedSliceSpec;
31
32fn shape_vec(shape: &[usize]) -> ShapeVec {
33    shape.iter().copied().collect()
34}
35
36fn stride_vec(strides: &[isize]) -> StrideVec {
37    strides.iter().copied().collect()
38}
39
40fn representation_pair_error(
41    op: &'static str,
42    from: DType,
43    to: DType,
44    message: impl Into<String>,
45) -> crate::Error {
46    crate::Error::unsupported_dtype_conversion(op, from, to, message)
47}
48
49fn validate_representation_pair(op: &'static str, from: DType, to: DType) -> crate::Result<()> {
50    let valid = match (from, to) {
51        (DType::C32, DType::F32) | (DType::F32, DType::C32) => {
52            size_of::<Complex32>() == 2 * size_of::<f32>()
53                && align_of::<Complex32>() == align_of::<f32>()
54                && offset_of!(Complex32, re) == 0
55                && offset_of!(Complex32, im) == size_of::<f32>()
56                && !needs_drop::<Complex32>()
57                && !needs_drop::<f32>()
58        }
59        (DType::C64, DType::F64) | (DType::F64, DType::C64) => {
60            size_of::<Complex64>() == 2 * size_of::<f64>()
61                && align_of::<Complex64>() == align_of::<f64>()
62                && offset_of!(Complex64, re) == 0
63                && offset_of!(Complex64, im) == size_of::<f64>()
64                && !needs_drop::<Complex64>()
65                && !needs_drop::<f64>()
66        }
67        _ => false,
68    };
69    if valid {
70        Ok(())
71    } else {
72        Err(representation_pair_error(
73            op,
74            from,
75            to,
76            "only the sealed Complex<f32><->f32 and Complex<f64><->f64 representations are supported",
77        ))
78    }
79}
80
81fn reinterpret_complex_to_real_layout(
82    shape: &[usize],
83    strides: &[isize],
84    offset: isize,
85    complex_buffer_len: usize,
86    op: &'static str,
87) -> crate::Result<TensorLayout<DynRank>> {
88    let real_buffer_len = complex_buffer_len
89        .checked_mul(2)
90        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
91    let mut real_shape = Vec::with_capacity(
92        shape
93            .len()
94            .checked_add(1)
95            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
96    );
97    real_shape.push(2);
98    real_shape.extend_from_slice(shape);
99    let real_strides = strides
100        .iter()
101        .map(|&stride| {
102            stride
103                .checked_mul(2)
104                .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
105        })
106        .collect::<crate::Result<Vec<_>>>()?;
107    let mut all_strides = Vec::with_capacity(
108        real_strides
109            .len()
110            .checked_add(1)
111            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
112    );
113    all_strides.push(1);
114    all_strides.extend(real_strides);
115    let real_offset = offset
116        .checked_mul(2)
117        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
118    TensorLayout::from_parts(
119        real_shape.into(),
120        all_strides.into(),
121        real_offset,
122        real_buffer_len,
123    )
124    .map_err(|err| tensor_layout_error(op, err))
125}
126
127fn reinterpret_real_to_complex_layout(
128    shape: &[usize],
129    strides: &[isize],
130    offset: isize,
131    real_buffer_len: usize,
132    op: &'static str,
133) -> crate::Result<TensorLayout<DynRank>> {
134    if shape.first().copied() != Some(2) {
135        return Err(crate::Error::invalid_argument(
136            op,
137            "shape",
138            "the leading extent must be 2 for a complex reinterpretation",
139        ));
140    }
141    if strides.first().copied() != Some(1) {
142        return Err(crate::Error::invalid_argument(
143            op,
144            "strides",
145            "the leading stride must be 1 for a complex reinterpretation",
146        ));
147    }
148    if offset % 2 != 0 {
149        return Err(crate::Error::invalid_argument(
150            op,
151            "offset",
152            "the offset must be divisible by 2 for a complex reinterpretation",
153        ));
154    }
155    let complex_strides = strides[1..]
156        .iter()
157        .map(|&stride| {
158            if stride % 2 != 0 {
159                return Err(crate::Error::invalid_argument(
160                    op,
161                    "strides",
162                    "all non-leading strides must be divisible by 2",
163                ));
164            }
165            Ok(stride / 2)
166        })
167        .collect::<crate::Result<Vec<_>>>()?;
168    let complex_buffer_len = real_buffer_len / 2;
169    TensorLayout::from_parts(
170        shape[1..].to_vec().into(),
171        complex_strides.into(),
172        offset / 2,
173        complex_buffer_len,
174    )
175    .map_err(|err| tensor_layout_error(op, err))
176}
177
178fn reinterpret_host_slice<'a, T: TensorScalar, U: TensorScalar>(
179    data: &'a [T],
180    op: &'static str,
181) -> crate::Result<&'a [U]> {
182    let byte_len = data
183        .len()
184        .checked_mul(size_of::<T>())
185        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
186    if !byte_len.is_multiple_of(size_of::<U>()) {
187        return Err(crate::Error::validation(
188            op,
189            ValidationError::ViewOutOfBounds,
190        ));
191    }
192    if data.as_ptr().align_offset(align_of::<U>()) != 0 {
193        return Err(crate::Error::invalid_argument(
194            op,
195            "alignment",
196            "the source allocation is not aligned for the target representation",
197        ));
198    }
199    // SAFETY: `validate_representation_pair` seals the only supported pairs;
200    // sizes, alignment, field order, and drop properties are checked before
201    // exposing the borrowed target slice.
202    Ok(unsafe { std::slice::from_raw_parts(data.as_ptr().cast::<U>(), byte_len / size_of::<U>()) })
203}
204
205fn reinterpret_host_slice_mut<'a, T: TensorScalar, U: TensorScalar>(
206    data: &'a mut [T],
207    op: &'static str,
208) -> crate::Result<&'a mut [U]> {
209    let byte_len = data
210        .len()
211        .checked_mul(size_of::<T>())
212        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
213    if !byte_len.is_multiple_of(size_of::<U>()) {
214        return Err(crate::Error::validation(
215            op,
216            ValidationError::ViewOutOfBounds,
217        ));
218    }
219    if data.as_mut_ptr().align_offset(align_of::<U>()) != 0 {
220        return Err(crate::Error::invalid_argument(
221            op,
222            "alignment",
223            "the source allocation is not aligned for the target representation",
224        ));
225    }
226    // SAFETY: the mutable source borrow is unique and the sealed pair has no
227    // padding or drop glue, so the target slice covers the same bytes exactly.
228    Ok(unsafe {
229        std::slice::from_raw_parts_mut(data.as_mut_ptr().cast::<U>(), byte_len / size_of::<U>())
230    })
231}
232
233/// Memory location for tensor storage.
234///
235/// # Examples
236///
237/// ```rust
238/// use tenferro_tensor::MemoryKind;
239///
240/// let kind = MemoryKind::UnpinnedHost;
241/// ```
242#[derive(Clone, Debug, PartialEq, Eq, Hash)]
243pub enum MemoryKind {
244    Device,
245    PinnedHost,
246    UnpinnedHost,
247    Managed,
248    Other(String),
249}
250
251/// Compute device family.
252///
253/// # Examples
254///
255/// ```rust
256/// use tenferro_tensor::DeviceKind;
257///
258/// let kind = DeviceKind::Cpu;
259/// ```
260#[derive(Clone, Debug, PartialEq, Eq, Hash)]
261pub enum DeviceKind {
262    Cpu,
263    Gpu(GpuBackendKind),
264    Other(String),
265}
266
267/// GPU backend family used by placement metadata.
268///
269/// # Examples
270///
271/// ```rust
272/// use tenferro_tensor::GpuBackendKind;
273///
274/// let kind = GpuBackendKind::Cuda;
275/// let webgpu = GpuBackendKind::WebGpu;
276/// assert_ne!(kind, webgpu);
277/// ```
278#[derive(Clone, Debug, PartialEq, Eq, Hash)]
279pub enum GpuBackendKind {
280    Cuda,
281    WebGpu,
282    Rocm,
283    Other(String),
284}
285
286/// Concrete compute device identifier.
287///
288/// # Examples
289///
290/// ```rust
291/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind};
292///
293/// let device = DeviceId {
294///     kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
295///     ordinal: 0,
296/// };
297/// ```
298#[derive(Clone, Debug, PartialEq, Eq, Hash)]
299pub struct DeviceId {
300    pub kind: DeviceKind,
301    pub ordinal: usize,
302}
303
304/// Caller-stable identity for a CPU execution domain.
305///
306/// Domain IDs are metadata supplied by the caller or execution coordinator;
307/// creating an ID does not allocate a process-global identity.
308///
309/// # Examples
310///
311/// ```rust
312/// use tenferro_tensor::CpuDomainId;
313///
314/// let domain = CpuDomainId::new(17);
315/// assert_eq!(domain.as_u64(), 17);
316/// ```
317#[repr(transparent)]
318#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
319pub struct CpuDomainId(u64);
320
321impl CpuDomainId {
322    /// Create a caller-stable CPU domain identity.
323    ///
324    /// # Examples
325    ///
326    /// ```rust
327    /// use tenferro_tensor::CpuDomainId;
328    ///
329    /// assert_eq!(CpuDomainId::new(3), CpuDomainId::new(3));
330    /// ```
331    pub const fn new(id: u64) -> Self {
332        Self(id)
333    }
334
335    /// Return the caller-supplied integer identity.
336    ///
337    /// # Examples
338    ///
339    /// ```rust
340    /// use tenferro_tensor::CpuDomainId;
341    ///
342    /// assert_eq!(CpuDomainId::new(9).as_u64(), 9);
343    /// ```
344    pub const fn as_u64(self) -> u64 {
345        self.0
346    }
347}
348
349/// Placement metadata for a tensor buffer.
350///
351/// # Examples
352///
353/// ```rust
354/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind, MemoryKind, Placement};
355///
356/// let placement = Placement {
357///     memory_kind: MemoryKind::Device,
358///     device: Some(DeviceId {
359///         kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
360///         ordinal: 0,
361///     }),
362///     cpu_affinity: None,
363/// };
364/// assert!(placement.cpu_affinity.is_none());
365/// ```
366#[derive(Clone, Debug, PartialEq, Eq, Hash)]
367pub struct Placement {
368    /// Storage memory class, independent of execution routing metadata.
369    pub memory_kind: MemoryKind,
370    /// Device that owns or addresses the storage, when applicable.
371    pub device: Option<DeviceId>,
372    /// Preferred or producing CPU execution domain for routing and locality.
373    ///
374    /// This tag is not proof of allocation ownership, page residency, NUMA
375    /// pinning, or worker-affinity enforcement. Backend allocation-domain
376    /// metadata remains attached to the buffer independently.
377    pub cpu_affinity: Option<CpuDomainId>,
378}
379
380impl Default for Placement {
381    fn default() -> Self {
382        default_placement()
383    }
384}
385
386/// Backend-owned buffer handle.
387///
388/// `BackendStorageHandle::new` creates an empty opaque handle. Use
389/// [`BackendStorageHandle::new_with_len`] when test or adapter code needs to model a
390/// non-empty backend allocation.
391///
392/// # Examples
393///
394/// ```rust
395/// use tenferro_tensor::BackendStorageHandle;
396///
397/// let handle = BackendStorageHandle::<f64>::new(7);
398/// ```
399pub struct BackendStorageHandle<T> {
400    id: u64,
401    len: usize,
402    allocation_domain: AllocationDomainId,
403    _phantom: std::marker::PhantomData<T>,
404}
405
406/// Identity of a backend-owned allocation domain.
407///
408/// Domains let cooperating backends accept shared allocations without treating
409/// another context's physically similar buffer as compatible.
410///
411/// # Examples
412///
413/// ```rust
414/// use tenferro_tensor::AllocationDomainId;
415///
416/// assert_ne!(AllocationDomainId::fresh(), AllocationDomainId::fresh());
417/// ```
418#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
419pub struct AllocationDomainId(u64);
420
421impl AllocationDomainId {
422    /// Create a process-unique allocation-domain identity.
423    ///
424    /// # Examples
425    ///
426    /// ```rust
427    /// use tenferro_tensor::AllocationDomainId;
428    ///
429    /// let domain = AllocationDomainId::fresh();
430    /// assert_eq!(domain, domain);
431    /// ```
432    pub fn fresh() -> Self {
433        static NEXT_DOMAIN_ID: AtomicU64 = AtomicU64::new(1);
434        Self(NEXT_DOMAIN_ID.fetch_add(1, Ordering::Relaxed))
435    }
436}
437
438/// Stable physical identity of one backend allocation.
439///
440/// # Examples
441///
442/// ```rust
443/// use tenferro_tensor::AllocationId;
444///
445/// assert_eq!(AllocationId::from_backend_id(7), AllocationId::from_backend_id(7));
446/// ```
447#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
448pub struct AllocationId(u64);
449
450impl AllocationId {
451    /// Wrap an allocation identity supplied by the owning backend.
452    ///
453    /// # Examples
454    ///
455    /// ```rust
456    /// use tenferro_tensor::AllocationId;
457    ///
458    /// let id = AllocationId::from_backend_id(3);
459    /// assert_eq!(id, AllocationId::from_backend_id(3));
460    /// ```
461    pub const fn from_backend_id(id: u64) -> Self {
462        Self(id)
463    }
464}
465
466/// Typed failure returned by guarded backend host access.
467///
468/// # Examples
469///
470/// ```rust
471/// use tenferro_tensor::HostAccessError;
472///
473/// let error = HostAccessError::Unsupported { backend: "opaque" };
474/// assert!(error.to_string().contains("opaque"));
475/// ```
476#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
477#[non_exhaustive]
478pub enum HostAccessError {
479    /// The backend does not expose guarded host access for this allocation.
480    #[error("backend `{backend}` does not support guarded host access")]
481    Unsupported { backend: &'static str },
482    /// The allocation belongs to another shared-allocation domain.
483    #[error("allocation belongs to domain {actual:?}, expected {expected:?}")]
484    ForeignDomain {
485        expected: AllocationDomainId,
486        actual: AllocationDomainId,
487    },
488    /// Another host mapping overlaps this allocation.
489    #[error("the allocation already has an active host mapping")]
490    OverlappingHostMapping,
491    /// GPU work currently owns or has reserved the allocation.
492    #[error("GPU access is in progress for the allocation")]
493    GpuAccessInProgress,
494    /// Host mapping is active while GPU access was requested.
495    #[error("the allocation is mapped for host access")]
496    MappedForHost,
497    /// The backend failed to complete the map operation.
498    #[error("backend host mapping failed: {message}")]
499    BackendFailure { message: String },
500    /// The source did not cover the full write-only mapping.
501    #[error("host write length mismatch: expected {expected}, got {actual}")]
502    LengthMismatch { expected: usize, actual: usize },
503}
504
505/// Metadata sealed at the tensor/root boundary before a provider launch.
506///
507/// Providers receive this request exactly once for a prepared access. Binding
508/// code consumes the resulting opaque state and does not receive replacement
509/// storage, ranges, or raw pointers.
510#[doc(hidden)]
511#[derive(Clone, Copy, Debug)]
512pub struct DeviceAccessRequest<'a> {
513    allocation_domain: AllocationDomainId,
514    allocation_id: AllocationId,
515    byte_len: usize,
516    element_size: usize,
517    shape: &'a [usize],
518    strides: &'a [isize],
519    offset: isize,
520}
521
522impl<'a> DeviceAccessRequest<'a> {
523    pub(crate) fn new(
524        allocation_domain: AllocationDomainId,
525        allocation_id: AllocationId,
526        byte_len: usize,
527        element_size: usize,
528        shape: &'a [usize],
529        strides: &'a [isize],
530        offset: isize,
531    ) -> Self {
532        Self {
533            allocation_domain,
534            allocation_id,
535            byte_len,
536            element_size,
537            shape,
538            strides,
539            offset,
540        }
541    }
542
543    pub fn allocation_domain(&self) -> AllocationDomainId {
544        self.allocation_domain
545    }
546
547    pub fn allocation_id(&self) -> AllocationId {
548        self.allocation_id
549    }
550
551    pub fn byte_len(&self) -> usize {
552        self.byte_len
553    }
554
555    pub fn element_size(&self) -> usize {
556        self.element_size
557    }
558
559    pub fn shape(&self) -> &[usize] {
560        self.shape
561    }
562
563    pub fn strides(&self) -> &[isize] {
564        self.strides
565    }
566
567    pub fn offset(&self) -> isize {
568        self.offset
569    }
570}
571
572/// Typed failure returned while preparing a provider-native device access.
573#[doc(hidden)]
574#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
575pub enum DeviceAccessError {
576    #[error("backend `{backend}` does not support prepared device access")]
577    Unsupported { backend: &'static str },
578    #[error("prepared device access request is invalid: {message}")]
579    InvalidRequest { message: String },
580    #[error("provider device preparation failed: {message}")]
581    ProviderFailure { message: String },
582}
583
584/// Opaque provider-prepared state retained for one device binding.
585#[doc(hidden)]
586pub trait PreparedDeviceAccess: Debug {
587    fn as_any(&self) -> &dyn Any;
588
589    fn into_any(self: Box<Self>) -> Box<dyn Any>;
590}
591
592trait ReadGuardAccess<T> {
593    fn as_slice(&self) -> &[T];
594}
595
596impl<T, G> ReadGuardAccess<T> for G
597where
598    G: Deref,
599    G::Target: AsRef<[T]>,
600{
601    fn as_slice(&self) -> &[T] {
602        self.deref().as_ref()
603    }
604}
605
606/// Closure-scoped read mapping of a backend allocation.
607///
608/// # Examples
609///
610/// ```rust
611/// use tenferro_tensor::HostReadGuard;
612///
613/// let guard = HostReadGuard::new(vec![1_u32, 2]);
614/// assert_eq!(&*guard, &[1, 2]);
615/// ```
616pub struct HostReadGuard<'a, T> {
617    access: Box<dyn ReadGuardAccess<T> + 'a>,
618}
619
620impl<T> Debug for HostReadGuard<'_, T> {
621    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622        formatter
623            .debug_struct("HostReadGuard")
624            .field("len", &self.len())
625            .finish_non_exhaustive()
626    }
627}
628
629impl<'a, T> HostReadGuard<'a, T> {
630    /// Wrap a backend-native read guard without exposing its concrete type.
631    ///
632    /// # Examples
633    ///
634    /// ```rust
635    /// use tenferro_tensor::HostReadGuard;
636    ///
637    /// let guard = HostReadGuard::new(vec![3_i32]);
638    /// assert_eq!(guard[0], 3);
639    /// ```
640    pub fn new<G>(guard: G) -> Self
641    where
642        G: Deref + 'a,
643        G::Target: AsRef<[T]>,
644        T: 'a,
645    {
646        Self {
647            access: Box::new(guard),
648        }
649    }
650}
651
652impl<T> Deref for HostReadGuard<'_, T> {
653    type Target = [T];
654
655    fn deref(&self) -> &Self::Target {
656        self.access.as_slice()
657    }
658}
659
660/// Backend-neutral owner of one shared tensor allocation domain.
661///
662/// CPU operation crates use this object-safe boundary to allocate results in
663/// the same managed domain without depending on a GPU provider crate.
664///
665/// # Examples
666///
667/// ```rust
668/// use std::sync::Arc;
669/// use tenferro_tensor::SharedTensorAllocationDomain;
670///
671/// let _domain: Option<Arc<dyn SharedTensorAllocationDomain>> = None;
672/// ```
673pub trait SharedTensorAllocationDomain: Debug + Send + Sync + 'static {
674    /// Return the stable identity shared by every allocation from this owner.
675    fn id(&self) -> AllocationDomainId;
676
677    /// Allocate an uninitialized compact column-major tensor in this domain.
678    ///
679    /// # Errors
680    ///
681    /// Returns a typed validation, unsupported-dtype, or backend allocation error.
682    fn allocate(&self, dtype: DType, shape: &[usize]) -> crate::Result<Tensor>;
683}
684
685type HostWriteCopy<'a, T> = dyn FnMut(&[T]) -> Result<(), HostAccessError> + 'a;
686
687/// Closure-scoped write-only mapping of a backend allocation.
688///
689/// # Examples
690///
691/// ```rust
692/// use tenferro_tensor::{HostAccessError, HostWriteGuard};
693///
694/// let mut written = Vec::new();
695/// {
696///     let mut guard = HostWriteGuard::new(2, |source: &[u32]| {
697///         written.extend_from_slice(source);
698///         Ok::<(), HostAccessError>(())
699///     });
700///     guard.copy_from_slice(&[4, 5]).unwrap();
701/// }
702/// assert_eq!(written, [4, 5]);
703/// ```
704pub struct HostWriteGuard<'a, T> {
705    len: usize,
706    copy: Box<HostWriteCopy<'a, T>>,
707}
708
709impl<T> Debug for HostWriteGuard<'_, T> {
710    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
711        formatter
712            .debug_struct("HostWriteGuard")
713            .field("len", &self.len)
714            .finish_non_exhaustive()
715    }
716}
717
718impl<'a, T> HostWriteGuard<'a, T> {
719    /// Wrap a backend-native write guard without exposing its concrete type.
720    ///
721    /// # Examples
722    ///
723    /// ```rust
724    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
725    ///
726    /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
727    /// assert!(guard.is_empty());
728    /// ```
729    ///
730    /// # Errors
731    ///
732    /// Construction is infallible. A callback failure such as
733    /// [`HostAccessError::BackendFailure`] is returned later by
734    /// [`Self::copy_from_slice`].
735    pub fn new<F>(len: usize, copy: F) -> Self
736    where
737        F: FnMut(&[T]) -> Result<(), HostAccessError> + 'a,
738        T: 'a,
739    {
740        Self {
741            len,
742            copy: Box::new(copy),
743        }
744    }
745
746    /// Number of elements covered by this write-only mapping.
747    ///
748    /// # Examples
749    ///
750    /// ```rust
751    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
752    ///
753    /// let guard = HostWriteGuard::new(2, |_source: &[f32]| Ok::<(), HostAccessError>(()));
754    /// assert_eq!(guard.len(), 2);
755    /// ```
756    pub fn len(&self) -> usize {
757        self.len
758    }
759
760    /// Returns `true` when this mapping covers no elements.
761    ///
762    /// # Examples
763    ///
764    /// ```rust
765    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
766    ///
767    /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
768    /// assert!(guard.is_empty());
769    /// ```
770    pub fn is_empty(&self) -> bool {
771        self.len == 0
772    }
773
774    /// Replace the full mapped allocation contents.
775    ///
776    /// # Examples
777    ///
778    /// ```rust
779    /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
780    ///
781    /// let mut guard = HostWriteGuard::new(1, |_source: &[f32]| Ok::<(), HostAccessError>(()));
782    /// guard.copy_from_slice(&[1.0]).unwrap();
783    /// ```
784    ///
785    /// # Errors
786    ///
787    /// Returns [`HostAccessError::LengthMismatch`] when `source` does not cover
788    /// the complete mapping, or the typed backend error returned by the owning
789    /// write guard.
790    pub fn copy_from_slice(&mut self, source: &[T]) -> Result<(), HostAccessError> {
791        if source.len() != self.len {
792            return Err(HostAccessError::LengthMismatch {
793                expected: self.len,
794                actual: source.len(),
795            });
796        }
797        (self.copy)(source)
798    }
799}
800
801impl<T> Debug for BackendStorageHandle<T> {
802    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
803        f.debug_struct("BackendStorageHandle")
804            .field("id", &self.id)
805            .finish()
806    }
807}
808
809impl<T> BackendStorageHandle<T> {
810    /// Create a new backend buffer handle.
811    ///
812    /// # Examples
813    ///
814    /// ```rust
815    /// use tenferro_tensor::BackendStorageHandle;
816    ///
817    /// let handle = BackendStorageHandle::<f64>::new(1);
818    /// assert_eq!(tenferro_tensor::BackendStorage::len(&handle), 0);
819    /// ```
820    pub fn new(id: u64) -> Self {
821        Self::new_with_len(id, 0)
822    }
823
824    /// Create a new backend buffer handle with a logical element count.
825    ///
826    /// # Examples
827    ///
828    /// ```rust
829    /// use tenferro_tensor::{BackendStorage, BackendStorageHandle};
830    ///
831    /// let handle = BackendStorageHandle::<f64>::new_with_len(1, 4);
832    /// assert_eq!(BackendStorage::len(&handle), 4);
833    /// ```
834    pub fn new_with_len(id: u64, len: usize) -> Self {
835        Self {
836            id,
837            len,
838            // Synthetic opaque handles are test/adapter allocations. Give
839            // each one an explicit domain so root import never fabricates
840            // identity from a missing provider field.
841            allocation_domain: AllocationDomainId::fresh(),
842            _phantom: std::marker::PhantomData,
843        }
844    }
845}
846
847/// Opaque backend-owned tensor buffer.
848///
849/// Tensor core never inspects backend-native allocations directly. Backend
850/// crates store their own concrete handle types behind this trait and
851/// downcast inside the owning backend only.
852///
853/// # Examples
854///
855/// ```rust
856/// use std::sync::Arc;
857/// use tenferro_tensor::{BackendStorage, BackendStorageHandle};
858///
859/// let buffer: Arc<dyn BackendStorage<f64>> = Arc::new(BackendStorageHandle::<f64>::new_with_len(7, 2));
860/// assert_eq!(buffer.backend_family(), "opaque");
861/// assert_eq!(buffer.len(), 2);
862/// ```
863pub trait BackendStorage<T>: Debug + Send + Sync + 'static {
864    /// Stable backend family identifier.
865    fn backend_family(&self) -> &'static str;
866
867    /// Number of logical elements in the backend allocation.
868    fn len(&self) -> usize;
869
870    /// Returns `true` when the backend allocation is empty.
871    fn is_empty(&self) -> bool {
872        self.len() == 0
873    }
874
875    /// Return the shared-allocation domain, when this buffer belongs to one.
876    fn allocation_domain(&self) -> Option<AllocationDomainId> {
877        None
878    }
879
880    /// Return the stable physical allocation identity, when available.
881    fn allocation_id(&self) -> Option<AllocationId> {
882        None
883    }
884
885    /// Prepare one provider-native device access from the root-owned buffer.
886    ///
887    /// The returned state is consumed by the provider binding path. Providers
888    /// that do not expose device launches return [`DeviceAccessError::Unsupported`].
889    #[doc(hidden)]
890    fn prepare_device_access(
891        &self,
892        _request: DeviceAccessRequest<'_>,
893    ) -> Result<Box<dyn PreparedDeviceAccess>, DeviceAccessError> {
894        Err(DeviceAccessError::Unsupported {
895            backend: self.backend_family(),
896        })
897    }
898
899    /// Map the allocation for closure-scoped host reads.
900    ///
901    /// # Errors
902    ///
903    /// The default returns [`HostAccessError::Unsupported`]. Host-visible
904    /// backends return typed overlap, pending-GPU, or backend mapping failures.
905    fn map_read(&self) -> Result<HostReadGuard<'_, T>, HostAccessError> {
906        Err(HostAccessError::Unsupported {
907            backend: self.backend_family(),
908        })
909    }
910
911    /// Map the allocation for closure-scoped host writes.
912    ///
913    /// The mutable receiver keeps write authority with the owning tensor or
914    /// provider object; borrowed views do not clone or share that authority.
915    ///
916    /// # Errors
917    ///
918    /// The default returns [`HostAccessError::Unsupported`]. Host-visible
919    /// backends return typed overlap, pending-GPU, or backend mapping failures.
920    fn map_write(&mut self) -> Result<HostWriteGuard<'_, T>, HostAccessError> {
921        Err(HostAccessError::Unsupported {
922            backend: self.backend_family(),
923        })
924    }
925
926    /// Type-erased access for the backend crate that owns the concrete handle.
927    fn as_any(&self) -> &dyn Any;
928}
929
930impl<T: Send + Sync + 'static> BackendStorage<T> for BackendStorageHandle<T> {
931    fn backend_family(&self) -> &'static str {
932        "opaque"
933    }
934
935    fn len(&self) -> usize {
936        self.len
937    }
938
939    fn allocation_domain(&self) -> Option<AllocationDomainId> {
940        Some(self.allocation_domain)
941    }
942
943    fn allocation_id(&self) -> Option<AllocationId> {
944        Some(AllocationId::from_backend_id(self.id))
945    }
946
947    fn as_any(&self) -> &dyn Any {
948        self
949    }
950}
951
952/// Tensor storage.
953///
954/// # Examples
955///
956/// ```rust
957/// use tenferro_tensor::StorageBuffer;
958///
959/// let host = StorageBuffer::Host(vec![1.0_f64, 2.0]);
960/// ```
961#[derive(Debug)]
962pub enum StorageBuffer<T> {
963    Host(Vec<T>),
964    Backend(Box<dyn BackendStorage<T>>),
965}
966
967impl<T: 'static> StorageBuffer<T> {
968    /// Return the physical element count in this buffer.
969    ///
970    /// # Examples
971    ///
972    /// ```rust
973    /// use tenferro_tensor::StorageBuffer;
974    ///
975    /// assert_eq!(StorageBuffer::Host(vec![1_i32, 2]).len(), 2);
976    /// ```
977    pub fn len(&self) -> usize {
978        match self {
979            Self::Host(data) => data.len(),
980            Self::Backend(buffer) => buffer.len(),
981        }
982    }
983
984    /// Return whether this buffer has no physical elements.
985    ///
986    /// # Examples
987    ///
988    /// ```rust
989    /// use tenferro_tensor::StorageBuffer;
990    ///
991    /// assert!(StorageBuffer::<i32>::Host(Vec::new()).is_empty());
992    /// ```
993    pub fn is_empty(&self) -> bool {
994        self.len() == 0
995    }
996
997    /// Return whether the storage is backend-owned rather than host-owned.
998    ///
999    /// # Examples
1000    ///
1001    /// ```rust
1002    /// use tenferro_tensor::StorageBuffer;
1003    ///
1004    /// assert!(!StorageBuffer::Host(vec![1_i32]).is_backend());
1005    /// ```
1006    pub fn is_backend(&self) -> bool {
1007        matches!(self, Self::Backend(_))
1008    }
1009}
1010
1011/// Runtime typed tensor storage with compile-time scalar type and rank metadata.
1012///
1013/// Owned tensors are compact column-major. Arbitrary strides and metadata-only
1014/// layout changes are represented by [`TypedTensorView`] and
1015/// [`TypedTensorViewMut`]. The buffer may be host-backed or backend-backed;
1016/// host-inspection methods do not download backend buffers implicitly.
1017///
1018/// # Examples
1019///
1020/// ```
1021/// use tenferro_tensor::{Rank, Tensor, TypedTensor};
1022///
1023/// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
1024/// assert_eq!(t.shape(), &[2, 2]);
1025///
1026/// let static_rank = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
1027/// assert_eq!(static_rank.rank(), 2);
1028///
1029/// let dynamic = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64; 4]).unwrap();
1030/// assert_eq!(dynamic.shape(), &[2, 2]);
1031/// ```
1032///
1033/// The `R` parameter stores rank metadata. It defaults to dynamic rank
1034/// (`DynRank`); use [`Rank<N>`](Rank) for compile-time rank validation.
1035/// The dtype-erased [`Tensor`] enum remains dynamic-rank.
1036/// The scalar-independent core of an owned tensor.
1037///
1038/// `TypedTensor<T, R>` is a zero-cost typed wrapper over this struct: the element type lives
1039/// in the descriptor as [`DType`], so the core holds every field whose representation depends
1040/// on the ownership and layout rather than on `T`. Keeping the core as one named type is what
1041/// lets a single erased payload reborrow any preset scalar from it.
1042#[derive(Debug)]
1043pub(crate) struct TensorCore<R: TensorRank = DynRank> {
1044    pub(crate) group: OwnedTensorGroup<R>,
1045    pub(crate) layout: TensorLayout<R>,
1046    pub(crate) placement: Placement,
1047}
1048
1049#[derive(Debug)]
1050#[repr(transparent)]
1051pub struct TypedTensor<T, R: TensorRank = DynRank> {
1052    pub(crate) core: TensorCore<R>,
1053    _scalar: PhantomData<T>,
1054}
1055
1056/// The sole owner handle for host tensors. The allocation group owns the
1057/// provider root; the descriptor slot carries only the logical view metadata.
1058pub(crate) struct OwnedTensorGroup<R: TensorRank> {
1059    group: AllocationGroup,
1060    slot: DescriptorSlot,
1061    allocation_index: AllocationSlot,
1062    // INVARIANT: this non-owning address points into the group root, whose host
1063    // vector cannot resize while the owning tensor is borrowed. It is stored as
1064    // `NonZeroUsize` rather than `NonNull<u8>`: the niche keeps the field at one
1065    // word, and unlike `NonNull` it preserves the `Send`/`Sync` auto traits the
1066    // typed tensor contract requires.
1067    host_ptr: Option<NonZeroUsize>,
1068    host_byte_len: usize,
1069    _rank: PhantomData<R>,
1070}
1071
1072impl<R: TensorRank> Debug for OwnedTensorGroup<R> {
1073    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1074        formatter
1075            .debug_struct("OwnedTensorGroup")
1076            .field("slot", &self.slot)
1077            .finish_non_exhaustive()
1078    }
1079}
1080
1081impl<R: TensorRank> OwnedTensorGroup<R> {
1082    /// The element dtype this owner's descriptor records.
1083    ///
1084    /// INVARIANT: an owned group is constructed together with its descriptor, so the slot
1085    /// always resolves; the erased `Tensor` reads its dtype from here rather than carrying a
1086    /// duplicate tag.
1087    fn dtype(&self) -> DType {
1088        self.group
1089            .descriptor_dtype(self.slot)
1090            .unwrap_or_else(|| unreachable!("an owned group always carries its descriptor"))
1091    }
1092
1093    fn from_host_vec<T: TensorScalar>(shape: R::Shape, data: Vec<T>) -> crate::Result<Self> {
1094        let (group, slot) = AllocationGroup::from_host_vec::<T, R>(shape, data)
1095            .map_err(|error| group_error("TypedTensor::from_host_vec", error))?;
1096        let allocation_index = group
1097            .allocation_index(slot)
1098            .map_err(|error| group_error("TypedTensor::from_host_vec", error))?;
1099        let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
1100        Ok(Self {
1101            group,
1102            slot,
1103            allocation_index,
1104            host_ptr,
1105            host_byte_len,
1106            _rank: PhantomData,
1107        })
1108    }
1109
1110    fn from_backend_buffer<T: TensorScalar + Send + Sync + 'static>(
1111        shape: R::Shape,
1112        buffer: StorageBuffer<T>,
1113        placement: Placement,
1114    ) -> crate::Result<Self> {
1115        let (mut group, slot) = AllocationGroup::from_backend_buffer::<T, R>(shape, buffer)
1116            .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
1117        group
1118            .set_descriptor_placement(slot, placement)
1119            .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
1120        let allocation_index = group
1121            .allocation_index(slot)
1122            .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
1123        let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
1124        Ok(Self {
1125            group,
1126            slot,
1127            allocation_index,
1128            host_ptr,
1129            host_byte_len,
1130            _rank: PhantomData,
1131        })
1132    }
1133
1134    fn view<T: TensorScalar>(&self) -> crate::Result<GroupReadView<'_, T, R>> {
1135        self.group
1136            .view(self.slot)
1137            .map_err(|error| group_error("TypedTensor::group_view", error))
1138    }
1139
1140    fn view_dyn<T: TensorScalar>(&self) -> crate::Result<GroupReadView<'_, T, DynRank>> {
1141        self.group
1142            .view(self.slot)
1143            .map_err(|error| group_error("TypedTensor::group_view", error))
1144    }
1145
1146    fn view_mut<T: TensorScalar>(&mut self) -> crate::Result<GroupWriteView<'_, T, R>> {
1147        self.group
1148            .view_mut(self.slot)
1149            .map_err(|error| group_error("TypedTensor::group_view_mut", error))
1150    }
1151
1152    fn view_mut_dyn<T: TensorScalar>(&mut self) -> crate::Result<GroupWriteView<'_, T, DynRank>> {
1153        self.group
1154            .view_mut(self.slot)
1155            .map_err(|error| group_error("TypedTensor::group_view_mut", error))
1156    }
1157
1158    fn prepare_device_read_for_layout<T: TensorScalar>(
1159        &self,
1160        layout: &TensorLayout<R>,
1161    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>> {
1162        self.group
1163            .prepare_device_read_for_layout::<T, R>(self.slot, layout)
1164            .map_err(|error| {
1165                crate::Error::runtime_state_source("TypedTensor::prepare_device_read", error)
1166            })
1167    }
1168
1169    fn prepare_device_write_for_layout<T: TensorScalar>(
1170        &mut self,
1171        layout: &TensorLayout<R>,
1172    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>> {
1173        self.group
1174            .prepare_device_write_for_layout::<T, R>(self.slot, layout)
1175            .map_err(|error| {
1176                crate::Error::runtime_state_source("TypedTensor::prepare_device_write", error)
1177            })
1178    }
1179
1180    fn host_buffer<T: 'static>(&self) -> Option<&StorageBuffer<T>> {
1181        self.group.host_buffer_at::<T>(self.allocation_index)
1182    }
1183
1184    fn host_slice<T: 'static>(&self) -> crate::Result<&[T]> {
1185        let Some(pointer) = self.host_ptr else {
1186            return Err(crate::Error::runtime_state(
1187                "TypedTensor::host_data",
1188                "backend storage cannot be borrowed as host data; download explicitly first",
1189            ));
1190        };
1191        let element_size = size_of::<T>();
1192        let Some(element_count) = self.host_byte_len.checked_div(element_size) else {
1193            return Err(crate::Error::runtime_state(
1194                "TypedTensor::host_data",
1195                "host allocation byte length is not aligned to the requested dtype",
1196            ));
1197        };
1198        // SAFETY: the pointer and byte length were captured from the unique
1199        // root's full host allocation; the root cannot resize while borrowed.
1200        Ok(unsafe { std::slice::from_raw_parts(pointer.get() as *const T, element_count) })
1201    }
1202
1203    fn host_slice_mut<T: 'static>(&mut self) -> crate::Result<&mut [T]> {
1204        let Some(pointer) = self.host_ptr else {
1205            return Err(crate::Error::runtime_state(
1206                "TypedTensor::host_data_mut",
1207                "backend storage cannot be borrowed as host data; download explicitly first",
1208            ));
1209        };
1210        let element_size = size_of::<T>();
1211        let Some(element_count) = self.host_byte_len.checked_div(element_size) else {
1212            return Err(crate::Error::runtime_state(
1213                "TypedTensor::host_data_mut",
1214                "host allocation byte length is not aligned to the requested dtype",
1215            ));
1216        };
1217        // SAFETY: the pointer and byte length were captured from the unique
1218        // root; this method has the only mutable borrow of that root.
1219        Ok(unsafe { std::slice::from_raw_parts_mut(pointer.get() as *mut T, element_count) })
1220    }
1221
1222    fn backend_buffer<T: 'static>(&self) -> Option<&StorageBuffer<T>> {
1223        self.group.backend_buffer::<T>(self.slot)
1224    }
1225
1226    fn backend_buffer_mut<T: 'static>(&mut self) -> Option<&mut StorageBuffer<T>> {
1227        self.group.backend_buffer_mut::<T>(self.slot)
1228    }
1229
1230    fn into_host_vec<T: TensorScalar>(self) -> crate::Result<Vec<T>> {
1231        self.group
1232            .into_host_vec::<T>(self.slot)
1233            .map_err(|error| crate::Error::runtime_state("TypedTensor::into_vec_col_major", error))
1234    }
1235
1236    fn into_parts(self) -> (AllocationGroup, DescriptorSlot) {
1237        (self.group, self.slot)
1238    }
1239
1240    #[allow(clippy::result_large_err)]
1241    fn reinterpret<T: TensorScalar, U: TensorScalar>(
1242        self,
1243        shape: Vec<usize>,
1244        strides: Vec<isize>,
1245        offset: isize,
1246    ) -> Result<OwnedTensorGroup<DynRank>, (Self, crate::Error)> {
1247        let OwnedTensorGroup {
1248            group,
1249            slot,
1250            allocation_index,
1251            host_ptr,
1252            host_byte_len,
1253            _rank: _,
1254        } = self;
1255        match group.reinterpret_descriptor::<T, U>(slot, shape, strides, offset) {
1256            Ok(group) => Ok(OwnedTensorGroup {
1257                group,
1258                slot,
1259                allocation_index,
1260                host_ptr,
1261                host_byte_len,
1262                _rank: PhantomData,
1263            }),
1264            Err((group, error)) => Err((
1265                OwnedTensorGroup {
1266                    group,
1267                    slot,
1268                    allocation_index,
1269                    host_ptr,
1270                    host_byte_len,
1271                    _rank: PhantomData,
1272                },
1273                group_error("TypedTensor::reinterpret", error),
1274            )),
1275        }
1276    }
1277}
1278
1279fn host_metadata<T: 'static>(
1280    group: &AllocationGroup,
1281    slot: DescriptorSlot,
1282) -> (Option<NonZeroUsize>, usize) {
1283    group
1284        .host_root_metadata::<T>(slot)
1285        .map_or((None, 0), |(pointer, byte_len)| {
1286            // INVARIANT: `host_root_metadata` reports the base address of the group's live
1287            // host allocation, which is non-null for any slice, empty included.
1288            (NonZeroUsize::new(pointer), byte_len)
1289        })
1290}
1291
1292fn group_error(op: &'static str, error: GroupError) -> crate::Error {
1293    crate::Error::runtime_state(op, error.to_string())
1294}
1295
1296/// Borrowed tensor buffer reference used by read-only typed views.
1297///
1298/// # Examples
1299///
1300/// ```rust
1301/// use tenferro_tensor::TensorStorageRef;
1302///
1303/// let data = [1_i32, 2];
1304/// let buffer = TensorStorageRef::Host(&data);
1305/// assert_eq!(buffer.len(), 2);
1306/// ```
1307#[derive(Debug)]
1308pub enum TensorStorageRef<'a, T> {
1309    Host(&'a [T]),
1310    Backend(&'a dyn BackendStorage<T>),
1311    #[doc(hidden)]
1312    Root(&'a dyn BackendAllocation),
1313}
1314
1315impl<T> Clone for TensorStorageRef<'_, T> {
1316    fn clone(&self) -> Self {
1317        match self {
1318            Self::Host(data) => Self::Host(data),
1319            Self::Backend(buffer) => Self::Backend(*buffer),
1320            Self::Root(allocation) => Self::Root(*allocation),
1321        }
1322    }
1323}
1324
1325impl<T: 'static> TensorStorageRef<'_, T> {
1326    /// Return the logical length of the backing allocation.
1327    ///
1328    /// # Examples
1329    ///
1330    /// ```rust
1331    /// use tenferro_tensor::TensorStorageRef;
1332    ///
1333    /// let data = [1_i32, 2, 3];
1334    /// assert_eq!(TensorStorageRef::Host(&data).len(), 3);
1335    /// ```
1336    pub fn len(&self) -> usize {
1337        match self {
1338            Self::Host(data) => data.len(),
1339            Self::Backend(buffer) => buffer.len(),
1340            Self::Root(allocation) => allocation
1341                .root_extent()
1342                .byte_len()
1343                .checked_div(std::mem::size_of::<T>())
1344                .unwrap_or(0),
1345        }
1346    }
1347
1348    /// Return whether the backing allocation is empty.
1349    ///
1350    /// # Examples
1351    ///
1352    /// ```rust
1353    /// use tenferro_tensor::TensorStorageRef;
1354    ///
1355    /// let data: [f64; 0] = [];
1356    /// assert!(TensorStorageRef::Host(&data).is_empty());
1357    /// ```
1358    pub fn is_empty(&self) -> bool {
1359        self.len() == 0
1360    }
1361}
1362
1363/// Borrowed tensor buffer reference used by mutable typed views.
1364///
1365/// Backend buffers can be represented for residency metadata, but this crate
1366/// does not expose host mutation for backend-native allocations.
1367///
1368/// # Examples
1369///
1370/// ```rust
1371/// use tenferro_tensor::TensorStorageRefMut;
1372///
1373/// let mut data = [1_i32, 2];
1374/// let buffer = TensorStorageRefMut::Host(&mut data);
1375/// assert_eq!(buffer.len(), 2);
1376/// ```
1377#[derive(Debug)]
1378pub enum TensorStorageRefMut<'a, T> {
1379    Host(&'a mut [T]),
1380    Backend(&'a mut dyn BackendStorage<T>),
1381}
1382
1383impl<T: 'static> TensorStorageRefMut<'_, T> {
1384    /// Return the logical length of the backing allocation.
1385    ///
1386    /// # Examples
1387    ///
1388    /// ```rust
1389    /// use tenferro_tensor::TensorStorageRefMut;
1390    ///
1391    /// let mut data = [1_i32, 2, 3];
1392    /// assert_eq!(TensorStorageRefMut::Host(&mut data).len(), 3);
1393    /// ```
1394    pub fn len(&self) -> usize {
1395        match self {
1396            Self::Host(data) => data.len(),
1397            Self::Backend(buffer) => buffer.len(),
1398        }
1399    }
1400
1401    /// Return whether the backing allocation is empty.
1402    ///
1403    /// # Examples
1404    ///
1405    /// ```rust
1406    /// use tenferro_tensor::TensorStorageRefMut;
1407    ///
1408    /// let mut data: [f64; 0] = [];
1409    /// assert!(TensorStorageRefMut::Host(&mut data).is_empty());
1410    /// ```
1411    pub fn is_empty(&self) -> bool {
1412        self.len() == 0
1413    }
1414}
1415
1416/// Read-only borrowed view of typed tensor storage with arbitrary strides.
1417///
1418/// `TypedTensorView` is the typed representation for layout-only tensor
1419/// transformations. It borrows an existing host or backend allocation and
1420/// carries a logical shape, strides, and an offset. Slicing, reshaping when
1421/// stride-compatible, and [`transpose_view`](TypedTensorView::transpose_view)
1422/// update only metadata and do not copy storage.
1423///
1424/// Materialize through [`TensorStructural::to_contiguous_read`](crate::TensorStructural::to_contiguous_read)
1425/// on the active backend session when a compact owned [`TypedTensor`] is
1426/// required. Use [`TypedTensorView::as_slice`] only when the current view is
1427/// contiguous in the requested layout.
1428///
1429/// # Examples
1430///
1431/// ```rust
1432/// use tenferro_tensor::{Rank, TypedTensorView};
1433///
1434/// let data = [1_i32, 2, 3, 4];
1435/// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
1436/// assert_eq!(view.get(&[1, 1]), Some(&4));
1437/// # Ok::<(), tenferro_tensor::Error>(())
1438/// ```
1439#[derive(Clone, Debug)]
1440pub struct TypedTensorView<'a, T, R: TensorRank = DynRank> {
1441    buffer: TensorStorageRef<'a, T>,
1442    root: Option<GroupReadView<'a, T, R>>,
1443    layout: TensorLayout<R>,
1444    placement: Placement,
1445}
1446
1447impl<'a, T: 'static> TypedTensorView<'a, T, DynRank> {
1448    /// Create a borrowed dynamic-rank view over compact column-major host data.
1449    ///
1450    /// # Examples
1451    ///
1452    /// ```rust
1453    /// use tenferro_tensor::TypedTensorView;
1454    ///
1455    /// let data = [1_i32, 2, 3, 4];
1456    /// let view = TypedTensorView::from_col_major(&[2, 2], &data)?;
1457    /// assert_eq!(view.strides(), &[1, 2]);
1458    /// # Ok::<(), tenferro_tensor::Error>(())
1459    /// ```
1460    ///
1461    /// # Errors
1462    ///
1463    /// Returns [`crate::Error::Validation`] with
1464    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when compact
1465    /// strides or reachable bounds overflow, or
1466    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
1467    /// requested shape reaches beyond `data`.
1468    pub fn from_col_major(shape: &[usize], data: &'a [T]) -> crate::Result<Self> {
1469        let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
1470            .map_err(|err| tensor_layout_error("TypedTensorView::from_col_major", err))?;
1471        Self::from_buffer_ref(
1472            shape_vec(layout.shape()),
1473            stride_vec(layout.strides()),
1474            layout.offset(),
1475            TensorStorageRef::Host(data),
1476            default_placement(),
1477            "TypedTensorView::from_col_major",
1478        )
1479    }
1480
1481    /// Create a borrowed host view from explicit layout metadata.
1482    ///
1483    /// # Examples
1484    ///
1485    /// ```rust
1486    /// use tenferro_tensor::TypedTensorView;
1487    ///
1488    /// let data = [1_i32, 2, 3];
1489    /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
1490    /// assert_eq!(view.get(&[2]), Some(&1));
1491    /// # Ok::<(), tenferro_tensor::Error>(())
1492    /// ```
1493    ///
1494    /// # Errors
1495    ///
1496    /// Returns [`crate::Error::Validation`] with
1497    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
1498    /// `strides` have different ranks,
1499    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
1500    /// reachable layout exceeds `data`, or
1501    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
1502    /// arithmetic overflows.
1503    pub fn from_slice(
1504        shape: impl AsRef<[usize]>,
1505        strides: impl AsRef<[isize]>,
1506        offset: isize,
1507        data: &'a [T],
1508    ) -> crate::Result<Self> {
1509        Self::from_buffer_ref(
1510            shape_vec(shape.as_ref()),
1511            stride_vec(strides.as_ref()),
1512            offset,
1513            TensorStorageRef::Host(data),
1514            default_placement(),
1515            "TypedTensorView::from_slice",
1516        )
1517    }
1518}
1519
1520impl<'a, T: 'static, R: TensorRank> TypedTensorView<'a, T, R> {
1521    /// Create a rank-generic borrowed host view from explicit layout metadata.
1522    ///
1523    /// # Examples
1524    ///
1525    /// ```rust
1526    /// use tenferro_tensor::{Rank, TypedTensorView};
1527    ///
1528    /// let data = [1_i32, 2, 3, 4];
1529    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
1530    /// assert_eq!(view.get(&[1, 1]), Some(&4));
1531    /// # Ok::<(), tenferro_tensor::Error>(())
1532    /// ```
1533    ///
1534    /// # Errors
1535    ///
1536    /// Returns [`crate::Error::Validation`] with
1537    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
1538    /// rank does not match `shape` or `strides`,
1539    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
1540    /// reachable layout exceeds `data`, or
1541    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
1542    /// arithmetic overflows.
1543    pub fn from_slice_ranked(
1544        shape: impl Into<R::Shape>,
1545        strides: impl Into<R::Strides>,
1546        offset: isize,
1547        data: &'a [T],
1548    ) -> crate::Result<Self> {
1549        Self::from_buffer_ref(
1550            shape,
1551            strides,
1552            offset,
1553            TensorStorageRef::Host(data),
1554            default_placement(),
1555            "TypedTensorView::from_slice_ranked",
1556        )
1557    }
1558
1559    fn from_buffer_ref(
1560        shape: impl Into<R::Shape>,
1561        strides: impl Into<R::Strides>,
1562        offset: isize,
1563        buffer: TensorStorageRef<'a, T>,
1564        placement: Placement,
1565        op: &'static str,
1566    ) -> crate::Result<Self> {
1567        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
1568            .map_err(|err| tensor_layout_error(op, err))?;
1569        Ok(Self {
1570            buffer,
1571            root: None,
1572            layout,
1573            placement,
1574        })
1575    }
1576
1577    /// Return the logical shape.
1578    ///
1579    /// # Examples
1580    ///
1581    /// ```rust
1582    /// use tenferro_tensor::TypedTensorView;
1583    ///
1584    /// let data = [0_i32; 2];
1585    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
1586    /// assert_eq!(view.shape(), &[2]);
1587    /// # Ok::<(), tenferro_tensor::Error>(())
1588    /// ```
1589    pub fn shape(&self) -> &[usize] {
1590        self.layout.shape()
1591    }
1592
1593    /// Return the logical rank carried by this view.
1594    pub fn rank(&self) -> usize {
1595        self.shape().len()
1596    }
1597
1598    /// Return strides in element units.
1599    ///
1600    /// # Examples
1601    ///
1602    /// ```rust
1603    /// use tenferro_tensor::TypedTensorView;
1604    ///
1605    /// let data = [0_i32; 2];
1606    /// let view = TypedTensorView::from_slice(vec![2], vec![-1], 1, &data)?;
1607    /// assert_eq!(view.strides(), &[-1]);
1608    /// # Ok::<(), tenferro_tensor::Error>(())
1609    /// ```
1610    pub fn strides(&self) -> &[isize] {
1611        self.layout.strides()
1612    }
1613
1614    /// Return the physical element offset.
1615    ///
1616    /// # Examples
1617    ///
1618    /// ```rust
1619    /// use tenferro_tensor::TypedTensorView;
1620    ///
1621    /// let data = [1_i32, 2];
1622    /// let view = TypedTensorView::from_slice(vec![1], vec![1], 1, &data)?;
1623    /// assert_eq!(view.offset(), 1);
1624    /// # Ok::<(), tenferro_tensor::Error>(())
1625    /// ```
1626    pub fn offset(&self) -> isize {
1627        self.layout.offset()
1628    }
1629
1630    /// Return the borrowed host storage backing this view.
1631    ///
1632    /// This exposes the entire backing host allocation, not just the logical
1633    /// slice covered by this view. Use [`TypedTensorView::as_slice`] when the
1634    /// caller needs the contiguous logical region instead.
1635    ///
1636    /// # Examples
1637    ///
1638    /// ```rust
1639    /// use tenferro_tensor::TypedTensorView;
1640    ///
1641    /// let data = [1_i32, 2];
1642    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
1643    /// assert_eq!(view.host_storage()?, &[1, 2]);
1644    /// # Ok::<(), tenferro_tensor::Error>(())
1645    /// ```
1646    ///
1647    /// # Errors
1648    ///
1649    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
1650    /// buffer; backend storage must be downloaded before host inspection.
1651    pub fn host_storage(&self) -> crate::Result<&'a [T]> {
1652        match &self.buffer {
1653            TensorStorageRef::Host(data) => Ok(data),
1654            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
1655                Err(crate::Error::runtime_state(
1656                    "TypedTensorView::host_storage",
1657                    "backend buffers cannot expose host storage; download explicitly first",
1658                ))
1659            }
1660        }
1661    }
1662
1663    /// Return the number of logical elements in this view.
1664    ///
1665    /// # Examples
1666    ///
1667    /// ```rust
1668    /// use tenferro_tensor::TypedTensorView;
1669    ///
1670    /// let data = [0_i32; 6];
1671    /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
1672    /// assert_eq!(view.n_elements(), 6);
1673    /// # Ok::<(), tenferro_tensor::Error>(())
1674    /// ```
1675    pub fn n_elements(&self) -> usize {
1676        // Invariant: public view constructors validate logical element count.
1677        match checked_view_element_count(self.shape(), "TypedTensorView::n_elements") {
1678            Ok(n) => n,
1679            Err(err) => {
1680                unreachable!("TypedTensorView layout shape is validated at construction: {err}")
1681            }
1682        }
1683    }
1684
1685    /// Return layout metadata for this view.
1686    ///
1687    /// # Examples
1688    ///
1689    /// ```rust
1690    /// use tenferro_tensor::TypedTensorView;
1691    ///
1692    /// let data = [1_i32, 2];
1693    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
1694    /// assert!(view.layout().is_compact_col_major().unwrap());
1695    /// # Ok::<(), tenferro_tensor::Error>(())
1696    /// ```
1697    pub fn layout(&self) -> &TensorLayout<R> {
1698        &self.layout
1699    }
1700
1701    /// Return placement metadata for this view.
1702    ///
1703    /// # Examples
1704    ///
1705    /// ```rust
1706    /// use tenferro_tensor::{MemoryKind, TypedTensorView};
1707    ///
1708    /// let data = [1_i32];
1709    /// let view = TypedTensorView::from_slice(vec![1], vec![1], 0, &data)?;
1710    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
1711    /// # Ok::<(), tenferro_tensor::Error>(())
1712    /// ```
1713    pub fn placement(&self) -> &Placement {
1714        &self.placement
1715    }
1716
1717    /// Return the backend allocation for backend integrations.
1718    #[doc(hidden)]
1719    pub fn backing_len(&self) -> usize {
1720        self.buffer.len()
1721    }
1722
1723    /// Return the backend allocation for backend integrations.
1724    #[doc(hidden)]
1725    pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>> {
1726        match &self.buffer {
1727            TensorStorageRef::Host(_) => None,
1728            TensorStorageRef::Backend(buffer) => Some(*buffer),
1729            TensorStorageRef::Root(_) => {
1730                self.root
1731                    .as_ref()?
1732                    .backend_buffer()
1733                    .and_then(|buffer| match buffer {
1734                        StorageBuffer::Host(_) => None,
1735                        StorageBuffer::Backend(buffer) => Some(buffer.as_ref()),
1736                    })
1737            }
1738        }
1739    }
1740
1741    /// Return the provider family for this view when it is backend-owned.
1742    #[doc(hidden)]
1743    pub fn backend_family(&self) -> Option<&'static str>
1744    where
1745        T: TensorScalar + 'static,
1746    {
1747        self.root
1748            .as_ref()
1749            .and_then(|root| {
1750                root.backend_allocation()
1751                    .map(|_| root.provider_kind().as_str())
1752            })
1753            .or_else(|| self.backend_buffer().map(|buffer| buffer.backend_family()))
1754    }
1755
1756    /// Return the shared allocation domain for this view when backend-owned.
1757    #[doc(hidden)]
1758    pub fn allocation_domain(&self) -> Option<AllocationDomainId>
1759    where
1760        T: TensorScalar + 'static,
1761    {
1762        self.root
1763            .as_ref()
1764            .and_then(|root| root.backend_identity().map(|(domain, _)| domain))
1765            .or_else(|| {
1766                self.backend_buffer()
1767                    .and_then(|buffer| buffer.allocation_domain())
1768            })
1769    }
1770
1771    /// Return the physical allocation identity for this view when backend-owned.
1772    #[doc(hidden)]
1773    pub fn allocation_id(&self) -> Option<AllocationId>
1774    where
1775        T: TensorScalar + 'static,
1776    {
1777        self.root
1778            .as_ref()
1779            .and_then(|root| root.backend_identity().map(|(_, id)| id))
1780            .or_else(|| {
1781                self.backend_buffer()
1782                    .and_then(|buffer| buffer.allocation_id())
1783            })
1784    }
1785
1786    /// Prepare this backend view for one provider-native read binding.
1787    #[doc(hidden)]
1788    pub fn prepare_device_read(
1789        &self,
1790        op: &'static str,
1791    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
1792    where
1793        T: TensorScalar + 'static,
1794    {
1795        if let Some(root) = &self.root {
1796            return root
1797                .prepare_device_read_for_layout(&self.layout)
1798                .map_err(|error| crate::Error::runtime_state_source(op, error));
1799        }
1800        let buffer = self
1801            .backend_buffer()
1802            .ok_or_else(|| crate::Error::runtime_state(op, "expected a backend tensor view"))?;
1803        prepare_backend_access(buffer, &self.layout, op)
1804    }
1805
1806    /// Compute the physical element offset for a logical index.
1807    ///
1808    /// # Examples
1809    ///
1810    /// ```rust
1811    /// use tenferro_tensor::TypedTensorView;
1812    ///
1813    /// let data = [1_i32, 2, 3];
1814    /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
1815    /// assert_eq!(view.linear_offset(&[2]), Some(0));
1816    /// # Ok::<(), tenferro_tensor::Error>(())
1817    /// ```
1818    pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
1819        checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
1820    }
1821
1822    /// Compute the physical element offset for a logical index, returning a typed error.
1823    ///
1824    /// # Examples
1825    ///
1826    /// ```rust
1827    /// use tenferro_tensor::TypedTensorView;
1828    ///
1829    /// let data = [1_i32, 2, 3];
1830    /// let view = TypedTensorView::from_slice([3], [-1], 2, &data)?;
1831    /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
1832    /// # Ok::<(), tenferro_tensor::Error>(())
1833    /// ```
1834    ///
1835    /// # Errors
1836    ///
1837    /// Returns [`crate::Error::Validation`] with
1838    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
1839    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
1840    /// when an index is outside its axis extent, or
1841    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
1842    /// arithmetic overflows.
1843    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
1844        checked_view_offset_result(
1845            self.shape(),
1846            self.strides(),
1847            self.offset(),
1848            indices,
1849            "TypedTensorView::layout_linear_offset",
1850        )
1851    }
1852
1853    /// Return whether this view is compact column-major.
1854    ///
1855    /// # Examples
1856    ///
1857    /// ```rust
1858    /// use tenferro_tensor::TypedTensorView;
1859    ///
1860    /// let data = [1_i32, 2];
1861    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
1862    /// assert!(view.is_col_major_contiguous()?);
1863    /// # Ok::<(), tenferro_tensor::Error>(())
1864    /// ```
1865    ///
1866    /// # Errors
1867    ///
1868    /// Returns [`crate::Error::Validation`] with
1869    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] if compactness
1870    /// arithmetic overflows.
1871    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
1872        self.layout
1873            .is_compact_col_major()
1874            .map_err(|err| tensor_layout_error("TypedTensorView::is_col_major_contiguous", err))
1875    }
1876
1877    /// Return a compact string summary of this view's layout metadata.
1878    ///
1879    /// # Examples
1880    ///
1881    /// ```rust
1882    /// use tenferro_tensor::TypedTensorView;
1883    ///
1884    /// let data = [1_i32, 2];
1885    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
1886    /// assert!(view.layout_summary().contains("shape=[2]"));
1887    /// # Ok::<(), tenferro_tensor::Error>(())
1888    /// ```
1889    pub fn layout_summary(&self) -> String {
1890        layout_summary(self.shape(), self.strides(), self.offset())
1891    }
1892
1893    /// Assert this view is compact column-major.
1894    ///
1895    /// # Examples
1896    ///
1897    /// ```rust
1898    /// use tenferro_tensor::TypedTensorView;
1899    ///
1900    /// let data = [1_i32, 2];
1901    /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
1902    /// view.assert_col_major_contiguous()?;
1903    /// # Ok::<(), tenferro_tensor::Error>(())
1904    /// ```
1905    ///
1906    /// # Errors
1907    ///
1908    /// Returns [`crate::Error::Validation`] with
1909    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
1910    /// compactness arithmetic overflows, or
1911    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
1912    /// view is not compact column-major.
1913    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
1914        assert_layout_col_major_contiguous(
1915            self.is_col_major_contiguous()?,
1916            self.shape(),
1917            self.strides(),
1918            self.offset(),
1919            "TypedTensorView::assert_col_major_contiguous",
1920        )
1921    }
1922
1923    /// Borrow one host element by logical index.
1924    ///
1925    /// Returns `None` for out-of-bounds indices and backend buffers.
1926    ///
1927    /// # Examples
1928    ///
1929    /// ```rust
1930    /// use tenferro_tensor::TypedTensorView;
1931    ///
1932    /// let data = [1_i32, 2];
1933    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
1934    /// assert_eq!(view.get(&[1]), Some(&2));
1935    /// # Ok::<(), tenferro_tensor::Error>(())
1936    /// ```
1937    pub fn get(&self, indices: &[usize]) -> Option<&T> {
1938        let offset = self.linear_offset(indices)?;
1939        match &self.buffer {
1940            TensorStorageRef::Host(data) => data.get(offset),
1941            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => None,
1942        }
1943    }
1944
1945    /// Borrow the contiguous host slice covered by this view.
1946    ///
1947    /// Returns an explicit error for backend buffers and for non-contiguous
1948    /// layouts. This method never downloads or materializes backend data.
1949    ///
1950    /// # Examples
1951    ///
1952    /// ```rust
1953    /// use tenferro_tensor::TypedTensorView;
1954    ///
1955    /// let data = [1_i32, 2, 3];
1956    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?;
1957    /// assert_eq!(view.as_slice()?, &[2, 3]);
1958    /// # Ok::<(), tenferro_tensor::Error>(())
1959    /// ```
1960    ///
1961    /// # Errors
1962    ///
1963    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
1964    /// buffer, [`tenferro_tensor_core::ValidationError::InvalidArgument`] when
1965    /// the layout is not slice-contiguous or has a negative offset, or
1966    /// [`crate::Error::Validation`] with
1967    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] or
1968    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
1969    /// requested host range is invalid.
1970    pub fn as_slice(&self) -> crate::Result<&'a [T]> {
1971        let data = match &self.buffer {
1972            TensorStorageRef::Host(data) => data,
1973            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
1974                return Err(crate::Error::runtime_state(
1975                    "TypedTensorView::as_slice",
1976                    "backend buffers cannot be inspected as host slices; download explicitly first",
1977                ))
1978            }
1979        };
1980        contiguous_layout_slice(self.layout(), data, "TypedTensorView::as_slice")
1981    }
1982
1983    /// Borrow the compact logical region through a scoped host mapping.
1984    ///
1985    /// Backend integrations must validate placement and allocation-domain ownership
1986    /// before mapping. The guard cannot escape the callback; no transfer is performed.
1987    ///
1988    /// # Errors
1989    ///
1990    /// Returns a host-access or provider-preparation error when mapping fails,
1991    /// or a validation/unsupported error for a non-contiguous or invalid layout.
1992    #[doc(hidden)]
1993    pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
1994    where
1995        T: TensorScalar + 'static,
1996    {
1997        const OP: &str = "TypedTensorView::with_host_read";
1998        if !self.is_col_major_contiguous()? {
1999            return Err(crate::Error::unsupported(
2000                OP,
2001                "host guard access requires a compact descriptor",
2002            ));
2003        }
2004        if let Some(buffer) = self.backend_buffer() {
2005            let guard = buffer
2006                .map_read()
2007                .map_err(|error| crate::Error::host_access(OP, error))?;
2008            return Ok(f(contiguous_layout_slice(self.layout(), &guard, OP)?));
2009        }
2010        if let Some(root) = &self.root {
2011            let prepared = root
2012                .prepare_host_read_for_layout(self.layout())
2013                .map_err(|error| crate::Error::runtime_state_source(OP, error))?;
2014            let slice = prepared.as_slice().ok_or_else(|| {
2015                crate::Error::unsupported(OP, "host guard access requires a compact descriptor")
2016            })?;
2017            return Ok(f(slice));
2018        }
2019        Ok(f(self.as_slice()?))
2020    }
2021
2022    /// Explicitly duplicate a compact host view into a new owner.
2023    ///
2024    /// Backend views require an explicit provider canonicalization or download
2025    /// boundary; this method never transfers or materializes them implicitly.
2026    ///
2027    /// # Examples
2028    ///
2029    /// ```
2030    /// use tenferro_tensor::TypedTensorView;
2031    ///
2032    /// let data = [1_i32, 2];
2033    /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
2034    /// let copy = view.duplicate()?;
2035    /// assert_eq!(copy.as_slice()?, &[1, 2]);
2036    /// # Ok::<(), tenferro_tensor::Error>(())
2037    /// ```
2038    ///
2039    /// # Errors
2040    ///
2041    /// Returns [`crate::Error::HostAccess`] or
2042    /// [`ValidationError::NonContiguousViewAsSlice`] when the view is backend
2043    /// owned or not contiguous, and [`ValidationError::InvalidArgument`] when
2044    /// the static-rank shape cannot be reconstructed.
2045    pub fn duplicate(&self) -> crate::Result<TypedTensor<T, R>>
2046    where
2047        T: TensorScalar,
2048    {
2049        let data = self.as_slice()?.to_vec();
2050        let shape = R::shape_from_vec(shape_vec(self.shape()))
2051            .map_err(|err| tensor_layout_error("TypedTensorView::duplicate", err))?;
2052        let mut tensor = TypedTensor::from_vec_col_major(shape, data)?;
2053        tensor.core.placement = self.placement.clone();
2054        Ok(tensor)
2055    }
2056
2057    /// Return a metadata-only axis permutation.
2058    ///
2059    /// # Examples
2060    ///
2061    /// ```rust
2062    /// use tenferro_tensor::{Rank, TypedTensorView};
2063    ///
2064    /// let data = [1_i32, 2, 3, 4, 5, 6];
2065    /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 3], [1, 2], 0, &data)?;
2066    /// let transposed = view.transpose_view([1, 0])?;
2067    /// assert_eq!(transposed.shape(), &[3, 2]);
2068    /// # Ok::<(), tenferro_tensor::Error>(())
2069    /// ```
2070    /// # Errors
2071    ///
2072    /// Returns [`crate::Error::Validation`] with
2073    /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
2074    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
2075    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
2076    /// not a valid permutation of the view rank.
2077    pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
2078        let layout = self
2079            .layout
2080            .transpose_view(axes)
2081            .map_err(|err| tensor_layout_error("TypedTensorView::transpose_view", err))?;
2082        Ok(Self {
2083            buffer: self.buffer.clone(),
2084            root: self.root.clone(),
2085            layout,
2086            placement: self.placement.clone(),
2087        })
2088    }
2089
2090    /// Return a metadata-only slice using one [`StridedSliceSpec`] per axis.
2091    ///
2092    /// # Examples
2093    ///
2094    /// ```rust
2095    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
2096    ///
2097    /// let data = [1_i32, 2, 3];
2098    /// let view = TypedTensorView::from_slice(vec![3], vec![1], 0, &data)?;
2099    /// let reversed = view.try_slice(&[StridedSliceSpec::reverse()])?;
2100    /// assert_eq!(reversed.get(&[0]), Some(&3));
2101    /// # Ok::<(), tenferro_tensor::Error>(())
2102    /// ```
2103    /// # Errors
2104    ///
2105    /// Returns [`crate::Error::Validation`] with
2106    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
2107    /// count differs from the view rank,
2108    /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
2109    /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
2110    /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
2111    /// for slice arithmetic overflow, or
2112    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2113    /// resulting layout exceeds the backing buffer.
2114    pub fn try_slice(&self, slices: &[StridedSliceSpec]) -> crate::Result<Self> {
2115        let specs = core_slice_specs(slices, self.shape(), "TypedTensorView::try_slice")?;
2116        let layout = self
2117            .layout
2118            .slice_view(specs, self.buffer.len())
2119            .map_err(|err| tensor_layout_error("TypedTensorView::try_slice", err))?;
2120        Ok(Self {
2121            buffer: self.buffer.clone(),
2122            root: self.root.clone(),
2123            layout,
2124            placement: self.placement.clone(),
2125        })
2126    }
2127
2128    /// Return a metadata-only slice along one axis.
2129    ///
2130    /// # Examples
2131    ///
2132    /// ```rust
2133    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
2134    ///
2135    /// let data = [1_i32, 2, 3, 4];
2136    /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
2137    /// assert_eq!(view.try_slice_axis(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
2138    /// # Ok::<(), tenferro_tensor::Error>(())
2139    /// ```
2140    /// # Errors
2141    ///
2142    /// Returns [`crate::Error::Validation`] with
2143    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
2144    /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
2145    /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
2146    /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
2147    /// for slice arithmetic overflow, or
2148    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2149    /// resulting layout exceeds the backing buffer.
2150    pub fn try_slice_axis(&self, axis: usize, slice: StridedSliceSpec) -> crate::Result<Self> {
2151        let slices = slice_axis_specs(
2152            self.shape().len(),
2153            axis,
2154            slice,
2155            "TypedTensorView::try_slice_axis",
2156        )?;
2157        self.try_slice(&slices)
2158    }
2159
2160    /// Return a metadata-only dynamic-rank reshape for contiguous column-major views.
2161    ///
2162    /// # Examples
2163    ///
2164    /// ```rust
2165    /// use tenferro_tensor::TypedTensorView;
2166    ///
2167    /// let data = [1_i32, 2, 3, 4];
2168    /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
2169    /// assert_eq!(view.try_reshape(&[4])?.shape(), &[4]);
2170    /// # Ok::<(), tenferro_tensor::Error>(())
2171    /// ```
2172    /// # Errors
2173    ///
2174    /// Returns [`crate::Error::Validation`] with
2175    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
2176    /// the source is not compact column-major,
2177    /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
2178    /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
2179    /// records the counts) when element counts differ,
2180    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
2181    /// arithmetic overflow, or
2182    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2183    /// reshaped view exceeds the backing buffer.
2184    pub fn try_reshape(&self, shape: &[usize]) -> crate::Result<TypedTensorView<'a, T, DynRank>> {
2185        let layout = reshape_layout_dyn(
2186            &self.layout,
2187            shape,
2188            self.buffer.len(),
2189            "TypedTensorView::try_reshape",
2190        )?;
2191        Ok(TypedTensorView {
2192            buffer: self.buffer.clone(),
2193            root: self.root.as_ref().map(GroupReadView::clone_dyn),
2194            layout,
2195            placement: self.placement.clone(),
2196        })
2197    }
2198}
2199
2200impl<'a, R: TensorRank> TypedTensorView<'a, Complex32, R> {
2201    /// Borrow this complex view as an interleaved real view without copying.
2202    ///
2203    /// The result has dynamic rank because reinterpretation prepends the
2204    /// component axis `[2, ...]`. Only `Complex32 <-> f32` is sealed in this
2205    /// API; this is representation reinterpretation, not numeric conversion.
2206    /// # Errors
2207    ///
2208    /// Returns an error when the view layout is not a valid sealed
2209    /// representation or when backend reinterpretation is unsupported.
2210    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'a, f32, DynRank>> {
2211        let op = "TypedTensorView::as_real_view";
2212        validate_representation_pair(op, DType::C32, DType::F32)?;
2213        let layout = reinterpret_complex_to_real_layout(
2214            self.shape(),
2215            self.strides(),
2216            self.offset(),
2217            self.buffer.len(),
2218            op,
2219        )?;
2220        let buffer = match &self.buffer {
2221            TensorStorageRef::Host(data) => {
2222                TensorStorageRef::Host(reinterpret_host_slice::<Complex32, f32>(data, op)?)
2223            }
2224            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
2225                return Err(crate::Error::unsupported(
2226                    op,
2227                    "backend representation reinterpretation is enabled by the provider phases",
2228                ))
2229            }
2230        };
2231        Ok(TypedTensorView {
2232            buffer,
2233            root: None,
2234            layout,
2235            placement: self.placement.clone(),
2236        })
2237    }
2238}
2239
2240impl<'a, R: TensorRank> TypedTensorView<'a, Complex64, R> {
2241    /// Borrow this complex view as an interleaved real view without copying.
2242    ///
2243    /// The result has dynamic rank because reinterpretation prepends the
2244    /// component axis `[2, ...]`. Only `Complex64 <-> f64` is sealed in this
2245    /// API; this is representation reinterpretation, not numeric conversion.
2246    /// # Errors
2247    ///
2248    /// Returns an error when the view layout is not a valid sealed
2249    /// representation or when backend reinterpretation is unsupported.
2250    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'a, f64, DynRank>> {
2251        let op = "TypedTensorView::as_real_view";
2252        validate_representation_pair(op, DType::C64, DType::F64)?;
2253        let layout = reinterpret_complex_to_real_layout(
2254            self.shape(),
2255            self.strides(),
2256            self.offset(),
2257            self.buffer.len(),
2258            op,
2259        )?;
2260        let buffer = match &self.buffer {
2261            TensorStorageRef::Host(data) => {
2262                TensorStorageRef::Host(reinterpret_host_slice::<Complex64, f64>(data, op)?)
2263            }
2264            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
2265                return Err(crate::Error::unsupported(
2266                    op,
2267                    "backend representation reinterpretation is enabled by the provider phases",
2268                ))
2269            }
2270        };
2271        Ok(TypedTensorView {
2272            buffer,
2273            root: None,
2274            layout,
2275            placement: self.placement.clone(),
2276        })
2277    }
2278}
2279
2280impl<'a, R: TensorRank> TypedTensorView<'a, f32, R> {
2281    /// Borrow this interleaved real view as a complex view without copying.
2282    ///
2283    /// The source must have a leading extent and stride of `2` and `1`, and
2284    /// every remaining stride plus the offset must be divisible by `2`.
2285    /// # Errors
2286    ///
2287    /// Returns an error when the view layout is not a valid sealed
2288    /// representation or when backend reinterpretation is unsupported.
2289    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'a, Complex32, DynRank>> {
2290        let op = "TypedTensorView::as_complex_view";
2291        validate_representation_pair(op, DType::F32, DType::C32)?;
2292        let layout = reinterpret_real_to_complex_layout(
2293            self.shape(),
2294            self.strides(),
2295            self.offset(),
2296            self.buffer.len(),
2297            op,
2298        )?;
2299        let buffer = match &self.buffer {
2300            TensorStorageRef::Host(data) => {
2301                TensorStorageRef::Host(reinterpret_host_slice::<f32, Complex32>(data, op)?)
2302            }
2303            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
2304                return Err(crate::Error::unsupported(
2305                    op,
2306                    "backend representation reinterpretation is enabled by the provider phases",
2307                ))
2308            }
2309        };
2310        Ok(TypedTensorView {
2311            buffer,
2312            root: None,
2313            layout,
2314            placement: self.placement.clone(),
2315        })
2316    }
2317}
2318
2319impl<'a, R: TensorRank> TypedTensorView<'a, f64, R> {
2320    /// Borrow this interleaved real view as a complex view without copying.
2321    ///
2322    /// The source must have a leading extent and stride of `2` and `1`, and
2323    /// every remaining stride plus the offset must be divisible by `2`.
2324    /// # Errors
2325    ///
2326    /// Returns an error when the view layout is not a valid sealed
2327    /// representation or when backend reinterpretation is unsupported.
2328    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'a, Complex64, DynRank>> {
2329        let op = "TypedTensorView::as_complex_view";
2330        validate_representation_pair(op, DType::F64, DType::C64)?;
2331        let layout = reinterpret_real_to_complex_layout(
2332            self.shape(),
2333            self.strides(),
2334            self.offset(),
2335            self.buffer.len(),
2336            op,
2337        )?;
2338        let buffer = match &self.buffer {
2339            TensorStorageRef::Host(data) => {
2340                TensorStorageRef::Host(reinterpret_host_slice::<f64, Complex64>(data, op)?)
2341            }
2342            TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
2343                return Err(crate::Error::unsupported(
2344                    op,
2345                    "backend representation reinterpretation is enabled by the provider phases",
2346                ))
2347            }
2348        };
2349        Ok(TypedTensorView {
2350            buffer,
2351            root: None,
2352            layout,
2353            placement: self.placement.clone(),
2354        })
2355    }
2356}
2357
2358/// Mutable borrowed view of typed tensor storage with arbitrary strides.
2359///
2360/// # Examples
2361///
2362/// ```rust
2363/// use tenferro_tensor::TypedTensorViewMut;
2364///
2365/// let mut data = [1_i32, 2, 3];
2366/// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
2367/// *view.get_mut(&[2]).unwrap() = 10;
2368/// assert_eq!(view.as_read_only().get(&[2]), Some(&10));
2369/// # Ok::<(), tenferro_tensor::Error>(())
2370/// ```
2371#[derive(Debug)]
2372pub struct TypedTensorViewMut<'a, T, R: TensorRank = DynRank> {
2373    buffer: TensorStorageRefMut<'a, T>,
2374    root: Option<GroupWriteView<'a, T, R>>,
2375    layout: TensorLayout<R>,
2376    placement: Placement,
2377}
2378
2379/// Pair of mutable tensor views returned by disjoint multi-slice operations.
2380///
2381/// # Examples
2382///
2383/// ```rust
2384/// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut, TypedTensorViewMutSplit};
2385///
2386/// let mut data = [1_i32, 2, 3, 4];
2387/// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
2388/// let pair: TypedTensorViewMutSplit<'_, i32> = view
2389///     .try_multi_slice_mut(
2390///         &[StridedSliceSpec::new(0, Some(2), 1)],
2391///         &[StridedSliceSpec::new(2, Some(4), 1)],
2392///     )
2393///     ?
2394///     .unwrap();
2395/// assert_eq!(pair.0.shape(), &[2]);
2396/// assert_eq!(pair.1.shape(), &[2]);
2397/// # Ok::<(), tenferro_tensor::Error>(())
2398/// ```
2399pub type TypedTensorViewMutSplit<'a, T, R = DynRank> =
2400    (TypedTensorViewMut<'a, T, R>, TypedTensorViewMut<'a, T, R>);
2401
2402impl<'a, T: 'static> TypedTensorViewMut<'a, T, DynRank> {
2403    /// Create a mutable dynamic-rank view over compact column-major host data.
2404    ///
2405    /// # Examples
2406    ///
2407    /// ```rust
2408    /// use tenferro_tensor::TypedTensorViewMut;
2409    ///
2410    /// let mut data = [1_i32, 2, 3, 4];
2411    /// let view = TypedTensorViewMut::from_col_major(&[2, 2], &mut data)?;
2412    /// assert_eq!(view.strides(), &[1, 2]);
2413    /// # Ok::<(), tenferro_tensor::Error>(())
2414    /// ```
2415    /// # Errors
2416    ///
2417    /// Returns [`crate::Error::Validation`] with
2418    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
2419    /// shape or offset arithmetic overflow, or
2420    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2421    /// compact shape reaches beyond `data`.
2422    pub fn from_col_major(shape: &[usize], data: &'a mut [T]) -> crate::Result<Self> {
2423        let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
2424            .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_col_major", err))?;
2425        Self::from_buffer_ref_mut(
2426            shape_vec(layout.shape()),
2427            stride_vec(layout.strides()),
2428            layout.offset(),
2429            TensorStorageRefMut::Host(data),
2430            default_placement(),
2431            "TypedTensorViewMut::from_col_major",
2432        )
2433    }
2434
2435    /// Create a mutable host view from explicit layout metadata.
2436    ///
2437    /// Layouts where distinct logical elements can alias the same physical
2438    /// element are rejected.
2439    ///
2440    /// # Examples
2441    ///
2442    /// ```rust
2443    /// use tenferro_tensor::TypedTensorViewMut;
2444    ///
2445    /// let mut data = [1_i32, 2];
2446    /// assert!(TypedTensorViewMut::from_slice(vec![2], vec![0], 0, &mut data).is_err());
2447    /// ```
2448    /// # Errors
2449    ///
2450    /// Returns [`crate::Error::Validation`] with
2451    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
2452    /// `strides` have different ranks,
2453    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2454    /// layout reaches beyond `data`,
2455    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
2456    /// logical elements alias, or
2457    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
2458    /// arithmetic overflow.
2459    pub fn from_slice(
2460        shape: impl AsRef<[usize]>,
2461        strides: impl AsRef<[isize]>,
2462        offset: isize,
2463        data: &'a mut [T],
2464    ) -> crate::Result<Self> {
2465        Self::from_buffer_ref_mut(
2466            shape_vec(shape.as_ref()),
2467            stride_vec(strides.as_ref()),
2468            offset,
2469            TensorStorageRefMut::Host(data),
2470            default_placement(),
2471            "TypedTensorViewMut::from_slice",
2472        )
2473    }
2474}
2475
2476impl<'a, T: 'static, R: TensorRank> TypedTensorViewMut<'a, T, R> {
2477    /// Create a rank-generic mutable host view from explicit layout metadata.
2478    ///
2479    /// # Examples
2480    ///
2481    /// ```rust
2482    /// use tenferro_tensor::{Rank, TypedTensorViewMut};
2483    ///
2484    /// let mut data = [1_i32, 2, 3, 4];
2485    /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
2486    /// assert_eq!(view.shape(), &[2, 2]);
2487    /// # Ok::<(), tenferro_tensor::Error>(())
2488    /// ```
2489    /// # Errors
2490    ///
2491    /// Returns [`crate::Error::Validation`] with
2492    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
2493    /// rank does not match `shape` or `strides`,
2494    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2495    /// layout reaches beyond `data`,
2496    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
2497    /// logical elements alias, or
2498    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
2499    /// arithmetic overflow.
2500    pub fn from_slice_ranked(
2501        shape: impl Into<R::Shape>,
2502        strides: impl Into<R::Strides>,
2503        offset: isize,
2504        data: &'a mut [T],
2505    ) -> crate::Result<Self> {
2506        Self::from_buffer_ref_mut(
2507            shape,
2508            strides,
2509            offset,
2510            TensorStorageRefMut::Host(data),
2511            default_placement(),
2512            "TypedTensorViewMut::from_slice_ranked",
2513        )
2514    }
2515
2516    fn from_buffer_ref_mut(
2517        shape: impl Into<R::Shape>,
2518        strides: impl Into<R::Strides>,
2519        offset: isize,
2520        buffer: TensorStorageRefMut<'a, T>,
2521        placement: Placement,
2522        op: &'static str,
2523    ) -> crate::Result<Self> {
2524        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
2525            .map_err(|err| tensor_layout_error(op, err))?;
2526        layout
2527            .validate_mutable_no_overlap()
2528            .map_err(|err| tensor_layout_error(op, err))?;
2529        Ok(Self {
2530            buffer,
2531            root: None,
2532            layout,
2533            placement,
2534        })
2535    }
2536
2537    /// Return the logical shape.
2538    ///
2539    /// # Examples
2540    ///
2541    /// ```rust
2542    /// use tenferro_tensor::TypedTensorViewMut;
2543    ///
2544    /// let mut data = [0_i32; 2];
2545    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
2546    /// assert_eq!(view.shape(), &[2]);
2547    /// # Ok::<(), tenferro_tensor::Error>(())
2548    /// ```
2549    pub fn shape(&self) -> &[usize] {
2550        self.layout.shape()
2551    }
2552
2553    /// Return the logical rank carried by this mutable view.
2554    pub fn rank(&self) -> usize {
2555        self.shape().len()
2556    }
2557
2558    /// Return strides in element units.
2559    ///
2560    /// # Examples
2561    ///
2562    /// ```rust
2563    /// use tenferro_tensor::TypedTensorViewMut;
2564    ///
2565    /// let mut data = [0_i32; 2];
2566    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![-1], 1, &mut data)?;
2567    /// assert_eq!(view.strides(), &[-1]);
2568    /// # Ok::<(), tenferro_tensor::Error>(())
2569    /// ```
2570    pub fn strides(&self) -> &[isize] {
2571        self.layout.strides()
2572    }
2573
2574    /// Return the physical element offset.
2575    ///
2576    /// # Examples
2577    ///
2578    /// ```rust
2579    /// use tenferro_tensor::TypedTensorViewMut;
2580    ///
2581    /// let mut data = [1_i32, 2];
2582    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 1, &mut data)?;
2583    /// assert_eq!(view.offset(), 1);
2584    /// # Ok::<(), tenferro_tensor::Error>(())
2585    /// ```
2586    pub fn offset(&self) -> isize {
2587        self.layout.offset()
2588    }
2589
2590    /// Return the borrowed host storage backing this view.
2591    ///
2592    /// This exposes the entire backing host allocation, not just the logical
2593    /// slice covered by this view. Use [`TypedTensorViewMut::as_read_only`]
2594    /// with [`TypedTensorView::as_slice`] when the caller needs the contiguous
2595    /// logical region instead.
2596    ///
2597    /// # Examples
2598    ///
2599    /// ```rust
2600    /// use tenferro_tensor::TypedTensorViewMut;
2601    ///
2602    /// let mut data = [1_i32, 2];
2603    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
2604    /// assert_eq!(view.host_storage()?, &[1, 2]);
2605    /// # Ok::<(), tenferro_tensor::Error>(())
2606    /// ```
2607    /// # Errors
2608    ///
2609    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
2610    /// buffer; backend storage must be downloaded before host inspection.
2611    pub fn host_storage(&self) -> crate::Result<&[T]> {
2612        match &self.buffer {
2613            TensorStorageRefMut::Host(data) => Ok(data),
2614            TensorStorageRefMut::Backend(_) => Err(crate::Error::runtime_state(
2615                "TypedTensorViewMut::host_storage",
2616                "backend buffers cannot expose host storage; download explicitly first",
2617            )),
2618        }
2619    }
2620
2621    /// Mutably borrow the host storage backing this view.
2622    ///
2623    /// This exposes the entire backing host allocation, not just the logical
2624    /// slice covered by this view. Prefer scalar element accessors when mutating
2625    /// a logical region; tensor-sized copies belong to an active backend.
2626    ///
2627    /// # Examples
2628    ///
2629    /// ```rust
2630    /// use tenferro_tensor::TypedTensorViewMut;
2631    ///
2632    /// let mut data = [1_i32, 2];
2633    /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
2634    /// view.host_storage_mut()?[0] = 3;
2635    /// assert_eq!(view.get(&[0]), Some(&3));
2636    /// # Ok::<(), tenferro_tensor::Error>(())
2637    /// ```
2638    /// # Errors
2639    ///
2640    /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
2641    /// buffer; backend storage must be downloaded before host inspection.
2642    pub fn host_storage_mut(&mut self) -> crate::Result<&mut [T]> {
2643        match &mut self.buffer {
2644            TensorStorageRefMut::Host(data) => Ok(data),
2645            TensorStorageRefMut::Backend(_) => Err(crate::Error::runtime_state(
2646                "TypedTensorViewMut::host_storage_mut",
2647                "backend buffers cannot expose mutable host storage; download explicitly first",
2648            )),
2649        }
2650    }
2651
2652    /// Return the number of logical elements in this view.
2653    ///
2654    /// # Examples
2655    ///
2656    /// ```rust
2657    /// use tenferro_tensor::TypedTensorViewMut;
2658    ///
2659    /// let mut data = [0_i32; 6];
2660    /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
2661    /// assert_eq!(view.n_elements(), 6);
2662    /// # Ok::<(), tenferro_tensor::Error>(())
2663    /// ```
2664    pub fn n_elements(&self) -> usize {
2665        // Invariant: public mutable view constructors validate logical element count.
2666        match checked_view_element_count(self.shape(), "TypedTensorViewMut::n_elements") {
2667            Ok(n) => n,
2668            Err(err) => {
2669                unreachable!("TypedTensorViewMut layout shape is validated at construction: {err}")
2670            }
2671        }
2672    }
2673
2674    /// Return layout metadata for this view.
2675    ///
2676    /// # Examples
2677    ///
2678    /// ```rust
2679    /// use tenferro_tensor::TypedTensorViewMut;
2680    ///
2681    /// let mut data = [1_i32, 2];
2682    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
2683    /// assert!(view.layout().is_compact_col_major().unwrap());
2684    /// # Ok::<(), tenferro_tensor::Error>(())
2685    /// ```
2686    pub fn layout(&self) -> &TensorLayout<R> {
2687        &self.layout
2688    }
2689
2690    /// Return placement metadata for this view.
2691    ///
2692    /// # Examples
2693    ///
2694    /// ```rust
2695    /// use tenferro_tensor::{MemoryKind, TypedTensorViewMut};
2696    ///
2697    /// let mut data = [1_i32];
2698    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
2699    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
2700    /// # Ok::<(), tenferro_tensor::Error>(())
2701    /// ```
2702    pub fn placement(&self) -> &Placement {
2703        &self.placement
2704    }
2705
2706    /// Return the backend allocation for backend integrations.
2707    #[doc(hidden)]
2708    pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>> {
2709        match &self.buffer {
2710            TensorStorageRefMut::Host(_) => None,
2711            TensorStorageRefMut::Backend(buffer) => Some(&**buffer),
2712        }
2713    }
2714
2715    /// Prepare this backend view for one provider-native write binding.
2716    #[doc(hidden)]
2717    pub fn prepare_device_write(
2718        &mut self,
2719        op: &'static str,
2720    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
2721    where
2722        T: TensorScalar + 'static,
2723    {
2724        let layout = self.layout.clone();
2725        if self.root.is_none() {
2726            let buffer = self
2727                .backend_buffer()
2728                .ok_or_else(|| crate::Error::runtime_state(op, "expected a backend tensor view"))?;
2729            return prepare_backend_access(buffer, &self.layout, op);
2730        }
2731        let root = self
2732            .root
2733            .as_mut()
2734            .ok_or_else(|| crate::Error::runtime_state(op, "expected a root-backed tensor view"))?;
2735        root.prepare_device_write_for_layout(&layout)
2736            .map_err(|error| crate::Error::runtime_state_source(op, error))
2737    }
2738
2739    /// Compute the physical element offset for a logical index.
2740    ///
2741    /// # Examples
2742    ///
2743    /// ```rust
2744    /// use tenferro_tensor::TypedTensorViewMut;
2745    ///
2746    /// let mut data = [1_i32, 2, 3];
2747    /// let view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
2748    /// assert_eq!(view.linear_offset(&[2]), Some(0));
2749    /// # Ok::<(), tenferro_tensor::Error>(())
2750    /// ```
2751    pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
2752        checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
2753    }
2754
2755    /// Compute the physical element offset for a logical index, returning a typed error.
2756    ///
2757    /// # Examples
2758    ///
2759    /// ```rust
2760    /// use tenferro_tensor::TypedTensorViewMut;
2761    ///
2762    /// let mut data = [1_i32, 2, 3];
2763    /// let view = TypedTensorViewMut::from_slice([3], [-1], 2, &mut data)?;
2764    /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
2765    /// # Ok::<(), tenferro_tensor::Error>(())
2766    /// ```
2767    /// # Errors
2768    ///
2769    /// Returns [`crate::Error::Validation`] with
2770    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
2771    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
2772    /// when an index is outside its axis extent, or
2773    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
2774    /// arithmetic overflows.
2775    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2776        checked_view_offset_result(
2777            self.shape(),
2778            self.strides(),
2779            self.offset(),
2780            indices,
2781            "TypedTensorViewMut::layout_linear_offset",
2782        )
2783    }
2784
2785    /// Return whether this mutable view is compact column-major.
2786    ///
2787    /// # Examples
2788    ///
2789    /// ```rust
2790    /// use tenferro_tensor::TypedTensorViewMut;
2791    ///
2792    /// let mut data = [1_i32, 2];
2793    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
2794    /// assert!(view.is_col_major_contiguous()?);
2795    /// # Ok::<(), tenferro_tensor::Error>(())
2796    /// ```
2797    /// # Errors
2798    ///
2799    /// Returns [`crate::Error::Validation`] with
2800    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
2801    /// compactness arithmetic overflows.
2802    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
2803        self.layout
2804            .is_compact_col_major()
2805            .map_err(|err| tensor_layout_error("TypedTensorViewMut::is_col_major_contiguous", err))
2806    }
2807
2808    /// Return a compact string summary of this mutable view's layout metadata.
2809    ///
2810    /// # Examples
2811    ///
2812    /// ```rust
2813    /// use tenferro_tensor::TypedTensorViewMut;
2814    ///
2815    /// let mut data = [1_i32, 2];
2816    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
2817    /// assert!(view.layout_summary().contains("shape=[2]"));
2818    /// # Ok::<(), tenferro_tensor::Error>(())
2819    /// ```
2820    pub fn layout_summary(&self) -> String {
2821        layout_summary(self.shape(), self.strides(), self.offset())
2822    }
2823
2824    /// Assert this mutable view is compact column-major.
2825    ///
2826    /// # Examples
2827    ///
2828    /// ```rust
2829    /// use tenferro_tensor::TypedTensorViewMut;
2830    ///
2831    /// let mut data = [1_i32, 2];
2832    /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
2833    /// view.assert_col_major_contiguous()?;
2834    /// # Ok::<(), tenferro_tensor::Error>(())
2835    /// ```
2836    /// # Errors
2837    ///
2838    /// Returns [`crate::Error::Validation`] with
2839    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
2840    /// compactness arithmetic overflows, or
2841    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
2842    /// view is not compact column-major.
2843    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
2844        assert_layout_col_major_contiguous(
2845            self.is_col_major_contiguous()?,
2846            self.shape(),
2847            self.strides(),
2848            self.offset(),
2849            "TypedTensorViewMut::assert_col_major_contiguous",
2850        )
2851    }
2852
2853    /// Borrow one host element by logical index.
2854    ///
2855    /// # Examples
2856    ///
2857    /// ```rust
2858    /// use tenferro_tensor::TypedTensorViewMut;
2859    ///
2860    /// let mut data = [1_i32, 2];
2861    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
2862    /// assert_eq!(view.get(&[1]), Some(&2));
2863    /// # Ok::<(), tenferro_tensor::Error>(())
2864    /// ```
2865    pub fn get(&self, indices: &[usize]) -> Option<&T> {
2866        let offset = self.linear_offset(indices)?;
2867        match &self.buffer {
2868            TensorStorageRefMut::Host(data) => data.get(offset),
2869            TensorStorageRefMut::Backend(_) => None,
2870        }
2871    }
2872
2873    /// Mutably borrow one host element by logical index.
2874    ///
2875    /// # Examples
2876    ///
2877    /// ```rust
2878    /// use tenferro_tensor::TypedTensorViewMut;
2879    ///
2880    /// let mut data = [1_i32, 2];
2881    /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
2882    /// *view.get_mut(&[1]).unwrap() = 20;
2883    /// assert_eq!(view.get(&[1]), Some(&20));
2884    /// # Ok::<(), tenferro_tensor::Error>(())
2885    /// ```
2886    pub fn get_mut(&mut self, indices: &[usize]) -> Option<&mut T> {
2887        let offset = self.linear_offset(indices)?;
2888        match &mut self.buffer {
2889            TensorStorageRefMut::Host(data) => data.get_mut(offset),
2890            TensorStorageRefMut::Backend(_) => None,
2891        }
2892    }
2893
2894    /// Borrow this mutable view as a read-only view.
2895    ///
2896    /// # Examples
2897    ///
2898    /// ```rust
2899    /// use tenferro_tensor::TypedTensorViewMut;
2900    ///
2901    /// let mut data = [1_i32];
2902    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
2903    /// assert_eq!(view.as_read_only().get(&[0]), Some(&1));
2904    /// # Ok::<(), tenferro_tensor::Error>(())
2905    /// ```
2906    /// Explicitly duplicate the compact host data visible through this
2907    /// mutable view into a new owner.
2908    ///
2909    /// # Errors
2910    ///
2911    /// Returns [`crate::Error::HostAccess`] or
2912    /// [`ValidationError::NonContiguousViewAsSlice`] when the view is backend
2913    /// owned or not contiguous, and [`ValidationError::InvalidArgument`] when
2914    /// the static-rank shape cannot be reconstructed.
2915    ///
2916    /// # Examples
2917    ///
2918    /// ```
2919    /// use tenferro_tensor::TypedTensorViewMut;
2920    ///
2921    /// let mut data = [1_i32, 2];
2922    /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
2923    /// let copy = view.duplicate()?;
2924    /// assert_eq!(copy.as_slice()?, &[1, 2]);
2925    /// # Ok::<(), tenferro_tensor::Error>(())
2926    /// ```
2927    pub fn duplicate(&self) -> crate::Result<TypedTensor<T, R>>
2928    where
2929        T: TensorScalar,
2930    {
2931        self.as_read_only().duplicate()
2932    }
2933
2934    pub fn as_read_only(&self) -> TypedTensorView<'_, T, R> {
2935        let buffer = match &self.buffer {
2936            TensorStorageRefMut::Host(data) => TensorStorageRef::Host(data),
2937            TensorStorageRefMut::Backend(buffer) => TensorStorageRef::Backend(&**buffer),
2938        };
2939        TypedTensorView {
2940            buffer,
2941            root: None,
2942            layout: self.layout.clone(),
2943            placement: self.placement.clone(),
2944        }
2945    }
2946
2947    /// Convert this mutable view into a read-only view.
2948    ///
2949    /// # Examples
2950    ///
2951    /// ```rust
2952    /// use tenferro_tensor::TypedTensorViewMut;
2953    ///
2954    /// let mut data = [1_i32];
2955    /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
2956    /// assert_eq!(view.into_read_only().get(&[0]), Some(&1));
2957    /// # Ok::<(), tenferro_tensor::Error>(())
2958    /// ```
2959    pub fn into_read_only(self) -> TypedTensorView<'a, T, R> {
2960        let buffer = match self.buffer {
2961            TensorStorageRefMut::Host(data) => TensorStorageRef::Host(data),
2962            TensorStorageRefMut::Backend(buffer) => TensorStorageRef::Backend(buffer),
2963        };
2964        TypedTensorView {
2965            buffer,
2966            root: None,
2967            layout: self.layout,
2968            placement: self.placement,
2969        }
2970    }
2971
2972    /// Consume this mutable view and return a metadata-only axis permutation.
2973    ///
2974    /// # Examples
2975    ///
2976    /// ```rust
2977    /// use tenferro_tensor::{Rank, TypedTensorViewMut};
2978    ///
2979    /// let mut data = [1_i32, 2, 3, 4];
2980    /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
2981    /// let transposed = view.transpose_view([1, 0])?;
2982    /// assert_eq!(transposed.strides(), &[2, 1]);
2983    /// # Ok::<(), tenferro_tensor::Error>(())
2984    /// ```
2985    /// # Errors
2986    ///
2987    /// Returns [`crate::Error::Validation`] with
2988    /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
2989    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
2990    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
2991    /// not a valid permutation, [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`]
2992    /// when the permutation creates aliases, or
2993    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
2994    /// arithmetic overflow.
2995    pub fn transpose_view(
2996        self,
2997        axes: impl AsRef<[usize]>,
2998    ) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
2999        let Self {
3000            buffer,
3001            root,
3002            layout,
3003            placement,
3004        } = self;
3005        let layout = layout
3006            .transpose_view(axes)
3007            .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
3008        layout
3009            .validate_mutable_no_overlap()
3010            .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
3011        match buffer {
3012            TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
3013                buffer: TensorStorageRefMut::Host(data),
3014                root,
3015                layout,
3016                placement,
3017            }),
3018            TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
3019                buffer: TensorStorageRefMut::Backend(buffer),
3020                root,
3021                layout,
3022                placement,
3023            }),
3024        }
3025    }
3026
3027    /// Return a mutable metadata-only slice using one [`StridedSliceSpec`] per axis.
3028    ///
3029    /// # Examples
3030    ///
3031    /// ```rust
3032    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
3033    ///
3034    /// let mut data = [1_i32, 2, 3];
3035    /// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![1], 0, &mut data)?;
3036    /// *view.try_slice(&[StridedSliceSpec::reverse()])?.get_mut(&[0]).unwrap() = 30;
3037    /// assert_eq!(view.get(&[2]), Some(&30));
3038    /// # Ok::<(), tenferro_tensor::Error>(())
3039    /// ```
3040    /// # Errors
3041    ///
3042    /// Returns [`crate::Error::Validation`] with
3043    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
3044    /// count differs from the view rank,
3045    /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
3046    /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
3047    /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
3048    /// when the result exceeds the backing buffer,
3049    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
3050    /// logical elements alias, or
3051    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
3052    /// arithmetic overflow.
3053    pub fn try_slice(
3054        &mut self,
3055        slices: &[StridedSliceSpec],
3056    ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
3057        let specs = core_slice_specs(slices, self.shape(), "TypedTensorViewMut::try_slice")?;
3058        let layout = self
3059            .layout
3060            .slice_view(specs, self.buffer.len())
3061            .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_slice", err))?;
3062        layout
3063            .validate_mutable_no_overlap()
3064            .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_slice", err))?;
3065        let placement = self.placement.clone();
3066        match &mut self.buffer {
3067            TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
3068                buffer: TensorStorageRefMut::Host(data),
3069                root: None,
3070                layout,
3071                placement,
3072            }),
3073            TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
3074                buffer: TensorStorageRefMut::Backend(*buffer),
3075                root: None,
3076                layout,
3077                placement,
3078            }),
3079        }
3080    }
3081
3082    /// Return a mutable metadata-only slice along one axis.
3083    ///
3084    /// # Examples
3085    ///
3086    /// ```rust
3087    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
3088    ///
3089    /// let mut data = [1_i32, 2, 3, 4];
3090    /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
3091    /// assert_eq!(view.try_slice_axis(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
3092    /// # Ok::<(), tenferro_tensor::Error>(())
3093    /// ```
3094    /// # Errors
3095    ///
3096    /// Returns [`crate::Error::Validation`] with
3097    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
3098    /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
3099    /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
3100    /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
3101    /// when the result exceeds the backing buffer,
3102    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
3103    /// logical elements alias, or
3104    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
3105    /// arithmetic overflow.
3106    pub fn try_slice_axis(
3107        &mut self,
3108        axis: usize,
3109        slice: StridedSliceSpec,
3110    ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
3111        let slices = slice_axis_specs(
3112            self.shape().len(),
3113            axis,
3114            slice,
3115            "TypedTensorViewMut::try_slice_axis",
3116        )?;
3117        self.try_slice(&slices)
3118    }
3119
3120    /// Return two mutable metadata-only slices when their physical ranges are disjoint.
3121    ///
3122    /// # Examples
3123    ///
3124    /// ```rust
3125    /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
3126    ///
3127    /// let mut data = [1_i32, 2, 3, 4];
3128    /// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
3129    /// let (left, right) = view
3130    ///     .try_multi_slice_mut(
3131    ///         &[StridedSliceSpec::new(0, Some(2), 1)],
3132    ///         &[StridedSliceSpec::new(2, Some(4), 1)],
3133    ///     )
3134    ///     ?
3135    ///     .unwrap();
3136    /// assert_eq!(left.shape(), &[2]);
3137    /// assert_eq!(right.shape(), &[2]);
3138    /// # Ok::<(), tenferro_tensor::Error>(())
3139    /// ```
3140    /// # Errors
3141    ///
3142    /// Returns [`crate::Error::Validation`] with
3143    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for either
3144    /// slice count, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
3145    /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for
3146    /// invalid parameters, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
3147    /// when a result exceeds the backing buffer,
3148    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
3149    /// a result aliases, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
3150    /// for a negative reachable offset, or
3151    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
3152    /// arithmetic overflow. The method returns `Ok(None)` when the two ranges
3153    /// overlap or the view uses backend storage.
3154    pub fn try_multi_slice_mut(
3155        &mut self,
3156        first: &[StridedSliceSpec],
3157        second: &[StridedSliceSpec],
3158    ) -> crate::Result<Option<TypedTensorViewMutSplit<'_, T, R>>> {
3159        let op = "TypedTensorViewMut::try_multi_slice_mut";
3160        let first_specs = core_slice_specs(first, self.shape(), op)?;
3161        let second_specs = core_slice_specs(second, self.shape(), op)?;
3162        let buffer_len = self.buffer.len();
3163        let first_layout = self
3164            .layout
3165            .slice_view(first_specs, buffer_len)
3166            .map_err(|err| tensor_layout_error(op, err))?;
3167        let second_layout = self
3168            .layout
3169            .slice_view(second_specs, buffer_len)
3170            .map_err(|err| tensor_layout_error(op, err))?;
3171        first_layout
3172            .validate_mutable_no_overlap()
3173            .map_err(|err| tensor_layout_error(op, err))?;
3174        second_layout
3175            .validate_mutable_no_overlap()
3176            .map_err(|err| tensor_layout_error(op, err))?;
3177
3178        match (
3179            reachable_layout_span(
3180                first_layout.shape(),
3181                first_layout.strides(),
3182                first_layout.offset(),
3183            )?,
3184            reachable_layout_span(
3185                second_layout.shape(),
3186                second_layout.strides(),
3187                second_layout.offset(),
3188            )?,
3189        ) {
3190            (Some(first_span), Some(second_span)) => {
3191                let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
3192                let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
3193                let (first_data, second_data) = match &mut self.buffer {
3194                    TensorStorageRefMut::Host(data) => {
3195                        match split_two_mut_ranges(data, first_span, second_span) {
3196                            Some(ranges) => ranges,
3197                            None => return Ok(None),
3198                        }
3199                    }
3200                    TensorStorageRefMut::Backend(_) => return Ok(None),
3201                };
3202                let first_view = view_mut_from_layout_and_slice(
3203                    &first_layout,
3204                    first_offset,
3205                    first_data,
3206                    self.placement.clone(),
3207                )?;
3208                let second_view = view_mut_from_layout_and_slice(
3209                    &second_layout,
3210                    second_offset,
3211                    second_data,
3212                    self.placement.clone(),
3213                )?;
3214                Ok(Some((first_view, second_view)))
3215            }
3216            (None, Some(second_span)) => {
3217                let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
3218                let (_, after_start) = match &mut self.buffer {
3219                    TensorStorageRefMut::Host(data) => data.split_at_mut(second_span.0),
3220                    TensorStorageRefMut::Backend(_) => return Ok(None),
3221                };
3222                let (second_data, _) = after_start.split_at_mut(second_span.1 - second_span.0 + 1);
3223                let first_view = view_mut_from_layout_and_slice(
3224                    &first_layout,
3225                    0,
3226                    &mut [],
3227                    self.placement.clone(),
3228                )?;
3229                let second_view = view_mut_from_layout_and_slice(
3230                    &second_layout,
3231                    second_offset,
3232                    second_data,
3233                    self.placement.clone(),
3234                )?;
3235                Ok(Some((first_view, second_view)))
3236            }
3237            (Some(first_span), None) => {
3238                let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
3239                let (_, after_start) = match &mut self.buffer {
3240                    TensorStorageRefMut::Host(data) => data.split_at_mut(first_span.0),
3241                    TensorStorageRefMut::Backend(_) => return Ok(None),
3242                };
3243                let (first_data, _) = after_start.split_at_mut(first_span.1 - first_span.0 + 1);
3244                let first_view = view_mut_from_layout_and_slice(
3245                    &first_layout,
3246                    first_offset,
3247                    first_data,
3248                    self.placement.clone(),
3249                )?;
3250                let second_view = view_mut_from_layout_and_slice(
3251                    &second_layout,
3252                    0,
3253                    &mut [],
3254                    self.placement.clone(),
3255                )?;
3256                Ok(Some((first_view, second_view)))
3257            }
3258            (None, None) => {
3259                let first_view = view_mut_from_layout_and_slice(
3260                    &first_layout,
3261                    0,
3262                    &mut [],
3263                    self.placement.clone(),
3264                )?;
3265                let second_view = view_mut_from_layout_and_slice(
3266                    &second_layout,
3267                    0,
3268                    &mut [],
3269                    self.placement.clone(),
3270                )?;
3271                Ok(Some((first_view, second_view)))
3272            }
3273        }
3274    }
3275
3276    /// Return a mutable metadata-only dynamic-rank reshape for contiguous views.
3277    ///
3278    /// # Examples
3279    ///
3280    /// ```rust
3281    /// use tenferro_tensor::TypedTensorViewMut;
3282    ///
3283    /// let mut data = [1_i32, 2, 3, 4];
3284    /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
3285    /// assert_eq!(view.try_reshape(&[4])?.shape(), &[4]);
3286    /// # Ok::<(), tenferro_tensor::Error>(())
3287    /// ```
3288    /// # Errors
3289    ///
3290    /// Returns [`crate::Error::Validation`] with
3291    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
3292    /// the source is not compact column-major,
3293    /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
3294    /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
3295    /// records the counts) when element counts differ,
3296    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
3297    /// the reshaped layout aliases, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3298    /// for shape or layout arithmetic overflow, or
3299    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3300    /// reshaped view exceeds the backing buffer.
3301    pub fn try_reshape(
3302        &mut self,
3303        shape: &[usize],
3304    ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>> {
3305        let layout = reshape_layout_dyn(
3306            &self.layout,
3307            shape,
3308            self.buffer.len(),
3309            "TypedTensorViewMut::try_reshape",
3310        )?;
3311        layout
3312            .validate_mutable_no_overlap()
3313            .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_reshape", err))?;
3314        let placement = self.placement.clone();
3315        match &mut self.buffer {
3316            TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
3317                buffer: TensorStorageRefMut::Host(data),
3318                root: None,
3319                layout,
3320                placement,
3321            }),
3322            TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
3323                buffer: TensorStorageRefMut::Backend(*buffer),
3324                root: None,
3325                layout,
3326                placement,
3327            }),
3328        }
3329    }
3330}
3331
3332impl<'a, R: TensorRank> TypedTensorViewMut<'a, Complex32, R> {
3333    /// Borrow this mutable complex view as an interleaved real view.
3334    ///
3335    /// This changes only the typed descriptor and borrows the same host
3336    /// allocation. The result has dynamic rank because the component axis is
3337    /// prepended. Backend-native buffers are rejected until their provider
3338    /// phase supplies the corresponding mapping capability.
3339    ///
3340    /// # Errors
3341    ///
3342    /// Returns an error when the view layout is not injective, the sealed
3343    /// representation is invalid, or backend reinterpretation is unsupported.
3344    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
3345        let op = "TypedTensorViewMut::as_real_view_mut";
3346        validate_representation_pair(op, DType::C32, DType::F32)?;
3347        let layout = reinterpret_complex_to_real_layout(
3348            self.shape(),
3349            self.strides(),
3350            self.offset(),
3351            self.buffer.len(),
3352            op,
3353        )?;
3354        layout
3355            .validate_mutable_no_overlap()
3356            .map_err(|err| tensor_layout_error(op, err))?;
3357        let buffer = match &mut self.buffer {
3358            TensorStorageRefMut::Host(data) => {
3359                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(data, op)?)
3360            }
3361            TensorStorageRefMut::Backend(_) => {
3362                return Err(crate::Error::unsupported(
3363                    op,
3364                    "backend representation reinterpretation is enabled by the provider phases",
3365                ))
3366            }
3367        };
3368        Ok(TypedTensorViewMut {
3369            buffer,
3370            root: None,
3371            layout,
3372            placement: self.placement.clone(),
3373        })
3374    }
3375}
3376
3377impl<'a, R: TensorRank> TypedTensorViewMut<'a, Complex64, R> {
3378    /// Borrow this mutable complex view as an interleaved real view.
3379    ///
3380    /// This changes only the typed descriptor and borrows the same host
3381    /// allocation. The result has dynamic rank because the component axis is
3382    /// prepended. Backend-native buffers are rejected until their provider
3383    /// phase supplies the corresponding mapping capability.
3384    ///
3385    /// # Errors
3386    ///
3387    /// Returns an error when the view layout is not injective, the sealed
3388    /// representation is invalid, or backend reinterpretation is unsupported.
3389    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
3390        let op = "TypedTensorViewMut::as_real_view_mut";
3391        validate_representation_pair(op, DType::C64, DType::F64)?;
3392        let layout = reinterpret_complex_to_real_layout(
3393            self.shape(),
3394            self.strides(),
3395            self.offset(),
3396            self.buffer.len(),
3397            op,
3398        )?;
3399        layout
3400            .validate_mutable_no_overlap()
3401            .map_err(|err| tensor_layout_error(op, err))?;
3402        let buffer = match &mut self.buffer {
3403            TensorStorageRefMut::Host(data) => {
3404                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(data, op)?)
3405            }
3406            TensorStorageRefMut::Backend(_) => {
3407                return Err(crate::Error::unsupported(
3408                    op,
3409                    "backend representation reinterpretation is enabled by the provider phases",
3410                ))
3411            }
3412        };
3413        Ok(TypedTensorViewMut {
3414            buffer,
3415            root: None,
3416            layout,
3417            placement: self.placement.clone(),
3418        })
3419    }
3420}
3421
3422impl<'a, R: TensorRank> TypedTensorViewMut<'a, f32, R> {
3423    /// Borrow this mutable interleaved real view as a complex view.
3424    ///
3425    /// The source must have a leading extent and stride of `2` and `1`, and
3426    /// all remaining strides plus the offset must be divisible by `2`.
3427    ///
3428    /// # Errors
3429    ///
3430    /// Returns an error when the view layout is not injective, the sealed
3431    /// representation is invalid, or backend reinterpretation is unsupported.
3432    pub fn as_complex_view_mut(
3433        &mut self,
3434    ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
3435        let op = "TypedTensorViewMut::as_complex_view_mut";
3436        validate_representation_pair(op, DType::F32, DType::C32)?;
3437        let layout = reinterpret_real_to_complex_layout(
3438            self.shape(),
3439            self.strides(),
3440            self.offset(),
3441            self.buffer.len(),
3442            op,
3443        )?;
3444        layout
3445            .validate_mutable_no_overlap()
3446            .map_err(|err| tensor_layout_error(op, err))?;
3447        let buffer = match &mut self.buffer {
3448            TensorStorageRefMut::Host(data) => {
3449                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(data, op)?)
3450            }
3451            TensorStorageRefMut::Backend(_) => {
3452                return Err(crate::Error::unsupported(
3453                    op,
3454                    "backend representation reinterpretation is enabled by the provider phases",
3455                ))
3456            }
3457        };
3458        Ok(TypedTensorViewMut {
3459            buffer,
3460            root: None,
3461            layout,
3462            placement: self.placement.clone(),
3463        })
3464    }
3465}
3466
3467impl<'a, R: TensorRank> TypedTensorViewMut<'a, f64, R> {
3468    /// Borrow this mutable interleaved real view as a complex view.
3469    ///
3470    /// The source must have a leading extent and stride of `2` and `1`, and
3471    /// all remaining strides plus the offset must be divisible by `2`.
3472    ///
3473    /// # Errors
3474    ///
3475    /// Returns an error when the view layout is not injective, the sealed
3476    /// representation is invalid, or backend reinterpretation is unsupported.
3477    pub fn as_complex_view_mut(
3478        &mut self,
3479    ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
3480        let op = "TypedTensorViewMut::as_complex_view_mut";
3481        validate_representation_pair(op, DType::F64, DType::C64)?;
3482        let layout = reinterpret_real_to_complex_layout(
3483            self.shape(),
3484            self.strides(),
3485            self.offset(),
3486            self.buffer.len(),
3487            op,
3488        )?;
3489        layout
3490            .validate_mutable_no_overlap()
3491            .map_err(|err| tensor_layout_error(op, err))?;
3492        let buffer = match &mut self.buffer {
3493            TensorStorageRefMut::Host(data) => {
3494                TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(data, op)?)
3495            }
3496            TensorStorageRefMut::Backend(_) => {
3497                return Err(crate::Error::unsupported(
3498                    op,
3499                    "backend representation reinterpretation is enabled by the provider phases",
3500                ))
3501            }
3502        };
3503        Ok(TypedTensorViewMut {
3504            buffer,
3505            root: None,
3506            layout,
3507            placement: self.placement.clone(),
3508        })
3509    }
3510}
3511
3512/// Sealed trait for scalar types that can be stored in a [`Tensor`].
3513///
3514/// This trait is implemented for `f64`, `f32`, `i32`, `i64`, `bool`,
3515/// [`Complex64`], and [`Complex32`].
3516///
3517/// # Examples
3518///
3519/// ```
3520/// use tenferro_tensor::TensorScalar;
3521///
3522/// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
3523/// assert_eq!(tensor.as_slice::<f64>()?, [1.0, 2.0].as_slice());
3524/// # Ok::<(), tenferro_tensor::Error>(())
3525/// ```
3526pub trait TensorScalar: Copy + Clone + Send + Sync + 'static + private::Sealed {
3527    /// Real-valued counterpart of this scalar type.
3528    type Real: TensorScalar;
3529
3530    /// The [`DType`] tag corresponding to this scalar type.
3531    ///
3532    /// # Examples
3533    ///
3534    /// ```
3535    /// use tenferro_tensor::{DType, TensorScalar};
3536    ///
3537    /// assert_eq!(f64::dtype(), DType::F64);
3538    /// assert_eq!(f32::dtype(), DType::F32);
3539    /// ```
3540    fn dtype() -> DType;
3541
3542    /// Wrap typed column-major data into a [`Tensor`] enum variant.
3543    /// # Errors
3544    ///
3545    /// Returns [`crate::Error::Validation`] with
3546    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
3547    /// the shape product differs from `data.len()`, or
3548    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
3549    /// arithmetic overflows.
3550    fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor>;
3551
3552    /// Wrap a typed tensor into its dynamic [`Tensor`] enum variant.
3553    ///
3554    /// # Examples
3555    ///
3556    /// ```
3557    /// use tenferro_tensor::{DType, Tensor, TensorScalar, TypedTensor};
3558    ///
3559    /// let typed = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![3.0])?;
3560    /// let tensor = <f64 as TensorScalar>::typed_tensor_into_tensor(typed);
3561    /// assert!(matches!(tensor.dtype(), DType::F64));
3562    /// # Ok::<(), tenferro_tensor::Error>(())
3563    /// ```
3564    fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor;
3565
3566    /// Borrow a typed tensor as a dtype-erased [`TensorRead`] view.
3567    ///
3568    /// This keeps the typed tensor borrowed instead of copying host data into
3569    /// a new dynamic tensor.
3570    ///
3571    /// # Examples
3572    ///
3573    /// ```
3574    /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
3575    ///
3576    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3577    /// let read = f64::tensor_read(&tensor);
3578    /// assert_eq!(read.dtype(), DType::F64);
3579    /// assert_eq!(read.shape(), &[2]);
3580    /// ```
3581    fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_>;
3582
3583    /// Wrap a typed borrowed view as a dtype-erased [`TensorView`].
3584    ///
3585    /// # Examples
3586    ///
3587    /// ```
3588    /// use tenferro_tensor::{DType, TensorScalar, TypedTensorView};
3589    ///
3590    /// let data = [1.0_f64];
3591    /// let view = TypedTensorView::from_col_major(&[1], &data)?;
3592    /// assert_eq!(f64::tensor_view(view).dtype(), DType::F64);
3593    /// # Ok::<(), tenferro_tensor::Error>(())
3594    /// ```
3595    fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a>;
3596
3597    /// Wrap a typed mutable borrowed view as a dtype-erased [`TensorViewMut`].
3598    ///
3599    /// # Examples
3600    ///
3601    /// ```
3602    /// use tenferro_tensor::{DType, TensorScalar, TypedTensorViewMut};
3603    ///
3604    /// let mut data = [1.0_f64, 2.0];
3605    /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
3606    /// let erased = f64::tensor_view_mut(view);
3607    /// assert_eq!(erased.dtype(), DType::F64);
3608    /// assert_eq!(erased.shape(), &[2]);
3609    /// # Ok::<(), tenferro_tensor::Error>(())
3610    /// ```
3611    fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a>;
3612
3613    /// Mutably borrow a typed tensor as a dtype-erased [`TensorWrite`] view.
3614    ///
3615    /// This keeps the typed output borrowed instead of wrapping it in a
3616    /// temporary dynamic tensor.
3617    ///
3618    /// # Examples
3619    ///
3620    /// ```
3621    /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
3622    ///
3623    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0]).unwrap();
3624    /// let write = f64::tensor_write(&mut tensor);
3625    /// assert_eq!(write.dtype(), DType::F64);
3626    /// ```
3627    fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_>;
3628
3629    /// Borrow the host data from a [`Tensor`].
3630    /// # Errors
3631    ///
3632    /// Returns [`crate::Error::Validation`] with
3633    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
3634    /// is not the scalar type represented by this implementation, or
3635    /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
3636    /// storage that has not been downloaded.
3637    fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]>;
3638
3639    /// Mutably borrow the host data from a [`Tensor`].
3640    ///
3641    /// # Examples
3642    ///
3643    /// ```
3644    /// use tenferro_tensor::{Tensor, TensorScalar};
3645    ///
3646    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
3647    /// <f64 as TensorScalar>::as_slice_mut(&mut tensor)?[0] = 3.0;
3648    ///
3649    /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
3650    /// # Ok::<(), tenferro_tensor::Error>(())
3651    /// ```
3652    /// # Errors
3653    ///
3654    /// Returns [`crate::Error::Validation`] with
3655    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
3656    /// is not the scalar type represented by this implementation, or
3657    /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
3658    /// storage that has not been downloaded.
3659    fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]>;
3660
3661    /// Extract a [`TypedTensor<Self>`] from a dynamic [`Tensor`].
3662    ///
3663    /// # Examples
3664    ///
3665    /// ```
3666    /// use tenferro_tensor::{Tensor, TensorScalar};
3667    ///
3668    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
3669    /// let typed = <f64 as TensorScalar>::into_typed(tensor)?;
3670    ///
3671    /// assert_eq!(typed.as_slice()?, &[1.0, 2.0]);
3672    /// # Ok::<(), tenferro_tensor::Error>(())
3673    /// ```
3674    /// # Errors
3675    ///
3676    /// Returns [`crate::Error::Validation`] with
3677    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
3678    /// is not the scalar type represented by this implementation.
3679    fn into_typed(tensor: Tensor) -> crate::Result<TypedTensor<Self>>;
3680}
3681
3682mod private {
3683    pub trait Sealed {}
3684
3685    impl Sealed for f64 {}
3686    impl Sealed for f32 {}
3687    impl Sealed for i32 {}
3688    impl Sealed for i64 {}
3689    impl Sealed for bool {}
3690    impl Sealed for num_complex::Complex64 {}
3691    impl Sealed for num_complex::Complex32 {}
3692}
3693
3694macro_rules! impl_tensor_scalar {
3695    ($ty:ty, $real:ty, $dtype:ident, $variant:ident) => {
3696        impl TensorScalar for $ty {
3697            type Real = $real;
3698
3699            fn dtype() -> DType {
3700                DType::$dtype
3701            }
3702
3703            fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor> {
3704                TypedTensor::from_vec_col_major(shape, data)
3705                    .map(|tensor| Tensor::from_core(tensor.core))
3706            }
3707
3708            fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor {
3709                Tensor::from_core(tensor.core)
3710            }
3711
3712            fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_> {
3713                TensorRead::from_view(TensorView::$variant(tensor.as_view()))
3714            }
3715
3716            fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a> {
3717                TensorView::$variant(view)
3718            }
3719
3720            fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a> {
3721                TensorViewMut::$variant(view)
3722            }
3723
3724            fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_> {
3725                TensorWrite::from_view(TensorViewMut::$variant(tensor.as_view_mut()))
3726            }
3727
3728            fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]> {
3729                tensor
3730                    .as_typed::<Self>()
3731                    .ok_or_else(|| {
3732                        crate::Error::validation(
3733                            "Tensor::as_slice",
3734                            ValidationError::DTypeMismatch {
3735                                expected: Self::dtype(),
3736                                actual: tensor.dtype(),
3737                            },
3738                        )
3739                    })?
3740                    .host_data()
3741            }
3742
3743            fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]> {
3744                let actual = tensor.dtype();
3745                let typed = tensor.as_typed_mut::<Self>().ok_or_else(|| {
3746                    crate::Error::validation(
3747                        "Tensor::as_slice_mut",
3748                        ValidationError::DTypeMismatch {
3749                            expected: Self::dtype(),
3750                            actual,
3751                        },
3752                    )
3753                })?;
3754                typed.host_data_mut()
3755            }
3756
3757            fn into_typed(tensor: Tensor) -> crate::Result<TypedTensor<Self>> {
3758                tensor.into_preset::<Self>()
3759            }
3760        }
3761    };
3762}
3763
3764impl_tensor_scalar!(f64, f64, F64, F64);
3765impl_tensor_scalar!(f32, f32, F32, F32);
3766impl_tensor_scalar!(i64, i64, I64, I64);
3767impl_tensor_scalar!(i32, i32, I32, I32);
3768impl_tensor_scalar!(bool, bool, Bool, Bool);
3769impl_tensor_scalar!(Complex64, f64, C64, C64);
3770impl_tensor_scalar!(Complex32, f32, C32, C32);
3771
3772/// Dynamic tensor enum over the supported scalar types.
3773///
3774/// The enum keeps dtype dynamic and rank dynamic. Use
3775/// [`TypedTensor<T, R>`](TypedTensor) directly when the scalar type or rank
3776/// should be represented in Rust's type system.
3777///
3778/// # Examples
3779///
3780/// ```rust
3781/// use tenferro_tensor::{Tensor, TypedTensor};
3782///
3783/// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
3784/// assert_eq!(t.shape(), &[2]);
3785///
3786/// let erased = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 2.0]).unwrap();
3787/// assert_eq!(erased.shape().len(), 2);
3788/// ```
3789/// The owning payload behind the erased [`Tensor`].
3790///
3791/// `Native` holds the scalar-independent core of a preset tensor: the element type is the
3792/// descriptor's dtype, so no second tag is stored. `External` holds a caller-owned host
3793/// payload beside the placement it lives in, and is recovered by its own element type
3794/// without reinterpreting any bytes.
3795// The inline `Native` payload is the point of the representation: it keeps the erased
3796// tensor at 776 B with no per-tensor allocation, so the size gap against `External` is
3797// deliberate rather than an accident. #1823 sliced the metadata duplication that made it
3798// 1464 B; `erased_tensor_size_stays_within_the_documented_bound` in `types/tests.rs`
3799// holds the current bound.
3800// No cast depends on the payload layout: the only pointer casts are
3801// `TensorCore` <-> `TypedTensor`, and they cast the `TensorCore` field, not
3802// `TensorPayload`. The default repr keeps the tag in a niche and is 8 B smaller.
3803#[allow(clippy::large_enum_variant)]
3804#[derive(Debug)]
3805enum TensorPayload {
3806    Native(TensorCore<DynRank>),
3807    External(tenferro_tensor_core::ErasedHostTensor, Placement),
3808}
3809
3810/// Dynamic tensor over the supported scalar types.
3811///
3812/// The erased tensor keeps dtype dynamic and rank dynamic: the preset scalars share one
3813/// `Native` core whose descriptor records the element type, and a caller-owned scalar is an
3814/// `External` payload recovered by its own type. Use [`TypedTensor<T, R>`](TypedTensor)
3815/// directly when the scalar type or rank should be represented in Rust's type system.
3816///
3817/// # Examples
3818///
3819/// ```
3820/// use tenferro_tensor::{DType, Tensor};
3821///
3822/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
3823/// assert_eq!(tensor.dtype(), DType::F64);
3824/// # Ok::<(), tenferro_tensor::Error>(())
3825/// ```
3826#[derive(Debug)]
3827pub struct Tensor {
3828    payload: TensorPayload,
3829}
3830
3831impl Tensor {
3832    /// Build a tensor from a typed tensor's core; the descriptor carries the element dtype.
3833    pub(crate) fn from_core(core: TensorCore<DynRank>) -> Self {
3834        Self {
3835            payload: TensorPayload::Native(core),
3836        }
3837    }
3838
3839    /// The preset tensor's core, when this holds a preset scalar.
3840    /// The preset tensor's core, when this holds a preset scalar.
3841    /// Move the preset core out as a typed tensor, or report the dtype mismatch.
3842    fn into_preset<T: TensorScalar>(self) -> crate::Result<TypedTensor<T>> {
3843        let actual = self.dtype();
3844        match self.payload {
3845            TensorPayload::Native(core) if actual == T::dtype() => Ok(TypedTensor {
3846                core,
3847                _scalar: PhantomData,
3848            }),
3849            _ => Err(crate::Error::validation(
3850                "TensorScalar::into_typed",
3851                ValidationError::DTypeMismatch {
3852                    expected: T::dtype(),
3853                    actual,
3854                },
3855            )),
3856        }
3857    }
3858}
3859
3860impl TensorCore<DynRank> {
3861    /// Whether this core's descriptor names a non-CPU provider.
3862    fn is_backend_buffer(&self) -> bool {
3863        !matches!(
3864            self.group.group.provider_kind(self.group.slot),
3865            None | Some(crate::storage::ProviderKind::Cpu)
3866        )
3867    }
3868
3869    /// The storage length this core's descriptor records.
3870    pub(crate) fn buffer_len(&self) -> usize {
3871        self.group
3872            .group
3873            .descriptor_len(self.group.slot)
3874            .unwrap_or_else(|| unreachable!("typed tensor group descriptor mismatch"))
3875    }
3876
3877    /// Reborrow this core as a typed tensor.
3878    ///
3879    /// # Safety
3880    ///
3881    /// `T::dtype()` must equal the dtype this core's descriptor records.
3882    unsafe fn as_typed<T: TensorScalar>(&self) -> &TypedTensor<T> {
3883        // SAFETY: `TypedTensor<T, DynRank>` is `#[repr(transparent)]` over
3884        // `TensorCore<DynRank>` with a zero-sized `PhantomData<T>`, so both share an address,
3885        // size and alignment; the caller proves the element type.
3886        unsafe { &*(self as *const TensorCore<DynRank> as *const TypedTensor<T>) }
3887    }
3888
3889    /// Reborrow this core as a mutable typed tensor.
3890    ///
3891    /// # Safety
3892    ///
3893    /// `T::dtype()` must equal the dtype this core's descriptor records, and the borrow must
3894    /// be exclusive.
3895    unsafe fn as_typed_mut<T: TensorScalar>(&mut self) -> &mut TypedTensor<T> {
3896        // SAFETY: as in `as_typed`, with an exclusive borrow of the same address.
3897        unsafe { &mut *(self as *mut TensorCore<DynRank> as *mut TypedTensor<T>) }
3898    }
3899}
3900
3901impl Tensor {
3902    /// Carry an externally defined scalar as a caller-owned payload.
3903    ///
3904    /// The payload keeps its own element type and is recovered by that type, so no
3905    /// bytes are reinterpreted. Placement defaults to unpinned host memory, which
3906    /// is where a caller-owned payload lives.
3907    ///
3908    /// # Examples
3909    ///
3910    /// ```rust
3911    /// use tenferro_tensor::{DType, Tensor};
3912    /// use tenferro_tensor_core::{ErasedHostTensor, HostTensor};
3913    ///
3914    /// let payload = ErasedHostTensor::new(HostTensor::from_vec_col_major(vec![1], vec![1.0_f64])?);
3915    /// let element = payload.element_type_id();
3916    /// let tensor = Tensor::external(payload);
3917    /// assert_eq!(tensor.dtype(), DType::External(element));
3918    /// assert_eq!(tensor.shape(), &[1]);
3919    /// # Ok::<(), tenferro_tensor_core::ValidationError>(())
3920    /// ```
3921    #[must_use]
3922    pub fn external(payload: tenferro_tensor_core::ErasedHostTensor) -> Self {
3923        Self {
3924            payload: TensorPayload::External(payload, Placement::default()),
3925        }
3926    }
3927
3928    /// Build a tensor from a typed one, without naming its variant.
3929    ///
3930    /// A call site that constructs a tensor from a typed tensor should use this rather than a variant, so
3931    /// that changing how the erased representation is stored changes this function and not its 1290 call
3932    /// sites. The variants remain until the removal's last step, so both forms currently produce the same
3933    /// value.
3934    ///
3935    /// # Examples
3936    ///
3937    /// ```
3938    /// use tenferro_tensor::Tensor;
3939    ///
3940    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
3941    /// let typed = tensor.into_typed::<f64>()?;
3942    /// let rebuilt = Tensor::from_typed(typed);
3943    /// assert_eq!(rebuilt.as_typed::<f64>().unwrap().shape(), &[2]);
3944    /// # Ok::<(), tenferro_tensor::Error>(())
3945    /// ```
3946    #[must_use]
3947    pub fn from_typed<T: TensorScalar>(typed: TypedTensor<T>) -> Self {
3948        T::typed_tensor_into_tensor(typed)
3949    }
3950
3951    /// Borrow the erased payload of an externally defined tensor.
3952    ///
3953    /// This is the counterpart of [`Tensor::external`] for dispatch: a table that matches on
3954    /// [`Tensor::dtype`] reaches the externally defined tag and needs the payload that tag stands
3955    /// for, just as the typed tags reach theirs through [`Tensor::as_typed`]. Every other tag
3956    /// returns `None`.
3957    ///
3958    /// # Examples
3959    ///
3960    /// ```
3961    /// use tenferro_tensor::Tensor;
3962    /// use tenferro_tensor_core::{ErasedHostTensor, HostTensor};
3963    ///
3964    /// let payload = ErasedHostTensor::new(HostTensor::from_vec_col_major(vec![1], vec![1.0_f64])?);
3965    /// let tensor = Tensor::external(payload);
3966    /// assert!(tensor.external_payload().is_some());
3967    /// # Ok::<(), tenferro_tensor_core::ValidationError>(())
3968    /// ```
3969    #[must_use]
3970    pub fn external_payload(&self) -> Option<&tenferro_tensor_core::ErasedHostTensor> {
3971        match &self.payload {
3972            TensorPayload::External(payload, _) => Some(payload),
3973            TensorPayload::Native(_) => None,
3974        }
3975    }
3976
3977    /// Carry an externally defined payload with an explicit placement.
3978    ///
3979    /// [`Tensor::external`] defaults the placement to unpinned host memory, which is where
3980    /// a caller-owned payload normally lives; this entry point is for a caller that knows
3981    /// the placement it wants.
3982    ///
3983    /// # Examples
3984    ///
3985    /// ```
3986    /// use tenferro_tensor::{Placement, Tensor};
3987    /// use tenferro_tensor_core::{ErasedHostTensor, HostTensor};
3988    ///
3989    /// let payload = ErasedHostTensor::new(HostTensor::from_vec_col_major(vec![1], vec![1.0_f64])?);
3990    /// let tensor = Tensor::external_with_placement(payload, Placement::default());
3991    /// assert!(tensor.external_payload().is_some());
3992    /// # Ok::<(), tenferro_tensor_core::ValidationError>(())
3993    /// ```
3994    #[must_use]
3995    pub fn external_with_placement(
3996        payload: tenferro_tensor_core::ErasedHostTensor,
3997        placement: Placement,
3998    ) -> Self {
3999        Self {
4000            payload: TensorPayload::External(payload, placement),
4001        }
4002    }
4003
4004    /// Mutably borrow the erased payload of an externally defined tensor.
4005    ///
4006    /// The counterpart of [`Tensor::external_payload`] for callers that update the payload
4007    /// in place, such as a mutation test that checks the copy boundary.
4008    ///
4009    /// # Examples
4010    ///
4011    /// ```
4012    /// use tenferro_tensor::Tensor;
4013    /// use tenferro_tensor_core::{ErasedHostTensor, HostTensor};
4014    ///
4015    /// let mut tensor = Tensor::external(ErasedHostTensor::new(
4016    ///     HostTensor::from_vec_col_major(vec![1], vec![1.0_f64])?,
4017    /// ));
4018    /// assert!(tensor.external_payload_mut().is_some());
4019    /// # Ok::<(), tenferro_tensor_core::ValidationError>(())
4020    /// ```
4021    #[must_use]
4022    pub fn external_payload_mut(&mut self) -> Option<&mut tenferro_tensor_core::ErasedHostTensor> {
4023        match &mut self.payload {
4024            TensorPayload::External(payload, _) => Some(payload),
4025            TensorPayload::Native(_) => None,
4026        }
4027    }
4028
4029    pub(crate) fn into_group_parts(self) -> (AllocationGroup, DescriptorSlot) {
4030        match self.payload {
4031            TensorPayload::Native(core) => {
4032                let TensorCore {
4033                    group, placement, ..
4034                } = core;
4035                let (mut group, slot) = group.into_parts();
4036                // The group round trip rebuilds a tensor from the descriptor, so the
4037                // core copy has to reach the descriptor before it is dropped.
4038                group.publish_live_descriptor_placement(slot, placement);
4039                (group, slot)
4040            }
4041            // INVARIANT: a caller-owned payload has no allocation group.
4042            TensorPayload::External(..) => {
4043                unreachable!("an externally defined payload has no allocation group")
4044            }
4045        }
4046    }
4047}
4048
4049/// Dynamic read-only borrowed tensor view.
4050///
4051/// `TensorView` keeps dtype erased while borrowing typed view metadata and
4052/// storage. Use [`TypedTensorView`] directly when the scalar type is statically
4053/// known.
4054///
4055/// # Examples
4056///
4057/// ```
4058/// use tenferro_tensor::{DType, TensorView, TypedTensorView};
4059///
4060/// let data = [1_i32, 2, 3, 4];
4061/// let typed = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data)?;
4062/// let view = TensorView::I32(typed);
4063///
4064/// assert_eq!(view.dtype(), DType::I32);
4065/// assert_eq!(view.shape(), &[2, 2]);
4066/// # Ok::<(), tenferro_tensor::Error>(())
4067/// ```
4068#[derive(Clone, Debug)]
4069pub enum TensorView<'a> {
4070    F32(TypedTensorView<'a, f32>),
4071    F64(TypedTensorView<'a, f64>),
4072    I32(TypedTensorView<'a, i32>),
4073    I64(TypedTensorView<'a, i64>),
4074    Bool(TypedTensorView<'a, bool>),
4075    C32(TypedTensorView<'a, Complex<f32>>),
4076    C64(TypedTensorView<'a, Complex<f64>>),
4077}
4078
4079/// Dynamic mutable borrowed tensor view.
4080///
4081/// `TensorViewMut` is the mutable counterpart to [`TensorView`]. It keeps the
4082/// dtype erased while preserving the typed mutable view's shape, strides, and
4083/// offset metadata.
4084///
4085/// # Examples
4086///
4087/// ```
4088/// use tenferro_tensor::{DType, TensorViewMut, TypedTensorViewMut};
4089///
4090/// let mut data = [1.0_f64, 2.0];
4091/// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?);
4092/// assert_eq!(view.dtype(), DType::F64);
4093/// # Ok::<(), tenferro_tensor::Error>(())
4094/// ```
4095#[allow(clippy::large_enum_variant)]
4096#[derive(Debug)]
4097pub enum TensorViewMut<'a> {
4098    F32(TypedTensorViewMut<'a, f32>),
4099    F64(TypedTensorViewMut<'a, f64>),
4100    I32(TypedTensorViewMut<'a, i32>),
4101    I64(TypedTensorViewMut<'a, i64>),
4102    Bool(TypedTensorViewMut<'a, bool>),
4103    C32(TypedTensorViewMut<'a, Complex<f32>>),
4104    C64(TypedTensorViewMut<'a, Complex<f64>>),
4105}
4106
4107/// Read-only tensor input accepted by synchronous eager kernels.
4108///
4109/// `TensorRead` lets kernels accept either an owned tensor reference or a
4110/// borrowed [`TensorView`] without forcing callers to materialize first.
4111/// The `View` variant preserves arbitrary strides and offsets, so kernels that
4112/// support strided reads can consume transposes, slices, and broadcasts directly.
4113///
4114/// `TensorRead` is intentionally borrowed. It is an input-dispatch type, not an
4115/// owned lazy tensor value. APIs that need to store a lazy layout result should
4116/// keep an owned base tensor plus layout metadata, then expose a `TensorRead`
4117/// only for the duration of kernel dispatch.
4118///
4119/// # Examples
4120///
4121/// ```
4122/// use tenferro_tensor::{DType, Tensor, TensorRead};
4123///
4124/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
4125/// let read = TensorRead::from_tensor(&tensor);
4126///
4127/// assert_eq!(read.dtype(), DType::F64);
4128/// assert_eq!(read.shape(), &[2]);
4129/// ```
4130// Keep borrowed views inline to avoid allocation on read-only tensor dispatch paths.
4131#[allow(clippy::large_enum_variant)]
4132#[derive(Clone, Debug)]
4133pub enum TensorRead<'a> {
4134    Tensor(&'a Tensor),
4135    View(TensorView<'a>),
4136}
4137
4138/// Mutable typed tensor output accepted by synchronous eager kernels.
4139///
4140/// `TypedTensorWrite` is the typed counterpart to [`TensorWrite`]. It accepts
4141/// either an owned compact [`TypedTensor`] or an arbitrary-strided mutable
4142/// [`TypedTensorViewMut`] without erasing the scalar type at the public API
4143/// boundary.
4144///
4145/// # Examples
4146///
4147/// ```
4148/// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
4149///
4150/// let mut data = [0.0_f64, 1.0, 0.0, 2.0];
4151/// let view = TypedTensorViewMut::from_slice([2], [2], 1, &mut data)?;
4152/// let write = TypedTensorWrite::from_view(view).into_tensor_write();
4153/// assert_eq!(write.shape(), &[2]);
4154/// assert_eq!(write.strides()?, [2]);
4155/// # Ok::<(), tenferro_tensor::Error>(())
4156/// ```
4157#[allow(clippy::large_enum_variant)]
4158#[derive(Debug)]
4159pub enum TypedTensorWrite<'a, T> {
4160    /// An owned compact typed tensor borrowed mutably for the write.
4161    Tensor(&'a mut TypedTensor<T>),
4162    /// An arbitrary-strided mutable typed tensor view.
4163    View(TypedTensorViewMut<'a, T>),
4164}
4165
4166impl<'a, T> TypedTensorWrite<'a, T> {
4167    /// Create a writable target from an owned typed tensor.
4168    ///
4169    /// # Examples
4170    ///
4171    /// ```
4172    /// use tenferro_tensor::{TypedTensor, TypedTensorWrite};
4173    ///
4174    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0])?;
4175    /// let write = TypedTensorWrite::from_tensor(&mut tensor);
4176    /// assert!(matches!(write, TypedTensorWrite::Tensor(_)));
4177    /// # Ok::<(), tenferro_tensor::Error>(())
4178    /// ```
4179    pub fn from_tensor(tensor: &'a mut TypedTensor<T>) -> Self {
4180        Self::Tensor(tensor)
4181    }
4182
4183    /// Create a writable target from a mutable typed tensor view.
4184    ///
4185    /// # Examples
4186    ///
4187    /// ```
4188    /// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
4189    ///
4190    /// let mut data = [0.0_f64, 1.0];
4191    /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
4192    /// let write = TypedTensorWrite::from_view(view);
4193    /// assert!(matches!(write, TypedTensorWrite::View(_)));
4194    /// # Ok::<(), tenferro_tensor::Error>(())
4195    /// ```
4196    pub fn from_view(view: TypedTensorViewMut<'a, T>) -> Self {
4197        Self::View(view)
4198    }
4199}
4200
4201impl<'a, T: TensorScalar> TypedTensorWrite<'a, T> {
4202    /// Erase the scalar type while preserving the output layout.
4203    ///
4204    /// # Examples
4205    ///
4206    /// ```
4207    /// use tenferro_tensor::{DType, TypedTensor, TypedTensorWrite};
4208    ///
4209    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![0.0; 2])?;
4210    /// let write = TypedTensorWrite::from_tensor(&mut tensor).into_tensor_write();
4211    /// assert_eq!(write.dtype(), DType::F64);
4212    /// assert_eq!(write.shape(), &[2]);
4213    /// # Ok::<(), tenferro_tensor::Error>(())
4214    /// ```
4215    pub fn into_tensor_write(self) -> TensorWrite<'a> {
4216        match self {
4217            Self::Tensor(tensor) => T::tensor_write(tensor),
4218            Self::View(view) => TensorWrite::from_view(T::tensor_view_mut(view)),
4219        }
4220    }
4221}
4222
4223impl<'a, T> From<&'a mut TypedTensor<T>> for TypedTensorWrite<'a, T> {
4224    fn from(tensor: &'a mut TypedTensor<T>) -> Self {
4225        Self::from_tensor(tensor)
4226    }
4227}
4228
4229impl<'a, T> From<TypedTensorViewMut<'a, T>> for TypedTensorWrite<'a, T> {
4230    fn from(view: TypedTensorViewMut<'a, T>) -> Self {
4231        Self::from_view(view)
4232    }
4233}
4234
4235/// Mutable tensor output accepted by synchronous eager kernels.
4236///
4237/// `TensorWrite` mirrors [`TensorRead`] for output dispatch: it can target an
4238/// owned compact [`Tensor`] or a borrowed mutable [`TensorViewMut`]. The target
4239/// is never resized.
4240///
4241/// # Examples
4242///
4243/// ```
4244/// use tenferro_tensor::{Tensor, TensorWrite};
4245///
4246/// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
4247/// let write = TensorWrite::from_tensor(&mut tensor);
4248/// assert_eq!(write.shape(), &[1]);
4249/// # Ok::<(), tenferro_tensor::Error>(())
4250/// ```
4251#[allow(clippy::large_enum_variant)]
4252#[derive(Debug)]
4253pub enum TensorWrite<'a> {
4254    Tensor(&'a mut Tensor),
4255    View(TensorViewMut<'a>),
4256}
4257
4258/// Owned tensor value with one move-only physical owner and metadata-only layout.
4259///
4260/// `TensorValue` is intentionally not cloneable. View transformations consume
4261/// the value and move its existing owner; [`TensorValue::duplicate`] is the
4262/// explicit boundary for creating another physical allocation.
4263///
4264/// # Examples
4265///
4266/// ```
4267/// use tenferro_tensor::{Tensor, TensorValue};
4268/// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4269/// let view = value.transpose_view([1, 0])?;
4270/// assert_eq!(view.strides(), &[2, 1]);
4271/// assert!(view.is_view());
4272/// # Ok::<(), tenferro_tensor::Error>(())
4273/// ```
4274#[derive(Debug)]
4275pub struct TensorValue {
4276    owner: Tensor,
4277    layout: TensorLayout<DynRank>,
4278}
4279
4280/// A consuming view transformation failed while retaining its unchanged owner.
4281///
4282/// # Examples
4283///
4284/// ```
4285/// use tenferro_tensor::{Tensor, TensorValue};
4286/// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
4287/// let failure = value.try_reshape_view([3]).unwrap_err();
4288/// let (recovered, cause) = failure.into_parts();
4289/// assert_eq!(recovered.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
4290/// assert!(matches!(cause, tenferro_tensor::Error::Validation { .. }));
4291/// # Ok::<(), tenferro_tensor::Error>(())
4292/// ```
4293#[derive(Debug)]
4294pub struct TensorValueViewError {
4295    value: TensorValue,
4296    source: crate::Error,
4297}
4298
4299impl TensorValueViewError {
4300    /// Return the unchanged value and the typed validation/backend error.
4301    ///
4302    /// # Examples
4303    ///
4304    /// ```
4305    /// use tenferro_tensor::{Tensor, TensorValue};
4306    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([1], vec![7.])?);
4307    /// let (value, error) = value.try_reshape_view([2]).unwrap_err().into_parts();
4308    /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[7.]);
4309    /// assert!(matches!(error, tenferro_tensor::Error::Validation { .. }));
4310    /// # Ok::<(), tenferro_tensor::Error>(())
4311    /// ```
4312    pub fn into_parts(self) -> (TensorValue, crate::Error) {
4313        (self.value, self.source)
4314    }
4315
4316    fn new(value: TensorValue, source: crate::Error) -> Self {
4317        Self { value, source }
4318    }
4319}
4320
4321impl std::fmt::Display for TensorValueViewError {
4322    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4323        std::fmt::Display::fmt(&self.source, formatter)
4324    }
4325}
4326
4327impl std::error::Error for TensorValueViewError {
4328    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
4329        Some(&self.source)
4330    }
4331}
4332
4333impl TensorValue {
4334    /// Explicitly duplicate the physical owner represented by this value.
4335    ///
4336    /// # Examples
4337    ///
4338    /// ```
4339    /// use tenferro_tensor::{Tensor, TensorValue};
4340    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
4341    /// let copy = value.duplicate()?.into_tensor()?;
4342    /// assert_eq!(copy.as_slice::<f64>()?, &[3., 4.]);
4343    /// assert_eq!(value.shape(), &[2]);
4344    /// # Ok::<(), tenferro_tensor::Error>(())
4345    /// ```
4346    ///
4347    /// # Errors
4348    ///
4349    /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
4350    /// when the backend/storage owner cannot be duplicated.
4351    pub fn duplicate(&self) -> crate::Result<Self> {
4352        let tensor = self.owner.duplicate()?;
4353        Self::from_parts(
4354            tensor,
4355            self.shape().to_vec(),
4356            self.strides().to_vec(),
4357            self.offset(),
4358        )
4359    }
4360
4361    /// Retain a compact tensor as a move-only value.
4362    ///
4363    /// # Examples
4364    ///
4365    /// ```
4366    /// use tenferro_tensor::{Tensor, TensorValue};
4367    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
4368    /// assert!(!value.is_view());
4369    /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
4370    /// # Ok::<(), tenferro_tensor::Error>(())
4371    /// ```
4372    pub fn from_tensor(tensor: Tensor) -> Self {
4373        let layout = tensor_layout(&tensor);
4374        Self {
4375            owner: tensor,
4376            layout,
4377        }
4378    }
4379
4380    /// # Errors
4381    ///
4382    /// Returns [`ValidationError::InvalidArgument`] or
4383    /// [`ValidationError::IntegerOverflow`] when the supplied layout is
4384    /// invalid for the tensor's physical buffer.
4385    ///
4386    /// # Examples
4387    ///
4388    /// ```
4389    /// use tenferro_tensor::{Tensor, TensorValue};
4390    /// let tensor = Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?;
4391    /// let view = TensorValue::from_parts(tensor, vec![2], vec![1], 2)?;
4392    /// assert_eq!(view.shape(), &[2]);
4393    /// assert_eq!(view.offset(), 2);
4394    /// # Ok::<(), tenferro_tensor::Error>(())
4395    /// ```
4396    pub fn from_parts(
4397        tensor: Tensor,
4398        shape: Vec<usize>,
4399        strides: Vec<isize>,
4400        offset: isize,
4401    ) -> crate::Result<Self> {
4402        let layout = TensorLayout::from_parts(
4403            shape.into(),
4404            strides.into(),
4405            offset,
4406            tensor_buffer_len(&tensor),
4407        )
4408        .map_err(|err| tensor_layout_error("TensorValue::from_parts", err))?;
4409        Ok(Self {
4410            owner: tensor,
4411            layout,
4412        })
4413    }
4414
4415    // INVARIANT: unchanged-owner recovery is part of the consuming ownership
4416    // contract, so this intentionally carries the large move-only error.
4417    #[doc(hidden)]
4418    /// Move the value's sole physical owner into an allocation group.
4419    ///
4420    /// This preserves metadata-only views without copying. The consumed value
4421    /// always contains one unique owner, so no compatibility fallback exists.
4422    ///
4423    /// # Errors
4424    ///
4425    /// Returns the unchanged value when descriptor publication fails because
4426    /// of [`ValidationError::InvalidArgument`] or
4427    /// [`ValidationError::IntegerOverflow`].
4428    #[allow(clippy::result_large_err)]
4429    pub fn try_into_group_parts(
4430        self,
4431    ) -> std::result::Result<(AllocationGroup, DescriptorSlot, DType, Vec<usize>), Self> {
4432        let Self { owner, layout } = self;
4433        if owner.external_payload().is_some() {
4434            // A caller-owned payload owns no allocation group, so it is returned
4435            // unchanged instead of being forced into one.
4436            return Err(Self { owner, layout });
4437        }
4438        let dtype = owner.dtype();
4439        let shape = layout.shape().to_vec();
4440        let strides = layout.strides().to_vec();
4441        let offset = layout.offset();
4442        let (group, slot) = owner.into_group_parts();
4443        match group.update_descriptor_layout(slot, shape, strides, offset) {
4444            Ok(group) => Ok((group, slot, dtype, layout.shape().to_vec())),
4445            Err((_group, _error)) => {
4446                unreachable!("TensorValue layout was validated before group ownership transfer")
4447            }
4448        }
4449    }
4450
4451    /// Consume a value with its owner's original layout and return that owner.
4452    ///
4453    /// # Examples
4454    ///
4455    /// ```
4456    /// use tenferro_tensor::{Tensor, TensorValue};
4457    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
4458    /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
4459    /// # Ok::<(), tenferro_tensor::Error>(())
4460    /// ```
4461    ///
4462    /// # Errors
4463    ///
4464    /// Returns [`crate::Error::Unsupported`] when the value's metadata-only
4465    /// view differs from its physical owner's layout.
4466    pub fn into_tensor(self) -> crate::Result<Tensor> {
4467        if self.layout != tensor_layout(&self.owner) {
4468            return Err(crate::Error::unsupported(
4469                "TensorValue::into_tensor",
4470                "a metadata-only view has no compact tensor owner",
4471            ));
4472        }
4473        Ok(self.owner)
4474    }
4475
4476    ///
4477    /// # Examples
4478    ///
4479    /// ```
4480    /// use tenferro_tensor::{Tensor, TensorValue};
4481    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4482    /// assert_eq!(value.as_tensor().unwrap().as_slice::<f64>()?, &[1., 2., 3., 4.]);
4483    /// assert!(value.transpose_view([1, 0])?.as_tensor().is_none());
4484    /// # Ok::<(), tenferro_tensor::Error>(())
4485    /// ```
4486    pub fn as_tensor(&self) -> Option<&Tensor> {
4487        (self.layout == tensor_layout(&self.owner)).then_some(&self.owner)
4488    }
4489
4490    ///
4491    /// # Examples
4492    ///
4493    /// ```
4494    /// use tenferro_tensor::{Tensor, TensorValue};
4495    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4496    /// assert!(!value.is_view());
4497    /// assert!(value.transpose_view([1, 0])?.is_view());
4498    /// # Ok::<(), tenferro_tensor::Error>(())
4499    /// ```
4500    pub fn is_view(&self) -> bool {
4501        self.as_tensor().is_none()
4502    }
4503
4504    ///
4505    /// # Examples
4506    ///
4507    /// ```
4508    /// use tenferro_tensor::{Tensor, TensorValue};
4509    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4510    /// assert_eq!(value.dtype(), tenferro_tensor::DType::F64);
4511    /// # Ok::<(), tenferro_tensor::Error>(())
4512    /// ```
4513    pub fn dtype(&self) -> DType {
4514        self.owner.dtype()
4515    }
4516
4517    ///
4518    /// # Examples
4519    ///
4520    /// ```
4521    /// use tenferro_tensor::{Tensor, TensorValue};
4522    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4523    /// assert_eq!(value.reshape_view([4])?.shape(), &[4]);
4524    /// # Ok::<(), tenferro_tensor::Error>(())
4525    /// ```
4526    pub fn shape(&self) -> &[usize] {
4527        self.layout.shape()
4528    }
4529
4530    ///
4531    /// # Examples
4532    ///
4533    /// ```
4534    /// use tenferro_tensor::{Tensor, TensorValue};
4535    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4536    /// assert_eq!(value.transpose_view([1, 0])?.strides(), &[2, 1]);
4537    /// # Ok::<(), tenferro_tensor::Error>(())
4538    /// ```
4539    pub fn strides(&self) -> &[isize] {
4540        self.layout.strides()
4541    }
4542
4543    ///
4544    /// # Examples
4545    ///
4546    /// ```
4547    /// use tenferro_tensor::{Tensor, TensorValue};
4548    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4549    /// assert_eq!(value.offset(), 0);
4550    /// # Ok::<(), tenferro_tensor::Error>(())
4551    /// ```
4552    pub fn offset(&self) -> isize {
4553        self.layout.offset()
4554    }
4555
4556    ///
4557    /// # Examples
4558    ///
4559    /// ```
4560    /// use tenferro_tensor::{Tensor, TensorValue};
4561    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4562    /// let view = value.tensor_view();
4563    /// assert_eq!(view.shape(), &[2, 2]);
4564    /// assert_eq!(view.dtype(), tenferro_tensor::DType::F64);
4565    /// # Ok::<(), tenferro_tensor::Error>(())
4566    /// ```
4567    pub fn tensor_view(&self) -> TensorView<'_> {
4568        tensor_view_with_layout(&self.owner, self.layout.clone())
4569    }
4570
4571    ///
4572    /// # Examples
4573    ///
4574    /// ```
4575    /// use tenferro_tensor::{Tensor, TensorValue};
4576    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4577    /// let read = value.tensor_read();
4578    /// assert_eq!(read.shape(), &[2, 2]);
4579    /// assert_eq!(read.dtype(), tenferro_tensor::DType::F64);
4580    /// # Ok::<(), tenferro_tensor::Error>(())
4581    /// ```
4582    pub fn tensor_read(&self) -> TensorRead<'_> {
4583        self.as_tensor()
4584            .map(TensorRead::from_tensor)
4585            .unwrap_or_else(|| TensorRead::from_view(self.tensor_view()))
4586    }
4587
4588    /// # Errors
4589    ///
4590    /// Returns [`crate::Error::Validation`] with
4591    /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
4592    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
4593    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
4594    /// not a valid permutation of the value rank.
4595    ///
4596    /// # Examples
4597    ///
4598    /// ```
4599    /// use tenferro_tensor::{Tensor, TensorValue};
4600    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4601    /// let view = value.transpose_view([1, 0])?;
4602    /// assert_eq!(view.strides(), &[2, 1]);
4603    /// assert!(view.is_view());
4604    /// # Ok::<(), tenferro_tensor::Error>(())
4605    /// ```
4606    pub fn transpose_view(self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
4607        let layout = self
4608            .layout
4609            .transpose_view(axes)
4610            .map_err(|err| tensor_layout_error("TensorValue::transpose_view", err))?;
4611        Ok(Self {
4612            owner: self.owner,
4613            layout,
4614        })
4615    }
4616
4617    // INVARIANT: unchanged-owner recovery is part of the consuming view
4618    // contract, so this intentionally carries the large move-only error.
4619    /// # Errors
4620    ///
4621    /// Returns [`crate::Error::Validation`] with
4622    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
4623    /// the source is not compact column-major,
4624    /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
4625    /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
4626    /// records the counts) when element counts differ,
4627    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
4628    /// arithmetic overflow, or
4629    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4630    /// reshaped view exceeds the backing buffer.
4631    #[allow(clippy::result_large_err)]
4632    ///
4633    /// # Examples
4634    ///
4635    /// ```
4636    /// use tenferro_tensor::{Tensor, TensorValue};
4637    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4638    /// let (recovered, error) = value.try_reshape_view([3]).unwrap_err().into_parts();
4639    /// assert_eq!(recovered.into_tensor()?.as_slice::<f64>()?, &[1., 2., 3., 4.]);
4640    /// assert!(matches!(error, tenferro_tensor::Error::Validation { .. }));
4641    /// # Ok::<(), tenferro_tensor::Error>(())
4642    /// ```
4643    pub fn try_reshape_view(
4644        self,
4645        shape: impl tenferro_tensor_core::IntoShapeVec,
4646    ) -> std::result::Result<Self, TensorValueViewError> {
4647        let shape = shape.into_shape_vec();
4648        let layout = match reshape_layout_dyn(
4649            &self.layout,
4650            &shape,
4651            tensor_buffer_len(&self.owner),
4652            "TensorValue::reshape_view",
4653        ) {
4654            Ok(layout) => layout,
4655            Err(error) => return Err(TensorValueViewError::new(self, error)),
4656        };
4657        Ok(Self {
4658            owner: self.owner,
4659            layout,
4660        })
4661    }
4662
4663    /// # Errors
4664    ///
4665    /// Returns [`crate::Error::Validation`] with
4666    /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]
4667    /// when the source is not compact, [`tenferro_tensor_core::ValidationError::ShapeMismatch`]
4668    /// when element counts differ, or [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
4669    /// / [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] for invalid
4670    /// target-shape arithmetic or bounds.
4671    ///
4672    /// # Examples
4673    ///
4674    /// ```
4675    /// use tenferro_tensor::{Tensor, TensorValue};
4676    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4677    /// let view = value.reshape_view([4])?;
4678    /// assert_eq!(view.shape(), &[4]);
4679    /// assert_eq!(view.strides(), &[1]);
4680    /// # Ok::<(), tenferro_tensor::Error>(())
4681    /// ```
4682    pub fn reshape_view(
4683        self,
4684        shape: impl tenferro_tensor_core::IntoShapeVec,
4685    ) -> crate::Result<Self> {
4686        self.try_reshape_view(shape).map_err(|error| error.source)
4687    }
4688
4689    /// # Errors
4690    ///
4691    /// Returns [`crate::Error::Validation`] with
4692    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a slice
4693    /// vector does not match the value rank,
4694    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when a bound
4695    /// or stride cannot be represented or is invalid,
4696    /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
4697    /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for slice
4698    /// parameters, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
4699    /// for slice arithmetic overflow, or
4700    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4701    /// result exceeds the backing buffer.
4702    ///
4703    /// # Examples
4704    ///
4705    /// ```
4706    /// use tenferro_tensor::{Tensor, TensorValue};
4707    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
4708    /// let view = value.slice_view(&tenferro_tensor::SliceConfig {
4709    ///    starts: vec![0, 1], limits: vec![2, 2], strides: vec![1, 1],
4710    /// })?;
4711    /// assert_eq!(view.shape(), &[2, 1]);
4712    /// assert_eq!(view.offset(), 2);
4713    /// # Ok::<(), tenferro_tensor::Error>(())
4714    /// ```
4715    pub fn slice_view(self, config: &SliceConfig) -> crate::Result<Self> {
4716        let op = "TensorValue::slice_view";
4717        if config.starts.len() != self.shape().len()
4718            || config.limits.len() != self.shape().len()
4719            || config.strides.len() != self.shape().len()
4720        {
4721            return Err(crate::Error::validation(
4722                op,
4723                ValidationError::RankMismatch {
4724                    expected: self.shape().len(),
4725                    actual: config.starts.len(),
4726                },
4727            ));
4728        }
4729        let mut slices = Vec::with_capacity(self.shape().len());
4730        for ((&start, &limit), &stride) in config
4731            .starts
4732            .iter()
4733            .zip(config.limits.iter())
4734            .zip(config.strides.iter())
4735        {
4736            let start = isize::try_from(start).map_err(|_| {
4737                crate::Error::invalid_argument(
4738                    op,
4739                    "slice start",
4740                    "slice start does not fit in isize",
4741                )
4742            })?;
4743            let limit = isize::try_from(limit).map_err(|_| {
4744                crate::Error::invalid_argument(
4745                    op,
4746                    "slice limit",
4747                    "slice limit does not fit in isize",
4748                )
4749            })?;
4750            let stride = isize::try_from(stride).map_err(|_| {
4751                crate::Error::invalid_argument(
4752                    op,
4753                    "slice stride",
4754                    "slice stride does not fit in isize",
4755                )
4756            })?;
4757            slices.push(StridedSliceSpec::new(start, Some(limit), stride));
4758        }
4759        let specs = core_slice_specs(&slices, self.shape(), op)?;
4760        let layout = self
4761            .layout
4762            .slice_view(&specs, tensor_buffer_len(&self.owner))
4763            .map_err(|err| tensor_layout_error(op, err))?;
4764        Ok(Self {
4765            owner: self.owner,
4766            layout,
4767        })
4768    }
4769
4770    /// # Errors
4771    ///
4772    /// Returns [`crate::Error::Validation`] with
4773    /// [`tenferro_tensor_core::ValidationError::RankMismatch`],
4774    /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
4775    /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] for invalid
4776    /// dimension mappings,
4777    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] for
4778    /// incompatible extents,
4779    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4780    /// result exceeds the backing buffer, or
4781    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4782    /// arithmetic overflow.
4783    ///
4784    /// # Examples
4785    ///
4786    /// ```
4787    /// use tenferro_tensor::{Tensor, TensorValue};
4788    /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
4789    /// let view = value.broadcast_in_dim_view([2, 3], [0])?;
4790    /// assert_eq!(view.shape(), &[2, 3]);
4791    /// assert_eq!(view.strides(), &[1, 0]);
4792    /// # Ok::<(), tenferro_tensor::Error>(())
4793    /// ```
4794    pub fn broadcast_in_dim_view(
4795        self,
4796        shape: impl tenferro_tensor_core::IntoShapeVec,
4797        dims: impl AsRef<[usize]>,
4798    ) -> crate::Result<Self> {
4799        let shape = shape.into_shape_vec();
4800        let layout = self
4801            .layout
4802            .broadcast_in_dim_view::<DynRank>(shape, dims, tensor_buffer_len(&self.owner))
4803            .map_err(|err| tensor_layout_error("TensorValue::broadcast_in_dim_view", err))?;
4804        Ok(Self {
4805            owner: self.owner,
4806            layout,
4807        })
4808    }
4809}
4810
4811fn tensor_layout(tensor: &Tensor) -> TensorLayout<DynRank> {
4812    match &tensor.payload {
4813        // A caller-owned payload carries its own view layout, so strides and offset
4814        // come from the payload rather than from a compact assumption.
4815        TensorPayload::External(payload, _) => TensorLayout::<DynRank>::from_parts(
4816            tenferro_tensor_core::ShapeVec::from_slice(payload.shape()),
4817            tenferro_tensor_core::StrideVec::from_slice(payload.strides()),
4818            payload.offset(),
4819            payload.payload_element_count(),
4820        )
4821        .unwrap_or_else(|_| {
4822            // INVARIANT: a payload is built from a validated host tensor, and the
4823            // only layout change is a permutation, so its strides stay inside the
4824            // payload it was created from and this construction cannot fail.
4825            unreachable!("a validated payload yields a layout inside its own storage")
4826        }),
4827        TensorPayload::Native(core) => core.layout.clone(),
4828    }
4829}
4830
4831fn tensor_buffer_len(tensor: &Tensor) -> usize {
4832    match &tensor.payload {
4833        // A caller-owned payload stores its element count directly.
4834        TensorPayload::External(payload, _) => payload.element_count(),
4835        TensorPayload::Native(core) => core.buffer_len(),
4836    }
4837}
4838
4839fn prepare_backend_access<'a, T: 'static, R: TensorRank>(
4840    buffer: &'a dyn BackendStorage<T>,
4841    layout: &'a TensorLayout<R>,
4842    op: &'static str,
4843) -> crate::Result<Box<dyn PreparedDeviceAccess + 'a>> {
4844    let domain = buffer.allocation_domain().ok_or_else(|| {
4845        crate::Error::runtime_state(op, "backend buffer is missing an allocation domain")
4846    })?;
4847    let allocation_id = buffer.allocation_id().ok_or_else(|| {
4848        crate::Error::runtime_state(op, "backend buffer is missing an allocation identity")
4849    })?;
4850    let byte_len = buffer
4851        .len()
4852        .checked_mul(size_of::<T>())
4853        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
4854    let request = DeviceAccessRequest::new(
4855        domain,
4856        allocation_id,
4857        byte_len,
4858        size_of::<T>(),
4859        layout.shape(),
4860        layout.strides(),
4861        layout.offset(),
4862    );
4863    buffer
4864        .prepare_device_access(request)
4865        .map_err(|error| crate::Error::runtime_state_source(op, error))
4866}
4867
4868fn cast_view_slice<S: 'static, T: TensorScalar>(source: &[S]) -> crate::Result<&[T]> {
4869    if size_of::<S>() != size_of::<T>() || align_of::<S>() != align_of::<T>() {
4870        return Err(crate::Error::invalid_argument(
4871            "TensorView::as_slice",
4872            "dtype",
4873            "matching dtypes must have identical scalar layout",
4874        ));
4875    }
4876    // SAFETY: the dtype check above is exhaustive over the sealed scalar set;
4877    // equal size/alignment preserve the element boundaries and the source
4878    // slice remains borrowed for the returned lifetime.
4879    Ok(unsafe { std::slice::from_raw_parts(source.as_ptr().cast::<T>(), source.len()) })
4880}
4881
4882fn tensor_view_with_layout(tensor: &Tensor, layout: TensorLayout<DynRank>) -> TensorView<'_> {
4883    match tensor.dtype() {
4884        DType::F32 => TensorView::F32(typed_view_with_layout(
4885            tensor
4886                .as_typed::<f32>()
4887                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
4888            layout,
4889        )),
4890        DType::F64 => TensorView::F64(typed_view_with_layout(
4891            tensor
4892                .as_typed::<f64>()
4893                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
4894            layout,
4895        )),
4896        DType::I32 => TensorView::I32(typed_view_with_layout(
4897            tensor
4898                .as_typed::<i32>()
4899                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
4900            layout,
4901        )),
4902        DType::I64 => TensorView::I64(typed_view_with_layout(
4903            tensor
4904                .as_typed::<i64>()
4905                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
4906            layout,
4907        )),
4908        DType::Bool => TensorView::Bool(typed_view_with_layout(
4909            tensor
4910                .as_typed::<bool>()
4911                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
4912            layout,
4913        )),
4914        DType::C32 => TensorView::C32(typed_view_with_layout(
4915            tensor
4916                .as_typed::<Complex<f32>>()
4917                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
4918            layout,
4919        )),
4920        DType::C64 => TensorView::C64(typed_view_with_layout(
4921            tensor
4922                .as_typed::<Complex<f64>>()
4923                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
4924            layout,
4925        )),
4926        // INVARIANT: `TensorView` has no externally defined variant, and a view is
4927        // never requested for a caller-owned payload.
4928        DType::External(_) => unreachable!("views cover the preset scalars"),
4929    }
4930}
4931
4932fn typed_view_with_layout<T: TensorScalar + 'static>(
4933    tensor: &TypedTensor<T>,
4934    layout: TensorLayout<DynRank>,
4935) -> TypedTensorView<'_, T> {
4936    let root = match tensor.core.group.view::<T>() {
4937        Ok(root) => root,
4938        Err(error) => unreachable!("typed tensor group descriptor mismatch: {error}"),
4939    };
4940    let buffer = if let Some(allocation) = root.backend_allocation() {
4941        TensorStorageRef::Root(allocation)
4942    } else {
4943        TensorStorageRef::Host(tensor.group_host_slice())
4944    };
4945    TypedTensorView {
4946        buffer,
4947        root: Some(root),
4948        layout,
4949        placement: tensor.core.placement.clone(),
4950    }
4951}
4952
4953pub(crate) fn tensor_view_from_group<'a, T: TensorScalar>(
4954    view: GroupReadView<'a, T, DynRank>,
4955) -> crate::Result<TensorView<'a>> {
4956    let buffer = if let Some(allocation) = view.backend_allocation() {
4957        TensorStorageRef::Root(allocation)
4958    } else {
4959        let storage = view.storage_buffer().ok_or_else(|| {
4960            crate::Error::runtime_state(
4961                "AllocationGroup::tensor_read",
4962                "group descriptor has no backing storage",
4963            )
4964        })?;
4965        match storage {
4966            StorageBuffer::Host(data) => TensorStorageRef::Host(data),
4967            StorageBuffer::Backend(buffer) => TensorStorageRef::Backend(buffer.as_ref()),
4968        }
4969    };
4970    let layout = view.descriptor().layout().clone();
4971    let placement = view.descriptor().placement().clone();
4972    let typed = TypedTensorView {
4973        buffer,
4974        root: Some(view.clone()),
4975        layout,
4976        placement,
4977    };
4978    Ok(T::tensor_view(typed))
4979}
4980
4981pub(crate) fn tensor_from_group(
4982    group: AllocationGroup,
4983    slot: DescriptorSlot,
4984    allocation_index: AllocationSlot,
4985    dtype: DType,
4986    layout: TensorLayout<DynRank>,
4987    placement: Placement,
4988) -> Tensor {
4989    fn typed<T: TensorScalar>(
4990        group: AllocationGroup,
4991        slot: DescriptorSlot,
4992        allocation_index: AllocationSlot,
4993        layout: TensorLayout<DynRank>,
4994        placement: Placement,
4995    ) -> TypedTensor<T> {
4996        let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
4997        TypedTensor {
4998            core: TensorCore {
4999                group: OwnedTensorGroup {
5000                    group,
5001                    slot,
5002                    allocation_index,
5003                    host_ptr,
5004                    host_byte_len,
5005                    _rank: PhantomData,
5006                },
5007                layout,
5008                placement,
5009            },
5010            _scalar: PhantomData,
5011        }
5012    }
5013
5014    match dtype {
5015        DType::F32 => {
5016            Tensor::from_core(typed::<f32>(group, slot, allocation_index, layout, placement).core)
5017        }
5018        DType::F64 => {
5019            Tensor::from_core(typed::<f64>(group, slot, allocation_index, layout, placement).core)
5020        }
5021        DType::I32 => {
5022            Tensor::from_core(typed::<i32>(group, slot, allocation_index, layout, placement).core)
5023        }
5024        DType::I64 => {
5025            Tensor::from_core(typed::<i64>(group, slot, allocation_index, layout, placement).core)
5026        }
5027        DType::Bool => {
5028            Tensor::from_core(typed::<bool>(group, slot, allocation_index, layout, placement).core)
5029        }
5030        DType::C32 => Tensor::from_core(
5031            typed::<Complex<f32>>(group, slot, allocation_index, layout, placement).core,
5032        ),
5033        DType::C64 => Tensor::from_core(
5034            typed::<Complex<f64>>(group, slot, allocation_index, layout, placement).core,
5035        ),
5036        // INVARIANT: an allocation group is created from a sealed preset scalar,
5037        // so it can never carry an externally defined one. External payloads are
5038        // caller-owned and do not enter a group.
5039        DType::External(_) => unreachable!("allocation groups are preset-typed"),
5040    }
5041}
5042
5043/// Wrap an `f64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
5044///
5045/// # Examples
5046///
5047/// ```
5048/// use tenferro_tensor::{Tensor, TypedTensor};
5049///
5050/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
5051/// let tensor: Tensor = typed.into();
5052/// assert_eq!(tensor.shape(), &[2]);
5053/// ```
5054impl From<TypedTensor<f64>> for Tensor {
5055    fn from(t: TypedTensor<f64>) -> Self {
5056        Tensor::from_core(t.core)
5057    }
5058}
5059
5060/// Wrap an `f32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
5061///
5062/// # Examples
5063///
5064/// ```
5065/// use tenferro_tensor::{Tensor, TypedTensor};
5066///
5067/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
5068/// let tensor: Tensor = typed.into();
5069/// assert_eq!(tensor.shape(), &[2]);
5070/// ```
5071impl From<TypedTensor<f32>> for Tensor {
5072    fn from(t: TypedTensor<f32>) -> Self {
5073        Tensor::from_core(t.core)
5074    }
5075}
5076
5077/// Wrap an `i64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
5078///
5079/// # Examples
5080///
5081/// ```
5082/// use tenferro_tensor::{DType, Tensor, TypedTensor};
5083///
5084/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i64, 2]).unwrap();
5085/// let tensor: Tensor = typed.into();
5086/// assert_eq!(tensor.dtype(), DType::I64);
5087/// assert_eq!(tensor.shape(), &[2]);
5088/// ```
5089impl From<TypedTensor<i64>> for Tensor {
5090    fn from(t: TypedTensor<i64>) -> Self {
5091        Tensor::from_core(t.core)
5092    }
5093}
5094
5095/// Wrap an `i32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
5096///
5097/// # Examples
5098///
5099/// ```
5100/// use tenferro_tensor::{DType, Tensor, TypedTensor};
5101///
5102/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i32, 2]).unwrap();
5103/// let tensor: Tensor = typed.into();
5104/// assert_eq!(tensor.dtype(), DType::I32);
5105/// assert_eq!(tensor.shape(), &[2]);
5106/// ```
5107impl From<TypedTensor<i32>> for Tensor {
5108    fn from(t: TypedTensor<i32>) -> Self {
5109        Tensor::from_core(t.core)
5110    }
5111}
5112
5113/// Wrap a `bool` [`TypedTensor`] into the corresponding [`Tensor`] variant.
5114///
5115/// # Examples
5116///
5117/// ```
5118/// use tenferro_tensor::{DType, Tensor, TypedTensor};
5119///
5120/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![true, false]).unwrap();
5121/// let tensor: Tensor = typed.into();
5122/// assert_eq!(tensor.dtype(), DType::Bool);
5123/// assert_eq!(tensor.shape(), &[2]);
5124/// ```
5125impl From<TypedTensor<bool>> for Tensor {
5126    fn from(t: TypedTensor<bool>) -> Self {
5127        Tensor::from_core(t.core)
5128    }
5129}
5130
5131/// Wrap a [`Complex64`] [`TypedTensor`] into the corresponding [`Tensor`]
5132/// variant.
5133///
5134/// # Examples
5135///
5136/// ```
5137/// use num_complex::Complex64;
5138/// use tenferro_tensor::{Tensor, TypedTensor};
5139///
5140/// let typed = TypedTensor::from_vec_col_major(
5141///     vec![1],
5142///     vec![Complex64::new(1.0, 2.0)],
5143/// ).unwrap();
5144/// let tensor: Tensor = typed.into();
5145/// assert_eq!(tensor.shape(), &[1]);
5146/// ```
5147impl From<TypedTensor<Complex<f64>>> for Tensor {
5148    fn from(t: TypedTensor<Complex<f64>>) -> Self {
5149        Tensor::from_core(t.core)
5150    }
5151}
5152
5153/// Wrap a [`Complex32`] [`TypedTensor`] into the corresponding [`Tensor`]
5154/// variant.
5155///
5156/// # Examples
5157///
5158/// ```
5159/// use num_complex::Complex32;
5160/// use tenferro_tensor::{Tensor, TypedTensor};
5161///
5162/// let typed = TypedTensor::from_vec_col_major(
5163///     vec![1],
5164///     vec![Complex32::new(1.0, 2.0)],
5165/// ).unwrap();
5166/// let tensor: Tensor = typed.into();
5167/// assert_eq!(tensor.shape(), &[1]);
5168/// ```
5169impl From<TypedTensor<Complex<f32>>> for Tensor {
5170    fn from(t: TypedTensor<Complex<f32>>) -> Self {
5171        Tensor::from_core(t.core)
5172    }
5173}
5174
5175impl<'a> TensorView<'a> {
5176    /// Create a dynamic `f32` view over compact column-major host data.
5177    ///
5178    /// # Examples
5179    ///
5180    /// ```
5181    /// use tenferro_tensor::{DType, TensorView};
5182    ///
5183    /// let data = [1.0_f32, 2.0];
5184    /// let view = TensorView::f32(&[2], &data)?;
5185    /// assert_eq!(view.dtype(), DType::F32);
5186    /// # Ok::<(), tenferro_tensor::Error>(())
5187    /// ```
5188    /// # Errors
5189    ///
5190    /// Returns [`crate::Error::Validation`] with
5191    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
5192    /// shape or offset arithmetic overflow, or
5193    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5194    /// compact shape reaches beyond `data`.
5195    pub fn f32(shape: &'a [usize], data: &'a [f32]) -> crate::Result<Self> {
5196        Ok(Self::F32(TypedTensorView::from_col_major(shape, data)?))
5197    }
5198
5199    /// Create a dynamic `f64` view over compact column-major host data.
5200    ///
5201    /// # Examples
5202    ///
5203    /// ```
5204    /// use tenferro_tensor::{DType, TensorView};
5205    ///
5206    /// let data = [1.0_f64, 2.0];
5207    /// let view = TensorView::f64(&[2], &data)?;
5208    /// assert_eq!(view.dtype(), DType::F64);
5209    /// # Ok::<(), tenferro_tensor::Error>(())
5210    /// ```
5211    /// # Errors
5212    ///
5213    /// Returns [`crate::Error::Validation`] with
5214    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
5215    /// shape or offset arithmetic overflow, or
5216    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5217    /// compact shape reaches beyond `data`.
5218    pub fn f64(shape: &'a [usize], data: &'a [f64]) -> crate::Result<Self> {
5219        Ok(Self::F64(TypedTensorView::from_col_major(shape, data)?))
5220    }
5221
5222    /// Create a dynamic `i64` view over compact column-major host data.
5223    ///
5224    /// # Examples
5225    ///
5226    /// ```
5227    /// use tenferro_tensor::{DType, TensorView};
5228    ///
5229    /// let data = [1_i64, 2];
5230    /// let view = TensorView::i64(&[2], &data)?;
5231    /// assert_eq!(view.dtype(), DType::I64);
5232    /// # Ok::<(), tenferro_tensor::Error>(())
5233    /// ```
5234    /// # Errors
5235    ///
5236    /// Returns [`crate::Error::Validation`] with
5237    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
5238    /// shape or offset arithmetic overflow, or
5239    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5240    /// compact shape reaches beyond `data`.
5241    pub fn i64(shape: &'a [usize], data: &'a [i64]) -> crate::Result<Self> {
5242        Ok(Self::I64(TypedTensorView::from_col_major(shape, data)?))
5243    }
5244
5245    /// Create a dynamic `i32` view over compact column-major host data.
5246    ///
5247    /// # Examples
5248    ///
5249    /// ```
5250    /// use tenferro_tensor::{DType, TensorView};
5251    ///
5252    /// let data = [1_i32, 2];
5253    /// let view = TensorView::i32(&[2], &data)?;
5254    /// assert_eq!(view.dtype(), DType::I32);
5255    /// # Ok::<(), tenferro_tensor::Error>(())
5256    /// ```
5257    /// # Errors
5258    ///
5259    /// Returns [`crate::Error::Validation`] with
5260    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
5261    /// shape or offset arithmetic overflow, or
5262    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5263    /// compact shape reaches beyond `data`.
5264    pub fn i32(shape: &'a [usize], data: &'a [i32]) -> crate::Result<Self> {
5265        Ok(Self::I32(TypedTensorView::from_col_major(shape, data)?))
5266    }
5267
5268    /// Create a dynamic `bool` view over compact column-major host data.
5269    ///
5270    /// # Examples
5271    ///
5272    /// ```
5273    /// use tenferro_tensor::{DType, TensorView};
5274    ///
5275    /// let data = [true, false];
5276    /// let view = TensorView::bool(&[2], &data)?;
5277    /// assert_eq!(view.dtype(), DType::Bool);
5278    /// # Ok::<(), tenferro_tensor::Error>(())
5279    /// ```
5280    /// # Errors
5281    ///
5282    /// Returns [`crate::Error::Validation`] with
5283    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
5284    /// shape or offset arithmetic overflow, or
5285    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5286    /// compact shape reaches beyond `data`.
5287    pub fn bool(shape: &'a [usize], data: &'a [bool]) -> crate::Result<Self> {
5288        Ok(Self::Bool(TypedTensorView::from_col_major(shape, data)?))
5289    }
5290
5291    /// Create a dynamic `Complex32` view over compact column-major host data.
5292    ///
5293    /// # Examples
5294    ///
5295    /// ```
5296    /// use num_complex::Complex32;
5297    /// use tenferro_tensor::{DType, TensorView};
5298    ///
5299    /// let data = [Complex32::new(1.0, 2.0)];
5300    /// let view = TensorView::c32(&[1], &data)?;
5301    /// assert_eq!(view.dtype(), DType::C32);
5302    /// # Ok::<(), tenferro_tensor::Error>(())
5303    /// ```
5304    /// # Errors
5305    ///
5306    /// Returns [`crate::Error::Validation`] with
5307    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
5308    /// shape or offset arithmetic overflow, or
5309    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5310    /// compact shape reaches beyond `data`.
5311    pub fn c32(shape: &'a [usize], data: &'a [Complex32]) -> crate::Result<Self> {
5312        Ok(Self::C32(TypedTensorView::from_col_major(shape, data)?))
5313    }
5314
5315    /// Create a dynamic `Complex64` view over compact column-major host data.
5316    ///
5317    /// # Examples
5318    ///
5319    /// ```
5320    /// use num_complex::Complex64;
5321    /// use tenferro_tensor::{DType, TensorView};
5322    ///
5323    /// let data = [Complex64::new(1.0, 2.0)];
5324    /// let view = TensorView::c64(&[1], &data)?;
5325    /// assert_eq!(view.dtype(), DType::C64);
5326    /// # Ok::<(), tenferro_tensor::Error>(())
5327    /// ```
5328    /// # Errors
5329    ///
5330    /// Returns [`crate::Error::Validation`] with
5331    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
5332    /// shape or offset arithmetic overflow, or
5333    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5334    /// compact shape reaches beyond `data`.
5335    pub fn c64(shape: &'a [usize], data: &'a [Complex64]) -> crate::Result<Self> {
5336        Ok(Self::C64(TypedTensorView::from_col_major(shape, data)?))
5337    }
5338
5339    pub fn dtype(&self) -> DType {
5340        match self {
5341            Self::F32(_) => DType::F32,
5342            Self::F64(_) => DType::F64,
5343            Self::I32(_) => DType::I32,
5344            Self::I64(_) => DType::I64,
5345            Self::Bool(_) => DType::Bool,
5346            Self::C32(_) => DType::C32,
5347            Self::C64(_) => DType::C64,
5348        }
5349    }
5350
5351    pub fn shape(&self) -> &[usize] {
5352        match self {
5353            Self::F32(t) => t.shape(),
5354            Self::F64(t) => t.shape(),
5355            Self::I32(t) => t.shape(),
5356            Self::I64(t) => t.shape(),
5357            Self::Bool(t) => t.shape(),
5358            Self::C32(t) => t.shape(),
5359            Self::C64(t) => t.shape(),
5360        }
5361    }
5362
5363    /// Borrow a contiguous host slice when the requested scalar matches this view's dtype.
5364    ///
5365    /// Backend buffers and non-contiguous views return an explicit error. No
5366    /// download or materialization is performed.
5367    ///
5368    /// # Errors
5369    ///
5370    /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
5371    /// view dtype, [`ValidationError::NonContiguousViewAsSlice`] for a
5372    /// non-contiguous layout, or [`crate::Error::HostAccess`] for unavailable
5373    /// backend host access.
5374    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
5375        if self.dtype() != T::dtype() {
5376            return Err(crate::Error::validation(
5377                "TensorView::as_slice",
5378                ValidationError::DTypeMismatch {
5379                    expected: T::dtype(),
5380                    actual: self.dtype(),
5381                },
5382            ));
5383        }
5384        match self {
5385            Self::F32(view) => cast_view_slice(view.as_slice()?),
5386            Self::F64(view) => cast_view_slice(view.as_slice()?),
5387            Self::I32(view) => cast_view_slice(view.as_slice()?),
5388            Self::I64(view) => cast_view_slice(view.as_slice()?),
5389            Self::Bool(view) => cast_view_slice(view.as_slice()?),
5390            Self::C32(view) => cast_view_slice(view.as_slice()?),
5391            Self::C64(view) => cast_view_slice(view.as_slice()?),
5392        }
5393    }
5394
5395    /// Reinterpret a complex view as its sealed real representation.
5396    ///
5397    /// # Errors
5398    ///
5399    /// Returns [`crate::Error::Unsupported`] for the wrong dtype pair and
5400    /// [`ValidationError::InvalidArgument`] or
5401    /// [`ValidationError::ViewOutOfBounds`] for invalid layout metadata.
5402    pub fn as_real_view(&self) -> crate::Result<Self> {
5403        match self {
5404            Self::C32(t) => t.as_real_view().map(Self::F32),
5405            Self::C64(t) => t.as_real_view().map(Self::F64),
5406            _ => Err(crate::Error::unsupported(
5407                "TensorView::as_real_view",
5408                "only complex views have a sealed real representation",
5409            )),
5410        }
5411    }
5412
5413    /// Reinterpret a real view as its sealed complex representation.
5414    ///
5415    /// # Errors
5416    ///
5417    /// Returns [`crate::Error::Unsupported`] for the wrong dtype pair and
5418    /// [`ValidationError::InvalidArgument`] or
5419    /// [`ValidationError::ViewOutOfBounds`] for invalid layout metadata.
5420    pub fn as_complex_view(&self) -> crate::Result<Self> {
5421        match self {
5422            Self::F32(t) => t.as_complex_view().map(Self::C32),
5423            Self::F64(t) => t.as_complex_view().map(Self::C64),
5424            _ => Err(crate::Error::unsupported(
5425                "TensorView::as_complex_view",
5426                "only real views have a sealed complex representation",
5427            )),
5428        }
5429    }
5430
5431    /// Return the placement metadata carried by this borrowed view.
5432    ///
5433    /// # Examples
5434    ///
5435    /// ```
5436    /// use tenferro_tensor::{MemoryKind, TensorView};
5437    ///
5438    /// let view = TensorView::f64(&[1], &[1.0])?;
5439    /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
5440    /// # Ok::<(), tenferro_tensor::Error>(())
5441    /// ```
5442    pub fn placement(&self) -> &Placement {
5443        match self {
5444            Self::F32(t) => t.placement(),
5445            Self::F64(t) => t.placement(),
5446            Self::I32(t) => t.placement(),
5447            Self::I64(t) => t.placement(),
5448            Self::Bool(t) => t.placement(),
5449            Self::C32(t) => t.placement(),
5450            Self::C64(t) => t.placement(),
5451        }
5452    }
5453
5454    /// Return the physical backend family, when this view is backend-owned.
5455    ///
5456    /// # Examples
5457    ///
5458    /// ```
5459    /// use tenferro_tensor::TensorView;
5460    ///
5461    /// let view = TensorView::f64(&[1], &[1.0])?;
5462    /// assert_eq!(view.backend_family(), None);
5463    /// # Ok::<(), tenferro_tensor::Error>(())
5464    /// ```
5465    pub fn backend_family(&self) -> Option<&'static str> {
5466        match self {
5467            Self::F32(t) => t.backend_family(),
5468            Self::F64(t) => t.backend_family(),
5469            Self::I32(t) => t.backend_family(),
5470            Self::I64(t) => t.backend_family(),
5471            Self::Bool(t) => t.backend_family(),
5472            Self::C32(t) => t.backend_family(),
5473            Self::C64(t) => t.backend_family(),
5474        }
5475    }
5476
5477    /// Return the shared allocation domain, when this view has one.
5478    ///
5479    /// # Examples
5480    ///
5481    /// ```
5482    /// use tenferro_tensor::TensorView;
5483    ///
5484    /// let view = TensorView::f64(&[1], &[1.0])?;
5485    /// assert_eq!(view.allocation_domain(), None);
5486    /// # Ok::<(), tenferro_tensor::Error>(())
5487    /// ```
5488    pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
5489        match self {
5490            Self::F32(t) => t.allocation_domain(),
5491            Self::F64(t) => t.allocation_domain(),
5492            Self::I32(t) => t.allocation_domain(),
5493            Self::I64(t) => t.allocation_domain(),
5494            Self::Bool(t) => t.allocation_domain(),
5495            Self::C32(t) => t.allocation_domain(),
5496            Self::C64(t) => t.allocation_domain(),
5497        }
5498    }
5499
5500    /// Return strides in element units.
5501    pub fn strides(&self) -> &[isize] {
5502        match self {
5503            Self::F32(t) => t.strides(),
5504            Self::F64(t) => t.strides(),
5505            Self::I32(t) => t.strides(),
5506            Self::I64(t) => t.strides(),
5507            Self::Bool(t) => t.strides(),
5508            Self::C32(t) => t.strides(),
5509            Self::C64(t) => t.strides(),
5510        }
5511    }
5512
5513    /// Return the physical element offset.
5514    pub fn offset(&self) -> isize {
5515        match self {
5516            Self::F32(t) => t.offset(),
5517            Self::F64(t) => t.offset(),
5518            Self::I32(t) => t.offset(),
5519            Self::I64(t) => t.offset(),
5520            Self::Bool(t) => t.offset(),
5521            Self::C32(t) => t.offset(),
5522            Self::C64(t) => t.offset(),
5523        }
5524    }
5525
5526    /// Compute the physical element offset for a logical index.
5527    /// # Errors
5528    ///
5529    /// Returns [`crate::Error::Validation`] with
5530    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5531    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5532    /// when an index is outside its axis extent, or
5533    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
5534    /// arithmetic overflows.
5535    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
5536        match self {
5537            Self::F32(t) => t.layout_linear_offset(indices),
5538            Self::F64(t) => t.layout_linear_offset(indices),
5539            Self::I32(t) => t.layout_linear_offset(indices),
5540            Self::I64(t) => t.layout_linear_offset(indices),
5541            Self::Bool(t) => t.layout_linear_offset(indices),
5542            Self::C32(t) => t.layout_linear_offset(indices),
5543            Self::C64(t) => t.layout_linear_offset(indices),
5544        }
5545    }
5546
5547    /// Return whether this view is compact column-major.
5548    /// # Errors
5549    ///
5550    /// Returns [`crate::Error::Validation`] with
5551    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5552    /// compactness arithmetic overflows.
5553    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
5554        match self {
5555            Self::F32(t) => t.is_col_major_contiguous(),
5556            Self::F64(t) => t.is_col_major_contiguous(),
5557            Self::I32(t) => t.is_col_major_contiguous(),
5558            Self::I64(t) => t.is_col_major_contiguous(),
5559            Self::Bool(t) => t.is_col_major_contiguous(),
5560            Self::C32(t) => t.is_col_major_contiguous(),
5561            Self::C64(t) => t.is_col_major_contiguous(),
5562        }
5563    }
5564
5565    /// Return a compact string summary of this view's layout metadata.
5566    pub fn layout_summary(&self) -> String {
5567        layout_summary(self.shape(), self.strides(), self.offset())
5568    }
5569
5570    /// Assert this view is compact column-major.
5571    /// # Errors
5572    ///
5573    /// Returns [`crate::Error::Validation`] with
5574    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5575    /// compactness arithmetic overflows, or
5576    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
5577    /// view is not compact column-major.
5578    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
5579        assert_layout_col_major_contiguous(
5580            self.is_col_major_contiguous()?,
5581            self.shape(),
5582            self.strides(),
5583            self.offset(),
5584            "TensorView::assert_col_major_contiguous",
5585        )
5586    }
5587
5588    /// Explicitly duplicate a compact host view into a fresh tensor.
5589    ///
5590    /// Backend views and non-contiguous layouts return a typed error; this
5591    /// operation never downloads or silently canonicalizes a view.
5592    ///
5593    /// # Examples
5594    ///
5595    /// ```
5596    /// use tenferro_tensor::{TensorView, TypedTensorView};
5597    ///
5598    /// let data = [1_i32, 2];
5599    /// let view = TensorView::I32(TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?);
5600    /// let copy = view.duplicate()?;
5601    /// assert_eq!(copy.shape(), &[2]);
5602    /// # Ok::<(), tenferro_tensor::Error>(())
5603    /// ```
5604    ///
5605    /// # Errors
5606    ///
5607    /// Returns [`crate::Error::HostAccess`] for backend-owned views,
5608    /// [`ValidationError::NonContiguousViewAsSlice`] for non-contiguous views,
5609    /// or [`ValidationError::InvalidArgument`] for invalid layout metadata.
5610    pub fn duplicate(&self) -> crate::Result<Tensor> {
5611        fn duplicate_typed<T: TensorScalar>(
5612            view: &TypedTensorView<'_, T>,
5613        ) -> crate::Result<TypedTensor<T>> {
5614            let mut tensor = TypedTensor::<T>::from_vec_col_major(
5615                view.shape().to_vec(),
5616                view.as_slice()?.to_vec(),
5617            )?;
5618            tensor.set_placement(view.placement().clone());
5619            Ok(tensor)
5620        }
5621
5622        match self {
5623            Self::F32(view) => duplicate_typed(view).map(|t| Tensor::from_core(t.core)),
5624            Self::F64(view) => duplicate_typed(view).map(|t| Tensor::from_core(t.core)),
5625            Self::I32(view) => duplicate_typed(view).map(|t| Tensor::from_core(t.core)),
5626            Self::I64(view) => duplicate_typed(view).map(|t| Tensor::from_core(t.core)),
5627            Self::Bool(view) => duplicate_typed(view).map(|t| Tensor::from_core(t.core)),
5628            Self::C32(view) => duplicate_typed(view).map(|t| Tensor::from_core(t.core)),
5629            Self::C64(view) => duplicate_typed(view).map(|t| Tensor::from_core(t.core)),
5630        }
5631    }
5632}
5633
5634macro_rules! tensor_view_mut_constructor {
5635    ($name:ident, $variant:ident, $scalar:ty) => {
5636        /// Create a dynamic mutable view over compact column-major host data.
5637        ///
5638        /// # Errors
5639        ///
5640        /// Returns [`crate::Error::Validation`] with
5641        /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5642        /// compact layout arithmetic overflows, or
5643        /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
5644        /// requested shape exceeds `data`.
5645        pub fn $name(shape: &'a [usize], data: &'a mut [$scalar]) -> crate::Result<Self> {
5646            Ok(Self::$variant(TypedTensorViewMut::from_col_major(
5647                shape, data,
5648            )?))
5649        }
5650    };
5651}
5652
5653impl<'a> TensorViewMut<'a> {
5654    tensor_view_mut_constructor!(f32, F32, f32);
5655    tensor_view_mut_constructor!(f64, F64, f64);
5656    tensor_view_mut_constructor!(i32, I32, i32);
5657    tensor_view_mut_constructor!(i64, I64, i64);
5658    tensor_view_mut_constructor!(bool, Bool, bool);
5659    tensor_view_mut_constructor!(c32, C32, Complex32);
5660    tensor_view_mut_constructor!(c64, C64, Complex64);
5661
5662    pub fn dtype(&self) -> DType {
5663        match self {
5664            Self::F32(_) => DType::F32,
5665            Self::F64(_) => DType::F64,
5666            Self::I32(_) => DType::I32,
5667            Self::I64(_) => DType::I64,
5668            Self::Bool(_) => DType::Bool,
5669            Self::C32(_) => DType::C32,
5670            Self::C64(_) => DType::C64,
5671        }
5672    }
5673
5674    pub fn shape(&self) -> &[usize] {
5675        match self {
5676            Self::F32(t) => t.shape(),
5677            Self::F64(t) => t.shape(),
5678            Self::I32(t) => t.shape(),
5679            Self::I64(t) => t.shape(),
5680            Self::Bool(t) => t.shape(),
5681            Self::C32(t) => t.shape(),
5682            Self::C64(t) => t.shape(),
5683        }
5684    }
5685
5686    pub fn strides(&self) -> &[isize] {
5687        match self {
5688            Self::F32(t) => t.strides(),
5689            Self::F64(t) => t.strides(),
5690            Self::I32(t) => t.strides(),
5691            Self::I64(t) => t.strides(),
5692            Self::Bool(t) => t.strides(),
5693            Self::C32(t) => t.strides(),
5694            Self::C64(t) => t.strides(),
5695        }
5696    }
5697
5698    pub fn offset(&self) -> isize {
5699        match self {
5700            Self::F32(t) => t.offset(),
5701            Self::F64(t) => t.offset(),
5702            Self::I32(t) => t.offset(),
5703            Self::I64(t) => t.offset(),
5704            Self::Bool(t) => t.offset(),
5705            Self::C32(t) => t.offset(),
5706            Self::C64(t) => t.offset(),
5707        }
5708    }
5709
5710    /// Compute the physical element offset for a logical index.
5711    ///
5712    /// # Errors
5713    ///
5714    /// Returns [`crate::Error::Validation`] with
5715    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5716    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5717    /// when an index is outside its axis extent, or
5718    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
5719    /// arithmetic overflows.
5720    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
5721        match self {
5722            Self::F32(t) => t.layout_linear_offset(indices),
5723            Self::F64(t) => t.layout_linear_offset(indices),
5724            Self::I32(t) => t.layout_linear_offset(indices),
5725            Self::I64(t) => t.layout_linear_offset(indices),
5726            Self::Bool(t) => t.layout_linear_offset(indices),
5727            Self::C32(t) => t.layout_linear_offset(indices),
5728            Self::C64(t) => t.layout_linear_offset(indices),
5729        }
5730    }
5731
5732    /// Return whether this view is compact column-major.
5733    ///
5734    /// # Errors
5735    ///
5736    /// Returns [`crate::Error::Validation`] with
5737    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5738    /// compactness arithmetic overflows.
5739    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
5740        match self {
5741            Self::F32(t) => t.is_col_major_contiguous(),
5742            Self::F64(t) => t.is_col_major_contiguous(),
5743            Self::I32(t) => t.is_col_major_contiguous(),
5744            Self::I64(t) => t.is_col_major_contiguous(),
5745            Self::Bool(t) => t.is_col_major_contiguous(),
5746            Self::C32(t) => t.is_col_major_contiguous(),
5747            Self::C64(t) => t.is_col_major_contiguous(),
5748        }
5749    }
5750
5751    pub fn layout_summary(&self) -> String {
5752        layout_summary(self.shape(), self.strides(), self.offset())
5753    }
5754
5755    /// Assert this view is compact column-major.
5756    ///
5757    /// # Errors
5758    ///
5759    /// Returns [`crate::Error::Validation`] with
5760    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5761    /// compactness arithmetic overflows, or
5762    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
5763    /// view is not compact column-major.
5764    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
5765        assert_layout_col_major_contiguous(
5766            self.is_col_major_contiguous()?,
5767            self.shape(),
5768            self.strides(),
5769            self.offset(),
5770            "TensorViewMut::assert_col_major_contiguous",
5771        )
5772    }
5773
5774    /// Explicitly duplicate the compact host data visible through this
5775    /// mutable view into a new tensor owner.
5776    ///
5777    /// # Examples
5778    ///
5779    /// ```
5780    /// use tenferro_tensor::{TensorViewMut, TypedTensorViewMut};
5781    ///
5782    /// let mut data = [1_i32, 2];
5783    /// let view = TensorViewMut::I32(TypedTensorViewMut::from_slice(
5784    ///     vec![2], vec![1], 0, &mut data,
5785    /// )?);
5786    /// let copy = view.duplicate()?;
5787    /// assert_eq!(copy.shape(), &[2]);
5788    /// # Ok::<(), tenferro_tensor::Error>(())
5789    /// ```
5790    ///
5791    /// # Errors
5792    ///
5793    /// Returns [`crate::Error::HostAccess`] for backend-owned views,
5794    /// [`ValidationError::NonContiguousViewAsSlice`] for non-contiguous views,
5795    /// or [`ValidationError::InvalidArgument`] for invalid layout metadata.
5796    pub fn duplicate(&self) -> crate::Result<Tensor> {
5797        self.as_read_only().duplicate()
5798    }
5799
5800    pub fn as_read_only(&self) -> TensorView<'_> {
5801        match self {
5802            Self::F32(t) => TensorView::F32(t.as_read_only()),
5803            Self::F64(t) => TensorView::F64(t.as_read_only()),
5804            Self::I32(t) => TensorView::I32(t.as_read_only()),
5805            Self::I64(t) => TensorView::I64(t.as_read_only()),
5806            Self::Bool(t) => TensorView::Bool(t.as_read_only()),
5807            Self::C32(t) => TensorView::C32(t.as_read_only()),
5808            Self::C64(t) => TensorView::C64(t.as_read_only()),
5809        }
5810    }
5811}
5812
5813impl<'a> TensorRead<'a> {
5814    pub fn from_tensor(tensor: &'a Tensor) -> Self {
5815        Self::Tensor(tensor)
5816    }
5817
5818    pub fn from_view(view: TensorView<'a>) -> Self {
5819        Self::View(view)
5820    }
5821
5822    /// Convert this read target into a dtype-erased tensor view.
5823    ///
5824    /// Owned tensors are borrowed without copying their storage. Existing
5825    /// views preserve their layout and placement metadata.
5826    /// # Examples
5827    ///
5828    /// ```rust
5829    /// # use tenferro_tensor::{Tensor, TensorRead};
5830    /// # let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5831    /// let view = TensorRead::from_tensor(&tensor).tensor_view();
5832    /// assert_eq!(view.shape(), &[2]);
5833    /// assert_eq!(view.as_slice::<f64>()?, &[1.0, 2.0]);
5834    /// # Ok::<(), tenferro_tensor::Error>(())
5835    /// ```
5836    pub fn tensor_view(self) -> TensorView<'a> {
5837        match self {
5838            Self::Tensor(tensor) => tensor_view_with_layout(tensor, tensor_layout(tensor)),
5839            Self::View(view) => view,
5840        }
5841    }
5842
5843    /// Borrow this read target as a typed compact host slice without allocation.
5844    ///
5845    /// This delegates to [`TensorView::as_slice`]. Cloning a `TensorRead` is a
5846    /// shallow clone of its borrowed reference or view metadata, so the returned
5847    /// slice retains the original `'a` storage lifetime and never outlives the
5848    /// storage borrowed by this read target. This method does not materialize,
5849    /// transfer, or otherwise canonicalize the input.
5850    ///
5851    /// # Errors
5852    ///
5853    /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
5854    /// input dtype, [`ValidationError::InvalidArgument`] for a noncompact view,
5855    /// or a typed runtime-state host-access error for backend-owned storage.
5856    /// Backend-owned
5857    /// inputs are never downloaded implicitly.
5858    ///
5859    /// # Examples
5860    ///
5861    /// ```
5862    /// use tenferro_tensor::{Tensor, TensorRead};
5863    ///
5864    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5865    /// let read = TensorRead::from_tensor(&tensor);
5866    /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
5867    /// # Ok::<(), tenferro_tensor::Error>(())
5868    /// ```
5869    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
5870        self.clone().tensor_view().as_slice()
5871    }
5872
5873    pub fn dtype(&self) -> DType {
5874        match self {
5875            Self::Tensor(tensor) => tensor.dtype(),
5876            Self::View(view) => view.dtype(),
5877        }
5878    }
5879
5880    pub fn shape(&self) -> &[usize] {
5881        match self {
5882            Self::Tensor(tensor) => tensor.shape(),
5883            Self::View(view) => view.shape(),
5884        }
5885    }
5886
5887    /// Return the placement metadata carried by this read target.
5888    ///
5889    /// # Examples
5890    ///
5891    /// ```
5892    /// use tenferro_tensor::{MemoryKind, Tensor, TensorRead};
5893    ///
5894    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
5895    /// let read = TensorRead::from_tensor(&tensor);
5896    /// assert_eq!(read.placement().memory_kind, MemoryKind::UnpinnedHost);
5897    /// # Ok::<(), tenferro_tensor::Error>(())
5898    /// ```
5899    pub fn placement(&self) -> &Placement {
5900        match self {
5901            Self::Tensor(tensor) => tensor.placement(),
5902            Self::View(view) => view.placement(),
5903        }
5904    }
5905
5906    /// Return the physical backend family of this read target, when backend-owned.
5907    ///
5908    /// # Examples
5909    ///
5910    /// ```
5911    /// use tenferro_tensor::{Tensor, TensorRead};
5912    ///
5913    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
5914    /// assert_eq!(TensorRead::from_tensor(&tensor).backend_family(), None);
5915    /// # Ok::<(), tenferro_tensor::Error>(())
5916    /// ```
5917    pub fn backend_family(&self) -> Option<&'static str> {
5918        match self {
5919            Self::Tensor(tensor) => match tensor.dtype() {
5920                // A caller-owned payload is host memory without backend family.
5921                DType::External(_) => None,
5922                DType::F32 => tensor.as_typed::<f32>().and_then(|t| t.backend_family()),
5923                DType::F64 => tensor.as_typed::<f64>().and_then(|t| t.backend_family()),
5924                DType::I32 => tensor.as_typed::<i32>().and_then(|t| t.backend_family()),
5925                DType::I64 => tensor.as_typed::<i64>().and_then(|t| t.backend_family()),
5926                DType::Bool => tensor.as_typed::<bool>().and_then(|t| t.backend_family()),
5927                DType::C32 => tensor
5928                    .as_typed::<Complex<f32>>()
5929                    .and_then(|t| t.backend_family()),
5930                DType::C64 => tensor
5931                    .as_typed::<Complex<f64>>()
5932                    .and_then(|t| t.backend_family()),
5933            },
5934            Self::View(view) => view.backend_family(),
5935        }
5936    }
5937
5938    /// Return the shared allocation domain of this read target, when present.
5939    ///
5940    /// # Examples
5941    ///
5942    /// ```
5943    /// use tenferro_tensor::{Tensor, TensorRead};
5944    ///
5945    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
5946    /// assert_eq!(TensorRead::from_tensor(&tensor).allocation_domain(), None);
5947    /// # Ok::<(), tenferro_tensor::Error>(())
5948    /// ```
5949    pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
5950        match self {
5951            Self::Tensor(tensor) => match tensor.dtype() {
5952                // A caller-owned payload has no allocation domain.
5953                DType::External(_) => None,
5954                DType::F32 => tensor.as_typed::<f32>().and_then(|t| t.allocation_domain()),
5955                DType::F64 => tensor.as_typed::<f64>().and_then(|t| t.allocation_domain()),
5956                DType::I32 => tensor.as_typed::<i32>().and_then(|t| t.allocation_domain()),
5957                DType::I64 => tensor.as_typed::<i64>().and_then(|t| t.allocation_domain()),
5958                DType::Bool => tensor
5959                    .as_typed::<bool>()
5960                    .and_then(|t| t.allocation_domain()),
5961                DType::C32 => tensor
5962                    .as_typed::<Complex<f32>>()
5963                    .and_then(|t| t.allocation_domain()),
5964                DType::C64 => tensor
5965                    .as_typed::<Complex<f64>>()
5966                    .and_then(|t| t.allocation_domain()),
5967            },
5968            Self::View(view) => view.allocation_domain(),
5969        }
5970    }
5971
5972    /// # Errors
5973    ///
5974    /// Returns [`crate::Error::Validation`] with
5975    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5976    /// column-major stride arithmetic overflows.
5977    pub fn strides(&self) -> crate::Result<Vec<isize>> {
5978        match self {
5979            Self::Tensor(tensor) => col_major_strides(tensor.shape()),
5980            Self::View(view) => Ok(view.strides().to_vec()),
5981        }
5982    }
5983
5984    pub fn offset(&self) -> isize {
5985        match self {
5986            Self::Tensor(_) => 0,
5987            Self::View(view) => view.offset(),
5988        }
5989    }
5990
5991    /// # Errors
5992    ///
5993    /// Returns [`crate::Error::Validation`] with
5994    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5995    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5996    /// when an index is outside its axis extent, or
5997    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
5998    /// arithmetic overflows.
5999    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
6000        match self {
6001            Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
6002            Self::View(view) => view.layout_linear_offset(indices),
6003        }
6004    }
6005
6006    /// # Errors
6007    ///
6008    /// Returns [`crate::Error::Validation`] with
6009    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
6010    /// compactness arithmetic overflows.
6011    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
6012        match self {
6013            Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
6014            Self::View(view) => view.is_col_major_contiguous(),
6015        }
6016    }
6017
6018    pub fn layout_summary(&self) -> String {
6019        let strides = match self.strides() {
6020            Ok(strides) => strides,
6021            Err(err) => return format!("layout unavailable: {err}"),
6022        };
6023        layout_summary(self.shape(), &strides, self.offset())
6024    }
6025
6026    /// # Errors
6027    ///
6028    /// Returns [`crate::Error::Validation`] with
6029    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
6030    /// compactness arithmetic overflows, or
6031    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
6032    /// view is not compact column-major.
6033    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
6034        let strides = self.strides()?;
6035        assert_layout_col_major_contiguous(
6036            self.is_col_major_contiguous()?,
6037            self.shape(),
6038            &strides,
6039            self.offset(),
6040            "TensorRead::assert_col_major_contiguous",
6041        )
6042    }
6043
6044    pub fn as_tensor(&self) -> Option<&'a Tensor> {
6045        match self {
6046            Self::Tensor(tensor) => Some(*tensor),
6047            Self::View(_) => None,
6048        }
6049    }
6050}
6051
6052impl<'a> TensorWrite<'a> {
6053    pub fn from_tensor(tensor: &'a mut Tensor) -> Self {
6054        Self::Tensor(tensor)
6055    }
6056
6057    pub fn from_view(view: TensorViewMut<'a>) -> Self {
6058        Self::View(view)
6059    }
6060
6061    /// Borrow this writable target as a read-only tensor input.
6062    ///
6063    /// This is useful for explicit read-modify-write kernels such as
6064    /// accumulation updates. The returned view borrows through `&self`, so it
6065    /// cannot outlive the current read-only borrow of the writable target.
6066    ///
6067    /// # Examples
6068    ///
6069    /// ```rust
6070    /// use tenferro_tensor::{DType, Tensor, TensorWrite};
6071    ///
6072    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
6073    /// let write = TensorWrite::from_tensor(&mut tensor);
6074    /// let read = write.as_read();
6075    /// assert_eq!(read.dtype(), DType::F64);
6076    /// # Ok::<(), tenferro_tensor::Error>(())
6077    /// ```
6078    pub fn as_read(&self) -> TensorRead<'_> {
6079        match self {
6080            Self::Tensor(tensor) => TensorRead::from_tensor(tensor),
6081            Self::View(view) => TensorRead::from_view(view.as_read_only()),
6082        }
6083    }
6084
6085    pub fn dtype(&self) -> DType {
6086        match self {
6087            Self::Tensor(tensor) => tensor.dtype(),
6088            Self::View(view) => view.dtype(),
6089        }
6090    }
6091
6092    pub fn shape(&self) -> &[usize] {
6093        match self {
6094            Self::Tensor(tensor) => tensor.shape(),
6095            Self::View(view) => view.shape(),
6096        }
6097    }
6098
6099    /// # Errors
6100    ///
6101    /// Returns [`crate::Error::Validation`] with
6102    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
6103    /// column-major stride arithmetic overflows.
6104    pub fn strides(&self) -> crate::Result<Vec<isize>> {
6105        match self {
6106            Self::Tensor(tensor) => col_major_strides(tensor.shape()),
6107            Self::View(view) => Ok(view.strides().to_vec()),
6108        }
6109    }
6110
6111    pub fn offset(&self) -> isize {
6112        match self {
6113            Self::Tensor(_) => 0,
6114            Self::View(view) => view.offset(),
6115        }
6116    }
6117
6118    /// # Errors
6119    ///
6120    /// Returns [`crate::Error::Validation`] with
6121    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
6122    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
6123    /// when an index is outside its axis extent, or
6124    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
6125    /// arithmetic overflows.
6126    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
6127        match self {
6128            Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
6129            Self::View(view) => view.layout_linear_offset(indices),
6130        }
6131    }
6132
6133    /// # Errors
6134    ///
6135    /// Returns [`crate::Error::Validation`] with
6136    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
6137    /// compactness arithmetic overflows.
6138    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
6139        match self {
6140            Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
6141            Self::View(view) => view.is_col_major_contiguous(),
6142        }
6143    }
6144
6145    pub fn layout_summary(&self) -> String {
6146        let strides = match self.strides() {
6147            Ok(strides) => strides,
6148            Err(err) => return format!("layout unavailable: {err}"),
6149        };
6150        layout_summary(self.shape(), &strides, self.offset())
6151    }
6152
6153    /// # Errors
6154    ///
6155    /// Returns [`crate::Error::Validation`] with
6156    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
6157    /// compactness arithmetic overflows, or
6158    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
6159    /// view is not compact column-major.
6160    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
6161        let strides = self.strides()?;
6162        assert_layout_col_major_contiguous(
6163            self.is_col_major_contiguous()?,
6164            self.shape(),
6165            &strides,
6166            self.offset(),
6167            "TensorWrite::assert_col_major_contiguous",
6168        )
6169    }
6170}
6171
6172/// Column-major strides derived from a shape.
6173///
6174/// # Examples
6175///
6176/// ```rust
6177/// use tenferro_tensor::col_major_strides;
6178///
6179/// assert_eq!(col_major_strides(&[2, 3])?, vec![1, 2]);
6180/// # Ok::<(), tenferro_tensor::Error>(())
6181/// ```
6182/// # Errors
6183///
6184/// Returns [`crate::Error::Validation`] with
6185/// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when a
6186/// column-major stride product overflows.
6187pub fn col_major_strides(shape: &[usize]) -> crate::Result<Vec<isize>> {
6188    let mut strides = Vec::with_capacity(shape.len());
6189    let mut stride = 1isize;
6190    for &extent in shape {
6191        strides.push(stride);
6192        let extent = isize::try_from(extent).map_err(|_| {
6193            crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
6194        })?;
6195        stride = stride.checked_mul(extent).ok_or_else(|| {
6196            crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
6197        })?;
6198    }
6199    Ok(strides)
6200}
6201
6202fn try_linear_offset_for_shape(
6203    shape: &[usize],
6204    indices: &[usize],
6205    op: &'static str,
6206) -> crate::Result<usize> {
6207    if indices.len() != shape.len() {
6208        return Err(crate::Error::validation(
6209            op,
6210            ValidationError::RankMismatch {
6211                expected: shape.len(),
6212                actual: indices.len(),
6213            },
6214        ));
6215    }
6216    let mut offset = 0usize;
6217    let mut stride = 1usize;
6218    for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
6219        if idx >= extent {
6220            return Err(crate::Error::invalid_argument(
6221                op,
6222                "index",
6223                format!("index {idx} out of bounds for axis {axis} extent {extent}"),
6224            ));
6225        }
6226        offset =
6227            offset
6228                .checked_add(idx.checked_mul(stride).ok_or_else(|| {
6229                    crate::Error::validation(op, ValidationError::IntegerOverflow)
6230                })?)
6231                .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6232        stride = stride
6233            .checked_mul(extent)
6234            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6235    }
6236    Ok(offset)
6237}
6238
6239fn checked_view_offset_result(
6240    shape: &[usize],
6241    strides: &[isize],
6242    base_offset: isize,
6243    indices: &[usize],
6244    op: &'static str,
6245) -> crate::Result<usize> {
6246    if indices.len() != shape.len() {
6247        return Err(crate::Error::validation(
6248            op,
6249            ValidationError::RankMismatch {
6250                expected: shape.len(),
6251                actual: indices.len(),
6252            },
6253        ));
6254    }
6255    for (axis, (&index, &extent)) in indices.iter().zip(shape).enumerate() {
6256        if index >= extent {
6257            return Err(crate::Error::invalid_argument(
6258                op,
6259                "index",
6260                format!("index {index} out of bounds for axis {axis} extent {extent}"),
6261            ));
6262        }
6263    }
6264    checked_view_offset(shape, strides, base_offset, indices)
6265        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
6266}
6267
6268fn layout_summary(shape: &[usize], strides: &[isize], offset: isize) -> String {
6269    format!("shape={shape:?} strides={strides:?} offset={offset}")
6270}
6271
6272fn assert_layout_col_major_contiguous(
6273    is_contiguous: bool,
6274    shape: &[usize],
6275    strides: &[isize],
6276    offset: isize,
6277    op: &'static str,
6278) -> crate::Result<()> {
6279    if is_contiguous {
6280        Ok(())
6281    } else {
6282        Err(crate::Error::invalid_argument(
6283            op,
6284            "layout",
6285            format!(
6286                "expected compact column-major layout, got {}",
6287                layout_summary(shape, strides, offset)
6288            ),
6289        ))
6290    }
6291}
6292
6293fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
6294    shape.iter().try_fold(1usize, |acc, &dim| {
6295        acc.checked_mul(dim)
6296            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
6297    })
6298}
6299
6300fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
6301    let n = try_shape_product(shape, op)?;
6302    if data_len != n {
6303        return Err(crate::Error::validation(
6304            op,
6305            ValidationError::ShapeDataLengthMismatch {
6306                expected: n,
6307                actual: data_len,
6308            },
6309        ));
6310    }
6311    Ok(())
6312}
6313
6314fn try_compact_layout<R: TensorRank>(
6315    shape: impl tenferro_tensor_core::IntoRankShape<R>,
6316    op: &'static str,
6317) -> crate::Result<TensorLayout<R>> {
6318    let shape = shape
6319        .into_rank_shape()
6320        .map_err(|err| tensor_layout_error(op, err))?;
6321    TensorLayout::compact(shape).map_err(|err| tensor_layout_error(op, err))
6322}
6323
6324fn tensor_layout_error(
6325    op: &'static str,
6326    err: tenferro_tensor_core::ValidationError,
6327) -> crate::Error {
6328    crate::Error::validation(op, err)
6329}
6330
6331fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
6332    if shape.contains(&0) {
6333        return Ok(0);
6334    }
6335    shape.iter().try_fold(1usize, |product, &dim| {
6336        product
6337            .checked_mul(dim)
6338            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
6339    })
6340}
6341
6342fn checked_view_offset(
6343    shape: &[usize],
6344    strides: &[isize],
6345    base_offset: isize,
6346    indices: &[usize],
6347) -> Option<usize> {
6348    if indices.len() != shape.len() {
6349        return None;
6350    }
6351
6352    let mut offset = base_offset;
6353    for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
6354        if index >= extent {
6355            return None;
6356        }
6357        let index = isize::try_from(index).ok()?;
6358        let delta = index.checked_mul(stride)?;
6359        offset = offset.checked_add(delta)?;
6360    }
6361
6362    usize::try_from(offset).ok()
6363}
6364
6365fn reachable_layout_span(
6366    shape: &[usize],
6367    strides: &[isize],
6368    offset: isize,
6369) -> crate::Result<Option<(usize, usize)>> {
6370    if shape.contains(&0) {
6371        return Ok(None);
6372    }
6373
6374    let mut min_offset = offset;
6375    let mut max_offset = offset;
6376    for (&extent, &stride) in shape.iter().zip(strides) {
6377        let steps = isize::try_from(extent.saturating_sub(1)).map_err(|_| {
6378            crate::Error::validation(
6379                "TypedTensorViewMut::try_multi_slice_mut",
6380                ValidationError::IntegerOverflow,
6381            )
6382        })?;
6383        let end = stride.checked_mul(steps).ok_or_else(|| {
6384            crate::Error::validation(
6385                "TypedTensorViewMut::try_multi_slice_mut",
6386                ValidationError::IntegerOverflow,
6387            )
6388        })?;
6389        let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
6390        min_offset = min_offset.checked_add(axis_min).ok_or_else(|| {
6391            crate::Error::validation(
6392                "TypedTensorViewMut::try_multi_slice_mut",
6393                ValidationError::IntegerOverflow,
6394            )
6395        })?;
6396        max_offset = max_offset.checked_add(axis_max).ok_or_else(|| {
6397            crate::Error::validation(
6398                "TypedTensorViewMut::try_multi_slice_mut",
6399                ValidationError::IntegerOverflow,
6400            )
6401        })?;
6402    }
6403
6404    let min_offset = usize::try_from(min_offset).map_err(|_| {
6405        crate::Error::invalid_argument(
6406            "TypedTensorViewMut::try_multi_slice_mut",
6407            "layout",
6408            "minimum reachable offset is negative",
6409        )
6410    })?;
6411    let max_offset = usize::try_from(max_offset).map_err(|_| {
6412        crate::Error::invalid_argument(
6413            "TypedTensorViewMut::try_multi_slice_mut",
6414            "layout",
6415            "maximum reachable offset is negative",
6416        )
6417    })?;
6418    Ok(Some((min_offset, max_offset)))
6419}
6420
6421fn split_two_mut_ranges<T>(
6422    data: &mut [T],
6423    first: (usize, usize),
6424    second: (usize, usize),
6425) -> Option<(&mut [T], &mut [T])> {
6426    if first.1 < second.0 {
6427        let (_, after_first_start) = data.split_at_mut(first.0);
6428        let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
6429        let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
6430        let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
6431        Some((first_slice, second_slice))
6432    } else if second.1 < first.0 {
6433        let (_, after_second_start) = data.split_at_mut(second.0);
6434        let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
6435        let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
6436        let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
6437        Some((first_slice, second_slice))
6438    } else {
6439        None
6440    }
6441}
6442
6443fn adjusted_view_offset(offset: isize, span_start: usize) -> crate::Result<isize> {
6444    let span_start = isize::try_from(span_start).map_err(|_| {
6445        crate::Error::validation(
6446            "TypedTensorViewMut::try_multi_slice_mut",
6447            ValidationError::IntegerOverflow,
6448        )
6449    })?;
6450    offset.checked_sub(span_start).ok_or_else(|| {
6451        crate::Error::validation(
6452            "TypedTensorViewMut::try_multi_slice_mut",
6453            ValidationError::IntegerOverflow,
6454        )
6455    })
6456}
6457
6458fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
6459    layout: &TensorLayout<R>,
6460    offset: isize,
6461    data: &'a mut [T],
6462    placement: Placement,
6463) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
6464    let shape = R::shape_from_vec(shape_vec(layout.shape()))
6465        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
6466    let strides = R::strides_from_vec(stride_vec(layout.strides()))
6467        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
6468    TypedTensorViewMut::from_buffer_ref_mut(
6469        shape,
6470        strides,
6471        offset,
6472        TensorStorageRefMut::Host(data),
6473        placement,
6474        "TypedTensorViewMut::try_multi_slice_mut",
6475    )
6476}
6477
6478fn contiguous_layout_slice<'a, T, R: TensorRank>(
6479    layout: &TensorLayout<R>,
6480    data: &'a [T],
6481    op: &'static str,
6482) -> crate::Result<&'a [T]> {
6483    if !layout
6484        .is_compact_col_major()
6485        .map_err(|err| tensor_layout_error(op, err))?
6486    {
6487        return Err(crate::Error::invalid_argument(
6488            op,
6489            "layout",
6490            "view is not contiguous column-major",
6491        ));
6492    }
6493    let len = checked_view_element_count(layout.shape(), op)?;
6494    let start = usize::try_from(layout.offset())
6495        .map_err(|_| crate::Error::invalid_argument(op, "layout", "view offset is negative"))?;
6496    let end = start
6497        .checked_add(len)
6498        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6499    data.get(start..end)
6500        .ok_or_else(|| crate::Error::validation(op, ValidationError::ViewOutOfBounds))
6501}
6502
6503fn relaxed_col_major_contiguous(
6504    shape: &[usize],
6505    strides: &[isize],
6506    op: &'static str,
6507) -> crate::Result<bool> {
6508    if shape.contains(&0) {
6509        return Ok(true);
6510    }
6511
6512    let mut expected = 1isize;
6513    for (&extent, &stride) in shape.iter().zip(strides) {
6514        if extent <= 1 {
6515            continue;
6516        }
6517        if stride != expected {
6518            return Ok(false);
6519        }
6520        let extent = isize::try_from(extent)
6521            .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6522        expected = expected
6523            .checked_mul(extent)
6524            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6525    }
6526    Ok(true)
6527}
6528
6529fn reshape_layout_dyn<R: TensorRank>(
6530    layout: &TensorLayout<R>,
6531    shape: &[usize],
6532    buffer_len: usize,
6533    op: &'static str,
6534) -> crate::Result<TensorLayout<DynRank>> {
6535    match layout.reshape_view_as::<DynRank>(shape_vec(shape), buffer_len) {
6536        Ok(layout) => Ok(layout),
6537        Err(err) => {
6538            if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
6539                return Err(tensor_layout_error(op, err));
6540            }
6541            let from = checked_view_element_count(layout.shape(), op)?;
6542            let to = checked_view_element_count(shape, op)?;
6543            if from != to {
6544                return Err(tensor_layout_error(
6545                    op,
6546                    tenferro_tensor_core::ShapeMismatch::ReshapeElementCount { from, to }.into(),
6547                ));
6548            }
6549            TensorLayout::<DynRank>::compact(shape_vec(shape))
6550                .and_then(|compact| {
6551                    TensorLayout::from_parts(
6552                        shape_vec(compact.shape()),
6553                        stride_vec(compact.strides()),
6554                        layout.offset(),
6555                        buffer_len,
6556                    )
6557                })
6558                .map_err(|err| tensor_layout_error(op, err))
6559        }
6560    }
6561}
6562
6563fn core_slice_specs(
6564    slices: &[StridedSliceSpec],
6565    shape: &[usize],
6566    op: &'static str,
6567) -> crate::Result<Vec<CoreSliceSpec>> {
6568    if slices.len() != shape.len() {
6569        return Err(crate::Error::validation(
6570            op,
6571            ValidationError::RankMismatch {
6572                expected: shape.len(),
6573                actual: slices.len(),
6574            },
6575        ));
6576    }
6577
6578    let mut specs = Vec::with_capacity(slices.len());
6579    for (slice, &axis_len) in slices.iter().zip(shape) {
6580        specs.push(core_slice_spec(*slice, axis_len, op)?);
6581    }
6582    Ok(specs)
6583}
6584
6585fn core_slice_spec(
6586    slice: StridedSliceSpec,
6587    axis_len: usize,
6588    op: &'static str,
6589) -> crate::Result<CoreSliceSpec> {
6590    if slice.step() == 0 {
6591        return Err(crate::Error::validation(
6592            op,
6593            ValidationError::InvalidSliceStep { step: slice.step() },
6594        ));
6595    }
6596
6597    let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
6598    let end = match slice.end() {
6599        Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
6600        None => isize::try_from(axis_len)
6601            .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
6602    };
6603
6604    if slice.step() > 0 {
6605        return Ok(CoreSliceSpec {
6606            start,
6607            end,
6608            step: slice.step(),
6609        });
6610    }
6611
6612    if start >= end {
6613        return Ok(CoreSliceSpec {
6614            start,
6615            end: start,
6616            step: slice.step(),
6617        });
6618    }
6619
6620    Ok(CoreSliceSpec {
6621        start: end
6622            .checked_sub(1)
6623            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
6624        end: start
6625            .checked_sub(1)
6626            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
6627        step: slice.step(),
6628    })
6629}
6630
6631fn normalize_strided_bound(
6632    bound: isize,
6633    axis_len: usize,
6634    op: &'static str,
6635    role: &'static str,
6636) -> crate::Result<isize> {
6637    let original_axis_len = axis_len;
6638    let axis_len = isize::try_from(axis_len)
6639        .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6640    let bound = if bound < 0 {
6641        axis_len
6642            .checked_add(bound)
6643            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?
6644    } else {
6645        bound
6646    };
6647    if !(0..=axis_len).contains(&bound) {
6648        let (start, end) = if role == "slice start" {
6649            (bound, bound)
6650        } else {
6651            (0, bound)
6652        };
6653        return Err(crate::Error::validation(
6654            op,
6655            ValidationError::InvalidSliceBounds {
6656                start,
6657                end,
6658                axis_len: original_axis_len,
6659            },
6660        ));
6661    }
6662    Ok(bound)
6663}
6664
6665fn slice_axis_specs(
6666    rank: usize,
6667    axis: usize,
6668    slice: StridedSliceSpec,
6669    op: &'static str,
6670) -> crate::Result<Vec<StridedSliceSpec>> {
6671    if axis >= rank {
6672        return Err(crate::Error::validation(
6673            op,
6674            ValidationError::AxisOutOfBounds { axis, rank },
6675        ));
6676    }
6677
6678    let mut slices = vec![StridedSliceSpec::all(); rank];
6679    slices[axis] = slice;
6680    Ok(slices)
6681}
6682
6683pub(crate) fn default_placement() -> Placement {
6684    Placement {
6685        memory_kind: MemoryKind::UnpinnedHost,
6686        device: None,
6687        cpu_affinity: None,
6688    }
6689}
6690
6691fn typed_tensor_from_vec_col_major<T: TensorScalar, R: TensorRank>(
6692    shape: impl tenferro_tensor_core::IntoRankShape<R>,
6693    data: Vec<T>,
6694    op: &'static str,
6695) -> crate::Result<TypedTensor<T, R>> {
6696    try_typed_tensor_from_vec_col_major(shape, data, op)
6697}
6698
6699fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
6700    shape: impl tenferro_tensor_core::IntoRankShape<R>,
6701    data: Vec<T>,
6702    op: &'static str,
6703) -> crate::Result<TypedTensor<T, R>>
6704where
6705    T: TensorScalar,
6706{
6707    let layout = try_compact_layout(shape, op)?;
6708    try_checked_shape_len(layout.shape(), data.len(), op)?;
6709    let group_shape =
6710        R::shape_from_vec(shape_vec(layout.shape())).map_err(|err| tensor_layout_error(op, err))?;
6711    let group = OwnedTensorGroup::from_host_vec(group_shape, data)?;
6712    Ok(TypedTensor {
6713        core: TensorCore {
6714            group,
6715            layout,
6716            placement: default_placement(),
6717        },
6718        _scalar: PhantomData,
6719    })
6720}
6721
6722fn typed_tensor_zeros<T: TensorScalar + Zero, R: TensorRank>(
6723    shape: impl tenferro_tensor_core::IntoRankShape<R>,
6724) -> crate::Result<TypedTensor<T, R>> {
6725    try_typed_tensor_zeros(shape)
6726}
6727
6728fn try_typed_tensor_zeros<T: TensorScalar + Clone + Zero, R: TensorRank>(
6729    shape: impl tenferro_tensor_core::IntoRankShape<R>,
6730) -> crate::Result<TypedTensor<T, R>> {
6731    let layout = try_compact_layout(shape, "zeros")?;
6732    let n = try_shape_product(layout.shape(), "zeros")?;
6733    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6734        .map_err(|err| tensor_layout_error("zeros", err))?;
6735    let group = OwnedTensorGroup::from_host_vec(group_shape, vec![T::zero(); n])?;
6736    Ok(TypedTensor {
6737        core: TensorCore {
6738            group,
6739            layout,
6740            placement: default_placement(),
6741        },
6742        _scalar: PhantomData,
6743    })
6744}
6745
6746fn typed_tensor_ones<T: TensorScalar + One + Zero, R: TensorRank>(
6747    shape: impl tenferro_tensor_core::IntoRankShape<R>,
6748) -> crate::Result<TypedTensor<T, R>> {
6749    try_typed_tensor_ones(shape)
6750}
6751
6752fn try_typed_tensor_ones<T: TensorScalar + Clone + One + Zero, R: TensorRank>(
6753    shape: impl tenferro_tensor_core::IntoRankShape<R>,
6754) -> crate::Result<TypedTensor<T, R>> {
6755    let layout = try_compact_layout(shape, "ones")?;
6756    let n = try_shape_product(layout.shape(), "ones")?;
6757    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6758        .map_err(|err| tensor_layout_error("ones", err))?;
6759    let group = OwnedTensorGroup::from_host_vec(group_shape, vec![T::one(); n])?;
6760    Ok(TypedTensor {
6761        core: TensorCore {
6762            group,
6763            layout,
6764            placement: default_placement(),
6765        },
6766        _scalar: PhantomData,
6767    })
6768}
6769
6770fn typed_tensor_from_buffer_col_major<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
6771    shape: impl tenferro_tensor_core::IntoRankShape<R>,
6772    buffer: StorageBuffer<T>,
6773    placement: Placement,
6774) -> crate::Result<TypedTensor<T, R>> {
6775    try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
6776}
6777
6778#[doc(hidden)]
6779fn typed_tensor_from_backend_allocation<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
6780    shape: impl tenferro_tensor_core::IntoRankShape<R>,
6781    allocation: Box<dyn crate::BackendAllocation>,
6782    placement: Placement,
6783) -> crate::Result<TypedTensor<T, R>> {
6784    let layout = try_compact_layout(shape, "from_backend_allocation")?;
6785    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6786        .map_err(|err| tensor_layout_error("from_backend_allocation", err))?;
6787    let (mut group, slot) =
6788        AllocationGroup::from_backend_allocation::<T, R>(group_shape, allocation)
6789            .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
6790    group
6791        .set_descriptor_placement(slot, placement.clone())
6792        .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
6793    let allocation_index = group
6794        .allocation_index(slot)
6795        .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
6796    let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
6797    Ok(TypedTensor {
6798        core: TensorCore {
6799            group: OwnedTensorGroup {
6800                group,
6801                slot,
6802                allocation_index,
6803                host_ptr,
6804                host_byte_len,
6805                _rank: PhantomData,
6806            },
6807            layout,
6808            placement,
6809        },
6810        _scalar: PhantomData,
6811    })
6812}
6813
6814fn try_typed_tensor_from_buffer_col_major<
6815    T: TensorScalar + Send + Sync + 'static,
6816    R: TensorRank,
6817>(
6818    shape: impl tenferro_tensor_core::IntoRankShape<R>,
6819    buffer: StorageBuffer<T>,
6820    placement: Placement,
6821) -> crate::Result<TypedTensor<T, R>> {
6822    let layout = try_compact_layout(shape, "from_buffer_col_major")?;
6823    let len = buffer.len();
6824    try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
6825    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6826        .map_err(|err| tensor_layout_error("from_buffer_col_major", err))?;
6827    let group = match buffer {
6828        StorageBuffer::Host(data) => OwnedTensorGroup::from_host_vec(group_shape, data)?,
6829        StorageBuffer::Backend(buffer) => OwnedTensorGroup::from_backend_buffer(
6830            group_shape,
6831            StorageBuffer::Backend(buffer),
6832            placement.clone(),
6833        )?,
6834    };
6835    Ok(TypedTensor {
6836        core: TensorCore {
6837            group,
6838            layout,
6839            placement,
6840        },
6841        _scalar: PhantomData,
6842    })
6843}
6844
6845impl<T: TensorScalar + Zero, R: TensorRank> TypedTensor<T, R> {
6846    /// Allocate a zero-filled tensor.
6847    ///
6848    /// # Examples
6849    ///
6850    /// ```rust
6851    /// use tenferro_tensor::TypedTensor;
6852    ///
6853    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
6854    /// assert_eq!(t.n_elements(), 6);
6855    /// ```
6856    /// # Errors
6857    ///
6858    /// Returns [`crate::Error::Validation`] with
6859    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
6860    /// product or compact-stride arithmetic overflows.
6861    /// A vector or slice with a different static rank returns
6862    /// [`tenferro_tensor_core::ValidationError::RankMismatch`].
6863    pub fn zeros(shape: impl tenferro_tensor_core::IntoRankShape<R>) -> crate::Result<Self> {
6864        typed_tensor_zeros(shape)
6865    }
6866}
6867
6868impl<T: TensorScalar + One + Zero, R: TensorRank> TypedTensor<T, R> {
6869    /// Allocate a one-filled tensor.
6870    ///
6871    /// # Examples
6872    ///
6873    /// ```rust
6874    /// use tenferro_tensor::TypedTensor;
6875    ///
6876    /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
6877    /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
6878    /// ```
6879    /// # Errors
6880    ///
6881    /// Returns [`crate::Error::Validation`] with
6882    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
6883    /// product or compact-stride arithmetic overflows.
6884    /// A vector or slice with a different static rank returns
6885    /// [`tenferro_tensor_core::ValidationError::RankMismatch`].
6886    pub fn ones(shape: impl tenferro_tensor_core::IntoRankShape<R>) -> crate::Result<Self> {
6887        typed_tensor_ones(shape)
6888    }
6889}
6890
6891impl<T, R: TensorRank> TypedTensor<T, R> {
6892    /// Create a tensor from an existing buffer and compact column-major layout.
6893    ///
6894    /// This preserves the owned tensor invariant that layout metadata is
6895    /// compact column-major, including for backend-owned buffers.
6896    ///
6897    /// # Examples
6898    ///
6899    /// ```
6900    /// use tenferro_tensor::{StorageBuffer, Placement, TypedTensor};
6901    ///
6902    /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
6903    ///     vec![2],
6904    ///     StorageBuffer::Host(vec![1.0, 2.0]),
6905    ///     Placement {
6906    ///         memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
6907    ///         device: None,
6908    ///         cpu_affinity: None,
6909    ///     },
6910    /// )
6911    /// .unwrap();
6912    /// assert_eq!(tensor.shape(), &[2]);
6913    /// ```
6914    /// # Errors
6915    ///
6916    /// Returns [`crate::Error::Validation`] with
6917    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
6918    /// the shape product differs from the buffer length,
6919    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape or
6920    /// stride arithmetic overflows, or
6921    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a supplied
6922    /// rank-specific shape cannot be represented.
6923    pub fn from_buffer_col_major(
6924        shape: impl tenferro_tensor_core::IntoRankShape<R>,
6925        buffer: StorageBuffer<T>,
6926        placement: Placement,
6927    ) -> crate::Result<Self>
6928    where
6929        T: TensorScalar + Send + Sync + 'static,
6930    {
6931        typed_tensor_from_buffer_col_major(shape, buffer, placement)
6932    }
6933
6934    /// Consume a scalar-independent provider root into one compact tensor.
6935    #[doc(hidden)]
6936    pub fn from_backend_allocation(
6937        shape: impl tenferro_tensor_core::IntoRankShape<R>,
6938        allocation: Box<dyn crate::BackendAllocation>,
6939        placement: Placement,
6940    ) -> crate::Result<Self>
6941    where
6942        T: TensorScalar + Send + Sync + 'static,
6943    {
6944        typed_tensor_from_backend_allocation(shape, allocation, placement)
6945    }
6946
6947    /// Convert this tensor into static rank metadata after validating its rank.
6948    ///
6949    /// The buffer and placement are preserved. This method changes only the
6950    /// compile-time rank marker on the owned compact column-major tensor.
6951    ///
6952    /// # Examples
6953    ///
6954    /// ```rust
6955    /// use tenferro_tensor::{Rank, TypedTensor};
6956    ///
6957    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
6958    /// let ranked: TypedTensor<f64, Rank<2>> = tensor.try_into_rank::<2>()?;
6959    /// assert_eq!(ranked.shape(), &[2, 3]);
6960    /// # Ok::<(), tenferro_tensor::Error>(())
6961    /// ```
6962    /// # Errors
6963    ///
6964    /// Returns [`crate::Error::Validation`] with
6965    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
6966    /// rank does not match the existing shape,
6967    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when compact
6968    /// strides cannot be computed, or
6969    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6970    /// preserved buffer cannot hold the rank-converted layout.
6971    pub fn try_into_rank<const N: usize>(self) -> crate::Result<TypedTensor<T, Rank<N>>> {
6972        let op = "TypedTensor::try_into_rank";
6973        let actual = self.shape().len();
6974        let shape: [usize; N] = self.shape().try_into().map_err(|_| {
6975            tensor_layout_error(
6976                op,
6977                ValidationError::RankMismatch {
6978                    expected: N,
6979                    actual,
6980                },
6981            )
6982        })?;
6983        let layout =
6984            TensorLayout::<Rank<N>>::compact(shape).map_err(|err| tensor_layout_error(op, err))?;
6985        let owned = self.core.group;
6986        Ok(TypedTensor {
6987            core: TensorCore {
6988                group: OwnedTensorGroup {
6989                    group: owned.group,
6990                    slot: owned.slot,
6991                    allocation_index: owned.allocation_index,
6992                    host_ptr: owned.host_ptr,
6993                    host_byte_len: owned.host_byte_len,
6994                    _rank: PhantomData,
6995                },
6996                layout,
6997                placement: self.core.placement,
6998            },
6999            _scalar: PhantomData,
7000        })
7001    }
7002
7003    /// Number of elements in the tensor.
7004    ///
7005    /// # Examples
7006    ///
7007    /// ```rust
7008    /// use tenferro_tensor::TypedTensor;
7009    ///
7010    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
7011    /// assert_eq!(t.n_elements(), 6);
7012    /// ```
7013    pub fn n_elements(&self) -> usize {
7014        // Invariant: owned tensor constructors validate compact shape length against buffer length.
7015        match try_shape_product(self.shape(), "TypedTensor::n_elements") {
7016            Ok(n) => n,
7017            Err(err) => {
7018                unreachable!("TypedTensor compact shape is validated at construction: {err}")
7019            }
7020        }
7021    }
7022
7023    /// Tensor shape.
7024    ///
7025    /// # Examples
7026    ///
7027    /// ```
7028    /// use tenferro_tensor::TypedTensor;
7029    ///
7030    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7031    /// assert_eq!(t.shape(), &[2]);
7032    /// ```
7033    pub fn shape(&self) -> &[usize] {
7034        self.core.layout.shape()
7035    }
7036
7037    /// Tensor rank.
7038    ///
7039    /// # Examples
7040    ///
7041    /// ```
7042    /// use tenferro_tensor::TypedTensor;
7043    ///
7044    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
7045    /// assert_eq!(t.rank(), 2);
7046    /// ```
7047    pub fn rank(&self) -> usize {
7048        self.shape().len()
7049    }
7050
7051    /// Tensor layout metadata.
7052    ///
7053    /// Owned typed tensors are always compact column-major layouts.
7054    ///
7055    /// # Examples
7056    ///
7057    /// ```
7058    /// use tenferro_tensor::TypedTensor;
7059    ///
7060    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
7061    /// assert_eq!(t.layout().strides(), &[1, 2]);
7062    /// ```
7063    pub fn layout(&self) -> &TensorLayout<R> {
7064        &self.core.layout
7065    }
7066
7067    /// Return the storage backing this tensor.
7068    ///
7069    /// This is an explicit storage-inspection API for backend glue and tests.
7070    /// Host value inspection should prefer [`TypedTensor::host_data`] when the
7071    /// caller requires host storage.
7072    ///
7073    /// # Panics
7074    ///
7075    /// Panics only if the typed descriptor and its single group owner are
7076    /// internally inconsistent.
7077    ///
7078    /// # Examples
7079    ///
7080    /// ```
7081    /// use tenferro_tensor::{StorageBuffer, TypedTensor};
7082    ///
7083    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7084    /// assert!(matches!(t.buffer(), StorageBuffer::Host(_)));
7085    /// ```
7086    pub fn buffer(&self) -> &StorageBuffer<T>
7087    where
7088        T: 'static,
7089    {
7090        match self
7091            .core
7092            .group
7093            .host_buffer::<T>()
7094            .or_else(|| self.core.group.backend_buffer::<T>())
7095        {
7096            Some(buffer) => buffer,
7097            None => unreachable!("typed tensor group storage mismatch"),
7098        }
7099    }
7100
7101    /// Return the provider family for this tensor when backend-owned.
7102    #[doc(hidden)]
7103    pub fn backend_family(&self) -> Option<&'static str>
7104    where
7105        T: TensorScalar + 'static,
7106    {
7107        self.as_view().backend_family()
7108    }
7109
7110    /// Return the opaque backend buffer for backend-owned tensors.
7111    #[doc(hidden)]
7112    pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>>
7113    where
7114        T: 'static,
7115    {
7116        match self.core.group.backend_buffer::<T>() {
7117            Some(StorageBuffer::Backend(buffer)) => Some(buffer.as_ref()),
7118            Some(StorageBuffer::Host(_)) | None => None,
7119        }
7120    }
7121
7122    /// Return the mutable backend buffer for an exclusive owner borrow.
7123    #[doc(hidden)]
7124    pub fn backend_buffer_mut(&mut self) -> Option<&mut dyn BackendStorage<T>>
7125    where
7126        T: 'static,
7127    {
7128        let buffer = self.core.group.backend_buffer_mut::<T>()?;
7129        match buffer {
7130            StorageBuffer::Host(_) => None,
7131            StorageBuffer::Backend(buffer) => Some(buffer.as_mut()),
7132        }
7133    }
7134
7135    /// Prepare this backend tensor for one provider-native read binding.
7136    #[doc(hidden)]
7137    pub fn prepare_device_read(
7138        &self,
7139        op: &'static str,
7140    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
7141    where
7142        T: TensorScalar + 'static,
7143    {
7144        self.core
7145            .group
7146            .prepare_device_read_for_layout::<T>(&self.core.layout)
7147            .map_err(|error| crate::Error::runtime_state_source(op, error))
7148    }
7149
7150    /// Prepare this backend tensor for one provider-native write binding.
7151    #[doc(hidden)]
7152    pub fn prepare_device_write(
7153        &mut self,
7154        op: &'static str,
7155    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
7156    where
7157        T: TensorScalar + 'static,
7158    {
7159        let layout = self.core.layout.clone();
7160        self.core
7161            .group
7162            .prepare_device_write_for_layout::<T>(&layout)
7163            .map_err(|error| crate::Error::runtime_state_source(op, error))
7164    }
7165
7166    pub(crate) fn buffer_len(&self) -> usize
7167    where
7168        T: 'static,
7169    {
7170        self.core
7171            .group
7172            .group
7173            .descriptor_len(self.core.group.slot)
7174            .unwrap_or_else(|| unreachable!("typed tensor group descriptor mismatch"))
7175    }
7176
7177    /// Return the shared-allocation domain carried by the backend buffer.
7178    ///
7179    /// # Examples
7180    ///
7181    /// ```rust
7182    /// use tenferro_tensor::TypedTensor;
7183    ///
7184    /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
7185    /// assert_eq!(tensor.allocation_domain(), None);
7186    /// # Ok::<(), tenferro_tensor::Error>(())
7187    /// ```
7188    pub fn allocation_domain(&self) -> Option<AllocationDomainId>
7189    where
7190        T: 'static,
7191    {
7192        self.core
7193            .group
7194            .group
7195            .backend_identity(self.core.group.slot)
7196            .map(|(domain, _)| domain)
7197    }
7198
7199    /// Return the stable physical backend allocation identity.
7200    ///
7201    /// # Examples
7202    ///
7203    /// ```rust
7204    /// use tenferro_tensor::TypedTensor;
7205    ///
7206    /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
7207    /// assert_eq!(tensor.allocation_id(), None);
7208    /// # Ok::<(), tenferro_tensor::Error>(())
7209    /// ```
7210    pub fn allocation_id(&self) -> Option<AllocationId>
7211    where
7212        T: 'static,
7213    {
7214        self.core
7215            .group
7216            .group
7217            .backend_identity(self.core.group.slot)
7218            .map(|(_, allocation)| allocation)
7219    }
7220
7221    /// Return placement metadata for this tensor.
7222    ///
7223    /// # Examples
7224    ///
7225    /// ```
7226    /// use tenferro_tensor::{MemoryKind, TypedTensor};
7227    ///
7228    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
7229    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
7230    /// ```
7231    pub fn placement(&self) -> &Placement {
7232        &self.core.placement
7233    }
7234
7235    /// Replace placement metadata without changing the storage buffer.
7236    ///
7237    /// # Examples
7238    ///
7239    /// ```
7240    /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
7241    ///
7242    /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
7243    /// t.set_placement(Placement {
7244    ///     memory_kind: MemoryKind::PinnedHost,
7245    ///     device: None,
7246    ///     cpu_affinity: None,
7247    /// });
7248    /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
7249    /// ```
7250    pub fn set_placement(&mut self, placement: Placement) {
7251        self.core.placement = placement;
7252    }
7253
7254    /// Replace only CPU routing/locality metadata without changing storage.
7255    ///
7256    /// Device, memory kind, backend allocation domain, and allocation identity
7257    /// remain unchanged.
7258    ///
7259    /// # Examples
7260    ///
7261    /// ```rust
7262    /// use tenferro_tensor::{CpuDomainId, TypedTensor};
7263    ///
7264    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0])?;
7265    /// tensor.set_cpu_affinity(Some(CpuDomainId::new(4)));
7266    /// assert_eq!(tensor.placement().cpu_affinity, Some(CpuDomainId::new(4)));
7267    /// # Ok::<(), tenferro_tensor::Error>(())
7268    /// ```
7269    pub fn set_cpu_affinity(&mut self, cpu_affinity: Option<CpuDomainId>) {
7270        self.core.placement.cpu_affinity = cpu_affinity;
7271    }
7272
7273    /// Borrow this tensor as a typed view preserving rank and layout metadata.
7274    ///
7275    /// # Panics
7276    ///
7277    /// Panics only if the typed descriptor and its single group owner are
7278    /// internally inconsistent.
7279    ///
7280    /// # Examples
7281    ///
7282    /// ```rust
7283    /// use tenferro_tensor::{Rank, TypedTensor};
7284    ///
7285    /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
7286    /// let view = tensor.as_view();
7287    /// assert_eq!(view.strides(), &[1, 2]);
7288    /// ```
7289    pub fn as_view(&self) -> TypedTensorView<'_, T, R>
7290    where
7291        T: TensorScalar + 'static,
7292    {
7293        let root = match self.core.group.view::<T>() {
7294            Ok(root) => root,
7295            Err(error) => unreachable!("typed tensor group descriptor mismatch: {error}"),
7296        };
7297        let buffer = if let Some(allocation) = root.backend_allocation() {
7298            TensorStorageRef::Root(allocation)
7299        } else {
7300            TensorStorageRef::Host(self.group_host_slice())
7301        };
7302        let root = Some(root);
7303        TypedTensorView {
7304            buffer,
7305            root,
7306            layout: self.core.layout.clone(),
7307            placement: self.core.placement.clone(),
7308        }
7309    }
7310
7311    /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
7312    ///
7313    /// # Panics
7314    ///
7315    /// Panics only if the typed descriptor and its single group owner are
7316    /// internally inconsistent.
7317    ///
7318    /// # Examples
7319    ///
7320    /// ```rust
7321    /// use tenferro_tensor::TypedTensor;
7322    ///
7323    /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
7324    /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
7325    /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
7326    /// ```
7327    pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
7328    where
7329        T: TensorScalar + 'static,
7330    {
7331        let layout = self.core.layout.clone();
7332        let placement = self.core.placement.clone();
7333        let mut root = match self.core.group.view_mut::<T>() {
7334            Ok(root) => root,
7335            Err(error) => unreachable!("typed tensor group descriptor mismatch: {error}"),
7336        };
7337        let buffer = if let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut() {
7338            TensorStorageRefMut::Backend(buffer.as_mut())
7339        } else {
7340            TensorStorageRefMut::Host(match root.host_slice_mut() {
7341                Ok(slice) => slice,
7342                Err(error) => {
7343                    unreachable!("typed tensor group descriptor is not host-backed: {error}")
7344                }
7345            })
7346        };
7347        TypedTensorViewMut {
7348            buffer,
7349            root: Some(root),
7350            layout,
7351            placement,
7352        }
7353    }
7354
7355    /// Borrow a read-only strided region view over this tensor's backend
7356    /// (device) buffer from explicit layout metadata.
7357    ///
7358    /// This is a metadata-only view: no data is copied or transferred. The
7359    /// layout's reachable element span is validated against the backend
7360    /// buffer's physical length. Host-backed tensors are rejected with an
7361    /// explicit backend error; host regions are expressed with
7362    /// [`TypedTensorView::from_slice`] over host storage instead.
7363    ///
7364    /// # Examples
7365    ///
7366    /// ```rust
7367    /// use tenferro_tensor::TypedTensor;
7368    ///
7369    /// // Host tensors are rejected: this constructor is for backend buffers.
7370    /// let host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
7371    /// let err = host.backend_region_view(vec![2, 2], vec![1, 2], 0).unwrap_err();
7372    /// assert!(err.to_string().contains("backend"));
7373    /// ```
7374    /// # Errors
7375    ///
7376    /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
7377    /// backend region views require a backend buffer. It returns
7378    /// [`crate::Error::Validation`] with
7379    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
7380    /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
7381    /// when the region exceeds the backend buffer, or
7382    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
7383    /// arithmetic overflow.
7384    pub fn backend_region_view(
7385        &self,
7386        shape: Vec<usize>,
7387        strides: Vec<isize>,
7388        offset: isize,
7389    ) -> crate::Result<TypedTensorView<'_, T, DynRank>>
7390    where
7391        T: TensorScalar + 'static,
7392    {
7393        let op = "TypedTensor::backend_region_view";
7394        let root = self.core.group.view_dyn::<T>()?;
7395        let Some(allocation) = root.backend_allocation() else {
7396            return Err(crate::Error::runtime_state(
7397                op,
7398                "expected a backend (device) allocation; host tensors use \
7399                 TypedTensorView::from_slice over host storage",
7400            ));
7401        };
7402        let element_len = allocation
7403            .root_extent()
7404            .byte_len()
7405            .checked_div(size_of::<T>())
7406            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
7407        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, element_len)
7408            .map_err(|err| tensor_layout_error(op, err))?;
7409        Ok(TypedTensorView {
7410            buffer: TensorStorageRef::Root(allocation),
7411            root: Some(root),
7412            layout,
7413            placement: self.core.placement.clone(),
7414        })
7415    }
7416
7417    /// Borrow a mutable strided region view over this tensor's backend
7418    /// (device) buffer from explicit layout metadata.
7419    ///
7420    /// This is the mutable counterpart of
7421    /// [`TypedTensor::backend_region_view`]. The layout's reachable element
7422    /// span is validated against the backend buffer's physical length, and
7423    /// layouts whose logical elements alias the same physical element are
7424    /// rejected. Host-backed tensors are rejected with an explicit backend
7425    /// error; mutable host regions must go through
7426    /// [`TypedTensorViewMut::try_multi_slice_mut`] or host constructors.
7427    ///
7428    /// The returned view borrows the tensor's backend owner exclusively for its
7429    /// lifetime. This keeps write authority tied to the owner; a second mutable
7430    /// region view must be created only after the first borrow ends.
7431    ///
7432    /// # Examples
7433    ///
7434    /// ```rust
7435    /// use tenferro_tensor::TypedTensor;
7436    ///
7437    /// // Host tensors are rejected: this constructor is for backend buffers.
7438    /// let mut host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
7439    /// let err = host.backend_region_view_mut(vec![2, 2], vec![1, 2], 0).unwrap_err();
7440    /// assert!(err.to_string().contains("backend"));
7441    /// ```
7442    /// # Errors
7443    ///
7444    /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
7445    /// mutable backend region views require a backend buffer. It returns
7446    /// [`crate::Error::Validation`] with
7447    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
7448    /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
7449    /// when the region exceeds the backend buffer,
7450    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
7451    /// logical elements alias, or
7452    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
7453    /// arithmetic overflow.
7454    pub fn backend_region_view_mut(
7455        &mut self,
7456        shape: Vec<usize>,
7457        strides: Vec<isize>,
7458        offset: isize,
7459    ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>>
7460    where
7461        T: TensorScalar + 'static,
7462    {
7463        let op = "TypedTensor::backend_region_view_mut";
7464        let mut root = self.core.group.view_mut_dyn::<T>()?;
7465        let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut() else {
7466            return Err(crate::Error::runtime_state(
7467                op,
7468                "expected a backend (device) buffer; mutable host regions use \
7469                 TypedTensorViewMut host constructors or try_multi_slice_mut",
7470            ));
7471        };
7472        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
7473            .map_err(|err| tensor_layout_error(op, err))?;
7474        layout
7475            .validate_mutable_no_overlap()
7476            .map_err(|err| tensor_layout_error(op, err))?;
7477        Ok(TypedTensorViewMut {
7478            buffer: TensorStorageRefMut::Backend(buffer.as_mut()),
7479            root: Some(root),
7480            layout,
7481            placement: self.core.placement.clone(),
7482        })
7483    }
7484
7485    /// Consume this tensor and return its layout metadata.
7486    ///
7487    /// # Examples
7488    ///
7489    /// ```
7490    /// use tenferro_tensor::TypedTensor;
7491    ///
7492    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7493    /// assert!(t.into_layout().is_compact_col_major().unwrap());
7494    /// ```
7495    pub fn into_layout(self) -> TensorLayout<R> {
7496        self.core.layout
7497    }
7498
7499    /// Consume this tensor and return its storage, layout, and placement.
7500    ///
7501    /// # Examples
7502    ///
7503    /// ```
7504    /// use tenferro_tensor::{StorageBuffer, TypedTensor};
7505    ///
7506    /// # fn main() -> tenferro_tensor::Result<()> {
7507    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
7508    /// let (buffer, layout, placement) = t.into_parts()?;
7509    /// assert!(matches!(buffer, StorageBuffer::Host(_)));
7510    /// assert_eq!(layout.shape(), &[2]);
7511    /// assert!(placement.device.is_none());
7512    /// # Ok(())
7513    /// # }
7514    /// ```
7515    ///
7516    /// # Errors
7517    ///
7518    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7519    /// storage; download it before extracting host storage.
7520    pub fn into_parts(self) -> crate::Result<(StorageBuffer<T>, TensorLayout<R>, Placement)>
7521    where
7522        T: TensorScalar,
7523    {
7524        let TypedTensor {
7525            core:
7526                TensorCore {
7527                    group,
7528                    layout,
7529                    placement,
7530                },
7531            ..
7532        } = self;
7533        let data = group.into_host_vec::<T>()?;
7534        Ok((StorageBuffer::Host(data), layout, placement))
7535    }
7536}
7537
7538impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R> {
7539    /// Create a tensor from a column-major buffer.
7540    ///
7541    /// # Examples
7542    ///
7543    /// ```
7544    /// use tenferro_tensor::TypedTensor;
7545    ///
7546    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
7547    /// assert_eq!(t.get(&[1, 0])?, &2.0);
7548    /// # Ok::<(), tenferro_tensor::Error>(())
7549    /// ```
7550    /// # Errors
7551    ///
7552    /// Returns [`crate::Error::Validation`] with
7553    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
7554    /// the shape product differs from `data.len()`, or
7555    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
7556    /// arithmetic overflows.
7557    ///
7558    /// Arrays and array references must match a static rank at compile time.
7559    /// Vectors and slices instead return `ValidationError::RankMismatch` when
7560    /// their length differs from the static rank. Dynamic rank accepts arrays
7561    /// of any length.
7562    ///
7563    /// ```
7564    /// use tenferro_tensor::{Rank, TypedTensor};
7565    /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([1, 2], vec![3., 4.])?;
7566    /// assert_eq!(tensor.shape(), &[1, 2]);
7567    /// # Ok::<(), tenferro_tensor::Error>(())
7568    /// ```
7569    ///
7570    /// ```compile_fail
7571    /// use tenferro_tensor::{Rank, TypedTensor};
7572    /// let _ = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2], vec![3., 4.]);
7573    /// ```
7574    pub fn from_vec_col_major(
7575        shape: impl tenferro_tensor_core::IntoRankShape<R>,
7576        data: Vec<T>,
7577    ) -> crate::Result<Self> {
7578        typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
7579    }
7580
7581    /// Construct a backend-pooled host tensor with weak final-owner reclamation.
7582    /// Explicit Vec extraction disarms reclamation; aliases and retained groups
7583    /// keep the original root alive. The recycler must not acquire session locks.
7584    ///
7585    /// # Errors
7586    /// Returns validation errors for invalid shape/length and runtime-state errors
7587    /// if the freshly created host root cannot attach its recycler.
7588    #[doc(hidden)]
7589    pub fn from_vec_col_major_with_recycler(
7590        shape: impl tenferro_tensor_core::IntoRankShape<R>,
7591        data: Vec<T>,
7592        recycler: std::sync::Weak<dyn crate::HostBufferRecycler<T>>,
7593    ) -> crate::Result<Self> {
7594        let mut tensor = Self::from_vec_col_major(shape, data)?;
7595        let allocation_index = tensor.core.group.allocation_index;
7596        tensor
7597            .core
7598            .group
7599            .group
7600            .set_host_recycler(allocation_index.index(), recycler)
7601            .map_err(|error| crate::Error::runtime_state_source("pooled host tensor", error))?;
7602        Ok(tensor)
7603    }
7604
7605    /// Make an explicit owning copy of this tensor.
7606    ///
7607    /// Host storage is copied into a fresh allocation. Backend-owned storage
7608    /// must be duplicated by the active backend, so this generic tensor layer
7609    /// reports that operation as unsupported.
7610    ///
7611    /// # Errors
7612    ///
7613    /// Returns [`crate::Error::RuntimeState`] when host data cannot be
7614    /// borrowed, or [`ValidationError::InvalidArgument`] when the new group
7615    /// cannot be constructed.
7616    pub fn duplicate(&self) -> crate::Result<Self> {
7617        self.as_view().duplicate()
7618    }
7619
7620    /// Consume this tensor and return its owned column-major host buffer.
7621    ///
7622    /// # Examples
7623    ///
7624    /// ```
7625    /// use tenferro_tensor::TypedTensor;
7626    ///
7627    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7628    /// let (shape, data) = t.into_vec_col_major().unwrap();
7629    /// assert_eq!(shape, vec![2]);
7630    /// assert_eq!(data, vec![1.0, 2.0]);
7631    /// ```
7632    /// # Errors
7633    ///
7634    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7635    /// storage; download it before exporting a host `Vec`.
7636    pub fn into_vec_col_major(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
7637        let shape = self.shape().to_vec();
7638        if self.core.group.backend_buffer::<T>().is_some() {
7639            return Err(crate::Error::runtime_state(
7640                "into_vec_col_major",
7641                "backend buffers cannot be exported as host Vec",
7642            ));
7643        }
7644        Ok((shape, self.core.group.into_host_vec::<T>()?))
7645    }
7646
7647    /// Consume this tensor and return its owned host data without rebuilding
7648    /// shape metadata. This is intended for ownership-preserving buffer-pool
7649    /// handoff; callers that need the shape should use [`Self::into_vec_col_major`].
7650    ///
7651    /// # Errors
7652    ///
7653    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7654    /// storage.
7655    pub fn into_host_vec(self) -> crate::Result<Vec<T>> {
7656        if self.core.group.backend_buffer::<T>().is_some() {
7657            return Err(crate::Error::runtime_state(
7658                "into_host_vec",
7659                "backend buffers cannot be exported as host Vec",
7660            ));
7661        }
7662        self.core.group.into_host_vec::<T>()
7663    }
7664
7665    /// Borrow the host buffer.
7666    ///
7667    /// # Examples
7668    ///
7669    /// ```rust
7670    /// use tenferro_tensor::TypedTensor;
7671    ///
7672    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7673    /// assert_eq!(t.host_data()?, &[1.0, 2.0]);
7674    /// # Ok::<(), tenferro_tensor::Error>(())
7675    /// ```
7676    /// # Errors
7677    ///
7678    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7679    /// storage; download it before borrowing host data.
7680    pub fn host_data(&self) -> crate::Result<&[T]> {
7681        self.core.group.host_slice::<T>()
7682    }
7683
7684    /// Borrow compact host-visible storage through one synchronization guard.
7685    #[doc(hidden)]
7686    pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
7687    where
7688        T: TensorScalar + 'static,
7689    {
7690        self.as_view().with_host_read(f)
7691    }
7692
7693    /// Borrow compact host-visible storage through one exclusive write guard.
7694    #[doc(hidden)]
7695    pub fn with_host_write<U>(&mut self, f: impl FnOnce(&mut [T]) -> U) -> crate::Result<U>
7696    where
7697        T: TensorScalar + 'static,
7698    {
7699        let slot = self.core.group.slot;
7700        let mut view = self
7701            .core
7702            .group
7703            .group
7704            .view_mut::<T, R>(slot)
7705            .map_err(|error| group_error("TypedTensor::with_host_write", error))?;
7706        let mut prepared = view.prepare_host_write().map_err(|error| {
7707            crate::Error::runtime_state("TypedTensor::with_host_write", error.to_string())
7708        })?;
7709        let slice = prepared.as_slice_mut().ok_or_else(|| {
7710            crate::Error::unsupported(
7711                "TypedTensor::with_host_write",
7712                "host guard access requires a compact descriptor",
7713            )
7714        })?;
7715        Ok(f(slice))
7716    }
7717
7718    /// View the tensor data as a flat slice.
7719    ///
7720    /// This is an alias for `host_data()` for API consistency with
7721    /// `Tensor::as_slice`.
7722    ///
7723    /// # Examples
7724    ///
7725    /// ```
7726    /// use tenferro_tensor::TypedTensor;
7727    ///
7728    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7729    /// assert_eq!(t.as_slice()?, &[1.0, 2.0]);
7730    /// # Ok::<(), tenferro_tensor::Error>(())
7731    /// ```
7732    /// # Errors
7733    ///
7734    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7735    /// storage; download it before borrowing it as a host slice.
7736    pub fn as_slice(&self) -> crate::Result<&[T]> {
7737        self.host_data()
7738    }
7739
7740    /// Mutably borrow the host buffer.
7741    ///
7742    /// # Examples
7743    ///
7744    /// ```rust
7745    /// use tenferro_tensor::TypedTensor;
7746    ///
7747    /// let mut t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7748    /// t.host_data_mut()?[0] = 3.0;
7749    /// assert_eq!(t.host_data()?, &[3.0, 0.0]);
7750    /// # Ok::<(), tenferro_tensor::Error>(())
7751    /// ```
7752    /// # Errors
7753    ///
7754    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7755    /// storage; download it before mutably borrowing host data.
7756    pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
7757        self.core.group.host_slice_mut::<T>()
7758    }
7759
7760    fn group_host_slice(&self) -> &[T] {
7761        self.core
7762            .group
7763            .view::<T>()
7764            .ok()
7765            .and_then(|view| view.host_slice().ok())
7766            .unwrap_or_default()
7767    }
7768
7769    fn group_host_slice_mut(&mut self) -> &mut [T] {
7770        self.core
7771            .group
7772            .view_mut::<T>()
7773            .ok()
7774            .and_then(|mut view| view.host_slice_mut().ok())
7775            .unwrap_or_default()
7776    }
7777
7778    /// Compute the linear physical-buffer offset for a logical index.
7779    ///
7780    /// # Examples
7781    ///
7782    /// ```rust
7783    /// use tenferro_tensor::TypedTensor;
7784    ///
7785    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
7786    /// assert_eq!(t.linear_offset(&[1, 2])?, 5);
7787    /// # Ok::<(), tenferro_tensor::Error>(())
7788    /// ```
7789    /// # Errors
7790    ///
7791    /// Returns [`crate::Error::Validation`] with
7792    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7793    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7794    /// when an index is outside its axis extent, or
7795    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7796    /// arithmetic overflows.
7797    pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7798        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
7799    }
7800
7801    /// Compute the physical element offset for a logical index.
7802    ///
7803    /// # Examples
7804    ///
7805    /// ```rust
7806    /// use tenferro_tensor::TypedTensor;
7807    ///
7808    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
7809    /// assert_eq!(t.layout_linear_offset(&[1, 2])?, 5);
7810    /// # Ok::<(), tenferro_tensor::Error>(())
7811    /// ```
7812    /// # Errors
7813    ///
7814    /// Returns [`crate::Error::Validation`] with
7815    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7816    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7817    /// when an index is outside its axis extent, or
7818    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7819    /// arithmetic overflows.
7820    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7821        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::layout_linear_offset")
7822    }
7823
7824    /// Return whether this owned tensor is compact column-major.
7825    ///
7826    /// # Examples
7827    ///
7828    /// ```rust
7829    /// use tenferro_tensor::TypedTensor;
7830    ///
7831    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7832    /// assert!(t.is_col_major_contiguous()?);
7833    /// # Ok::<(), tenferro_tensor::Error>(())
7834    /// ```
7835    /// # Errors
7836    ///
7837    /// Returns [`crate::Error::Validation`] with
7838    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7839    /// compactness arithmetic overflows.
7840    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
7841        self.core
7842            .layout
7843            .is_compact_col_major()
7844            .map_err(|err| tensor_layout_error("TypedTensor::is_col_major_contiguous", err))
7845    }
7846
7847    /// Return a compact string summary of this tensor's layout metadata.
7848    ///
7849    /// # Examples
7850    ///
7851    /// ```rust
7852    /// use tenferro_tensor::TypedTensor;
7853    ///
7854    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7855    /// assert!(t.layout_summary().contains("shape=[2]"));
7856    /// # Ok::<(), tenferro_tensor::Error>(())
7857    /// ```
7858    pub fn layout_summary(&self) -> String {
7859        layout_summary(
7860            self.shape(),
7861            self.core.layout.strides(),
7862            self.core.layout.offset(),
7863        )
7864    }
7865
7866    /// Assert this tensor is compact column-major.
7867    ///
7868    /// # Examples
7869    ///
7870    /// ```rust
7871    /// use tenferro_tensor::TypedTensor;
7872    ///
7873    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7874    /// t.assert_col_major_contiguous()?;
7875    /// # Ok::<(), tenferro_tensor::Error>(())
7876    /// ```
7877    /// # Errors
7878    ///
7879    /// Returns [`crate::Error::Validation`] with
7880    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7881    /// compactness arithmetic overflows, or
7882    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7883    /// tensor is not compact column-major.
7884    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
7885        assert_layout_col_major_contiguous(
7886            self.is_col_major_contiguous()?,
7887            self.shape(),
7888            self.core.layout.strides(),
7889            self.core.layout.offset(),
7890            "TypedTensor::assert_col_major_contiguous",
7891        )
7892    }
7893
7894    /// Borrow a single element by multi-index.
7895    ///
7896    /// # Examples
7897    ///
7898    /// ```rust
7899    /// use tenferro_tensor::TypedTensor;
7900    ///
7901    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7902    /// assert_eq!(t.get(&[1])?, &2.0);
7903    /// # Ok::<(), tenferro_tensor::Error>(())
7904    /// ```
7905    /// # Errors
7906    ///
7907    /// Returns [`crate::Error::Validation`] with
7908    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7909    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7910    /// when an index is outside its axis extent, or
7911    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7912    /// computed offset is outside the host buffer. It returns
7913    /// [`crate::Error::RuntimeState`] when the tensor uses backend storage.
7914    pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
7915        let off = self.linear_offset(indices)?;
7916        self.host_data()?.get(off).ok_or_else(|| {
7917            crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
7918        })
7919    }
7920
7921    /// Mutably borrow a single element by multi-index.
7922    ///
7923    /// # Examples
7924    ///
7925    /// ```rust
7926    /// use tenferro_tensor::TypedTensor;
7927    ///
7928    /// let mut t = TypedTensor::<f64>::zeros(vec![1]).unwrap();
7929    /// *t.get_mut(&[0])? = 7.0;
7930    /// assert_eq!(t.host_data()?, &[7.0]);
7931    /// # Ok::<(), tenferro_tensor::Error>(())
7932    /// ```
7933    /// # Errors
7934    ///
7935    /// Returns [`crate::Error::Validation`] with
7936    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7937    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7938    /// when an index is outside its axis extent, or
7939    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7940    /// computed offset is outside the host buffer. It returns
7941    /// [`crate::Error::RuntimeState`] when the tensor uses backend storage.
7942    pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
7943        let off = self.linear_offset(indices)?;
7944        self.host_data_mut()?.get_mut(off).ok_or_else(|| {
7945            crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
7946        })
7947    }
7948}
7949
7950impl<R: TensorRank> TypedTensor<Complex32, R> {
7951    /// Borrow this tensor as an interleaved `f32` view without copying.
7952    ///
7953    /// # Errors
7954    ///
7955    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7956    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
7957    /// invalid tensor layout.
7958    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f32, DynRank>> {
7959        self.as_view().as_real_view()
7960    }
7961
7962    /// Borrow this tensor mutably as an interleaved `f32` view without copying.
7963    ///
7964    /// # Errors
7965    ///
7966    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7967    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
7968    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
7969    /// invalid representation metadata.
7970    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
7971        let op = "TypedTensor::as_real_view_mut";
7972        validate_representation_pair(op, DType::C32, DType::F32)?;
7973        let layout = reinterpret_complex_to_real_layout(
7974            self.shape(),
7975            self.core.layout.strides(),
7976            self.core.layout.offset(),
7977            self.buffer_len(),
7978            op,
7979        )?;
7980        layout
7981            .validate_mutable_no_overlap()
7982            .map_err(|err| tensor_layout_error(op, err))?;
7983        if self.backend_buffer().is_some() {
7984            return Err(crate::Error::unsupported(
7985                op,
7986                "backend representation reinterpretation is enabled by the provider phases",
7987            ));
7988        }
7989        let placement = self.core.placement.clone();
7990        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(
7991            self.group_host_slice_mut(),
7992            op,
7993        )?);
7994        Ok(TypedTensorViewMut {
7995            buffer,
7996            root: None,
7997            layout,
7998            placement,
7999        })
8000    }
8001
8002    /// Consume this tensor and reinterpret its owner as `f32` without copying.
8003    ///
8004    /// A failed operation returns the unchanged owner through
8005    /// [`ReinterpretError::into_owner`].
8006    ///
8007    /// # Errors
8008    ///
8009    /// Returns [`ReinterpretError::error`] containing
8010    /// [`ValidationError::InvalidArgument`] or
8011    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
8012    /// owner.
8013    pub fn into_real(self) -> Result<TypedTensor<f32, DynRank>, ReinterpretError<Self>> {
8014        let op = "TypedTensor::into_real";
8015        if let Err(error) = validate_representation_pair(op, DType::C32, DType::F32) {
8016            return Err(ReinterpretError::new(self, error));
8017        }
8018        let source_shape = self.shape().to_vec();
8019        let source_strides = self.core.layout.strides().to_vec();
8020        let source_offset = self.core.layout.offset();
8021        let target_layout = match reinterpret_complex_to_real_layout(
8022            &source_shape,
8023            &source_strides,
8024            source_offset,
8025            self.buffer_len(),
8026            op,
8027        ) {
8028            Ok(layout) => layout,
8029            Err(error) => return Err(ReinterpretError::new(self, error)),
8030        };
8031        let TypedTensor {
8032            core:
8033                TensorCore {
8034                    group,
8035                    layout: source_layout,
8036                    placement,
8037                },
8038            ..
8039        } = self;
8040        match group.reinterpret::<Complex32, f32>(
8041            target_layout.shape().to_vec(),
8042            target_layout.strides().to_vec(),
8043            target_layout.offset(),
8044        ) {
8045            Ok(group) => Ok(TypedTensor {
8046                core: TensorCore {
8047                    group,
8048                    layout: target_layout,
8049                    placement,
8050                },
8051                _scalar: PhantomData,
8052            }),
8053            Err((group, error)) => Err(ReinterpretError::new(
8054                TypedTensor {
8055                    core: TensorCore {
8056                        group,
8057                        layout: source_layout,
8058                        placement,
8059                    },
8060                    _scalar: PhantomData,
8061                },
8062                error,
8063            )),
8064        }
8065    }
8066}
8067
8068impl<R: TensorRank> TypedTensor<Complex64, R> {
8069    /// Borrow this tensor as an interleaved `f64` view without copying.
8070    ///
8071    /// # Errors
8072    ///
8073    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
8074    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
8075    /// invalid tensor layout.
8076    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f64, DynRank>> {
8077        self.as_view().as_real_view()
8078    }
8079
8080    /// Borrow this tensor mutably as an interleaved `f64` view without copying.
8081    ///
8082    /// # Errors
8083    ///
8084    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
8085    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
8086    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
8087    /// invalid representation metadata.
8088    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
8089        let op = "TypedTensor::as_real_view_mut";
8090        validate_representation_pair(op, DType::C64, DType::F64)?;
8091        let layout = reinterpret_complex_to_real_layout(
8092            self.shape(),
8093            self.core.layout.strides(),
8094            self.core.layout.offset(),
8095            self.buffer_len(),
8096            op,
8097        )?;
8098        layout
8099            .validate_mutable_no_overlap()
8100            .map_err(|err| tensor_layout_error(op, err))?;
8101        if self.backend_buffer().is_some() {
8102            return Err(crate::Error::unsupported(
8103                op,
8104                "backend representation reinterpretation is enabled by the provider phases",
8105            ));
8106        }
8107        let placement = self.core.placement.clone();
8108        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(
8109            self.group_host_slice_mut(),
8110            op,
8111        )?);
8112        Ok(TypedTensorViewMut {
8113            buffer,
8114            root: None,
8115            layout,
8116            placement,
8117        })
8118    }
8119
8120    /// Consume this tensor and reinterpret its owner as `f64` without copying.
8121    ///
8122    /// A failed operation returns the unchanged owner through
8123    /// [`ReinterpretError::into_owner`].
8124    ///
8125    /// # Errors
8126    ///
8127    /// Returns [`ReinterpretError::error`] containing
8128    /// [`ValidationError::InvalidArgument`] or
8129    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
8130    /// owner.
8131    pub fn into_real(self) -> Result<TypedTensor<f64, DynRank>, ReinterpretError<Self>> {
8132        let op = "TypedTensor::into_real";
8133        if let Err(error) = validate_representation_pair(op, DType::C64, DType::F64) {
8134            return Err(ReinterpretError::new(self, error));
8135        }
8136        let source_shape = self.shape().to_vec();
8137        let source_strides = self.core.layout.strides().to_vec();
8138        let source_offset = self.core.layout.offset();
8139        let target_layout = match reinterpret_complex_to_real_layout(
8140            &source_shape,
8141            &source_strides,
8142            source_offset,
8143            self.buffer_len(),
8144            op,
8145        ) {
8146            Ok(layout) => layout,
8147            Err(error) => return Err(ReinterpretError::new(self, error)),
8148        };
8149        let TypedTensor {
8150            core:
8151                TensorCore {
8152                    group,
8153                    layout: source_layout,
8154                    placement,
8155                },
8156            ..
8157        } = self;
8158        match group.reinterpret::<Complex64, f64>(
8159            target_layout.shape().to_vec(),
8160            target_layout.strides().to_vec(),
8161            target_layout.offset(),
8162        ) {
8163            Ok(group) => Ok(TypedTensor {
8164                core: TensorCore {
8165                    group,
8166                    layout: target_layout,
8167                    placement,
8168                },
8169                _scalar: PhantomData,
8170            }),
8171            Err((group, error)) => Err(ReinterpretError::new(
8172                TypedTensor {
8173                    core: TensorCore {
8174                        group,
8175                        layout: source_layout,
8176                        placement,
8177                    },
8178                    _scalar: PhantomData,
8179                },
8180                error,
8181            )),
8182        }
8183    }
8184}
8185
8186impl<R: TensorRank> TypedTensor<f32, R> {
8187    /// Borrow this tensor as a complex view without copying.
8188    ///
8189    /// # Errors
8190    ///
8191    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
8192    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
8193    /// invalid tensor layout.
8194    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex32, DynRank>> {
8195        self.as_view().as_complex_view()
8196    }
8197
8198    /// Borrow this tensor mutably as a complex view without copying.
8199    ///
8200    /// # Errors
8201    ///
8202    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
8203    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
8204    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
8205    /// invalid representation metadata.
8206    pub fn as_complex_view_mut(
8207        &mut self,
8208    ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
8209        let op = "TypedTensor::as_complex_view_mut";
8210        validate_representation_pair(op, DType::F32, DType::C32)?;
8211        let layout = reinterpret_real_to_complex_layout(
8212            self.shape(),
8213            self.core.layout.strides(),
8214            self.core.layout.offset(),
8215            self.buffer_len(),
8216            op,
8217        )?;
8218        layout
8219            .validate_mutable_no_overlap()
8220            .map_err(|err| tensor_layout_error(op, err))?;
8221        if self.backend_buffer().is_some() {
8222            return Err(crate::Error::unsupported(
8223                op,
8224                "backend representation reinterpretation is enabled by the provider phases",
8225            ));
8226        }
8227        let placement = self.core.placement.clone();
8228        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(
8229            self.group_host_slice_mut(),
8230            op,
8231        )?);
8232        Ok(TypedTensorViewMut {
8233            buffer,
8234            root: None,
8235            layout,
8236            placement,
8237        })
8238    }
8239
8240    /// Consume this tensor and reinterpret its owner as `Complex32` without copying.
8241    ///
8242    /// The compact source must have an even physical element count. A failed
8243    /// operation returns the unchanged owner through
8244    /// [`ReinterpretError::into_owner`].
8245    ///
8246    /// # Errors
8247    ///
8248    /// Returns [`ReinterpretError::error`] containing
8249    /// [`ValidationError::InvalidArgument`] or
8250    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
8251    /// owner.
8252    pub fn into_complex(self) -> Result<TypedTensor<Complex32, DynRank>, ReinterpretError<Self>> {
8253        let op = "TypedTensor::into_complex";
8254        if let Err(error) = validate_representation_pair(op, DType::F32, DType::C32) {
8255            return Err(ReinterpretError::new(self, error));
8256        }
8257        if !self.buffer_len().is_multiple_of(2) {
8258            return Err(ReinterpretError::new(
8259                self,
8260                crate::Error::invalid_argument(
8261                    op,
8262                    "buffer",
8263                    "the owned real buffer must contain an even number of elements",
8264                ),
8265            ));
8266        }
8267        let source_shape = self.shape().to_vec();
8268        let source_strides = self.core.layout.strides().to_vec();
8269        let source_offset = self.core.layout.offset();
8270        let target_layout = match reinterpret_real_to_complex_layout(
8271            &source_shape,
8272            &source_strides,
8273            source_offset,
8274            self.buffer_len(),
8275            op,
8276        ) {
8277            Ok(layout) => layout,
8278            Err(error) => return Err(ReinterpretError::new(self, error)),
8279        };
8280        let TypedTensor {
8281            core:
8282                TensorCore {
8283                    group,
8284                    layout: source_layout,
8285                    placement,
8286                },
8287            ..
8288        } = self;
8289        match group.reinterpret::<f32, Complex32>(
8290            target_layout.shape().to_vec(),
8291            target_layout.strides().to_vec(),
8292            target_layout.offset(),
8293        ) {
8294            Ok(group) => Ok(TypedTensor {
8295                core: TensorCore {
8296                    group,
8297                    layout: target_layout,
8298                    placement,
8299                },
8300                _scalar: PhantomData,
8301            }),
8302            Err((group, error)) => Err(ReinterpretError::new(
8303                TypedTensor {
8304                    core: TensorCore {
8305                        group,
8306                        layout: source_layout,
8307                        placement,
8308                    },
8309                    _scalar: PhantomData,
8310                },
8311                error,
8312            )),
8313        }
8314    }
8315}
8316
8317impl<R: TensorRank> TypedTensor<f64, R> {
8318    /// Borrow this tensor as a complex view without copying.
8319    ///
8320    /// # Errors
8321    ///
8322    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
8323    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
8324    /// invalid tensor layout.
8325    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex64, DynRank>> {
8326        self.as_view().as_complex_view()
8327    }
8328
8329    /// Borrow this tensor mutably as a complex view without copying.
8330    ///
8331    /// # Errors
8332    ///
8333    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
8334    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
8335    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
8336    /// invalid representation metadata.
8337    pub fn as_complex_view_mut(
8338        &mut self,
8339    ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
8340        let op = "TypedTensor::as_complex_view_mut";
8341        validate_representation_pair(op, DType::F64, DType::C64)?;
8342        let layout = reinterpret_real_to_complex_layout(
8343            self.shape(),
8344            self.core.layout.strides(),
8345            self.core.layout.offset(),
8346            self.buffer_len(),
8347            op,
8348        )?;
8349        layout
8350            .validate_mutable_no_overlap()
8351            .map_err(|err| tensor_layout_error(op, err))?;
8352        if self.backend_buffer().is_some() {
8353            return Err(crate::Error::unsupported(
8354                op,
8355                "backend representation reinterpretation is enabled by the provider phases",
8356            ));
8357        }
8358        let placement = self.core.placement.clone();
8359        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(
8360            self.group_host_slice_mut(),
8361            op,
8362        )?);
8363        Ok(TypedTensorViewMut {
8364            buffer,
8365            root: None,
8366            layout,
8367            placement,
8368        })
8369    }
8370
8371    /// Consume this tensor and reinterpret its owner as `Complex64` without copying.
8372    ///
8373    /// The compact source must have an even physical element count. A failed
8374    /// operation returns the unchanged owner through
8375    /// [`ReinterpretError::into_owner`].
8376    ///
8377    /// # Errors
8378    ///
8379    /// Returns [`ReinterpretError::error`] containing
8380    /// [`ValidationError::InvalidArgument`] or
8381    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
8382    /// owner.
8383    pub fn into_complex(self) -> Result<TypedTensor<Complex64, DynRank>, ReinterpretError<Self>> {
8384        let op = "TypedTensor::into_complex";
8385        if let Err(error) = validate_representation_pair(op, DType::F64, DType::C64) {
8386            return Err(ReinterpretError::new(self, error));
8387        }
8388        if !self.buffer_len().is_multiple_of(2) {
8389            return Err(ReinterpretError::new(
8390                self,
8391                crate::Error::invalid_argument(
8392                    op,
8393                    "buffer",
8394                    "the owned real buffer must contain an even number of elements",
8395                ),
8396            ));
8397        }
8398        let source_shape = self.shape().to_vec();
8399        let source_strides = self.core.layout.strides().to_vec();
8400        let source_offset = self.core.layout.offset();
8401        let target_layout = match reinterpret_real_to_complex_layout(
8402            &source_shape,
8403            &source_strides,
8404            source_offset,
8405            self.buffer_len(),
8406            op,
8407        ) {
8408            Ok(layout) => layout,
8409            Err(error) => return Err(ReinterpretError::new(self, error)),
8410        };
8411        let TypedTensor {
8412            core:
8413                TensorCore {
8414                    group,
8415                    layout: source_layout,
8416                    placement,
8417                },
8418            ..
8419        } = self;
8420        match group.reinterpret::<f64, Complex64>(
8421            target_layout.shape().to_vec(),
8422            target_layout.strides().to_vec(),
8423            target_layout.offset(),
8424        ) {
8425            Ok(group) => Ok(TypedTensor {
8426                core: TensorCore {
8427                    group,
8428                    layout: target_layout,
8429                    placement,
8430                },
8431                _scalar: PhantomData,
8432            }),
8433            Err((group, error)) => Err(ReinterpretError::new(
8434                TypedTensor {
8435                    core: TensorCore {
8436                        group,
8437                        layout: source_layout,
8438                        placement,
8439                    },
8440                    _scalar: PhantomData,
8441                },
8442                error,
8443            )),
8444        }
8445    }
8446}
8447
8448impl Tensor {
8449    /// Borrow a complex tensor as its sealed interleaved real representation.
8450    ///
8451    /// # Errors
8452    ///
8453    /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
8454    /// [`ValidationError::ViewOutOfBounds`] or
8455    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
8456    pub fn as_real_view(&self) -> crate::Result<TensorView<'_>> {
8457        match self.dtype() {
8458            DType::C32 => self
8459                .as_typed::<Complex<f32>>()
8460                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
8461                .as_real_view()
8462                .map(TensorView::F32),
8463            DType::C64 => self
8464                .as_typed::<Complex<f64>>()
8465                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
8466                .as_real_view()
8467                .map(TensorView::F64),
8468            other => Err(crate::Error::unsupported_dtype_conversion(
8469                "Tensor::as_real_view",
8470                other,
8471                DType::F32,
8472                "only complex tensors have a sealed real representation view",
8473            )),
8474        }
8475    }
8476
8477    /// Borrow a complex tensor mutably as its sealed interleaved real representation.
8478    ///
8479    /// # Errors
8480    ///
8481    /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
8482    /// [`ValidationError::ViewOutOfBounds`] or
8483    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
8484    pub fn as_real_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
8485        match self.dtype() {
8486            DType::C32 => self
8487                .as_typed_mut::<Complex<f32>>()
8488                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
8489                .as_real_view_mut()
8490                .map(TensorViewMut::F32),
8491            DType::C64 => self
8492                .as_typed_mut::<Complex<f64>>()
8493                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
8494                .as_real_view_mut()
8495                .map(TensorViewMut::F64),
8496            other => Err(crate::Error::unsupported_dtype_conversion(
8497                "Tensor::as_real_view_mut",
8498                other,
8499                DType::F32,
8500                "only complex tensors have a sealed real representation view",
8501            )),
8502        }
8503    }
8504
8505    /// Consume a complex tensor and reinterpret its owner as real without copying.
8506    ///
8507    /// # Errors
8508    ///
8509    /// Returns [`ReinterpretError::error`] containing
8510    /// [`ValidationError::InvalidArgument`] or
8511    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
8512    /// owner.
8513    pub fn into_real(self) -> Result<Self, ReinterpretError<Self>> {
8514        let dtype = self.dtype();
8515        match dtype {
8516            DType::C32 => {
8517                let tensor = self
8518                    .into_preset::<Complex<f32>>()
8519                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
8520                tensor
8521                    .into_real()
8522                    .map(Tensor::from_typed::<f32>)
8523                    .map_err(|error| {
8524                        let (owner, error) = error.into_parts();
8525                        ReinterpretError::new(Tensor::from_typed::<Complex<f32>>(owner), error)
8526                    })
8527            }
8528            DType::C64 => {
8529                let tensor = self
8530                    .into_preset::<Complex<f64>>()
8531                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
8532                tensor
8533                    .into_real()
8534                    .map(Tensor::from_typed::<f64>)
8535                    .map_err(|error| {
8536                        let (owner, error) = error.into_parts();
8537                        ReinterpretError::new(Tensor::from_typed::<Complex<f64>>(owner), error)
8538                    })
8539            }
8540            _ => Err(ReinterpretError::new(
8541                self,
8542                crate::Error::unsupported(
8543                    "Tensor::into_real",
8544                    "only complex tensors have a sealed real representation",
8545                ),
8546            )),
8547        }
8548    }
8549
8550    /// Borrow an interleaved real tensor as its sealed complex representation.
8551    ///
8552    /// # Errors
8553    ///
8554    /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
8555    /// [`ValidationError::ViewOutOfBounds`] or
8556    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
8557    pub fn as_complex_view(&self) -> crate::Result<TensorView<'_>> {
8558        match self.dtype() {
8559            DType::F32 => self
8560                .as_typed::<f32>()
8561                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
8562                .as_complex_view()
8563                .map(TensorView::C32),
8564            DType::F64 => self
8565                .as_typed::<f64>()
8566                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
8567                .as_complex_view()
8568                .map(TensorView::C64),
8569            other => Err(crate::Error::unsupported_dtype_conversion(
8570                "Tensor::as_complex_view",
8571                other,
8572                DType::C32,
8573                "only real tensors can have a sealed complex representation view",
8574            )),
8575        }
8576    }
8577
8578    /// Borrow an interleaved real tensor mutably as its sealed complex representation.
8579    ///
8580    /// # Errors
8581    ///
8582    /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
8583    /// [`ValidationError::ViewOutOfBounds`] or
8584    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
8585    pub fn as_complex_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
8586        match self.dtype() {
8587            DType::F32 => self
8588                .as_typed_mut::<f32>()
8589                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
8590                .as_complex_view_mut()
8591                .map(TensorViewMut::C32),
8592            DType::F64 => self
8593                .as_typed_mut::<f64>()
8594                .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
8595                .as_complex_view_mut()
8596                .map(TensorViewMut::C64),
8597            other => Err(crate::Error::unsupported_dtype_conversion(
8598                "Tensor::as_complex_view_mut",
8599                other,
8600                DType::C32,
8601                "only real tensors can have a sealed complex representation view",
8602            )),
8603        }
8604    }
8605
8606    /// Consume a real tensor and reinterpret its owner as complex without copying.
8607    ///
8608    /// # Errors
8609    ///
8610    /// Returns [`ReinterpretError::error`] containing
8611    /// [`ValidationError::InvalidArgument`] or
8612    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
8613    /// owner.
8614    pub fn into_complex(self) -> Result<Self, ReinterpretError<Self>> {
8615        let dtype = self.dtype();
8616        match dtype {
8617            DType::F32 => {
8618                let tensor = self
8619                    .into_preset::<f32>()
8620                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
8621                tensor
8622                    .into_complex()
8623                    .map(Tensor::from_typed::<Complex<f32>>)
8624                    .map_err(|error| {
8625                        let (owner, error) = error.into_parts();
8626                        ReinterpretError::new(Tensor::from_typed::<f32>(owner), error)
8627                    })
8628            }
8629            DType::F64 => {
8630                let tensor = self
8631                    .into_preset::<f64>()
8632                    .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
8633                tensor
8634                    .into_complex()
8635                    .map(Tensor::from_typed::<Complex<f64>>)
8636                    .map_err(|error| {
8637                        let (owner, error) = error.into_parts();
8638                        ReinterpretError::new(Tensor::from_typed::<f64>(owner), error)
8639                    })
8640            }
8641            _ => Err(ReinterpretError::new(
8642                self,
8643                crate::Error::unsupported(
8644                    "Tensor::into_complex",
8645                    "only real tensors have a sealed complex representation",
8646                ),
8647            )),
8648        }
8649    }
8650
8651    /// Make an explicit owning copy of this dtype-erased tensor.
8652    ///
8653    /// # Errors
8654    ///
8655    /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
8656    /// when the selected backend/storage owner cannot be duplicated.
8657    pub fn duplicate(&self) -> crate::Result<Self> {
8658        match &self.payload {
8659            // A caller-owned payload is copied through its own entry point, which
8660            // keeps its element type and its view; sharing the payload instead
8661            // would alias the caller's storage.
8662            TensorPayload::External(payload, placement) => Ok(Self {
8663                payload: TensorPayload::External(payload.duplicate(), placement.clone()),
8664            }),
8665            TensorPayload::Native(_) => match self.dtype() {
8666                DType::F32 => self.duplicate_typed::<f32>(),
8667                DType::F64 => self.duplicate_typed::<f64>(),
8668                DType::I32 => self.duplicate_typed::<i32>(),
8669                DType::I64 => self.duplicate_typed::<i64>(),
8670                DType::Bool => self.duplicate_typed::<bool>(),
8671                DType::C32 => self.duplicate_typed::<Complex<f32>>(),
8672                DType::C64 => self.duplicate_typed::<Complex<f64>>(),
8673                DType::External(_) => unreachable!("a native payload carries a preset dtype"),
8674            },
8675        }
8676    }
8677
8678    /// Duplicate a preset tensor through its typed owner.
8679    fn duplicate_typed<T: TensorScalar>(&self) -> crate::Result<Self> {
8680        self.as_typed::<T>()
8681            .unwrap_or_else(|| unreachable!("the caller selected this arm from the dtype"))
8682            .duplicate()
8683            .map(|duplicate| Self::from_core(duplicate.core))
8684    }
8685
8686    /// Create a tensor from a shape and column-major flat data.
8687    ///
8688    /// This is the `Tensor`-level equivalent of
8689    /// `TypedTensor::<T>::from_vec_col_major`.
8690    ///
8691    /// # Examples
8692    ///
8693    /// ```
8694    /// use tenferro_tensor::Tensor;
8695    ///
8696    /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
8697    /// assert_eq!(t.shape(), &[2, 2]);
8698    /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
8699    /// ```
8700    /// # Errors
8701    ///
8702    /// Returns [`crate::Error::Validation`] with
8703    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
8704    /// the shape product differs from `data.len()`, or
8705    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
8706    /// arithmetic overflows.
8707    pub fn from_vec_col_major<T: TensorScalar>(
8708        shape: impl tenferro_tensor_core::IntoShapeVec,
8709        data: Vec<T>,
8710    ) -> crate::Result<Self> {
8711        T::into_tensor(shape.into_shape_vec().to_vec(), data)
8712    }
8713
8714    /// Tensor shape.
8715    ///
8716    /// # Examples
8717    ///
8718    /// ```rust
8719    /// use tenferro_tensor::{Tensor, TypedTensor};
8720    ///
8721    /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
8722    /// assert_eq!(t.shape(), &[2]);
8723    /// ```
8724    pub fn shape(&self) -> &[usize] {
8725        match &self.payload {
8726            TensorPayload::Native(core) => core.layout.shape(),
8727            // The payload keeps its shape from construction.
8728            TensorPayload::External(payload, _) => payload.shape(),
8729        }
8730    }
8731
8732    /// Tensor dtype tag.
8733    ///
8734    /// # Examples
8735    ///
8736    /// ```rust
8737    /// use tenferro_tensor::{DType, Tensor, TypedTensor};
8738    ///
8739    /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
8740    /// assert_eq!(t.dtype(), DType::F64);
8741    /// ```
8742    pub fn dtype(&self) -> DType {
8743        match &self.payload {
8744            TensorPayload::Native(core) => core.group.dtype(),
8745            // The payload carries its own element identity.
8746            TensorPayload::External(payload, _) => DType::External(payload.element_type_id()),
8747        }
8748    }
8749
8750    /// Return placement metadata for this dtype-erased tensor.
8751    ///
8752    /// # Examples
8753    ///
8754    /// ```rust
8755    /// use tenferro_tensor::{MemoryKind, Tensor};
8756    ///
8757    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
8758    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
8759    /// ```
8760    pub fn placement(&self) -> &Placement {
8761        match &self.payload {
8762            TensorPayload::Native(core) => &core.placement,
8763            // The placement is stored with the payload.
8764            TensorPayload::External(_, placement) => placement,
8765        }
8766    }
8767
8768    /// Return whether this tensor is backed by backend-native storage.
8769    ///
8770    /// # Examples
8771    ///
8772    /// ```rust
8773    /// use tenferro_tensor::Tensor;
8774    ///
8775    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
8776    /// assert!(!t.is_backend_buffer());
8777    /// ```
8778    pub fn is_backend_buffer(&self) -> bool {
8779        match &self.payload {
8780            TensorPayload::Native(core) => core.is_backend_buffer(),
8781            // A caller-owned payload is host memory, never a backend buffer.
8782            TensorPayload::External(..) => false,
8783        }
8784    }
8785
8786    /// Compute the physical element offset for a logical index.
8787    ///
8788    /// # Examples
8789    ///
8790    /// ```rust
8791    /// use tenferro_tensor::Tensor;
8792    ///
8793    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8794    /// assert_eq!(t.layout_linear_offset(&[1])?, 1);
8795    /// # Ok::<(), tenferro_tensor::Error>(())
8796    /// ```
8797    /// # Errors
8798    ///
8799    /// Returns [`crate::Error::Validation`] with
8800    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8801    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8802    /// when an index is outside its axis extent, or
8803    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8804    /// arithmetic overflows.
8805    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8806        match &self.payload {
8807            TensorPayload::Native(core) => checked_view_offset_result(
8808                core.layout.shape(),
8809                core.layout.strides(),
8810                core.layout.offset(),
8811                indices,
8812                "Tensor::layout_linear_offset",
8813            ),
8814            // A caller-owned payload's owner resolves its own offsets.
8815            TensorPayload::External(..) => Err(crate::Error::unsupported_dtype(
8816                "layout_linear_offset",
8817                self.dtype(),
8818                "an externally defined payload resolves its own offsets",
8819            )),
8820        }
8821    }
8822
8823    /// Return whether this tensor is compact column-major.
8824    ///
8825    /// # Examples
8826    ///
8827    /// ```rust
8828    /// use tenferro_tensor::Tensor;
8829    ///
8830    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8831    /// assert!(t.is_col_major_contiguous()?);
8832    /// # Ok::<(), tenferro_tensor::Error>(())
8833    /// ```
8834    /// # Errors
8835    ///
8836    /// Returns [`crate::Error::Validation`] with
8837    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8838    /// compactness arithmetic overflows.
8839    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8840        match &self.payload {
8841            TensorPayload::Native(core) => core
8842                .layout
8843                .is_compact_col_major()
8844                .map_err(|err| tensor_layout_error("Tensor::is_col_major_contiguous", err)),
8845            // The payload is a compact column-major host tensor by construction.
8846            TensorPayload::External(..) => Ok(true),
8847        }
8848    }
8849
8850    /// Return a compact string summary of this tensor's layout metadata.
8851    ///
8852    /// # Examples
8853    ///
8854    /// ```rust
8855    /// use tenferro_tensor::Tensor;
8856    ///
8857    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8858    /// assert!(t.layout_summary().contains("shape=[2]"));
8859    /// # Ok::<(), tenferro_tensor::Error>(())
8860    /// ```
8861    pub fn layout_summary(&self) -> String {
8862        let layout = tensor_layout(self);
8863        layout_summary(layout.shape(), layout.strides(), layout.offset())
8864    }
8865
8866    /// Assert this tensor is compact column-major.
8867    ///
8868    /// # Examples
8869    ///
8870    /// ```rust
8871    /// use tenferro_tensor::Tensor;
8872    ///
8873    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8874    /// t.assert_col_major_contiguous()?;
8875    /// # Ok::<(), tenferro_tensor::Error>(())
8876    /// ```
8877    /// # Errors
8878    ///
8879    /// Returns [`crate::Error::Validation`] with
8880    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8881    /// compactness arithmetic overflows, or
8882    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8883    /// tensor is not compact column-major.
8884    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8885        let layout = tensor_layout(self);
8886        assert_layout_col_major_contiguous(
8887            self.is_col_major_contiguous()?,
8888            layout.shape(),
8889            layout.strides(),
8890            layout.offset(),
8891            "Tensor::assert_col_major_contiguous",
8892        )
8893    }
8894
8895    /// Try to borrow the host data as a typed slice.
8896    ///
8897    /// Returns an error if the tensor dtype does not match `T`.
8898    ///
8899    /// # Examples
8900    ///
8901    /// ```
8902    /// use tenferro_tensor::{Tensor, TypedTensor};
8903    ///
8904    /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
8905    /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
8906    /// assert!(t.as_slice::<f32>().is_err());
8907    /// ```
8908    /// # Errors
8909    ///
8910    /// Returns [`crate::Error::Validation`] with
8911    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
8912    /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
8913    /// matching tensor uses backend storage that has not been downloaded.
8914    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
8915        T::as_slice(self)
8916    }
8917
8918    /// Borrow the typed tensor when the requested scalar matches this tensor's dtype.
8919    ///
8920    /// This is the accessor tag-based dispatch needs: it recovers the typed tensor — and with it the
8921    /// device buffer — from a value whose element type is only known at run time, so a caller can
8922    /// dispatch on [`Tensor::dtype`] instead of matching every variant. An externally defined scalar
8923    /// is not a typed tensor, so it returns `None` rather than guessing a representation.
8924    ///
8925    /// # Examples
8926    ///
8927    /// ```
8928    /// use tenferro_tensor::Tensor;
8929    ///
8930    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8931    /// assert!(tensor.as_typed::<f64>().is_some());
8932    /// assert!(tensor.as_typed::<f32>().is_none());
8933    /// assert_eq!(tensor.as_typed::<f64>().unwrap().shape(), &[2]);
8934    /// # Ok::<(), tenferro_tensor::Error>(())
8935    /// ```
8936    #[must_use]
8937    pub fn as_typed<T: TensorScalar>(&self) -> Option<&TypedTensor<T>> {
8938        if self.dtype() != T::dtype() {
8939            return None;
8940        }
8941        match &self.payload {
8942            // SAFETY: the dtype check above proves `T` is the element type the core records,
8943            // and `TypedTensor<T>` is `#[repr(transparent)]` over that core.
8944            TensorPayload::Native(core) => Some(unsafe { core.as_typed::<T>() }),
8945            TensorPayload::External(..) => None,
8946        }
8947    }
8948
8949    /// Mutably borrow the typed tensor when the requested scalar matches this tensor's dtype.
8950    ///
8951    /// This is the mutable half of [`Tensor::as_typed`], for the tables whose arm calls a method that
8952    /// needs `&mut`, such as marking a freshly allocated output with its placement. An externally
8953    /// defined scalar is not a typed tensor, so it returns `None` for the same reason.
8954    ///
8955    /// # Examples
8956    ///
8957    /// ```
8958    /// use tenferro_tensor::Tensor;
8959    ///
8960    /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8961    /// assert!(tensor.as_typed_mut::<f64>().is_some());
8962    /// assert!(tensor.as_typed_mut::<f32>().is_none());
8963    /// # Ok::<(), tenferro_tensor::Error>(())
8964    /// ```
8965    #[must_use]
8966    pub fn as_typed_mut<T: TensorScalar>(&mut self) -> Option<&mut TypedTensor<T>> {
8967        if self.dtype() != T::dtype() {
8968            return None;
8969        }
8970        match &mut self.payload {
8971            // SAFETY: the dtype check above proves `T` is the element type the core records,
8972            // and `TypedTensor<T>` is `#[repr(transparent)]` over that core.
8973            TensorPayload::Native(core) => Some(unsafe { core.as_typed_mut::<T>() }),
8974            TensorPayload::External(..) => None,
8975        }
8976    }
8977
8978    /// Consume this tensor and return the owned typed tensor when the dtype matches.
8979    ///
8980    /// This is the consuming counterpart of [`Tensor::as_typed`], for the tables whose arm hands the typed
8981    /// tensor to a function that takes it by value — reusing its buffer rather than copying it.
8982    ///
8983    /// # Examples
8984    ///
8985    /// ```
8986    /// use tenferro_tensor::Tensor;
8987    ///
8988    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8989    /// assert!(tensor.into_typed::<f64>().is_ok());
8990    ///
8991    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0])?;
8992    /// assert!(tensor.into_typed::<f64>().is_err());
8993    /// # Ok::<(), tenferro_tensor::Error>(())
8994    /// ```
8995    /// # Errors
8996    ///
8997    /// Returns [`crate::Error::Validation`] with
8998    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` is not this tensor's dtype.
8999    /// A matching tensor is handed over as it is, including one whose storage lives in a backend
9000    /// buffer.
9001    pub fn into_typed<T: TensorScalar>(self) -> crate::Result<TypedTensor<T>> {
9002        T::into_typed(self)
9003    }
9004
9005    /// Consume this tensor and return its owned column-major buffer when the
9006    /// dtype matches.
9007    ///
9008    /// # Examples
9009    ///
9010    /// ```
9011    /// use tenferro_tensor::Tensor;
9012    ///
9013    /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
9014    /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
9015    /// ```
9016    /// # Errors
9017    ///
9018    /// Returns [`crate::Error::Validation`] with
9019    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
9020    /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
9021    /// matching tensor uses backend storage that has not been downloaded.
9022    pub fn into_vec_col_major<T: TensorScalar>(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
9023        let typed = T::into_typed(self)?;
9024        typed.into_vec_col_major()
9025    }
9026}
9027
9028// INVARIANT: retained for crate-local layout tests while tensor indexing
9029// helpers remain split across the tensor and CPU crates.
9030#[allow(dead_code)]
9031pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
9032    for i in 0..shape.len() {
9033        if shape[i] == 0 {
9034            out[i] = 0;
9035        } else {
9036            out[i] = flat % shape[i];
9037            flat /= shape[i];
9038        }
9039    }
9040}