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