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