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    /// Borrow this read target as a typed compact host slice without allocation.
5287    ///
5288    /// This delegates to [`TensorView::as_slice`]. Cloning a `TensorRead` is a
5289    /// shallow clone of its borrowed reference or view metadata, so the returned
5290    /// slice retains the original `'a` storage lifetime and never outlives the
5291    /// storage borrowed by this read target. This method does not materialize,
5292    /// transfer, or otherwise canonicalize the input.
5293    ///
5294    /// # Errors
5295    ///
5296    /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
5297    /// input dtype, [`ValidationError::InvalidArgument`] for a noncompact view,
5298    /// or a typed runtime-state host-access error for backend-owned storage.
5299    /// Backend-owned
5300    /// inputs are never downloaded implicitly.
5301    ///
5302    /// # Examples
5303    ///
5304    /// ```
5305    /// use tenferro_tensor::{Tensor, TensorRead};
5306    ///
5307    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5308    /// let read = TensorRead::from_tensor(&tensor);
5309    /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
5310    /// # Ok::<(), tenferro_tensor::Error>(())
5311    /// ```
5312    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
5313        self.clone().tensor_view().as_slice()
5314    }
5315
5316    pub fn dtype(&self) -> DType {
5317        match self {
5318            Self::Tensor(tensor) => tensor.dtype(),
5319            Self::View(view) => view.dtype(),
5320        }
5321    }
5322
5323    pub fn shape(&self) -> &[usize] {
5324        match self {
5325            Self::Tensor(tensor) => tensor.shape(),
5326            Self::View(view) => view.shape(),
5327        }
5328    }
5329
5330    /// Return the placement metadata carried by this read target.
5331    ///
5332    /// # Examples
5333    ///
5334    /// ```
5335    /// use tenferro_tensor::{MemoryKind, Tensor, TensorRead};
5336    ///
5337    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
5338    /// let read = TensorRead::from_tensor(&tensor);
5339    /// assert_eq!(read.placement().memory_kind, MemoryKind::UnpinnedHost);
5340    /// # Ok::<(), tenferro_tensor::Error>(())
5341    /// ```
5342    pub fn placement(&self) -> &Placement {
5343        match self {
5344            Self::Tensor(tensor) => tensor.placement(),
5345            Self::View(view) => view.placement(),
5346        }
5347    }
5348
5349    /// Return the physical backend family of this read target, when backend-owned.
5350    ///
5351    /// # Examples
5352    ///
5353    /// ```
5354    /// use tenferro_tensor::{Tensor, TensorRead};
5355    ///
5356    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
5357    /// assert_eq!(TensorRead::from_tensor(&tensor).backend_family(), None);
5358    /// # Ok::<(), tenferro_tensor::Error>(())
5359    /// ```
5360    pub fn backend_family(&self) -> Option<&'static str> {
5361        match self {
5362            Self::Tensor(tensor) => match tensor {
5363                Tensor::F32(t) => t.backend_family(),
5364                Tensor::F64(t) => t.backend_family(),
5365                Tensor::I32(t) => t.backend_family(),
5366                Tensor::I64(t) => t.backend_family(),
5367                Tensor::Bool(t) => t.backend_family(),
5368                Tensor::C32(t) => t.backend_family(),
5369                Tensor::C64(t) => t.backend_family(),
5370            },
5371            Self::View(view) => view.backend_family(),
5372        }
5373    }
5374
5375    /// Return the shared allocation domain of this read target, when present.
5376    ///
5377    /// # Examples
5378    ///
5379    /// ```
5380    /// use tenferro_tensor::{Tensor, TensorRead};
5381    ///
5382    /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
5383    /// assert_eq!(TensorRead::from_tensor(&tensor).allocation_domain(), None);
5384    /// # Ok::<(), tenferro_tensor::Error>(())
5385    /// ```
5386    pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
5387        match self {
5388            Self::Tensor(tensor) => match tensor {
5389                Tensor::F32(t) => t.allocation_domain(),
5390                Tensor::F64(t) => t.allocation_domain(),
5391                Tensor::I32(t) => t.allocation_domain(),
5392                Tensor::I64(t) => t.allocation_domain(),
5393                Tensor::Bool(t) => t.allocation_domain(),
5394                Tensor::C32(t) => t.allocation_domain(),
5395                Tensor::C64(t) => t.allocation_domain(),
5396            },
5397            Self::View(view) => view.allocation_domain(),
5398        }
5399    }
5400
5401    /// # Errors
5402    ///
5403    /// Returns [`crate::Error::Validation`] with
5404    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5405    /// column-major stride arithmetic overflows.
5406    pub fn strides(&self) -> crate::Result<Vec<isize>> {
5407        match self {
5408            Self::Tensor(tensor) => col_major_strides(tensor.shape()),
5409            Self::View(view) => Ok(view.strides().to_vec()),
5410        }
5411    }
5412
5413    pub fn offset(&self) -> isize {
5414        match self {
5415            Self::Tensor(_) => 0,
5416            Self::View(view) => view.offset(),
5417        }
5418    }
5419
5420    /// # Errors
5421    ///
5422    /// Returns [`crate::Error::Validation`] with
5423    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5424    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5425    /// when an index is outside its axis extent, or
5426    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
5427    /// arithmetic overflows.
5428    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
5429        match self {
5430            Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
5431            Self::View(view) => view.layout_linear_offset(indices),
5432        }
5433    }
5434
5435    /// # Errors
5436    ///
5437    /// Returns [`crate::Error::Validation`] with
5438    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5439    /// compactness arithmetic overflows.
5440    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
5441        match self {
5442            Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
5443            Self::View(view) => view.is_col_major_contiguous(),
5444        }
5445    }
5446
5447    pub fn layout_summary(&self) -> String {
5448        let strides = match self.strides() {
5449            Ok(strides) => strides,
5450            Err(err) => return format!("layout unavailable: {err}"),
5451        };
5452        layout_summary(self.shape(), &strides, self.offset())
5453    }
5454
5455    /// # Errors
5456    ///
5457    /// Returns [`crate::Error::Validation`] with
5458    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5459    /// compactness arithmetic overflows, or
5460    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
5461    /// view is not compact column-major.
5462    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
5463        let strides = self.strides()?;
5464        assert_layout_col_major_contiguous(
5465            self.is_col_major_contiguous()?,
5466            self.shape(),
5467            &strides,
5468            self.offset(),
5469            "TensorRead::assert_col_major_contiguous",
5470        )
5471    }
5472
5473    pub fn as_tensor(&self) -> Option<&'a Tensor> {
5474        match self {
5475            Self::Tensor(tensor) => Some(*tensor),
5476            Self::View(_) => None,
5477        }
5478    }
5479}
5480
5481impl<'a> TensorWrite<'a> {
5482    pub fn from_tensor(tensor: &'a mut Tensor) -> Self {
5483        Self::Tensor(tensor)
5484    }
5485
5486    pub fn from_view(view: TensorViewMut<'a>) -> Self {
5487        Self::View(view)
5488    }
5489
5490    /// Borrow this writable target as a read-only tensor input.
5491    ///
5492    /// This is useful for explicit read-modify-write kernels such as
5493    /// accumulation updates. The returned view borrows through `&self`, so it
5494    /// cannot outlive the current read-only borrow of the writable target.
5495    ///
5496    /// # Examples
5497    ///
5498    /// ```rust
5499    /// use tenferro_tensor::{DType, Tensor, TensorWrite};
5500    ///
5501    /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
5502    /// let write = TensorWrite::from_tensor(&mut tensor);
5503    /// let read = write.as_read();
5504    /// assert_eq!(read.dtype(), DType::F64);
5505    /// # Ok::<(), tenferro_tensor::Error>(())
5506    /// ```
5507    pub fn as_read(&self) -> TensorRead<'_> {
5508        match self {
5509            Self::Tensor(tensor) => TensorRead::from_tensor(tensor),
5510            Self::View(view) => TensorRead::from_view(view.as_read_only()),
5511        }
5512    }
5513
5514    pub fn dtype(&self) -> DType {
5515        match self {
5516            Self::Tensor(tensor) => tensor.dtype(),
5517            Self::View(view) => view.dtype(),
5518        }
5519    }
5520
5521    pub fn shape(&self) -> &[usize] {
5522        match self {
5523            Self::Tensor(tensor) => tensor.shape(),
5524            Self::View(view) => view.shape(),
5525        }
5526    }
5527
5528    /// # Errors
5529    ///
5530    /// Returns [`crate::Error::Validation`] with
5531    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5532    /// column-major stride arithmetic overflows.
5533    pub fn strides(&self) -> crate::Result<Vec<isize>> {
5534        match self {
5535            Self::Tensor(tensor) => col_major_strides(tensor.shape()),
5536            Self::View(view) => Ok(view.strides().to_vec()),
5537        }
5538    }
5539
5540    pub fn offset(&self) -> isize {
5541        match self {
5542            Self::Tensor(_) => 0,
5543            Self::View(view) => view.offset(),
5544        }
5545    }
5546
5547    /// # Errors
5548    ///
5549    /// Returns [`crate::Error::Validation`] with
5550    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5551    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5552    /// when an index is outside its axis extent, or
5553    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
5554    /// arithmetic overflows.
5555    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
5556        match self {
5557            Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
5558            Self::View(view) => view.layout_linear_offset(indices),
5559        }
5560    }
5561
5562    /// # Errors
5563    ///
5564    /// Returns [`crate::Error::Validation`] with
5565    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5566    /// compactness arithmetic overflows.
5567    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
5568        match self {
5569            Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
5570            Self::View(view) => view.is_col_major_contiguous(),
5571        }
5572    }
5573
5574    pub fn layout_summary(&self) -> String {
5575        let strides = match self.strides() {
5576            Ok(strides) => strides,
5577            Err(err) => return format!("layout unavailable: {err}"),
5578        };
5579        layout_summary(self.shape(), &strides, self.offset())
5580    }
5581
5582    /// # Errors
5583    ///
5584    /// Returns [`crate::Error::Validation`] with
5585    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5586    /// compactness arithmetic overflows, or
5587    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
5588    /// view is not compact column-major.
5589    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
5590        let strides = self.strides()?;
5591        assert_layout_col_major_contiguous(
5592            self.is_col_major_contiguous()?,
5593            self.shape(),
5594            &strides,
5595            self.offset(),
5596            "TensorWrite::assert_col_major_contiguous",
5597        )
5598    }
5599}
5600
5601/// Column-major strides derived from a shape.
5602///
5603/// # Examples
5604///
5605/// ```rust
5606/// use tenferro_tensor::col_major_strides;
5607///
5608/// assert_eq!(col_major_strides(&[2, 3])?, vec![1, 2]);
5609/// # Ok::<(), tenferro_tensor::Error>(())
5610/// ```
5611/// # Errors
5612///
5613/// Returns [`crate::Error::Validation`] with
5614/// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when a
5615/// column-major stride product overflows.
5616pub fn col_major_strides(shape: &[usize]) -> crate::Result<Vec<isize>> {
5617    let mut strides = Vec::with_capacity(shape.len());
5618    let mut stride = 1isize;
5619    for &extent in shape {
5620        strides.push(stride);
5621        let extent = isize::try_from(extent).map_err(|_| {
5622            crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
5623        })?;
5624        stride = stride.checked_mul(extent).ok_or_else(|| {
5625            crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
5626        })?;
5627    }
5628    Ok(strides)
5629}
5630
5631fn try_linear_offset_for_shape(
5632    shape: &[usize],
5633    indices: &[usize],
5634    op: &'static str,
5635) -> crate::Result<usize> {
5636    if indices.len() != shape.len() {
5637        return Err(crate::Error::validation(
5638            op,
5639            ValidationError::RankMismatch {
5640                expected: shape.len(),
5641                actual: indices.len(),
5642            },
5643        ));
5644    }
5645    let mut offset = 0usize;
5646    let mut stride = 1usize;
5647    for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
5648        if idx >= extent {
5649            return Err(crate::Error::invalid_argument(
5650                op,
5651                "index",
5652                format!("index {idx} out of bounds for axis {axis} extent {extent}"),
5653            ));
5654        }
5655        offset =
5656            offset
5657                .checked_add(idx.checked_mul(stride).ok_or_else(|| {
5658                    crate::Error::validation(op, ValidationError::IntegerOverflow)
5659                })?)
5660                .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
5661        stride = stride
5662            .checked_mul(extent)
5663            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
5664    }
5665    Ok(offset)
5666}
5667
5668fn checked_view_offset_result(
5669    shape: &[usize],
5670    strides: &[isize],
5671    base_offset: isize,
5672    indices: &[usize],
5673    op: &'static str,
5674) -> crate::Result<usize> {
5675    if indices.len() != shape.len() {
5676        return Err(crate::Error::validation(
5677            op,
5678            ValidationError::RankMismatch {
5679                expected: shape.len(),
5680                actual: indices.len(),
5681            },
5682        ));
5683    }
5684    for (axis, (&index, &extent)) in indices.iter().zip(shape).enumerate() {
5685        if index >= extent {
5686            return Err(crate::Error::invalid_argument(
5687                op,
5688                "index",
5689                format!("index {index} out of bounds for axis {axis} extent {extent}"),
5690            ));
5691        }
5692    }
5693    checked_view_offset(shape, strides, base_offset, indices)
5694        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
5695}
5696
5697fn layout_summary(shape: &[usize], strides: &[isize], offset: isize) -> String {
5698    format!("shape={shape:?} strides={strides:?} offset={offset}")
5699}
5700
5701fn assert_layout_col_major_contiguous(
5702    is_contiguous: bool,
5703    shape: &[usize],
5704    strides: &[isize],
5705    offset: isize,
5706    op: &'static str,
5707) -> crate::Result<()> {
5708    if is_contiguous {
5709        Ok(())
5710    } else {
5711        Err(crate::Error::invalid_argument(
5712            op,
5713            "layout",
5714            format!(
5715                "expected compact column-major layout, got {}",
5716                layout_summary(shape, strides, offset)
5717            ),
5718        ))
5719    }
5720}
5721
5722fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
5723    shape.iter().try_fold(1usize, |acc, &dim| {
5724        acc.checked_mul(dim)
5725            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
5726    })
5727}
5728
5729fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
5730    let n = try_shape_product(shape, op)?;
5731    if data_len != n {
5732        return Err(crate::Error::validation(
5733            op,
5734            ValidationError::ShapeDataLengthMismatch {
5735                expected: n,
5736                actual: data_len,
5737            },
5738        ));
5739    }
5740    Ok(())
5741}
5742
5743fn try_compact_layout<R: TensorRank>(
5744    shape: impl Into<R::Shape>,
5745    op: &'static str,
5746) -> crate::Result<TensorLayout<R>> {
5747    TensorLayout::compact(shape.into()).map_err(|err| tensor_layout_error(op, err))
5748}
5749
5750fn tensor_layout_error(
5751    op: &'static str,
5752    err: tenferro_tensor_core::ValidationError,
5753) -> crate::Error {
5754    crate::Error::validation(op, err)
5755}
5756
5757fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
5758    if shape.contains(&0) {
5759        return Ok(0);
5760    }
5761    shape.iter().try_fold(1usize, |product, &dim| {
5762        product
5763            .checked_mul(dim)
5764            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
5765    })
5766}
5767
5768fn checked_view_offset(
5769    shape: &[usize],
5770    strides: &[isize],
5771    base_offset: isize,
5772    indices: &[usize],
5773) -> Option<usize> {
5774    if indices.len() != shape.len() {
5775        return None;
5776    }
5777
5778    let mut offset = base_offset;
5779    for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
5780        if index >= extent {
5781            return None;
5782        }
5783        let index = isize::try_from(index).ok()?;
5784        let delta = index.checked_mul(stride)?;
5785        offset = offset.checked_add(delta)?;
5786    }
5787
5788    usize::try_from(offset).ok()
5789}
5790
5791fn reachable_layout_span(
5792    shape: &[usize],
5793    strides: &[isize],
5794    offset: isize,
5795) -> crate::Result<Option<(usize, usize)>> {
5796    if shape.contains(&0) {
5797        return Ok(None);
5798    }
5799
5800    let mut min_offset = offset;
5801    let mut max_offset = offset;
5802    for (&extent, &stride) in shape.iter().zip(strides) {
5803        let steps = isize::try_from(extent.saturating_sub(1)).map_err(|_| {
5804            crate::Error::validation(
5805                "TypedTensorViewMut::try_multi_slice_mut",
5806                ValidationError::IntegerOverflow,
5807            )
5808        })?;
5809        let end = stride.checked_mul(steps).ok_or_else(|| {
5810            crate::Error::validation(
5811                "TypedTensorViewMut::try_multi_slice_mut",
5812                ValidationError::IntegerOverflow,
5813            )
5814        })?;
5815        let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
5816        min_offset = min_offset.checked_add(axis_min).ok_or_else(|| {
5817            crate::Error::validation(
5818                "TypedTensorViewMut::try_multi_slice_mut",
5819                ValidationError::IntegerOverflow,
5820            )
5821        })?;
5822        max_offset = max_offset.checked_add(axis_max).ok_or_else(|| {
5823            crate::Error::validation(
5824                "TypedTensorViewMut::try_multi_slice_mut",
5825                ValidationError::IntegerOverflow,
5826            )
5827        })?;
5828    }
5829
5830    let min_offset = usize::try_from(min_offset).map_err(|_| {
5831        crate::Error::invalid_argument(
5832            "TypedTensorViewMut::try_multi_slice_mut",
5833            "layout",
5834            "minimum reachable offset is negative",
5835        )
5836    })?;
5837    let max_offset = usize::try_from(max_offset).map_err(|_| {
5838        crate::Error::invalid_argument(
5839            "TypedTensorViewMut::try_multi_slice_mut",
5840            "layout",
5841            "maximum reachable offset is negative",
5842        )
5843    })?;
5844    Ok(Some((min_offset, max_offset)))
5845}
5846
5847fn split_two_mut_ranges<T>(
5848    data: &mut [T],
5849    first: (usize, usize),
5850    second: (usize, usize),
5851) -> Option<(&mut [T], &mut [T])> {
5852    if first.1 < second.0 {
5853        let (_, after_first_start) = data.split_at_mut(first.0);
5854        let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
5855        let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
5856        let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
5857        Some((first_slice, second_slice))
5858    } else if second.1 < first.0 {
5859        let (_, after_second_start) = data.split_at_mut(second.0);
5860        let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
5861        let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
5862        let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
5863        Some((first_slice, second_slice))
5864    } else {
5865        None
5866    }
5867}
5868
5869fn adjusted_view_offset(offset: isize, span_start: usize) -> crate::Result<isize> {
5870    let span_start = isize::try_from(span_start).map_err(|_| {
5871        crate::Error::validation(
5872            "TypedTensorViewMut::try_multi_slice_mut",
5873            ValidationError::IntegerOverflow,
5874        )
5875    })?;
5876    offset.checked_sub(span_start).ok_or_else(|| {
5877        crate::Error::validation(
5878            "TypedTensorViewMut::try_multi_slice_mut",
5879            ValidationError::IntegerOverflow,
5880        )
5881    })
5882}
5883
5884fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
5885    layout: &TensorLayout<R>,
5886    offset: isize,
5887    data: &'a mut [T],
5888    placement: Placement,
5889) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
5890    let shape = R::shape_from_vec(shape_vec(layout.shape()))
5891        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
5892    let strides = R::strides_from_vec(stride_vec(layout.strides()))
5893        .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
5894    TypedTensorViewMut::from_buffer_ref_mut(
5895        shape,
5896        strides,
5897        offset,
5898        TensorStorageRefMut::Host(data),
5899        placement,
5900        "TypedTensorViewMut::try_multi_slice_mut",
5901    )
5902}
5903
5904fn contiguous_layout_slice<'a, T, R: TensorRank>(
5905    layout: &TensorLayout<R>,
5906    data: &'a [T],
5907    op: &'static str,
5908) -> crate::Result<&'a [T]> {
5909    if !layout
5910        .is_compact_col_major()
5911        .map_err(|err| tensor_layout_error(op, err))?
5912    {
5913        return Err(crate::Error::invalid_argument(
5914            op,
5915            "layout",
5916            "view is not contiguous column-major",
5917        ));
5918    }
5919    let len = checked_view_element_count(layout.shape(), op)?;
5920    let start = usize::try_from(layout.offset())
5921        .map_err(|_| crate::Error::invalid_argument(op, "layout", "view offset is negative"))?;
5922    let end = start
5923        .checked_add(len)
5924        .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
5925    data.get(start..end)
5926        .ok_or_else(|| crate::Error::validation(op, ValidationError::ViewOutOfBounds))
5927}
5928
5929fn relaxed_col_major_contiguous(
5930    shape: &[usize],
5931    strides: &[isize],
5932    op: &'static str,
5933) -> crate::Result<bool> {
5934    if shape.contains(&0) {
5935        return Ok(true);
5936    }
5937
5938    let mut expected = 1isize;
5939    for (&extent, &stride) in shape.iter().zip(strides) {
5940        if extent <= 1 {
5941            continue;
5942        }
5943        if stride != expected {
5944            return Ok(false);
5945        }
5946        let extent = isize::try_from(extent)
5947            .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
5948        expected = expected
5949            .checked_mul(extent)
5950            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
5951    }
5952    Ok(true)
5953}
5954
5955fn reshape_layout_dyn<R: TensorRank>(
5956    layout: &TensorLayout<R>,
5957    shape: &[usize],
5958    buffer_len: usize,
5959    op: &'static str,
5960) -> crate::Result<TensorLayout<DynRank>> {
5961    match layout.reshape_view_as::<DynRank>(shape_vec(shape), buffer_len) {
5962        Ok(layout) => Ok(layout),
5963        Err(err) => {
5964            if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
5965                return Err(tensor_layout_error(op, err));
5966            }
5967            let from = checked_view_element_count(layout.shape(), op)?;
5968            let to = checked_view_element_count(shape, op)?;
5969            if from != to {
5970                return Err(tensor_layout_error(
5971                    op,
5972                    tenferro_tensor_core::ShapeMismatch::ReshapeElementCount { from, to }.into(),
5973                ));
5974            }
5975            TensorLayout::<DynRank>::compact(shape_vec(shape))
5976                .and_then(|compact| {
5977                    TensorLayout::from_parts(
5978                        shape_vec(compact.shape()),
5979                        stride_vec(compact.strides()),
5980                        layout.offset(),
5981                        buffer_len,
5982                    )
5983                })
5984                .map_err(|err| tensor_layout_error(op, err))
5985        }
5986    }
5987}
5988
5989fn core_slice_specs(
5990    slices: &[StridedSliceSpec],
5991    shape: &[usize],
5992    op: &'static str,
5993) -> crate::Result<Vec<CoreSliceSpec>> {
5994    if slices.len() != shape.len() {
5995        return Err(crate::Error::validation(
5996            op,
5997            ValidationError::RankMismatch {
5998                expected: shape.len(),
5999                actual: slices.len(),
6000            },
6001        ));
6002    }
6003
6004    let mut specs = Vec::with_capacity(slices.len());
6005    for (slice, &axis_len) in slices.iter().zip(shape) {
6006        specs.push(core_slice_spec(*slice, axis_len, op)?);
6007    }
6008    Ok(specs)
6009}
6010
6011fn core_slice_spec(
6012    slice: StridedSliceSpec,
6013    axis_len: usize,
6014    op: &'static str,
6015) -> crate::Result<CoreSliceSpec> {
6016    if slice.step() == 0 {
6017        return Err(crate::Error::validation(
6018            op,
6019            ValidationError::InvalidSliceStep { step: slice.step() },
6020        ));
6021    }
6022
6023    let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
6024    let end = match slice.end() {
6025        Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
6026        None => isize::try_from(axis_len)
6027            .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
6028    };
6029
6030    if slice.step() > 0 {
6031        return Ok(CoreSliceSpec {
6032            start,
6033            end,
6034            step: slice.step(),
6035        });
6036    }
6037
6038    if start >= end {
6039        return Ok(CoreSliceSpec {
6040            start,
6041            end: start,
6042            step: slice.step(),
6043        });
6044    }
6045
6046    Ok(CoreSliceSpec {
6047        start: end
6048            .checked_sub(1)
6049            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
6050        end: start
6051            .checked_sub(1)
6052            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
6053        step: slice.step(),
6054    })
6055}
6056
6057fn normalize_strided_bound(
6058    bound: isize,
6059    axis_len: usize,
6060    op: &'static str,
6061    role: &'static str,
6062) -> crate::Result<isize> {
6063    let original_axis_len = axis_len;
6064    let axis_len = isize::try_from(axis_len)
6065        .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6066    let bound = if bound < 0 {
6067        axis_len
6068            .checked_add(bound)
6069            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?
6070    } else {
6071        bound
6072    };
6073    if !(0..=axis_len).contains(&bound) {
6074        let (start, end) = if role == "slice start" {
6075            (bound, bound)
6076        } else {
6077            (0, bound)
6078        };
6079        return Err(crate::Error::validation(
6080            op,
6081            ValidationError::InvalidSliceBounds {
6082                start,
6083                end,
6084                axis_len: original_axis_len,
6085            },
6086        ));
6087    }
6088    Ok(bound)
6089}
6090
6091fn slice_axis_specs(
6092    rank: usize,
6093    axis: usize,
6094    slice: StridedSliceSpec,
6095    op: &'static str,
6096) -> crate::Result<Vec<StridedSliceSpec>> {
6097    if axis >= rank {
6098        return Err(crate::Error::validation(
6099            op,
6100            ValidationError::AxisOutOfBounds { axis, rank },
6101        ));
6102    }
6103
6104    let mut slices = vec![StridedSliceSpec::all(); rank];
6105    slices[axis] = slice;
6106    Ok(slices)
6107}
6108
6109pub(crate) fn default_placement() -> Placement {
6110    Placement {
6111        memory_kind: MemoryKind::UnpinnedHost,
6112        device: None,
6113        cpu_affinity: None,
6114    }
6115}
6116
6117fn typed_tensor_from_vec_col_major<T: TensorScalar, R: TensorRank>(
6118    shape: impl Into<R::Shape>,
6119    data: Vec<T>,
6120    op: &'static str,
6121) -> crate::Result<TypedTensor<T, R>> {
6122    try_typed_tensor_from_vec_col_major(shape, data, op)
6123}
6124
6125fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
6126    shape: impl Into<R::Shape>,
6127    data: Vec<T>,
6128    op: &'static str,
6129) -> crate::Result<TypedTensor<T, R>>
6130where
6131    T: TensorScalar,
6132{
6133    let layout = try_compact_layout(shape, op)?;
6134    try_checked_shape_len(layout.shape(), data.len(), op)?;
6135    let group_shape =
6136        R::shape_from_vec(shape_vec(layout.shape())).map_err(|err| tensor_layout_error(op, err))?;
6137    let group = OwnedTensorGroup::from_host_vec(group_shape, data)?;
6138    Ok(TypedTensor {
6139        group,
6140        layout,
6141        placement: default_placement(),
6142        _scalar: PhantomData,
6143    })
6144}
6145
6146fn typed_tensor_zeros<T: TensorScalar + Zero, R: TensorRank>(
6147    shape: impl Into<R::Shape>,
6148) -> crate::Result<TypedTensor<T, R>> {
6149    try_typed_tensor_zeros(shape)
6150}
6151
6152fn try_typed_tensor_zeros<T: TensorScalar + Clone + Zero, R: TensorRank>(
6153    shape: impl Into<R::Shape>,
6154) -> crate::Result<TypedTensor<T, R>> {
6155    let layout = try_compact_layout(shape, "zeros")?;
6156    let n = try_shape_product(layout.shape(), "zeros")?;
6157    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6158        .map_err(|err| tensor_layout_error("zeros", err))?;
6159    let group = OwnedTensorGroup::from_host_vec(group_shape, vec![T::zero(); n])?;
6160    Ok(TypedTensor {
6161        group,
6162        layout,
6163        placement: default_placement(),
6164        _scalar: PhantomData,
6165    })
6166}
6167
6168fn typed_tensor_ones<T: TensorScalar + One + Zero, R: TensorRank>(
6169    shape: impl Into<R::Shape>,
6170) -> crate::Result<TypedTensor<T, R>> {
6171    try_typed_tensor_ones(shape)
6172}
6173
6174fn try_typed_tensor_ones<T: TensorScalar + Clone + One + Zero, R: TensorRank>(
6175    shape: impl Into<R::Shape>,
6176) -> crate::Result<TypedTensor<T, R>> {
6177    let layout = try_compact_layout(shape, "ones")?;
6178    let n = try_shape_product(layout.shape(), "ones")?;
6179    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6180        .map_err(|err| tensor_layout_error("ones", err))?;
6181    let group = OwnedTensorGroup::from_host_vec(group_shape, vec![T::one(); n])?;
6182    Ok(TypedTensor {
6183        group,
6184        layout,
6185        placement: default_placement(),
6186        _scalar: PhantomData,
6187    })
6188}
6189
6190fn typed_tensor_from_buffer_col_major<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
6191    shape: impl Into<R::Shape>,
6192    buffer: StorageBuffer<T>,
6193    placement: Placement,
6194) -> crate::Result<TypedTensor<T, R>> {
6195    try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
6196}
6197
6198#[doc(hidden)]
6199fn typed_tensor_from_backend_allocation<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
6200    shape: impl Into<R::Shape>,
6201    allocation: Box<dyn crate::BackendAllocation>,
6202    placement: Placement,
6203) -> crate::Result<TypedTensor<T, R>> {
6204    let layout = try_compact_layout(shape, "from_backend_allocation")?;
6205    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6206        .map_err(|err| tensor_layout_error("from_backend_allocation", err))?;
6207    let (group, slot) =
6208        AllocationGroup::from_backend_allocation::<T, R>(group_shape, allocation)
6209            .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
6210    let allocation_index = group
6211        .allocation_index(slot)
6212        .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
6213    let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
6214    Ok(TypedTensor {
6215        group: OwnedTensorGroup {
6216            group,
6217            slot,
6218            allocation_index,
6219            host_ptr,
6220            host_byte_len,
6221            _rank: PhantomData,
6222        },
6223        layout,
6224        placement,
6225        _scalar: PhantomData,
6226    })
6227}
6228
6229fn try_typed_tensor_from_buffer_col_major<
6230    T: TensorScalar + Send + Sync + 'static,
6231    R: TensorRank,
6232>(
6233    shape: impl Into<R::Shape>,
6234    buffer: StorageBuffer<T>,
6235    placement: Placement,
6236) -> crate::Result<TypedTensor<T, R>> {
6237    let layout = try_compact_layout(shape, "from_buffer_col_major")?;
6238    let len = buffer.len();
6239    try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
6240    let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6241        .map_err(|err| tensor_layout_error("from_buffer_col_major", err))?;
6242    let group = match buffer {
6243        StorageBuffer::Host(data) => OwnedTensorGroup::from_host_vec(group_shape, data)?,
6244        StorageBuffer::Backend(buffer) => OwnedTensorGroup::from_backend_buffer(
6245            group_shape,
6246            StorageBuffer::Backend(buffer),
6247            placement.clone(),
6248        )?,
6249    };
6250    Ok(TypedTensor {
6251        group,
6252        layout,
6253        placement,
6254        _scalar: PhantomData,
6255    })
6256}
6257
6258impl<T: TensorScalar + Zero, R: TensorRank> TypedTensor<T, R> {
6259    /// Allocate a zero-filled tensor.
6260    ///
6261    /// # Examples
6262    ///
6263    /// ```rust
6264    /// use tenferro_tensor::TypedTensor;
6265    ///
6266    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
6267    /// assert_eq!(t.n_elements(), 6);
6268    /// ```
6269    /// # Errors
6270    ///
6271    /// Returns [`crate::Error::Validation`] with
6272    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
6273    /// product or compact-stride arithmetic overflows.
6274    pub fn zeros(shape: impl Into<R::Shape>) -> crate::Result<Self> {
6275        typed_tensor_zeros(shape)
6276    }
6277}
6278
6279impl<T: TensorScalar + One + Zero, R: TensorRank> TypedTensor<T, R> {
6280    /// Allocate a one-filled tensor.
6281    ///
6282    /// # Examples
6283    ///
6284    /// ```rust
6285    /// use tenferro_tensor::TypedTensor;
6286    ///
6287    /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
6288    /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
6289    /// ```
6290    /// # Errors
6291    ///
6292    /// Returns [`crate::Error::Validation`] with
6293    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
6294    /// product or compact-stride arithmetic overflows.
6295    pub fn ones(shape: impl Into<R::Shape>) -> crate::Result<Self> {
6296        typed_tensor_ones(shape)
6297    }
6298}
6299
6300impl<T, R: TensorRank> TypedTensor<T, R> {
6301    /// Create a tensor from an existing buffer and compact column-major layout.
6302    ///
6303    /// This preserves the owned tensor invariant that layout metadata is
6304    /// compact column-major, including for backend-owned buffers.
6305    ///
6306    /// # Examples
6307    ///
6308    /// ```
6309    /// use tenferro_tensor::{StorageBuffer, Placement, TypedTensor};
6310    ///
6311    /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
6312    ///     vec![2],
6313    ///     StorageBuffer::Host(vec![1.0, 2.0]),
6314    ///     Placement {
6315    ///         memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
6316    ///         device: None,
6317    ///         cpu_affinity: None,
6318    ///     },
6319    /// )
6320    /// .unwrap();
6321    /// assert_eq!(tensor.shape(), &[2]);
6322    /// ```
6323    /// # Errors
6324    ///
6325    /// Returns [`crate::Error::Validation`] with
6326    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
6327    /// the shape product differs from the buffer length,
6328    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape or
6329    /// stride arithmetic overflows, or
6330    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a supplied
6331    /// rank-specific shape cannot be represented.
6332    pub fn from_buffer_col_major(
6333        shape: impl Into<R::Shape>,
6334        buffer: StorageBuffer<T>,
6335        placement: Placement,
6336    ) -> crate::Result<Self>
6337    where
6338        T: TensorScalar + Send + Sync + 'static,
6339    {
6340        typed_tensor_from_buffer_col_major(shape, buffer, placement)
6341    }
6342
6343    /// Consume a scalar-independent provider root into one compact tensor.
6344    #[doc(hidden)]
6345    pub fn from_backend_allocation(
6346        shape: impl Into<R::Shape>,
6347        allocation: Box<dyn crate::BackendAllocation>,
6348        placement: Placement,
6349    ) -> crate::Result<Self>
6350    where
6351        T: TensorScalar + Send + Sync + 'static,
6352    {
6353        typed_tensor_from_backend_allocation(shape, allocation, placement)
6354    }
6355
6356    /// Convert this tensor into static rank metadata after validating its rank.
6357    ///
6358    /// The buffer and placement are preserved. This method changes only the
6359    /// compile-time rank marker on the owned compact column-major tensor.
6360    ///
6361    /// # Examples
6362    ///
6363    /// ```rust
6364    /// use tenferro_tensor::{Rank, TypedTensor};
6365    ///
6366    /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
6367    /// let ranked: TypedTensor<f64, Rank<2>> = tensor.try_into_rank::<2>()?;
6368    /// assert_eq!(ranked.shape(), &[2, 3]);
6369    /// # Ok::<(), tenferro_tensor::Error>(())
6370    /// ```
6371    /// # Errors
6372    ///
6373    /// Returns [`crate::Error::Validation`] with
6374    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
6375    /// rank does not match the existing shape,
6376    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when compact
6377    /// strides cannot be computed, or
6378    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6379    /// preserved buffer cannot hold the rank-converted layout.
6380    pub fn try_into_rank<const N: usize>(self) -> crate::Result<TypedTensor<T, Rank<N>>> {
6381        let op = "TypedTensor::try_into_rank";
6382        let actual = self.shape().len();
6383        let shape: [usize; N] = self.shape().try_into().map_err(|_| {
6384            tensor_layout_error(
6385                op,
6386                ValidationError::RankMismatch {
6387                    expected: N,
6388                    actual,
6389                },
6390            )
6391        })?;
6392        let layout =
6393            TensorLayout::<Rank<N>>::compact(shape).map_err(|err| tensor_layout_error(op, err))?;
6394        let owned = self.group;
6395        Ok(TypedTensor {
6396            group: OwnedTensorGroup {
6397                group: owned.group,
6398                slot: owned.slot,
6399                allocation_index: owned.allocation_index,
6400                host_ptr: owned.host_ptr,
6401                host_byte_len: owned.host_byte_len,
6402                _rank: PhantomData,
6403            },
6404            layout,
6405            placement: self.placement,
6406            _scalar: PhantomData,
6407        })
6408    }
6409
6410    /// Number of elements in the tensor.
6411    ///
6412    /// # Examples
6413    ///
6414    /// ```rust
6415    /// use tenferro_tensor::TypedTensor;
6416    ///
6417    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
6418    /// assert_eq!(t.n_elements(), 6);
6419    /// ```
6420    pub fn n_elements(&self) -> usize {
6421        // Invariant: owned tensor constructors validate compact shape length against buffer length.
6422        match try_shape_product(self.shape(), "TypedTensor::n_elements") {
6423            Ok(n) => n,
6424            Err(err) => {
6425                unreachable!("TypedTensor compact shape is validated at construction: {err}")
6426            }
6427        }
6428    }
6429
6430    /// Tensor shape.
6431    ///
6432    /// # Examples
6433    ///
6434    /// ```
6435    /// use tenferro_tensor::TypedTensor;
6436    ///
6437    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6438    /// assert_eq!(t.shape(), &[2]);
6439    /// ```
6440    pub fn shape(&self) -> &[usize] {
6441        self.layout.shape()
6442    }
6443
6444    /// Tensor rank.
6445    ///
6446    /// # Examples
6447    ///
6448    /// ```
6449    /// use tenferro_tensor::TypedTensor;
6450    ///
6451    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
6452    /// assert_eq!(t.rank(), 2);
6453    /// ```
6454    pub fn rank(&self) -> usize {
6455        self.shape().len()
6456    }
6457
6458    /// Tensor layout metadata.
6459    ///
6460    /// Owned typed tensors are always compact column-major layouts.
6461    ///
6462    /// # Examples
6463    ///
6464    /// ```
6465    /// use tenferro_tensor::TypedTensor;
6466    ///
6467    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
6468    /// assert_eq!(t.layout().strides(), &[1, 2]);
6469    /// ```
6470    pub fn layout(&self) -> &TensorLayout<R> {
6471        &self.layout
6472    }
6473
6474    /// Return the storage backing this tensor.
6475    ///
6476    /// This is an explicit storage-inspection API for backend glue and tests.
6477    /// Host value inspection should prefer [`TypedTensor::host_data`] when the
6478    /// caller requires host storage.
6479    ///
6480    /// # Panics
6481    ///
6482    /// Panics only if the typed descriptor and its single group owner are
6483    /// internally inconsistent.
6484    ///
6485    /// # Examples
6486    ///
6487    /// ```
6488    /// use tenferro_tensor::{StorageBuffer, TypedTensor};
6489    ///
6490    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6491    /// assert!(matches!(t.buffer(), StorageBuffer::Host(_)));
6492    /// ```
6493    pub fn buffer(&self) -> &StorageBuffer<T>
6494    where
6495        T: 'static,
6496    {
6497        match self
6498            .group
6499            .host_buffer::<T>()
6500            .or_else(|| self.group.backend_buffer::<T>())
6501        {
6502            Some(buffer) => buffer,
6503            None => unreachable!("typed tensor group storage mismatch"),
6504        }
6505    }
6506
6507    /// Return the provider family for this tensor when backend-owned.
6508    #[doc(hidden)]
6509    pub fn backend_family(&self) -> Option<&'static str>
6510    where
6511        T: TensorScalar + 'static,
6512    {
6513        self.as_view().backend_family()
6514    }
6515
6516    /// Return the opaque backend buffer for backend-owned tensors.
6517    #[doc(hidden)]
6518    pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>>
6519    where
6520        T: 'static,
6521    {
6522        match self.group.backend_buffer::<T>() {
6523            Some(StorageBuffer::Backend(buffer)) => Some(buffer.as_ref()),
6524            Some(StorageBuffer::Host(_)) | None => None,
6525        }
6526    }
6527
6528    /// Return the mutable backend buffer for an exclusive owner borrow.
6529    #[doc(hidden)]
6530    pub fn backend_buffer_mut(&mut self) -> Option<&mut dyn BackendStorage<T>>
6531    where
6532        T: 'static,
6533    {
6534        let buffer = self.group.backend_buffer_mut::<T>()?;
6535        match buffer {
6536            StorageBuffer::Host(_) => None,
6537            StorageBuffer::Backend(buffer) => Some(buffer.as_mut()),
6538        }
6539    }
6540
6541    /// Prepare this backend tensor for one provider-native read binding.
6542    #[doc(hidden)]
6543    pub fn prepare_device_read(
6544        &self,
6545        op: &'static str,
6546    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
6547    where
6548        T: TensorScalar + 'static,
6549    {
6550        self.group
6551            .prepare_device_read_for_layout::<T>(&self.layout)
6552            .map_err(|error| crate::Error::runtime_state(op, error.to_string()))
6553    }
6554
6555    /// Prepare this backend tensor for one provider-native write binding.
6556    #[doc(hidden)]
6557    pub fn prepare_device_write(
6558        &mut self,
6559        op: &'static str,
6560    ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
6561    where
6562        T: TensorScalar + 'static,
6563    {
6564        let layout = self.layout.clone();
6565        self.group
6566            .prepare_device_write_for_layout::<T>(&layout)
6567            .map_err(|error| crate::Error::runtime_state(op, error.to_string()))
6568    }
6569
6570    pub(crate) fn buffer_len(&self) -> usize
6571    where
6572        T: 'static,
6573    {
6574        self.group
6575            .group
6576            .descriptor_len(self.group.slot)
6577            .unwrap_or_else(|| unreachable!("typed tensor group descriptor mismatch"))
6578    }
6579
6580    /// Return the shared-allocation domain carried by the backend buffer.
6581    ///
6582    /// # Examples
6583    ///
6584    /// ```rust
6585    /// use tenferro_tensor::TypedTensor;
6586    ///
6587    /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
6588    /// assert_eq!(tensor.allocation_domain(), None);
6589    /// # Ok::<(), tenferro_tensor::Error>(())
6590    /// ```
6591    pub fn allocation_domain(&self) -> Option<AllocationDomainId>
6592    where
6593        T: 'static,
6594    {
6595        self.group
6596            .group
6597            .backend_identity(self.group.slot)
6598            .map(|(domain, _)| domain)
6599    }
6600
6601    /// Return the stable physical backend allocation identity.
6602    ///
6603    /// # Examples
6604    ///
6605    /// ```rust
6606    /// use tenferro_tensor::TypedTensor;
6607    ///
6608    /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
6609    /// assert_eq!(tensor.allocation_id(), None);
6610    /// # Ok::<(), tenferro_tensor::Error>(())
6611    /// ```
6612    pub fn allocation_id(&self) -> Option<AllocationId>
6613    where
6614        T: 'static,
6615    {
6616        self.group
6617            .group
6618            .backend_identity(self.group.slot)
6619            .map(|(_, allocation)| allocation)
6620    }
6621
6622    /// Return placement metadata for this tensor.
6623    ///
6624    /// # Examples
6625    ///
6626    /// ```
6627    /// use tenferro_tensor::{MemoryKind, TypedTensor};
6628    ///
6629    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
6630    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
6631    /// ```
6632    pub fn placement(&self) -> &Placement {
6633        &self.placement
6634    }
6635
6636    /// Replace placement metadata without changing the storage buffer.
6637    ///
6638    /// # Examples
6639    ///
6640    /// ```
6641    /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
6642    ///
6643    /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
6644    /// t.set_placement(Placement {
6645    ///     memory_kind: MemoryKind::PinnedHost,
6646    ///     device: None,
6647    ///     cpu_affinity: None,
6648    /// });
6649    /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
6650    /// ```
6651    pub fn set_placement(&mut self, placement: Placement) {
6652        self.placement = placement;
6653    }
6654
6655    /// Replace only CPU routing/locality metadata without changing storage.
6656    ///
6657    /// Device, memory kind, backend allocation domain, and allocation identity
6658    /// remain unchanged.
6659    ///
6660    /// # Examples
6661    ///
6662    /// ```rust
6663    /// use tenferro_tensor::{CpuDomainId, TypedTensor};
6664    ///
6665    /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0])?;
6666    /// tensor.set_cpu_affinity(Some(CpuDomainId::new(4)));
6667    /// assert_eq!(tensor.placement().cpu_affinity, Some(CpuDomainId::new(4)));
6668    /// # Ok::<(), tenferro_tensor::Error>(())
6669    /// ```
6670    pub fn set_cpu_affinity(&mut self, cpu_affinity: Option<CpuDomainId>) {
6671        self.placement.cpu_affinity = cpu_affinity;
6672    }
6673
6674    /// Borrow this tensor as a typed view preserving rank and layout metadata.
6675    ///
6676    /// # Panics
6677    ///
6678    /// Panics only if the typed descriptor and its single group owner are
6679    /// internally inconsistent.
6680    ///
6681    /// # Examples
6682    ///
6683    /// ```rust
6684    /// use tenferro_tensor::{Rank, TypedTensor};
6685    ///
6686    /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
6687    /// let view = tensor.as_view();
6688    /// assert_eq!(view.strides(), &[1, 2]);
6689    /// ```
6690    pub fn as_view(&self) -> TypedTensorView<'_, T, R>
6691    where
6692        T: TensorScalar + 'static,
6693    {
6694        let root = match self.group.view::<T>() {
6695            Ok(root) => root,
6696            Err(error) => unreachable!("typed tensor group descriptor mismatch: {error}"),
6697        };
6698        let buffer = if let Some(allocation) = root.backend_allocation() {
6699            TensorStorageRef::Root(allocation)
6700        } else {
6701            TensorStorageRef::Host(self.group_host_slice())
6702        };
6703        let root = Some(root);
6704        TypedTensorView {
6705            buffer,
6706            root,
6707            layout: self.layout.clone(),
6708            placement: self.placement.clone(),
6709        }
6710    }
6711
6712    /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
6713    ///
6714    /// # Panics
6715    ///
6716    /// Panics only if the typed descriptor and its single group owner are
6717    /// internally inconsistent.
6718    ///
6719    /// # Examples
6720    ///
6721    /// ```rust
6722    /// use tenferro_tensor::TypedTensor;
6723    ///
6724    /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
6725    /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
6726    /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
6727    /// ```
6728    pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
6729    where
6730        T: TensorScalar + 'static,
6731    {
6732        let layout = self.layout.clone();
6733        let placement = self.placement.clone();
6734        let mut root = match self.group.view_mut::<T>() {
6735            Ok(root) => root,
6736            Err(error) => unreachable!("typed tensor group descriptor mismatch: {error}"),
6737        };
6738        let buffer = if let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut() {
6739            TensorStorageRefMut::Backend(buffer.as_mut())
6740        } else {
6741            TensorStorageRefMut::Host(match root.host_slice_mut() {
6742                Ok(slice) => slice,
6743                Err(error) => {
6744                    unreachable!("typed tensor group descriptor is not host-backed: {error}")
6745                }
6746            })
6747        };
6748        TypedTensorViewMut {
6749            buffer,
6750            root: Some(root),
6751            layout,
6752            placement,
6753        }
6754    }
6755
6756    /// Borrow a read-only strided region view over this tensor's backend
6757    /// (device) buffer from explicit layout metadata.
6758    ///
6759    /// This is a metadata-only view: no data is copied or transferred. The
6760    /// layout's reachable element span is validated against the backend
6761    /// buffer's physical length. Host-backed tensors are rejected with an
6762    /// explicit backend error; host regions are expressed with
6763    /// [`TypedTensorView::from_slice`] over host storage instead.
6764    ///
6765    /// # Examples
6766    ///
6767    /// ```rust
6768    /// use tenferro_tensor::TypedTensor;
6769    ///
6770    /// // Host tensors are rejected: this constructor is for backend buffers.
6771    /// let host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
6772    /// let err = host.backend_region_view(vec![2, 2], vec![1, 2], 0).unwrap_err();
6773    /// assert!(err.to_string().contains("backend"));
6774    /// ```
6775    /// # Errors
6776    ///
6777    /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
6778    /// backend region views require a backend buffer. It returns
6779    /// [`crate::Error::Validation`] with
6780    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
6781    /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
6782    /// when the region exceeds the backend buffer, or
6783    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
6784    /// arithmetic overflow.
6785    pub fn backend_region_view(
6786        &self,
6787        shape: Vec<usize>,
6788        strides: Vec<isize>,
6789        offset: isize,
6790    ) -> crate::Result<TypedTensorView<'_, T, DynRank>>
6791    where
6792        T: TensorScalar + 'static,
6793    {
6794        let op = "TypedTensor::backend_region_view";
6795        let root = self.group.view_dyn::<T>()?;
6796        let Some(allocation) = root.backend_allocation() else {
6797            return Err(crate::Error::runtime_state(
6798                op,
6799                "expected a backend (device) allocation; host tensors use \
6800                 TypedTensorView::from_slice over host storage",
6801            ));
6802        };
6803        let element_len = allocation
6804            .root_extent()
6805            .byte_len()
6806            .checked_div(size_of::<T>())
6807            .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6808        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, element_len)
6809            .map_err(|err| tensor_layout_error(op, err))?;
6810        Ok(TypedTensorView {
6811            buffer: TensorStorageRef::Root(allocation),
6812            root: Some(root),
6813            layout,
6814            placement: self.placement.clone(),
6815        })
6816    }
6817
6818    /// Borrow a mutable strided region view over this tensor's backend
6819    /// (device) buffer from explicit layout metadata.
6820    ///
6821    /// This is the mutable counterpart of
6822    /// [`TypedTensor::backend_region_view`]. The layout's reachable element
6823    /// span is validated against the backend buffer's physical length, and
6824    /// layouts whose logical elements alias the same physical element are
6825    /// rejected. Host-backed tensors are rejected with an explicit backend
6826    /// error; mutable host regions must go through
6827    /// [`TypedTensorViewMut::try_multi_slice_mut`] or host constructors.
6828    ///
6829    /// The returned view borrows the tensor's backend owner exclusively for its
6830    /// lifetime. This keeps write authority tied to the owner; a second mutable
6831    /// region view must be created only after the first borrow ends.
6832    ///
6833    /// # Examples
6834    ///
6835    /// ```rust
6836    /// use tenferro_tensor::TypedTensor;
6837    ///
6838    /// // Host tensors are rejected: this constructor is for backend buffers.
6839    /// let mut host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
6840    /// let err = host.backend_region_view_mut(vec![2, 2], vec![1, 2], 0).unwrap_err();
6841    /// assert!(err.to_string().contains("backend"));
6842    /// ```
6843    /// # Errors
6844    ///
6845    /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
6846    /// mutable backend region views require a backend buffer. It returns
6847    /// [`crate::Error::Validation`] with
6848    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
6849    /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
6850    /// when the region exceeds the backend buffer,
6851    /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
6852    /// logical elements alias, or
6853    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
6854    /// arithmetic overflow.
6855    pub fn backend_region_view_mut(
6856        &mut self,
6857        shape: Vec<usize>,
6858        strides: Vec<isize>,
6859        offset: isize,
6860    ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>>
6861    where
6862        T: TensorScalar + 'static,
6863    {
6864        let op = "TypedTensor::backend_region_view_mut";
6865        let mut root = self.group.view_mut_dyn::<T>()?;
6866        let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut() else {
6867            return Err(crate::Error::runtime_state(
6868                op,
6869                "expected a backend (device) buffer; mutable host regions use \
6870                 TypedTensorViewMut host constructors or try_multi_slice_mut",
6871            ));
6872        };
6873        let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
6874            .map_err(|err| tensor_layout_error(op, err))?;
6875        layout
6876            .validate_mutable_no_overlap()
6877            .map_err(|err| tensor_layout_error(op, err))?;
6878        Ok(TypedTensorViewMut {
6879            buffer: TensorStorageRefMut::Backend(buffer.as_mut()),
6880            root: Some(root),
6881            layout,
6882            placement: self.placement.clone(),
6883        })
6884    }
6885
6886    /// Consume this tensor and return its layout metadata.
6887    ///
6888    /// # Examples
6889    ///
6890    /// ```
6891    /// use tenferro_tensor::TypedTensor;
6892    ///
6893    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6894    /// assert!(t.into_layout().is_compact_col_major().unwrap());
6895    /// ```
6896    pub fn into_layout(self) -> TensorLayout<R> {
6897        self.layout
6898    }
6899
6900    /// Consume this tensor and return its storage, layout, and placement.
6901    ///
6902    /// # Examples
6903    ///
6904    /// ```
6905    /// use tenferro_tensor::{StorageBuffer, TypedTensor};
6906    ///
6907    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6908    /// let (buffer, layout, placement) = t.into_parts();
6909    /// assert!(matches!(buffer, StorageBuffer::Host(_)));
6910    /// assert_eq!(layout.shape(), &[2]);
6911    /// assert!(placement.device.is_none());
6912    /// ```
6913    pub fn into_parts(self) -> (StorageBuffer<T>, TensorLayout<R>, Placement)
6914    where
6915        T: TensorScalar,
6916    {
6917        let TypedTensor {
6918            group,
6919            layout,
6920            placement,
6921            ..
6922        } = self;
6923        let buffer = match group.into_host_vec::<T>() {
6924            Ok(data) => StorageBuffer::Host(data),
6925            Err(_) => StorageBuffer::Host(Vec::new()),
6926        };
6927        (buffer, layout, placement)
6928    }
6929}
6930
6931impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R> {
6932    /// Create a tensor from a column-major buffer.
6933    ///
6934    /// # Examples
6935    ///
6936    /// ```
6937    /// use tenferro_tensor::TypedTensor;
6938    ///
6939    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
6940    /// assert_eq!(t.get(&[1, 0])?, &2.0);
6941    /// # Ok::<(), tenferro_tensor::Error>(())
6942    /// ```
6943    /// # Errors
6944    ///
6945    /// Returns [`crate::Error::Validation`] with
6946    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
6947    /// the shape product differs from `data.len()`, or
6948    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
6949    /// arithmetic overflows.
6950    pub fn from_vec_col_major(shape: impl Into<R::Shape>, data: Vec<T>) -> crate::Result<Self> {
6951        typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
6952    }
6953
6954    /// Make an explicit owning copy of this tensor.
6955    ///
6956    /// Host storage is copied into a fresh allocation. Backend-owned storage
6957    /// must be duplicated by the active backend, so this generic tensor layer
6958    /// reports that operation as unsupported.
6959    ///
6960    /// # Errors
6961    ///
6962    /// Returns [`crate::Error::RuntimeState`] when host data cannot be
6963    /// borrowed, or [`ValidationError::InvalidArgument`] when the new group
6964    /// cannot be constructed.
6965    pub fn duplicate(&self) -> crate::Result<Self> {
6966        self.as_view().duplicate()
6967    }
6968
6969    /// Consume this tensor and return its owned column-major host buffer.
6970    ///
6971    /// # Examples
6972    ///
6973    /// ```
6974    /// use tenferro_tensor::TypedTensor;
6975    ///
6976    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6977    /// let (shape, data) = t.into_vec_col_major().unwrap();
6978    /// assert_eq!(shape, vec![2]);
6979    /// assert_eq!(data, vec![1.0, 2.0]);
6980    /// ```
6981    /// # Errors
6982    ///
6983    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
6984    /// storage; download it before exporting a host `Vec`.
6985    pub fn into_vec_col_major(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
6986        let shape = self.shape().to_vec();
6987        if self.group.backend_buffer::<T>().is_some() {
6988            return Err(crate::Error::runtime_state(
6989                "into_vec_col_major",
6990                "backend buffers cannot be exported as host Vec",
6991            ));
6992        }
6993        Ok((shape, self.group.into_host_vec::<T>()?))
6994    }
6995
6996    /// Consume this tensor and return its owned host data without rebuilding
6997    /// shape metadata. This is intended for ownership-preserving buffer-pool
6998    /// handoff; callers that need the shape should use [`Self::into_vec_col_major`].
6999    ///
7000    /// # Errors
7001    ///
7002    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7003    /// storage.
7004    pub fn into_host_vec(self) -> crate::Result<Vec<T>> {
7005        if self.group.backend_buffer::<T>().is_some() {
7006            return Err(crate::Error::runtime_state(
7007                "into_host_vec",
7008                "backend buffers cannot be exported as host Vec",
7009            ));
7010        }
7011        self.group.into_host_vec::<T>()
7012    }
7013
7014    /// Borrow the host buffer.
7015    ///
7016    /// # Examples
7017    ///
7018    /// ```rust
7019    /// use tenferro_tensor::TypedTensor;
7020    ///
7021    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7022    /// assert_eq!(t.host_data()?, &[1.0, 2.0]);
7023    /// # Ok::<(), tenferro_tensor::Error>(())
7024    /// ```
7025    /// # Errors
7026    ///
7027    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7028    /// storage; download it before borrowing host data.
7029    pub fn host_data(&self) -> crate::Result<&[T]> {
7030        self.group.host_slice::<T>()
7031    }
7032
7033    /// Borrow compact host-visible storage through one synchronization guard.
7034    #[doc(hidden)]
7035    pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
7036    where
7037        T: TensorScalar + 'static,
7038    {
7039        let view = self
7040            .group
7041            .group
7042            .view::<T, R>(self.group.slot)
7043            .map_err(|error| group_error("TypedTensor::with_host_read", error))?;
7044        let prepared = view.prepare_host_read().map_err(|error| {
7045            crate::Error::runtime_state("TypedTensor::with_host_read", error.to_string())
7046        })?;
7047        let slice = prepared.as_slice().ok_or_else(|| {
7048            crate::Error::unsupported(
7049                "TypedTensor::with_host_read",
7050                "host guard access requires a compact descriptor",
7051            )
7052        })?;
7053        Ok(f(slice))
7054    }
7055
7056    /// Borrow compact host-visible storage through one exclusive write guard.
7057    #[doc(hidden)]
7058    pub fn with_host_write<U>(&mut self, f: impl FnOnce(&mut [T]) -> U) -> crate::Result<U>
7059    where
7060        T: TensorScalar + 'static,
7061    {
7062        let mut view = self
7063            .group
7064            .group
7065            .view_mut::<T, R>(self.group.slot)
7066            .map_err(|error| group_error("TypedTensor::with_host_write", error))?;
7067        let mut prepared = view.prepare_host_write().map_err(|error| {
7068            crate::Error::runtime_state("TypedTensor::with_host_write", error.to_string())
7069        })?;
7070        let slice = prepared.as_slice_mut().ok_or_else(|| {
7071            crate::Error::unsupported(
7072                "TypedTensor::with_host_write",
7073                "host guard access requires a compact descriptor",
7074            )
7075        })?;
7076        Ok(f(slice))
7077    }
7078
7079    /// View the tensor data as a flat slice.
7080    ///
7081    /// This is an alias for `host_data()` for API consistency with
7082    /// `Tensor::as_slice`.
7083    ///
7084    /// # Examples
7085    ///
7086    /// ```
7087    /// use tenferro_tensor::TypedTensor;
7088    ///
7089    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7090    /// assert_eq!(t.as_slice()?, &[1.0, 2.0]);
7091    /// # Ok::<(), tenferro_tensor::Error>(())
7092    /// ```
7093    /// # Errors
7094    ///
7095    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7096    /// storage; download it before borrowing it as a host slice.
7097    pub fn as_slice(&self) -> crate::Result<&[T]> {
7098        self.host_data()
7099    }
7100
7101    /// Mutably borrow the host buffer.
7102    ///
7103    /// # Examples
7104    ///
7105    /// ```rust
7106    /// use tenferro_tensor::TypedTensor;
7107    ///
7108    /// let mut t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7109    /// t.host_data_mut()?[0] = 3.0;
7110    /// assert_eq!(t.host_data()?, &[3.0, 0.0]);
7111    /// # Ok::<(), tenferro_tensor::Error>(())
7112    /// ```
7113    /// # Errors
7114    ///
7115    /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7116    /// storage; download it before mutably borrowing host data.
7117    pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
7118        self.group.host_slice_mut::<T>()
7119    }
7120
7121    fn group_host_slice(&self) -> &[T] {
7122        self.group
7123            .view::<T>()
7124            .ok()
7125            .and_then(|view| view.host_slice().ok())
7126            .unwrap_or_default()
7127    }
7128
7129    fn group_host_slice_mut(&mut self) -> &mut [T] {
7130        self.group
7131            .view_mut::<T>()
7132            .ok()
7133            .and_then(|mut view| view.host_slice_mut().ok())
7134            .unwrap_or_default()
7135    }
7136
7137    /// Compute the linear physical-buffer offset for a logical index.
7138    ///
7139    /// # Examples
7140    ///
7141    /// ```rust
7142    /// use tenferro_tensor::TypedTensor;
7143    ///
7144    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
7145    /// assert_eq!(t.linear_offset(&[1, 2])?, 5);
7146    /// # Ok::<(), tenferro_tensor::Error>(())
7147    /// ```
7148    /// # Errors
7149    ///
7150    /// Returns [`crate::Error::Validation`] with
7151    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7152    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7153    /// when an index is outside its axis extent, or
7154    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7155    /// arithmetic overflows.
7156    pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7157        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
7158    }
7159
7160    /// Compute the physical element offset for a logical index.
7161    ///
7162    /// # Examples
7163    ///
7164    /// ```rust
7165    /// use tenferro_tensor::TypedTensor;
7166    ///
7167    /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
7168    /// assert_eq!(t.layout_linear_offset(&[1, 2])?, 5);
7169    /// # Ok::<(), tenferro_tensor::Error>(())
7170    /// ```
7171    /// # Errors
7172    ///
7173    /// Returns [`crate::Error::Validation`] with
7174    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7175    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7176    /// when an index is outside its axis extent, or
7177    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7178    /// arithmetic overflows.
7179    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7180        try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::layout_linear_offset")
7181    }
7182
7183    /// Return whether this owned tensor is compact column-major.
7184    ///
7185    /// # Examples
7186    ///
7187    /// ```rust
7188    /// use tenferro_tensor::TypedTensor;
7189    ///
7190    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7191    /// assert!(t.is_col_major_contiguous()?);
7192    /// # Ok::<(), tenferro_tensor::Error>(())
7193    /// ```
7194    /// # Errors
7195    ///
7196    /// Returns [`crate::Error::Validation`] with
7197    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7198    /// compactness arithmetic overflows.
7199    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
7200        self.layout
7201            .is_compact_col_major()
7202            .map_err(|err| tensor_layout_error("TypedTensor::is_col_major_contiguous", err))
7203    }
7204
7205    /// Return a compact string summary of this tensor's layout metadata.
7206    ///
7207    /// # Examples
7208    ///
7209    /// ```rust
7210    /// use tenferro_tensor::TypedTensor;
7211    ///
7212    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7213    /// assert!(t.layout_summary().contains("shape=[2]"));
7214    /// # Ok::<(), tenferro_tensor::Error>(())
7215    /// ```
7216    pub fn layout_summary(&self) -> String {
7217        layout_summary(self.shape(), self.layout.strides(), self.layout.offset())
7218    }
7219
7220    /// Assert this tensor is compact column-major.
7221    ///
7222    /// # Examples
7223    ///
7224    /// ```rust
7225    /// use tenferro_tensor::TypedTensor;
7226    ///
7227    /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7228    /// t.assert_col_major_contiguous()?;
7229    /// # Ok::<(), tenferro_tensor::Error>(())
7230    /// ```
7231    /// # Errors
7232    ///
7233    /// Returns [`crate::Error::Validation`] with
7234    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7235    /// compactness arithmetic overflows, or
7236    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7237    /// tensor is not compact column-major.
7238    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
7239        assert_layout_col_major_contiguous(
7240            self.is_col_major_contiguous()?,
7241            self.shape(),
7242            self.layout.strides(),
7243            self.layout.offset(),
7244            "TypedTensor::assert_col_major_contiguous",
7245        )
7246    }
7247
7248    /// Borrow a single element by multi-index.
7249    ///
7250    /// # Examples
7251    ///
7252    /// ```rust
7253    /// use tenferro_tensor::TypedTensor;
7254    ///
7255    /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7256    /// assert_eq!(t.get(&[1])?, &2.0);
7257    /// # Ok::<(), tenferro_tensor::Error>(())
7258    /// ```
7259    /// # Errors
7260    ///
7261    /// Returns [`crate::Error::Validation`] with
7262    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7263    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7264    /// when an index is outside its axis extent, or
7265    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7266    /// computed offset is outside the host buffer. It returns
7267    /// [`crate::Error::RuntimeState`] when the tensor uses backend storage.
7268    pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
7269        let off = self.linear_offset(indices)?;
7270        self.host_data()?.get(off).ok_or_else(|| {
7271            crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
7272        })
7273    }
7274
7275    /// Mutably borrow a single element by multi-index.
7276    ///
7277    /// # Examples
7278    ///
7279    /// ```rust
7280    /// use tenferro_tensor::TypedTensor;
7281    ///
7282    /// let mut t = TypedTensor::<f64>::zeros(vec![1]).unwrap();
7283    /// *t.get_mut(&[0])? = 7.0;
7284    /// assert_eq!(t.host_data()?, &[7.0]);
7285    /// # Ok::<(), tenferro_tensor::Error>(())
7286    /// ```
7287    /// # Errors
7288    ///
7289    /// Returns [`crate::Error::Validation`] with
7290    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7291    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7292    /// when an index is outside its axis extent, or
7293    /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7294    /// computed offset is outside the host buffer. It returns
7295    /// [`crate::Error::RuntimeState`] when the tensor uses backend storage.
7296    pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
7297        let off = self.linear_offset(indices)?;
7298        self.host_data_mut()?.get_mut(off).ok_or_else(|| {
7299            crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
7300        })
7301    }
7302}
7303
7304impl<R: TensorRank> TypedTensor<Complex32, R> {
7305    /// Borrow this tensor as an interleaved `f32` view without copying.
7306    ///
7307    /// # Errors
7308    ///
7309    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7310    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
7311    /// invalid tensor layout.
7312    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f32, DynRank>> {
7313        self.as_view().as_real_view()
7314    }
7315
7316    /// Borrow this tensor mutably as an interleaved `f32` view without copying.
7317    ///
7318    /// # Errors
7319    ///
7320    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7321    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
7322    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
7323    /// invalid representation metadata.
7324    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
7325        let op = "TypedTensor::as_real_view_mut";
7326        validate_representation_pair(op, DType::C32, DType::F32)?;
7327        let layout = reinterpret_complex_to_real_layout(
7328            self.shape(),
7329            self.layout.strides(),
7330            self.layout.offset(),
7331            self.buffer_len(),
7332            op,
7333        )?;
7334        layout
7335            .validate_mutable_no_overlap()
7336            .map_err(|err| tensor_layout_error(op, err))?;
7337        if self.backend_buffer().is_some() {
7338            return Err(crate::Error::unsupported(
7339                op,
7340                "backend representation reinterpretation is enabled by the provider phases",
7341            ));
7342        }
7343        let placement = self.placement.clone();
7344        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(
7345            self.group_host_slice_mut(),
7346            op,
7347        )?);
7348        Ok(TypedTensorViewMut {
7349            buffer,
7350            root: None,
7351            layout,
7352            placement,
7353        })
7354    }
7355
7356    /// Consume this tensor and reinterpret its owner as `f32` without copying.
7357    ///
7358    /// A failed operation returns the unchanged owner through
7359    /// [`ReinterpretError::into_owner`].
7360    ///
7361    /// # Errors
7362    ///
7363    /// Returns [`ReinterpretError::error`] containing
7364    /// [`ValidationError::InvalidArgument`] or
7365    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7366    /// owner.
7367    pub fn into_real(self) -> Result<TypedTensor<f32, DynRank>, ReinterpretError<Self>> {
7368        let op = "TypedTensor::into_real";
7369        if let Err(error) = validate_representation_pair(op, DType::C32, DType::F32) {
7370            return Err(ReinterpretError::new(self, error));
7371        }
7372        let source_shape = self.shape().to_vec();
7373        let source_strides = self.layout.strides().to_vec();
7374        let source_offset = self.layout.offset();
7375        let target_layout = match reinterpret_complex_to_real_layout(
7376            &source_shape,
7377            &source_strides,
7378            source_offset,
7379            self.buffer_len(),
7380            op,
7381        ) {
7382            Ok(layout) => layout,
7383            Err(error) => return Err(ReinterpretError::new(self, error)),
7384        };
7385        let TypedTensor {
7386            group,
7387            layout: source_layout,
7388            placement,
7389            ..
7390        } = self;
7391        match group.reinterpret::<Complex32, f32>(
7392            target_layout.shape().to_vec(),
7393            target_layout.strides().to_vec(),
7394            target_layout.offset(),
7395        ) {
7396            Ok(group) => Ok(TypedTensor {
7397                group,
7398                layout: target_layout,
7399                placement,
7400                _scalar: PhantomData,
7401            }),
7402            Err((group, error)) => Err(ReinterpretError::new(
7403                TypedTensor {
7404                    group,
7405                    layout: source_layout,
7406                    placement,
7407                    _scalar: PhantomData,
7408                },
7409                error,
7410            )),
7411        }
7412    }
7413}
7414
7415impl<R: TensorRank> TypedTensor<Complex64, R> {
7416    /// Borrow this tensor as an interleaved `f64` view without copying.
7417    ///
7418    /// # Errors
7419    ///
7420    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7421    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
7422    /// invalid tensor layout.
7423    pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f64, DynRank>> {
7424        self.as_view().as_real_view()
7425    }
7426
7427    /// Borrow this tensor mutably as an interleaved `f64` view without copying.
7428    ///
7429    /// # Errors
7430    ///
7431    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7432    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
7433    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
7434    /// invalid representation metadata.
7435    pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
7436        let op = "TypedTensor::as_real_view_mut";
7437        validate_representation_pair(op, DType::C64, DType::F64)?;
7438        let layout = reinterpret_complex_to_real_layout(
7439            self.shape(),
7440            self.layout.strides(),
7441            self.layout.offset(),
7442            self.buffer_len(),
7443            op,
7444        )?;
7445        layout
7446            .validate_mutable_no_overlap()
7447            .map_err(|err| tensor_layout_error(op, err))?;
7448        if self.backend_buffer().is_some() {
7449            return Err(crate::Error::unsupported(
7450                op,
7451                "backend representation reinterpretation is enabled by the provider phases",
7452            ));
7453        }
7454        let placement = self.placement.clone();
7455        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(
7456            self.group_host_slice_mut(),
7457            op,
7458        )?);
7459        Ok(TypedTensorViewMut {
7460            buffer,
7461            root: None,
7462            layout,
7463            placement,
7464        })
7465    }
7466
7467    /// Consume this tensor and reinterpret its owner as `f64` without copying.
7468    ///
7469    /// A failed operation returns the unchanged owner through
7470    /// [`ReinterpretError::into_owner`].
7471    ///
7472    /// # Errors
7473    ///
7474    /// Returns [`ReinterpretError::error`] containing
7475    /// [`ValidationError::InvalidArgument`] or
7476    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7477    /// owner.
7478    pub fn into_real(self) -> Result<TypedTensor<f64, DynRank>, ReinterpretError<Self>> {
7479        let op = "TypedTensor::into_real";
7480        if let Err(error) = validate_representation_pair(op, DType::C64, DType::F64) {
7481            return Err(ReinterpretError::new(self, error));
7482        }
7483        let source_shape = self.shape().to_vec();
7484        let source_strides = self.layout.strides().to_vec();
7485        let source_offset = self.layout.offset();
7486        let target_layout = match reinterpret_complex_to_real_layout(
7487            &source_shape,
7488            &source_strides,
7489            source_offset,
7490            self.buffer_len(),
7491            op,
7492        ) {
7493            Ok(layout) => layout,
7494            Err(error) => return Err(ReinterpretError::new(self, error)),
7495        };
7496        let TypedTensor {
7497            group,
7498            layout: source_layout,
7499            placement,
7500            ..
7501        } = self;
7502        match group.reinterpret::<Complex64, f64>(
7503            target_layout.shape().to_vec(),
7504            target_layout.strides().to_vec(),
7505            target_layout.offset(),
7506        ) {
7507            Ok(group) => Ok(TypedTensor {
7508                group,
7509                layout: target_layout,
7510                placement,
7511                _scalar: PhantomData,
7512            }),
7513            Err((group, error)) => Err(ReinterpretError::new(
7514                TypedTensor {
7515                    group,
7516                    layout: source_layout,
7517                    placement,
7518                    _scalar: PhantomData,
7519                },
7520                error,
7521            )),
7522        }
7523    }
7524}
7525
7526impl<R: TensorRank> TypedTensor<f32, R> {
7527    /// Borrow this tensor as a complex view without copying.
7528    ///
7529    /// # Errors
7530    ///
7531    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7532    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
7533    /// invalid tensor layout.
7534    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex32, DynRank>> {
7535        self.as_view().as_complex_view()
7536    }
7537
7538    /// Borrow this tensor mutably as a complex view without copying.
7539    ///
7540    /// # Errors
7541    ///
7542    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7543    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
7544    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
7545    /// invalid representation metadata.
7546    pub fn as_complex_view_mut(
7547        &mut self,
7548    ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
7549        let op = "TypedTensor::as_complex_view_mut";
7550        validate_representation_pair(op, DType::F32, DType::C32)?;
7551        let layout = reinterpret_real_to_complex_layout(
7552            self.shape(),
7553            self.layout.strides(),
7554            self.layout.offset(),
7555            self.buffer_len(),
7556            op,
7557        )?;
7558        layout
7559            .validate_mutable_no_overlap()
7560            .map_err(|err| tensor_layout_error(op, err))?;
7561        if self.backend_buffer().is_some() {
7562            return Err(crate::Error::unsupported(
7563                op,
7564                "backend representation reinterpretation is enabled by the provider phases",
7565            ));
7566        }
7567        let placement = self.placement.clone();
7568        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(
7569            self.group_host_slice_mut(),
7570            op,
7571        )?);
7572        Ok(TypedTensorViewMut {
7573            buffer,
7574            root: None,
7575            layout,
7576            placement,
7577        })
7578    }
7579
7580    /// Consume this tensor and reinterpret its owner as `Complex32` without copying.
7581    ///
7582    /// The compact source must have an even physical element count. A failed
7583    /// operation returns the unchanged owner through
7584    /// [`ReinterpretError::into_owner`].
7585    ///
7586    /// # Errors
7587    ///
7588    /// Returns [`ReinterpretError::error`] containing
7589    /// [`ValidationError::InvalidArgument`] or
7590    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7591    /// owner.
7592    pub fn into_complex(self) -> Result<TypedTensor<Complex32, DynRank>, ReinterpretError<Self>> {
7593        let op = "TypedTensor::into_complex";
7594        if let Err(error) = validate_representation_pair(op, DType::F32, DType::C32) {
7595            return Err(ReinterpretError::new(self, error));
7596        }
7597        if !self.buffer_len().is_multiple_of(2) {
7598            return Err(ReinterpretError::new(
7599                self,
7600                crate::Error::invalid_argument(
7601                    op,
7602                    "buffer",
7603                    "the owned real buffer must contain an even number of elements",
7604                ),
7605            ));
7606        }
7607        let source_shape = self.shape().to_vec();
7608        let source_strides = self.layout.strides().to_vec();
7609        let source_offset = self.layout.offset();
7610        let target_layout = match reinterpret_real_to_complex_layout(
7611            &source_shape,
7612            &source_strides,
7613            source_offset,
7614            self.buffer_len(),
7615            op,
7616        ) {
7617            Ok(layout) => layout,
7618            Err(error) => return Err(ReinterpretError::new(self, error)),
7619        };
7620        let TypedTensor {
7621            group,
7622            layout: source_layout,
7623            placement,
7624            ..
7625        } = self;
7626        match group.reinterpret::<f32, Complex32>(
7627            target_layout.shape().to_vec(),
7628            target_layout.strides().to_vec(),
7629            target_layout.offset(),
7630        ) {
7631            Ok(group) => Ok(TypedTensor {
7632                group,
7633                layout: target_layout,
7634                placement,
7635                _scalar: PhantomData,
7636            }),
7637            Err((group, error)) => Err(ReinterpretError::new(
7638                TypedTensor {
7639                    group,
7640                    layout: source_layout,
7641                    placement,
7642                    _scalar: PhantomData,
7643                },
7644                error,
7645            )),
7646        }
7647    }
7648}
7649
7650impl<R: TensorRank> TypedTensor<f64, R> {
7651    /// Borrow this tensor as a complex view without copying.
7652    ///
7653    /// # Errors
7654    ///
7655    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7656    /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
7657    /// invalid tensor layout.
7658    pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex64, DynRank>> {
7659        self.as_view().as_complex_view()
7660    }
7661
7662    /// Borrow this tensor mutably as a complex view without copying.
7663    ///
7664    /// # Errors
7665    ///
7666    /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7667    /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
7668    /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
7669    /// invalid representation metadata.
7670    pub fn as_complex_view_mut(
7671        &mut self,
7672    ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
7673        let op = "TypedTensor::as_complex_view_mut";
7674        validate_representation_pair(op, DType::F64, DType::C64)?;
7675        let layout = reinterpret_real_to_complex_layout(
7676            self.shape(),
7677            self.layout.strides(),
7678            self.layout.offset(),
7679            self.buffer_len(),
7680            op,
7681        )?;
7682        layout
7683            .validate_mutable_no_overlap()
7684            .map_err(|err| tensor_layout_error(op, err))?;
7685        if self.backend_buffer().is_some() {
7686            return Err(crate::Error::unsupported(
7687                op,
7688                "backend representation reinterpretation is enabled by the provider phases",
7689            ));
7690        }
7691        let placement = self.placement.clone();
7692        let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(
7693            self.group_host_slice_mut(),
7694            op,
7695        )?);
7696        Ok(TypedTensorViewMut {
7697            buffer,
7698            root: None,
7699            layout,
7700            placement,
7701        })
7702    }
7703
7704    /// Consume this tensor and reinterpret its owner as `Complex64` without copying.
7705    ///
7706    /// The compact source must have an even physical element count. A failed
7707    /// operation returns the unchanged owner through
7708    /// [`ReinterpretError::into_owner`].
7709    ///
7710    /// # Errors
7711    ///
7712    /// Returns [`ReinterpretError::error`] containing
7713    /// [`ValidationError::InvalidArgument`] or
7714    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7715    /// owner.
7716    pub fn into_complex(self) -> Result<TypedTensor<Complex64, DynRank>, ReinterpretError<Self>> {
7717        let op = "TypedTensor::into_complex";
7718        if let Err(error) = validate_representation_pair(op, DType::F64, DType::C64) {
7719            return Err(ReinterpretError::new(self, error));
7720        }
7721        if !self.buffer_len().is_multiple_of(2) {
7722            return Err(ReinterpretError::new(
7723                self,
7724                crate::Error::invalid_argument(
7725                    op,
7726                    "buffer",
7727                    "the owned real buffer must contain an even number of elements",
7728                ),
7729            ));
7730        }
7731        let source_shape = self.shape().to_vec();
7732        let source_strides = self.layout.strides().to_vec();
7733        let source_offset = self.layout.offset();
7734        let target_layout = match reinterpret_real_to_complex_layout(
7735            &source_shape,
7736            &source_strides,
7737            source_offset,
7738            self.buffer_len(),
7739            op,
7740        ) {
7741            Ok(layout) => layout,
7742            Err(error) => return Err(ReinterpretError::new(self, error)),
7743        };
7744        let TypedTensor {
7745            group,
7746            layout: source_layout,
7747            placement,
7748            ..
7749        } = self;
7750        match group.reinterpret::<f64, Complex64>(
7751            target_layout.shape().to_vec(),
7752            target_layout.strides().to_vec(),
7753            target_layout.offset(),
7754        ) {
7755            Ok(group) => Ok(TypedTensor {
7756                group,
7757                layout: target_layout,
7758                placement,
7759                _scalar: PhantomData,
7760            }),
7761            Err((group, error)) => Err(ReinterpretError::new(
7762                TypedTensor {
7763                    group,
7764                    layout: source_layout,
7765                    placement,
7766                    _scalar: PhantomData,
7767                },
7768                error,
7769            )),
7770        }
7771    }
7772}
7773
7774impl Tensor {
7775    /// Borrow a complex tensor as its sealed interleaved real representation.
7776    ///
7777    /// # Errors
7778    ///
7779    /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
7780    /// [`ValidationError::ViewOutOfBounds`] or
7781    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
7782    pub fn as_real_view(&self) -> crate::Result<TensorView<'_>> {
7783        match self {
7784            Tensor::C32(tensor) => tensor.as_real_view().map(TensorView::F32),
7785            Tensor::C64(tensor) => tensor.as_real_view().map(TensorView::F64),
7786            other => Err(crate::Error::unsupported_dtype_conversion(
7787                "Tensor::as_real_view",
7788                other.dtype(),
7789                DType::F32,
7790                "only complex tensors have a sealed real representation view",
7791            )),
7792        }
7793    }
7794
7795    /// Borrow a complex tensor mutably as its sealed interleaved real representation.
7796    ///
7797    /// # Errors
7798    ///
7799    /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
7800    /// [`ValidationError::ViewOutOfBounds`] or
7801    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
7802    pub fn as_real_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
7803        match self {
7804            Tensor::C32(tensor) => tensor.as_real_view_mut().map(TensorViewMut::F32),
7805            Tensor::C64(tensor) => tensor.as_real_view_mut().map(TensorViewMut::F64),
7806            other => Err(crate::Error::unsupported_dtype_conversion(
7807                "Tensor::as_real_view_mut",
7808                other.dtype(),
7809                DType::F32,
7810                "only complex tensors have a sealed real representation view",
7811            )),
7812        }
7813    }
7814
7815    /// Consume a complex tensor and reinterpret its owner as real without copying.
7816    ///
7817    /// # Errors
7818    ///
7819    /// Returns [`ReinterpretError::error`] containing
7820    /// [`ValidationError::InvalidArgument`] or
7821    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7822    /// owner.
7823    pub fn into_real(self) -> Result<Self, ReinterpretError<Self>> {
7824        match self {
7825            Tensor::C32(tensor) => tensor.into_real().map(Tensor::F32).map_err(|error| {
7826                let (owner, error) = error.into_parts();
7827                ReinterpretError::new(Tensor::C32(owner), error)
7828            }),
7829            Tensor::C64(tensor) => tensor.into_real().map(Tensor::F64).map_err(|error| {
7830                let (owner, error) = error.into_parts();
7831                ReinterpretError::new(Tensor::C64(owner), error)
7832            }),
7833            tensor => Err(ReinterpretError::new(
7834                tensor,
7835                crate::Error::unsupported(
7836                    "Tensor::into_real",
7837                    "only complex tensors have a sealed real representation",
7838                ),
7839            )),
7840        }
7841    }
7842
7843    /// Borrow an interleaved real tensor as its sealed complex representation.
7844    ///
7845    /// # Errors
7846    ///
7847    /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
7848    /// [`ValidationError::ViewOutOfBounds`] or
7849    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
7850    pub fn as_complex_view(&self) -> crate::Result<TensorView<'_>> {
7851        match self {
7852            Tensor::F32(tensor) => tensor.as_complex_view().map(TensorView::C32),
7853            Tensor::F64(tensor) => tensor.as_complex_view().map(TensorView::C64),
7854            other => Err(crate::Error::unsupported_dtype_conversion(
7855                "Tensor::as_complex_view",
7856                other.dtype(),
7857                DType::C32,
7858                "only real tensors can have a sealed complex representation view",
7859            )),
7860        }
7861    }
7862
7863    /// Borrow an interleaved real tensor mutably as its sealed complex representation.
7864    ///
7865    /// # Errors
7866    ///
7867    /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
7868    /// [`ValidationError::ViewOutOfBounds`] or
7869    /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
7870    pub fn as_complex_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
7871        match self {
7872            Tensor::F32(tensor) => tensor.as_complex_view_mut().map(TensorViewMut::C32),
7873            Tensor::F64(tensor) => tensor.as_complex_view_mut().map(TensorViewMut::C64),
7874            other => Err(crate::Error::unsupported_dtype_conversion(
7875                "Tensor::as_complex_view_mut",
7876                other.dtype(),
7877                DType::C32,
7878                "only real tensors can have a sealed complex representation view",
7879            )),
7880        }
7881    }
7882
7883    /// Consume a real tensor and reinterpret its owner as complex without copying.
7884    ///
7885    /// # Errors
7886    ///
7887    /// Returns [`ReinterpretError::error`] containing
7888    /// [`ValidationError::InvalidArgument`] or
7889    /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7890    /// owner.
7891    pub fn into_complex(self) -> Result<Self, ReinterpretError<Self>> {
7892        match self {
7893            Tensor::F32(tensor) => tensor.into_complex().map(Tensor::C32).map_err(|error| {
7894                let (owner, error) = error.into_parts();
7895                ReinterpretError::new(Tensor::F32(owner), error)
7896            }),
7897            Tensor::F64(tensor) => tensor.into_complex().map(Tensor::C64).map_err(|error| {
7898                let (owner, error) = error.into_parts();
7899                ReinterpretError::new(Tensor::F64(owner), error)
7900            }),
7901            tensor => Err(ReinterpretError::new(
7902                tensor,
7903                crate::Error::unsupported(
7904                    "Tensor::into_complex",
7905                    "only real tensors have a sealed complex representation",
7906                ),
7907            )),
7908        }
7909    }
7910
7911    /// Make an explicit owning copy of this dtype-erased tensor.
7912    ///
7913    /// # Errors
7914    ///
7915    /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
7916    /// when the selected backend/storage owner cannot be duplicated.
7917    pub fn duplicate(&self) -> crate::Result<Self> {
7918        match self {
7919            Tensor::F32(t) => t.duplicate().map(Tensor::F32),
7920            Tensor::F64(t) => t.duplicate().map(Tensor::F64),
7921            Tensor::I32(t) => t.duplicate().map(Tensor::I32),
7922            Tensor::I64(t) => t.duplicate().map(Tensor::I64),
7923            Tensor::Bool(t) => t.duplicate().map(Tensor::Bool),
7924            Tensor::C32(t) => t.duplicate().map(Tensor::C32),
7925            Tensor::C64(t) => t.duplicate().map(Tensor::C64),
7926        }
7927    }
7928
7929    /// Create a tensor from a shape and column-major flat data.
7930    ///
7931    /// This is the `Tensor`-level equivalent of
7932    /// `TypedTensor::<T>::from_vec_col_major`.
7933    ///
7934    /// # Examples
7935    ///
7936    /// ```
7937    /// use tenferro_tensor::Tensor;
7938    ///
7939    /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
7940    /// assert_eq!(t.shape(), &[2, 2]);
7941    /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
7942    /// ```
7943    /// # Errors
7944    ///
7945    /// Returns [`crate::Error::Validation`] with
7946    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
7947    /// the shape product differs from `data.len()`, or
7948    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
7949    /// arithmetic overflows.
7950    pub fn from_vec_col_major<T: TensorScalar>(
7951        shape: impl tenferro_tensor_core::IntoShapeVec,
7952        data: Vec<T>,
7953    ) -> crate::Result<Self> {
7954        T::into_tensor(shape.into_shape_vec().to_vec(), data)
7955    }
7956
7957    /// Tensor shape.
7958    ///
7959    /// # Examples
7960    ///
7961    /// ```rust
7962    /// use tenferro_tensor::{Tensor, TypedTensor};
7963    ///
7964    /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
7965    /// assert_eq!(t.shape(), &[2]);
7966    /// ```
7967    pub fn shape(&self) -> &[usize] {
7968        match self {
7969            Tensor::F32(t) => t.shape(),
7970            Tensor::F64(t) => t.shape(),
7971            Tensor::I32(t) => t.shape(),
7972            Tensor::I64(t) => t.shape(),
7973            Tensor::Bool(t) => t.shape(),
7974            Tensor::C32(t) => t.shape(),
7975            Tensor::C64(t) => t.shape(),
7976        }
7977    }
7978
7979    /// Tensor dtype tag.
7980    ///
7981    /// # Examples
7982    ///
7983    /// ```rust
7984    /// use tenferro_tensor::{DType, Tensor, TypedTensor};
7985    ///
7986    /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
7987    /// assert_eq!(t.dtype(), DType::F64);
7988    /// ```
7989    pub fn dtype(&self) -> DType {
7990        match self {
7991            Tensor::F32(_) => DType::F32,
7992            Tensor::F64(_) => DType::F64,
7993            Tensor::I32(_) => DType::I32,
7994            Tensor::I64(_) => DType::I64,
7995            Tensor::Bool(_) => DType::Bool,
7996            Tensor::C32(_) => DType::C32,
7997            Tensor::C64(_) => DType::C64,
7998        }
7999    }
8000
8001    /// Return placement metadata for this dtype-erased tensor.
8002    ///
8003    /// # Examples
8004    ///
8005    /// ```rust
8006    /// use tenferro_tensor::{MemoryKind, Tensor};
8007    ///
8008    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
8009    /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
8010    /// ```
8011    pub fn placement(&self) -> &Placement {
8012        match self {
8013            Tensor::F32(t) => t.placement(),
8014            Tensor::F64(t) => t.placement(),
8015            Tensor::I32(t) => t.placement(),
8016            Tensor::I64(t) => t.placement(),
8017            Tensor::Bool(t) => t.placement(),
8018            Tensor::C32(t) => t.placement(),
8019            Tensor::C64(t) => t.placement(),
8020        }
8021    }
8022
8023    /// Return whether this tensor is backed by backend-native storage.
8024    ///
8025    /// # Examples
8026    ///
8027    /// ```rust
8028    /// use tenferro_tensor::Tensor;
8029    ///
8030    /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
8031    /// assert!(!t.is_backend_buffer());
8032    /// ```
8033    pub fn is_backend_buffer(&self) -> bool {
8034        match self {
8035            Tensor::F32(t) => t.backend_family().is_some(),
8036            Tensor::F64(t) => t.backend_family().is_some(),
8037            Tensor::I32(t) => t.backend_family().is_some(),
8038            Tensor::I64(t) => t.backend_family().is_some(),
8039            Tensor::Bool(t) => t.backend_family().is_some(),
8040            Tensor::C32(t) => t.backend_family().is_some(),
8041            Tensor::C64(t) => t.backend_family().is_some(),
8042        }
8043    }
8044
8045    /// Compute the physical element offset for a logical index.
8046    ///
8047    /// # Examples
8048    ///
8049    /// ```rust
8050    /// use tenferro_tensor::Tensor;
8051    ///
8052    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8053    /// assert_eq!(t.layout_linear_offset(&[1])?, 1);
8054    /// # Ok::<(), tenferro_tensor::Error>(())
8055    /// ```
8056    /// # Errors
8057    ///
8058    /// Returns [`crate::Error::Validation`] with
8059    /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8060    /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8061    /// when an index is outside its axis extent, or
8062    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8063    /// arithmetic overflows.
8064    pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8065        match self {
8066            Tensor::F32(t) => t.layout_linear_offset(indices),
8067            Tensor::F64(t) => t.layout_linear_offset(indices),
8068            Tensor::I32(t) => t.layout_linear_offset(indices),
8069            Tensor::I64(t) => t.layout_linear_offset(indices),
8070            Tensor::Bool(t) => t.layout_linear_offset(indices),
8071            Tensor::C32(t) => t.layout_linear_offset(indices),
8072            Tensor::C64(t) => t.layout_linear_offset(indices),
8073        }
8074    }
8075
8076    /// Return whether this tensor is compact column-major.
8077    ///
8078    /// # Examples
8079    ///
8080    /// ```rust
8081    /// use tenferro_tensor::Tensor;
8082    ///
8083    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8084    /// assert!(t.is_col_major_contiguous()?);
8085    /// # Ok::<(), tenferro_tensor::Error>(())
8086    /// ```
8087    /// # Errors
8088    ///
8089    /// Returns [`crate::Error::Validation`] with
8090    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8091    /// compactness arithmetic overflows.
8092    pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8093        match self {
8094            Tensor::F32(t) => t.is_col_major_contiguous(),
8095            Tensor::F64(t) => t.is_col_major_contiguous(),
8096            Tensor::I32(t) => t.is_col_major_contiguous(),
8097            Tensor::I64(t) => t.is_col_major_contiguous(),
8098            Tensor::Bool(t) => t.is_col_major_contiguous(),
8099            Tensor::C32(t) => t.is_col_major_contiguous(),
8100            Tensor::C64(t) => t.is_col_major_contiguous(),
8101        }
8102    }
8103
8104    /// Return a compact string summary of this tensor's layout metadata.
8105    ///
8106    /// # Examples
8107    ///
8108    /// ```rust
8109    /// use tenferro_tensor::Tensor;
8110    ///
8111    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8112    /// assert!(t.layout_summary().contains("shape=[2]"));
8113    /// # Ok::<(), tenferro_tensor::Error>(())
8114    /// ```
8115    pub fn layout_summary(&self) -> String {
8116        let layout = tensor_layout(self);
8117        layout_summary(layout.shape(), layout.strides(), layout.offset())
8118    }
8119
8120    /// Assert this tensor is compact column-major.
8121    ///
8122    /// # Examples
8123    ///
8124    /// ```rust
8125    /// use tenferro_tensor::Tensor;
8126    ///
8127    /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8128    /// t.assert_col_major_contiguous()?;
8129    /// # Ok::<(), tenferro_tensor::Error>(())
8130    /// ```
8131    /// # Errors
8132    ///
8133    /// Returns [`crate::Error::Validation`] with
8134    /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8135    /// compactness arithmetic overflows, or
8136    /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8137    /// tensor is not compact column-major.
8138    pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8139        let layout = tensor_layout(self);
8140        assert_layout_col_major_contiguous(
8141            self.is_col_major_contiguous()?,
8142            layout.shape(),
8143            layout.strides(),
8144            layout.offset(),
8145            "Tensor::assert_col_major_contiguous",
8146        )
8147    }
8148
8149    /// Try to borrow the host data as a typed slice.
8150    ///
8151    /// Returns an error if the tensor dtype does not match `T`.
8152    ///
8153    /// # Examples
8154    ///
8155    /// ```
8156    /// use tenferro_tensor::{Tensor, TypedTensor};
8157    ///
8158    /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
8159    /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
8160    /// assert!(t.as_slice::<f32>().is_err());
8161    /// ```
8162    /// # Errors
8163    ///
8164    /// Returns [`crate::Error::Validation`] with
8165    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
8166    /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
8167    /// matching tensor uses backend storage that has not been downloaded.
8168    pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
8169        T::as_slice(self)
8170    }
8171
8172    /// Consume this tensor and return its owned column-major buffer when the
8173    /// dtype matches.
8174    ///
8175    /// # Examples
8176    ///
8177    /// ```
8178    /// use tenferro_tensor::Tensor;
8179    ///
8180    /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
8181    /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
8182    /// ```
8183    /// # Errors
8184    ///
8185    /// Returns [`crate::Error::Validation`] with
8186    /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
8187    /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
8188    /// matching tensor uses backend storage that has not been downloaded.
8189    pub fn into_vec_col_major<T: TensorScalar>(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
8190        let typed = T::into_typed(self)?;
8191        typed.into_vec_col_major()
8192    }
8193}
8194
8195// Kept for crate-local layout tests while tensor indexing helpers remain split
8196// across tensor and CPU crates.
8197#[allow(dead_code)]
8198pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
8199    for i in 0..shape.len() {
8200        if shape[i] == 0 {
8201            out[i] = 0;
8202        } else {
8203            out[i] = flat % shape[i];
8204            flat /= shape[i];
8205        }
8206    }
8207}