tenferro_tensor/types.rs
1use num_complex::{Complex, Complex32, Complex64};
2use num_traits::{One, Zero};
3use std::any::Any;
4use std::fmt::Debug;
5use std::marker::PhantomData;
6use std::mem::{align_of, needs_drop, offset_of, size_of};
7use std::num::NonZeroUsize;
8use std::ops::Deref;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11use crate::config::SliceConfig;
12use crate::error::ReinterpretError;
13pub use tenferro_tensor_core::{DType, DynRank, Rank, TensorLayout, TensorRank};
14use tenferro_tensor_core::{ShapeVec, StrideVec};
15use tenferro_tensor_core::{SliceSpec as CoreSliceSpec, ValidationError};
16use tinyvec::TinyVec;
17
18use crate::storage::{
19 AllocationGroup, AllocationSlot, BackendAllocation, DescriptorSlot, GroupError, GroupReadView,
20 GroupWriteView,
21};
22
23mod accessors;
24mod col_major;
25mod shape_packing;
26mod strided_view;
27#[cfg(test)]
28mod tests;
29
30pub use col_major::{ColMajorView, ColMajorViewMut};
31pub use strided_view::StridedSliceSpec;
32
33pub(crate) fn shape_vec(shape: &[usize]) -> ShapeVec {
34 shape.iter().copied().collect()
35}
36
37pub(crate) fn stride_vec(strides: &[isize]) -> StrideVec {
38 strides.iter().copied().collect()
39}
40
41pub(crate) fn shape_vec_zeros(len: usize) -> ShapeVec {
42 std::iter::repeat_n(0, len).collect()
43}
44
45fn representation_pair_error(
46 op: &'static str,
47 from: DType,
48 to: DType,
49 message: impl Into<String>,
50) -> crate::Error {
51 crate::Error::unsupported_dtype_conversion(op, from, to, message)
52}
53
54fn validate_representation_pair(op: &'static str, from: DType, to: DType) -> crate::Result<()> {
55 let valid = match (from, to) {
56 (DType::C32, DType::F32) | (DType::F32, DType::C32) => {
57 size_of::<Complex32>() == 2 * size_of::<f32>()
58 && align_of::<Complex32>() == align_of::<f32>()
59 && offset_of!(Complex32, re) == 0
60 && offset_of!(Complex32, im) == size_of::<f32>()
61 && !needs_drop::<Complex32>()
62 && !needs_drop::<f32>()
63 }
64 (DType::C64, DType::F64) | (DType::F64, DType::C64) => {
65 size_of::<Complex64>() == 2 * size_of::<f64>()
66 && align_of::<Complex64>() == align_of::<f64>()
67 && offset_of!(Complex64, re) == 0
68 && offset_of!(Complex64, im) == size_of::<f64>()
69 && !needs_drop::<Complex64>()
70 && !needs_drop::<f64>()
71 }
72 _ => false,
73 };
74 if valid {
75 Ok(())
76 } else {
77 Err(representation_pair_error(
78 op,
79 from,
80 to,
81 "only the sealed Complex<f32><->f32 and Complex<f64><->f64 representations are supported",
82 ))
83 }
84}
85
86fn reinterpret_complex_to_real_layout(
87 shape: &[usize],
88 strides: &[isize],
89 offset: isize,
90 complex_buffer_len: usize,
91 op: &'static str,
92) -> crate::Result<TensorLayout<DynRank>> {
93 let real_buffer_len = complex_buffer_len
94 .checked_mul(2)
95 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
96 let rank = shape
97 .len()
98 .checked_add(1)
99 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
100 let mut real_shape = ShapeVec::with_capacity(rank);
101 real_shape.push(2);
102 real_shape.extend_from_slice(shape);
103 let mut real_strides = StrideVec::with_capacity(rank);
104 real_strides.push(1);
105 for &stride in strides {
106 real_strides.push(
107 stride
108 .checked_mul(2)
109 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
110 );
111 }
112 let real_offset = offset
113 .checked_mul(2)
114 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
115 TensorLayout::from_parts(real_shape, real_strides, real_offset, real_buffer_len)
116 .map_err(|err| tensor_layout_error(op, err))
117}
118
119fn reinterpret_real_to_complex_layout(
120 shape: &[usize],
121 strides: &[isize],
122 offset: isize,
123 real_buffer_len: usize,
124 op: &'static str,
125) -> crate::Result<TensorLayout<DynRank>> {
126 if shape.first().copied() != Some(2) {
127 return Err(crate::Error::invalid_argument(
128 op,
129 "shape",
130 "the leading extent must be 2 for a complex reinterpretation",
131 ));
132 }
133 if strides.first().copied() != Some(1) {
134 return Err(crate::Error::invalid_argument(
135 op,
136 "strides",
137 "the leading stride must be 1 for a complex reinterpretation",
138 ));
139 }
140 if offset % 2 != 0 {
141 return Err(crate::Error::invalid_argument(
142 op,
143 "offset",
144 "the offset must be divisible by 2 for a complex reinterpretation",
145 ));
146 }
147 let mut complex_strides = StrideVec::with_capacity(strides.len() - 1);
148 for &stride in &strides[1..] {
149 if stride % 2 != 0 {
150 return Err(crate::Error::invalid_argument(
151 op,
152 "strides",
153 "all non-leading strides must be divisible by 2",
154 ));
155 }
156 complex_strides.push(stride / 2);
157 }
158 let complex_buffer_len = real_buffer_len / 2;
159 TensorLayout::from_parts(
160 shape[1..].iter().copied().collect(),
161 complex_strides,
162 offset / 2,
163 complex_buffer_len,
164 )
165 .map_err(|err| tensor_layout_error(op, err))
166}
167
168fn reinterpret_host_slice<'a, T: TensorScalar, U: TensorScalar>(
169 data: &'a [T],
170 op: &'static str,
171) -> crate::Result<&'a [U]> {
172 let byte_len = data
173 .len()
174 .checked_mul(size_of::<T>())
175 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
176 if !byte_len.is_multiple_of(size_of::<U>()) {
177 return Err(crate::Error::validation(
178 op,
179 ValidationError::ViewOutOfBounds,
180 ));
181 }
182 if data.as_ptr().align_offset(align_of::<U>()) != 0 {
183 return Err(crate::Error::invalid_argument(
184 op,
185 "alignment",
186 "the source allocation is not aligned for the target representation",
187 ));
188 }
189 // SAFETY: `validate_representation_pair` seals the only supported pairs;
190 // sizes, alignment, field order, and drop properties are checked before
191 // exposing the borrowed target slice.
192 Ok(unsafe { std::slice::from_raw_parts(data.as_ptr().cast::<U>(), byte_len / size_of::<U>()) })
193}
194
195fn reinterpret_host_slice_mut<'a, T: TensorScalar, U: TensorScalar>(
196 data: &'a mut [T],
197 op: &'static str,
198) -> crate::Result<&'a mut [U]> {
199 let byte_len = data
200 .len()
201 .checked_mul(size_of::<T>())
202 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
203 if !byte_len.is_multiple_of(size_of::<U>()) {
204 return Err(crate::Error::validation(
205 op,
206 ValidationError::ViewOutOfBounds,
207 ));
208 }
209 if data.as_mut_ptr().align_offset(align_of::<U>()) != 0 {
210 return Err(crate::Error::invalid_argument(
211 op,
212 "alignment",
213 "the source allocation is not aligned for the target representation",
214 ));
215 }
216 // SAFETY: the mutable source borrow is unique and the sealed pair has no
217 // padding or drop glue, so the target slice covers the same bytes exactly.
218 Ok(unsafe {
219 std::slice::from_raw_parts_mut(data.as_mut_ptr().cast::<U>(), byte_len / size_of::<U>())
220 })
221}
222
223/// Memory location for tensor storage.
224///
225/// # Examples
226///
227/// ```rust
228/// use tenferro_tensor::MemoryKind;
229///
230/// let kind = MemoryKind::UnpinnedHost;
231/// ```
232#[derive(Clone, Debug, PartialEq, Eq, Hash)]
233pub enum MemoryKind {
234 Device,
235 PinnedHost,
236 UnpinnedHost,
237 Managed,
238 Other(String),
239}
240
241/// Compute device family.
242///
243/// # Examples
244///
245/// ```rust
246/// use tenferro_tensor::DeviceKind;
247///
248/// let kind = DeviceKind::Cpu;
249/// ```
250#[derive(Clone, Debug, PartialEq, Eq, Hash)]
251pub enum DeviceKind {
252 Cpu,
253 Gpu(GpuBackendKind),
254 Other(String),
255}
256
257/// GPU backend family used by placement metadata.
258///
259/// # Examples
260///
261/// ```rust
262/// use tenferro_tensor::GpuBackendKind;
263///
264/// let kind = GpuBackendKind::Cuda;
265/// let webgpu = GpuBackendKind::WebGpu;
266/// assert_ne!(kind, webgpu);
267/// ```
268#[derive(Clone, Debug, PartialEq, Eq, Hash)]
269pub enum GpuBackendKind {
270 Cuda,
271 WebGpu,
272 Rocm,
273 Other(String),
274}
275
276/// Concrete compute device identifier.
277///
278/// # Examples
279///
280/// ```rust
281/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind};
282///
283/// let device = DeviceId {
284/// kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
285/// ordinal: 0,
286/// };
287/// ```
288#[derive(Clone, Debug, PartialEq, Eq, Hash)]
289pub struct DeviceId {
290 pub kind: DeviceKind,
291 pub ordinal: usize,
292}
293
294/// Caller-stable identity for a CPU execution domain.
295///
296/// Domain IDs are metadata supplied by the caller or execution coordinator;
297/// creating an ID does not allocate a process-global identity.
298///
299/// # Examples
300///
301/// ```rust
302/// use tenferro_tensor::CpuDomainId;
303///
304/// let domain = CpuDomainId::new(17);
305/// assert_eq!(domain.as_u64(), 17);
306/// ```
307#[repr(transparent)]
308#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
309pub struct CpuDomainId(u64);
310
311impl CpuDomainId {
312 /// Create a caller-stable CPU domain identity.
313 ///
314 /// # Examples
315 ///
316 /// ```rust
317 /// use tenferro_tensor::CpuDomainId;
318 ///
319 /// assert_eq!(CpuDomainId::new(3), CpuDomainId::new(3));
320 /// ```
321 pub const fn new(id: u64) -> Self {
322 Self(id)
323 }
324
325 /// Return the caller-supplied integer identity.
326 ///
327 /// # Examples
328 ///
329 /// ```rust
330 /// use tenferro_tensor::CpuDomainId;
331 ///
332 /// assert_eq!(CpuDomainId::new(9).as_u64(), 9);
333 /// ```
334 pub const fn as_u64(self) -> u64 {
335 self.0
336 }
337}
338
339/// Placement metadata for a tensor buffer.
340///
341/// # Examples
342///
343/// ```rust
344/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind, MemoryKind, Placement};
345///
346/// let placement = Placement {
347/// memory_kind: MemoryKind::Device,
348/// device: Some(DeviceId {
349/// kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
350/// ordinal: 0,
351/// }),
352/// cpu_affinity: None,
353/// };
354/// assert!(placement.cpu_affinity.is_none());
355/// ```
356#[derive(Clone, Debug, PartialEq, Eq, Hash)]
357pub struct Placement {
358 /// Storage memory class, independent of execution routing metadata.
359 pub memory_kind: MemoryKind,
360 /// Device that owns or addresses the storage, when applicable.
361 pub device: Option<DeviceId>,
362 /// Preferred or producing CPU execution domain for routing and locality.
363 ///
364 /// This tag is not proof of allocation ownership, page residency, NUMA
365 /// pinning, or worker-affinity enforcement. Backend allocation-domain
366 /// metadata remains attached to the buffer independently.
367 pub cpu_affinity: Option<CpuDomainId>,
368}
369
370impl Default for Placement {
371 fn default() -> Self {
372 default_placement()
373 }
374}
375
376/// Backend-owned buffer handle.
377///
378/// `BackendStorageHandle::new` creates an empty opaque handle. Use
379/// [`BackendStorageHandle::new_with_len`] when test or adapter code needs to model a
380/// non-empty backend allocation.
381///
382/// # Examples
383///
384/// ```rust
385/// use tenferro_tensor::BackendStorageHandle;
386///
387/// let handle = BackendStorageHandle::<f64>::new(7);
388/// ```
389pub struct BackendStorageHandle<T> {
390 id: u64,
391 len: usize,
392 allocation_domain: AllocationDomainId,
393 _phantom: std::marker::PhantomData<T>,
394}
395
396/// Identity of a backend-owned allocation domain.
397///
398/// Domains let cooperating backends accept shared allocations without treating
399/// another context's physically similar buffer as compatible.
400///
401/// # Examples
402///
403/// ```rust
404/// use tenferro_tensor::AllocationDomainId;
405///
406/// assert_ne!(AllocationDomainId::fresh(), AllocationDomainId::fresh());
407/// ```
408#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
409pub struct AllocationDomainId(u64);
410
411impl AllocationDomainId {
412 /// Create a process-unique allocation-domain identity.
413 ///
414 /// # Examples
415 ///
416 /// ```rust
417 /// use tenferro_tensor::AllocationDomainId;
418 ///
419 /// let domain = AllocationDomainId::fresh();
420 /// assert_eq!(domain, domain);
421 /// ```
422 pub fn fresh() -> Self {
423 static NEXT_DOMAIN_ID: AtomicU64 = AtomicU64::new(1);
424 Self(NEXT_DOMAIN_ID.fetch_add(1, Ordering::Relaxed))
425 }
426}
427
428/// Stable physical identity of one backend allocation.
429///
430/// # Examples
431///
432/// ```rust
433/// use tenferro_tensor::AllocationId;
434///
435/// assert_eq!(AllocationId::from_backend_id(7), AllocationId::from_backend_id(7));
436/// ```
437#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
438pub struct AllocationId(u64);
439
440impl AllocationId {
441 /// Wrap an allocation identity supplied by the owning backend.
442 ///
443 /// # Examples
444 ///
445 /// ```rust
446 /// use tenferro_tensor::AllocationId;
447 ///
448 /// let id = AllocationId::from_backend_id(3);
449 /// assert_eq!(id, AllocationId::from_backend_id(3));
450 /// ```
451 pub const fn from_backend_id(id: u64) -> Self {
452 Self(id)
453 }
454}
455
456/// Typed failure returned by guarded backend host access.
457///
458/// # Examples
459///
460/// ```rust
461/// use tenferro_tensor::HostAccessError;
462///
463/// let error = HostAccessError::Unsupported { backend: "opaque" };
464/// assert!(error.to_string().contains("opaque"));
465/// ```
466#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
467#[non_exhaustive]
468pub enum HostAccessError {
469 /// The backend does not expose guarded host access for this allocation.
470 #[error("backend `{backend}` does not support guarded host access")]
471 Unsupported { backend: &'static str },
472 /// The allocation belongs to another shared-allocation domain.
473 #[error("allocation belongs to domain {actual:?}, expected {expected:?}")]
474 ForeignDomain {
475 expected: AllocationDomainId,
476 actual: AllocationDomainId,
477 },
478 /// Another host mapping overlaps this allocation.
479 #[error("the allocation already has an active host mapping")]
480 OverlappingHostMapping,
481 /// GPU work currently owns or has reserved the allocation.
482 #[error("GPU access is in progress for the allocation")]
483 GpuAccessInProgress,
484 /// Host mapping is active while GPU access was requested.
485 #[error("the allocation is mapped for host access")]
486 MappedForHost,
487 /// The backend failed to complete the map operation.
488 #[error("backend host mapping failed: {message}")]
489 BackendFailure { message: String },
490 /// The source did not cover the full write-only mapping.
491 #[error("host write length mismatch: expected {expected}, got {actual}")]
492 LengthMismatch { expected: usize, actual: usize },
493}
494
495/// Metadata sealed at the tensor/root boundary before a provider launch.
496///
497/// Providers receive this request exactly once for a prepared access. Binding
498/// code consumes the resulting opaque state and does not receive replacement
499/// storage, ranges, or raw pointers.
500#[doc(hidden)]
501#[derive(Clone, Copy, Debug)]
502pub struct DeviceAccessRequest<'a> {
503 allocation_domain: AllocationDomainId,
504 allocation_id: AllocationId,
505 byte_len: usize,
506 element_size: usize,
507 shape: &'a [usize],
508 strides: &'a [isize],
509 offset: isize,
510}
511
512impl<'a> DeviceAccessRequest<'a> {
513 pub(crate) fn new(
514 allocation_domain: AllocationDomainId,
515 allocation_id: AllocationId,
516 byte_len: usize,
517 element_size: usize,
518 shape: &'a [usize],
519 strides: &'a [isize],
520 offset: isize,
521 ) -> Self {
522 Self {
523 allocation_domain,
524 allocation_id,
525 byte_len,
526 element_size,
527 shape,
528 strides,
529 offset,
530 }
531 }
532
533 pub fn allocation_domain(&self) -> AllocationDomainId {
534 self.allocation_domain
535 }
536
537 pub fn allocation_id(&self) -> AllocationId {
538 self.allocation_id
539 }
540
541 pub fn byte_len(&self) -> usize {
542 self.byte_len
543 }
544
545 pub fn element_size(&self) -> usize {
546 self.element_size
547 }
548
549 pub fn shape(&self) -> &[usize] {
550 self.shape
551 }
552
553 pub fn strides(&self) -> &[isize] {
554 self.strides
555 }
556
557 pub fn offset(&self) -> isize {
558 self.offset
559 }
560}
561
562/// Typed failure returned while preparing a provider-native device access.
563#[doc(hidden)]
564#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
565pub enum DeviceAccessError {
566 #[error("backend `{backend}` does not support prepared device access")]
567 Unsupported { backend: &'static str },
568 #[error("prepared device access request is invalid: {message}")]
569 InvalidRequest { message: String },
570 #[error("provider device preparation failed: {message}")]
571 ProviderFailure { message: String },
572}
573
574/// Opaque provider-prepared state retained for one device binding.
575#[doc(hidden)]
576pub trait PreparedDeviceAccess: Debug {
577 fn as_any(&self) -> &dyn Any;
578
579 fn into_any(self: Box<Self>) -> Box<dyn Any>;
580}
581
582trait ReadGuardAccess<T> {
583 fn as_slice(&self) -> &[T];
584}
585
586impl<T, G> ReadGuardAccess<T> for G
587where
588 G: Deref,
589 G::Target: AsRef<[T]>,
590{
591 fn as_slice(&self) -> &[T] {
592 self.deref().as_ref()
593 }
594}
595
596/// Closure-scoped read mapping of a backend allocation.
597///
598/// # Examples
599///
600/// ```rust
601/// use tenferro_tensor::HostReadGuard;
602///
603/// let guard = HostReadGuard::new(vec![1_u32, 2]);
604/// assert_eq!(&*guard, &[1, 2]);
605/// ```
606pub struct HostReadGuard<'a, T> {
607 access: Box<dyn ReadGuardAccess<T> + 'a>,
608}
609
610impl<T> Debug for HostReadGuard<'_, T> {
611 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
612 formatter
613 .debug_struct("HostReadGuard")
614 .field("len", &self.len())
615 .finish_non_exhaustive()
616 }
617}
618
619impl<'a, T> HostReadGuard<'a, T> {
620 /// Wrap a backend-native read guard without exposing its concrete type.
621 ///
622 /// # Examples
623 ///
624 /// ```rust
625 /// use tenferro_tensor::HostReadGuard;
626 ///
627 /// let guard = HostReadGuard::new(vec![3_i32]);
628 /// assert_eq!(guard[0], 3);
629 /// ```
630 pub fn new<G>(guard: G) -> Self
631 where
632 G: Deref + 'a,
633 G::Target: AsRef<[T]>,
634 T: 'a,
635 {
636 Self {
637 access: Box::new(guard),
638 }
639 }
640
641 /// Borrow the mapped elements as a rank-1 host view.
642 ///
643 /// The view lends the mapping's shared borrow, so the guard stays alive for
644 /// as long as the view is used and no copy or transfer happens.
645 ///
646 /// # Examples
647 ///
648 /// ```rust
649 /// use tenferro_tensor::HostReadGuard;
650 ///
651 /// let guard = HostReadGuard::new(vec![3_i32, 4]);
652 /// let view = guard.as_view()?;
653 /// assert_eq!(view.shape(), &[2]);
654 /// assert_eq!(view.get(&[1]), Some(&4));
655 /// # Ok::<(), tenferro_tensor::Error>(())
656 /// ```
657 ///
658 /// # Errors
659 ///
660 /// Returns [`crate::Error::Validation`] with
661 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] or
662 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
663 /// mapped length cannot be represented as a rank-1 layout.
664 pub fn as_view(&self) -> crate::Result<TypedTensorView<'_, T, DynRank>>
665 where
666 T: 'static,
667 {
668 let data: &[T] = self;
669 TypedTensorView::from_slice(vec![data.len()], vec![1], 0, data)
670 }
671}
672
673impl<T> Deref for HostReadGuard<'_, T> {
674 type Target = [T];
675
676 fn deref(&self) -> &Self::Target {
677 self.access.as_slice()
678 }
679}
680
681/// Backend-neutral owner of one shared tensor allocation domain.
682///
683/// CPU operation crates use this object-safe boundary to allocate results in
684/// the same managed domain without depending on a GPU provider crate.
685///
686/// # Examples
687///
688/// ```rust
689/// use std::sync::Arc;
690/// use tenferro_tensor::SharedTensorAllocationDomain;
691///
692/// let _domain: Option<Arc<dyn SharedTensorAllocationDomain>> = None;
693/// ```
694pub trait SharedTensorAllocationDomain: Debug + Send + Sync + 'static {
695 /// Return the stable identity shared by every allocation from this owner.
696 fn id(&self) -> AllocationDomainId;
697
698 /// Allocate an uninitialized compact column-major tensor in this domain.
699 ///
700 /// # Errors
701 ///
702 /// Returns a typed validation, unsupported-dtype, or backend allocation error.
703 fn allocate(&self, dtype: DType, shape: &[usize]) -> crate::Result<Tensor>;
704}
705
706type HostWriteCopy<'a, T> = dyn FnMut(&[T]) -> Result<(), HostAccessError> + 'a;
707
708/// Closure-scoped write-only mapping of a backend allocation.
709///
710/// # Examples
711///
712/// ```rust
713/// use tenferro_tensor::{HostAccessError, HostWriteGuard};
714///
715/// let mut written = Vec::new();
716/// {
717/// let mut guard = HostWriteGuard::new(2, |source: &[u32]| {
718/// written.extend_from_slice(source);
719/// Ok::<(), HostAccessError>(())
720/// });
721/// guard.copy_from_slice(&[4, 5]).unwrap();
722/// }
723/// assert_eq!(written, [4, 5]);
724/// ```
725pub struct HostWriteGuard<'a, T> {
726 len: usize,
727 copy: Box<HostWriteCopy<'a, T>>,
728}
729
730impl<T> Debug for HostWriteGuard<'_, T> {
731 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
732 formatter
733 .debug_struct("HostWriteGuard")
734 .field("len", &self.len)
735 .finish_non_exhaustive()
736 }
737}
738
739impl<'a, T> HostWriteGuard<'a, T> {
740 /// Wrap a backend-native write guard without exposing its concrete type.
741 ///
742 /// # Examples
743 ///
744 /// ```rust
745 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
746 ///
747 /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
748 /// assert!(guard.is_empty());
749 /// ```
750 ///
751 /// # Errors
752 ///
753 /// Construction is infallible. A callback failure such as
754 /// [`HostAccessError::BackendFailure`] is returned later by
755 /// [`Self::copy_from_slice`].
756 pub fn new<F>(len: usize, copy: F) -> Self
757 where
758 F: FnMut(&[T]) -> Result<(), HostAccessError> + 'a,
759 T: 'a,
760 {
761 Self {
762 len,
763 copy: Box::new(copy),
764 }
765 }
766
767 /// Number of elements covered by this write-only mapping.
768 ///
769 /// # Examples
770 ///
771 /// ```rust
772 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
773 ///
774 /// let guard = HostWriteGuard::new(2, |_source: &[f32]| Ok::<(), HostAccessError>(()));
775 /// assert_eq!(guard.len(), 2);
776 /// ```
777 pub fn len(&self) -> usize {
778 self.len
779 }
780
781 /// Returns `true` when this mapping covers no elements.
782 ///
783 /// # Examples
784 ///
785 /// ```rust
786 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
787 ///
788 /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
789 /// assert!(guard.is_empty());
790 /// ```
791 pub fn is_empty(&self) -> bool {
792 self.len == 0
793 }
794
795 /// Replace the full mapped allocation contents.
796 ///
797 /// # Examples
798 ///
799 /// ```rust
800 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
801 ///
802 /// let mut guard = HostWriteGuard::new(1, |_source: &[f32]| Ok::<(), HostAccessError>(()));
803 /// guard.copy_from_slice(&[1.0]).unwrap();
804 /// ```
805 ///
806 /// # Errors
807 ///
808 /// Returns [`HostAccessError::LengthMismatch`] when `source` does not cover
809 /// the complete mapping, or the typed backend error returned by the owning
810 /// write guard.
811 pub fn copy_from_slice(&mut self, source: &[T]) -> Result<(), HostAccessError> {
812 if source.len() != self.len {
813 return Err(HostAccessError::LengthMismatch {
814 expected: self.len,
815 actual: source.len(),
816 });
817 }
818 (self.copy)(source)
819 }
820}
821
822impl<T> Debug for BackendStorageHandle<T> {
823 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
824 f.debug_struct("BackendStorageHandle")
825 .field("id", &self.id)
826 .finish()
827 }
828}
829
830impl<T> BackendStorageHandle<T> {
831 /// Create a new backend buffer handle.
832 ///
833 /// # Examples
834 ///
835 /// ```rust
836 /// use tenferro_tensor::BackendStorageHandle;
837 ///
838 /// let handle = BackendStorageHandle::<f64>::new(1);
839 /// assert_eq!(tenferro_tensor::BackendStorage::len(&handle), 0);
840 /// ```
841 pub fn new(id: u64) -> Self {
842 Self::new_with_len(id, 0)
843 }
844
845 /// Create a new backend buffer handle with a logical element count.
846 ///
847 /// # Examples
848 ///
849 /// ```rust
850 /// use tenferro_tensor::{BackendStorage, BackendStorageHandle};
851 ///
852 /// let handle = BackendStorageHandle::<f64>::new_with_len(1, 4);
853 /// assert_eq!(BackendStorage::len(&handle), 4);
854 /// ```
855 pub fn new_with_len(id: u64, len: usize) -> Self {
856 Self {
857 id,
858 len,
859 // Synthetic opaque handles are test/adapter allocations. Give
860 // each one an explicit domain so root import never fabricates
861 // identity from a missing provider field.
862 allocation_domain: AllocationDomainId::fresh(),
863 _phantom: std::marker::PhantomData,
864 }
865 }
866}
867
868/// Opaque backend-owned tensor buffer.
869///
870/// Tensor core never inspects backend-native allocations directly. Backend
871/// crates store their own concrete handle types behind this trait and
872/// downcast inside the owning backend only.
873///
874/// # Examples
875///
876/// ```rust
877/// use std::sync::Arc;
878/// use tenferro_tensor::{BackendStorage, BackendStorageHandle};
879///
880/// let buffer: Arc<dyn BackendStorage<f64>> = Arc::new(BackendStorageHandle::<f64>::new_with_len(7, 2));
881/// assert_eq!(buffer.backend_family(), "opaque");
882/// assert_eq!(buffer.len(), 2);
883/// ```
884pub trait BackendStorage<T>: Debug + Send + Sync + 'static {
885 /// Stable backend family identifier.
886 fn backend_family(&self) -> &'static str;
887
888 /// Number of logical elements in the backend allocation.
889 fn len(&self) -> usize;
890
891 /// Returns `true` when the backend allocation is empty.
892 fn is_empty(&self) -> bool {
893 self.len() == 0
894 }
895
896 /// Return the shared-allocation domain, when this buffer belongs to one.
897 fn allocation_domain(&self) -> Option<AllocationDomainId> {
898 None
899 }
900
901 /// Return the stable physical allocation identity, when available.
902 fn allocation_id(&self) -> Option<AllocationId> {
903 None
904 }
905
906 /// Prepare one provider-native device access from the root-owned buffer.
907 ///
908 /// The returned state is consumed by the provider binding path. Providers
909 /// that do not expose device launches return [`DeviceAccessError::Unsupported`].
910 #[doc(hidden)]
911 fn prepare_device_access(
912 &self,
913 _request: DeviceAccessRequest<'_>,
914 ) -> Result<Box<dyn PreparedDeviceAccess>, DeviceAccessError> {
915 Err(DeviceAccessError::Unsupported {
916 backend: self.backend_family(),
917 })
918 }
919
920 /// Map the allocation for closure-scoped host reads.
921 ///
922 /// # Errors
923 ///
924 /// The default returns [`HostAccessError::Unsupported`]. Host-visible
925 /// backends return typed overlap, pending-GPU, or backend mapping failures.
926 fn map_read(&self) -> Result<HostReadGuard<'_, T>, HostAccessError> {
927 Err(HostAccessError::Unsupported {
928 backend: self.backend_family(),
929 })
930 }
931
932 /// Map the allocation for closure-scoped host writes.
933 ///
934 /// The mutable receiver keeps write authority with the owning tensor or
935 /// provider object; borrowed views do not clone or share that authority.
936 ///
937 /// # Errors
938 ///
939 /// The default returns [`HostAccessError::Unsupported`]. Host-visible
940 /// backends return typed overlap, pending-GPU, or backend mapping failures.
941 fn map_write(&mut self) -> Result<HostWriteGuard<'_, T>, HostAccessError> {
942 Err(HostAccessError::Unsupported {
943 backend: self.backend_family(),
944 })
945 }
946
947 /// Type-erased access for the backend crate that owns the concrete handle.
948 fn as_any(&self) -> &dyn Any;
949}
950
951impl<T: Send + Sync + 'static> BackendStorage<T> for BackendStorageHandle<T> {
952 fn backend_family(&self) -> &'static str {
953 "opaque"
954 }
955
956 fn len(&self) -> usize {
957 self.len
958 }
959
960 fn allocation_domain(&self) -> Option<AllocationDomainId> {
961 Some(self.allocation_domain)
962 }
963
964 fn allocation_id(&self) -> Option<AllocationId> {
965 Some(AllocationId::from_backend_id(self.id))
966 }
967
968 fn as_any(&self) -> &dyn Any {
969 self
970 }
971}
972
973/// Tensor storage.
974///
975/// # Examples
976///
977/// ```rust
978/// use tenferro_tensor::StorageBuffer;
979///
980/// let host = StorageBuffer::Host(vec![1.0_f64, 2.0]);
981/// ```
982#[derive(Debug)]
983pub enum StorageBuffer<T> {
984 Host(Vec<T>),
985 Backend(Box<dyn BackendStorage<T>>),
986}
987
988impl<T: 'static> StorageBuffer<T> {
989 /// Return the physical element count in this buffer.
990 ///
991 /// # Examples
992 ///
993 /// ```rust
994 /// use tenferro_tensor::StorageBuffer;
995 ///
996 /// assert_eq!(StorageBuffer::Host(vec![1_i32, 2]).len(), 2);
997 /// ```
998 pub fn len(&self) -> usize {
999 match self {
1000 Self::Host(data) => data.len(),
1001 Self::Backend(buffer) => buffer.len(),
1002 }
1003 }
1004
1005 /// Return whether this buffer has no physical elements.
1006 ///
1007 /// # Examples
1008 ///
1009 /// ```rust
1010 /// use tenferro_tensor::StorageBuffer;
1011 ///
1012 /// assert!(StorageBuffer::<i32>::Host(Vec::new()).is_empty());
1013 /// ```
1014 pub fn is_empty(&self) -> bool {
1015 self.len() == 0
1016 }
1017
1018 /// Return whether the storage is backend-owned rather than host-owned.
1019 ///
1020 /// # Examples
1021 ///
1022 /// ```rust
1023 /// use tenferro_tensor::StorageBuffer;
1024 ///
1025 /// assert!(!StorageBuffer::Host(vec![1_i32]).is_backend());
1026 /// ```
1027 pub fn is_backend(&self) -> bool {
1028 matches!(self, Self::Backend(_))
1029 }
1030}
1031
1032/// Sealed marker for the plain and pooled host payload of a [`TypedTensor`].
1033///
1034/// `TypedTensor<T, R, Host>` owns host elements directly, so it offers
1035/// infallible host access (`as_slice`, `get`, `get_mut`, `Index`/`IndexMut`)
1036/// and [`Clone`] without a runtime device check. `T` needs no `Copy`,
1037/// [`TensorScalar`] or arithmetic bound for that.
1038///
1039/// # Examples
1040///
1041/// ```
1042/// use tenferro_tensor::{DynRank, Host, TypedTensor};
1043///
1044/// let tensor: TypedTensor<f64, DynRank, Host> =
1045/// TypedTensor::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1046/// assert_eq!(tensor.as_slice(), &[1.0, 2.0]);
1047/// assert_eq!(tensor[&[1]], 2.0);
1048/// # Ok::<(), tenferro_tensor::Error>(())
1049/// ```
1050#[derive(Debug)]
1051pub struct Host;
1052
1053/// Sealed marker for the group-backed payload of a [`TypedTensor`].
1054///
1055/// `TypedTensor<T, R, Gpu>` carries provider allocation authority, identity
1056/// and retirement state through its allocation group, so host access stays
1057/// fallible and must be prepared or mapped explicitly. The marker is not proof
1058/// of a particular device ordinal or provider.
1059///
1060/// # Examples
1061///
1062/// ```
1063/// use tenferro_tensor::{DynRank, Gpu, Host, TypedTensor};
1064///
1065/// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1066/// let gpu: TypedTensor<f64, DynRank, Gpu> = host.promote()?;
1067/// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
1068/// # Ok::<(), tenferro_tensor::Error>(())
1069/// ```
1070#[derive(Debug)]
1071pub struct Gpu;
1072
1073/// Sealed marker for the runtime union of the host and group-backed payloads.
1074///
1075/// This is the default representation: one type for ordinary tensors that may
1076/// be host-resident or backend-resident at runtime.
1077///
1078/// # Examples
1079///
1080/// ```
1081/// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1082///
1083/// let host = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1084/// let dynamic: TypedTensor<i32, DynRank, Dynamic> = host.into_dynamic();
1085/// assert_eq!(dynamic.host_data()?, &[1, 2]);
1086/// # Ok::<(), tenferro_tensor::Error>(())
1087/// ```
1088#[derive(Debug)]
1089pub struct Dynamic;
1090
1091mod representation_sealed {
1092 pub trait Sealed {}
1093 impl Sealed for super::Host {}
1094 impl Sealed for super::Gpu {}
1095 impl Sealed for super::Dynamic {}
1096}
1097
1098/// Sealed selector for the owned payload representation of a [`TypedTensor`].
1099///
1100/// The three markers are [`Host`], [`Gpu`] and [`Dynamic`]. The trait is
1101/// sealed: downstream crates select an existing representation, they do not
1102/// define one.
1103///
1104/// # Examples
1105///
1106/// ```
1107/// use tenferro_tensor::{Dynamic, DynRank, Host, Representation, TypedTensor};
1108///
1109/// fn extent<D: Representation>(tensor: &TypedTensor<f64, DynRank, D>) -> usize {
1110/// tensor.shape()[0]
1111/// }
1112/// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![3], vec![0.0; 3])?;
1113/// assert_eq!(extent(&host), 3);
1114/// let dynamic: TypedTensor<f64, DynRank, Dynamic> = host.into_dynamic();
1115/// assert_eq!(extent(&dynamic), 3);
1116/// # Ok::<(), tenferro_tensor::Error>(())
1117/// ```
1118pub trait Representation: representation_sealed::Sealed + 'static {
1119 /// Owned payload stored for this representation.
1120 #[doc(hidden)]
1121 type Storage<T, R: TensorRank>;
1122}
1123
1124impl Representation for Host {
1125 #[doc(hidden)]
1126 type Storage<T, R: TensorRank> = HostStorage<T>;
1127}
1128
1129impl Representation for Gpu {
1130 #[doc(hidden)]
1131 type Storage<T, R: TensorRank> = GroupStorage<R>;
1132}
1133
1134impl Representation for Dynamic {
1135 #[doc(hidden)]
1136 type Storage<T, R: TensorRank> = DynamicStorage<T, R>;
1137}
1138
1139/// Directly owned plain or pooled host elements.
1140#[doc(hidden)]
1141pub struct HostStorage<T> {
1142 data: HostData<T>,
1143}
1144
1145/// Group-backed payload: provider allocation authority, identity and retirement.
1146#[doc(hidden)]
1147pub struct GroupStorage<R: TensorRank> {
1148 group: Box<OwnedTensorGroup<R>>,
1149}
1150
1151/// Runtime union of the [`Host`] and [`Gpu`] owned payloads.
1152#[doc(hidden)]
1153pub enum DynamicStorage<T, R: TensorRank> {
1154 Host(HostStorage<T>),
1155 Group(GroupStorage<R>),
1156}
1157
1158impl<T> std::fmt::Debug for HostStorage<T> {
1159 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1160 self.data.fmt(formatter)
1161 }
1162}
1163
1164impl<R: TensorRank> std::fmt::Debug for GroupStorage<R> {
1165 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1166 formatter
1167 .debug_struct("GroupStorage")
1168 .field("group", &self.group)
1169 .finish()
1170 }
1171}
1172
1173impl<T, R: TensorRank> std::fmt::Debug for DynamicStorage<T, R> {
1174 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1175 match self {
1176 Self::Host(host) => formatter.debug_tuple("Host").field(host).finish(),
1177 Self::Group(group) => formatter.debug_tuple("Group").field(group).finish(),
1178 }
1179 }
1180}
1181
1182/// Owned compact column-major typed tensor.
1183///
1184/// `T` is the element type, `R` the rank metadata (default [`DynRank`]) and `D`
1185/// the owned representation (default [`Dynamic`]). Shape and placement live on
1186/// the tensor itself, once, never inside a storage arm.
1187///
1188/// No bound is imposed on `T` by the type: plain host adoption only needs the
1189/// constructor's own requirements, and `T: Clone` is required only by the
1190/// copying constructors. A preset [`TensorScalar`] appears only where a dtype
1191/// identity or a numerical capability is actually used, never for plain host
1192/// ownership.
1193///
1194/// Owned tensors are compact column-major. Arbitrary strides and metadata-only
1195/// layout changes are represented by [`TypedTensorView`] and
1196/// [`TypedTensorViewMut`].
1197///
1198/// # Examples
1199///
1200/// ```
1201/// use tenferro_tensor::{DynRank, Host, Rank, Tensor, TypedTensor};
1202///
1203/// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
1204/// assert_eq!(t.shape(), &[2, 2]);
1205///
1206/// let static_rank = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
1207/// assert_eq!(static_rank.rank(), 2);
1208///
1209/// let host: TypedTensor<i32, DynRank, Host> =
1210/// TypedTensor::from_host_vec_col_major(vec![2], vec![1, 2]).unwrap();
1211/// assert_eq!(host.get(&[1])?, &2);
1212///
1213/// let dynamic = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64; 4]).unwrap();
1214/// assert_eq!(dynamic.shape(), &[2, 2]);
1215/// # Ok::<(), tenferro_tensor::Error>(())
1216/// ```
1217pub struct TypedTensor<T, R: TensorRank = DynRank, D: Representation = Dynamic> {
1218 shape: R::Shape,
1219 placement: Placement,
1220 /// Column-major element strides, cached at construction so a strided view of
1221 /// this tensor borrows its layout instead of recomputing it per operation.
1222 layout: StrideVec,
1223 storage: D::Storage<T, R>,
1224}
1225
1226/// Validate and adopt a column-major host vector as a statically host-owned tensor.
1227fn typed_host_tensor_from_vec_col_major<T, R: TensorRank>(
1228 shape: impl tenferro_tensor_core::IntoRankShape<R>,
1229 data: Vec<T>,
1230 op: &'static str,
1231) -> crate::Result<TypedTensor<T, R, Host>> {
1232 let shape = shape
1233 .into_rank_shape()
1234 .map_err(|err| tensor_layout_error(op, err))?;
1235 let layout = tenferro_tensor_core::col_major_strides(shape.as_ref())
1236 .map_err(|err| tensor_layout_error(op, err))?;
1237 try_checked_shape_len(shape.as_ref(), data.len(), op)?;
1238 Ok(TypedTensor {
1239 shape,
1240 placement: default_placement(),
1241 layout,
1242 storage: HostStorage {
1243 data: HostData::new(data),
1244 },
1245 })
1246}
1247
1248/// Reorder explicit row-major host values into column-major order.
1249fn row_major_reorder<T: Clone, R: TensorRank>(
1250 shape: impl tenferro_tensor_core::IntoRankShape<R>,
1251 data: Vec<T>,
1252 op: &'static str,
1253) -> crate::Result<(R::Shape, Vec<T>)> {
1254 let shape = shape
1255 .into_rank_shape()
1256 .map_err(|err| tensor_layout_error(op, err))?;
1257 tenferro_tensor_core::col_major_strides(shape.as_ref())
1258 .map_err(|err| tensor_layout_error(op, err))?;
1259 try_checked_shape_len(shape.as_ref(), data.len(), op)?;
1260 let mut row_strides = shape_vec_zeros(shape.as_ref().len());
1261 let mut stride = 1usize;
1262 for axis in (0..row_strides.len()).rev() {
1263 row_strides[axis] = stride;
1264 stride = stride
1265 .checked_mul(shape.as_ref()[axis])
1266 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
1267 }
1268 let mut coordinates = shape_vec_zeros(row_strides.len());
1269 let mut source_offset = 0usize;
1270 let mut reordered = Vec::with_capacity(data.len());
1271 for _ in 0..data.len() {
1272 reordered.push(data[source_offset].clone());
1273 for axis in 0..coordinates.len() {
1274 coordinates[axis] += 1;
1275 if coordinates[axis] < shape.as_ref()[axis] {
1276 source_offset += row_strides[axis];
1277 break;
1278 }
1279 coordinates[axis] = 0;
1280 source_offset -= row_strides[axis] * (shape.as_ref()[axis] - 1);
1281 }
1282 }
1283 Ok((shape, reordered))
1284}
1285
1286impl<T, R: TensorRank> TypedTensor<T, R, Host> {
1287 /// Adopt a column-major host `Vec<T>` as a statically host-owned tensor.
1288 ///
1289 /// The payload is the caller's vector itself; `T` needs no `Copy`,
1290 /// [`TensorScalar`] or arithmetic bound, and no group, session or device is
1291 /// involved.
1292 ///
1293 /// # Examples
1294 /// ```
1295 /// use tenferro_tensor::{Host, Rank, TypedTensor};
1296 /// struct Custom(String);
1297 /// let tensor = TypedTensor::<Custom, Rank<2>, Host>::from_host_vec_col_major(
1298 /// [1, 2], vec![Custom("a".into()), Custom("b".into())],
1299 /// )?;
1300 /// assert_eq!(tensor[&[0, 1]].0.as_str(), "b");
1301 /// # Ok::<(), tenferro_tensor::Error>(())
1302 /// ```
1303 /// # Errors
1304 /// Returns [`crate::Error::Validation`] for a rank mismatch, shape/data
1305 /// length mismatch or shape/stride arithmetic overflow.
1306 pub fn from_host_vec_col_major(
1307 shape: impl tenferro_tensor_core::IntoRankShape<R>,
1308 data: Vec<T>,
1309 ) -> crate::Result<Self> {
1310 typed_host_tensor_from_vec_col_major(shape, data, "from_host_vec_col_major")
1311 }
1312
1313 /// Explicitly import row-major host values into column-major storage.
1314 ///
1315 /// Clones each input element once.
1316 ///
1317 /// # Examples
1318 /// ```
1319 /// use tenferro_tensor::{Host, Rank, TypedTensor};
1320 /// let tensor = TypedTensor::<i32, Rank<2>, Host>::from_host_vec_row_major(
1321 /// [2, 3], vec![1, 2, 3, 4, 5, 6],
1322 /// )?;
1323 /// assert_eq!(tensor[&[1, 0]], 4);
1324 /// assert_eq!(tensor[&[0, 2]], 3);
1325 /// # Ok::<(), tenferro_tensor::Error>(())
1326 /// ```
1327 /// # Errors
1328 /// Returns [`crate::Error::Validation`] for a rank, shape-length or stride
1329 /// overflow, or when the shape product disagrees with the input length.
1330 pub fn from_host_vec_row_major(
1331 shape: impl tenferro_tensor_core::IntoRankShape<R>,
1332 data: Vec<T>,
1333 ) -> crate::Result<Self>
1334 where
1335 T: Clone,
1336 {
1337 let (shape, data) = row_major_reorder(shape, data, "from_host_vec_row_major")?;
1338 typed_host_tensor_from_vec_col_major(shape, data, "from_host_vec_row_major")
1339 }
1340
1341 /// Borrow the owned host elements.
1342 ///
1343 /// # Examples
1344 /// ```
1345 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1346 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1347 /// assert_eq!(tensor.as_slice(), &[1, 2]);
1348 /// # Ok::<(), tenferro_tensor::Error>(())
1349 /// ```
1350 pub fn as_slice(&self) -> &[T] {
1351 self.storage.data.as_slice()
1352 }
1353
1354 /// Alias of [`Self::as_slice`].
1355 ///
1356 /// # Examples
1357 ///
1358 /// ```
1359 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1360 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1361 /// assert_eq!(tensor.host_data(), &[1, 2]);
1362 /// # Ok::<(), tenferro_tensor::Error>(())
1363 /// ```
1364 pub fn host_data(&self) -> &[T] {
1365 self.as_slice()
1366 }
1367
1368 /// Exclusively borrow the owned host elements.
1369 ///
1370 /// # Examples
1371 ///
1372 /// ```
1373 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1374 /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1375 /// tensor.host_data_mut()[0] = 5;
1376 /// assert_eq!(tensor.as_slice(), &[5, 2]);
1377 /// # Ok::<(), tenferro_tensor::Error>(())
1378 /// ```
1379 pub fn host_data_mut(&mut self) -> &mut [T] {
1380 self.storage.data.as_mut_slice()
1381 }
1382
1383 /// Borrow one element by checked column-major multi-index.
1384 ///
1385 /// # Examples
1386 ///
1387 /// ```
1388 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1389 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2, 2], vec![1, 2, 3, 4])?;
1390 /// assert_eq!(tensor.get(&[1, 1])?, &4);
1391 /// assert!(tensor.get(&[2, 0]).is_err());
1392 /// # Ok::<(), tenferro_tensor::Error>(())
1393 /// ```
1394 ///
1395 /// # Errors
1396 /// Returns [`crate::Error::Validation`] for a wrong rank, an out-of-range
1397 /// coordinate or offset arithmetic overflow.
1398 pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
1399 let offset = self.linear_offset(indices)?;
1400 self.as_slice().get(offset).ok_or_else(|| {
1401 crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
1402 })
1403 }
1404
1405 /// Exclusively borrow one element by checked column-major multi-index.
1406 ///
1407 /// # Examples
1408 ///
1409 /// ```
1410 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1411 /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1412 /// *tensor.get_mut(&[1])? = 9;
1413 /// assert_eq!(tensor.as_slice(), &[1, 9]);
1414 /// # Ok::<(), tenferro_tensor::Error>(())
1415 /// ```
1416 ///
1417 /// # Errors
1418 /// Returns [`crate::Error::Validation`] for a wrong rank, an out-of-range
1419 /// coordinate or offset arithmetic overflow.
1420 pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
1421 let offset = self.linear_offset(indices)?;
1422 self.host_data_mut().get_mut(offset).ok_or_else(|| {
1423 crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
1424 })
1425 }
1426
1427 /// Consume this tensor and return the original host vector.
1428 ///
1429 /// # Examples
1430 ///
1431 /// ```
1432 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1433 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1434 /// assert_eq!(tensor.into_host_vec(), vec![1, 2]);
1435 /// # Ok::<(), tenferro_tensor::Error>(())
1436 /// ```
1437 pub fn into_host_vec(self) -> Vec<T> {
1438 self.storage.data.into_vec()
1439 }
1440
1441 /// Consume this tensor and return its shape and column-major host vector.
1442 ///
1443 /// # Examples
1444 ///
1445 /// ```
1446 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1447 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2, 1], vec![1, 2])?;
1448 /// let (shape, data) = tensor.into_vec_col_major();
1449 /// assert_eq!(shape, vec![2, 1]);
1450 /// assert_eq!(data, vec![1, 2]);
1451 /// # Ok::<(), tenferro_tensor::Error>(())
1452 /// ```
1453 pub fn into_vec_col_major(self) -> (Vec<usize>, Vec<T>) {
1454 let shape = self.shape().to_vec();
1455 (shape, self.into_host_vec())
1456 }
1457
1458 /// Make an explicit independent host copy with the same placement.
1459 ///
1460 /// # Examples
1461 /// ```
1462 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1463 /// let tensor = TypedTensor::<String, DynRank, Host>::from_host_vec_col_major(vec![1], vec!["a".into()])?;
1464 /// let copy = tensor.clone();
1465 /// assert_eq!(copy[&[0]], "a");
1466 /// # Ok::<(), tenferro_tensor::Error>(())
1467 /// ```
1468 pub fn duplicate(&self) -> Self
1469 where
1470 T: Clone,
1471 {
1472 let mut copy = typed_host_tensor_from_vec_col_major::<T, R>(
1473 self.shape.clone(),
1474 self.as_slice().to_vec(),
1475 "TypedTensor::duplicate",
1476 )
1477 .unwrap_or_else(|err| unreachable!("a validated host owner re-copies: {err}"));
1478 copy.placement = self.placement.clone();
1479 copy
1480 }
1481
1482 /// Move this host owner into the runtime union without copying or regrouping.
1483 ///
1484 /// # Examples
1485 /// ```
1486 /// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1487 /// let host = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7])?;
1488 /// let dynamic: TypedTensor<i32, DynRank, Dynamic> = host.into_dynamic();
1489 /// assert_eq!(dynamic.host_data()?, &[7]);
1490 /// # Ok::<(), tenferro_tensor::Error>(())
1491 /// ```
1492 pub fn into_dynamic(self) -> TypedTensor<T, R, Dynamic> {
1493 TypedTensor {
1494 shape: self.shape,
1495 placement: self.placement,
1496 layout: self.layout,
1497 storage: DynamicStorage::Host(self.storage),
1498 }
1499 }
1500
1501 /// Borrow this host owner as a typed view.
1502 ///
1503 /// # Examples
1504 ///
1505 /// ```
1506 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1507 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1508 /// let view = tensor.as_view();
1509 /// assert_eq!(view.shape(), &[2]);
1510 /// assert_eq!(view.as_host_slice(), &[1, 2]);
1511 /// # Ok::<(), tenferro_tensor::Error>(())
1512 /// ```
1513 pub fn as_view(&self) -> TypedTensorView<'_, T, R, Host> {
1514 TypedTensorView {
1515 buffer: TensorStorageRef::Host(self.as_slice()),
1516 root: None,
1517 layout: self.layout(),
1518 placement: self.placement.clone(),
1519 _representation: std::marker::PhantomData,
1520 }
1521 }
1522
1523 /// Exclusively borrow this host owner as a mutable typed view.
1524 ///
1525 /// # Examples
1526 ///
1527 /// ```
1528 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1529 /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1530 /// tensor.as_view_mut().as_host_slice_mut()[1] = 4;
1531 /// assert_eq!(tensor.as_slice(), &[1, 4]);
1532 /// # Ok::<(), tenferro_tensor::Error>(())
1533 /// ```
1534 pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R, Host> {
1535 let layout = self.layout();
1536 let placement = self.placement.clone();
1537 TypedTensorViewMut {
1538 buffer: TensorStorageRefMut::Host(self.storage.data.as_mut_slice()),
1539 root: None,
1540 layout,
1541 placement,
1542 _representation: std::marker::PhantomData,
1543 }
1544 }
1545}
1546
1547impl<T: Clone, R: TensorRank> Clone for TypedTensor<T, R, Host> {
1548 fn clone(&self) -> Self {
1549 self.duplicate()
1550 }
1551}
1552
1553impl<T, R: TensorRank> std::ops::Index<&[usize]> for TypedTensor<T, R, Host> {
1554 type Output = T;
1555
1556 /// # Panics
1557 ///
1558 /// Panics when the index has the wrong rank or is out of bounds, like any
1559 /// other slice indexing. Use [`TypedTensor::get`] for the checked form.
1560 fn index(&self, indices: &[usize]) -> &T {
1561 match self.get(indices) {
1562 Ok(value) => value,
1563 Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1564 }
1565 }
1566}
1567
1568impl<T, R: TensorRank> std::ops::IndexMut<&[usize]> for TypedTensor<T, R, Host> {
1569 /// # Panics
1570 ///
1571 /// Panics when the index has the wrong rank or is out of bounds, like any
1572 /// other slice indexing. Use [`TypedTensor::get_mut`] for the checked form.
1573 fn index_mut(&mut self, indices: &[usize]) -> &mut T {
1574 match self.get_mut(indices) {
1575 Ok(value) => value,
1576 Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1577 }
1578 }
1579}
1580
1581impl<T, const N: usize> std::ops::Index<[usize; N]> for TypedTensor<T, Rank<N>, Host> {
1582 type Output = T;
1583
1584 /// # Panics
1585 ///
1586 /// Panics when an index is out of bounds, like any other slice indexing.
1587 /// Use [`TypedTensor::get`] for the checked form.
1588 ///
1589 /// # Examples
1590 ///
1591 /// ```rust
1592 /// use tenferro_tensor::{Host, Rank, TypedTensor};
1593 /// let tensor =
1594 /// TypedTensor::<i32, Rank<2>, Host>::from_host_vec_col_major([2, 2], vec![1, 2, 3, 4])?;
1595 /// assert_eq!(tensor[[0, 1]], 3);
1596 /// # Ok::<(), tenferro_tensor::Error>(())
1597 /// ```
1598 fn index(&self, indices: [usize; N]) -> &T {
1599 match self.get(&indices) {
1600 Ok(value) => value,
1601 Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1602 }
1603 }
1604}
1605
1606impl<T, const N: usize> std::ops::Index<&[usize; N]> for TypedTensor<T, Rank<N>, Host> {
1607 type Output = T;
1608
1609 /// The `&[usize; N]` spelling, which keeps a bare `&[i, j]` literal
1610 /// resolving as an integer index rather than forcing `usize` inference to
1611 /// choose between the slice and array spellings.
1612 ///
1613 /// # Panics
1614 ///
1615 /// Panics when an index is out of bounds, like any other slice indexing.
1616 /// Use [`TypedTensor::get`] for the checked form.
1617 ///
1618 /// # Examples
1619 ///
1620 /// ```rust
1621 /// use tenferro_tensor::{Host, Rank, TypedTensor};
1622 /// let tensor =
1623 /// TypedTensor::<i32, Rank<2>, Host>::from_host_vec_col_major([2, 2], vec![1, 2, 3, 4])?;
1624 /// assert_eq!(tensor[&[0, 1]], 3);
1625 /// # Ok::<(), tenferro_tensor::Error>(())
1626 /// ```
1627 fn index(&self, indices: &[usize; N]) -> &T {
1628 std::ops::Index::index(self, *indices)
1629 }
1630}
1631
1632impl<T, const N: usize> std::ops::IndexMut<[usize; N]> for TypedTensor<T, Rank<N>, Host> {
1633 /// # Panics
1634 ///
1635 /// Panics when an index is out of bounds, like any other slice indexing.
1636 /// Use [`TypedTensor::get_mut`] for the checked form.
1637 ///
1638 /// # Examples
1639 ///
1640 /// ```rust
1641 /// use tenferro_tensor::{Host, Rank, TypedTensor};
1642 /// let mut tensor =
1643 /// TypedTensor::<i32, Rank<2>, Host>::from_host_vec_col_major([2, 2], vec![1, 2, 3, 4])?;
1644 /// tensor[[1, 0]] = 7;
1645 /// assert_eq!(tensor.as_slice(), &[1, 7, 3, 4]);
1646 /// # Ok::<(), tenferro_tensor::Error>(())
1647 /// ```
1648 fn index_mut(&mut self, indices: [usize; N]) -> &mut T {
1649 match self.get_mut(&indices) {
1650 Ok(value) => value,
1651 Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1652 }
1653 }
1654}
1655
1656impl<T, const N: usize> std::ops::IndexMut<&[usize; N]> for TypedTensor<T, Rank<N>, Host> {
1657 /// # Panics
1658 ///
1659 /// Panics when an index is out of bounds, like any other slice indexing.
1660 /// Use [`TypedTensor::get_mut`] for the checked form.
1661 ///
1662 /// # Examples
1663 ///
1664 /// ```rust
1665 /// use tenferro_tensor::{Host, Rank, TypedTensor};
1666 /// let mut tensor =
1667 /// TypedTensor::<i32, Rank<2>, Host>::from_host_vec_col_major([2, 2], vec![1, 2, 3, 4])?;
1668 /// tensor[&[1, 0]] = 7;
1669 /// assert_eq!(tensor.as_slice(), &[1, 7, 3, 4]);
1670 /// # Ok::<(), tenferro_tensor::Error>(())
1671 /// ```
1672 fn index_mut(&mut self, indices: &[usize; N]) -> &mut T {
1673 std::ops::IndexMut::index_mut(self, *indices)
1674 }
1675}
1676
1677impl<T, const N: usize> std::ops::Index<[usize; N]> for TypedTensor<T, Rank<N>> {
1678 type Output = T;
1679
1680 /// # Panics
1681 ///
1682 /// Panics when an index is out of bounds or when the storage is not
1683 /// host-accessible, like any other slice indexing. Use
1684 /// [`TypedTensor::get`] for the checked form.
1685 ///
1686 /// # Examples
1687 ///
1688 /// ```rust
1689 /// use tenferro_tensor::{Rank, TypedTensor};
1690 /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?;
1691 /// assert_eq!(tensor[[0, 1]], 3.);
1692 /// # Ok::<(), tenferro_tensor::Error>(())
1693 /// ```
1694 fn index(&self, indices: [usize; N]) -> &T {
1695 match self.get(&indices) {
1696 Ok(value) => value,
1697 Err(err) => panic!("TypedTensor index {indices:?} is invalid: {err}"),
1698 }
1699 }
1700}
1701
1702impl<T, const N: usize> std::ops::IndexMut<[usize; N]> for TypedTensor<T, Rank<N>> {
1703 /// # Panics
1704 ///
1705 /// Panics when an index is out of bounds or when the storage is not
1706 /// host-accessible, like any other slice indexing. Use
1707 /// [`TypedTensor::get_mut`] for the checked form.
1708 ///
1709 /// # Examples
1710 ///
1711 /// ```rust
1712 /// use tenferro_tensor::{Rank, TypedTensor};
1713 /// let mut tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?;
1714 /// tensor[[1, 0]] = 7.;
1715 /// assert_eq!(tensor.as_slice()?, &[1., 7., 3., 4.]);
1716 /// # Ok::<(), tenferro_tensor::Error>(())
1717 /// ```
1718 fn index_mut(&mut self, indices: [usize; N]) -> &mut T {
1719 match self.get_mut(&indices) {
1720 Ok(value) => value,
1721 Err(err) => panic!("TypedTensor index {indices:?} is invalid: {err}"),
1722 }
1723 }
1724}
1725
1726/// Read mapping over a host slice, as one owning guard.
1727fn host_read_guard<T>(data: &[T]) -> HostReadGuard<'_, T> {
1728 HostReadGuard::new(data)
1729}
1730
1731/// Write mapping over an exclusive host slice, as one owning guard.
1732fn host_write_guard<T: Clone>(data: &mut [T]) -> HostWriteGuard<'_, T> {
1733 let len = data.len();
1734 HostWriteGuard::new(len, move |source| {
1735 data.clone_from_slice(source);
1736 Ok(())
1737 })
1738}
1739
1740/// Promote a plain host payload into a group-backed root without copying elements.
1741fn promote_host_group<T: TensorScalar, R: TensorRank>(
1742 shape: R::Shape,
1743 host: HostData<T>,
1744) -> crate::Result<OwnedTensorGroup<R>> {
1745 let recycler = host.recycler.clone();
1746 let mut group = OwnedTensorGroup::from_host_vec(shape, host.into_vec())?;
1747 if let Some(recycler) = recycler {
1748 group
1749 .group
1750 .set_host_recycler(group.allocation_index.index(), recycler)
1751 .map_err(|error| crate::Error::runtime_state_source("TypedTensor::promote", error))?;
1752 }
1753 Ok(group)
1754}
1755
1756impl<T, R: TensorRank> TypedTensor<T, R, Host> {
1757 /// Borrow the owned host elements through one owning read mapping.
1758 ///
1759 /// The guard retains the exclusive borrow of this owner, so no other access
1760 /// can overlap it while the mapping is alive.
1761 ///
1762 /// # Examples
1763 /// ```
1764 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1765 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1766 /// let guard = tensor.map_read();
1767 /// assert_eq!(&guard[..], &[1, 2]);
1768 /// # Ok::<(), tenferro_tensor::Error>(())
1769 /// ```
1770 pub fn map_read(&self) -> HostReadGuard<'_, T> {
1771 host_read_guard(self.as_slice())
1772 }
1773
1774 /// Exclusively borrow the owned host elements through one owning write mapping.
1775 ///
1776 /// Publish data with [`HostWriteGuard::copy_from_slice`]; the mapping is
1777 /// released when the guard is dropped.
1778 ///
1779 /// # Examples
1780 /// ```
1781 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1782 /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1783 /// tensor.map_write().copy_from_slice(&[3, 4]).unwrap();
1784 /// assert_eq!(tensor.as_slice(), &[3, 4]);
1785 /// # Ok::<(), tenferro_tensor::Error>(())
1786 /// ```
1787 pub fn map_write(&mut self) -> HostWriteGuard<'_, T>
1788 where
1789 T: Clone,
1790 {
1791 host_write_guard(self.host_data_mut())
1792 }
1793}
1794
1795impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R, Host> {
1796 /// Promote this plain host owner into a group-backed owner.
1797 ///
1798 /// The payload is adopted as a provider root: no element is copied and a
1799 /// pooled return target survives the promotion.
1800 ///
1801 /// # Examples
1802 /// ```
1803 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1804 /// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1805 /// let gpu = host.promote()?;
1806 /// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
1807 /// # Ok::<(), tenferro_tensor::Error>(())
1808 /// ```
1809 /// # Errors
1810 /// Returns [`crate::Error::RuntimeState`] when the new allocation group
1811 /// rejects the promoted root or its recycler.
1812 pub fn promote(self) -> crate::Result<TypedTensor<T, R, Gpu>> {
1813 let TypedTensor {
1814 shape,
1815 placement,
1816 layout,
1817 storage,
1818 } = self;
1819 let group = promote_host_group(shape.clone(), storage.data)?;
1820 Ok(TypedTensor {
1821 shape,
1822 placement,
1823 layout,
1824 storage: GroupStorage {
1825 group: Box::new(group),
1826 },
1827 })
1828 }
1829}
1830
1831impl<T, R: TensorRank> TypedTensor<T, R, Dynamic> {
1832 /// Borrow host elements through one owning read mapping.
1833 ///
1834 /// # Examples
1835 ///
1836 /// ```
1837 /// use tenferro_tensor::TypedTensor;
1838 /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
1839 /// let guard = tensor.map_read()?;
1840 /// assert_eq!(&guard[..], &[1, 2]);
1841 /// # Ok::<(), tenferro_tensor::Error>(())
1842 /// ```
1843 ///
1844 /// # Errors
1845 /// Returns [`crate::Error::HostAccess`] with
1846 /// [`HostAccessError::Unsupported`] when the group's allocation is not
1847 /// host-accessible, or [`crate::Error::Validation`] with
1848 /// [`ValidationError::NonContiguousViewAsSlice`] or
1849 /// [`ValidationError::ViewOutOfBounds`] when the owner has no contiguous
1850 /// logical extent inside its backing storage.
1851 pub fn map_read(&self) -> crate::Result<HostReadGuard<'_, T>> {
1852 match &self.storage {
1853 DynamicStorage::Host(host) => Ok(host_read_guard(host.data.as_slice())),
1854 DynamicStorage::Group(core) => {
1855 // A root-preserving extraction exports only its descriptor
1856 // extent (#2023), so the mapping must not expose the root.
1857 let range = group_logical_range(&core.group, "TypedTensor::map_read")?;
1858 let data = core.group.host_slice::<T>()?;
1859 data.get(range).map(host_read_guard).ok_or_else(|| {
1860 crate::Error::validation(
1861 "TypedTensor::map_read",
1862 ValidationError::ViewOutOfBounds,
1863 )
1864 })
1865 }
1866 }
1867 }
1868
1869 /// Exclusively borrow host elements through one owning write mapping.
1870 ///
1871 /// Publish data with [`HostWriteGuard::copy_from_slice`].
1872 ///
1873 /// # Examples
1874 ///
1875 /// ```
1876 /// use tenferro_tensor::{Error, TypedTensor};
1877 /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
1878 /// tensor
1879 /// .map_write()?
1880 /// .copy_from_slice(&[3, 4])
1881 /// .map_err(|err| Error::host_access("map_write", err))?;
1882 /// assert_eq!(tensor.host_data()?, &[3, 4]);
1883 /// # Ok::<(), tenferro_tensor::Error>(())
1884 /// ```
1885 ///
1886 /// # Errors
1887 /// Returns [`crate::Error::HostAccess`] with
1888 /// [`HostAccessError::Unsupported`] when the group's allocation is not
1889 /// host-writable, or [`crate::Error::Validation`] with
1890 /// [`ValidationError::NonContiguousViewAsSlice`] or
1891 /// [`ValidationError::ViewOutOfBounds`] when the owner has no contiguous
1892 /// logical extent inside its backing storage.
1893 pub fn map_write(&mut self) -> crate::Result<HostWriteGuard<'_, T>>
1894 where
1895 T: Clone,
1896 {
1897 match &mut self.storage {
1898 DynamicStorage::Host(host) => Ok(host_write_guard(host.data.as_mut_slice())),
1899 DynamicStorage::Group(core) => {
1900 let range = group_logical_range(&core.group, "TypedTensor::map_write")?;
1901 let data = core.group.host_slice_mut::<T>()?;
1902 data.get_mut(range).map(host_write_guard).ok_or_else(|| {
1903 crate::Error::validation(
1904 "TypedTensor::map_write",
1905 ValidationError::ViewOutOfBounds,
1906 )
1907 })
1908 }
1909 }
1910 }
1911}
1912
1913impl<T, R: TensorRank> TypedTensor<T, R, Gpu> {
1914 /// Adopt a backend-owned buffer as a statically group-backed owner.
1915 ///
1916 /// A host buffer belongs to the [`Host`] representation; pass it to
1917 /// `TypedTensor::<T, R, Host>` instead of silently regrouping it here.
1918 ///
1919 /// # Examples
1920 /// ```
1921 /// use tenferro_tensor::{BackendStorageHandle, Placement, StorageBuffer, TypedTensor};
1922 /// let handle = BackendStorageHandle::<f32>::new_with_len(1, 2);
1923 /// let gpu = TypedTensor::<f32, tenferro_tensor::DynRank, tenferro_tensor::Gpu>::from_backend_buffer_col_major(
1924 /// vec![2],
1925 /// StorageBuffer::Backend(Box::new(handle)),
1926 /// Placement::default(),
1927 /// )?;
1928 /// assert!(gpu.is_backend_buffer());
1929 /// # Ok::<(), tenferro_tensor::Error>(())
1930 /// ```
1931 /// # Errors
1932 /// Returns [`crate::Error::Validation`] when the compact shape disagrees
1933 /// with the buffer length, [`crate::Error::RuntimeState`] for a host buffer,
1934 /// or a runtime-state error when the allocation group cannot be built.
1935 pub fn from_backend_buffer_col_major(
1936 shape: impl tenferro_tensor_core::IntoRankShape<R>,
1937 buffer: StorageBuffer<T>,
1938 placement: Placement,
1939 ) -> crate::Result<Self>
1940 where
1941 T: TensorScalar + Send + Sync + 'static,
1942 {
1943 let op = "TypedTensor::from_backend_buffer_col_major";
1944 let layout = try_compact_layout(shape, op)?;
1945 try_checked_shape_len(layout.shape(), buffer.len(), op)?;
1946 let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
1947 .map_err(|err| tensor_layout_error(op, err))?;
1948 match buffer {
1949 StorageBuffer::Host(_) => Err(crate::Error::runtime_state(
1950 op,
1951 "a host buffer is plain host storage; use TypedTensor::<T, R, Host>::from_vec_col_major",
1952 )),
1953 StorageBuffer::Backend(buffer) => {
1954 let group = OwnedTensorGroup::from_backend_buffer(
1955 group_shape.clone(),
1956 StorageBuffer::Backend(buffer),
1957 placement.clone(),
1958 )?;
1959 Ok(TypedTensor {
1960 shape: group_shape,
1961 placement,
1962 layout: stride_vec(layout.strides()),
1963 storage: GroupStorage {
1964 group: Box::new(group),
1965 },
1966 })
1967 }
1968 }
1969 }
1970
1971 /// Move this group-backed owner into the runtime union without rewriting it.
1972 ///
1973 /// # Examples
1974 ///
1975 /// ```
1976 /// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1977 /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7.0])?.promote()?;
1978 /// let dynamic: TypedTensor<f64, DynRank, Dynamic> = gpu.into_dynamic();
1979 /// assert_eq!(dynamic.host_data()?, &[7.0]);
1980 /// # Ok::<(), tenferro_tensor::Error>(())
1981 /// ```
1982 pub fn into_dynamic(self) -> TypedTensor<T, R, Dynamic> {
1983 TypedTensor {
1984 shape: self.shape,
1985 placement: self.placement,
1986 layout: self.layout,
1987 storage: DynamicStorage::Group(self.storage),
1988 }
1989 }
1990
1991 /// Whether this group's descriptor names a non-CPU provider.
1992 ///
1993 /// # Examples
1994 ///
1995 /// ```
1996 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1997 /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7.0])?.promote()?;
1998 /// // A promoted host payload stays on the CPU provider.
1999 /// assert!(!gpu.is_backend_buffer());
2000 /// # Ok::<(), tenferro_tensor::Error>(())
2001 /// ```
2002 pub fn is_backend_buffer(&self) -> bool {
2003 self.storage.group.is_backend_buffer()
2004 }
2005
2006 /// Borrow host elements when the group's allocation is host-accessible.
2007 ///
2008 /// # Examples
2009 ///
2010 /// ```
2011 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
2012 /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?.promote()?;
2013 /// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
2014 /// # Ok::<(), tenferro_tensor::Error>(())
2015 /// ```
2016 ///
2017 /// # Errors
2018 /// Returns [`crate::Error::RuntimeState`] for a device-only allocation and
2019 /// the non-compact-descriptor validation error for a strided owner.
2020 pub fn host_data(&self) -> crate::Result<&[T]> {
2021 let data = self.storage.group.host_slice::<T>()?;
2022 let range = group_logical_range(&self.storage.group, "TypedTensor::host_data")?;
2023 data.get(range).ok_or_else(|| {
2024 crate::Error::validation("TypedTensor::host_data", ValidationError::ViewOutOfBounds)
2025 })
2026 }
2027
2028 /// Exclusively borrow host elements when the group's allocation is host-accessible.
2029 ///
2030 /// # Examples
2031 ///
2032 /// ```
2033 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
2034 /// let mut gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?.promote()?;
2035 /// gpu.host_data_mut()?[0] = 3.0;
2036 /// assert_eq!(gpu.host_data()?, &[3.0, 2.0]);
2037 /// # Ok::<(), tenferro_tensor::Error>(())
2038 /// ```
2039 ///
2040 /// # Errors
2041 /// Returns [`crate::Error::RuntimeState`] for a device-only allocation and
2042 /// the non-compact-descriptor validation error for a strided owner.
2043 pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
2044 let range = group_logical_range(&self.storage.group, "TypedTensor::host_data_mut")?;
2045 let data = self.storage.group.host_slice_mut::<T>()?;
2046 data.get_mut(range).ok_or_else(|| {
2047 crate::Error::validation(
2048 "TypedTensor::host_data_mut",
2049 ValidationError::ViewOutOfBounds,
2050 )
2051 })
2052 }
2053
2054 /// Prepare this group-backed owner for one provider-native read binding.
2055 ///
2056 /// # Errors
2057 /// Returns a provider preparation error when the group has no device-read
2058 /// path for this layout.
2059 #[doc(hidden)]
2060 pub fn prepare_device_read(
2061 &self,
2062 op: &'static str,
2063 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
2064 where
2065 T: TensorScalar + 'static,
2066 {
2067 let layout = group_physical_layout(&self.storage.group);
2068 self.storage
2069 .group
2070 .prepare_device_read_for_layout::<T>(&layout)
2071 .map_err(|error| crate::Error::runtime_state_source(op, error))
2072 }
2073
2074 /// Prepare this group-backed owner for one provider-native write binding.
2075 ///
2076 /// # Errors
2077 /// Returns a provider preparation error when the group has no device-write
2078 /// path for this layout.
2079 #[doc(hidden)]
2080 pub fn prepare_device_write(
2081 &mut self,
2082 op: &'static str,
2083 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
2084 where
2085 T: TensorScalar + 'static,
2086 {
2087 let layout = group_physical_layout(&self.storage.group);
2088 self.storage
2089 .group
2090 .prepare_device_write_for_layout::<T>(&layout)
2091 .map_err(|error| crate::Error::runtime_state_source(op, error))
2092 }
2093}
2094
2095impl<T, R: TensorRank> TypedTensor<T, R, Dynamic> {
2096 /// Checked narrowing to the statically host-owned representation.
2097 ///
2098 /// Fails without consuming ownership of the source tensor.
2099 ///
2100 /// # Examples
2101 /// ```
2102 /// use tenferro_tensor::TypedTensor;
2103 /// let dynamic = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![7])?;
2104 /// let host = dynamic.into_host().unwrap();
2105 /// assert_eq!(host.as_slice(), &[7]);
2106 /// # Ok::<(), tenferro_tensor::Error>(())
2107 /// ```
2108 /// # Errors
2109 /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is
2110 /// group-backed rather than plain host storage.
2111 #[allow(clippy::result_large_err)]
2112 pub fn into_host(self) -> Result<TypedTensor<T, R, Host>, ReinterpretError<Self>> {
2113 let TypedTensor {
2114 shape,
2115 placement,
2116 layout,
2117 storage,
2118 } = self;
2119 match storage {
2120 DynamicStorage::Host(host) => Ok(TypedTensor {
2121 shape,
2122 placement,
2123 layout,
2124 storage: host,
2125 }),
2126 DynamicStorage::Group(group) => Err(ReinterpretError::new(
2127 TypedTensor {
2128 shape,
2129 placement,
2130 layout,
2131 storage: DynamicStorage::Group(group),
2132 },
2133 crate::Error::runtime_state(
2134 "TypedTensor::into_host",
2135 "the tensor is group-backed rather than plain host storage",
2136 ),
2137 )),
2138 }
2139 }
2140
2141 /// Checked narrowing to the group-backed representation.
2142 ///
2143 /// Fails without consuming ownership of the source tensor.
2144 ///
2145 /// # Examples
2146 ///
2147 /// ```
2148 /// use tenferro_tensor::{DynRank, Gpu, Host, TypedTensor};
2149 /// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![2.0])?;
2150 /// let dynamic = host.promote()?.into_dynamic();
2151 /// let gpu: TypedTensor<f64, DynRank, Gpu> = dynamic.into_gpu().map_err(|f| f.into_parts().1)?;
2152 /// assert_eq!(gpu.host_data()?, &[2.0]);
2153 /// let plain = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![2.0])?;
2154 /// assert!(plain.into_gpu().is_err());
2155 /// # Ok::<(), tenferro_tensor::Error>(())
2156 /// ```
2157 ///
2158 /// # Errors
2159 /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is a
2160 /// plain host owner rather than a group-backed one.
2161 #[allow(clippy::result_large_err)]
2162 pub fn into_gpu(self) -> Result<TypedTensor<T, R, Gpu>, ReinterpretError<Self>> {
2163 let TypedTensor {
2164 shape,
2165 placement,
2166 layout,
2167 storage,
2168 } = self;
2169 match storage {
2170 DynamicStorage::Group(group) => Ok(TypedTensor {
2171 shape,
2172 placement,
2173 layout,
2174 storage: group,
2175 }),
2176 DynamicStorage::Host(host) => Err(ReinterpretError::new(
2177 TypedTensor {
2178 shape,
2179 placement,
2180 layout,
2181 storage: DynamicStorage::Host(host),
2182 },
2183 crate::Error::runtime_state(
2184 "TypedTensor::into_gpu",
2185 "the tensor is a plain host owner rather than group-backed storage",
2186 ),
2187 )),
2188 }
2189 }
2190}
2191
2192impl<T, R: TensorRank> TypedTensor<T, R, Dynamic> {
2193 /// Layout that reflects the owned storage rather than the compact shape.
2194 ///
2195 /// [`Self::layout`] reports the compact construction layout. A
2196 /// root-preserving extraction may instead carry a nonzero descriptor offset
2197 /// and fewer logical elements than the backing root, so callers that
2198 /// canonicalize an owned tensor must use this accessor.
2199 pub(crate) fn physical_layout(&self) -> TensorLayout<R> {
2200 match &self.storage {
2201 DynamicStorage::Host(_) => TensorLayout::<R>::compact(self.shape.clone())
2202 .unwrap_or_else(|err| unreachable!("validated owned shape: {err}")),
2203 DynamicStorage::Group(core) => group_physical_layout(&core.group),
2204 }
2205 }
2206}
2207
2208/// Descriptor layout of a group-backed owner over its whole backing root.
2209///
2210/// The descriptor is the storage truth for a root-preserving extraction, so its
2211/// offset and backing length override the compact logical shape cached on the
2212/// tensor.
2213fn group_physical_layout<R: TensorRank>(core: &OwnedTensorGroup<R>) -> TensorLayout<R> {
2214 let descriptor = core
2215 .group
2216 .descriptor_layout(core.slot)
2217 .unwrap_or_else(|| unreachable!("a live group carries its descriptor layout"));
2218 let shape = R::shape_from_vec(shape_vec(descriptor.shape()))
2219 .unwrap_or_else(|err| unreachable!("validated descriptor shape: {err:?}"));
2220 let strides = R::strides_from_vec(stride_vec(descriptor.strides()))
2221 .unwrap_or_else(|err| unreachable!("validated descriptor strides: {err:?}"));
2222 let buffer_len = core
2223 .group
2224 .descriptor_backing_len(core.slot)
2225 .unwrap_or_else(|| unreachable!("a live group carries its root length"));
2226 TensorLayout::<R>::from_parts(shape, strides, descriptor.offset(), buffer_len)
2227 .unwrap_or_else(|err| unreachable!("validated descriptor layout: {err:?}"))
2228}
2229
2230/// Contiguous logical element range of a group-backed owner inside its root.
2231fn group_logical_range<R: TensorRank>(
2232 core: &OwnedTensorGroup<R>,
2233 op: &'static str,
2234) -> crate::Result<std::ops::Range<usize>> {
2235 core.group
2236 .descriptor_logical_range(core.slot)
2237 .ok_or_else(|| crate::Error::validation(op, ValidationError::NonContiguousViewAsSlice))
2238}
2239
2240impl<T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensor<T, R, D> {
2241 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2242 formatter
2243 .debug_struct("TypedTensor")
2244 .field("shape", &self.shape.as_ref())
2245 .field("placement", &self.placement)
2246 .finish_non_exhaustive()
2247 }
2248}
2249
2250impl<T, R: TensorRank, D: Representation> TypedTensor<T, R, D> {
2251 /// Number of elements in the tensor.
2252 ///
2253 /// # Examples
2254 ///
2255 /// ```rust
2256 /// use tenferro_tensor::TypedTensor;
2257 ///
2258 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2259 /// assert_eq!(t.n_elements(), 6);
2260 /// ```
2261 pub fn n_elements(&self) -> usize {
2262 // Invariant: owned tensor constructors validate compact shape length against buffer length.
2263 match try_shape_product(self.shape(), "TypedTensor::n_elements") {
2264 Ok(n) => n,
2265 Err(err) => {
2266 unreachable!("TypedTensor compact shape is validated at construction: {err}")
2267 }
2268 }
2269 }
2270
2271 /// Tensor shape.
2272 ///
2273 /// # Examples
2274 ///
2275 /// ```
2276 /// use tenferro_tensor::TypedTensor;
2277 ///
2278 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
2279 /// assert_eq!(t.shape(), &[2]);
2280 /// ```
2281 pub fn shape(&self) -> &[usize] {
2282 self.shape.as_ref()
2283 }
2284
2285 /// Column-major element strides of this compact tensor.
2286 ///
2287 /// The layout is cached at construction, so views of a tensor borrow it
2288 /// rather than computing (and allocating) it once per operation.
2289 ///
2290 /// # Examples
2291 ///
2292 /// ```
2293 /// use tenferro_tensor::TypedTensor;
2294 ///
2295 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2296 /// assert_eq!(t.strides(), &[1, 2]);
2297 /// ```
2298 pub fn strides(&self) -> &[isize] {
2299 &self.layout
2300 }
2301
2302 /// Tensor rank.
2303 ///
2304 /// # Examples
2305 ///
2306 /// ```
2307 /// use tenferro_tensor::TypedTensor;
2308 ///
2309 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2310 /// assert_eq!(t.rank(), 2);
2311 /// ```
2312 pub fn rank(&self) -> usize {
2313 self.shape().len()
2314 }
2315
2316 /// Tensor layout metadata.
2317 ///
2318 /// Owned typed tensors are always compact column-major layouts.
2319 ///
2320 /// # Examples
2321 ///
2322 /// ```
2323 /// use tenferro_tensor::TypedTensor;
2324 ///
2325 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2326 /// assert_eq!(t.layout().strides(), &[1, 2]);
2327 /// ```
2328 pub fn layout(&self) -> TensorLayout<R> {
2329 TensorLayout::compact(self.shape.clone())
2330 .unwrap_or_else(|err| unreachable!("validated owned shape: {err}"))
2331 }
2332
2333 /// Return placement metadata for this tensor.
2334 ///
2335 /// # Examples
2336 ///
2337 /// ```
2338 /// use tenferro_tensor::{MemoryKind, TypedTensor};
2339 ///
2340 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
2341 /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
2342 /// ```
2343 pub fn placement(&self) -> &Placement {
2344 &self.placement
2345 }
2346
2347 /// Replace placement metadata without changing the storage buffer.
2348 ///
2349 /// # Examples
2350 ///
2351 /// ```
2352 /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
2353 ///
2354 /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
2355 /// t.set_placement(Placement {
2356 /// memory_kind: MemoryKind::PinnedHost,
2357 /// device: None,
2358 /// cpu_affinity: None,
2359 /// });
2360 /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
2361 /// ```
2362 pub fn set_placement(&mut self, placement: Placement) {
2363 self.placement = placement;
2364 }
2365
2366 /// Replace only CPU routing/locality metadata without changing storage.
2367 ///
2368 /// Device, memory kind, backend allocation domain, and allocation identity
2369 /// remain unchanged.
2370 ///
2371 /// # Examples
2372 ///
2373 /// ```rust
2374 /// use tenferro_tensor::{CpuDomainId, TypedTensor};
2375 ///
2376 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0])?;
2377 /// tensor.set_cpu_affinity(Some(CpuDomainId::new(4)));
2378 /// assert_eq!(tensor.placement().cpu_affinity, Some(CpuDomainId::new(4)));
2379 /// # Ok::<(), tenferro_tensor::Error>(())
2380 /// ```
2381 pub fn set_cpu_affinity(&mut self, cpu_affinity: Option<CpuDomainId>) {
2382 self.placement.cpu_affinity = cpu_affinity;
2383 }
2384
2385 /// Compute the checked compact column-major offset of an element.
2386 ///
2387 /// # Examples
2388 /// ```
2389 /// use tenferro_tensor::TypedTensor;
2390 /// let tensor = TypedTensor::<String>::from_vec_col_major([2, 3], vec![String::new(); 6])?;
2391 /// assert_eq!(tensor.linear_offset(&[1, 2])?, 5);
2392 /// # Ok::<(), tenferro_tensor::Error>(())
2393 /// ```
2394 /// # Errors
2395 /// Wrong rank, out-of-range coordinates or arithmetic overflow return
2396 /// [`crate::Error::Validation`].
2397 pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2398 try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
2399 }
2400
2401 /// Consume this tensor and return its layout metadata.
2402 ///
2403 /// # Examples
2404 ///
2405 /// ```
2406 /// use tenferro_tensor::TypedTensor;
2407 ///
2408 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
2409 /// assert!(t.into_layout().is_compact_col_major().unwrap());
2410 /// ```
2411 pub fn into_layout(self) -> TensorLayout<R> {
2412 self.layout()
2413 }
2414
2415 /// Compute the physical element offset for a logical index.
2416 ///
2417 /// # Examples
2418 ///
2419 /// ```rust
2420 /// use tenferro_tensor::TypedTensor;
2421 ///
2422 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
2423 /// assert_eq!(t.layout_linear_offset(&[1, 2])?, 5);
2424 /// # Ok::<(), tenferro_tensor::Error>(())
2425 /// ```
2426 /// # Errors
2427 ///
2428 /// Returns [`crate::Error::Validation`] with
2429 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
2430 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
2431 /// when an index is outside its axis extent, or
2432 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
2433 /// arithmetic overflows.
2434 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2435 try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::layout_linear_offset")
2436 }
2437
2438 /// Return whether this owned tensor is compact column-major.
2439 ///
2440 /// # Examples
2441 ///
2442 /// ```rust
2443 /// use tenferro_tensor::TypedTensor;
2444 ///
2445 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2446 /// assert!(t.is_col_major_contiguous()?);
2447 /// # Ok::<(), tenferro_tensor::Error>(())
2448 /// ```
2449 /// # Errors
2450 ///
2451 /// Returns [`crate::Error::Validation`] with
2452 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
2453 /// compactness arithmetic overflows.
2454 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
2455 self.layout()
2456 .is_compact_col_major()
2457 .map_err(|err| tensor_layout_error("TypedTensor::is_col_major_contiguous", err))
2458 }
2459
2460 /// Return a compact string summary of this tensor's layout metadata.
2461 ///
2462 /// # Examples
2463 ///
2464 /// ```rust
2465 /// use tenferro_tensor::TypedTensor;
2466 ///
2467 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2468 /// assert!(t.layout_summary().contains("shape=[2]"));
2469 /// # Ok::<(), tenferro_tensor::Error>(())
2470 /// ```
2471 pub fn layout_summary(&self) -> String {
2472 let layout = self.layout();
2473 layout_summary(self.shape(), layout.strides(), layout.offset())
2474 }
2475
2476 /// Assert this tensor is compact column-major.
2477 ///
2478 /// # Examples
2479 ///
2480 /// ```rust
2481 /// use tenferro_tensor::TypedTensor;
2482 ///
2483 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2484 /// t.assert_col_major_contiguous()?;
2485 /// # Ok::<(), tenferro_tensor::Error>(())
2486 /// ```
2487 /// # Errors
2488 ///
2489 /// Returns [`crate::Error::Validation`] with
2490 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
2491 /// compactness arithmetic overflows, or
2492 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
2493 /// tensor is not compact column-major.
2494 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
2495 let layout = self.layout();
2496 assert_layout_col_major_contiguous(
2497 self.is_col_major_contiguous()?,
2498 self.shape(),
2499 layout.strides(),
2500 layout.offset(),
2501 "TypedTensor::assert_col_major_contiguous",
2502 )
2503 }
2504}
2505
2506struct HostData<T> {
2507 buffer: StorageBuffer<T>,
2508 recycler: Option<std::sync::Weak<dyn crate::HostBufferRecycler<T>>>,
2509}
2510
2511impl<T> std::fmt::Debug for HostData<T> {
2512 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2513 formatter
2514 .debug_struct("HostData")
2515 .field("pooled", &self.recycler.is_some())
2516 .finish_non_exhaustive()
2517 }
2518}
2519
2520impl<T> HostData<T> {
2521 fn new(data: Vec<T>) -> Self {
2522 Self {
2523 buffer: StorageBuffer::Host(data),
2524 recycler: None,
2525 }
2526 }
2527
2528 fn into_vec(mut self) -> Vec<T> {
2529 self.recycler = None;
2530 match std::mem::replace(&mut self.buffer, StorageBuffer::Host(Vec::new())) {
2531 StorageBuffer::Host(data) => data,
2532 StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2533 }
2534 }
2535
2536 fn as_slice(&self) -> &[T] {
2537 match &self.buffer {
2538 StorageBuffer::Host(data) => data,
2539 StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2540 }
2541 }
2542
2543 fn as_mut_slice(&mut self) -> &mut [T] {
2544 match &mut self.buffer {
2545 StorageBuffer::Host(data) => data,
2546 StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2547 }
2548 }
2549}
2550
2551impl<T> Drop for HostData<T> {
2552 fn drop(&mut self) {
2553 let Some(recycler) = self.recycler.take().and_then(|weak| weak.upgrade()) else {
2554 return;
2555 };
2556 if let StorageBuffer::Host(data) = &mut self.buffer {
2557 recycler.recycle(std::mem::take(data));
2558 }
2559 }
2560}
2561
2562/// The sole owner handle for host tensors. The allocation group owns the
2563/// provider root; the descriptor slot carries only the logical view metadata.
2564pub(crate) struct OwnedTensorGroup<R: TensorRank> {
2565 group: AllocationGroup,
2566 slot: DescriptorSlot,
2567 allocation_index: AllocationSlot,
2568 // INVARIANT: this non-owning address points into the group root, whose host
2569 // vector cannot resize while the owning tensor is borrowed. It is stored as
2570 // `NonZeroUsize` rather than `NonNull<u8>`: the niche keeps the field at one
2571 // word, and unlike `NonNull` it preserves the `Send`/`Sync` auto traits the
2572 // typed tensor contract requires.
2573 host_ptr: Option<NonZeroUsize>,
2574 host_byte_len: usize,
2575 _rank: PhantomData<R>,
2576}
2577
2578impl<R: TensorRank> Debug for OwnedTensorGroup<R> {
2579 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2580 formatter
2581 .debug_struct("OwnedTensorGroup")
2582 .field("slot", &self.slot)
2583 .finish_non_exhaustive()
2584 }
2585}
2586
2587impl<R: TensorRank> OwnedTensorGroup<R> {
2588 fn from_host_vec<T: TensorScalar>(shape: R::Shape, data: Vec<T>) -> crate::Result<Self> {
2589 let (group, slot) = AllocationGroup::from_host_vec::<T, R>(shape, data)
2590 .map_err(|error| group_error("TypedTensor::from_host_vec", error))?;
2591 let allocation_index = group
2592 .allocation_index(slot)
2593 .map_err(|error| group_error("TypedTensor::from_host_vec", error))?;
2594 let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
2595 Ok(Self {
2596 group,
2597 slot,
2598 allocation_index,
2599 host_ptr,
2600 host_byte_len,
2601 _rank: PhantomData,
2602 })
2603 }
2604
2605 fn from_backend_buffer<T: TensorScalar + Send + Sync + 'static>(
2606 shape: R::Shape,
2607 buffer: StorageBuffer<T>,
2608 placement: Placement,
2609 ) -> crate::Result<Self> {
2610 let (mut group, slot) = AllocationGroup::from_backend_buffer::<T, R>(shape, buffer)
2611 .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2612 group
2613 .set_descriptor_placement(slot, placement)
2614 .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2615 let allocation_index = group
2616 .allocation_index(slot)
2617 .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2618 let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
2619 Ok(Self {
2620 group,
2621 slot,
2622 allocation_index,
2623 host_ptr,
2624 host_byte_len,
2625 _rank: PhantomData,
2626 })
2627 }
2628
2629 fn view<T: TensorScalar>(&self) -> crate::Result<GroupReadView<'_, T, R>> {
2630 self.group
2631 .view(self.slot)
2632 .map_err(|error| group_error("TypedTensor::group_view", error))
2633 }
2634
2635 fn view_dyn<T: TensorScalar>(&self) -> crate::Result<GroupReadView<'_, T, DynRank>> {
2636 self.group
2637 .view(self.slot)
2638 .map_err(|error| group_error("TypedTensor::group_view", error))
2639 }
2640
2641 fn view_mut<T: TensorScalar>(&mut self) -> crate::Result<GroupWriteView<'_, T, R>> {
2642 self.group
2643 .view_mut(self.slot)
2644 .map_err(|error| group_error("TypedTensor::group_view_mut", error))
2645 }
2646
2647 fn view_mut_dyn<T: TensorScalar>(&mut self) -> crate::Result<GroupWriteView<'_, T, DynRank>> {
2648 self.group
2649 .view_mut(self.slot)
2650 .map_err(|error| group_error("TypedTensor::group_view_mut", error))
2651 }
2652
2653 fn prepare_device_read_for_layout<T: TensorScalar>(
2654 &self,
2655 layout: &TensorLayout<R>,
2656 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>> {
2657 self.group
2658 .prepare_device_read_for_layout::<T, R>(self.slot, layout)
2659 .map_err(|error| {
2660 crate::Error::runtime_state_source("TypedTensor::prepare_device_read", error)
2661 })
2662 }
2663
2664 fn prepare_device_write_for_layout<T: TensorScalar>(
2665 &mut self,
2666 layout: &TensorLayout<R>,
2667 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>> {
2668 self.group
2669 .prepare_device_write_for_layout::<T, R>(self.slot, layout)
2670 .map_err(|error| {
2671 crate::Error::runtime_state_source("TypedTensor::prepare_device_write", error)
2672 })
2673 }
2674
2675 fn host_buffer<T: 'static>(&self) -> Option<&StorageBuffer<T>> {
2676 self.group.host_buffer_at::<T>(self.allocation_index)
2677 }
2678
2679 fn host_slice<T>(&self) -> crate::Result<&[T]> {
2680 let Some(pointer) = self.host_ptr else {
2681 return Err(crate::Error::runtime_state(
2682 "TypedTensor::host_data",
2683 "backend storage cannot be borrowed as host data; download explicitly first",
2684 ));
2685 };
2686 let element_size = size_of::<T>();
2687 let Some(element_count) = self.host_byte_len.checked_div(element_size) else {
2688 return Err(crate::Error::runtime_state(
2689 "TypedTensor::host_data",
2690 "host allocation byte length is not aligned to the requested dtype",
2691 ));
2692 };
2693 // SAFETY: the pointer and byte length were captured from the unique
2694 // root's full host allocation; the root cannot resize while borrowed.
2695 Ok(unsafe { std::slice::from_raw_parts(pointer.get() as *const T, element_count) })
2696 }
2697
2698 fn host_slice_mut<T>(&mut self) -> crate::Result<&mut [T]> {
2699 let Some(pointer) = self.host_ptr else {
2700 return Err(crate::Error::runtime_state(
2701 "TypedTensor::host_data_mut",
2702 "backend storage cannot be borrowed as host data; download explicitly first",
2703 ));
2704 };
2705 let element_size = size_of::<T>();
2706 let Some(element_count) = self.host_byte_len.checked_div(element_size) else {
2707 return Err(crate::Error::runtime_state(
2708 "TypedTensor::host_data_mut",
2709 "host allocation byte length is not aligned to the requested dtype",
2710 ));
2711 };
2712 // SAFETY: the pointer and byte length were captured from the unique
2713 // root; this method has the only mutable borrow of that root.
2714 Ok(unsafe { std::slice::from_raw_parts_mut(pointer.get() as *mut T, element_count) })
2715 }
2716
2717 fn backend_buffer<T: 'static>(&self) -> Option<&StorageBuffer<T>> {
2718 self.group.backend_buffer::<T>(self.slot)
2719 }
2720
2721 fn backend_buffer_mut<T: 'static>(&mut self) -> Option<&mut StorageBuffer<T>> {
2722 self.group.backend_buffer_mut::<T>(self.slot)
2723 }
2724
2725 // INVARIANT: the failure carrier must return the unchanged group owner, so
2726 // the wide `(Self, Error)` pair is deliberate rather than a boxing bug.
2727 #[allow(clippy::result_large_err)]
2728 fn into_host_vec<T: 'static>(self) -> std::result::Result<Vec<T>, (Self, crate::Error)> {
2729 if self.backend_buffer::<T>().is_some() {
2730 return Err((
2731 self,
2732 crate::Error::runtime_state(
2733 "TypedTensor::into_host_vec",
2734 "backend buffers cannot be exported as host Vec; download the tensor first",
2735 ),
2736 ));
2737 }
2738 let OwnedTensorGroup {
2739 group,
2740 slot,
2741 allocation_index,
2742 host_ptr,
2743 host_byte_len,
2744 ..
2745 } = self;
2746 match group.into_host_vec::<T>(slot) {
2747 Ok(data) => Ok(data),
2748 Err((group, error)) => Err((
2749 OwnedTensorGroup {
2750 group,
2751 slot,
2752 allocation_index,
2753 host_ptr,
2754 host_byte_len,
2755 _rank: PhantomData,
2756 },
2757 crate::Error::runtime_state("TypedTensor::into_host_vec", error),
2758 )),
2759 }
2760 }
2761
2762 fn into_parts(self) -> (AllocationGroup, DescriptorSlot) {
2763 (self.group, self.slot)
2764 }
2765
2766 #[allow(clippy::result_large_err)]
2767 fn reinterpret<T: TensorScalar, U: TensorScalar>(
2768 self,
2769 shape: Vec<usize>,
2770 strides: Vec<isize>,
2771 offset: isize,
2772 ) -> Result<OwnedTensorGroup<DynRank>, (Self, crate::Error)> {
2773 let OwnedTensorGroup {
2774 group,
2775 slot,
2776 allocation_index,
2777 host_ptr,
2778 host_byte_len,
2779 _rank: _,
2780 } = self;
2781 match group.reinterpret_descriptor::<T, U>(slot, shape, strides, offset) {
2782 Ok(group) => Ok(OwnedTensorGroup {
2783 group,
2784 slot,
2785 allocation_index,
2786 host_ptr,
2787 host_byte_len,
2788 _rank: PhantomData,
2789 }),
2790 Err((group, error)) => Err((
2791 OwnedTensorGroup {
2792 group,
2793 slot,
2794 allocation_index,
2795 host_ptr,
2796 host_byte_len,
2797 _rank: PhantomData,
2798 },
2799 group_error("TypedTensor::reinterpret", error),
2800 )),
2801 }
2802 }
2803}
2804
2805fn host_metadata<T: TensorScalar>(
2806 group: &AllocationGroup,
2807 slot: DescriptorSlot,
2808) -> (Option<NonZeroUsize>, usize) {
2809 group
2810 .host_root_metadata::<T>(slot)
2811 .map_or((None, 0), |(pointer, byte_len)| {
2812 // INVARIANT: `host_root_metadata` reports the base address of the group's live
2813 // host allocation, which is non-null for any slice, empty included.
2814 (NonZeroUsize::new(pointer), byte_len)
2815 })
2816}
2817
2818fn group_error(op: &'static str, error: GroupError) -> crate::Error {
2819 crate::Error::runtime_state(op, error.to_string())
2820}
2821
2822/// Borrowed tensor buffer reference used by read-only typed views.
2823///
2824/// # Examples
2825///
2826/// ```rust
2827/// use tenferro_tensor::TensorStorageRef;
2828///
2829/// let data = [1_i32, 2];
2830/// let buffer = TensorStorageRef::Host(&data);
2831/// assert_eq!(buffer.len(), 2);
2832/// ```
2833#[derive(Debug)]
2834pub enum TensorStorageRef<'a, T> {
2835 Host(&'a [T]),
2836 Backend(&'a dyn BackendStorage<T>),
2837 #[doc(hidden)]
2838 Root(&'a dyn BackendAllocation),
2839}
2840
2841impl<T> Clone for TensorStorageRef<'_, T> {
2842 fn clone(&self) -> Self {
2843 match self {
2844 Self::Host(data) => Self::Host(data),
2845 Self::Backend(buffer) => Self::Backend(*buffer),
2846 Self::Root(allocation) => Self::Root(*allocation),
2847 }
2848 }
2849}
2850
2851impl<T: 'static> TensorStorageRef<'_, T> {
2852 /// Return the logical length of the backing allocation.
2853 ///
2854 /// # Examples
2855 ///
2856 /// ```rust
2857 /// use tenferro_tensor::TensorStorageRef;
2858 ///
2859 /// let data = [1_i32, 2, 3];
2860 /// assert_eq!(TensorStorageRef::Host(&data).len(), 3);
2861 /// ```
2862 pub fn len(&self) -> usize {
2863 match self {
2864 Self::Host(data) => data.len(),
2865 Self::Backend(buffer) => buffer.len(),
2866 Self::Root(allocation) => allocation
2867 .root_extent()
2868 .byte_len()
2869 .checked_div(std::mem::size_of::<T>())
2870 .unwrap_or(0),
2871 }
2872 }
2873
2874 /// Return whether the backing allocation is empty.
2875 ///
2876 /// # Examples
2877 ///
2878 /// ```rust
2879 /// use tenferro_tensor::TensorStorageRef;
2880 ///
2881 /// let data: [f64; 0] = [];
2882 /// assert!(TensorStorageRef::Host(&data).is_empty());
2883 /// ```
2884 pub fn is_empty(&self) -> bool {
2885 self.len() == 0
2886 }
2887}
2888
2889/// Borrowed tensor buffer reference used by mutable typed views.
2890///
2891/// Backend buffers can be represented for residency metadata, but this crate
2892/// does not expose host mutation for backend-native allocations.
2893///
2894/// # Examples
2895///
2896/// ```rust
2897/// use tenferro_tensor::TensorStorageRefMut;
2898///
2899/// let mut data = [1_i32, 2];
2900/// let buffer = TensorStorageRefMut::Host(&mut data);
2901/// assert_eq!(buffer.len(), 2);
2902/// ```
2903#[derive(Debug)]
2904pub enum TensorStorageRefMut<'a, T> {
2905 Host(&'a mut [T]),
2906 Backend(&'a mut dyn BackendStorage<T>),
2907}
2908
2909impl<T: 'static> TensorStorageRefMut<'_, T> {
2910 /// Return the logical length of the backing allocation.
2911 ///
2912 /// # Examples
2913 ///
2914 /// ```rust
2915 /// use tenferro_tensor::TensorStorageRefMut;
2916 ///
2917 /// let mut data = [1_i32, 2, 3];
2918 /// assert_eq!(TensorStorageRefMut::Host(&mut data).len(), 3);
2919 /// ```
2920 pub fn len(&self) -> usize {
2921 match self {
2922 Self::Host(data) => data.len(),
2923 Self::Backend(buffer) => buffer.len(),
2924 }
2925 }
2926
2927 /// Return whether the backing allocation is empty.
2928 ///
2929 /// # Examples
2930 ///
2931 /// ```rust
2932 /// use tenferro_tensor::TensorStorageRefMut;
2933 ///
2934 /// let mut data: [f64; 0] = [];
2935 /// assert!(TensorStorageRefMut::Host(&mut data).is_empty());
2936 /// ```
2937 pub fn is_empty(&self) -> bool {
2938 self.len() == 0
2939 }
2940}
2941
2942/// Read-only borrowed view of typed tensor storage with arbitrary strides.
2943///
2944/// `TypedTensorView` is the typed representation for layout-only tensor
2945/// transformations. It borrows an existing host or backend allocation and
2946/// carries a logical shape, strides, and an offset. Slicing, reshaping when
2947/// stride-compatible, and [`transpose_view`](TypedTensorView::transpose_view)
2948/// update only metadata and do not copy storage.
2949///
2950/// Materialize through [`TensorStructural::to_contiguous_read`](crate::TensorStructural::to_contiguous_read)
2951/// on the active backend session when a compact owned [`TypedTensor`] is
2952/// required. Use [`TypedTensorView::as_slice`] only when the current view is
2953/// contiguous in the requested layout.
2954///
2955/// # Examples
2956///
2957/// ```rust
2958/// use tenferro_tensor::{Rank, TypedTensorView};
2959///
2960/// let data = [1_i32, 2, 3, 4];
2961/// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
2962/// assert_eq!(view.get(&[1, 1]), Some(&4));
2963/// # Ok::<(), tenferro_tensor::Error>(())
2964/// ```
2965/// Borrowed typed view of one tensor representation.
2966///
2967/// The buffer stays concrete on purpose: a generic-associated buffer would make
2968/// the projection invariant in `'a`, and the borrowed-read surface relies on
2969/// `TensorRead<'long>` shortening to `TensorRead<'short>`. `D` therefore marks
2970/// which representation produced the view - `Host` is only ever built from host
2971/// slices, with no retained region.
2972pub struct TypedTensorView<'a, T, R: TensorRank = DynRank, D: Representation = Dynamic> {
2973 buffer: TensorStorageRef<'a, T>,
2974 root: Option<GroupReadView<'a, T, R>>,
2975 layout: TensorLayout<R>,
2976 placement: Placement,
2977 _representation: std::marker::PhantomData<D>,
2978}
2979
2980impl<'a, T, R: TensorRank, D: Representation> Clone for TypedTensorView<'a, T, R, D> {
2981 fn clone(&self) -> Self {
2982 Self {
2983 buffer: self.buffer.clone(),
2984 root: self.root.clone(),
2985 layout: self.layout.clone(),
2986 placement: self.placement.clone(),
2987 _representation: std::marker::PhantomData,
2988 }
2989 }
2990}
2991
2992impl<'a, T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensorView<'a, T, R, D> {
2993 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2994 formatter
2995 .debug_struct("TypedTensorView")
2996 .field("shape", &self.layout.shape())
2997 .field("placement", &self.placement)
2998 .finish_non_exhaustive()
2999 }
3000}
3001
3002impl<'a, T: 'static> TypedTensorView<'a, T, DynRank> {
3003 /// Create a borrowed dynamic-rank view over compact column-major host data.
3004 ///
3005 /// # Examples
3006 ///
3007 /// ```rust
3008 /// use tenferro_tensor::TypedTensorView;
3009 ///
3010 /// let data = [1_i32, 2, 3, 4];
3011 /// let view = TypedTensorView::from_col_major(&[2, 2], &data)?;
3012 /// assert_eq!(view.strides(), &[1, 2]);
3013 /// # Ok::<(), tenferro_tensor::Error>(())
3014 /// ```
3015 ///
3016 /// # Errors
3017 ///
3018 /// Returns [`crate::Error::Validation`] with
3019 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when compact
3020 /// strides or reachable bounds overflow, or
3021 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3022 /// requested shape reaches beyond `data`.
3023 pub fn from_col_major(shape: &[usize], data: &'a [T]) -> crate::Result<Self> {
3024 let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
3025 .map_err(|err| tensor_layout_error("TypedTensorView::from_col_major", err))?;
3026 Self::from_buffer_ref(
3027 shape_vec(layout.shape()),
3028 stride_vec(layout.strides()),
3029 layout.offset(),
3030 TensorStorageRef::Host(data),
3031 default_placement(),
3032 "TypedTensorView::from_col_major",
3033 )
3034 }
3035
3036 /// Create a borrowed host view from explicit layout metadata.
3037 ///
3038 /// # Examples
3039 ///
3040 /// ```rust
3041 /// use tenferro_tensor::TypedTensorView;
3042 ///
3043 /// let data = [1_i32, 2, 3];
3044 /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
3045 /// assert_eq!(view.get(&[2]), Some(&1));
3046 /// # Ok::<(), tenferro_tensor::Error>(())
3047 /// ```
3048 ///
3049 /// # Errors
3050 ///
3051 /// Returns [`crate::Error::Validation`] with
3052 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
3053 /// `strides` have different ranks,
3054 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3055 /// reachable layout exceeds `data`, or
3056 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
3057 /// arithmetic overflows.
3058 pub fn from_slice(
3059 shape: impl AsRef<[usize]>,
3060 strides: impl AsRef<[isize]>,
3061 offset: isize,
3062 data: &'a [T],
3063 ) -> crate::Result<Self> {
3064 Self::from_buffer_ref(
3065 shape_vec(shape.as_ref()),
3066 stride_vec(strides.as_ref()),
3067 offset,
3068 TensorStorageRef::Host(data),
3069 default_placement(),
3070 "TypedTensorView::from_slice",
3071 )
3072 }
3073}
3074
3075impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
3076 /// Create a rank-generic borrowed host view from explicit layout metadata.
3077 ///
3078 /// # Examples
3079 ///
3080 /// ```rust
3081 /// use tenferro_tensor::{Rank, TypedTensorView};
3082 ///
3083 /// let data = [1_i32, 2, 3, 4];
3084 /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
3085 /// assert_eq!(view.get(&[1, 1]), Some(&4));
3086 /// # Ok::<(), tenferro_tensor::Error>(())
3087 /// ```
3088 ///
3089 /// # Errors
3090 ///
3091 /// Returns [`crate::Error::Validation`] with
3092 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
3093 /// rank does not match `shape` or `strides`,
3094 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3095 /// reachable layout exceeds `data`, or
3096 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
3097 /// arithmetic overflows.
3098 pub fn from_slice_ranked(
3099 shape: impl Into<R::Shape>,
3100 strides: impl Into<R::Strides>,
3101 offset: isize,
3102 data: &'a [T],
3103 ) -> crate::Result<Self> {
3104 Self::from_buffer_ref(
3105 shape,
3106 strides,
3107 offset,
3108 TensorStorageRef::Host(data),
3109 default_placement(),
3110 "TypedTensorView::from_slice_ranked",
3111 )
3112 }
3113}
3114
3115impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
3116 fn from_buffer_ref(
3117 shape: impl Into<R::Shape>,
3118 strides: impl Into<R::Strides>,
3119 offset: isize,
3120 buffer: TensorStorageRef<'a, T>,
3121 placement: Placement,
3122 op: &'static str,
3123 ) -> crate::Result<Self> {
3124 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
3125 .map_err(|err| tensor_layout_error(op, err))?;
3126 Ok(Self {
3127 buffer,
3128 root: None,
3129 layout,
3130 placement,
3131 _representation: std::marker::PhantomData,
3132 })
3133 }
3134}
3135
3136impl<'a, T: 'static, R: TensorRank> TypedTensorView<'a, T, R, Host> {
3137 /// Create a representation-marked view over an explicit host layout.
3138 ///
3139 /// A `Host`-marked view is only ever built from a host slice, so it carries
3140 /// no retained region and its host slice needs no runtime check.
3141 ///
3142 /// # Examples
3143 ///
3144 /// ```rust
3145 /// use tenferro_tensor::{DynRank, Host, TypedTensorView};
3146 ///
3147 /// let data = [1_i32, 2, 3, 4];
3148 /// let view: TypedTensorView<'_, i32, DynRank, Host> =
3149 /// TypedTensorView::from_host_slice(vec![2, 2].as_slice(), vec![1, 2].as_slice(), 0, &data)?;
3150 /// assert_eq!(view.as_host_slice(), &[1, 2, 3, 4]);
3151 /// # Ok::<(), tenferro_tensor::Error>(())
3152 /// ```
3153 ///
3154 /// # Errors
3155 ///
3156 /// Returns [`crate::Error::Validation`] with
3157 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `strides`
3158 /// has a different rank, or
3159 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] /
3160 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
3161 /// reachable layout leaves `data` or overflows.
3162 pub fn from_host_slice(
3163 shape: impl Into<R::Shape>,
3164 strides: impl Into<R::Strides>,
3165 offset: isize,
3166 data: &'a [T],
3167 ) -> crate::Result<Self> {
3168 // Built here rather than through the shared helper so that the Host
3169 // marker can only ever be paired with a host slice.
3170 let buffer = TensorStorageRef::Host(data);
3171 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
3172 .map_err(|err| tensor_layout_error("TypedTensorView::from_host_slice", err))?;
3173 Ok(Self {
3174 buffer,
3175 root: None,
3176 layout,
3177 placement: default_placement(),
3178 _representation: std::marker::PhantomData,
3179 })
3180 }
3181
3182 /// Borrow the host elements without a runtime representation check.
3183 ///
3184 /// # Examples
3185 ///
3186 /// ```rust
3187 /// use tenferro_tensor::{DynRank, Host, TypedTensorView};
3188 ///
3189 /// let data = [1_i32, 2];
3190 /// let view: TypedTensorView<'_, i32, DynRank, Host> =
3191 /// TypedTensorView::from_host_slice(vec![2].as_slice(), vec![1].as_slice(), 0, &data)?;
3192 /// assert_eq!(view.as_host_slice(), &[1, 2]);
3193 /// # Ok::<(), tenferro_tensor::Error>(())
3194 /// ```
3195 ///
3196 /// # Panics
3197 ///
3198 /// Panics only if a `Host`-marked view was built from non-host storage,
3199 /// which no constructor in this crate does.
3200 pub fn as_host_slice(&self) -> &'a [T] {
3201 match &self.buffer {
3202 TensorStorageRef::Host(data) => data,
3203 // INVARIANT: `Host`-marked views are constructed only by
3204 // `from_host_slice` and by `TypedTensor<_, _, Host>::as_view`.
3205 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3206 unreachable!("a Host-marked view always borrows host storage")
3207 }
3208 }
3209 }
3210}
3211
3212impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
3213 /// Erase a borrowed view's rank and dtype for session dispatch without
3214 /// allocating tensor storage or promoting it into an allocation group.
3215 /// Common ranks use the existing inline shape and stride metadata.
3216 ///
3217 /// # Examples
3218 ///
3219 /// ```rust
3220 /// use tenferro_tensor::{Rank, TypedTensorView};
3221 /// let data = [1.0_f64, 2.0];
3222 /// let view = TypedTensorView::<_, Rank<1>>::from_slice_ranked([2], [1], 0, &data)?;
3223 /// let read = view.into_tensor_read()?;
3224 /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
3225 /// # Ok::<(), tenferro_tensor::Error>(())
3226 /// ```
3227 /// # Errors
3228 /// Returns [`crate::Error::Validation`] with
3229 /// [`ValidationError::IntegerOverflow`] for unrepresentable metadata or
3230 /// [`ValidationError::ViewOutOfBounds`] if the layout leaves its allocation.
3231 pub fn into_tensor_read(self) -> crate::Result<TensorRead<'a>>
3232 where
3233 T: TensorScalar,
3234 {
3235 let layout = TensorLayout::<DynRank>::from_parts(
3236 shape_vec(self.layout.shape()),
3237 stride_vec(self.layout.strides()),
3238 self.layout.offset(),
3239 self.buffer.len(),
3240 )
3241 .map_err(|err| tensor_layout_error("TypedTensorView::into_tensor_read", err))?;
3242 let view = TypedTensorView {
3243 buffer: self.buffer,
3244 root: self.root.map(GroupReadView::into_dyn),
3245 layout,
3246 placement: self.placement,
3247 _representation: std::marker::PhantomData,
3248 };
3249 Ok(TensorRead::from_view(T::tensor_view(view)))
3250 }
3251
3252 /// Return the logical shape.
3253 ///
3254 /// # Examples
3255 ///
3256 /// ```rust
3257 /// use tenferro_tensor::TypedTensorView;
3258 ///
3259 /// let data = [0_i32; 2];
3260 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3261 /// assert_eq!(view.shape(), &[2]);
3262 /// # Ok::<(), tenferro_tensor::Error>(())
3263 /// ```
3264 pub fn shape(&self) -> &[usize] {
3265 self.layout.shape()
3266 }
3267
3268 /// Return the logical rank carried by this view.
3269 ///
3270 /// # Examples
3271 ///
3272 /// ```
3273 /// use tenferro_tensor::TypedTensorView;
3274 ///
3275 /// let data = [0_i32; 6];
3276 /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
3277 /// assert_eq!(view.rank(), 2);
3278 /// # Ok::<(), tenferro_tensor::Error>(())
3279 /// ```
3280 pub fn rank(&self) -> usize {
3281 self.shape().len()
3282 }
3283
3284 /// Return strides in element units.
3285 ///
3286 /// # Examples
3287 ///
3288 /// ```rust
3289 /// use tenferro_tensor::TypedTensorView;
3290 ///
3291 /// let data = [0_i32; 2];
3292 /// let view = TypedTensorView::from_slice(vec![2], vec![-1], 1, &data)?;
3293 /// assert_eq!(view.strides(), &[-1]);
3294 /// # Ok::<(), tenferro_tensor::Error>(())
3295 /// ```
3296 pub fn strides(&self) -> &[isize] {
3297 self.layout.strides()
3298 }
3299
3300 /// Return the physical element offset.
3301 ///
3302 /// # Examples
3303 ///
3304 /// ```rust
3305 /// use tenferro_tensor::TypedTensorView;
3306 ///
3307 /// let data = [1_i32, 2];
3308 /// let view = TypedTensorView::from_slice(vec![1], vec![1], 1, &data)?;
3309 /// assert_eq!(view.offset(), 1);
3310 /// # Ok::<(), tenferro_tensor::Error>(())
3311 /// ```
3312 pub fn offset(&self) -> isize {
3313 self.layout.offset()
3314 }
3315
3316 /// Return the borrowed host storage backing this view.
3317 ///
3318 /// This exposes the entire backing host allocation, not just the logical
3319 /// slice covered by this view. Use [`TypedTensorView::as_slice`] when the
3320 /// caller needs the contiguous logical region instead.
3321 ///
3322 /// # Examples
3323 ///
3324 /// ```rust
3325 /// use tenferro_tensor::TypedTensorView;
3326 ///
3327 /// let data = [1_i32, 2];
3328 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3329 /// assert_eq!(view.host_storage()?, &[1, 2]);
3330 /// # Ok::<(), tenferro_tensor::Error>(())
3331 /// ```
3332 ///
3333 /// # Errors
3334 ///
3335 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
3336 /// buffer; backend storage must be downloaded before host inspection.
3337 pub fn host_storage(&self) -> crate::Result<&'a [T]> {
3338 match &self.buffer {
3339 TensorStorageRef::Host(data) => Ok(data),
3340 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3341 Err(crate::Error::runtime_state(
3342 "TypedTensorView::host_storage",
3343 "backend buffers cannot expose host storage; download explicitly first",
3344 ))
3345 }
3346 }
3347 }
3348
3349 /// Return the number of logical elements in this view.
3350 ///
3351 /// # Examples
3352 ///
3353 /// ```rust
3354 /// use tenferro_tensor::TypedTensorView;
3355 ///
3356 /// let data = [0_i32; 6];
3357 /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
3358 /// assert_eq!(view.n_elements(), 6);
3359 /// # Ok::<(), tenferro_tensor::Error>(())
3360 /// ```
3361 pub fn n_elements(&self) -> usize {
3362 // Invariant: public view constructors validate logical element count.
3363 match checked_view_element_count(self.shape(), "TypedTensorView::n_elements") {
3364 Ok(n) => n,
3365 Err(err) => {
3366 unreachable!("TypedTensorView layout shape is validated at construction: {err}")
3367 }
3368 }
3369 }
3370
3371 /// Return layout metadata for this view.
3372 ///
3373 /// # Examples
3374 ///
3375 /// ```rust
3376 /// use tenferro_tensor::TypedTensorView;
3377 ///
3378 /// let data = [1_i32, 2];
3379 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3380 /// assert!(view.layout().is_compact_col_major().unwrap());
3381 /// # Ok::<(), tenferro_tensor::Error>(())
3382 /// ```
3383 pub fn layout(&self) -> &TensorLayout<R> {
3384 &self.layout
3385 }
3386
3387 /// Return placement metadata for this view.
3388 ///
3389 /// # Examples
3390 ///
3391 /// ```rust
3392 /// use tenferro_tensor::{MemoryKind, TypedTensorView};
3393 ///
3394 /// let data = [1_i32];
3395 /// let view = TypedTensorView::from_slice(vec![1], vec![1], 0, &data)?;
3396 /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
3397 /// # Ok::<(), tenferro_tensor::Error>(())
3398 /// ```
3399 pub fn placement(&self) -> &Placement {
3400 &self.placement
3401 }
3402
3403 /// Return the backend allocation for backend integrations.
3404 #[doc(hidden)]
3405 pub fn backing_len(&self) -> usize {
3406 self.buffer.len()
3407 }
3408
3409 /// Return the backend allocation for backend integrations.
3410 #[doc(hidden)]
3411 pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>> {
3412 match &self.buffer {
3413 TensorStorageRef::Host(_) => None,
3414 TensorStorageRef::Backend(buffer) => Some(*buffer),
3415 TensorStorageRef::Root(_) => {
3416 self.root
3417 .as_ref()?
3418 .backend_buffer()
3419 .and_then(|buffer| match buffer {
3420 StorageBuffer::Host(_) => None,
3421 StorageBuffer::Backend(buffer) => Some(buffer.as_ref()),
3422 })
3423 }
3424 }
3425 }
3426
3427 /// Return the provider family for this view when it is backend-owned.
3428 #[doc(hidden)]
3429 pub fn backend_family(&self) -> Option<&'static str>
3430 where
3431 T: TensorScalar + 'static,
3432 {
3433 self.root
3434 .as_ref()
3435 .and_then(|root| {
3436 root.backend_allocation()
3437 .map(|_| root.provider_kind().as_str())
3438 })
3439 .or_else(|| self.backend_buffer().map(|buffer| buffer.backend_family()))
3440 }
3441
3442 /// Return the shared allocation domain for this view when backend-owned.
3443 #[doc(hidden)]
3444 pub fn allocation_domain(&self) -> Option<AllocationDomainId>
3445 where
3446 T: TensorScalar + 'static,
3447 {
3448 self.root
3449 .as_ref()
3450 .and_then(|root| root.backend_identity().map(|(domain, _)| domain))
3451 .or_else(|| {
3452 self.backend_buffer()
3453 .and_then(|buffer| buffer.allocation_domain())
3454 })
3455 }
3456
3457 /// Return the physical allocation identity for this view when backend-owned.
3458 #[doc(hidden)]
3459 pub fn allocation_id(&self) -> Option<AllocationId>
3460 where
3461 T: TensorScalar + 'static,
3462 {
3463 self.root
3464 .as_ref()
3465 .and_then(|root| root.backend_identity().map(|(_, id)| id))
3466 .or_else(|| {
3467 self.backend_buffer()
3468 .and_then(|buffer| buffer.allocation_id())
3469 })
3470 }
3471
3472 /// Prepare this backend view for one provider-native read binding.
3473 #[doc(hidden)]
3474 pub fn prepare_device_read(
3475 &self,
3476 op: &'static str,
3477 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
3478 where
3479 T: TensorScalar + 'static,
3480 {
3481 if let Some(root) = &self.root {
3482 return root
3483 .prepare_device_read_for_layout(&self.layout)
3484 .map_err(|error| crate::Error::runtime_state_source(op, error));
3485 }
3486 let buffer = self.backend_buffer().ok_or_else(|| {
3487 crate::Error::runtime_state_source(
3488 op,
3489 crate::AccessError::Unsupported { backend: "host" },
3490 )
3491 })?;
3492 prepare_backend_access(buffer, &self.layout, op)
3493 }
3494
3495 /// Compute the physical element offset for a logical index.
3496 ///
3497 /// # Examples
3498 ///
3499 /// ```rust
3500 /// use tenferro_tensor::TypedTensorView;
3501 ///
3502 /// let data = [1_i32, 2, 3];
3503 /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
3504 /// assert_eq!(view.linear_offset(&[2]), Some(0));
3505 /// # Ok::<(), tenferro_tensor::Error>(())
3506 /// ```
3507 pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
3508 checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
3509 }
3510
3511 /// Compute the physical element offset for a logical index, returning a typed error.
3512 ///
3513 /// # Examples
3514 ///
3515 /// ```rust
3516 /// use tenferro_tensor::TypedTensorView;
3517 ///
3518 /// let data = [1_i32, 2, 3];
3519 /// let view = TypedTensorView::from_slice([3], [-1], 2, &data)?;
3520 /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
3521 /// # Ok::<(), tenferro_tensor::Error>(())
3522 /// ```
3523 ///
3524 /// # Errors
3525 ///
3526 /// Returns [`crate::Error::Validation`] with
3527 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
3528 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
3529 /// when an index is outside its axis extent, or
3530 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
3531 /// arithmetic overflows.
3532 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
3533 checked_view_offset_result(
3534 self.shape(),
3535 self.strides(),
3536 self.offset(),
3537 indices,
3538 "TypedTensorView::layout_linear_offset",
3539 )
3540 }
3541
3542 /// Return whether this view is compact column-major.
3543 ///
3544 /// # Examples
3545 ///
3546 /// ```rust
3547 /// use tenferro_tensor::TypedTensorView;
3548 ///
3549 /// let data = [1_i32, 2];
3550 /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3551 /// assert!(view.is_col_major_contiguous()?);
3552 /// # Ok::<(), tenferro_tensor::Error>(())
3553 /// ```
3554 ///
3555 /// # Errors
3556 ///
3557 /// Returns [`crate::Error::Validation`] with
3558 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] if compactness
3559 /// arithmetic overflows.
3560 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
3561 self.layout
3562 .is_compact_col_major()
3563 .map_err(|err| tensor_layout_error("TypedTensorView::is_col_major_contiguous", err))
3564 }
3565
3566 /// Return a compact string summary of this view's layout metadata.
3567 ///
3568 /// # Examples
3569 ///
3570 /// ```rust
3571 /// use tenferro_tensor::TypedTensorView;
3572 ///
3573 /// let data = [1_i32, 2];
3574 /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3575 /// assert!(view.layout_summary().contains("shape=[2]"));
3576 /// # Ok::<(), tenferro_tensor::Error>(())
3577 /// ```
3578 pub fn layout_summary(&self) -> String {
3579 layout_summary(self.shape(), self.strides(), self.offset())
3580 }
3581
3582 /// Assert this view is compact column-major.
3583 ///
3584 /// # Examples
3585 ///
3586 /// ```rust
3587 /// use tenferro_tensor::TypedTensorView;
3588 ///
3589 /// let data = [1_i32, 2];
3590 /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3591 /// view.assert_col_major_contiguous()?;
3592 /// # Ok::<(), tenferro_tensor::Error>(())
3593 /// ```
3594 ///
3595 /// # Errors
3596 ///
3597 /// Returns [`crate::Error::Validation`] with
3598 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
3599 /// compactness arithmetic overflows, or
3600 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
3601 /// view is not compact column-major.
3602 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
3603 assert_layout_col_major_contiguous(
3604 self.is_col_major_contiguous()?,
3605 self.shape(),
3606 self.strides(),
3607 self.offset(),
3608 "TypedTensorView::assert_col_major_contiguous",
3609 )
3610 }
3611
3612 /// Borrow one host element by logical index.
3613 ///
3614 /// Returns `None` for out-of-bounds indices and backend buffers.
3615 ///
3616 /// # Examples
3617 ///
3618 /// ```rust
3619 /// use tenferro_tensor::TypedTensorView;
3620 ///
3621 /// let data = [1_i32, 2];
3622 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3623 /// assert_eq!(view.get(&[1]), Some(&2));
3624 /// # Ok::<(), tenferro_tensor::Error>(())
3625 /// ```
3626 pub fn get(&self, indices: &[usize]) -> Option<&T> {
3627 let offset = self.linear_offset(indices)?;
3628 match &self.buffer {
3629 TensorStorageRef::Host(data) => data.get(offset),
3630 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => None,
3631 }
3632 }
3633
3634 /// Borrow the contiguous host slice covered by this view.
3635 ///
3636 /// Returns an explicit error for backend buffers and for non-contiguous
3637 /// layouts. This method never downloads or materializes backend data.
3638 ///
3639 /// # Examples
3640 ///
3641 /// ```rust
3642 /// use tenferro_tensor::TypedTensorView;
3643 ///
3644 /// let data = [1_i32, 2, 3];
3645 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?;
3646 /// assert_eq!(view.as_slice()?, &[2, 3]);
3647 /// # Ok::<(), tenferro_tensor::Error>(())
3648 /// ```
3649 ///
3650 /// # Errors
3651 ///
3652 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
3653 /// buffer, [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]
3654 /// when the layout is not compact column-major,
3655 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the view
3656 /// offset is negative, or [`crate::Error::Validation`] with
3657 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] or
3658 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
3659 /// requested host range is invalid.
3660 pub fn as_slice(&self) -> crate::Result<&'a [T]> {
3661 let data = match &self.buffer {
3662 TensorStorageRef::Host(data) => data,
3663 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3664 return Err(crate::Error::runtime_state(
3665 "TypedTensorView::as_slice",
3666 "backend buffers cannot be inspected as host slices; download explicitly first",
3667 ))
3668 }
3669 };
3670 contiguous_layout_slice(self.layout(), data, "TypedTensorView::as_slice")
3671 }
3672
3673 /// Borrow the compact logical region through a scoped host mapping.
3674 ///
3675 /// Backend integrations must validate placement and allocation-domain ownership
3676 /// before mapping. The guard cannot escape the callback; no transfer is performed.
3677 ///
3678 /// # Errors
3679 ///
3680 /// Returns a host-access or provider-preparation error when mapping fails,
3681 /// or a validation/unsupported error for a non-contiguous or invalid layout.
3682 #[doc(hidden)]
3683 pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
3684 where
3685 T: TensorScalar + 'static,
3686 {
3687 const OP: &str = "TypedTensorView::with_host_read";
3688 if !self.is_col_major_contiguous()? {
3689 return Err(crate::Error::unsupported(
3690 OP,
3691 "host guard access requires a compact descriptor",
3692 ));
3693 }
3694 if let Some(buffer) = self.backend_buffer() {
3695 let guard = buffer
3696 .map_read()
3697 .map_err(|error| crate::Error::host_access(OP, error))?;
3698 return Ok(f(contiguous_layout_slice(self.layout(), &guard, OP)?));
3699 }
3700 if let Some(root) = &self.root {
3701 let prepared = root
3702 .prepare_host_read_for_layout(self.layout())
3703 .map_err(|error| crate::Error::runtime_state_source(OP, error))?;
3704 let slice = prepared.as_slice().ok_or_else(|| {
3705 crate::Error::unsupported(OP, "host guard access requires a compact descriptor")
3706 })?;
3707 return Ok(f(slice));
3708 }
3709 Ok(f(self.as_slice()?))
3710 }
3711
3712 /// Explicitly duplicate a compact host view into a new owner.
3713 ///
3714 /// Backend views require an explicit provider canonicalization or download
3715 /// boundary; this method never transfers or materializes them implicitly.
3716 ///
3717 /// # Examples
3718 ///
3719 /// ```
3720 /// use tenferro_tensor::TypedTensorView;
3721 ///
3722 /// let data = [1_i32, 2];
3723 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3724 /// let copy = view.duplicate()?;
3725 /// assert_eq!(copy.as_slice()?, &[1, 2]);
3726 /// # Ok::<(), tenferro_tensor::Error>(())
3727 /// ```
3728 ///
3729 /// # Errors
3730 ///
3731 /// Returns [`crate::Error::HostAccess`] or
3732 /// [`ValidationError::NonContiguousViewAsSlice`] when the view is backend
3733 /// owned or not contiguous, and [`ValidationError::InvalidArgument`] when
3734 /// the static-rank shape cannot be reconstructed.
3735 pub fn duplicate(&self) -> crate::Result<TypedTensor<T, R>>
3736 where
3737 T: Clone,
3738 {
3739 let data = self.as_slice()?.to_vec();
3740 let shape = R::shape_from_vec(shape_vec(self.shape()))
3741 .map_err(|err| tensor_layout_error("TypedTensorView::duplicate", err))?;
3742 let mut tensor = TypedTensor::from_vec_col_major(shape, data)?;
3743 tensor.set_placement(self.placement.clone());
3744 Ok(tensor)
3745 }
3746
3747 /// Explicitly copy a host view, including strided/offset views, to a compact owner.
3748 ///
3749 /// # Examples
3750 /// ```
3751 /// use tenferro_tensor::{Rank, TypedTensorView};
3752 /// let storage = [1, 4, 2, 5, 3, 6];
3753 /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked(
3754 /// [2, 3], [1, 2], 0, &storage,
3755 /// )?;
3756 /// let transposed = view.transpose_view([1, 0])?;
3757 /// assert_eq!(transposed.to_col_major()?.as_slice()?, &[1, 2, 3, 4, 5, 6]);
3758 /// # Ok::<(), tenferro_tensor::Error>(())
3759 /// ```
3760 /// # Errors
3761 /// Backend-only storage returns [`crate::Error::RuntimeState`]; invalid
3762 /// layout or rank conversion returns [`crate::Error::Validation`].
3763 pub fn to_col_major(&self) -> crate::Result<TypedTensor<T, R>>
3764 where
3765 T: Clone,
3766 {
3767 let data = self.host_storage()?;
3768 let mut coordinates = shape_vec_zeros(self.shape().len());
3769 let element_count = self.n_elements();
3770 let mut copied = Vec::with_capacity(element_count);
3771 for _ in 0..element_count {
3772 let offset = self.layout_linear_offset(&coordinates)?;
3773 let value = data.get(offset).ok_or_else(|| {
3774 crate::Error::validation(
3775 "TypedTensorView::to_col_major",
3776 ValidationError::ViewOutOfBounds,
3777 )
3778 })?;
3779 copied.push(value.clone());
3780 for axis in 0..coordinates.len() {
3781 coordinates[axis] += 1;
3782 if coordinates[axis] < self.shape()[axis] {
3783 break;
3784 }
3785 coordinates[axis] = 0;
3786 }
3787 }
3788 let shape = R::shape_from_vec(shape_vec(self.shape()))
3789 .map_err(|err| tensor_layout_error("TypedTensorView::to_col_major", err))?;
3790 let mut tensor = TypedTensor::from_vec_col_major(shape, copied)?;
3791 tensor.set_placement(self.placement.clone());
3792 Ok(tensor)
3793 }
3794
3795 /// Return a metadata-only axis permutation.
3796 ///
3797 /// # Examples
3798 ///
3799 /// ```rust
3800 /// use tenferro_tensor::{Rank, TypedTensorView};
3801 ///
3802 /// let data = [1_i32, 2, 3, 4, 5, 6];
3803 /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 3], [1, 2], 0, &data)?;
3804 /// let transposed = view.transpose_view([1, 0])?;
3805 /// assert_eq!(transposed.shape(), &[3, 2]);
3806 /// # Ok::<(), tenferro_tensor::Error>(())
3807 /// ```
3808 /// # Errors
3809 ///
3810 /// Returns [`crate::Error::Validation`] with
3811 /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
3812 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
3813 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
3814 /// not a valid permutation of the view rank.
3815 pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
3816 let layout = self
3817 .layout
3818 .transpose_view(axes)
3819 .map_err(|err| tensor_layout_error("TypedTensorView::transpose_view", err))?;
3820 Ok(Self {
3821 buffer: self.buffer.clone(),
3822 root: self.root.clone(),
3823 layout,
3824 placement: self.placement.clone(),
3825 _representation: std::marker::PhantomData,
3826 })
3827 }
3828
3829 /// Return a metadata-only slice using one [`StridedSliceSpec`] per axis.
3830 ///
3831 /// # Examples
3832 ///
3833 /// ```rust
3834 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
3835 ///
3836 /// let data = [1_i32, 2, 3];
3837 /// let view = TypedTensorView::from_slice(vec![3], vec![1], 0, &data)?;
3838 /// let reversed = view.slice_view(&[StridedSliceSpec::reverse()])?;
3839 /// assert_eq!(reversed.get(&[0]), Some(&3));
3840 /// # Ok::<(), tenferro_tensor::Error>(())
3841 /// ```
3842 /// # Errors
3843 ///
3844 /// Returns [`crate::Error::Validation`] with
3845 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
3846 /// count differs from the view rank,
3847 /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
3848 /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
3849 /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3850 /// for slice arithmetic overflow, or
3851 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3852 /// resulting layout exceeds the backing buffer.
3853 pub fn slice_view(&self, slices: &[StridedSliceSpec]) -> crate::Result<Self> {
3854 let specs = core_slice_specs(slices, self.shape(), "TypedTensorView::slice_view")?;
3855 let layout = self
3856 .layout
3857 .slice_view(specs, self.buffer.len())
3858 .map_err(|err| tensor_layout_error("TypedTensorView::slice_view", err))?;
3859 Ok(Self {
3860 buffer: self.buffer.clone(),
3861 root: self.root.clone(),
3862 layout,
3863 placement: self.placement.clone(),
3864 _representation: std::marker::PhantomData,
3865 })
3866 }
3867
3868 /// Return a metadata-only slice along one axis.
3869 ///
3870 /// # Examples
3871 ///
3872 /// ```rust
3873 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
3874 ///
3875 /// let data = [1_i32, 2, 3, 4];
3876 /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
3877 /// assert_eq!(view.slice_axis_view(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
3878 /// # Ok::<(), tenferro_tensor::Error>(())
3879 /// ```
3880 /// # Errors
3881 ///
3882 /// Returns [`crate::Error::Validation`] with
3883 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
3884 /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
3885 /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
3886 /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3887 /// for slice arithmetic overflow, or
3888 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3889 /// resulting layout exceeds the backing buffer.
3890 pub fn slice_axis_view(&self, axis: usize, slice: StridedSliceSpec) -> crate::Result<Self> {
3891 let slices = slice_axis_specs(
3892 self.shape().len(),
3893 axis,
3894 slice,
3895 "TypedTensorView::slice_axis_view",
3896 )?;
3897 self.slice_view(&slices)
3898 }
3899
3900 /// Return a metadata-only dynamic-rank reshape for contiguous column-major views.
3901 ///
3902 /// # Examples
3903 ///
3904 /// ```rust
3905 /// use tenferro_tensor::TypedTensorView;
3906 ///
3907 /// let data = [1_i32, 2, 3, 4];
3908 /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
3909 /// assert_eq!(view.reshape_view(&[4])?.shape(), &[4]);
3910 /// # Ok::<(), tenferro_tensor::Error>(())
3911 /// ```
3912 /// # Errors
3913 ///
3914 /// Returns [`crate::Error::Validation`] with
3915 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
3916 /// the source is not compact column-major,
3917 /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
3918 /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
3919 /// records the counts) when element counts differ,
3920 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
3921 /// arithmetic overflow, or
3922 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3923 /// reshaped view exceeds the backing buffer.
3924 pub fn reshape_view(
3925 &self,
3926 shape: &[usize],
3927 ) -> crate::Result<TypedTensorView<'a, T, DynRank, D>> {
3928 let layout = reshape_layout_dyn(
3929 &self.layout,
3930 shape,
3931 self.buffer.len(),
3932 "TypedTensorView::reshape_view",
3933 )?;
3934 Ok(TypedTensorView {
3935 buffer: self.buffer.clone(),
3936 root: self.root.as_ref().map(GroupReadView::clone_dyn),
3937 layout,
3938 placement: self.placement.clone(),
3939 _representation: std::marker::PhantomData,
3940 })
3941 }
3942}
3943
3944impl<'a, R: TensorRank> TypedTensorView<'a, Complex32, R> {
3945 /// Borrow this complex view as an interleaved real view without copying.
3946 ///
3947 /// The result has dynamic rank because reinterpretation prepends the
3948 /// component axis `[2, ...]`. Only `Complex32 <-> f32` is sealed in this
3949 /// API; this is representation reinterpretation, not numeric conversion.
3950 ///
3951 /// # Examples
3952 ///
3953 /// ```
3954 /// use tenferro_tensor::{Complex32, TypedTensorView};
3955 ///
3956 /// let data = [Complex32::new(1.0, 2.0)];
3957 /// let view = TypedTensorView::from_col_major(&[1], &data)?;
3958 /// let real = view.as_real_view()?;
3959 /// assert_eq!(real.shape(), &[2, 1]);
3960 /// assert_eq!(real.as_slice()?, &[1.0_f32, 2.0]);
3961 /// # Ok::<(), tenferro_tensor::Error>(())
3962 /// ```
3963 ///
3964 /// # Errors
3965 ///
3966 /// Returns an error when the view layout is not a valid sealed
3967 /// representation or when backend reinterpretation is unsupported.
3968 pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'a, f32, DynRank>> {
3969 let op = "TypedTensorView::as_real_view";
3970 validate_representation_pair(op, DType::C32, DType::F32)?;
3971 let layout = reinterpret_complex_to_real_layout(
3972 self.shape(),
3973 self.strides(),
3974 self.offset(),
3975 self.buffer.len(),
3976 op,
3977 )?;
3978 let buffer = match &self.buffer {
3979 TensorStorageRef::Host(data) => {
3980 TensorStorageRef::Host(reinterpret_host_slice::<Complex32, f32>(data, op)?)
3981 }
3982 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3983 return Err(crate::Error::unsupported(
3984 op,
3985 "backend representation reinterpretation is enabled by the provider phases",
3986 ))
3987 }
3988 };
3989 Ok(TypedTensorView {
3990 buffer,
3991 root: None,
3992 layout,
3993 placement: self.placement.clone(),
3994 _representation: std::marker::PhantomData,
3995 })
3996 }
3997}
3998
3999impl<'a, R: TensorRank> TypedTensorView<'a, Complex64, R> {
4000 /// Borrow this complex view as an interleaved real view without copying.
4001 ///
4002 /// The result has dynamic rank because reinterpretation prepends the
4003 /// component axis `[2, ...]`. Only `Complex64 <-> f64` is sealed in this
4004 /// API; this is representation reinterpretation, not numeric conversion.
4005 ///
4006 /// # Examples
4007 ///
4008 /// ```
4009 /// use tenferro_tensor::{Complex64, TypedTensorView};
4010 ///
4011 /// let data = [Complex64::new(1.0, 2.0)];
4012 /// let view = TypedTensorView::from_col_major(&[1], &data)?;
4013 /// let real = view.as_real_view()?;
4014 /// assert_eq!(real.shape(), &[2, 1]);
4015 /// assert_eq!(real.as_slice()?, &[1.0_f64, 2.0]);
4016 /// # Ok::<(), tenferro_tensor::Error>(())
4017 /// ```
4018 ///
4019 /// # Errors
4020 ///
4021 /// Returns an error when the view layout is not a valid sealed
4022 /// representation or when backend reinterpretation is unsupported.
4023 pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'a, f64, DynRank>> {
4024 let op = "TypedTensorView::as_real_view";
4025 validate_representation_pair(op, DType::C64, DType::F64)?;
4026 let layout = reinterpret_complex_to_real_layout(
4027 self.shape(),
4028 self.strides(),
4029 self.offset(),
4030 self.buffer.len(),
4031 op,
4032 )?;
4033 let buffer = match &self.buffer {
4034 TensorStorageRef::Host(data) => {
4035 TensorStorageRef::Host(reinterpret_host_slice::<Complex64, f64>(data, op)?)
4036 }
4037 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
4038 return Err(crate::Error::unsupported(
4039 op,
4040 "backend representation reinterpretation is enabled by the provider phases",
4041 ))
4042 }
4043 };
4044 Ok(TypedTensorView {
4045 buffer,
4046 root: None,
4047 layout,
4048 placement: self.placement.clone(),
4049 _representation: std::marker::PhantomData,
4050 })
4051 }
4052}
4053
4054impl<'a, R: TensorRank> TypedTensorView<'a, f32, R> {
4055 /// Borrow this interleaved real view as a complex view without copying.
4056 ///
4057 /// The source must have a leading extent and stride of `2` and `1`, and
4058 /// every remaining stride plus the offset must be divisible by `2`.
4059 ///
4060 /// # Examples
4061 ///
4062 /// ```
4063 /// use tenferro_tensor::{Complex32, TypedTensorView};
4064 ///
4065 /// let data = [1.0_f32, 2.0];
4066 /// let view = TypedTensorView::from_col_major(&[2, 1], &data)?;
4067 /// let complex = view.as_complex_view()?;
4068 /// assert_eq!(complex.shape(), &[1]);
4069 /// assert_eq!(complex.as_slice()?, &[Complex32::new(1.0, 2.0)]);
4070 /// # Ok::<(), tenferro_tensor::Error>(())
4071 /// ```
4072 ///
4073 /// # Errors
4074 ///
4075 /// Returns an error when the view layout is not a valid sealed
4076 /// representation or when backend reinterpretation is unsupported.
4077 pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'a, Complex32, DynRank>> {
4078 let op = "TypedTensorView::as_complex_view";
4079 validate_representation_pair(op, DType::F32, DType::C32)?;
4080 let layout = reinterpret_real_to_complex_layout(
4081 self.shape(),
4082 self.strides(),
4083 self.offset(),
4084 self.buffer.len(),
4085 op,
4086 )?;
4087 let buffer = match &self.buffer {
4088 TensorStorageRef::Host(data) => {
4089 TensorStorageRef::Host(reinterpret_host_slice::<f32, Complex32>(data, op)?)
4090 }
4091 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
4092 return Err(crate::Error::unsupported(
4093 op,
4094 "backend representation reinterpretation is enabled by the provider phases",
4095 ))
4096 }
4097 };
4098 Ok(TypedTensorView {
4099 buffer,
4100 root: None,
4101 layout,
4102 placement: self.placement.clone(),
4103 _representation: std::marker::PhantomData,
4104 })
4105 }
4106}
4107
4108impl<'a, R: TensorRank> TypedTensorView<'a, f64, R> {
4109 /// Borrow this interleaved real view as a complex view without copying.
4110 ///
4111 /// The source must have a leading extent and stride of `2` and `1`, and
4112 /// every remaining stride plus the offset must be divisible by `2`.
4113 ///
4114 /// # Examples
4115 ///
4116 /// ```
4117 /// use tenferro_tensor::{Complex64, TypedTensorView};
4118 ///
4119 /// let data = [1.0_f64, 2.0];
4120 /// let view = TypedTensorView::from_col_major(&[2, 1], &data)?;
4121 /// let complex = view.as_complex_view()?;
4122 /// assert_eq!(complex.shape(), &[1]);
4123 /// assert_eq!(complex.as_slice()?, &[Complex64::new(1.0, 2.0)]);
4124 /// # Ok::<(), tenferro_tensor::Error>(())
4125 /// ```
4126 ///
4127 /// # Errors
4128 ///
4129 /// Returns an error when the view layout is not a valid sealed
4130 /// representation or when backend reinterpretation is unsupported.
4131 pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'a, Complex64, DynRank>> {
4132 let op = "TypedTensorView::as_complex_view";
4133 validate_representation_pair(op, DType::F64, DType::C64)?;
4134 let layout = reinterpret_real_to_complex_layout(
4135 self.shape(),
4136 self.strides(),
4137 self.offset(),
4138 self.buffer.len(),
4139 op,
4140 )?;
4141 let buffer = match &self.buffer {
4142 TensorStorageRef::Host(data) => {
4143 TensorStorageRef::Host(reinterpret_host_slice::<f64, Complex64>(data, op)?)
4144 }
4145 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
4146 return Err(crate::Error::unsupported(
4147 op,
4148 "backend representation reinterpretation is enabled by the provider phases",
4149 ))
4150 }
4151 };
4152 Ok(TypedTensorView {
4153 buffer,
4154 root: None,
4155 layout,
4156 placement: self.placement.clone(),
4157 _representation: std::marker::PhantomData,
4158 })
4159 }
4160}
4161
4162/// Mutable borrowed view of typed tensor storage with arbitrary strides.
4163///
4164/// # Examples
4165///
4166/// ```rust
4167/// use tenferro_tensor::TypedTensorViewMut;
4168///
4169/// let mut data = [1_i32, 2, 3];
4170/// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
4171/// *view.get_mut(&[2]).unwrap() = 10;
4172/// assert_eq!(view.as_read_only().get(&[2]), Some(&10));
4173/// # Ok::<(), tenferro_tensor::Error>(())
4174/// ```
4175/// Exclusive typed view of one tensor representation.
4176///
4177/// As with [`TypedTensorView`], the buffer stays concrete so the view keeps its
4178/// lifetime covariance, and `D` marks which representation produced the view.
4179pub struct TypedTensorViewMut<'a, T, R: TensorRank = DynRank, D: Representation = Dynamic> {
4180 buffer: TensorStorageRefMut<'a, T>,
4181 root: Option<GroupWriteView<'a, T, R>>,
4182 layout: TensorLayout<R>,
4183 placement: Placement,
4184 _representation: std::marker::PhantomData<D>,
4185}
4186
4187impl<'a, T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensorViewMut<'a, T, R, D> {
4188 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4189 formatter
4190 .debug_struct("TypedTensorViewMut")
4191 .field("shape", &self.layout.shape())
4192 .field("placement", &self.placement)
4193 .finish_non_exhaustive()
4194 }
4195}
4196
4197/// Pair of mutable tensor views returned by disjoint multi-slice operations.
4198///
4199/// # Examples
4200///
4201/// ```rust
4202/// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut, TypedTensorViewMutSplit};
4203///
4204/// let mut data = [1_i32, 2, 3, 4];
4205/// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
4206/// let pair: TypedTensorViewMutSplit<'_, i32> = view
4207/// .try_multi_slice_mut(
4208/// &[StridedSliceSpec::new(0, Some(2), 1)],
4209/// &[StridedSliceSpec::new(2, Some(4), 1)],
4210/// )
4211/// ?
4212/// .unwrap();
4213/// assert_eq!(pair.0.shape(), &[2]);
4214/// assert_eq!(pair.1.shape(), &[2]);
4215/// # Ok::<(), tenferro_tensor::Error>(())
4216/// ```
4217pub type TypedTensorViewMutSplit<'a, T, R = DynRank> =
4218 (TypedTensorViewMut<'a, T, R>, TypedTensorViewMut<'a, T, R>);
4219
4220impl<'a, T: 'static> TypedTensorViewMut<'a, T, DynRank> {
4221 /// Create a mutable dynamic-rank view over compact column-major host data.
4222 ///
4223 /// # Examples
4224 ///
4225 /// ```rust
4226 /// use tenferro_tensor::TypedTensorViewMut;
4227 ///
4228 /// let mut data = [1_i32, 2, 3, 4];
4229 /// let view = TypedTensorViewMut::from_col_major(&[2, 2], &mut data)?;
4230 /// assert_eq!(view.strides(), &[1, 2]);
4231 /// # Ok::<(), tenferro_tensor::Error>(())
4232 /// ```
4233 /// # Errors
4234 ///
4235 /// Returns [`crate::Error::Validation`] with
4236 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
4237 /// shape or offset arithmetic overflow, or
4238 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4239 /// compact shape reaches beyond `data`.
4240 pub fn from_col_major(shape: &[usize], data: &'a mut [T]) -> crate::Result<Self> {
4241 let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
4242 .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_col_major", err))?;
4243 Self::from_buffer_ref_mut(
4244 shape_vec(layout.shape()),
4245 stride_vec(layout.strides()),
4246 layout.offset(),
4247 TensorStorageRefMut::Host(data),
4248 default_placement(),
4249 "TypedTensorViewMut::from_col_major",
4250 )
4251 }
4252
4253 /// Create a mutable host view from explicit layout metadata.
4254 ///
4255 /// Layouts where distinct logical elements can alias the same physical
4256 /// element are rejected.
4257 ///
4258 /// # Examples
4259 ///
4260 /// ```rust
4261 /// use tenferro_tensor::TypedTensorViewMut;
4262 ///
4263 /// let mut data = [1_i32, 2];
4264 /// assert!(TypedTensorViewMut::from_slice(vec![2], vec![0], 0, &mut data).is_err());
4265 /// ```
4266 /// # Errors
4267 ///
4268 /// Returns [`crate::Error::Validation`] with
4269 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
4270 /// `strides` have different ranks,
4271 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4272 /// layout reaches beyond `data`,
4273 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4274 /// logical elements alias, or
4275 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4276 /// arithmetic overflow.
4277 pub fn from_slice(
4278 shape: impl AsRef<[usize]>,
4279 strides: impl AsRef<[isize]>,
4280 offset: isize,
4281 data: &'a mut [T],
4282 ) -> crate::Result<Self> {
4283 Self::from_buffer_ref_mut(
4284 shape_vec(shape.as_ref()),
4285 stride_vec(strides.as_ref()),
4286 offset,
4287 TensorStorageRefMut::Host(data),
4288 default_placement(),
4289 "TypedTensorViewMut::from_slice",
4290 )
4291 }
4292}
4293
4294impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4295 /// Create a rank-generic mutable host view from explicit layout metadata.
4296 ///
4297 /// # Examples
4298 ///
4299 /// ```rust
4300 /// use tenferro_tensor::{Rank, TypedTensorViewMut};
4301 ///
4302 /// let mut data = [1_i32, 2, 3, 4];
4303 /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
4304 /// assert_eq!(view.shape(), &[2, 2]);
4305 /// # Ok::<(), tenferro_tensor::Error>(())
4306 /// ```
4307 /// # Errors
4308 ///
4309 /// Returns [`crate::Error::Validation`] with
4310 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
4311 /// rank does not match `shape` or `strides`,
4312 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4313 /// layout reaches beyond `data`,
4314 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4315 /// logical elements alias, or
4316 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4317 /// arithmetic overflow.
4318 pub fn from_slice_ranked(
4319 shape: impl Into<R::Shape>,
4320 strides: impl Into<R::Strides>,
4321 offset: isize,
4322 data: &'a mut [T],
4323 ) -> crate::Result<Self> {
4324 Self::from_buffer_ref_mut(
4325 shape,
4326 strides,
4327 offset,
4328 TensorStorageRefMut::Host(data),
4329 default_placement(),
4330 "TypedTensorViewMut::from_slice_ranked",
4331 )
4332 }
4333}
4334
4335impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4336 fn from_buffer_ref_mut(
4337 shape: impl Into<R::Shape>,
4338 strides: impl Into<R::Strides>,
4339 offset: isize,
4340 buffer: TensorStorageRefMut<'a, T>,
4341 placement: Placement,
4342 op: &'static str,
4343 ) -> crate::Result<Self> {
4344 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
4345 .map_err(|err| tensor_layout_error(op, err))?;
4346 layout
4347 .validate_mutable_no_overlap()
4348 .map_err(|err| tensor_layout_error(op, err))?;
4349 Ok(Self {
4350 buffer,
4351 root: None,
4352 layout,
4353 placement,
4354 _representation: std::marker::PhantomData,
4355 })
4356 }
4357}
4358
4359impl<'a, T: 'static, R: TensorRank> TypedTensorViewMut<'a, T, R, Host> {
4360 /// Create a representation-marked mutable view over an explicit host layout.
4361 ///
4362 /// # Examples
4363 ///
4364 /// ```rust
4365 /// use tenferro_tensor::{DynRank, Host, TypedTensorViewMut};
4366 ///
4367 /// let mut data = [1_i32, 2];
4368 /// let mut view: TypedTensorViewMut<'_, i32, DynRank, Host> =
4369 /// TypedTensorViewMut::from_host_slice(vec![2].as_slice(), vec![1].as_slice(), 0, &mut data)?;
4370 /// view.slice_view(&[tenferro_tensor::StridedSliceSpec::all()])?;
4371 /// # Ok::<(), tenferro_tensor::Error>(())
4372 /// ```
4373 ///
4374 /// # Errors
4375 ///
4376 /// Returns [`crate::Error::Validation`] with
4377 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `strides`
4378 /// has a different rank, [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`]
4379 /// when logical elements alias, or
4380 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] /
4381 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
4382 /// reachable layout leaves `data` or overflows.
4383 pub fn from_host_slice(
4384 shape: impl Into<R::Shape>,
4385 strides: impl Into<R::Strides>,
4386 offset: isize,
4387 data: &'a mut [T],
4388 ) -> crate::Result<Self> {
4389 // Built here rather than through the shared helper so that the Host
4390 // marker can only ever be paired with a host slice.
4391 let buffer = TensorStorageRefMut::Host(data);
4392 let layout =
4393 TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
4394 .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_host_slice", err))?;
4395 layout
4396 .validate_mutable_no_overlap()
4397 .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_host_slice", err))?;
4398 Ok(Self {
4399 buffer,
4400 root: None,
4401 layout,
4402 placement: default_placement(),
4403 _representation: std::marker::PhantomData,
4404 })
4405 }
4406
4407 /// Exclusively borrow the host elements without a runtime check.
4408 ///
4409 /// # Examples
4410 ///
4411 /// ```
4412 /// use tenferro_tensor::{DynRank, Host, TypedTensorViewMut};
4413 ///
4414 /// let mut data = [1_i32, 2];
4415 /// let mut view: TypedTensorViewMut<'_, i32, DynRank, Host> =
4416 /// TypedTensorViewMut::from_host_slice(vec![2].as_slice(), vec![1].as_slice(), 0, &mut data)?;
4417 /// view.as_host_slice_mut()[0] = 5;
4418 /// assert_eq!(data, [5, 2]);
4419 /// # Ok::<(), tenferro_tensor::Error>(())
4420 /// ```
4421 ///
4422 /// # Panics
4423 ///
4424 /// Panics only if a `Host`-marked view was built from non-host storage,
4425 /// which no constructor in this crate does.
4426 pub fn as_host_slice_mut(&mut self) -> &mut [T] {
4427 match &mut self.buffer {
4428 TensorStorageRefMut::Host(data) => data,
4429 // INVARIANT: `Host`-marked views are constructed only by
4430 // `from_host_slice` and by `TypedTensor<_, _, Host>::as_view_mut`.
4431 TensorStorageRefMut::Backend(_) => {
4432 unreachable!("a Host-marked view always borrows host storage")
4433 }
4434 }
4435 }
4436}
4437
4438impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4439 /// Return the logical shape.
4440 ///
4441 /// # Examples
4442 ///
4443 /// ```rust
4444 /// use tenferro_tensor::TypedTensorViewMut;
4445 ///
4446 /// let mut data = [0_i32; 2];
4447 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4448 /// assert_eq!(view.shape(), &[2]);
4449 /// # Ok::<(), tenferro_tensor::Error>(())
4450 /// ```
4451 pub fn shape(&self) -> &[usize] {
4452 self.layout.shape()
4453 }
4454
4455 /// Return the logical rank carried by this mutable view.
4456 ///
4457 /// # Examples
4458 ///
4459 /// ```
4460 /// use tenferro_tensor::TypedTensorViewMut;
4461 ///
4462 /// let mut data = [0_i32; 6];
4463 /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
4464 /// assert_eq!(view.rank(), 2);
4465 /// # Ok::<(), tenferro_tensor::Error>(())
4466 /// ```
4467 pub fn rank(&self) -> usize {
4468 self.shape().len()
4469 }
4470
4471 /// Return strides in element units.
4472 ///
4473 /// # Examples
4474 ///
4475 /// ```rust
4476 /// use tenferro_tensor::TypedTensorViewMut;
4477 ///
4478 /// let mut data = [0_i32; 2];
4479 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![-1], 1, &mut data)?;
4480 /// assert_eq!(view.strides(), &[-1]);
4481 /// # Ok::<(), tenferro_tensor::Error>(())
4482 /// ```
4483 pub fn strides(&self) -> &[isize] {
4484 self.layout.strides()
4485 }
4486
4487 /// Return the physical element offset.
4488 ///
4489 /// # Examples
4490 ///
4491 /// ```rust
4492 /// use tenferro_tensor::TypedTensorViewMut;
4493 ///
4494 /// let mut data = [1_i32, 2];
4495 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 1, &mut data)?;
4496 /// assert_eq!(view.offset(), 1);
4497 /// # Ok::<(), tenferro_tensor::Error>(())
4498 /// ```
4499 pub fn offset(&self) -> isize {
4500 self.layout.offset()
4501 }
4502
4503 /// Return the borrowed host storage backing this view.
4504 ///
4505 /// This exposes the entire backing host allocation, not just the logical
4506 /// slice covered by this view. Use [`TypedTensorViewMut::as_read_only`]
4507 /// with [`TypedTensorView::as_slice`] when the caller needs the contiguous
4508 /// logical region instead.
4509 ///
4510 /// # Examples
4511 ///
4512 /// ```rust
4513 /// use tenferro_tensor::TypedTensorViewMut;
4514 ///
4515 /// let mut data = [1_i32, 2];
4516 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4517 /// assert_eq!(view.host_storage()?, &[1, 2]);
4518 /// # Ok::<(), tenferro_tensor::Error>(())
4519 /// ```
4520 /// # Errors
4521 ///
4522 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
4523 /// buffer; backend storage must be downloaded before host inspection.
4524 pub fn host_storage(&self) -> crate::Result<&[T]> {
4525 match &self.buffer {
4526 TensorStorageRefMut::Host(data) => Ok(data),
4527 TensorStorageRefMut::Backend(_) => Err(crate::Error::runtime_state(
4528 "TypedTensorViewMut::host_storage",
4529 "backend buffers cannot expose host storage; download explicitly first",
4530 )),
4531 }
4532 }
4533
4534 /// Mutably borrow the host storage backing this view.
4535 ///
4536 /// This exposes the entire backing host allocation, not just the logical
4537 /// slice covered by this view. Prefer scalar element accessors when mutating
4538 /// a logical region; tensor-sized copies belong to an active backend.
4539 ///
4540 /// # Examples
4541 ///
4542 /// ```rust
4543 /// use tenferro_tensor::TypedTensorViewMut;
4544 ///
4545 /// let mut data = [1_i32, 2];
4546 /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4547 /// view.host_storage_mut()?[0] = 3;
4548 /// assert_eq!(view.get(&[0]), Some(&3));
4549 /// # Ok::<(), tenferro_tensor::Error>(())
4550 /// ```
4551 /// # Errors
4552 ///
4553 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
4554 /// buffer; backend storage must be downloaded before host inspection.
4555 pub fn host_storage_mut(&mut self) -> crate::Result<&mut [T]> {
4556 match &mut self.buffer {
4557 TensorStorageRefMut::Host(data) => Ok(data),
4558 TensorStorageRefMut::Backend(_) => Err(crate::Error::runtime_state(
4559 "TypedTensorViewMut::host_storage_mut",
4560 "backend buffers cannot expose mutable host storage; download explicitly first",
4561 )),
4562 }
4563 }
4564
4565 /// Return the number of logical elements in this view.
4566 ///
4567 /// # Examples
4568 ///
4569 /// ```rust
4570 /// use tenferro_tensor::TypedTensorViewMut;
4571 ///
4572 /// let mut data = [0_i32; 6];
4573 /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
4574 /// assert_eq!(view.n_elements(), 6);
4575 /// # Ok::<(), tenferro_tensor::Error>(())
4576 /// ```
4577 pub fn n_elements(&self) -> usize {
4578 // Invariant: public mutable view constructors validate logical element count.
4579 match checked_view_element_count(self.shape(), "TypedTensorViewMut::n_elements") {
4580 Ok(n) => n,
4581 Err(err) => {
4582 unreachable!("TypedTensorViewMut layout shape is validated at construction: {err}")
4583 }
4584 }
4585 }
4586
4587 /// Return layout metadata for this view.
4588 ///
4589 /// # Examples
4590 ///
4591 /// ```rust
4592 /// use tenferro_tensor::TypedTensorViewMut;
4593 ///
4594 /// let mut data = [1_i32, 2];
4595 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4596 /// assert!(view.layout().is_compact_col_major().unwrap());
4597 /// # Ok::<(), tenferro_tensor::Error>(())
4598 /// ```
4599 pub fn layout(&self) -> &TensorLayout<R> {
4600 &self.layout
4601 }
4602
4603 /// Return placement metadata for this view.
4604 ///
4605 /// # Examples
4606 ///
4607 /// ```rust
4608 /// use tenferro_tensor::{MemoryKind, TypedTensorViewMut};
4609 ///
4610 /// let mut data = [1_i32];
4611 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4612 /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
4613 /// # Ok::<(), tenferro_tensor::Error>(())
4614 /// ```
4615 pub fn placement(&self) -> &Placement {
4616 &self.placement
4617 }
4618
4619 /// Return the backend allocation for backend integrations.
4620 #[doc(hidden)]
4621 pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>> {
4622 match &self.buffer {
4623 TensorStorageRefMut::Host(_) => None,
4624 TensorStorageRefMut::Backend(buffer) => Some(&**buffer),
4625 }
4626 }
4627
4628 /// Prepare this backend view for one provider-native write binding.
4629 #[doc(hidden)]
4630 pub fn prepare_device_write(
4631 &mut self,
4632 op: &'static str,
4633 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
4634 where
4635 T: TensorScalar + 'static,
4636 {
4637 let layout = self.layout.clone();
4638 if self.root.is_none() {
4639 let buffer = self.backend_buffer().ok_or_else(|| {
4640 crate::Error::runtime_state_source(
4641 op,
4642 crate::AccessError::Unsupported { backend: "host" },
4643 )
4644 })?;
4645 return prepare_backend_access(buffer, &self.layout, op);
4646 }
4647 let root = self
4648 .root
4649 .as_mut()
4650 .ok_or_else(|| crate::Error::runtime_state(op, "expected a root-backed tensor view"))?;
4651 root.prepare_device_write_for_layout(&layout)
4652 .map_err(|error| crate::Error::runtime_state_source(op, error))
4653 }
4654
4655 /// Compute the physical element offset for a logical index.
4656 ///
4657 /// # Examples
4658 ///
4659 /// ```rust
4660 /// use tenferro_tensor::TypedTensorViewMut;
4661 ///
4662 /// let mut data = [1_i32, 2, 3];
4663 /// let view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
4664 /// assert_eq!(view.linear_offset(&[2]), Some(0));
4665 /// # Ok::<(), tenferro_tensor::Error>(())
4666 /// ```
4667 pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
4668 checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
4669 }
4670
4671 /// Compute the physical element offset for a logical index, returning a typed error.
4672 ///
4673 /// # Examples
4674 ///
4675 /// ```rust
4676 /// use tenferro_tensor::TypedTensorViewMut;
4677 ///
4678 /// let mut data = [1_i32, 2, 3];
4679 /// let view = TypedTensorViewMut::from_slice([3], [-1], 2, &mut data)?;
4680 /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
4681 /// # Ok::<(), tenferro_tensor::Error>(())
4682 /// ```
4683 /// # Errors
4684 ///
4685 /// Returns [`crate::Error::Validation`] with
4686 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
4687 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
4688 /// when an index is outside its axis extent, or
4689 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
4690 /// arithmetic overflows.
4691 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
4692 checked_view_offset_result(
4693 self.shape(),
4694 self.strides(),
4695 self.offset(),
4696 indices,
4697 "TypedTensorViewMut::layout_linear_offset",
4698 )
4699 }
4700
4701 /// Return whether this mutable view is compact column-major.
4702 ///
4703 /// # Examples
4704 ///
4705 /// ```rust
4706 /// use tenferro_tensor::TypedTensorViewMut;
4707 ///
4708 /// let mut data = [1_i32, 2];
4709 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4710 /// assert!(view.is_col_major_contiguous()?);
4711 /// # Ok::<(), tenferro_tensor::Error>(())
4712 /// ```
4713 /// # Errors
4714 ///
4715 /// Returns [`crate::Error::Validation`] with
4716 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4717 /// compactness arithmetic overflows.
4718 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
4719 self.layout
4720 .is_compact_col_major()
4721 .map_err(|err| tensor_layout_error("TypedTensorViewMut::is_col_major_contiguous", err))
4722 }
4723
4724 /// Return a compact string summary of this mutable view's layout metadata.
4725 ///
4726 /// # Examples
4727 ///
4728 /// ```rust
4729 /// use tenferro_tensor::TypedTensorViewMut;
4730 ///
4731 /// let mut data = [1_i32, 2];
4732 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4733 /// assert!(view.layout_summary().contains("shape=[2]"));
4734 /// # Ok::<(), tenferro_tensor::Error>(())
4735 /// ```
4736 pub fn layout_summary(&self) -> String {
4737 layout_summary(self.shape(), self.strides(), self.offset())
4738 }
4739
4740 /// Assert this mutable view is compact column-major.
4741 ///
4742 /// # Examples
4743 ///
4744 /// ```rust
4745 /// use tenferro_tensor::TypedTensorViewMut;
4746 ///
4747 /// let mut data = [1_i32, 2];
4748 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4749 /// view.assert_col_major_contiguous()?;
4750 /// # Ok::<(), tenferro_tensor::Error>(())
4751 /// ```
4752 /// # Errors
4753 ///
4754 /// Returns [`crate::Error::Validation`] with
4755 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4756 /// compactness arithmetic overflows, or
4757 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
4758 /// view is not compact column-major.
4759 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
4760 assert_layout_col_major_contiguous(
4761 self.is_col_major_contiguous()?,
4762 self.shape(),
4763 self.strides(),
4764 self.offset(),
4765 "TypedTensorViewMut::assert_col_major_contiguous",
4766 )
4767 }
4768
4769 /// Borrow one host element by logical index.
4770 ///
4771 /// # Examples
4772 ///
4773 /// ```rust
4774 /// use tenferro_tensor::TypedTensorViewMut;
4775 ///
4776 /// let mut data = [1_i32, 2];
4777 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4778 /// assert_eq!(view.get(&[1]), Some(&2));
4779 /// # Ok::<(), tenferro_tensor::Error>(())
4780 /// ```
4781 pub fn get(&self, indices: &[usize]) -> Option<&T> {
4782 let offset = self.linear_offset(indices)?;
4783 match &self.buffer {
4784 TensorStorageRefMut::Host(data) => data.get(offset),
4785 TensorStorageRefMut::Backend(_) => None,
4786 }
4787 }
4788
4789 /// Mutably borrow one host element by logical index.
4790 ///
4791 /// # Examples
4792 ///
4793 /// ```rust
4794 /// use tenferro_tensor::TypedTensorViewMut;
4795 ///
4796 /// let mut data = [1_i32, 2];
4797 /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4798 /// *view.get_mut(&[1]).unwrap() = 20;
4799 /// assert_eq!(view.get(&[1]), Some(&20));
4800 /// # Ok::<(), tenferro_tensor::Error>(())
4801 /// ```
4802 pub fn get_mut(&mut self, indices: &[usize]) -> Option<&mut T> {
4803 let offset = self.linear_offset(indices)?;
4804 match &mut self.buffer {
4805 TensorStorageRefMut::Host(data) => data.get_mut(offset),
4806 TensorStorageRefMut::Backend(_) => None,
4807 }
4808 }
4809
4810 /// Borrow this mutable view as a read-only view.
4811 ///
4812 /// # Examples
4813 ///
4814 /// ```rust
4815 /// use tenferro_tensor::TypedTensorViewMut;
4816 ///
4817 /// let mut data = [1_i32];
4818 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4819 /// assert_eq!(view.as_read_only().get(&[0]), Some(&1));
4820 /// # Ok::<(), tenferro_tensor::Error>(())
4821 /// ```
4822 /// Explicitly duplicate the compact host data visible through this
4823 /// mutable view into a new owner.
4824 ///
4825 /// # Errors
4826 ///
4827 /// Returns [`crate::Error::HostAccess`] or
4828 /// [`ValidationError::NonContiguousViewAsSlice`] when the view is backend
4829 /// owned or not contiguous, and [`ValidationError::InvalidArgument`] when
4830 /// the static-rank shape cannot be reconstructed.
4831 ///
4832 /// # Examples
4833 ///
4834 /// ```
4835 /// use tenferro_tensor::TypedTensorViewMut;
4836 ///
4837 /// let mut data = [1_i32, 2];
4838 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4839 /// let copy = view.duplicate()?;
4840 /// assert_eq!(copy.as_slice()?, &[1, 2]);
4841 /// # Ok::<(), tenferro_tensor::Error>(())
4842 /// ```
4843 pub fn duplicate(&self) -> crate::Result<TypedTensor<T, R>>
4844 where
4845 T: Clone,
4846 {
4847 self.as_read_only().duplicate()
4848 }
4849
4850 /// Explicitly copy this host view to a compact column-major owner.
4851 ///
4852 /// # Examples
4853 /// ```
4854 /// use tenferro_tensor::TypedTensorViewMut;
4855 /// let mut values = ["a".to_owned(), "b".to_owned()];
4856 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut values)?;
4857 /// assert_eq!(view.to_col_major()?.as_slice()?, &["a", "b"]);
4858 /// # Ok::<(), tenferro_tensor::Error>(())
4859 /// ```
4860 /// # Errors
4861 /// Backend-only storage returns [`crate::Error::RuntimeState`]; invalid
4862 /// layout or rank conversion returns [`crate::Error::Validation`].
4863 pub fn to_col_major(&self) -> crate::Result<TypedTensor<T, R>>
4864 where
4865 T: Clone,
4866 {
4867 self.as_read_only().to_col_major()
4868 }
4869
4870 /// Borrow this mutable view as a read-only typed view over the same storage.
4871 ///
4872 /// # Examples
4873 ///
4874 /// ```
4875 /// use tenferro_tensor::TypedTensorViewMut;
4876 ///
4877 /// let mut data = [1_i32, 2];
4878 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4879 /// assert_eq!(view.as_read_only().as_slice()?, &[1, 2]);
4880 /// # Ok::<(), tenferro_tensor::Error>(())
4881 /// ```
4882 pub fn as_read_only(&self) -> TypedTensorView<'_, T, R, D> {
4883 let buffer = match &self.buffer {
4884 TensorStorageRefMut::Host(data) => TensorStorageRef::Host(data),
4885 TensorStorageRefMut::Backend(buffer) => TensorStorageRef::Backend(&**buffer),
4886 };
4887 TypedTensorView {
4888 buffer,
4889 root: None,
4890 layout: self.layout.clone(),
4891 placement: self.placement.clone(),
4892 _representation: std::marker::PhantomData,
4893 }
4894 }
4895
4896 /// Convert this mutable view into a read-only view.
4897 ///
4898 /// # Examples
4899 ///
4900 /// ```rust
4901 /// use tenferro_tensor::TypedTensorViewMut;
4902 ///
4903 /// let mut data = [1_i32];
4904 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4905 /// assert_eq!(view.into_read_only().get(&[0]), Some(&1));
4906 /// # Ok::<(), tenferro_tensor::Error>(())
4907 /// ```
4908 pub fn into_read_only(self) -> TypedTensorView<'a, T, R, D> {
4909 let buffer = match self.buffer {
4910 TensorStorageRefMut::Host(data) => TensorStorageRef::Host(data),
4911 TensorStorageRefMut::Backend(buffer) => TensorStorageRef::Backend(buffer),
4912 };
4913 TypedTensorView {
4914 buffer,
4915 root: None,
4916 layout: self.layout,
4917 placement: self.placement,
4918 _representation: std::marker::PhantomData,
4919 }
4920 }
4921
4922 /// Consume this mutable view and return a metadata-only axis permutation.
4923 ///
4924 /// # Examples
4925 ///
4926 /// ```rust
4927 /// use tenferro_tensor::{Rank, TypedTensorViewMut};
4928 ///
4929 /// let mut data = [1_i32, 2, 3, 4];
4930 /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
4931 /// let transposed = view.transpose_view([1, 0])?;
4932 /// assert_eq!(transposed.strides(), &[2, 1]);
4933 /// # Ok::<(), tenferro_tensor::Error>(())
4934 /// ```
4935 /// # Errors
4936 ///
4937 /// Returns [`crate::Error::Validation`] with
4938 /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
4939 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
4940 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
4941 /// not a valid permutation, [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`]
4942 /// when the permutation creates aliases, or
4943 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4944 /// arithmetic overflow.
4945 pub fn transpose_view(
4946 self,
4947 axes: impl AsRef<[usize]>,
4948 ) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
4949 let Self {
4950 buffer,
4951 root,
4952 layout,
4953 placement,
4954 _representation: std::marker::PhantomData,
4955 } = self;
4956 let layout = layout
4957 .transpose_view(axes)
4958 .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
4959 layout
4960 .validate_mutable_no_overlap()
4961 .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
4962 match buffer {
4963 TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
4964 buffer: TensorStorageRefMut::Host(data),
4965 root,
4966 layout,
4967 placement,
4968 _representation: std::marker::PhantomData,
4969 }),
4970 TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
4971 buffer: TensorStorageRefMut::Backend(buffer),
4972 root,
4973 layout,
4974 placement,
4975 _representation: std::marker::PhantomData,
4976 }),
4977 }
4978 }
4979
4980 /// Return a mutable metadata-only slice using one [`StridedSliceSpec`] per axis.
4981 ///
4982 /// # Examples
4983 ///
4984 /// ```rust
4985 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
4986 ///
4987 /// let mut data = [1_i32, 2, 3];
4988 /// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![1], 0, &mut data)?;
4989 /// *view.slice_view(&[StridedSliceSpec::reverse()])?.get_mut(&[0]).unwrap() = 30;
4990 /// assert_eq!(view.get(&[2]), Some(&30));
4991 /// # Ok::<(), tenferro_tensor::Error>(())
4992 /// ```
4993 /// # Errors
4994 ///
4995 /// Returns [`crate::Error::Validation`] with
4996 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
4997 /// count differs from the view rank,
4998 /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
4999 /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
5000 /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
5001 /// when the result exceeds the backing buffer,
5002 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
5003 /// logical elements alias, or
5004 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
5005 /// arithmetic overflow.
5006 pub fn slice_view(
5007 &mut self,
5008 slices: &[StridedSliceSpec],
5009 ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
5010 let specs = core_slice_specs(slices, self.shape(), "TypedTensorViewMut::slice_view")?;
5011 let layout = self
5012 .layout
5013 .slice_view(specs, self.buffer.len())
5014 .map_err(|err| tensor_layout_error("TypedTensorViewMut::slice_view", err))?;
5015 layout
5016 .validate_mutable_no_overlap()
5017 .map_err(|err| tensor_layout_error("TypedTensorViewMut::slice_view", err))?;
5018 let placement = self.placement.clone();
5019 match &mut self.buffer {
5020 TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
5021 buffer: TensorStorageRefMut::Host(data),
5022 root: None,
5023 layout,
5024 placement,
5025 _representation: std::marker::PhantomData,
5026 }),
5027 TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
5028 buffer: TensorStorageRefMut::Backend(*buffer),
5029 root: None,
5030 layout,
5031 placement,
5032 _representation: std::marker::PhantomData,
5033 }),
5034 }
5035 }
5036
5037 /// Return a mutable metadata-only slice along one axis.
5038 ///
5039 /// # Examples
5040 ///
5041 /// ```rust
5042 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
5043 ///
5044 /// let mut data = [1_i32, 2, 3, 4];
5045 /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
5046 /// assert_eq!(view.slice_axis_view(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
5047 /// # Ok::<(), tenferro_tensor::Error>(())
5048 /// ```
5049 /// # Errors
5050 ///
5051 /// Returns [`crate::Error::Validation`] with
5052 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
5053 /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
5054 /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
5055 /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
5056 /// when the result exceeds the backing buffer,
5057 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
5058 /// logical elements alias, or
5059 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
5060 /// arithmetic overflow.
5061 pub fn slice_axis_view(
5062 &mut self,
5063 axis: usize,
5064 slice: StridedSliceSpec,
5065 ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
5066 let slices = slice_axis_specs(
5067 self.shape().len(),
5068 axis,
5069 slice,
5070 "TypedTensorViewMut::slice_axis_view",
5071 )?;
5072 self.slice_view(&slices)
5073 }
5074
5075 /// Return two mutable metadata-only slices when their physical ranges are disjoint.
5076 ///
5077 /// # Examples
5078 ///
5079 /// ```rust
5080 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
5081 ///
5082 /// let mut data = [1_i32, 2, 3, 4];
5083 /// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
5084 /// let (left, right) = view
5085 /// .try_multi_slice_mut(
5086 /// &[StridedSliceSpec::new(0, Some(2), 1)],
5087 /// &[StridedSliceSpec::new(2, Some(4), 1)],
5088 /// )
5089 /// ?
5090 /// .unwrap();
5091 /// assert_eq!(left.shape(), &[2]);
5092 /// assert_eq!(right.shape(), &[2]);
5093 /// # Ok::<(), tenferro_tensor::Error>(())
5094 /// ```
5095 /// # Errors
5096 ///
5097 /// Returns [`crate::Error::Validation`] with
5098 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for either
5099 /// slice count, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
5100 /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for
5101 /// invalid parameters, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
5102 /// when a result exceeds the backing buffer,
5103 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
5104 /// a result aliases, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5105 /// for a negative reachable offset, or
5106 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
5107 /// arithmetic overflow. The method returns `Ok(None)` when the two ranges
5108 /// overlap or the view uses backend storage.
5109 pub fn try_multi_slice_mut(
5110 &mut self,
5111 first: &[StridedSliceSpec],
5112 second: &[StridedSliceSpec],
5113 ) -> crate::Result<Option<TypedTensorViewMutSplit<'_, T, R>>> {
5114 let op = "TypedTensorViewMut::try_multi_slice_mut";
5115 let first_specs = core_slice_specs(first, self.shape(), op)?;
5116 let second_specs = core_slice_specs(second, self.shape(), op)?;
5117 let buffer_len = self.buffer.len();
5118 let first_layout = self
5119 .layout
5120 .slice_view(first_specs, buffer_len)
5121 .map_err(|err| tensor_layout_error(op, err))?;
5122 let second_layout = self
5123 .layout
5124 .slice_view(second_specs, buffer_len)
5125 .map_err(|err| tensor_layout_error(op, err))?;
5126 first_layout
5127 .validate_mutable_no_overlap()
5128 .map_err(|err| tensor_layout_error(op, err))?;
5129 second_layout
5130 .validate_mutable_no_overlap()
5131 .map_err(|err| tensor_layout_error(op, err))?;
5132
5133 match (
5134 reachable_layout_span(
5135 first_layout.shape(),
5136 first_layout.strides(),
5137 first_layout.offset(),
5138 )?,
5139 reachable_layout_span(
5140 second_layout.shape(),
5141 second_layout.strides(),
5142 second_layout.offset(),
5143 )?,
5144 ) {
5145 (Some(first_span), Some(second_span)) => {
5146 let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
5147 let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
5148 let (first_data, second_data) = match &mut self.buffer {
5149 TensorStorageRefMut::Host(data) => {
5150 match split_two_mut_ranges(data, first_span, second_span) {
5151 Some(ranges) => ranges,
5152 None => return Ok(None),
5153 }
5154 }
5155 TensorStorageRefMut::Backend(_) => return Ok(None),
5156 };
5157 let first_view = view_mut_from_layout_and_slice(
5158 &first_layout,
5159 first_offset,
5160 first_data,
5161 self.placement.clone(),
5162 )?;
5163 let second_view = view_mut_from_layout_and_slice(
5164 &second_layout,
5165 second_offset,
5166 second_data,
5167 self.placement.clone(),
5168 )?;
5169 Ok(Some((first_view, second_view)))
5170 }
5171 (None, Some(second_span)) => {
5172 let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
5173 let (_, after_start) = match &mut self.buffer {
5174 TensorStorageRefMut::Host(data) => data.split_at_mut(second_span.0),
5175 TensorStorageRefMut::Backend(_) => return Ok(None),
5176 };
5177 let (second_data, _) = after_start.split_at_mut(second_span.1 - second_span.0 + 1);
5178 let first_view = view_mut_from_layout_and_slice(
5179 &first_layout,
5180 0,
5181 &mut [],
5182 self.placement.clone(),
5183 )?;
5184 let second_view = view_mut_from_layout_and_slice(
5185 &second_layout,
5186 second_offset,
5187 second_data,
5188 self.placement.clone(),
5189 )?;
5190 Ok(Some((first_view, second_view)))
5191 }
5192 (Some(first_span), None) => {
5193 let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
5194 let (_, after_start) = match &mut self.buffer {
5195 TensorStorageRefMut::Host(data) => data.split_at_mut(first_span.0),
5196 TensorStorageRefMut::Backend(_) => return Ok(None),
5197 };
5198 let (first_data, _) = after_start.split_at_mut(first_span.1 - first_span.0 + 1);
5199 let first_view = view_mut_from_layout_and_slice(
5200 &first_layout,
5201 first_offset,
5202 first_data,
5203 self.placement.clone(),
5204 )?;
5205 let second_view = view_mut_from_layout_and_slice(
5206 &second_layout,
5207 0,
5208 &mut [],
5209 self.placement.clone(),
5210 )?;
5211 Ok(Some((first_view, second_view)))
5212 }
5213 (None, None) => {
5214 let first_view = view_mut_from_layout_and_slice(
5215 &first_layout,
5216 0,
5217 &mut [],
5218 self.placement.clone(),
5219 )?;
5220 let second_view = view_mut_from_layout_and_slice(
5221 &second_layout,
5222 0,
5223 &mut [],
5224 self.placement.clone(),
5225 )?;
5226 Ok(Some((first_view, second_view)))
5227 }
5228 }
5229 }
5230
5231 /// Return a mutable metadata-only dynamic-rank reshape for contiguous views.
5232 ///
5233 /// # Examples
5234 ///
5235 /// ```rust
5236 /// use tenferro_tensor::TypedTensorViewMut;
5237 ///
5238 /// let mut data = [1_i32, 2, 3, 4];
5239 /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
5240 /// assert_eq!(view.reshape_view(&[4])?.shape(), &[4]);
5241 /// # Ok::<(), tenferro_tensor::Error>(())
5242 /// ```
5243 /// # Errors
5244 ///
5245 /// Returns [`crate::Error::Validation`] with
5246 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
5247 /// the source is not compact column-major,
5248 /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
5249 /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
5250 /// records the counts) when element counts differ,
5251 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
5252 /// the reshaped layout aliases, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
5253 /// for shape or layout arithmetic overflow, or
5254 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5255 /// reshaped view exceeds the backing buffer.
5256 pub fn reshape_view(
5257 &mut self,
5258 shape: &[usize],
5259 ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank, D>> {
5260 let layout = reshape_layout_dyn(
5261 &self.layout,
5262 shape,
5263 self.buffer.len(),
5264 "TypedTensorViewMut::reshape_view",
5265 )?;
5266 layout
5267 .validate_mutable_no_overlap()
5268 .map_err(|err| tensor_layout_error("TypedTensorViewMut::reshape_view", err))?;
5269 let placement = self.placement.clone();
5270 match &mut self.buffer {
5271 TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
5272 buffer: TensorStorageRefMut::Host(data),
5273 root: None,
5274 layout,
5275 placement,
5276 _representation: std::marker::PhantomData,
5277 }),
5278 TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
5279 buffer: TensorStorageRefMut::Backend(*buffer),
5280 root: None,
5281 layout,
5282 placement,
5283 _representation: std::marker::PhantomData,
5284 }),
5285 }
5286 }
5287}
5288
5289impl<'a, R: TensorRank> TypedTensorViewMut<'a, Complex32, R> {
5290 /// Borrow this mutable complex view as an interleaved real view.
5291 ///
5292 /// This changes only the typed descriptor and borrows the same host
5293 /// allocation. The result has dynamic rank because the component axis is
5294 /// prepended. Backend-native buffers are rejected until their provider
5295 /// phase supplies the corresponding mapping capability.
5296 ///
5297 /// # Examples
5298 ///
5299 /// ```
5300 /// use tenferro_tensor::{Complex32, TypedTensorViewMut};
5301 ///
5302 /// let mut data = [Complex32::new(1.0, 2.0)];
5303 /// let mut view = TypedTensorViewMut::from_col_major(&[1], &mut data)?;
5304 /// let real = view.as_real_view_mut()?;
5305 /// assert_eq!(real.shape(), &[2, 1]);
5306 /// assert_eq!(real.as_read_only().as_slice()?, &[1.0_f32, 2.0]);
5307 /// # Ok::<(), tenferro_tensor::Error>(())
5308 /// ```
5309 ///
5310 /// # Errors
5311 ///
5312 /// Returns an error when the view layout is not injective, the sealed
5313 /// representation is invalid, or backend reinterpretation is unsupported.
5314 pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
5315 let op = "TypedTensorViewMut::as_real_view_mut";
5316 validate_representation_pair(op, DType::C32, DType::F32)?;
5317 let layout = reinterpret_complex_to_real_layout(
5318 self.shape(),
5319 self.strides(),
5320 self.offset(),
5321 self.buffer.len(),
5322 op,
5323 )?;
5324 layout
5325 .validate_mutable_no_overlap()
5326 .map_err(|err| tensor_layout_error(op, err))?;
5327 let buffer = match &mut self.buffer {
5328 TensorStorageRefMut::Host(data) => {
5329 TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(data, op)?)
5330 }
5331 TensorStorageRefMut::Backend(_) => {
5332 return Err(crate::Error::unsupported(
5333 op,
5334 "backend representation reinterpretation is enabled by the provider phases",
5335 ))
5336 }
5337 };
5338 Ok(TypedTensorViewMut {
5339 buffer,
5340 root: None,
5341 layout,
5342 placement: self.placement.clone(),
5343 _representation: std::marker::PhantomData,
5344 })
5345 }
5346}
5347
5348impl<'a, R: TensorRank> TypedTensorViewMut<'a, Complex64, R> {
5349 /// Borrow this mutable complex view as an interleaved real view.
5350 ///
5351 /// This changes only the typed descriptor and borrows the same host
5352 /// allocation. The result has dynamic rank because the component axis is
5353 /// prepended. Backend-native buffers are rejected until their provider
5354 /// phase supplies the corresponding mapping capability.
5355 ///
5356 /// # Examples
5357 ///
5358 /// ```
5359 /// use tenferro_tensor::{Complex64, TypedTensorViewMut};
5360 ///
5361 /// let mut data = [Complex64::new(1.0, 2.0)];
5362 /// let mut view = TypedTensorViewMut::from_col_major(&[1], &mut data)?;
5363 /// let real = view.as_real_view_mut()?;
5364 /// assert_eq!(real.shape(), &[2, 1]);
5365 /// assert_eq!(real.as_read_only().as_slice()?, &[1.0_f64, 2.0]);
5366 /// # Ok::<(), tenferro_tensor::Error>(())
5367 /// ```
5368 ///
5369 /// # Errors
5370 ///
5371 /// Returns an error when the view layout is not injective, the sealed
5372 /// representation is invalid, or backend reinterpretation is unsupported.
5373 pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
5374 let op = "TypedTensorViewMut::as_real_view_mut";
5375 validate_representation_pair(op, DType::C64, DType::F64)?;
5376 let layout = reinterpret_complex_to_real_layout(
5377 self.shape(),
5378 self.strides(),
5379 self.offset(),
5380 self.buffer.len(),
5381 op,
5382 )?;
5383 layout
5384 .validate_mutable_no_overlap()
5385 .map_err(|err| tensor_layout_error(op, err))?;
5386 let buffer = match &mut self.buffer {
5387 TensorStorageRefMut::Host(data) => {
5388 TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(data, op)?)
5389 }
5390 TensorStorageRefMut::Backend(_) => {
5391 return Err(crate::Error::unsupported(
5392 op,
5393 "backend representation reinterpretation is enabled by the provider phases",
5394 ))
5395 }
5396 };
5397 Ok(TypedTensorViewMut {
5398 buffer,
5399 root: None,
5400 layout,
5401 placement: self.placement.clone(),
5402 _representation: std::marker::PhantomData,
5403 })
5404 }
5405}
5406
5407impl<'a, R: TensorRank> TypedTensorViewMut<'a, f32, R> {
5408 /// Borrow this mutable interleaved real view as a complex view.
5409 ///
5410 /// The source must have a leading extent and stride of `2` and `1`, and
5411 /// all remaining strides plus the offset must be divisible by `2`.
5412 ///
5413 /// # Examples
5414 ///
5415 /// ```
5416 /// use tenferro_tensor::{Complex32, TypedTensorViewMut};
5417 ///
5418 /// let mut data = [1.0_f32, 2.0];
5419 /// let mut view = TypedTensorViewMut::from_col_major(&[2, 1], &mut data)?;
5420 /// let complex = view.as_complex_view_mut()?;
5421 /// assert_eq!(complex.shape(), &[1]);
5422 /// assert_eq!(complex.as_read_only().as_slice()?, &[Complex32::new(1.0, 2.0)]);
5423 /// # Ok::<(), tenferro_tensor::Error>(())
5424 /// ```
5425 ///
5426 /// # Errors
5427 ///
5428 /// Returns an error when the view layout is not injective, the sealed
5429 /// representation is invalid, or backend reinterpretation is unsupported.
5430 pub fn as_complex_view_mut(
5431 &mut self,
5432 ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
5433 let op = "TypedTensorViewMut::as_complex_view_mut";
5434 validate_representation_pair(op, DType::F32, DType::C32)?;
5435 let layout = reinterpret_real_to_complex_layout(
5436 self.shape(),
5437 self.strides(),
5438 self.offset(),
5439 self.buffer.len(),
5440 op,
5441 )?;
5442 layout
5443 .validate_mutable_no_overlap()
5444 .map_err(|err| tensor_layout_error(op, err))?;
5445 let buffer = match &mut self.buffer {
5446 TensorStorageRefMut::Host(data) => {
5447 TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(data, op)?)
5448 }
5449 TensorStorageRefMut::Backend(_) => {
5450 return Err(crate::Error::unsupported(
5451 op,
5452 "backend representation reinterpretation is enabled by the provider phases",
5453 ))
5454 }
5455 };
5456 Ok(TypedTensorViewMut {
5457 buffer,
5458 root: None,
5459 layout,
5460 placement: self.placement.clone(),
5461 _representation: std::marker::PhantomData,
5462 })
5463 }
5464}
5465
5466impl<'a, R: TensorRank> TypedTensorViewMut<'a, f64, R> {
5467 /// Borrow this mutable interleaved real view as a complex view.
5468 ///
5469 /// The source must have a leading extent and stride of `2` and `1`, and
5470 /// all remaining strides plus the offset must be divisible by `2`.
5471 ///
5472 /// # Examples
5473 ///
5474 /// ```
5475 /// use tenferro_tensor::{Complex64, TypedTensorViewMut};
5476 ///
5477 /// let mut data = [1.0_f64, 2.0];
5478 /// let mut view = TypedTensorViewMut::from_col_major(&[2, 1], &mut data)?;
5479 /// let complex = view.as_complex_view_mut()?;
5480 /// assert_eq!(complex.shape(), &[1]);
5481 /// assert_eq!(complex.as_read_only().as_slice()?, &[Complex64::new(1.0, 2.0)]);
5482 /// # Ok::<(), tenferro_tensor::Error>(())
5483 /// ```
5484 ///
5485 /// # Errors
5486 ///
5487 /// Returns an error when the view layout is not injective, the sealed
5488 /// representation is invalid, or backend reinterpretation is unsupported.
5489 pub fn as_complex_view_mut(
5490 &mut self,
5491 ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
5492 let op = "TypedTensorViewMut::as_complex_view_mut";
5493 validate_representation_pair(op, DType::F64, DType::C64)?;
5494 let layout = reinterpret_real_to_complex_layout(
5495 self.shape(),
5496 self.strides(),
5497 self.offset(),
5498 self.buffer.len(),
5499 op,
5500 )?;
5501 layout
5502 .validate_mutable_no_overlap()
5503 .map_err(|err| tensor_layout_error(op, err))?;
5504 let buffer = match &mut self.buffer {
5505 TensorStorageRefMut::Host(data) => {
5506 TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(data, op)?)
5507 }
5508 TensorStorageRefMut::Backend(_) => {
5509 return Err(crate::Error::unsupported(
5510 op,
5511 "backend representation reinterpretation is enabled by the provider phases",
5512 ))
5513 }
5514 };
5515 Ok(TypedTensorViewMut {
5516 buffer,
5517 root: None,
5518 layout,
5519 placement: self.placement.clone(),
5520 _representation: std::marker::PhantomData,
5521 })
5522 }
5523}
5524
5525/// Sealed trait for scalar types that can be stored in a [`Tensor`].
5526///
5527/// This trait is implemented for `f64`, `f32`, `i32`, `i64`, `bool`,
5528/// [`Complex64`], and [`Complex32`].
5529///
5530/// # Examples
5531///
5532/// ```
5533/// use tenferro_tensor::TensorScalar;
5534///
5535/// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
5536/// assert_eq!(tensor.as_slice::<f64>()?, [1.0, 2.0].as_slice());
5537/// # Ok::<(), tenferro_tensor::Error>(())
5538/// ```
5539pub trait TensorScalar: Copy + Clone + Send + Sync + 'static + private::Sealed {
5540 /// Real-valued counterpart of this scalar type.
5541 type Real: TensorScalar;
5542
5543 /// The [`DType`] tag corresponding to this scalar type.
5544 ///
5545 /// # Examples
5546 ///
5547 /// ```
5548 /// use tenferro_tensor::{DType, TensorScalar};
5549 ///
5550 /// assert_eq!(f64::dtype(), DType::F64);
5551 /// assert_eq!(f32::dtype(), DType::F32);
5552 /// ```
5553 fn dtype() -> DType;
5554
5555 /// Build the crate's default scalar set from validated column-major data.
5556 ///
5557 /// # Examples
5558 ///
5559 /// ```
5560 /// use tenferro_tensor::{DType, TensorScalar};
5561 ///
5562 /// let set = <f64 as TensorScalar>::into_default_scalars(vec![2], vec![1.0, 2.0])?;
5563 /// assert_eq!(set.dtype(), DType::F64);
5564 /// # Ok::<(), tenferro_tensor::Error>(())
5565 /// ```
5566 ///
5567 /// # Errors
5568 ///
5569 /// Returns a validation error carrying
5570 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when the shape product
5571 /// differs from `data.len()`, or [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
5572 /// when shape arithmetic overflows.
5573 fn into_default_scalars(
5574 shape: Vec<usize>,
5575 data: Vec<Self>,
5576 ) -> crate::Result<crate::DefaultScalars>;
5577
5578 /// Borrow the default scalar set's values when it holds this scalar type.
5579 ///
5580 /// Returns `None` when the set currently holds another member.
5581 ///
5582 /// # Examples
5583 ///
5584 /// ```
5585 /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5586 ///
5587 /// let set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5588 /// assert_eq!(<f64 as TensorScalar>::default_scalars_slice(&set), Some(&[1.0, 2.0][..]));
5589 /// assert!(<f32 as TensorScalar>::default_scalars_slice(&set).is_none());
5590 /// # Ok::<(), tenferro_tensor::Error>(())
5591 /// ```
5592 fn default_scalars_slice(set: &crate::DefaultScalars) -> Option<&[Self]>;
5593
5594 /// Exclusively borrow the default scalar set's values when it holds this scalar type.
5595 ///
5596 /// Returns `None` when the set currently holds another member.
5597 ///
5598 /// # Examples
5599 ///
5600 /// ```
5601 /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5602 ///
5603 /// let mut set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5604 /// if let Some(values) = <f64 as TensorScalar>::default_scalars_slice_mut(&mut set) {
5605 /// values[0] = 5.0;
5606 /// }
5607 /// assert_eq!(set.as_slice::<f64>()?, &[5.0, 2.0]);
5608 /// # Ok::<(), tenferro_tensor::Error>(())
5609 /// ```
5610 fn default_scalars_slice_mut(set: &mut crate::DefaultScalars) -> Option<&mut [Self]>;
5611
5612 /// Move the host tensor out of the default scalar set when it holds this scalar type.
5613 ///
5614 /// Returns `None` when the set currently holds another member.
5615 ///
5616 /// # Examples
5617 ///
5618 /// ```
5619 /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5620 ///
5621 /// let set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5622 /// let host = <f64 as TensorScalar>::from_default_scalars(set);
5623 /// assert_eq!(host.as_ref().map(|t| t.shape()), Some(&[2][..]));
5624 /// assert_eq!(host.as_ref().map(|t| t.as_slice()), Some(&[1.0, 2.0][..]));
5625 /// # Ok::<(), tenferro_tensor::Error>(())
5626 /// ```
5627 fn from_default_scalars(set: crate::DefaultScalars)
5628 -> Option<TypedTensor<Self, DynRank, Host>>;
5629
5630 /// Wrap typed column-major data into a [`Tensor`] enum variant.
5631 ///
5632 /// # Examples
5633 ///
5634 /// ```
5635 /// use tenferro_tensor::{DType, TensorScalar};
5636 ///
5637 /// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
5638 /// assert_eq!(tensor.dtype(), DType::F64);
5639 /// assert_eq!(tensor.shape(), &[2]);
5640 /// assert!(<f64 as TensorScalar>::into_tensor(vec![3], vec![1.0]).is_err());
5641 /// # Ok::<(), tenferro_tensor::Error>(())
5642 /// ```
5643 ///
5644 /// # Errors
5645 ///
5646 /// Returns [`crate::Error::Validation`] with
5647 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
5648 /// the shape product differs from `data.len()`, or
5649 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
5650 /// arithmetic overflows.
5651 fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor>;
5652
5653 /// Wrap a typed tensor into its dynamic [`Tensor`] enum variant.
5654 ///
5655 /// # Examples
5656 ///
5657 /// ```
5658 /// use tenferro_tensor::{DType, Tensor, TensorScalar, TypedTensor};
5659 ///
5660 /// let typed = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![3.0])?;
5661 /// let tensor = <f64 as TensorScalar>::typed_tensor_into_tensor(typed);
5662 /// assert!(matches!(tensor.dtype(), DType::F64));
5663 /// # Ok::<(), tenferro_tensor::Error>(())
5664 /// ```
5665 fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor;
5666
5667 /// Borrow a typed tensor as a dtype-erased [`TensorRead`] view.
5668 ///
5669 /// This keeps the typed tensor borrowed instead of copying host data into
5670 /// a new dynamic tensor.
5671 ///
5672 /// # Examples
5673 ///
5674 /// ```
5675 /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
5676 ///
5677 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5678 /// let read = f64::tensor_read(&tensor);
5679 /// assert_eq!(read.dtype(), DType::F64);
5680 /// assert_eq!(read.shape(), &[2]);
5681 /// ```
5682 fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_>;
5683
5684 /// Wrap a typed borrowed view as a dtype-erased [`TensorView`].
5685 ///
5686 /// # Examples
5687 ///
5688 /// ```
5689 /// use tenferro_tensor::{DType, TensorScalar, TypedTensorView};
5690 ///
5691 /// let data = [1.0_f64];
5692 /// let view = TypedTensorView::from_col_major(&[1], &data)?;
5693 /// assert_eq!(f64::tensor_view(view).dtype(), DType::F64);
5694 /// # Ok::<(), tenferro_tensor::Error>(())
5695 /// ```
5696 fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a>;
5697
5698 /// Wrap a typed mutable borrowed view as a dtype-erased [`TensorViewMut`].
5699 ///
5700 /// # Examples
5701 ///
5702 /// ```
5703 /// use tenferro_tensor::{DType, TensorScalar, TypedTensorViewMut};
5704 ///
5705 /// let mut data = [1.0_f64, 2.0];
5706 /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
5707 /// let erased = f64::tensor_view_mut(view);
5708 /// assert_eq!(erased.dtype(), DType::F64);
5709 /// assert_eq!(erased.shape(), &[2]);
5710 /// # Ok::<(), tenferro_tensor::Error>(())
5711 /// ```
5712 fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a>;
5713
5714 /// Mutably borrow a typed tensor as a dtype-erased [`TensorWrite`] view.
5715 ///
5716 /// This keeps the typed output borrowed instead of wrapping it in a
5717 /// temporary dynamic tensor.
5718 ///
5719 /// # Examples
5720 ///
5721 /// ```
5722 /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
5723 ///
5724 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0]).unwrap();
5725 /// let write = f64::tensor_write(&mut tensor);
5726 /// assert_eq!(write.dtype(), DType::F64);
5727 /// ```
5728 fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_>;
5729
5730 /// Borrow the host data from a [`Tensor`].
5731 ///
5732 /// # Examples
5733 ///
5734 /// ```
5735 /// use tenferro_tensor::{Tensor, TensorScalar};
5736 ///
5737 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5738 /// assert_eq!(<f64 as TensorScalar>::as_slice(&tensor)?, &[1.0, 2.0]);
5739 /// assert!(<f32 as TensorScalar>::as_slice(&tensor).is_err());
5740 /// # Ok::<(), tenferro_tensor::Error>(())
5741 /// ```
5742 ///
5743 /// # Errors
5744 ///
5745 /// Returns [`crate::Error::Validation`] with
5746 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
5747 /// is not the scalar type represented by this implementation, or
5748 /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
5749 /// storage that has not been downloaded.
5750 fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]>;
5751
5752 /// Mutably borrow the host data from a [`Tensor`].
5753 ///
5754 /// # Examples
5755 ///
5756 /// ```
5757 /// use tenferro_tensor::{Tensor, TensorScalar};
5758 ///
5759 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
5760 /// <f64 as TensorScalar>::as_slice_mut(&mut tensor)?[0] = 3.0;
5761 ///
5762 /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
5763 /// # Ok::<(), tenferro_tensor::Error>(())
5764 /// ```
5765 /// # Errors
5766 ///
5767 /// Returns [`crate::Error::Validation`] with
5768 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
5769 /// is not the scalar type represented by this implementation, or
5770 /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
5771 /// storage that has not been downloaded.
5772 fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]>;
5773
5774 /// Extract a [`TypedTensor<Self>`] from a dynamic [`Tensor`].
5775 ///
5776 /// # Examples
5777 ///
5778 /// ```
5779 /// use tenferro_tensor::{Tensor, TensorScalar};
5780 ///
5781 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5782 /// let Ok(typed) = <f64 as TensorScalar>::into_typed(tensor) else {
5783 /// panic!("the dtype matches by construction")
5784 /// };
5785 ///
5786 /// assert_eq!(typed.as_slice()?, &[1.0, 2.0]);
5787 /// # Ok::<(), tenferro_tensor::Error>(())
5788 /// ```
5789 /// # Errors
5790 ///
5791 /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is
5792 /// not the scalar type represented by this implementation, with
5793 /// [`crate::Error::Validation`] and
5794 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] as the cause.
5795 fn into_typed(
5796 tensor: Tensor,
5797 ) -> std::result::Result<TypedTensor<Self>, ReinterpretError<Tensor>>;
5798}
5799
5800mod private {
5801 pub trait Sealed {}
5802
5803 impl Sealed for f64 {}
5804 impl Sealed for f32 {}
5805 impl Sealed for i32 {}
5806 impl Sealed for i64 {}
5807 impl Sealed for bool {}
5808 impl Sealed for num_complex::Complex64 {}
5809 impl Sealed for num_complex::Complex32 {}
5810}
5811
5812macro_rules! impl_tensor_scalar {
5813 ($ty:ty, $real:ty, $dtype:ident, $variant:ident) => {
5814 impl TensorScalar for $ty {
5815 type Real = $real;
5816
5817 #[inline]
5818 fn dtype() -> DType {
5819 DType::$dtype
5820 }
5821
5822 fn into_default_scalars(
5823 shape: Vec<usize>,
5824 data: Vec<Self>,
5825 ) -> crate::Result<crate::DefaultScalars> {
5826 TypedTensor::<Self, DynRank, Host>::from_host_vec_col_major(shape, data).map(
5827 |tensor| {
5828 crate::DefaultScalars::from_payload(
5829 crate::default_scalars::DefaultScalarsValue::$variant(tensor),
5830 )
5831 },
5832 )
5833 }
5834
5835 fn default_scalars_slice(set: &crate::DefaultScalars) -> Option<&[Self]> {
5836 match set.payload() {
5837 crate::default_scalars::DefaultScalarsValue::$variant(tensor) => {
5838 Some(tensor.as_slice())
5839 }
5840 _ => None,
5841 }
5842 }
5843
5844 fn default_scalars_slice_mut(set: &mut crate::DefaultScalars) -> Option<&mut [Self]> {
5845 match set.payload_mut() {
5846 crate::default_scalars::DefaultScalarsValue::$variant(tensor) => {
5847 Some(tensor.host_data_mut())
5848 }
5849 _ => None,
5850 }
5851 }
5852
5853 fn from_default_scalars(
5854 set: crate::DefaultScalars,
5855 ) -> Option<TypedTensor<Self, DynRank, Host>> {
5856 match set.into_payload() {
5857 crate::default_scalars::DefaultScalarsValue::$variant(tensor) => Some(tensor),
5858 _ => None,
5859 }
5860 }
5861
5862 fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor> {
5863 TypedTensor::from_vec_col_major(shape, data).map(Self::typed_tensor_into_tensor)
5864 }
5865
5866 fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor {
5867 Tensor {
5868 payload: TensorPayload::Native(PresetTensor::$variant(tensor)),
5869 }
5870 }
5871
5872 fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_> {
5873 TensorRead::from_view(TensorView::$variant(tensor.as_view()))
5874 }
5875
5876 #[inline]
5877 fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a> {
5878 TensorView::$variant(view)
5879 }
5880
5881 #[inline]
5882 fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a> {
5883 TensorViewMut::$variant(view)
5884 }
5885
5886 fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_> {
5887 TensorWrite::from_view(TensorViewMut::$variant(tensor.as_view_mut()))
5888 }
5889
5890 fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]> {
5891 tensor
5892 .as_typed::<Self>()
5893 .ok_or_else(|| {
5894 crate::Error::validation(
5895 "Tensor::as_slice",
5896 ValidationError::DTypeMismatch {
5897 expected: Self::dtype(),
5898 actual: tensor.dtype(),
5899 },
5900 )
5901 })?
5902 .host_data()
5903 }
5904
5905 fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]> {
5906 let actual = tensor.dtype();
5907 let typed = tensor.as_typed_mut::<Self>().ok_or_else(|| {
5908 crate::Error::validation(
5909 "Tensor::as_slice_mut",
5910 ValidationError::DTypeMismatch {
5911 expected: Self::dtype(),
5912 actual,
5913 },
5914 )
5915 })?;
5916 typed.host_data_mut()
5917 }
5918
5919 fn into_typed(
5920 tensor: Tensor,
5921 ) -> std::result::Result<TypedTensor<Self>, ReinterpretError<Tensor>> {
5922 let actual = tensor.dtype();
5923 match tensor.payload {
5924 TensorPayload::Native(PresetTensor::$variant(typed)) => Ok(typed),
5925 payload => Err(ReinterpretError::new(
5926 Tensor { payload },
5927 crate::Error::validation(
5928 "TensorScalar::into_typed",
5929 ValidationError::DTypeMismatch {
5930 expected: Self::dtype(),
5931 actual,
5932 },
5933 ),
5934 )),
5935 }
5936 }
5937 }
5938 };
5939}
5940
5941impl_tensor_scalar!(f64, f64, F64, F64);
5942impl_tensor_scalar!(f32, f32, F32, F32);
5943impl_tensor_scalar!(i64, i64, I64, I64);
5944impl_tensor_scalar!(i32, i32, I32, I32);
5945impl_tensor_scalar!(bool, bool, Bool, Bool);
5946impl_tensor_scalar!(Complex64, f64, C64, C64);
5947impl_tensor_scalar!(Complex32, f32, C32, C32);
5948
5949/// Dynamic tensor enum over the supported scalar types.
5950///
5951/// The enum keeps dtype dynamic and rank dynamic. Use
5952/// [`TypedTensor<T, R>`](TypedTensor) directly when the scalar type or rank
5953/// should be represented in Rust's type system.
5954///
5955/// # Examples
5956///
5957/// ```rust
5958/// use tenferro_tensor::{Tensor, TypedTensor};
5959///
5960/// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
5961/// assert_eq!(t.shape(), &[2]);
5962///
5963/// let erased = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 2.0]).unwrap();
5964/// assert_eq!(erased.shape().len(), 2);
5965/// ```
5966/// The owning payload behind the erased [`Tensor`].
5967///
5968/// `Native` stores a preset scalar owner directly; `External` retains its
5969/// existing explicit caller-owned host payload.
5970#[derive(Debug)]
5971enum TensorPayload {
5972 Native(PresetTensor),
5973 External(crate::ErasedHostTensor, Placement),
5974}
5975
5976#[derive(Debug)]
5977enum PresetTensor {
5978 F32(TypedTensor<f32>),
5979 F64(TypedTensor<f64>),
5980 I32(TypedTensor<i32>),
5981 I64(TypedTensor<i64>),
5982 Bool(TypedTensor<bool>),
5983 C32(TypedTensor<Complex32>),
5984 C64(TypedTensor<Complex64>),
5985}
5986
5987macro_rules! with_preset {
5988 ($preset:expr, |$value:ident| $body:expr) => {
5989 match $preset {
5990 PresetTensor::F32($value) => $body,
5991 PresetTensor::F64($value) => $body,
5992 PresetTensor::I32($value) => $body,
5993 PresetTensor::I64($value) => $body,
5994 PresetTensor::Bool($value) => $body,
5995 PresetTensor::C32($value) => $body,
5996 PresetTensor::C64($value) => $body,
5997 }
5998 };
5999}
6000
6001/// Dynamic tensor over the supported scalar types.
6002///
6003/// The erased tensor keeps dtype and rank dynamic: each preset scalar retains its
6004/// typed owner without constructing a group, while caller-owned scalars remain
6005/// `External` payloads recovered by their own type. Use [`TypedTensor<T, R>`](TypedTensor)
6006/// directly when the scalar type or rank should be represented in Rust's type system.
6007///
6008/// # Examples
6009///
6010/// ```
6011/// use tenferro_tensor::{DType, Tensor};
6012///
6013/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
6014/// assert_eq!(tensor.dtype(), DType::F64);
6015/// # Ok::<(), tenferro_tensor::Error>(())
6016/// ```
6017#[derive(Debug)]
6018pub struct Tensor {
6019 payload: TensorPayload,
6020}
6021
6022impl<R: TensorRank> OwnedTensorGroup<R> {
6023 /// Whether this group's descriptor names a non-CPU provider.
6024 fn is_backend_buffer(&self) -> bool {
6025 !matches!(
6026 self.group.provider_kind(self.slot),
6027 None | Some(crate::storage::ProviderKind::Cpu)
6028 )
6029 }
6030}
6031
6032impl Tensor {
6033 /// Carry an externally defined scalar as a caller-owned payload.
6034 ///
6035 /// The payload keeps its own element type and is recovered by that type, so no
6036 /// bytes are reinterpreted. Placement defaults to unpinned host memory, which
6037 /// is where a caller-owned payload lives.
6038 ///
6039 /// # Examples
6040 ///
6041 /// ```rust
6042 /// use tenferro_tensor::{DType, Tensor};
6043 /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
6044 ///
6045 /// let payload = ErasedHostTensor::new(
6046 /// TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
6047 /// );
6048 /// let element = payload.element_type_id();
6049 /// let tensor = Tensor::external(payload);
6050 /// assert_eq!(tensor.dtype(), DType::External(element));
6051 /// assert_eq!(tensor.shape(), &[1]);
6052 /// # Ok::<(), tenferro_tensor::Error>(())
6053 /// ```
6054 #[must_use]
6055 pub fn external(payload: crate::ErasedHostTensor) -> Self {
6056 Self {
6057 payload: TensorPayload::External(payload, Placement::default()),
6058 }
6059 }
6060
6061 /// Build a tensor from a typed one, without naming its variant.
6062 ///
6063 /// A call site that constructs a tensor from a typed tensor should use this rather than a variant, so
6064 /// that changing how the erased representation is stored changes this function and not its 1290 call
6065 /// sites. The variants remain until the removal's last step, so both forms currently produce the same
6066 /// value.
6067 ///
6068 /// # Examples
6069 ///
6070 /// ```
6071 /// use tenferro_tensor::Tensor;
6072 ///
6073 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
6074 /// let typed = tensor.into_typed::<f64>().unwrap();
6075 /// let rebuilt = Tensor::from_typed(typed);
6076 /// assert_eq!(rebuilt.as_typed::<f64>().unwrap().shape(), &[2]);
6077 /// # Ok::<(), tenferro_tensor::Error>(())
6078 /// ```
6079 #[must_use]
6080 pub fn from_typed<T: TensorScalar>(typed: TypedTensor<T>) -> Self {
6081 T::typed_tensor_into_tensor(typed)
6082 }
6083
6084 /// Borrow the erased payload of an externally defined tensor.
6085 ///
6086 /// This is the counterpart of [`Tensor::external`] for dispatch: a table that matches on
6087 /// [`Tensor::dtype`] reaches the externally defined tag and needs the payload that tag stands
6088 /// for, just as the typed tags reach theirs through [`Tensor::as_typed`]. Every other tag
6089 /// returns `None`.
6090 ///
6091 /// # Examples
6092 ///
6093 /// ```
6094 /// use tenferro_tensor::Tensor;
6095 /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
6096 ///
6097 /// let payload = ErasedHostTensor::new(
6098 /// TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
6099 /// );
6100 /// let tensor = Tensor::external(payload);
6101 /// assert!(tensor.external_payload().is_some());
6102 /// # Ok::<(), tenferro_tensor::Error>(())
6103 /// ```
6104 #[must_use]
6105 pub fn external_payload(&self) -> Option<&crate::ErasedHostTensor> {
6106 match &self.payload {
6107 TensorPayload::External(payload, _) => Some(payload),
6108 TensorPayload::Native(_) => None,
6109 }
6110 }
6111
6112 /// Carry an externally defined payload with an explicit placement.
6113 ///
6114 /// [`Tensor::external`] defaults the placement to unpinned host memory, which is where
6115 /// a caller-owned payload normally lives; this entry point is for a caller that knows
6116 /// the placement it wants.
6117 ///
6118 /// # Examples
6119 ///
6120 /// ```
6121 /// use tenferro_tensor::{Placement, Tensor};
6122 /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
6123 ///
6124 /// let payload = ErasedHostTensor::new(
6125 /// TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
6126 /// );
6127 /// let tensor = Tensor::external_with_placement(payload, Placement::default());
6128 /// assert!(tensor.external_payload().is_some());
6129 /// # Ok::<(), tenferro_tensor::Error>(())
6130 /// ```
6131 #[must_use]
6132 pub fn external_with_placement(payload: crate::ErasedHostTensor, placement: Placement) -> Self {
6133 Self {
6134 payload: TensorPayload::External(payload, placement),
6135 }
6136 }
6137
6138 /// Mutably borrow the erased payload of an externally defined tensor.
6139 ///
6140 /// The counterpart of [`Tensor::external_payload`] for callers that update the payload
6141 /// in place, such as a mutation test that checks the copy boundary.
6142 ///
6143 /// # Examples
6144 ///
6145 /// ```
6146 /// use tenferro_tensor::Tensor;
6147 /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
6148 ///
6149 /// let mut tensor = Tensor::external(ErasedHostTensor::new(
6150 /// TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
6151 /// ));
6152 /// assert!(tensor.external_payload_mut().is_some());
6153 /// # Ok::<(), tenferro_tensor::Error>(())
6154 /// ```
6155 #[must_use]
6156 pub fn external_payload_mut(&mut self) -> Option<&mut crate::ErasedHostTensor> {
6157 match &mut self.payload {
6158 TensorPayload::External(payload, _) => Some(payload),
6159 TensorPayload::Native(_) => None,
6160 }
6161 }
6162
6163 pub(crate) fn into_group_parts(self) -> (AllocationGroup, DescriptorSlot) {
6164 match self.payload {
6165 TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.into_group_parts()),
6166 // INVARIANT: a caller-owned payload has no allocation group.
6167 TensorPayload::External(..) => {
6168 unreachable!("an externally defined payload has no allocation group")
6169 }
6170 }
6171 }
6172}
6173
6174/// Dynamic read-only borrowed tensor view.
6175///
6176/// `TensorView` keeps dtype erased while borrowing typed view metadata and
6177/// storage. Use [`TypedTensorView`] directly when the scalar type is statically
6178/// known.
6179///
6180/// # Examples
6181///
6182/// ```
6183/// use tenferro_tensor::{DType, TensorView, TypedTensorView};
6184///
6185/// let data = [1_i32, 2, 3, 4];
6186/// let typed = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data)?;
6187/// let view = TensorView::I32(typed);
6188///
6189/// assert_eq!(view.dtype(), DType::I32);
6190/// assert_eq!(view.shape(), &[2, 2]);
6191/// # Ok::<(), tenferro_tensor::Error>(())
6192/// ```
6193#[derive(Clone, Debug)]
6194pub enum TensorView<'a> {
6195 F32(TypedTensorView<'a, f32>),
6196 F64(TypedTensorView<'a, f64>),
6197 I32(TypedTensorView<'a, i32>),
6198 I64(TypedTensorView<'a, i64>),
6199 Bool(TypedTensorView<'a, bool>),
6200 C32(TypedTensorView<'a, Complex<f32>>),
6201 C64(TypedTensorView<'a, Complex<f64>>),
6202}
6203
6204/// Dynamic mutable borrowed tensor view.
6205///
6206/// `TensorViewMut` is the mutable counterpart to [`TensorView`]. It keeps the
6207/// dtype erased while preserving the typed mutable view's shape, strides, and
6208/// offset metadata.
6209///
6210/// # Examples
6211///
6212/// ```
6213/// use tenferro_tensor::{DType, TensorViewMut, TypedTensorViewMut};
6214///
6215/// let mut data = [1.0_f64, 2.0];
6216/// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?);
6217/// assert_eq!(view.dtype(), DType::F64);
6218/// # Ok::<(), tenferro_tensor::Error>(())
6219/// ```
6220#[allow(clippy::large_enum_variant)]
6221#[derive(Debug)]
6222pub enum TensorViewMut<'a> {
6223 F32(TypedTensorViewMut<'a, f32>),
6224 F64(TypedTensorViewMut<'a, f64>),
6225 I32(TypedTensorViewMut<'a, i32>),
6226 I64(TypedTensorViewMut<'a, i64>),
6227 Bool(TypedTensorViewMut<'a, bool>),
6228 C32(TypedTensorViewMut<'a, Complex<f32>>),
6229 C64(TypedTensorViewMut<'a, Complex<f64>>),
6230}
6231
6232/// Read-only tensor input accepted by synchronous eager kernels.
6233///
6234/// `TensorRead` lets kernels accept either an owned tensor reference or a
6235/// borrowed [`TensorView`] without forcing callers to materialize first.
6236/// The `View` variant preserves arbitrary strides and offsets, so kernels that
6237/// support strided reads can consume transposes, slices, and broadcasts directly.
6238///
6239/// `TensorRead` is intentionally borrowed. It is an input-dispatch type, not an
6240/// owned lazy tensor value. APIs that need to store a lazy layout result should
6241/// keep an owned base tensor plus layout metadata, then expose a `TensorRead`
6242/// only for the duration of kernel dispatch.
6243///
6244/// # Examples
6245///
6246/// ```
6247/// use tenferro_tensor::{DType, Tensor, TensorRead};
6248///
6249/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
6250/// let read = TensorRead::from_tensor(&tensor);
6251///
6252/// assert_eq!(read.dtype(), DType::F64);
6253/// assert_eq!(read.shape(), &[2]);
6254/// ```
6255// Keep borrowed views inline to avoid allocation on read-only tensor dispatch paths.
6256#[allow(clippy::large_enum_variant)]
6257#[derive(Clone, Debug)]
6258pub enum TensorRead<'a> {
6259 Tensor(&'a Tensor),
6260 View(TensorView<'a>),
6261}
6262
6263/// Mutable typed tensor output accepted by synchronous eager kernels.
6264///
6265/// `TypedTensorWrite` is the typed counterpart to [`TensorWrite`]. It accepts
6266/// either an owned compact [`TypedTensor`] or an arbitrary-strided mutable
6267/// [`TypedTensorViewMut`] without erasing the scalar type at the public API
6268/// boundary.
6269///
6270/// # Examples
6271///
6272/// ```
6273/// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
6274///
6275/// let mut data = [0.0_f64, 1.0, 0.0, 2.0];
6276/// let view = TypedTensorViewMut::from_slice([2], [2], 1, &mut data)?;
6277/// let write = TypedTensorWrite::from_view(view).into_tensor_write();
6278/// assert_eq!(write.shape(), &[2]);
6279/// assert_eq!(write.strides()?, [2]);
6280/// # Ok::<(), tenferro_tensor::Error>(())
6281/// ```
6282#[allow(clippy::large_enum_variant)]
6283#[derive(Debug)]
6284pub enum TypedTensorWrite<'a, T> {
6285 /// An owned compact typed tensor borrowed mutably for the write.
6286 Tensor(&'a mut TypedTensor<T>),
6287 /// An arbitrary-strided mutable typed tensor view.
6288 View(TypedTensorViewMut<'a, T>),
6289}
6290
6291impl<'a, T> TypedTensorWrite<'a, T> {
6292 /// Create a writable target from an owned typed tensor.
6293 ///
6294 /// # Examples
6295 ///
6296 /// ```
6297 /// use tenferro_tensor::{TypedTensor, TypedTensorWrite};
6298 ///
6299 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0])?;
6300 /// let write = TypedTensorWrite::from_tensor(&mut tensor);
6301 /// assert!(matches!(write, TypedTensorWrite::Tensor(_)));
6302 /// # Ok::<(), tenferro_tensor::Error>(())
6303 /// ```
6304 pub fn from_tensor(tensor: &'a mut TypedTensor<T>) -> Self {
6305 Self::Tensor(tensor)
6306 }
6307
6308 /// Create a writable target from a mutable typed tensor view.
6309 ///
6310 /// # Examples
6311 ///
6312 /// ```
6313 /// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
6314 ///
6315 /// let mut data = [0.0_f64, 1.0];
6316 /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
6317 /// let write = TypedTensorWrite::from_view(view);
6318 /// assert!(matches!(write, TypedTensorWrite::View(_)));
6319 /// # Ok::<(), tenferro_tensor::Error>(())
6320 /// ```
6321 pub fn from_view(view: TypedTensorViewMut<'a, T>) -> Self {
6322 Self::View(view)
6323 }
6324}
6325
6326impl<'a, T: TensorScalar> TypedTensorWrite<'a, T> {
6327 /// Erase the scalar type while preserving the output layout.
6328 ///
6329 /// # Examples
6330 ///
6331 /// ```
6332 /// use tenferro_tensor::{DType, TypedTensor, TypedTensorWrite};
6333 ///
6334 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![0.0; 2])?;
6335 /// let write = TypedTensorWrite::from_tensor(&mut tensor).into_tensor_write();
6336 /// assert_eq!(write.dtype(), DType::F64);
6337 /// assert_eq!(write.shape(), &[2]);
6338 /// # Ok::<(), tenferro_tensor::Error>(())
6339 /// ```
6340 pub fn into_tensor_write(self) -> TensorWrite<'a> {
6341 match self {
6342 Self::Tensor(tensor) => T::tensor_write(tensor),
6343 Self::View(view) => TensorWrite::from_view(T::tensor_view_mut(view)),
6344 }
6345 }
6346}
6347
6348impl<'a, T> From<&'a mut TypedTensor<T>> for TypedTensorWrite<'a, T> {
6349 fn from(tensor: &'a mut TypedTensor<T>) -> Self {
6350 Self::from_tensor(tensor)
6351 }
6352}
6353
6354impl<'a, T> From<TypedTensorViewMut<'a, T>> for TypedTensorWrite<'a, T> {
6355 fn from(view: TypedTensorViewMut<'a, T>) -> Self {
6356 Self::from_view(view)
6357 }
6358}
6359
6360/// Mutable tensor output accepted by synchronous eager kernels.
6361///
6362/// `TensorWrite` mirrors [`TensorRead`] for output dispatch: it can target an
6363/// owned compact [`Tensor`] or a borrowed mutable [`TensorViewMut`]. The target
6364/// is never resized.
6365///
6366/// # Examples
6367///
6368/// ```
6369/// use tenferro_tensor::{Tensor, TensorWrite};
6370///
6371/// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
6372/// let write = TensorWrite::from_tensor(&mut tensor);
6373/// assert_eq!(write.shape(), &[1]);
6374/// # Ok::<(), tenferro_tensor::Error>(())
6375/// ```
6376#[allow(clippy::large_enum_variant)]
6377#[derive(Debug)]
6378pub enum TensorWrite<'a> {
6379 Tensor(&'a mut Tensor),
6380 View(TensorViewMut<'a>),
6381}
6382
6383/// Owned tensor value with one move-only physical owner and metadata-only layout.
6384///
6385/// `TensorValue` is intentionally not cloneable. View transformations consume
6386/// the value and move its existing owner; [`TensorValue::duplicate`] is the
6387/// explicit boundary for creating another physical allocation.
6388///
6389/// # Examples
6390///
6391/// ```
6392/// use tenferro_tensor::{Tensor, TensorValue};
6393/// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6394/// let view = value.transpose_view([1, 0])?;
6395/// assert_eq!(view.strides(), &[2, 1]);
6396/// assert!(view.is_view());
6397/// # Ok::<(), tenferro_tensor::Error>(())
6398/// ```
6399#[derive(Debug)]
6400pub struct TensorValue {
6401 owner: Tensor,
6402 layout: TensorLayout<DynRank>,
6403}
6404
6405/// A consuming view transformation failed while retaining its unchanged owner.
6406///
6407/// # Examples
6408///
6409/// ```
6410/// use tenferro_tensor::{Tensor, TensorValue};
6411/// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6412/// let failure = value.try_reshape_view([3]).unwrap_err();
6413/// let (recovered, cause) = failure.into_parts();
6414/// assert_eq!(recovered.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6415/// assert!(matches!(cause, tenferro_tensor::Error::Validation { .. }));
6416/// # Ok::<(), tenferro_tensor::Error>(())
6417/// ```
6418#[derive(Debug)]
6419pub struct TensorValueViewError {
6420 value: TensorValue,
6421 source: crate::Error,
6422}
6423
6424impl TensorValueViewError {
6425 /// Return the unchanged value and the typed validation/backend error.
6426 ///
6427 /// # Examples
6428 ///
6429 /// ```
6430 /// use tenferro_tensor::{Tensor, TensorValue};
6431 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([1], vec![7.])?);
6432 /// let (value, error) = value.try_reshape_view([2]).unwrap_err().into_parts();
6433 /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[7.]);
6434 /// assert!(matches!(error, tenferro_tensor::Error::Validation { .. }));
6435 /// # Ok::<(), tenferro_tensor::Error>(())
6436 /// ```
6437 pub fn into_parts(self) -> (TensorValue, crate::Error) {
6438 (self.value, self.source)
6439 }
6440
6441 fn new(value: TensorValue, source: crate::Error) -> Self {
6442 Self { value, source }
6443 }
6444}
6445
6446impl std::fmt::Display for TensorValueViewError {
6447 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6448 std::fmt::Display::fmt(&self.source, formatter)
6449 }
6450}
6451
6452impl std::error::Error for TensorValueViewError {
6453 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
6454 Some(&self.source)
6455 }
6456}
6457
6458impl TensorValue {
6459 /// Explicitly duplicate the physical owner represented by this value.
6460 ///
6461 /// # Examples
6462 ///
6463 /// ```
6464 /// use tenferro_tensor::{Tensor, TensorValue};
6465 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6466 /// let copy = value.duplicate()?.into_tensor()?;
6467 /// assert_eq!(copy.as_slice::<f64>()?, &[3., 4.]);
6468 /// assert_eq!(value.shape(), &[2]);
6469 /// # Ok::<(), tenferro_tensor::Error>(())
6470 /// ```
6471 ///
6472 /// # Errors
6473 ///
6474 /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
6475 /// when the backend/storage owner cannot be duplicated.
6476 pub fn duplicate(&self) -> crate::Result<Self> {
6477 let tensor = duplicate_backing_storage(&self.owner)?;
6478 Self::from_parts(
6479 tensor,
6480 self.shape().to_vec(),
6481 self.strides().to_vec(),
6482 self.offset(),
6483 )
6484 }
6485
6486 /// Retain a compact tensor as a move-only value.
6487 ///
6488 /// # Examples
6489 ///
6490 /// ```
6491 /// use tenferro_tensor::{Tensor, TensorValue};
6492 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6493 /// assert!(!value.is_view());
6494 /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6495 /// # Ok::<(), tenferro_tensor::Error>(())
6496 /// ```
6497 pub fn from_tensor(tensor: Tensor) -> Self {
6498 let layout = tensor_layout(&tensor);
6499 Self {
6500 owner: tensor,
6501 layout,
6502 }
6503 }
6504
6505 /// # Errors
6506 ///
6507 /// Returns [`ValidationError::InvalidArgument`] or
6508 /// [`ValidationError::IntegerOverflow`] when the supplied layout is
6509 /// invalid for the tensor's physical buffer.
6510 ///
6511 /// # Examples
6512 ///
6513 /// ```
6514 /// use tenferro_tensor::{Tensor, TensorValue};
6515 /// let tensor = Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?;
6516 /// let view = TensorValue::from_parts(tensor, vec![2], vec![1], 2)?;
6517 /// assert_eq!(view.shape(), &[2]);
6518 /// assert_eq!(view.offset(), 2);
6519 /// # Ok::<(), tenferro_tensor::Error>(())
6520 /// ```
6521 pub fn from_parts(
6522 tensor: Tensor,
6523 shape: Vec<usize>,
6524 strides: Vec<isize>,
6525 offset: isize,
6526 ) -> crate::Result<Self> {
6527 let layout = TensorLayout::from_parts(
6528 shape_vec(&shape),
6529 stride_vec(&strides),
6530 offset,
6531 tensor_buffer_len(&tensor),
6532 )
6533 .map_err(|err| tensor_layout_error("TensorValue::from_parts", err))?;
6534 Ok(Self {
6535 owner: tensor,
6536 layout,
6537 })
6538 }
6539
6540 // INVARIANT: unchanged-owner recovery is part of the consuming ownership
6541 // contract, so this intentionally carries the large move-only error.
6542 #[doc(hidden)]
6543 /// Move the value's sole physical owner into an allocation group.
6544 ///
6545 /// This preserves metadata-only views without copying. The consumed value
6546 /// always contains one unique owner, so no compatibility fallback exists.
6547 ///
6548 /// # Errors
6549 ///
6550 /// Returns the unchanged value when descriptor publication fails because
6551 /// of [`ValidationError::InvalidArgument`] or
6552 /// [`ValidationError::IntegerOverflow`].
6553 #[allow(clippy::result_large_err)]
6554 pub fn try_into_group_parts(
6555 self,
6556 ) -> std::result::Result<(AllocationGroup, DescriptorSlot, DType, Vec<usize>), Self> {
6557 let Self { owner, layout } = self;
6558 if owner.external_payload().is_some() {
6559 // A caller-owned payload owns no allocation group, so it is returned
6560 // unchanged instead of being forced into one.
6561 return Err(Self { owner, layout });
6562 }
6563 let dtype = owner.dtype();
6564 let shape = layout.shape().to_vec();
6565 let strides = layout.strides().to_vec();
6566 let offset = layout.offset();
6567 let (group, slot) = owner.into_group_parts();
6568 match group.update_descriptor_layout(slot, shape, strides, offset) {
6569 Ok(group) => Ok((group, slot, dtype, layout.shape().to_vec())),
6570 Err((_group, _error)) => {
6571 unreachable!("TensorValue layout was validated before group ownership transfer")
6572 }
6573 }
6574 }
6575
6576 /// Consume a value with its owner's original layout and return that owner.
6577 ///
6578 /// # Examples
6579 ///
6580 /// ```
6581 /// use tenferro_tensor::{Tensor, TensorValue};
6582 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6583 /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6584 /// # Ok::<(), tenferro_tensor::Error>(())
6585 /// ```
6586 ///
6587 /// # Errors
6588 ///
6589 /// Returns [`crate::Error::Unsupported`] when the value's metadata-only
6590 /// view differs from its physical owner's layout.
6591 pub fn into_tensor(self) -> crate::Result<Tensor> {
6592 if self.layout != tensor_layout(&self.owner) {
6593 return Err(crate::Error::unsupported(
6594 "TensorValue::into_tensor",
6595 "a metadata-only view has no compact tensor owner",
6596 ));
6597 }
6598 Ok(self.owner)
6599 }
6600
6601 ///
6602 /// # Examples
6603 ///
6604 /// ```
6605 /// use tenferro_tensor::{Tensor, TensorValue};
6606 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6607 /// assert_eq!(value.as_tensor().unwrap().as_slice::<f64>()?, &[1., 2., 3., 4.]);
6608 /// assert!(value.transpose_view([1, 0])?.as_tensor().is_none());
6609 /// # Ok::<(), tenferro_tensor::Error>(())
6610 /// ```
6611 pub fn as_tensor(&self) -> Option<&Tensor> {
6612 (self.layout == tensor_layout(&self.owner)).then_some(&self.owner)
6613 }
6614
6615 ///
6616 /// # Examples
6617 ///
6618 /// ```
6619 /// use tenferro_tensor::{Tensor, TensorValue};
6620 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6621 /// assert!(!value.is_view());
6622 /// assert!(value.transpose_view([1, 0])?.is_view());
6623 /// # Ok::<(), tenferro_tensor::Error>(())
6624 /// ```
6625 pub fn is_view(&self) -> bool {
6626 self.as_tensor().is_none()
6627 }
6628
6629 ///
6630 /// # Examples
6631 ///
6632 /// ```
6633 /// use tenferro_tensor::{Tensor, TensorValue};
6634 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6635 /// assert_eq!(value.dtype(), tenferro_tensor::DType::F64);
6636 /// # Ok::<(), tenferro_tensor::Error>(())
6637 /// ```
6638 pub fn dtype(&self) -> DType {
6639 self.owner.dtype()
6640 }
6641
6642 ///
6643 /// # Examples
6644 ///
6645 /// ```
6646 /// use tenferro_tensor::{Tensor, TensorValue};
6647 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6648 /// assert_eq!(value.reshape_view([4])?.shape(), &[4]);
6649 /// # Ok::<(), tenferro_tensor::Error>(())
6650 /// ```
6651 pub fn shape(&self) -> &[usize] {
6652 self.layout.shape()
6653 }
6654
6655 ///
6656 /// # Examples
6657 ///
6658 /// ```
6659 /// use tenferro_tensor::{Tensor, TensorValue};
6660 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6661 /// assert_eq!(value.transpose_view([1, 0])?.strides(), &[2, 1]);
6662 /// # Ok::<(), tenferro_tensor::Error>(())
6663 /// ```
6664 pub fn strides(&self) -> &[isize] {
6665 self.layout.strides()
6666 }
6667
6668 ///
6669 /// # Examples
6670 ///
6671 /// ```
6672 /// use tenferro_tensor::{Tensor, TensorValue};
6673 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6674 /// assert_eq!(value.offset(), 0);
6675 /// # Ok::<(), tenferro_tensor::Error>(())
6676 /// ```
6677 pub fn offset(&self) -> isize {
6678 self.layout.offset()
6679 }
6680
6681 ///
6682 /// # Examples
6683 ///
6684 /// ```
6685 /// use tenferro_tensor::{Tensor, TensorValue};
6686 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6687 /// let view = value.tensor_view();
6688 /// assert_eq!(view.shape(), &[2, 2]);
6689 /// assert_eq!(view.dtype(), tenferro_tensor::DType::F64);
6690 /// # Ok::<(), tenferro_tensor::Error>(())
6691 /// ```
6692 pub fn tensor_view(&self) -> TensorView<'_> {
6693 tensor_view_with_layout(&self.owner, self.layout.clone())
6694 }
6695
6696 ///
6697 /// # Examples
6698 ///
6699 /// ```
6700 /// use tenferro_tensor::{Tensor, TensorValue};
6701 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6702 /// let read = value.tensor_read();
6703 /// assert_eq!(read.shape(), &[2, 2]);
6704 /// assert_eq!(read.dtype(), tenferro_tensor::DType::F64);
6705 /// # Ok::<(), tenferro_tensor::Error>(())
6706 /// ```
6707 pub fn tensor_read(&self) -> TensorRead<'_> {
6708 self.as_tensor()
6709 .map(TensorRead::from_tensor)
6710 .unwrap_or_else(|| TensorRead::from_view(self.tensor_view()))
6711 }
6712
6713 /// # Errors
6714 ///
6715 /// Returns [`crate::Error::Validation`] with
6716 /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
6717 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
6718 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
6719 /// not a valid permutation of the value rank.
6720 ///
6721 /// # Examples
6722 ///
6723 /// ```
6724 /// use tenferro_tensor::{Tensor, TensorValue};
6725 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6726 /// let view = value.transpose_view([1, 0])?;
6727 /// assert_eq!(view.strides(), &[2, 1]);
6728 /// assert!(view.is_view());
6729 /// # Ok::<(), tenferro_tensor::Error>(())
6730 /// ```
6731 pub fn transpose_view(self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
6732 let layout = self
6733 .layout
6734 .transpose_view(axes)
6735 .map_err(|err| tensor_layout_error("TensorValue::transpose_view", err))?;
6736 Ok(Self {
6737 owner: self.owner,
6738 layout,
6739 })
6740 }
6741
6742 // INVARIANT: unchanged-owner recovery is part of the consuming view
6743 // contract, so this intentionally carries the large move-only error.
6744 /// # Errors
6745 ///
6746 /// Returns [`crate::Error::Validation`] with
6747 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
6748 /// the source is not compact column-major,
6749 /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
6750 /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
6751 /// records the counts) when element counts differ,
6752 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
6753 /// arithmetic overflow, or
6754 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6755 /// reshaped view exceeds the backing buffer.
6756 #[allow(clippy::result_large_err)]
6757 ///
6758 /// # Examples
6759 ///
6760 /// ```
6761 /// use tenferro_tensor::{Tensor, TensorValue};
6762 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6763 /// let (recovered, error) = value.try_reshape_view([3]).unwrap_err().into_parts();
6764 /// assert_eq!(recovered.into_tensor()?.as_slice::<f64>()?, &[1., 2., 3., 4.]);
6765 /// assert!(matches!(error, tenferro_tensor::Error::Validation { .. }));
6766 /// # Ok::<(), tenferro_tensor::Error>(())
6767 /// ```
6768 pub fn try_reshape_view(
6769 self,
6770 shape: impl tenferro_tensor_core::IntoShapeVec,
6771 ) -> std::result::Result<Self, TensorValueViewError> {
6772 let shape = shape.into_shape_vec();
6773 let layout = match reshape_layout_dyn(
6774 &self.layout,
6775 &shape,
6776 tensor_buffer_len(&self.owner),
6777 "TensorValue::reshape_view",
6778 ) {
6779 Ok(layout) => layout,
6780 Err(error) => return Err(TensorValueViewError::new(self, error)),
6781 };
6782 Ok(Self {
6783 owner: self.owner,
6784 layout,
6785 })
6786 }
6787
6788 /// # Errors
6789 ///
6790 /// Returns [`crate::Error::Validation`] with
6791 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]
6792 /// when the source is not compact, [`tenferro_tensor_core::ValidationError::ShapeMismatch`]
6793 /// when element counts differ, or [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
6794 /// / [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] for invalid
6795 /// target-shape arithmetic or bounds.
6796 ///
6797 /// # Examples
6798 ///
6799 /// ```
6800 /// use tenferro_tensor::{Tensor, TensorValue};
6801 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6802 /// let view = value.reshape_view([4])?;
6803 /// assert_eq!(view.shape(), &[4]);
6804 /// assert_eq!(view.strides(), &[1]);
6805 /// # Ok::<(), tenferro_tensor::Error>(())
6806 /// ```
6807 pub fn reshape_view(
6808 self,
6809 shape: impl tenferro_tensor_core::IntoShapeVec,
6810 ) -> crate::Result<Self> {
6811 self.try_reshape_view(shape).map_err(|error| error.source)
6812 }
6813
6814 /// # Errors
6815 ///
6816 /// Returns [`crate::Error::Validation`] with
6817 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a slice
6818 /// vector does not match the value rank,
6819 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when a bound
6820 /// or stride cannot be represented or is invalid,
6821 /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
6822 /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for slice
6823 /// parameters, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
6824 /// for slice arithmetic overflow, or
6825 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6826 /// result exceeds the backing buffer.
6827 ///
6828 /// # Examples
6829 ///
6830 /// ```
6831 /// use tenferro_tensor::{Tensor, TensorValue};
6832 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6833 /// let view = value.slice_view(&tenferro_tensor::SliceConfig {
6834 /// starts: vec![0, 1], limits: vec![2, 2], strides: vec![1, 1],
6835 /// })?;
6836 /// assert_eq!(view.shape(), &[2, 1]);
6837 /// assert_eq!(view.offset(), 2);
6838 /// # Ok::<(), tenferro_tensor::Error>(())
6839 /// ```
6840 pub fn slice_view(self, config: &SliceConfig) -> crate::Result<Self> {
6841 let op = "TensorValue::slice_view";
6842 if config.starts.len() != self.shape().len()
6843 || config.limits.len() != self.shape().len()
6844 || config.strides.len() != self.shape().len()
6845 {
6846 return Err(crate::Error::validation(
6847 op,
6848 ValidationError::RankMismatch {
6849 expected: self.shape().len(),
6850 actual: config.starts.len(),
6851 },
6852 ));
6853 }
6854 // The per-axis specs stay inline: a metadata-only slice must not
6855 // allocate its own scratch at rank <= 8 (#2040).
6856 let mut specs = TinyVec::<[CoreSliceSpec; 8]>::with_capacity(self.shape().len());
6857 for (((&start, &limit), &stride), &axis_len) in config
6858 .starts
6859 .iter()
6860 .zip(config.limits.iter())
6861 .zip(config.strides.iter())
6862 .zip(self.shape())
6863 {
6864 let start = isize::try_from(start).map_err(|_| {
6865 crate::Error::invalid_argument(
6866 op,
6867 "slice start",
6868 "slice start does not fit in isize",
6869 )
6870 })?;
6871 let limit = isize::try_from(limit).map_err(|_| {
6872 crate::Error::invalid_argument(
6873 op,
6874 "slice limit",
6875 "slice limit does not fit in isize",
6876 )
6877 })?;
6878 let stride = isize::try_from(stride).map_err(|_| {
6879 crate::Error::invalid_argument(
6880 op,
6881 "slice stride",
6882 "slice stride does not fit in isize",
6883 )
6884 })?;
6885 specs.push(core_slice_spec(
6886 StridedSliceSpec::new(start, Some(limit), stride),
6887 axis_len,
6888 op,
6889 )?);
6890 }
6891 let layout = self
6892 .layout
6893 .slice_view(&specs, tensor_buffer_len(&self.owner))
6894 .map_err(|err| tensor_layout_error(op, err))?;
6895 Ok(Self {
6896 owner: self.owner,
6897 layout,
6898 })
6899 }
6900
6901 /// # Errors
6902 ///
6903 /// Returns [`crate::Error::Validation`] with
6904 /// [`tenferro_tensor_core::ValidationError::RankMismatch`],
6905 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
6906 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] for invalid
6907 /// dimension mappings,
6908 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] for
6909 /// incompatible extents,
6910 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6911 /// result exceeds the backing buffer, or
6912 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
6913 /// arithmetic overflow.
6914 ///
6915 /// # Examples
6916 ///
6917 /// ```
6918 /// use tenferro_tensor::{Tensor, TensorValue};
6919 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6920 /// let view = value.broadcast_in_dim_view([2, 3], [0])?;
6921 /// assert_eq!(view.shape(), &[2, 3]);
6922 /// assert_eq!(view.strides(), &[1, 0]);
6923 /// # Ok::<(), tenferro_tensor::Error>(())
6924 /// ```
6925 pub fn broadcast_in_dim_view(
6926 self,
6927 shape: impl tenferro_tensor_core::IntoShapeVec,
6928 dims: impl AsRef<[usize]>,
6929 ) -> crate::Result<Self> {
6930 let shape = shape.into_shape_vec();
6931 let layout = self
6932 .layout
6933 .broadcast_in_dim_view::<DynRank>(shape, dims, tensor_buffer_len(&self.owner))
6934 .map_err(|err| tensor_layout_error("TensorValue::broadcast_in_dim_view", err))?;
6935 Ok(Self {
6936 owner: self.owner,
6937 layout,
6938 })
6939 }
6940}
6941
6942/// Copy a value's backing storage into a fresh owned tensor carrying `layout`.
6943///
6944/// A value layout is relative to its owner's backing root. For a plain compact
6945/// owner that root is the owner itself, and [`TypedTensor::duplicate`] is
6946/// enough. A root-preserving extraction (#2023) exports only its descriptor
6947/// extent over a wider root, so the copy has to carry the root and be rebased
6948/// onto the same descriptor layout.
6949fn duplicate_backing_storage(tensor: &Tensor) -> crate::Result<Tensor> {
6950 fn typed<T: TensorScalar>(tensor: &TypedTensor<T>) -> crate::Result<Tensor> {
6951 // The owner's descriptor, not the outer value layout: the copy is an
6952 // owner of the same kind, and a value layout may be a strided view over
6953 // it that the owner must not adopt.
6954 let layout = tensor.physical_layout();
6955 // `as_view().host_storage()` reports the whole root for a host owner
6956 // (including one produced by an owning reinterpretation) and refuses a
6957 // backend owner, which `buffer()` cannot express.
6958 let root = tensor.as_view().host_storage()?.to_vec();
6959 let op = "TensorValue::duplicate";
6960 let (group, slot) =
6961 AllocationGroup::from_host_vec::<T, DynRank>(shape_vec(&[root.len()]), root)
6962 .map_err(|error| crate::Error::runtime_state_source(op, error))?;
6963 let group = group
6964 .update_descriptor_layout(
6965 slot,
6966 layout.shape().to_vec(),
6967 layout.strides().to_vec(),
6968 layout.offset(),
6969 )
6970 .map_err(|(_, error)| crate::Error::runtime_state_source(op, error))?;
6971 let allocation = group
6972 .allocation_index(slot)
6973 .unwrap_or_else(|_| unreachable!("the duplicate group carries a live descriptor"));
6974 let descriptor_layout = group
6975 .descriptor_layout(slot)
6976 .unwrap_or_else(|| unreachable!("the duplicate group carries a live descriptor"));
6977 let placement = tensor.placement().clone();
6978 Ok(tensor_from_group(
6979 group,
6980 slot,
6981 allocation,
6982 T::dtype(),
6983 descriptor_layout,
6984 placement,
6985 ))
6986 }
6987
6988 match &tensor.payload {
6989 // A caller-owned payload keeps its existing duplication route: it has no
6990 // allocation group to rebuild.
6991 TensorPayload::External(..) => tensor.duplicate(),
6992 TensorPayload::Native(preset) => with_preset!(preset, |inner| typed(inner)),
6993 }
6994}
6995
6996fn tensor_layout(tensor: &Tensor) -> TensorLayout<DynRank> {
6997 match &tensor.payload {
6998 // A caller-owned payload carries its own view layout, so strides and offset
6999 // come from the payload rather than from a compact assumption.
7000 TensorPayload::External(payload, _) => TensorLayout::<DynRank>::from_parts(
7001 shape_vec(payload.shape()),
7002 stride_vec(payload.strides()),
7003 payload.offset(),
7004 payload.payload_element_count(),
7005 )
7006 .unwrap_or_else(|_| {
7007 // INVARIANT: a payload is built from a validated host tensor, and the
7008 // only layout change is a permutation, so its strides stay inside the
7009 // payload it was created from and this construction cannot fail.
7010 unreachable!("a validated payload yields a layout inside its own storage")
7011 }),
7012 TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.physical_layout()),
7013 }
7014}
7015
7016fn tensor_buffer_len(tensor: &Tensor) -> usize {
7017 match &tensor.payload {
7018 // A caller-owned payload stores its element count directly.
7019 TensorPayload::External(payload, _) => payload.element_count(),
7020 TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.buffer_len()),
7021 }
7022}
7023
7024fn prepare_backend_access<'a, T: 'static, R: TensorRank>(
7025 buffer: &'a dyn BackendStorage<T>,
7026 layout: &'a TensorLayout<R>,
7027 op: &'static str,
7028) -> crate::Result<Box<dyn PreparedDeviceAccess + 'a>> {
7029 let domain = buffer.allocation_domain().ok_or_else(|| {
7030 crate::Error::runtime_state(op, "backend buffer is missing an allocation domain")
7031 })?;
7032 let allocation_id = buffer.allocation_id().ok_or_else(|| {
7033 crate::Error::runtime_state(op, "backend buffer is missing an allocation identity")
7034 })?;
7035 let byte_len = buffer
7036 .len()
7037 .checked_mul(size_of::<T>())
7038 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
7039 let request = DeviceAccessRequest::new(
7040 domain,
7041 allocation_id,
7042 byte_len,
7043 size_of::<T>(),
7044 layout.shape(),
7045 layout.strides(),
7046 layout.offset(),
7047 );
7048 buffer
7049 .prepare_device_access(request)
7050 .map_err(|error| crate::Error::runtime_state_source(op, error))
7051}
7052
7053fn cast_view_slice<S: 'static, T: TensorScalar>(source: &[S]) -> crate::Result<&[T]> {
7054 if size_of::<S>() != size_of::<T>() || align_of::<S>() != align_of::<T>() {
7055 return Err(crate::Error::invalid_argument(
7056 "TensorView::as_slice",
7057 "dtype",
7058 "matching dtypes must have identical scalar layout",
7059 ));
7060 }
7061 // SAFETY: the dtype check above is exhaustive over the sealed scalar set;
7062 // equal size/alignment preserve the element boundaries and the source
7063 // slice remains borrowed for the returned lifetime.
7064 Ok(unsafe { std::slice::from_raw_parts(source.as_ptr().cast::<T>(), source.len()) })
7065}
7066
7067/// Re-layout a borrowed typed view while keeping its buffer, retained root and
7068/// placement.
7069///
7070/// #2143 item 1: an eager implicit broadcast is a metadata change, so the view
7071/// keeps reading the same storage through zero strides instead of copying the
7072/// broadcast tensor into a fresh allocation.
7073fn broadcast_typed_view<'a, T: 'static, R: TensorRank>(
7074 view: TypedTensorView<'a, T, R>,
7075 shape: &[usize],
7076 dims: &[usize],
7077) -> crate::Result<TypedTensorView<'a, T, R>> {
7078 let shape = R::shape_from_vec(shape_vec(shape))
7079 .map_err(|err| tensor_layout_error("TensorRead::broadcast_in_dim_view", err))?;
7080 let layout = view
7081 .layout()
7082 .broadcast_in_dim_view::<R>(shape, dims, view.backing_len())
7083 .map_err(|err| tensor_layout_error("TensorRead::broadcast_in_dim_view", err))?;
7084 Ok(TypedTensorView {
7085 buffer: view.buffer,
7086 root: view.root,
7087 layout,
7088 placement: view.placement,
7089 _representation: std::marker::PhantomData,
7090 })
7091}
7092
7093fn tensor_view_with_layout(tensor: &Tensor, layout: TensorLayout<DynRank>) -> TensorView<'_> {
7094 match tensor.dtype() {
7095 DType::F32 => TensorView::F32(typed_view_with_layout(
7096 tensor
7097 .as_typed::<f32>()
7098 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7099 layout,
7100 )),
7101 DType::F64 => TensorView::F64(typed_view_with_layout(
7102 tensor
7103 .as_typed::<f64>()
7104 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7105 layout,
7106 )),
7107 DType::I32 => TensorView::I32(typed_view_with_layout(
7108 tensor
7109 .as_typed::<i32>()
7110 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7111 layout,
7112 )),
7113 DType::I64 => TensorView::I64(typed_view_with_layout(
7114 tensor
7115 .as_typed::<i64>()
7116 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7117 layout,
7118 )),
7119 DType::Bool => TensorView::Bool(typed_view_with_layout(
7120 tensor
7121 .as_typed::<bool>()
7122 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7123 layout,
7124 )),
7125 DType::C32 => TensorView::C32(typed_view_with_layout(
7126 tensor
7127 .as_typed::<Complex<f32>>()
7128 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7129 layout,
7130 )),
7131 DType::C64 => TensorView::C64(typed_view_with_layout(
7132 tensor
7133 .as_typed::<Complex<f64>>()
7134 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
7135 layout,
7136 )),
7137 // INVARIANT: `TensorView` has no externally defined variant, and a view is
7138 // never requested for a caller-owned payload.
7139 DType::External(_) => unreachable!("views cover the preset scalars"),
7140 }
7141}
7142
7143fn typed_view_with_layout<T: TensorScalar + 'static>(
7144 tensor: &TypedTensor<T>,
7145 layout: TensorLayout<DynRank>,
7146) -> TypedTensorView<'_, T> {
7147 match &tensor.storage {
7148 DynamicStorage::Host(HostStorage { data }) => TypedTensorView {
7149 buffer: TensorStorageRef::Host(data.as_slice()),
7150 root: None,
7151 layout,
7152 placement: tensor.placement.clone(),
7153 _representation: std::marker::PhantomData,
7154 },
7155 DynamicStorage::Group(core) => {
7156 let root = core.group.view::<T>().unwrap_or_else(|error| {
7157 unreachable!("typed tensor group descriptor mismatch: {error}")
7158 });
7159 let buffer = if let Some(allocation) = root.backend_allocation() {
7160 TensorStorageRef::Root(allocation)
7161 } else {
7162 TensorStorageRef::Host(tensor.group_host_slice())
7163 };
7164 TypedTensorView {
7165 buffer,
7166 root: Some(root),
7167 layout,
7168 placement: tensor.placement.clone(),
7169 _representation: std::marker::PhantomData,
7170 }
7171 }
7172 }
7173}
7174
7175pub(crate) fn tensor_view_from_group<'a, T: TensorScalar>(
7176 view: GroupReadView<'a, T, DynRank>,
7177) -> crate::Result<TensorView<'a>> {
7178 let buffer = if let Some(allocation) = view.backend_allocation() {
7179 TensorStorageRef::Root(allocation)
7180 } else {
7181 let storage = view.storage_buffer();
7182 match storage {
7183 Some(StorageBuffer::Host(data)) => TensorStorageRef::Host(data),
7184 Some(StorageBuffer::Backend(buffer)) => TensorStorageRef::Backend(buffer.as_ref()),
7185 // A sealed representation reinterpretation changes the descriptor's
7186 // dtype while the host root keeps its own, so the typed downcast
7187 // above cannot see it.
7188 None => match view.host_root_slice() {
7189 Some(data) => TensorStorageRef::Host(data),
7190 None => {
7191 return Err(crate::Error::runtime_state(
7192 "AllocationGroup::tensor_read",
7193 "group descriptor has no backing storage",
7194 ));
7195 }
7196 },
7197 }
7198 };
7199 let layout = view.descriptor().layout().clone();
7200 let placement = view.descriptor().placement().clone();
7201 let typed = TypedTensorView {
7202 buffer,
7203 root: Some(view.clone()),
7204 layout,
7205 placement,
7206 _representation: std::marker::PhantomData,
7207 };
7208 Ok(T::tensor_view(typed))
7209}
7210
7211pub(crate) fn tensor_from_group(
7212 group: AllocationGroup,
7213 slot: DescriptorSlot,
7214 allocation_index: AllocationSlot,
7215 dtype: DType,
7216 layout: TensorLayout<DynRank>,
7217 placement: Placement,
7218) -> Tensor {
7219 fn typed<T: TensorScalar>(
7220 group: AllocationGroup,
7221 slot: DescriptorSlot,
7222 allocation_index: AllocationSlot,
7223 layout: TensorLayout<DynRank>,
7224 placement: Placement,
7225 ) -> TypedTensor<T> {
7226 let descriptor_dtype = group
7227 .descriptor_dtype(slot)
7228 .unwrap_or_else(|| unreachable!("tensor_from_group requires a live typed descriptor"));
7229 assert_eq!(
7230 descriptor_dtype,
7231 T::dtype(),
7232 "managed typed owner must match its descriptor dtype",
7233 );
7234 let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
7235 TypedTensor {
7236 shape: shape_vec(layout.shape()),
7237 placement,
7238 layout: stride_vec(layout.strides()),
7239 storage: DynamicStorage::Group(GroupStorage {
7240 group: Box::new(OwnedTensorGroup {
7241 group,
7242 slot,
7243 allocation_index,
7244 host_ptr,
7245 host_byte_len,
7246 _rank: PhantomData,
7247 }),
7248 }),
7249 }
7250 }
7251
7252 match dtype {
7253 DType::F32 => <f32 as TensorScalar>::typed_tensor_into_tensor(typed::<f32>(
7254 group,
7255 slot,
7256 allocation_index,
7257 layout,
7258 placement,
7259 )),
7260 DType::F64 => <f64 as TensorScalar>::typed_tensor_into_tensor(typed::<f64>(
7261 group,
7262 slot,
7263 allocation_index,
7264 layout,
7265 placement,
7266 )),
7267 DType::I32 => <i32 as TensorScalar>::typed_tensor_into_tensor(typed::<i32>(
7268 group,
7269 slot,
7270 allocation_index,
7271 layout,
7272 placement,
7273 )),
7274 DType::I64 => <i64 as TensorScalar>::typed_tensor_into_tensor(typed::<i64>(
7275 group,
7276 slot,
7277 allocation_index,
7278 layout,
7279 placement,
7280 )),
7281 DType::Bool => <bool as TensorScalar>::typed_tensor_into_tensor(typed::<bool>(
7282 group,
7283 slot,
7284 allocation_index,
7285 layout,
7286 placement,
7287 )),
7288 DType::C32 => <Complex<f32> as TensorScalar>::typed_tensor_into_tensor(
7289 typed::<Complex<f32>>(group, slot, allocation_index, layout, placement),
7290 ),
7291 DType::C64 => <Complex<f64> as TensorScalar>::typed_tensor_into_tensor(
7292 typed::<Complex<f64>>(group, slot, allocation_index, layout, placement),
7293 ),
7294 // INVARIANT: an allocation group is created from a sealed preset scalar,
7295 // so it can never carry an externally defined one. External payloads are
7296 // caller-owned and do not enter a group.
7297 DType::External(_) => unreachable!("allocation groups are preset-typed"),
7298 }
7299}
7300
7301/// Wrap an `f64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7302///
7303/// # Examples
7304///
7305/// ```
7306/// use tenferro_tensor::{Tensor, TypedTensor};
7307///
7308/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
7309/// let tensor: Tensor = typed.into();
7310/// assert_eq!(tensor.shape(), &[2]);
7311/// ```
7312impl From<TypedTensor<f64>> for Tensor {
7313 fn from(t: TypedTensor<f64>) -> Self {
7314 Tensor::from_typed(t)
7315 }
7316}
7317
7318/// Wrap an `f32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7319///
7320/// # Examples
7321///
7322/// ```
7323/// use tenferro_tensor::{Tensor, TypedTensor};
7324///
7325/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
7326/// let tensor: Tensor = typed.into();
7327/// assert_eq!(tensor.shape(), &[2]);
7328/// ```
7329impl From<TypedTensor<f32>> for Tensor {
7330 fn from(t: TypedTensor<f32>) -> Self {
7331 Tensor::from_typed(t)
7332 }
7333}
7334
7335/// Wrap an `i64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7336///
7337/// # Examples
7338///
7339/// ```
7340/// use tenferro_tensor::{DType, Tensor, TypedTensor};
7341///
7342/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i64, 2]).unwrap();
7343/// let tensor: Tensor = typed.into();
7344/// assert_eq!(tensor.dtype(), DType::I64);
7345/// assert_eq!(tensor.shape(), &[2]);
7346/// ```
7347impl From<TypedTensor<i64>> for Tensor {
7348 fn from(t: TypedTensor<i64>) -> Self {
7349 Tensor::from_typed(t)
7350 }
7351}
7352
7353/// Wrap an `i32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7354///
7355/// # Examples
7356///
7357/// ```
7358/// use tenferro_tensor::{DType, Tensor, TypedTensor};
7359///
7360/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i32, 2]).unwrap();
7361/// let tensor: Tensor = typed.into();
7362/// assert_eq!(tensor.dtype(), DType::I32);
7363/// assert_eq!(tensor.shape(), &[2]);
7364/// ```
7365impl From<TypedTensor<i32>> for Tensor {
7366 fn from(t: TypedTensor<i32>) -> Self {
7367 Tensor::from_typed(t)
7368 }
7369}
7370
7371/// Wrap a `bool` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7372///
7373/// # Examples
7374///
7375/// ```
7376/// use tenferro_tensor::{DType, Tensor, TypedTensor};
7377///
7378/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![true, false]).unwrap();
7379/// let tensor: Tensor = typed.into();
7380/// assert_eq!(tensor.dtype(), DType::Bool);
7381/// assert_eq!(tensor.shape(), &[2]);
7382/// ```
7383impl From<TypedTensor<bool>> for Tensor {
7384 fn from(t: TypedTensor<bool>) -> Self {
7385 Tensor::from_typed(t)
7386 }
7387}
7388
7389/// Wrap a [`Complex64`] [`TypedTensor`] into the corresponding [`Tensor`]
7390/// variant.
7391///
7392/// # Examples
7393///
7394/// ```
7395/// use num_complex::Complex64;
7396/// use tenferro_tensor::{Tensor, TypedTensor};
7397///
7398/// let typed = TypedTensor::from_vec_col_major(
7399/// vec![1],
7400/// vec![Complex64::new(1.0, 2.0)],
7401/// ).unwrap();
7402/// let tensor: Tensor = typed.into();
7403/// assert_eq!(tensor.shape(), &[1]);
7404/// ```
7405impl From<TypedTensor<Complex<f64>>> for Tensor {
7406 fn from(t: TypedTensor<Complex<f64>>) -> Self {
7407 Tensor::from_typed(t)
7408 }
7409}
7410
7411/// Wrap a [`Complex32`] [`TypedTensor`] into the corresponding [`Tensor`]
7412/// variant.
7413///
7414/// # Examples
7415///
7416/// ```
7417/// use num_complex::Complex32;
7418/// use tenferro_tensor::{Tensor, TypedTensor};
7419///
7420/// let typed = TypedTensor::from_vec_col_major(
7421/// vec![1],
7422/// vec![Complex32::new(1.0, 2.0)],
7423/// ).unwrap();
7424/// let tensor: Tensor = typed.into();
7425/// assert_eq!(tensor.shape(), &[1]);
7426/// ```
7427impl From<TypedTensor<Complex<f32>>> for Tensor {
7428 fn from(t: TypedTensor<Complex<f32>>) -> Self {
7429 Tensor::from_typed(t)
7430 }
7431}
7432
7433impl<'a> TensorView<'a> {
7434 /// Create a dynamic `f32` view over compact column-major host data.
7435 ///
7436 /// # Examples
7437 ///
7438 /// ```
7439 /// use tenferro_tensor::{DType, TensorView};
7440 ///
7441 /// let data = [1.0_f32, 2.0];
7442 /// let view = TensorView::f32(&[2], &data)?;
7443 /// assert_eq!(view.dtype(), DType::F32);
7444 /// # Ok::<(), tenferro_tensor::Error>(())
7445 /// ```
7446 /// # Errors
7447 ///
7448 /// Returns [`crate::Error::Validation`] with
7449 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7450 /// shape or offset arithmetic overflow, or
7451 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7452 /// compact shape reaches beyond `data`.
7453 pub fn f32(shape: &'a [usize], data: &'a [f32]) -> crate::Result<Self> {
7454 Ok(Self::F32(TypedTensorView::from_col_major(shape, data)?))
7455 }
7456
7457 /// Create a dynamic `f64` view over compact column-major host data.
7458 ///
7459 /// # Examples
7460 ///
7461 /// ```
7462 /// use tenferro_tensor::{DType, TensorView};
7463 ///
7464 /// let data = [1.0_f64, 2.0];
7465 /// let view = TensorView::f64(&[2], &data)?;
7466 /// assert_eq!(view.dtype(), DType::F64);
7467 /// # Ok::<(), tenferro_tensor::Error>(())
7468 /// ```
7469 /// # Errors
7470 ///
7471 /// Returns [`crate::Error::Validation`] with
7472 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7473 /// shape or offset arithmetic overflow, or
7474 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7475 /// compact shape reaches beyond `data`.
7476 pub fn f64(shape: &'a [usize], data: &'a [f64]) -> crate::Result<Self> {
7477 Ok(Self::F64(TypedTensorView::from_col_major(shape, data)?))
7478 }
7479
7480 /// Create a dynamic `i64` view over compact column-major host data.
7481 ///
7482 /// # Examples
7483 ///
7484 /// ```
7485 /// use tenferro_tensor::{DType, TensorView};
7486 ///
7487 /// let data = [1_i64, 2];
7488 /// let view = TensorView::i64(&[2], &data)?;
7489 /// assert_eq!(view.dtype(), DType::I64);
7490 /// # Ok::<(), tenferro_tensor::Error>(())
7491 /// ```
7492 /// # Errors
7493 ///
7494 /// Returns [`crate::Error::Validation`] with
7495 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7496 /// shape or offset arithmetic overflow, or
7497 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7498 /// compact shape reaches beyond `data`.
7499 pub fn i64(shape: &'a [usize], data: &'a [i64]) -> crate::Result<Self> {
7500 Ok(Self::I64(TypedTensorView::from_col_major(shape, data)?))
7501 }
7502
7503 /// Create a dynamic `i32` view over compact column-major host data.
7504 ///
7505 /// # Examples
7506 ///
7507 /// ```
7508 /// use tenferro_tensor::{DType, TensorView};
7509 ///
7510 /// let data = [1_i32, 2];
7511 /// let view = TensorView::i32(&[2], &data)?;
7512 /// assert_eq!(view.dtype(), DType::I32);
7513 /// # Ok::<(), tenferro_tensor::Error>(())
7514 /// ```
7515 /// # Errors
7516 ///
7517 /// Returns [`crate::Error::Validation`] with
7518 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7519 /// shape or offset arithmetic overflow, or
7520 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7521 /// compact shape reaches beyond `data`.
7522 pub fn i32(shape: &'a [usize], data: &'a [i32]) -> crate::Result<Self> {
7523 Ok(Self::I32(TypedTensorView::from_col_major(shape, data)?))
7524 }
7525
7526 /// Create a dynamic `bool` view over compact column-major host data.
7527 ///
7528 /// # Examples
7529 ///
7530 /// ```
7531 /// use tenferro_tensor::{DType, TensorView};
7532 ///
7533 /// let data = [true, false];
7534 /// let view = TensorView::bool(&[2], &data)?;
7535 /// assert_eq!(view.dtype(), DType::Bool);
7536 /// # Ok::<(), tenferro_tensor::Error>(())
7537 /// ```
7538 /// # Errors
7539 ///
7540 /// Returns [`crate::Error::Validation`] with
7541 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7542 /// shape or offset arithmetic overflow, or
7543 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7544 /// compact shape reaches beyond `data`.
7545 pub fn bool(shape: &'a [usize], data: &'a [bool]) -> crate::Result<Self> {
7546 Ok(Self::Bool(TypedTensorView::from_col_major(shape, data)?))
7547 }
7548
7549 /// Create a dynamic `Complex32` view over compact column-major host data.
7550 ///
7551 /// # Examples
7552 ///
7553 /// ```
7554 /// use num_complex::Complex32;
7555 /// use tenferro_tensor::{DType, TensorView};
7556 ///
7557 /// let data = [Complex32::new(1.0, 2.0)];
7558 /// let view = TensorView::c32(&[1], &data)?;
7559 /// assert_eq!(view.dtype(), DType::C32);
7560 /// # Ok::<(), tenferro_tensor::Error>(())
7561 /// ```
7562 /// # Errors
7563 ///
7564 /// Returns [`crate::Error::Validation`] with
7565 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7566 /// shape or offset arithmetic overflow, or
7567 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7568 /// compact shape reaches beyond `data`.
7569 pub fn c32(shape: &'a [usize], data: &'a [Complex32]) -> crate::Result<Self> {
7570 Ok(Self::C32(TypedTensorView::from_col_major(shape, data)?))
7571 }
7572
7573 /// Create a dynamic `Complex64` view over compact column-major host data.
7574 ///
7575 /// # Examples
7576 ///
7577 /// ```
7578 /// use num_complex::Complex64;
7579 /// use tenferro_tensor::{DType, TensorView};
7580 ///
7581 /// let data = [Complex64::new(1.0, 2.0)];
7582 /// let view = TensorView::c64(&[1], &data)?;
7583 /// assert_eq!(view.dtype(), DType::C64);
7584 /// # Ok::<(), tenferro_tensor::Error>(())
7585 /// ```
7586 /// # Errors
7587 ///
7588 /// Returns [`crate::Error::Validation`] with
7589 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7590 /// shape or offset arithmetic overflow, or
7591 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7592 /// compact shape reaches beyond `data`.
7593 pub fn c64(shape: &'a [usize], data: &'a [Complex64]) -> crate::Result<Self> {
7594 Ok(Self::C64(TypedTensorView::from_col_major(shape, data)?))
7595 }
7596
7597 /// Return the element dtype of this borrowed view.
7598 ///
7599 /// # Examples
7600 ///
7601 /// ```
7602 /// use tenferro_tensor::{DType, TensorView};
7603 ///
7604 /// let view = TensorView::f64(&[2], &[1.0, 2.0])?;
7605 /// assert_eq!(view.dtype(), DType::F64);
7606 /// # Ok::<(), tenferro_tensor::Error>(())
7607 /// ```
7608 pub fn dtype(&self) -> DType {
7609 match self {
7610 Self::F32(_) => DType::F32,
7611 Self::F64(_) => DType::F64,
7612 Self::I32(_) => DType::I32,
7613 Self::I64(_) => DType::I64,
7614 Self::Bool(_) => DType::Bool,
7615 Self::C32(_) => DType::C32,
7616 Self::C64(_) => DType::C64,
7617 }
7618 }
7619
7620 /// Return the logical shape of this borrowed view.
7621 ///
7622 /// # Examples
7623 ///
7624 /// ```
7625 /// use tenferro_tensor::TensorView;
7626 ///
7627 /// let view = TensorView::i32(&[2, 1], &[1, 2])?;
7628 /// assert_eq!(view.shape(), &[2, 1]);
7629 /// # Ok::<(), tenferro_tensor::Error>(())
7630 /// ```
7631 pub fn shape(&self) -> &[usize] {
7632 match self {
7633 Self::F32(t) => t.shape(),
7634 Self::F64(t) => t.shape(),
7635 Self::I32(t) => t.shape(),
7636 Self::I64(t) => t.shape(),
7637 Self::Bool(t) => t.shape(),
7638 Self::C32(t) => t.shape(),
7639 Self::C64(t) => t.shape(),
7640 }
7641 }
7642
7643 /// Borrow a contiguous host slice when the requested scalar matches this view's dtype.
7644 ///
7645 /// Backend buffers and non-contiguous views return an explicit error. No
7646 /// download or materialization is performed.
7647 ///
7648 /// # Errors
7649 ///
7650 /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
7651 /// view dtype, [`ValidationError::NonContiguousViewAsSlice`] for a
7652 /// non-contiguous layout, or [`crate::Error::HostAccess`] for unavailable
7653 /// backend host access.
7654 pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
7655 if self.dtype() != T::dtype() {
7656 return Err(crate::Error::validation(
7657 "TensorView::as_slice",
7658 ValidationError::DTypeMismatch {
7659 expected: T::dtype(),
7660 actual: self.dtype(),
7661 },
7662 ));
7663 }
7664 match self {
7665 Self::F32(view) => cast_view_slice(view.as_slice()?),
7666 Self::F64(view) => cast_view_slice(view.as_slice()?),
7667 Self::I32(view) => cast_view_slice(view.as_slice()?),
7668 Self::I64(view) => cast_view_slice(view.as_slice()?),
7669 Self::Bool(view) => cast_view_slice(view.as_slice()?),
7670 Self::C32(view) => cast_view_slice(view.as_slice()?),
7671 Self::C64(view) => cast_view_slice(view.as_slice()?),
7672 }
7673 }
7674
7675 /// Reinterpret a complex view as its sealed real representation.
7676 ///
7677 /// # Examples
7678 ///
7679 /// ```
7680 /// use tenferro_tensor::{Complex64, DType, TensorView};
7681 ///
7682 /// let data = [Complex64::new(1.0, 2.0)];
7683 /// let real = TensorView::c64(&[1], &data)?.as_real_view()?;
7684 /// assert_eq!(real.dtype(), DType::F64);
7685 /// assert_eq!(real.as_slice::<f64>()?, &[1.0, 2.0]);
7686 /// # Ok::<(), tenferro_tensor::Error>(())
7687 /// ```
7688 ///
7689 /// # Errors
7690 ///
7691 /// Returns [`crate::Error::Unsupported`] for the wrong dtype pair and
7692 /// [`ValidationError::InvalidArgument`] or
7693 /// [`ValidationError::ViewOutOfBounds`] for invalid layout metadata.
7694 pub fn as_real_view(&self) -> crate::Result<Self> {
7695 match self {
7696 Self::C32(t) => t.as_real_view().map(Self::F32),
7697 Self::C64(t) => t.as_real_view().map(Self::F64),
7698 _ => Err(crate::Error::unsupported(
7699 "TensorView::as_real_view",
7700 "only complex views have a sealed real representation",
7701 )),
7702 }
7703 }
7704
7705 /// Reinterpret a real view as its sealed complex representation.
7706 ///
7707 /// # Examples
7708 ///
7709 /// ```
7710 /// use tenferro_tensor::{Complex64, DType, TensorView};
7711 ///
7712 /// let data = [1.0_f64, 2.0];
7713 /// let complex = TensorView::f64(&[2, 1], &data)?.as_complex_view()?;
7714 /// assert_eq!(complex.dtype(), DType::C64);
7715 /// assert_eq!(complex.as_slice::<Complex64>()?, &[Complex64::new(1.0, 2.0)]);
7716 /// # Ok::<(), tenferro_tensor::Error>(())
7717 /// ```
7718 ///
7719 /// # Errors
7720 ///
7721 /// Returns [`crate::Error::Unsupported`] for the wrong dtype pair and
7722 /// [`ValidationError::InvalidArgument`] or
7723 /// [`ValidationError::ViewOutOfBounds`] for invalid layout metadata.
7724 pub fn as_complex_view(&self) -> crate::Result<Self> {
7725 match self {
7726 Self::F32(t) => t.as_complex_view().map(Self::C32),
7727 Self::F64(t) => t.as_complex_view().map(Self::C64),
7728 _ => Err(crate::Error::unsupported(
7729 "TensorView::as_complex_view",
7730 "only real views have a sealed complex representation",
7731 )),
7732 }
7733 }
7734
7735 /// Return the placement metadata carried by this borrowed view.
7736 ///
7737 /// # Examples
7738 ///
7739 /// ```
7740 /// use tenferro_tensor::{MemoryKind, TensorView};
7741 ///
7742 /// let view = TensorView::f64(&[1], &[1.0])?;
7743 /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
7744 /// # Ok::<(), tenferro_tensor::Error>(())
7745 /// ```
7746 pub fn placement(&self) -> &Placement {
7747 match self {
7748 Self::F32(t) => t.placement(),
7749 Self::F64(t) => t.placement(),
7750 Self::I32(t) => t.placement(),
7751 Self::I64(t) => t.placement(),
7752 Self::Bool(t) => t.placement(),
7753 Self::C32(t) => t.placement(),
7754 Self::C64(t) => t.placement(),
7755 }
7756 }
7757
7758 /// Return the physical backend family, when this view is backend-owned.
7759 ///
7760 /// # Examples
7761 ///
7762 /// ```
7763 /// use tenferro_tensor::TensorView;
7764 ///
7765 /// let view = TensorView::f64(&[1], &[1.0])?;
7766 /// assert_eq!(view.backend_family(), None);
7767 /// # Ok::<(), tenferro_tensor::Error>(())
7768 /// ```
7769 pub fn backend_family(&self) -> Option<&'static str> {
7770 match self {
7771 Self::F32(t) => t.backend_family(),
7772 Self::F64(t) => t.backend_family(),
7773 Self::I32(t) => t.backend_family(),
7774 Self::I64(t) => t.backend_family(),
7775 Self::Bool(t) => t.backend_family(),
7776 Self::C32(t) => t.backend_family(),
7777 Self::C64(t) => t.backend_family(),
7778 }
7779 }
7780
7781 /// Return the shared allocation domain, when this view has one.
7782 ///
7783 /// # Examples
7784 ///
7785 /// ```
7786 /// use tenferro_tensor::TensorView;
7787 ///
7788 /// let view = TensorView::f64(&[1], &[1.0])?;
7789 /// assert_eq!(view.allocation_domain(), None);
7790 /// # Ok::<(), tenferro_tensor::Error>(())
7791 /// ```
7792 pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
7793 match self {
7794 Self::F32(t) => t.allocation_domain(),
7795 Self::F64(t) => t.allocation_domain(),
7796 Self::I32(t) => t.allocation_domain(),
7797 Self::I64(t) => t.allocation_domain(),
7798 Self::Bool(t) => t.allocation_domain(),
7799 Self::C32(t) => t.allocation_domain(),
7800 Self::C64(t) => t.allocation_domain(),
7801 }
7802 }
7803
7804 /// Return strides in element units.
7805 ///
7806 /// # Examples
7807 ///
7808 /// ```
7809 /// use tenferro_tensor::TensorView;
7810 ///
7811 /// let data = [0.0_f64; 6];
7812 /// let view = TensorView::f64(&[2, 3], &data)?;
7813 /// assert_eq!(view.strides(), &[1, 2]);
7814 /// # Ok::<(), tenferro_tensor::Error>(())
7815 /// ```
7816 pub fn strides(&self) -> &[isize] {
7817 match self {
7818 Self::F32(t) => t.strides(),
7819 Self::F64(t) => t.strides(),
7820 Self::I32(t) => t.strides(),
7821 Self::I64(t) => t.strides(),
7822 Self::Bool(t) => t.strides(),
7823 Self::C32(t) => t.strides(),
7824 Self::C64(t) => t.strides(),
7825 }
7826 }
7827
7828 /// Return the physical element offset.
7829 pub fn offset(&self) -> isize {
7830 match self {
7831 Self::F32(t) => t.offset(),
7832 Self::F64(t) => t.offset(),
7833 Self::I32(t) => t.offset(),
7834 Self::I64(t) => t.offset(),
7835 Self::Bool(t) => t.offset(),
7836 Self::C32(t) => t.offset(),
7837 Self::C64(t) => t.offset(),
7838 }
7839 }
7840
7841 /// Compute the physical element offset for a logical index.
7842 /// # Errors
7843 ///
7844 /// Returns [`crate::Error::Validation`] with
7845 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7846 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7847 /// when an index is outside its axis extent, or
7848 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7849 /// arithmetic overflows.
7850 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7851 match self {
7852 Self::F32(t) => t.layout_linear_offset(indices),
7853 Self::F64(t) => t.layout_linear_offset(indices),
7854 Self::I32(t) => t.layout_linear_offset(indices),
7855 Self::I64(t) => t.layout_linear_offset(indices),
7856 Self::Bool(t) => t.layout_linear_offset(indices),
7857 Self::C32(t) => t.layout_linear_offset(indices),
7858 Self::C64(t) => t.layout_linear_offset(indices),
7859 }
7860 }
7861
7862 /// Return whether this view is compact column-major.
7863 /// # Errors
7864 ///
7865 /// Returns [`crate::Error::Validation`] with
7866 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7867 /// compactness arithmetic overflows.
7868 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
7869 match self {
7870 Self::F32(t) => t.is_col_major_contiguous(),
7871 Self::F64(t) => t.is_col_major_contiguous(),
7872 Self::I32(t) => t.is_col_major_contiguous(),
7873 Self::I64(t) => t.is_col_major_contiguous(),
7874 Self::Bool(t) => t.is_col_major_contiguous(),
7875 Self::C32(t) => t.is_col_major_contiguous(),
7876 Self::C64(t) => t.is_col_major_contiguous(),
7877 }
7878 }
7879
7880 /// Return a compact string summary of this view's layout metadata.
7881 pub fn layout_summary(&self) -> String {
7882 layout_summary(self.shape(), self.strides(), self.offset())
7883 }
7884
7885 /// Assert this view is compact column-major.
7886 /// # Errors
7887 ///
7888 /// Returns [`crate::Error::Validation`] with
7889 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7890 /// compactness arithmetic overflows, or
7891 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7892 /// view is not compact column-major.
7893 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
7894 assert_layout_col_major_contiguous(
7895 self.is_col_major_contiguous()?,
7896 self.shape(),
7897 self.strides(),
7898 self.offset(),
7899 "TensorView::assert_col_major_contiguous",
7900 )
7901 }
7902
7903 /// Explicitly duplicate a compact host view into a fresh tensor.
7904 ///
7905 /// Backend views and non-contiguous layouts return a typed error; this
7906 /// operation never downloads or silently canonicalizes a view.
7907 ///
7908 /// # Examples
7909 ///
7910 /// ```
7911 /// use tenferro_tensor::{TensorView, TypedTensorView};
7912 ///
7913 /// let data = [1_i32, 2];
7914 /// let view = TensorView::I32(TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?);
7915 /// let copy = view.duplicate()?;
7916 /// assert_eq!(copy.shape(), &[2]);
7917 /// # Ok::<(), tenferro_tensor::Error>(())
7918 /// ```
7919 ///
7920 /// # Errors
7921 ///
7922 /// Returns [`crate::Error::HostAccess`] for backend-owned views,
7923 /// [`ValidationError::NonContiguousViewAsSlice`] for non-contiguous views,
7924 /// or [`ValidationError::InvalidArgument`] for invalid layout metadata.
7925 pub fn duplicate(&self) -> crate::Result<Tensor> {
7926 fn duplicate_typed<T: TensorScalar>(
7927 view: &TypedTensorView<'_, T>,
7928 ) -> crate::Result<TypedTensor<T>> {
7929 let mut tensor = TypedTensor::<T>::from_vec_col_major(
7930 view.shape().to_vec(),
7931 view.as_slice()?.to_vec(),
7932 )?;
7933 tensor.set_placement(view.placement().clone());
7934 Ok(tensor)
7935 }
7936
7937 match self {
7938 Self::F32(view) => duplicate_typed(view).map(Tensor::from_typed),
7939 Self::F64(view) => duplicate_typed(view).map(Tensor::from_typed),
7940 Self::I32(view) => duplicate_typed(view).map(Tensor::from_typed),
7941 Self::I64(view) => duplicate_typed(view).map(Tensor::from_typed),
7942 Self::Bool(view) => duplicate_typed(view).map(Tensor::from_typed),
7943 Self::C32(view) => duplicate_typed(view).map(Tensor::from_typed),
7944 Self::C64(view) => duplicate_typed(view).map(Tensor::from_typed),
7945 }
7946 }
7947}
7948
7949macro_rules! tensor_view_mut_constructor {
7950 ($name:ident, $variant:ident, $scalar:ty) => {
7951 /// Create a dynamic mutable view over compact column-major host data.
7952 ///
7953 /// # Errors
7954 ///
7955 /// Returns [`crate::Error::Validation`] with
7956 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7957 /// compact layout arithmetic overflows, or
7958 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7959 /// requested shape exceeds `data`.
7960 pub fn $name(shape: &'a [usize], data: &'a mut [$scalar]) -> crate::Result<Self> {
7961 Ok(Self::$variant(TypedTensorViewMut::from_col_major(
7962 shape, data,
7963 )?))
7964 }
7965 };
7966}
7967
7968impl<'a> TensorViewMut<'a> {
7969 tensor_view_mut_constructor!(f32, F32, f32);
7970 tensor_view_mut_constructor!(f64, F64, f64);
7971 tensor_view_mut_constructor!(i32, I32, i32);
7972 tensor_view_mut_constructor!(i64, I64, i64);
7973 tensor_view_mut_constructor!(bool, Bool, bool);
7974 tensor_view_mut_constructor!(c32, C32, Complex32);
7975 tensor_view_mut_constructor!(c64, C64, Complex64);
7976
7977 pub fn dtype(&self) -> DType {
7978 match self {
7979 Self::F32(_) => DType::F32,
7980 Self::F64(_) => DType::F64,
7981 Self::I32(_) => DType::I32,
7982 Self::I64(_) => DType::I64,
7983 Self::Bool(_) => DType::Bool,
7984 Self::C32(_) => DType::C32,
7985 Self::C64(_) => DType::C64,
7986 }
7987 }
7988
7989 pub fn shape(&self) -> &[usize] {
7990 match self {
7991 Self::F32(t) => t.shape(),
7992 Self::F64(t) => t.shape(),
7993 Self::I32(t) => t.shape(),
7994 Self::I64(t) => t.shape(),
7995 Self::Bool(t) => t.shape(),
7996 Self::C32(t) => t.shape(),
7997 Self::C64(t) => t.shape(),
7998 }
7999 }
8000
8001 /// Return strides in element units.
8002 ///
8003 /// # Examples
8004 ///
8005 /// ```
8006 /// use tenferro_tensor::TensorViewMut;
8007 ///
8008 /// let mut data = [0.0_f64; 6];
8009 /// let view = TensorViewMut::f64(&[2, 3], &mut data)?;
8010 /// assert_eq!(view.strides(), &[1, 2]);
8011 /// # Ok::<(), tenferro_tensor::Error>(())
8012 /// ```
8013 pub fn strides(&self) -> &[isize] {
8014 match self {
8015 Self::F32(t) => t.strides(),
8016 Self::F64(t) => t.strides(),
8017 Self::I32(t) => t.strides(),
8018 Self::I64(t) => t.strides(),
8019 Self::Bool(t) => t.strides(),
8020 Self::C32(t) => t.strides(),
8021 Self::C64(t) => t.strides(),
8022 }
8023 }
8024
8025 pub fn offset(&self) -> isize {
8026 match self {
8027 Self::F32(t) => t.offset(),
8028 Self::F64(t) => t.offset(),
8029 Self::I32(t) => t.offset(),
8030 Self::I64(t) => t.offset(),
8031 Self::Bool(t) => t.offset(),
8032 Self::C32(t) => t.offset(),
8033 Self::C64(t) => t.offset(),
8034 }
8035 }
8036
8037 /// Compute the physical element offset for a logical index.
8038 ///
8039 /// # Errors
8040 ///
8041 /// Returns [`crate::Error::Validation`] with
8042 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8043 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8044 /// when an index is outside its axis extent, or
8045 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8046 /// arithmetic overflows.
8047 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8048 match self {
8049 Self::F32(t) => t.layout_linear_offset(indices),
8050 Self::F64(t) => t.layout_linear_offset(indices),
8051 Self::I32(t) => t.layout_linear_offset(indices),
8052 Self::I64(t) => t.layout_linear_offset(indices),
8053 Self::Bool(t) => t.layout_linear_offset(indices),
8054 Self::C32(t) => t.layout_linear_offset(indices),
8055 Self::C64(t) => t.layout_linear_offset(indices),
8056 }
8057 }
8058
8059 /// Return whether this view is compact column-major.
8060 ///
8061 /// # Errors
8062 ///
8063 /// Returns [`crate::Error::Validation`] with
8064 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8065 /// compactness arithmetic overflows.
8066 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8067 match self {
8068 Self::F32(t) => t.is_col_major_contiguous(),
8069 Self::F64(t) => t.is_col_major_contiguous(),
8070 Self::I32(t) => t.is_col_major_contiguous(),
8071 Self::I64(t) => t.is_col_major_contiguous(),
8072 Self::Bool(t) => t.is_col_major_contiguous(),
8073 Self::C32(t) => t.is_col_major_contiguous(),
8074 Self::C64(t) => t.is_col_major_contiguous(),
8075 }
8076 }
8077
8078 pub fn layout_summary(&self) -> String {
8079 layout_summary(self.shape(), self.strides(), self.offset())
8080 }
8081
8082 /// Assert this view is compact column-major.
8083 ///
8084 /// # Errors
8085 ///
8086 /// Returns [`crate::Error::Validation`] with
8087 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8088 /// compactness arithmetic overflows, or
8089 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8090 /// view is not compact column-major.
8091 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8092 assert_layout_col_major_contiguous(
8093 self.is_col_major_contiguous()?,
8094 self.shape(),
8095 self.strides(),
8096 self.offset(),
8097 "TensorViewMut::assert_col_major_contiguous",
8098 )
8099 }
8100
8101 /// Explicitly duplicate the compact host data visible through this
8102 /// mutable view into a new tensor owner.
8103 ///
8104 /// # Examples
8105 ///
8106 /// ```
8107 /// use tenferro_tensor::{TensorViewMut, TypedTensorViewMut};
8108 ///
8109 /// let mut data = [1_i32, 2];
8110 /// let view = TensorViewMut::I32(TypedTensorViewMut::from_slice(
8111 /// vec![2], vec![1], 0, &mut data,
8112 /// )?);
8113 /// let copy = view.duplicate()?;
8114 /// assert_eq!(copy.shape(), &[2]);
8115 /// # Ok::<(), tenferro_tensor::Error>(())
8116 /// ```
8117 ///
8118 /// # Errors
8119 ///
8120 /// Returns [`crate::Error::HostAccess`] for backend-owned views,
8121 /// [`ValidationError::NonContiguousViewAsSlice`] for non-contiguous views,
8122 /// or [`ValidationError::InvalidArgument`] for invalid layout metadata.
8123 pub fn duplicate(&self) -> crate::Result<Tensor> {
8124 self.as_read_only().duplicate()
8125 }
8126
8127 /// Borrow this mutable view as a read-only dtype-erased view.
8128 ///
8129 /// # Examples
8130 ///
8131 /// ```
8132 /// use tenferro_tensor::TensorViewMut;
8133 ///
8134 /// let mut data = [1.0_f64, 2.0];
8135 /// let view = TensorViewMut::f64(&[2], &mut data)?;
8136 /// assert_eq!(view.as_read_only().as_slice::<f64>()?, &[1.0, 2.0]);
8137 /// # Ok::<(), tenferro_tensor::Error>(())
8138 /// ```
8139 pub fn as_read_only(&self) -> TensorView<'_> {
8140 match self {
8141 Self::F32(t) => TensorView::F32(t.as_read_only()),
8142 Self::F64(t) => TensorView::F64(t.as_read_only()),
8143 Self::I32(t) => TensorView::I32(t.as_read_only()),
8144 Self::I64(t) => TensorView::I64(t.as_read_only()),
8145 Self::Bool(t) => TensorView::Bool(t.as_read_only()),
8146 Self::C32(t) => TensorView::C32(t.as_read_only()),
8147 Self::C64(t) => TensorView::C64(t.as_read_only()),
8148 }
8149 }
8150}
8151
8152impl<'a> TensorRead<'a> {
8153 /// Borrow an owned tensor as a read target without copying it.
8154 ///
8155 /// # Examples
8156 ///
8157 /// ```
8158 /// use tenferro_tensor::{Tensor, TensorRead};
8159 ///
8160 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8161 /// let read = TensorRead::from_tensor(&tensor);
8162 /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
8163 /// # Ok::<(), tenferro_tensor::Error>(())
8164 /// ```
8165 pub fn from_tensor(tensor: &'a Tensor) -> Self {
8166 Self::Tensor(tensor)
8167 }
8168
8169 /// Wrap a borrowed dtype-erased view as a read target.
8170 ///
8171 /// # Examples
8172 ///
8173 /// ```
8174 /// use tenferro_tensor::{TensorRead, TensorView};
8175 ///
8176 /// let read = TensorRead::from_view(TensorView::i32(&[2], &[3, 4])?);
8177 /// assert_eq!(read.as_slice::<i32>()?, &[3, 4]);
8178 /// assert!(read.as_tensor().is_none());
8179 /// # Ok::<(), tenferro_tensor::Error>(())
8180 /// ```
8181 #[inline]
8182 pub fn from_view(view: TensorView<'a>) -> Self {
8183 Self::View(view)
8184 }
8185
8186 /// Convert this read target into a dtype-erased tensor view.
8187 ///
8188 /// Owned tensors are borrowed without copying their storage. Existing
8189 /// views preserve their layout and placement metadata.
8190 /// # Examples
8191 ///
8192 /// ```rust
8193 /// # use tenferro_tensor::{Tensor, TensorRead};
8194 /// # let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8195 /// let view = TensorRead::from_tensor(&tensor).tensor_view();
8196 /// assert_eq!(view.shape(), &[2]);
8197 /// assert_eq!(view.as_slice::<f64>()?, &[1.0, 2.0]);
8198 /// # Ok::<(), tenferro_tensor::Error>(())
8199 /// ```
8200 pub fn tensor_view(self) -> TensorView<'a> {
8201 match self {
8202 Self::Tensor(tensor) => tensor_view_with_layout(tensor, tensor_layout(tensor)),
8203 Self::View(view) => view,
8204 }
8205 }
8206
8207 /// Build a metadata-only stride-0 broadcast of this read.
8208 ///
8209 /// An owned preset is borrowed and an existing view keeps its buffer,
8210 /// retained root and placement, so the result copies no elements and
8211 /// allocates no storage. A caller that can consume a read-only operand
8212 /// should use this instead of the materializing session
8213 /// `broadcast_in_dim_read`.
8214 ///
8215 /// # Examples
8216 ///
8217 /// ```
8218 /// use tenferro_tensor::{Tensor, TensorRead};
8219 ///
8220 /// let tensor = Tensor::from_vec_col_major(vec![3], vec![1.0_f64, 2.0, 3.0])?;
8221 /// let read = TensorRead::from_tensor(&tensor).broadcast_in_dim_view(&[2, 3], &[1])?;
8222 /// assert_eq!(read.shape(), &[2, 3]);
8223 /// assert_eq!(read.strides()?, vec![0, 1]);
8224 /// # Ok::<(), tenferro_tensor::Error>(())
8225 /// ```
8226 ///
8227 /// # Errors
8228 ///
8229 /// Returns [`crate::Error::UnsupportedDType`] for a caller-owned payload,
8230 /// which has no borrowed view, or a validation error when `dims` does not
8231 /// map this read's layout into `shape`.
8232 #[doc(hidden)]
8233 pub fn broadcast_in_dim_view(self, shape: &[usize], dims: &[usize]) -> crate::Result<Self> {
8234 let dtype = self.dtype();
8235 // INVARIANT: `tensor_view()` has no arm for a caller-owned payload, so
8236 // the refusal must happen before the read is converted into a view.
8237 if let DType::External(_) = dtype {
8238 return Err(crate::Error::unsupported_dtype(
8239 "TensorRead::broadcast_in_dim_view",
8240 dtype,
8241 "a caller-owned payload has no borrowed broadcast view",
8242 ));
8243 }
8244 let view = self.tensor_view();
8245 Ok(Self::View(match view {
8246 TensorView::F32(view) => TensorView::F32(broadcast_typed_view(view, shape, dims)?),
8247 TensorView::F64(view) => TensorView::F64(broadcast_typed_view(view, shape, dims)?),
8248 TensorView::I32(view) => TensorView::I32(broadcast_typed_view(view, shape, dims)?),
8249 TensorView::I64(view) => TensorView::I64(broadcast_typed_view(view, shape, dims)?),
8250 TensorView::Bool(view) => TensorView::Bool(broadcast_typed_view(view, shape, dims)?),
8251 TensorView::C32(view) => TensorView::C32(broadcast_typed_view(view, shape, dims)?),
8252 TensorView::C64(view) => TensorView::C64(broadcast_typed_view(view, shape, dims)?),
8253 }))
8254 }
8255
8256 /// Borrow this read target as a typed compact host slice without allocation.
8257 ///
8258 /// This delegates to [`TensorView::as_slice`]. Cloning a `TensorRead` is a
8259 /// shallow clone of its borrowed reference or view metadata, so the returned
8260 /// slice retains the original `'a` storage lifetime and never outlives the
8261 /// storage borrowed by this read target. This method does not materialize,
8262 /// transfer, or otherwise canonicalize the input.
8263 ///
8264 /// # Errors
8265 ///
8266 /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
8267 /// input dtype, [`ValidationError::NonContiguousViewAsSlice`] for a
8268 /// noncompact view, or a typed runtime-state host-access error for
8269 /// backend-owned storage.
8270 /// Backend-owned
8271 /// inputs are never downloaded implicitly.
8272 ///
8273 /// # Examples
8274 ///
8275 /// ```
8276 /// use tenferro_tensor::{Tensor, TensorRead};
8277 ///
8278 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8279 /// let read = TensorRead::from_tensor(&tensor);
8280 /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
8281 /// # Ok::<(), tenferro_tensor::Error>(())
8282 /// ```
8283 pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
8284 self.clone().tensor_view().as_slice()
8285 }
8286
8287 /// Return the element dtype of this read target.
8288 ///
8289 /// # Examples
8290 ///
8291 /// ```
8292 /// use tenferro_tensor::{DType, Tensor, TensorRead};
8293 ///
8294 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1_i64])?;
8295 /// assert_eq!(TensorRead::from_tensor(&tensor).dtype(), DType::I64);
8296 /// # Ok::<(), tenferro_tensor::Error>(())
8297 /// ```
8298 pub fn dtype(&self) -> DType {
8299 match self {
8300 Self::Tensor(tensor) => tensor.dtype(),
8301 Self::View(view) => view.dtype(),
8302 }
8303 }
8304
8305 /// Return the logical shape of this read target.
8306 ///
8307 /// # Examples
8308 ///
8309 /// ```
8310 /// use tenferro_tensor::{Tensor, TensorRead};
8311 ///
8312 /// let tensor = Tensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
8313 /// assert_eq!(TensorRead::from_tensor(&tensor).shape(), &[2, 1]);
8314 /// # Ok::<(), tenferro_tensor::Error>(())
8315 /// ```
8316 pub fn shape(&self) -> &[usize] {
8317 match self {
8318 Self::Tensor(tensor) => tensor.shape(),
8319 Self::View(view) => view.shape(),
8320 }
8321 }
8322
8323 /// Return the placement metadata carried by this read target.
8324 ///
8325 /// # Examples
8326 ///
8327 /// ```
8328 /// use tenferro_tensor::{MemoryKind, Tensor, TensorRead};
8329 ///
8330 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
8331 /// let read = TensorRead::from_tensor(&tensor);
8332 /// assert_eq!(read.placement().memory_kind, MemoryKind::UnpinnedHost);
8333 /// # Ok::<(), tenferro_tensor::Error>(())
8334 /// ```
8335 pub fn placement(&self) -> &Placement {
8336 match self {
8337 Self::Tensor(tensor) => tensor.placement(),
8338 Self::View(view) => view.placement(),
8339 }
8340 }
8341
8342 /// Return the physical backend family of this read target, when backend-owned.
8343 ///
8344 /// # Examples
8345 ///
8346 /// ```
8347 /// use tenferro_tensor::{Tensor, TensorRead};
8348 ///
8349 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
8350 /// assert_eq!(TensorRead::from_tensor(&tensor).backend_family(), None);
8351 /// # Ok::<(), tenferro_tensor::Error>(())
8352 /// ```
8353 pub fn backend_family(&self) -> Option<&'static str> {
8354 match self {
8355 Self::Tensor(tensor) => match tensor.dtype() {
8356 // A caller-owned payload is host memory without backend family.
8357 DType::External(_) => None,
8358 DType::F32 => tensor.as_typed::<f32>().and_then(|t| t.backend_family()),
8359 DType::F64 => tensor.as_typed::<f64>().and_then(|t| t.backend_family()),
8360 DType::I32 => tensor.as_typed::<i32>().and_then(|t| t.backend_family()),
8361 DType::I64 => tensor.as_typed::<i64>().and_then(|t| t.backend_family()),
8362 DType::Bool => tensor.as_typed::<bool>().and_then(|t| t.backend_family()),
8363 DType::C32 => tensor
8364 .as_typed::<Complex<f32>>()
8365 .and_then(|t| t.backend_family()),
8366 DType::C64 => tensor
8367 .as_typed::<Complex<f64>>()
8368 .and_then(|t| t.backend_family()),
8369 },
8370 Self::View(view) => view.backend_family(),
8371 }
8372 }
8373
8374 /// Return the shared allocation domain of this read target, when present.
8375 ///
8376 /// # Examples
8377 ///
8378 /// ```
8379 /// use tenferro_tensor::{Tensor, TensorRead};
8380 ///
8381 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
8382 /// assert_eq!(TensorRead::from_tensor(&tensor).allocation_domain(), None);
8383 /// # Ok::<(), tenferro_tensor::Error>(())
8384 /// ```
8385 pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
8386 match self {
8387 Self::Tensor(tensor) => match tensor.dtype() {
8388 // A caller-owned payload has no allocation domain.
8389 DType::External(_) => None,
8390 DType::F32 => tensor.as_typed::<f32>().and_then(|t| t.allocation_domain()),
8391 DType::F64 => tensor.as_typed::<f64>().and_then(|t| t.allocation_domain()),
8392 DType::I32 => tensor.as_typed::<i32>().and_then(|t| t.allocation_domain()),
8393 DType::I64 => tensor.as_typed::<i64>().and_then(|t| t.allocation_domain()),
8394 DType::Bool => tensor
8395 .as_typed::<bool>()
8396 .and_then(|t| t.allocation_domain()),
8397 DType::C32 => tensor
8398 .as_typed::<Complex<f32>>()
8399 .and_then(|t| t.allocation_domain()),
8400 DType::C64 => tensor
8401 .as_typed::<Complex<f64>>()
8402 .and_then(|t| t.allocation_domain()),
8403 },
8404 Self::View(view) => view.allocation_domain(),
8405 }
8406 }
8407
8408 /// Return strides in element units; owned tensors report compact column-major strides.
8409 ///
8410 /// # Examples
8411 ///
8412 /// ```
8413 /// use tenferro_tensor::{Tensor, TensorRead};
8414 ///
8415 /// let tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8416 /// assert_eq!(TensorRead::from_tensor(&tensor).strides()?, vec![1, 2]);
8417 /// # Ok::<(), tenferro_tensor::Error>(())
8418 /// ```
8419 ///
8420 /// # Errors
8421 ///
8422 /// Returns [`crate::Error::Validation`] with
8423 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8424 /// column-major stride arithmetic overflows.
8425 pub fn strides(&self) -> crate::Result<Vec<isize>> {
8426 match self {
8427 Self::Tensor(tensor) => Ok(col_major_strides(tensor.shape())?.into_vec()),
8428 Self::View(view) => Ok(view.strides().to_vec()),
8429 }
8430 }
8431
8432 /// Return the physical element offset; owned tensors always start at `0`.
8433 ///
8434 /// # Examples
8435 ///
8436 /// ```
8437 /// use tenferro_tensor::{TensorRead, TensorView, TypedTensorView};
8438 ///
8439 /// let data = [1.0_f64, 2.0, 3.0];
8440 /// let view = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?);
8441 /// assert_eq!(TensorRead::from_view(view).offset(), 1);
8442 /// # Ok::<(), tenferro_tensor::Error>(())
8443 /// ```
8444 pub fn offset(&self) -> isize {
8445 match self {
8446 Self::Tensor(_) => 0,
8447 Self::View(view) => view.offset(),
8448 }
8449 }
8450
8451 /// Compute the physical element offset for a logical index.
8452 ///
8453 /// # Examples
8454 ///
8455 /// ```
8456 /// use tenferro_tensor::{Tensor, TensorRead};
8457 ///
8458 /// let tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8459 /// let read = TensorRead::from_tensor(&tensor);
8460 /// assert_eq!(read.layout_linear_offset(&[1, 2])?, 5);
8461 /// assert!(read.layout_linear_offset(&[2, 0]).is_err());
8462 /// # Ok::<(), tenferro_tensor::Error>(())
8463 /// ```
8464 ///
8465 /// # Errors
8466 ///
8467 /// Returns [`crate::Error::Validation`] with
8468 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8469 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8470 /// when an index is outside its axis extent, or
8471 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8472 /// arithmetic overflows.
8473 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8474 match self {
8475 Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
8476 Self::View(view) => view.layout_linear_offset(indices),
8477 }
8478 }
8479
8480 /// Return whether this read target is compact column-major.
8481 ///
8482 /// # Examples
8483 ///
8484 /// ```
8485 /// use tenferro_tensor::{Tensor, TensorRead, TensorView, TypedTensorView};
8486 ///
8487 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8488 /// assert!(TensorRead::from_tensor(&tensor).is_col_major_contiguous()?);
8489 /// let data = [1.0_f64, 2.0, 3.0];
8490 /// let strided = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![2], 0, &data)?);
8491 /// assert!(!TensorRead::from_view(strided).is_col_major_contiguous()?);
8492 /// # Ok::<(), tenferro_tensor::Error>(())
8493 /// ```
8494 ///
8495 /// # Errors
8496 ///
8497 /// Returns [`crate::Error::Validation`] with
8498 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8499 /// compactness arithmetic overflows.
8500 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8501 match self {
8502 Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
8503 Self::View(view) => view.is_col_major_contiguous(),
8504 }
8505 }
8506
8507 /// Return a compact string summary of this read target's layout metadata.
8508 ///
8509 /// # Examples
8510 ///
8511 /// ```
8512 /// use tenferro_tensor::{Tensor, TensorRead};
8513 ///
8514 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8515 /// assert_eq!(
8516 /// TensorRead::from_tensor(&tensor).layout_summary(),
8517 /// "shape=[2] strides=[1] offset=0"
8518 /// );
8519 /// # Ok::<(), tenferro_tensor::Error>(())
8520 /// ```
8521 pub fn layout_summary(&self) -> String {
8522 let strides = match self.strides() {
8523 Ok(strides) => strides,
8524 Err(err) => return format!("layout unavailable: {err}"),
8525 };
8526 layout_summary(self.shape(), &strides, self.offset())
8527 }
8528
8529 /// Assert this read target is compact column-major.
8530 ///
8531 /// # Examples
8532 ///
8533 /// ```
8534 /// use tenferro_tensor::{Tensor, TensorRead, TensorView, TypedTensorView};
8535 ///
8536 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8537 /// TensorRead::from_tensor(&tensor).assert_col_major_contiguous()?;
8538 /// let data = [1.0_f64, 2.0, 3.0];
8539 /// let strided = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![2], 0, &data)?);
8540 /// assert!(TensorRead::from_view(strided).assert_col_major_contiguous().is_err());
8541 /// # Ok::<(), tenferro_tensor::Error>(())
8542 /// ```
8543 ///
8544 /// # Errors
8545 ///
8546 /// Returns [`crate::Error::Validation`] with
8547 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8548 /// compactness arithmetic overflows, or
8549 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8550 /// view is not compact column-major.
8551 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8552 let strides = self.strides()?;
8553 assert_layout_col_major_contiguous(
8554 self.is_col_major_contiguous()?,
8555 self.shape(),
8556 &strides,
8557 self.offset(),
8558 "TensorRead::assert_col_major_contiguous",
8559 )
8560 }
8561
8562 /// Return the borrowed owned tensor, or `None` when this read target is a view.
8563 ///
8564 /// # Examples
8565 ///
8566 /// ```
8567 /// use tenferro_tensor::{Tensor, TensorRead, TensorView};
8568 ///
8569 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
8570 /// assert!(TensorRead::from_tensor(&tensor).as_tensor().is_some());
8571 /// assert!(TensorRead::from_view(TensorView::f64(&[1], &[1.0])?).as_tensor().is_none());
8572 /// # Ok::<(), tenferro_tensor::Error>(())
8573 /// ```
8574 pub fn as_tensor(&self) -> Option<&'a Tensor> {
8575 match self {
8576 Self::Tensor(tensor) => Some(*tensor),
8577 Self::View(_) => None,
8578 }
8579 }
8580}
8581
8582impl<'a> TensorWrite<'a> {
8583 /// Borrow an owned tensor as a writable target without copying it.
8584 ///
8585 /// # Examples
8586 ///
8587 /// ```
8588 /// use tenferro_tensor::{Tensor, TensorWrite};
8589 ///
8590 /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8591 /// let write = TensorWrite::from_tensor(&mut tensor);
8592 /// assert_eq!(write.shape(), &[2]);
8593 /// # Ok::<(), tenferro_tensor::Error>(())
8594 /// ```
8595 pub fn from_tensor(tensor: &'a mut Tensor) -> Self {
8596 Self::Tensor(tensor)
8597 }
8598
8599 /// Wrap a borrowed mutable dtype-erased view as a writable target.
8600 ///
8601 /// # Examples
8602 ///
8603 /// ```
8604 /// use tenferro_tensor::{DType, TensorViewMut, TensorWrite};
8605 ///
8606 /// let mut data = [1.0_f64, 2.0];
8607 /// let write = TensorWrite::from_view(TensorViewMut::f64(&[2], &mut data)?);
8608 /// assert_eq!(write.dtype(), DType::F64);
8609 /// assert_eq!(write.as_read().as_slice::<f64>()?, &[1.0, 2.0]);
8610 /// # Ok::<(), tenferro_tensor::Error>(())
8611 /// ```
8612 pub fn from_view(view: TensorViewMut<'a>) -> Self {
8613 Self::View(view)
8614 }
8615
8616 /// Borrow this writable target as a read-only tensor input.
8617 ///
8618 /// This is useful for explicit read-modify-write kernels such as
8619 /// accumulation updates. The returned view borrows through `&self`, so it
8620 /// cannot outlive the current read-only borrow of the writable target.
8621 ///
8622 /// # Examples
8623 ///
8624 /// ```rust
8625 /// use tenferro_tensor::{DType, Tensor, TensorWrite};
8626 ///
8627 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
8628 /// let write = TensorWrite::from_tensor(&mut tensor);
8629 /// let read = write.as_read();
8630 /// assert_eq!(read.dtype(), DType::F64);
8631 /// # Ok::<(), tenferro_tensor::Error>(())
8632 /// ```
8633 pub fn as_read(&self) -> TensorRead<'_> {
8634 match self {
8635 Self::Tensor(tensor) => TensorRead::from_tensor(tensor),
8636 Self::View(view) => TensorRead::from_view(view.as_read_only()),
8637 }
8638 }
8639
8640 /// Return the element dtype of this writable target.
8641 ///
8642 /// # Examples
8643 ///
8644 /// ```
8645 /// use tenferro_tensor::{DType, Tensor, TensorWrite};
8646 ///
8647 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![1_i32])?;
8648 /// assert_eq!(TensorWrite::from_tensor(&mut tensor).dtype(), DType::I32);
8649 /// # Ok::<(), tenferro_tensor::Error>(())
8650 /// ```
8651 pub fn dtype(&self) -> DType {
8652 match self {
8653 Self::Tensor(tensor) => tensor.dtype(),
8654 Self::View(view) => view.dtype(),
8655 }
8656 }
8657
8658 /// Return the logical shape of this writable target.
8659 ///
8660 /// # Examples
8661 ///
8662 /// ```
8663 /// use tenferro_tensor::{Tensor, TensorWrite};
8664 ///
8665 /// let mut tensor = Tensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
8666 /// assert_eq!(TensorWrite::from_tensor(&mut tensor).shape(), &[2, 1]);
8667 /// # Ok::<(), tenferro_tensor::Error>(())
8668 /// ```
8669 pub fn shape(&self) -> &[usize] {
8670 match self {
8671 Self::Tensor(tensor) => tensor.shape(),
8672 Self::View(view) => view.shape(),
8673 }
8674 }
8675
8676 /// Return strides in element units; owned tensors report compact column-major strides.
8677 ///
8678 /// # Examples
8679 ///
8680 /// ```
8681 /// use tenferro_tensor::{Tensor, TensorWrite};
8682 ///
8683 /// let mut tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8684 /// assert_eq!(TensorWrite::from_tensor(&mut tensor).strides()?, vec![1, 2]);
8685 /// # Ok::<(), tenferro_tensor::Error>(())
8686 /// ```
8687 ///
8688 /// # Errors
8689 ///
8690 /// Returns [`crate::Error::Validation`] with
8691 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8692 /// column-major stride arithmetic overflows.
8693 pub fn strides(&self) -> crate::Result<Vec<isize>> {
8694 match self {
8695 Self::Tensor(tensor) => Ok(col_major_strides(tensor.shape())?.into_vec()),
8696 Self::View(view) => Ok(view.strides().to_vec()),
8697 }
8698 }
8699
8700 /// Return the physical element offset; owned tensors always start at `0`.
8701 ///
8702 /// # Examples
8703 ///
8704 /// ```
8705 /// use tenferro_tensor::{TensorViewMut, TensorWrite, TypedTensorViewMut};
8706 ///
8707 /// let mut data = [1.0_f64, 2.0, 3.0];
8708 /// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![1], 1, &mut data)?);
8709 /// assert_eq!(TensorWrite::from_view(view).offset(), 1);
8710 /// # Ok::<(), tenferro_tensor::Error>(())
8711 /// ```
8712 pub fn offset(&self) -> isize {
8713 match self {
8714 Self::Tensor(_) => 0,
8715 Self::View(view) => view.offset(),
8716 }
8717 }
8718
8719 /// Compute the physical element offset for a logical index.
8720 ///
8721 /// # Examples
8722 ///
8723 /// ```
8724 /// use tenferro_tensor::{Tensor, TensorWrite};
8725 ///
8726 /// let mut tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8727 /// let write = TensorWrite::from_tensor(&mut tensor);
8728 /// assert_eq!(write.layout_linear_offset(&[1, 2])?, 5);
8729 /// assert!(write.layout_linear_offset(&[0]).is_err());
8730 /// # Ok::<(), tenferro_tensor::Error>(())
8731 /// ```
8732 ///
8733 /// # Errors
8734 ///
8735 /// Returns [`crate::Error::Validation`] with
8736 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8737 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8738 /// when an index is outside its axis extent, or
8739 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8740 /// arithmetic overflows.
8741 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8742 match self {
8743 Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
8744 Self::View(view) => view.layout_linear_offset(indices),
8745 }
8746 }
8747
8748 /// Return whether this writable target is compact column-major.
8749 ///
8750 /// # Examples
8751 ///
8752 /// ```
8753 /// use tenferro_tensor::{Tensor, TensorViewMut, TensorWrite, TypedTensorViewMut};
8754 ///
8755 /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8756 /// assert!(TensorWrite::from_tensor(&mut tensor).is_col_major_contiguous()?);
8757 /// let mut data = [1.0_f64, 2.0, 3.0];
8758 /// let strided = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![2], 0, &mut data)?);
8759 /// assert!(!TensorWrite::from_view(strided).is_col_major_contiguous()?);
8760 /// # Ok::<(), tenferro_tensor::Error>(())
8761 /// ```
8762 ///
8763 /// # Errors
8764 ///
8765 /// Returns [`crate::Error::Validation`] with
8766 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8767 /// compactness arithmetic overflows.
8768 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8769 match self {
8770 Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
8771 Self::View(view) => view.is_col_major_contiguous(),
8772 }
8773 }
8774
8775 /// Return a compact string summary of this writable target's layout metadata.
8776 ///
8777 /// # Examples
8778 ///
8779 /// ```
8780 /// use tenferro_tensor::{Tensor, TensorWrite};
8781 ///
8782 /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8783 /// assert_eq!(
8784 /// TensorWrite::from_tensor(&mut tensor).layout_summary(),
8785 /// "shape=[2] strides=[1] offset=0"
8786 /// );
8787 /// # Ok::<(), tenferro_tensor::Error>(())
8788 /// ```
8789 pub fn layout_summary(&self) -> String {
8790 let strides = match self.strides() {
8791 Ok(strides) => strides,
8792 Err(err) => return format!("layout unavailable: {err}"),
8793 };
8794 layout_summary(self.shape(), &strides, self.offset())
8795 }
8796
8797 /// Assert this writable target is compact column-major.
8798 ///
8799 /// # Examples
8800 ///
8801 /// ```
8802 /// use tenferro_tensor::{Tensor, TensorViewMut, TensorWrite, TypedTensorViewMut};
8803 ///
8804 /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8805 /// TensorWrite::from_tensor(&mut tensor).assert_col_major_contiguous()?;
8806 /// let mut data = [1.0_f64, 2.0, 3.0];
8807 /// let strided = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![2], 0, &mut data)?);
8808 /// assert!(TensorWrite::from_view(strided).assert_col_major_contiguous().is_err());
8809 /// # Ok::<(), tenferro_tensor::Error>(())
8810 /// ```
8811 ///
8812 /// # Errors
8813 ///
8814 /// Returns [`crate::Error::Validation`] with
8815 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8816 /// compactness arithmetic overflows, or
8817 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8818 /// view is not compact column-major.
8819 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8820 let strides = self.strides()?;
8821 assert_layout_col_major_contiguous(
8822 self.is_col_major_contiguous()?,
8823 self.shape(),
8824 &strides,
8825 self.offset(),
8826 "TensorWrite::assert_col_major_contiguous",
8827 )
8828 }
8829}
8830
8831/// Column-major strides derived from a shape.
8832///
8833/// # Examples
8834///
8835/// ```rust
8836/// use tenferro_tensor::col_major_strides;
8837///
8838/// assert_eq!(col_major_strides(&[2, 3])?.as_slice(), &[1, 2]);
8839/// # Ok::<(), tenferro_tensor::Error>(())
8840/// ```
8841/// # Errors
8842///
8843/// Returns [`crate::Error::Validation`] with
8844/// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when a
8845/// column-major stride product overflows.
8846pub fn col_major_strides(shape: &[usize]) -> crate::Result<StrideVec> {
8847 // Inline for common ranks: every compact destination on the CPU and CUDA
8848 // paths derives its strides here, so the heap is off this path entirely.
8849 let mut strides = StrideVec::with_capacity(shape.len());
8850 let mut stride = 1isize;
8851 for &extent in shape {
8852 strides.push(stride);
8853 let extent = isize::try_from(extent).map_err(|_| {
8854 crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
8855 })?;
8856 stride = stride.checked_mul(extent).ok_or_else(|| {
8857 crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
8858 })?;
8859 }
8860 Ok(strides)
8861}
8862
8863fn try_linear_offset_for_shape(
8864 shape: &[usize],
8865 indices: &[usize],
8866 op: &'static str,
8867) -> crate::Result<usize> {
8868 if indices.len() != shape.len() {
8869 return Err(crate::Error::validation(
8870 op,
8871 ValidationError::RankMismatch {
8872 expected: shape.len(),
8873 actual: indices.len(),
8874 },
8875 ));
8876 }
8877 let mut offset = 0usize;
8878 let mut stride = 1usize;
8879 for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
8880 if idx >= extent {
8881 return Err(crate::Error::invalid_argument(
8882 op,
8883 "index",
8884 format!("index {idx} out of bounds for axis {axis} extent {extent}"),
8885 ));
8886 }
8887 offset =
8888 offset
8889 .checked_add(idx.checked_mul(stride).ok_or_else(|| {
8890 crate::Error::validation(op, ValidationError::IntegerOverflow)
8891 })?)
8892 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8893 stride = stride
8894 .checked_mul(extent)
8895 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8896 }
8897 Ok(offset)
8898}
8899
8900fn checked_view_offset_result(
8901 shape: &[usize],
8902 strides: &[isize],
8903 base_offset: isize,
8904 indices: &[usize],
8905 op: &'static str,
8906) -> crate::Result<usize> {
8907 if indices.len() != shape.len() {
8908 return Err(crate::Error::validation(
8909 op,
8910 ValidationError::RankMismatch {
8911 expected: shape.len(),
8912 actual: indices.len(),
8913 },
8914 ));
8915 }
8916 for (axis, (&index, &extent)) in indices.iter().zip(shape).enumerate() {
8917 if index >= extent {
8918 return Err(crate::Error::invalid_argument(
8919 op,
8920 "index",
8921 format!("index {index} out of bounds for axis {axis} extent {extent}"),
8922 ));
8923 }
8924 }
8925 checked_view_offset(shape, strides, base_offset, indices)
8926 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
8927}
8928
8929fn layout_summary(shape: &[usize], strides: &[isize], offset: isize) -> String {
8930 format!("shape={shape:?} strides={strides:?} offset={offset}")
8931}
8932
8933fn assert_layout_col_major_contiguous(
8934 is_contiguous: bool,
8935 shape: &[usize],
8936 strides: &[isize],
8937 offset: isize,
8938 op: &'static str,
8939) -> crate::Result<()> {
8940 if is_contiguous {
8941 Ok(())
8942 } else {
8943 Err(crate::Error::invalid_argument(
8944 op,
8945 "layout",
8946 format!(
8947 "expected compact column-major layout, got {}",
8948 layout_summary(shape, strides, offset)
8949 ),
8950 ))
8951 }
8952}
8953
8954fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
8955 shape.iter().try_fold(1usize, |acc, &dim| {
8956 acc.checked_mul(dim)
8957 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
8958 })
8959}
8960
8961fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
8962 let n = try_shape_product(shape, op)?;
8963 if data_len != n {
8964 return Err(crate::Error::validation(
8965 op,
8966 ValidationError::ShapeDataLengthMismatch {
8967 expected: n,
8968 actual: data_len,
8969 },
8970 ));
8971 }
8972 Ok(())
8973}
8974
8975fn try_compact_layout<R: TensorRank>(
8976 shape: impl tenferro_tensor_core::IntoRankShape<R>,
8977 op: &'static str,
8978) -> crate::Result<TensorLayout<R>> {
8979 let shape = shape
8980 .into_rank_shape()
8981 .map_err(|err| tensor_layout_error(op, err))?;
8982 TensorLayout::compact(shape).map_err(|err| tensor_layout_error(op, err))
8983}
8984
8985fn tensor_layout_error(
8986 op: &'static str,
8987 err: tenferro_tensor_core::ValidationError,
8988) -> crate::Error {
8989 crate::Error::validation(op, err)
8990}
8991
8992fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
8993 if shape.contains(&0) {
8994 return Ok(0);
8995 }
8996 shape.iter().try_fold(1usize, |product, &dim| {
8997 product
8998 .checked_mul(dim)
8999 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
9000 })
9001}
9002
9003fn checked_view_offset(
9004 shape: &[usize],
9005 strides: &[isize],
9006 base_offset: isize,
9007 indices: &[usize],
9008) -> Option<usize> {
9009 if indices.len() != shape.len() {
9010 return None;
9011 }
9012
9013 let mut offset = base_offset;
9014 for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
9015 if index >= extent {
9016 return None;
9017 }
9018 let index = isize::try_from(index).ok()?;
9019 let delta = index.checked_mul(stride)?;
9020 offset = offset.checked_add(delta)?;
9021 }
9022
9023 usize::try_from(offset).ok()
9024}
9025
9026fn reachable_layout_span(
9027 shape: &[usize],
9028 strides: &[isize],
9029 offset: isize,
9030) -> crate::Result<Option<(usize, usize)>> {
9031 if shape.contains(&0) {
9032 return Ok(None);
9033 }
9034
9035 let mut min_offset = offset;
9036 let mut max_offset = offset;
9037 for (&extent, &stride) in shape.iter().zip(strides) {
9038 let steps = isize::try_from(extent.saturating_sub(1)).map_err(|_| {
9039 crate::Error::validation(
9040 "TypedTensorViewMut::try_multi_slice_mut",
9041 ValidationError::IntegerOverflow,
9042 )
9043 })?;
9044 let end = stride.checked_mul(steps).ok_or_else(|| {
9045 crate::Error::validation(
9046 "TypedTensorViewMut::try_multi_slice_mut",
9047 ValidationError::IntegerOverflow,
9048 )
9049 })?;
9050 let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
9051 min_offset = min_offset.checked_add(axis_min).ok_or_else(|| {
9052 crate::Error::validation(
9053 "TypedTensorViewMut::try_multi_slice_mut",
9054 ValidationError::IntegerOverflow,
9055 )
9056 })?;
9057 max_offset = max_offset.checked_add(axis_max).ok_or_else(|| {
9058 crate::Error::validation(
9059 "TypedTensorViewMut::try_multi_slice_mut",
9060 ValidationError::IntegerOverflow,
9061 )
9062 })?;
9063 }
9064
9065 let min_offset = usize::try_from(min_offset).map_err(|_| {
9066 crate::Error::invalid_argument(
9067 "TypedTensorViewMut::try_multi_slice_mut",
9068 "layout",
9069 "minimum reachable offset is negative",
9070 )
9071 })?;
9072 let max_offset = usize::try_from(max_offset).map_err(|_| {
9073 crate::Error::invalid_argument(
9074 "TypedTensorViewMut::try_multi_slice_mut",
9075 "layout",
9076 "maximum reachable offset is negative",
9077 )
9078 })?;
9079 Ok(Some((min_offset, max_offset)))
9080}
9081
9082fn split_two_mut_ranges<T>(
9083 data: &mut [T],
9084 first: (usize, usize),
9085 second: (usize, usize),
9086) -> Option<(&mut [T], &mut [T])> {
9087 if first.1 < second.0 {
9088 let (_, after_first_start) = data.split_at_mut(first.0);
9089 let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
9090 let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
9091 let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
9092 Some((first_slice, second_slice))
9093 } else if second.1 < first.0 {
9094 let (_, after_second_start) = data.split_at_mut(second.0);
9095 let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
9096 let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
9097 let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
9098 Some((first_slice, second_slice))
9099 } else {
9100 None
9101 }
9102}
9103
9104fn adjusted_view_offset(offset: isize, span_start: usize) -> crate::Result<isize> {
9105 let span_start = isize::try_from(span_start).map_err(|_| {
9106 crate::Error::validation(
9107 "TypedTensorViewMut::try_multi_slice_mut",
9108 ValidationError::IntegerOverflow,
9109 )
9110 })?;
9111 offset.checked_sub(span_start).ok_or_else(|| {
9112 crate::Error::validation(
9113 "TypedTensorViewMut::try_multi_slice_mut",
9114 ValidationError::IntegerOverflow,
9115 )
9116 })
9117}
9118
9119fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
9120 layout: &TensorLayout<R>,
9121 offset: isize,
9122 data: &'a mut [T],
9123 placement: Placement,
9124) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
9125 let shape = R::shape_from_vec(shape_vec(layout.shape()))
9126 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
9127 let strides = R::strides_from_vec(stride_vec(layout.strides()))
9128 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
9129 TypedTensorViewMut::from_buffer_ref_mut(
9130 shape,
9131 strides,
9132 offset,
9133 TensorStorageRefMut::Host(data),
9134 placement,
9135 "TypedTensorViewMut::try_multi_slice_mut",
9136 )
9137}
9138
9139fn contiguous_layout_slice<'a, T, R: TensorRank>(
9140 layout: &TensorLayout<R>,
9141 data: &'a [T],
9142 op: &'static str,
9143) -> crate::Result<&'a [T]> {
9144 if !layout
9145 .is_compact_col_major()
9146 .map_err(|err| tensor_layout_error(op, err))?
9147 {
9148 return Err(crate::Error::validation(
9149 op,
9150 ValidationError::NonContiguousViewAsSlice,
9151 ));
9152 }
9153 let len = checked_view_element_count(layout.shape(), op)?;
9154 let start = usize::try_from(layout.offset())
9155 .map_err(|_| crate::Error::invalid_argument(op, "layout", "view offset is negative"))?;
9156 let end = start
9157 .checked_add(len)
9158 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
9159 data.get(start..end)
9160 .ok_or_else(|| crate::Error::validation(op, ValidationError::ViewOutOfBounds))
9161}
9162
9163fn relaxed_col_major_contiguous(
9164 shape: &[usize],
9165 strides: &[isize],
9166 op: &'static str,
9167) -> crate::Result<bool> {
9168 if shape.contains(&0) {
9169 return Ok(true);
9170 }
9171
9172 let mut expected = 1isize;
9173 for (&extent, &stride) in shape.iter().zip(strides) {
9174 if extent <= 1 {
9175 continue;
9176 }
9177 if stride != expected {
9178 return Ok(false);
9179 }
9180 let extent = isize::try_from(extent)
9181 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
9182 expected = expected
9183 .checked_mul(extent)
9184 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
9185 }
9186 Ok(true)
9187}
9188
9189fn reshape_layout_dyn<R: TensorRank>(
9190 layout: &TensorLayout<R>,
9191 shape: &[usize],
9192 buffer_len: usize,
9193 op: &'static str,
9194) -> crate::Result<TensorLayout<DynRank>> {
9195 match layout.reshape_view_as::<DynRank>(shape_vec(shape), buffer_len) {
9196 Ok(layout) => Ok(layout),
9197 Err(err) => {
9198 if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
9199 return Err(tensor_layout_error(op, err));
9200 }
9201 let from = checked_view_element_count(layout.shape(), op)?;
9202 let to = checked_view_element_count(shape, op)?;
9203 if from != to {
9204 return Err(tensor_layout_error(
9205 op,
9206 tenferro_tensor_core::ShapeMismatch::ReshapeElementCount { from, to }.into(),
9207 ));
9208 }
9209 TensorLayout::<DynRank>::compact(shape_vec(shape))
9210 .and_then(|compact| {
9211 TensorLayout::from_parts(
9212 shape_vec(compact.shape()),
9213 stride_vec(compact.strides()),
9214 layout.offset(),
9215 buffer_len,
9216 )
9217 })
9218 .map_err(|err| tensor_layout_error(op, err))
9219 }
9220 }
9221}
9222
9223fn core_slice_specs(
9224 slices: &[StridedSliceSpec],
9225 shape: &[usize],
9226 op: &'static str,
9227) -> crate::Result<TinyVec<[CoreSliceSpec; 8]>> {
9228 if slices.len() != shape.len() {
9229 return Err(crate::Error::validation(
9230 op,
9231 ValidationError::RankMismatch {
9232 expected: shape.len(),
9233 actual: slices.len(),
9234 },
9235 ));
9236 }
9237
9238 let mut specs = TinyVec::<[CoreSliceSpec; 8]>::with_capacity(slices.len());
9239 for (slice, &axis_len) in slices.iter().zip(shape) {
9240 specs.push(core_slice_spec(*slice, axis_len, op)?);
9241 }
9242 Ok(specs)
9243}
9244
9245fn core_slice_spec(
9246 slice: StridedSliceSpec,
9247 axis_len: usize,
9248 op: &'static str,
9249) -> crate::Result<CoreSliceSpec> {
9250 if slice.step() == 0 {
9251 return Err(crate::Error::validation(
9252 op,
9253 ValidationError::InvalidSliceStep { step: slice.step() },
9254 ));
9255 }
9256
9257 let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
9258 let end = match slice.end() {
9259 Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
9260 None => isize::try_from(axis_len)
9261 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
9262 };
9263
9264 if slice.step() > 0 {
9265 return Ok(CoreSliceSpec {
9266 start,
9267 end,
9268 step: slice.step(),
9269 });
9270 }
9271
9272 if start >= end {
9273 // `core`'s negative-step contract requires the inclusive `start` to
9274 // point at an element (`start < axis_len`). An empty selection is
9275 // step-sign independent, so encode the `start == axis_len` boundary at
9276 // the last element instead of re-encoding it verbatim (which `core`
9277 // would reject as out of bounds).
9278 let empty_start = if axis_len == 0 {
9279 start
9280 } else {
9281 start.min(axis_len as isize - 1)
9282 };
9283 return Ok(CoreSliceSpec {
9284 start: empty_start,
9285 end: empty_start,
9286 step: slice.step(),
9287 });
9288 }
9289
9290 Ok(CoreSliceSpec {
9291 start: end
9292 .checked_sub(1)
9293 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
9294 end: start
9295 .checked_sub(1)
9296 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
9297 step: slice.step(),
9298 })
9299}
9300
9301fn normalize_strided_bound(
9302 bound: isize,
9303 axis_len: usize,
9304 op: &'static str,
9305 role: &'static str,
9306) -> crate::Result<isize> {
9307 let original_axis_len = axis_len;
9308 let axis_len = isize::try_from(axis_len)
9309 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
9310 let bound = if bound < 0 {
9311 axis_len
9312 .checked_add(bound)
9313 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?
9314 } else {
9315 bound
9316 };
9317 if !(0..=axis_len).contains(&bound) {
9318 let (start, end) = if role == "slice start" {
9319 (bound, bound)
9320 } else {
9321 (0, bound)
9322 };
9323 return Err(crate::Error::validation(
9324 op,
9325 ValidationError::InvalidSliceBounds {
9326 start,
9327 end,
9328 axis_len: original_axis_len,
9329 },
9330 ));
9331 }
9332 Ok(bound)
9333}
9334
9335fn slice_axis_specs(
9336 rank: usize,
9337 axis: usize,
9338 slice: StridedSliceSpec,
9339 op: &'static str,
9340) -> crate::Result<Vec<StridedSliceSpec>> {
9341 if axis >= rank {
9342 return Err(crate::Error::validation(
9343 op,
9344 ValidationError::AxisOutOfBounds { axis, rank },
9345 ));
9346 }
9347
9348 let mut slices = vec![StridedSliceSpec::all(); rank];
9349 slices[axis] = slice;
9350 Ok(slices)
9351}
9352
9353pub(crate) fn default_placement() -> Placement {
9354 Placement {
9355 memory_kind: MemoryKind::UnpinnedHost,
9356 device: None,
9357 cpu_affinity: None,
9358 }
9359}
9360
9361fn typed_tensor_from_vec_col_major<T, R: TensorRank>(
9362 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9363 data: Vec<T>,
9364 op: &'static str,
9365) -> crate::Result<TypedTensor<T, R>> {
9366 try_typed_tensor_from_vec_col_major(shape, data, op)
9367}
9368
9369fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
9370 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9371 data: Vec<T>,
9372 op: &'static str,
9373) -> crate::Result<TypedTensor<T, R>> {
9374 let shape = shape
9375 .into_rank_shape()
9376 .map_err(|err| tensor_layout_error(op, err))?;
9377 let layout = tenferro_tensor_core::col_major_strides(shape.as_ref())
9378 .map_err(|err| tensor_layout_error(op, err))?;
9379 try_checked_shape_len(shape.as_ref(), data.len(), op)?;
9380 Ok(TypedTensor {
9381 shape,
9382 placement: default_placement(),
9383 layout,
9384 storage: DynamicStorage::Host(HostStorage {
9385 data: HostData::new(data),
9386 }),
9387 })
9388}
9389
9390fn typed_tensor_zeros<T: TensorScalar + Zero, R: TensorRank>(
9391 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9392) -> crate::Result<TypedTensor<T, R>> {
9393 try_typed_tensor_zeros(shape)
9394}
9395
9396fn try_typed_tensor_zeros<T: TensorScalar + Clone + Zero, R: TensorRank>(
9397 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9398) -> crate::Result<TypedTensor<T, R>> {
9399 let layout = try_compact_layout(shape, "zeros")?;
9400 let n = try_shape_product(layout.shape(), "zeros")?;
9401 typed_tensor_from_vec_col_major(
9402 R::shape_from_vec(shape_vec(layout.shape()))
9403 .map_err(|err| tensor_layout_error("zeros", err))?,
9404 vec![T::zero(); n],
9405 "zeros",
9406 )
9407}
9408
9409fn typed_tensor_ones<T: TensorScalar + One + Zero, R: TensorRank>(
9410 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9411) -> crate::Result<TypedTensor<T, R>> {
9412 try_typed_tensor_ones(shape)
9413}
9414
9415fn try_typed_tensor_ones<T: TensorScalar + Clone + One + Zero, R: TensorRank>(
9416 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9417) -> crate::Result<TypedTensor<T, R>> {
9418 let layout = try_compact_layout(shape, "ones")?;
9419 let n = try_shape_product(layout.shape(), "ones")?;
9420 typed_tensor_from_vec_col_major(
9421 R::shape_from_vec(shape_vec(layout.shape()))
9422 .map_err(|err| tensor_layout_error("ones", err))?,
9423 vec![T::one(); n],
9424 "ones",
9425 )
9426}
9427
9428fn typed_tensor_from_buffer_col_major<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
9429 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9430 buffer: StorageBuffer<T>,
9431 placement: Placement,
9432) -> crate::Result<TypedTensor<T, R>> {
9433 try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
9434}
9435
9436#[doc(hidden)]
9437fn typed_tensor_from_backend_allocation<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
9438 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9439 allocation: Box<dyn crate::BackendAllocation>,
9440 placement: Placement,
9441) -> crate::Result<TypedTensor<T, R>> {
9442 let layout = try_compact_layout(shape, "from_backend_allocation")?;
9443 let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
9444 .map_err(|err| tensor_layout_error("from_backend_allocation", err))?;
9445 let shape = group_shape.clone();
9446 let (mut group, slot) =
9447 AllocationGroup::from_backend_allocation::<T, R>(group_shape, allocation)
9448 .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9449 group
9450 .set_descriptor_placement(slot, placement.clone())
9451 .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9452 let allocation_index = group
9453 .allocation_index(slot)
9454 .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9455 let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
9456 Ok(TypedTensor {
9457 shape,
9458 placement,
9459 layout: stride_vec(layout.strides()),
9460 storage: DynamicStorage::Group(GroupStorage {
9461 group: Box::new(OwnedTensorGroup {
9462 group,
9463 slot,
9464 allocation_index,
9465 host_ptr,
9466 host_byte_len,
9467 _rank: PhantomData,
9468 }),
9469 }),
9470 })
9471}
9472
9473fn try_typed_tensor_from_buffer_col_major<
9474 T: TensorScalar + Send + Sync + 'static,
9475 R: TensorRank,
9476>(
9477 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9478 buffer: StorageBuffer<T>,
9479 placement: Placement,
9480) -> crate::Result<TypedTensor<T, R>> {
9481 let layout = try_compact_layout(shape, "from_buffer_col_major")?;
9482 let len = buffer.len();
9483 try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
9484 let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
9485 .map_err(|err| tensor_layout_error("from_buffer_col_major", err))?;
9486 match buffer {
9487 StorageBuffer::Host(data) => Ok(TypedTensor {
9488 shape: group_shape,
9489 placement,
9490 layout: stride_vec(layout.strides()),
9491 storage: DynamicStorage::Host(HostStorage {
9492 data: HostData::new(data),
9493 }),
9494 }),
9495 StorageBuffer::Backend(buffer) => {
9496 let group = OwnedTensorGroup::from_backend_buffer(
9497 group_shape.clone(),
9498 StorageBuffer::Backend(buffer),
9499 placement.clone(),
9500 )?;
9501 Ok(TypedTensor {
9502 shape: group_shape,
9503 placement,
9504 layout: stride_vec(layout.strides()),
9505 storage: DynamicStorage::Group(GroupStorage {
9506 group: Box::new(group),
9507 }),
9508 })
9509 }
9510 }
9511}
9512
9513impl<T: TensorScalar + Zero, R: TensorRank> TypedTensor<T, R> {
9514 /// Allocate a zero-filled tensor.
9515 ///
9516 /// # Examples
9517 ///
9518 /// ```rust
9519 /// use tenferro_tensor::TypedTensor;
9520 ///
9521 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
9522 /// assert_eq!(t.n_elements(), 6);
9523 /// ```
9524 /// # Errors
9525 ///
9526 /// Returns [`crate::Error::Validation`] with
9527 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
9528 /// product or compact-stride arithmetic overflows.
9529 /// A vector or slice with a different static rank returns
9530 /// [`tenferro_tensor_core::ValidationError::RankMismatch`].
9531 pub fn zeros(shape: impl tenferro_tensor_core::IntoRankShape<R>) -> crate::Result<Self> {
9532 typed_tensor_zeros(shape)
9533 }
9534}
9535
9536impl<T: TensorScalar + One + Zero, R: TensorRank> TypedTensor<T, R> {
9537 /// Allocate a one-filled tensor.
9538 ///
9539 /// # Examples
9540 ///
9541 /// ```rust
9542 /// use tenferro_tensor::TypedTensor;
9543 ///
9544 /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
9545 /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
9546 /// ```
9547 /// # Errors
9548 ///
9549 /// Returns [`crate::Error::Validation`] with
9550 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
9551 /// product or compact-stride arithmetic overflows.
9552 /// A vector or slice with a different static rank returns
9553 /// [`tenferro_tensor_core::ValidationError::RankMismatch`].
9554 pub fn ones(shape: impl tenferro_tensor_core::IntoRankShape<R>) -> crate::Result<Self> {
9555 typed_tensor_ones(shape)
9556 }
9557}
9558
9559impl<T, R: TensorRank> TypedTensor<T, R> {
9560 /// Adopt a column-major host `Vec<T>` with no scalar, copy or thread-safety bound.
9561 ///
9562 /// # Examples
9563 /// ```
9564 /// use tenferro_tensor::{Rank, TypedTensor};
9565 /// struct Custom(String);
9566 /// let tensor = TypedTensor::<Custom, Rank<2>>::from_vec_col_major(
9567 /// [1, 2], vec![Custom("a".into()), Custom("b".into())],
9568 /// )?;
9569 /// assert_eq!(tensor.get(&[0, 1])?.0.as_str(), "b");
9570 /// # Ok::<(), tenferro_tensor::Error>(())
9571 /// ```
9572 /// # Errors
9573 /// Returns [`crate::Error::Validation`] for a rank mismatch, shape/data
9574 /// length mismatch or shape/stride arithmetic overflow.
9575 pub fn from_vec_col_major(
9576 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9577 data: Vec<T>,
9578 ) -> crate::Result<Self> {
9579 typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
9580 }
9581
9582 /// Explicitly import row-major host values into column-major storage.
9583 ///
9584 /// Clones each input element once; no backend or scalar registration is used.
9585 ///
9586 /// # Examples
9587 /// ```
9588 /// use tenferro_tensor::{Rank, TypedTensor};
9589 /// let tensor = TypedTensor::<i32, Rank<2>>::from_vec_row_major(
9590 /// [2, 3], vec![1, 2, 3, 4, 5, 6],
9591 /// )?;
9592 /// assert_eq!(tensor.get(&[1, 0])?, &4);
9593 /// assert_eq!(tensor.get(&[0, 2])?, &3);
9594 /// # Ok::<(), tenferro_tensor::Error>(())
9595 /// ```
9596 /// # Errors
9597 /// Returns [`crate::Error::Validation`] for a rank, shape-length or stride overflow,
9598 /// or when the shape product disagrees with the input length.
9599 pub fn from_vec_row_major(
9600 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9601 data: Vec<T>,
9602 ) -> crate::Result<Self>
9603 where
9604 T: Clone,
9605 {
9606 let op = "from_vec_row_major";
9607 let (shape, reordered) = row_major_reorder(shape, data, op)?;
9608 typed_tensor_from_vec_col_major(shape, reordered, op)
9609 }
9610
9611 /// Consume this compact tensor and return the original host `Vec<T>`.
9612 ///
9613 /// # Examples
9614 ///
9615 /// ```
9616 /// use tenferro_tensor::TypedTensor;
9617 /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
9618 /// let data = tensor.into_host_vec().map_err(|failure| failure.into_parts().1)?;
9619 /// assert_eq!(data, vec![1, 2]);
9620 /// # Ok::<(), tenferro_tensor::Error>(())
9621 /// ```
9622 ///
9623 /// # Errors
9624 /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9625 /// storage is device-only, the managed root cannot export a host vector, or
9626 /// a group-backed descriptor is not compact column-major
9627 /// ([`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]).
9628 pub fn into_host_vec(self) -> std::result::Result<Vec<T>, ReinterpretError<Self>>
9629 where
9630 T: 'static,
9631 {
9632 let TypedTensor {
9633 shape,
9634 placement,
9635 layout,
9636 storage,
9637 } = self;
9638 match storage {
9639 DynamicStorage::Host(HostStorage { data }) => Ok(data.into_vec()),
9640 DynamicStorage::Group(GroupStorage { group }) => {
9641 let range = match group_logical_range(&group, "TypedTensor::into_host_vec") {
9642 Ok(range) => range,
9643 Err(error) => {
9644 return Err(ReinterpretError::new(
9645 TypedTensor {
9646 shape,
9647 placement,
9648 layout,
9649 storage: DynamicStorage::Group(GroupStorage { group }),
9650 },
9651 error,
9652 ))
9653 }
9654 };
9655 match group.into_host_vec::<T>() {
9656 Ok(mut data) => {
9657 // INVARIANT: the live descriptor's compact logical range
9658 // was validated inside its root span, so the exported
9659 // vector covers it.
9660 debug_assert!(range.end <= data.len(), "descriptor range exceeds its root");
9661 data.truncate(range.end);
9662 data.drain(..range.start);
9663 // The exported vector represents the logical extent only,
9664 // so it must not retain the root's capacity.
9665 data.shrink_to_fit();
9666 Ok(data)
9667 }
9668 Err((group, error)) => Err(ReinterpretError::new(
9669 TypedTensor {
9670 shape,
9671 placement,
9672 layout,
9673 storage: DynamicStorage::Group(GroupStorage {
9674 group: Box::new(group),
9675 }),
9676 },
9677 error,
9678 )),
9679 }
9680 }
9681 }
9682 }
9683
9684 /// Consume the original host vector along with its column-major shape.
9685 ///
9686 /// # Examples
9687 ///
9688 /// ```
9689 /// use tenferro_tensor::TypedTensor;
9690 /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2, 1], vec![1, 2])?;
9691 /// let (shape, data) = tensor.into_vec_col_major().map_err(|failure| failure.into_parts().1)?;
9692 /// assert_eq!(shape, vec![2, 1]);
9693 /// assert_eq!(data, vec![1, 2]);
9694 /// # Ok::<(), tenferro_tensor::Error>(())
9695 /// ```
9696 ///
9697 /// # Errors
9698 /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9699 /// storage is device-only, the managed root cannot export a host vector, or
9700 /// a group-backed descriptor is not compact column-major
9701 /// ([`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]).
9702 pub fn into_vec_col_major(
9703 self,
9704 ) -> std::result::Result<(Vec<usize>, Vec<T>), ReinterpretError<Self>>
9705 where
9706 T: 'static,
9707 {
9708 let shape = self.shape().to_vec();
9709 match self.into_host_vec() {
9710 Ok(data) => Ok((shape, data)),
9711 Err(failure) => Err(failure),
9712 }
9713 }
9714
9715 /// Borrow the plain host values of this tensor.
9716 ///
9717 /// A group-backed owner extracted from a larger root exposes only its
9718 /// logical, compact values; [`Self::as_view`] retains the whole backing
9719 /// root when a caller needs to inspect elements outside the extent.
9720 ///
9721 /// # Examples
9722 ///
9723 /// ```
9724 /// use tenferro_tensor::TypedTensor;
9725 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
9726 /// assert_eq!(tensor.host_data()?, &[1.0, 2.0]);
9727 /// # Ok::<(), tenferro_tensor::Error>(())
9728 /// ```
9729 ///
9730 /// # Errors
9731 /// Returns [`crate::Error::RuntimeState`] for device-only storage and the
9732 /// non-compact-descriptor validation error for a strided owner.
9733 pub fn host_data(&self) -> crate::Result<&[T]> {
9734 match &self.storage {
9735 DynamicStorage::Host(HostStorage { data }) => Ok(data.as_slice()),
9736 DynamicStorage::Group(core) => {
9737 let data = core.group.host_slice::<T>()?;
9738 let range = group_logical_range(&core.group, "TypedTensor::host_data")?;
9739 data.get(range).ok_or_else(|| {
9740 crate::Error::validation(
9741 "TypedTensor::host_data",
9742 ValidationError::ViewOutOfBounds,
9743 )
9744 })
9745 }
9746 }
9747 }
9748
9749 /// Borrow the logical host values as a flat column-major slice without
9750 /// copying.
9751 ///
9752 /// This is the value view of [`Self::host_data`]: a root-preserving
9753 /// extraction exposes only its compact logical slice, never the root prefix
9754 /// and never elements outside its extent.
9755 ///
9756 /// # Examples
9757 /// ```
9758 /// use tenferro_tensor::TypedTensor;
9759 /// let tensor = TypedTensor::<String>::from_vec_col_major([2], vec!["a".into(), "b".into()])?;
9760 /// assert_eq!(tensor.as_slice()?, &["a", "b"]);
9761 /// # Ok::<(), tenferro_tensor::Error>(())
9762 /// ```
9763 /// # Errors
9764 /// Device-only storage returns [`crate::Error::RuntimeState`].
9765 pub fn as_slice(&self) -> crate::Result<&[T]> {
9766 self.host_data()
9767 }
9768
9769 /// Mutably borrow the plain host values of this tensor.
9770 ///
9771 /// The exclusive borrow covers exactly the logical, compact values of a
9772 /// root-preserving owner, so writes cannot reach elements outside its
9773 /// extent.
9774 ///
9775 /// # Examples
9776 ///
9777 /// ```
9778 /// use tenferro_tensor::TypedTensor;
9779 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
9780 /// tensor.host_data_mut()?[0] = 5.0;
9781 /// assert_eq!(tensor.host_data()?, &[5.0, 2.0]);
9782 /// # Ok::<(), tenferro_tensor::Error>(())
9783 /// ```
9784 ///
9785 /// # Errors
9786 /// Returns [`crate::Error::RuntimeState`] for device-only storage and the
9787 /// non-compact-descriptor validation error for a strided owner.
9788 pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
9789 match &mut self.storage {
9790 DynamicStorage::Host(HostStorage { data }) => Ok(data.as_mut_slice()),
9791 DynamicStorage::Group(core) => {
9792 let range = group_logical_range(&core.group, "TypedTensor::host_data_mut")?;
9793 let data = core.group.host_slice_mut::<T>()?;
9794 data.get_mut(range).ok_or_else(|| {
9795 crate::Error::validation(
9796 "TypedTensor::host_data_mut",
9797 ValidationError::ViewOutOfBounds,
9798 )
9799 })
9800 }
9801 }
9802 }
9803
9804 /// Borrow an element by checked column-major multi-index.
9805 ///
9806 /// # Examples
9807 /// ```
9808 /// use tenferro_tensor::TypedTensor;
9809 /// let tensor = TypedTensor::<String>::from_vec_col_major([2], vec!["a".into(), "b".into()])?;
9810 /// assert_eq!(tensor.get(&[1])?, "b");
9811 /// # Ok::<(), tenferro_tensor::Error>(())
9812 /// ```
9813 /// # Errors
9814 /// Invalid rank, coordinates or offset return [`crate::Error::Validation`];
9815 /// device-only storage returns [`crate::Error::RuntimeState`].
9816 pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
9817 let offset = self.linear_offset(indices)?;
9818 self.host_data()?.get(offset).ok_or_else(|| {
9819 crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
9820 })
9821 }
9822
9823 /// Exclusively borrow an element by checked column-major multi-index.
9824 ///
9825 /// # Examples
9826 /// ```
9827 /// use tenferro_tensor::TypedTensor;
9828 /// let mut tensor = TypedTensor::<String>::from_vec_col_major([1], vec!["a".into()])?;
9829 /// *tensor.get_mut(&[0])? = "b".into();
9830 /// assert_eq!(tensor.get(&[0])?, "b");
9831 /// # Ok::<(), tenferro_tensor::Error>(())
9832 /// ```
9833 /// # Errors
9834 /// Invalid rank, coordinates or offset return [`crate::Error::Validation`];
9835 /// device-only storage returns [`crate::Error::RuntimeState`].
9836 pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
9837 let offset = self.linear_offset(indices)?;
9838 self.host_data_mut()?.get_mut(offset).ok_or_else(|| {
9839 crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
9840 })
9841 }
9842
9843 /// Make an explicit independent host copy with the same shape and placement.
9844 ///
9845 /// The dynamic `TypedTensor` does not implement [`Clone`] (only the
9846 /// host-only representation does): the copy reads host data, rejects
9847 /// device-only storage, and creates a new owner, so it is fallible. See
9848 /// [`Tensor::duplicate`] for the sharing alternatives.
9849 ///
9850 /// # Examples
9851 /// ```
9852 /// use tenferro_tensor::TypedTensor;
9853 /// let tensor = TypedTensor::<String>::from_vec_col_major([1], vec!["a".into()])?;
9854 /// assert_eq!(tensor.duplicate()?.get(&[0])?, "a");
9855 /// # Ok::<(), tenferro_tensor::Error>(())
9856 /// ```
9857 /// # Errors
9858 /// Device-only storage returns [`crate::Error::RuntimeState`]; invalid
9859 /// host metadata returns [`crate::Error::Validation`].
9860 pub fn duplicate(&self) -> crate::Result<Self>
9861 where
9862 T: Clone,
9863 {
9864 let mut copy = Self::from_vec_col_major(
9865 R::shape_from_vec(shape_vec(self.shape()))
9866 .map_err(|err| tensor_layout_error("TypedTensor::duplicate", err))?,
9867 self.host_data()?.to_vec(),
9868 )?;
9869 copy.set_placement(self.placement().clone());
9870 Ok(copy)
9871 }
9872
9873 /// Create a tensor from an existing buffer and compact column-major layout.
9874 ///
9875 /// This preserves the owned tensor invariant that layout metadata is
9876 /// compact column-major, including for backend-owned buffers.
9877 ///
9878 /// # Examples
9879 ///
9880 /// ```
9881 /// use tenferro_tensor::{StorageBuffer, Placement, TypedTensor};
9882 ///
9883 /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
9884 /// vec![2],
9885 /// StorageBuffer::Host(vec![1.0, 2.0]),
9886 /// Placement {
9887 /// memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
9888 /// device: None,
9889 /// cpu_affinity: None,
9890 /// },
9891 /// )
9892 /// .unwrap();
9893 /// assert_eq!(tensor.shape(), &[2]);
9894 /// ```
9895 /// # Errors
9896 ///
9897 /// Returns [`crate::Error::Validation`] with
9898 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
9899 /// the shape product differs from the buffer length,
9900 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape or
9901 /// stride arithmetic overflows, or
9902 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a supplied
9903 /// rank-specific shape cannot be represented.
9904 pub fn from_buffer_col_major(
9905 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9906 buffer: StorageBuffer<T>,
9907 placement: Placement,
9908 ) -> crate::Result<Self>
9909 where
9910 T: TensorScalar + Send + Sync + 'static,
9911 {
9912 typed_tensor_from_buffer_col_major(shape, buffer, placement)
9913 }
9914
9915 /// Consume a scalar-independent provider root into one compact tensor.
9916 #[doc(hidden)]
9917 pub fn from_backend_allocation(
9918 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9919 allocation: Box<dyn crate::BackendAllocation>,
9920 placement: Placement,
9921 ) -> crate::Result<Self>
9922 where
9923 T: TensorScalar + Send + Sync + 'static,
9924 {
9925 typed_tensor_from_backend_allocation(shape, allocation, placement)
9926 }
9927
9928 /// Convert this tensor into static rank metadata after validating its rank.
9929 ///
9930 /// The buffer and placement are preserved. This method changes only the
9931 /// compile-time rank marker on the owned compact column-major tensor.
9932 ///
9933 /// # Examples
9934 ///
9935 /// ```
9936 /// use tenferro_tensor::{Rank, TypedTensor};
9937 ///
9938 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
9939 /// let Ok(ranked) = tensor.try_into_rank::<2>() else {
9940 /// panic!("a rank-2 tensor converts to two axes")
9941 /// };
9942 /// assert_eq!(ranked.shape(), &[2, 3]);
9943 /// # Ok::<(), tenferro_tensor::Error>(())
9944 /// ```
9945 /// # Errors
9946 ///
9947 /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9948 /// typed rank does not match the existing shape, with
9949 /// [`crate::Error::Validation`] and
9950 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] as the cause,
9951 /// or when the compact rank layout overflows.
9952 pub fn try_into_rank<const N: usize>(
9953 self,
9954 ) -> std::result::Result<TypedTensor<T, Rank<N>>, ReinterpretError<Self>> {
9955 let op = "TypedTensor::try_into_rank";
9956 let actual = self.shape().len();
9957 let shape: [usize; N] = match self.shape().try_into() {
9958 Ok(shape) => shape,
9959 Err(_) => {
9960 return Err(ReinterpretError::new(
9961 self,
9962 tensor_layout_error(
9963 op,
9964 ValidationError::RankMismatch {
9965 expected: N,
9966 actual,
9967 },
9968 ),
9969 ))
9970 }
9971 };
9972 let layout = match TensorLayout::<Rank<N>>::compact(shape) {
9973 Ok(layout) => layout,
9974 Err(error) => return Err(ReinterpretError::new(self, tensor_layout_error(op, error))),
9975 };
9976 let TypedTensor {
9977 placement, storage, ..
9978 } = self;
9979 let storage = match storage {
9980 DynamicStorage::Host(host) => DynamicStorage::Host(host),
9981 DynamicStorage::Group(GroupStorage { group }) => DynamicStorage::Group(GroupStorage {
9982 group: Box::new(OwnedTensorGroup {
9983 group: group.group,
9984 slot: group.slot,
9985 allocation_index: group.allocation_index,
9986 host_ptr: group.host_ptr,
9987 host_byte_len: group.host_byte_len,
9988 _rank: PhantomData,
9989 }),
9990 }),
9991 };
9992 Ok(TypedTensor {
9993 shape,
9994 placement,
9995 layout: stride_vec(layout.strides()),
9996 storage,
9997 })
9998 }
9999
10000 /// Return the storage backing this tensor.
10001 ///
10002 /// This is an explicit storage-inspection API for backend glue and tests.
10003 /// Host value inspection should prefer [`TypedTensor::host_data`] when the
10004 /// caller requires host storage.
10005 ///
10006 /// # Panics
10007 ///
10008 /// Panics only if the typed descriptor and its single group owner are
10009 /// internally inconsistent.
10010 ///
10011 /// # Examples
10012 ///
10013 /// ```
10014 /// use tenferro_tensor::{StorageBuffer, TypedTensor};
10015 ///
10016 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
10017 /// assert!(matches!(t.buffer(), StorageBuffer::Host(_)));
10018 /// ```
10019 pub fn buffer(&self) -> &StorageBuffer<T>
10020 where
10021 T: 'static,
10022 {
10023 match &self.storage {
10024 DynamicStorage::Host(HostStorage { data }) => &data.buffer,
10025 DynamicStorage::Group(core) => core
10026 .group
10027 .host_buffer::<T>()
10028 .or_else(|| core.group.backend_buffer::<T>())
10029 .unwrap_or_else(|| unreachable!("typed tensor group storage mismatch")),
10030 }
10031 }
10032
10033 /// Return the provider family for this tensor when backend-owned.
10034 #[doc(hidden)]
10035 pub fn backend_family(&self) -> Option<&'static str>
10036 where
10037 T: TensorScalar + 'static,
10038 {
10039 self.as_view().backend_family()
10040 }
10041
10042 /// Return the opaque backend buffer for backend-owned tensors.
10043 #[doc(hidden)]
10044 pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>>
10045 where
10046 T: 'static,
10047 {
10048 match &self.storage {
10049 DynamicStorage::Host(..) => None,
10050 DynamicStorage::Group(core) => match core.group.backend_buffer::<T>() {
10051 Some(StorageBuffer::Backend(buffer)) => Some(buffer.as_ref()),
10052 Some(StorageBuffer::Host(_)) | None => None,
10053 },
10054 }
10055 }
10056
10057 /// Return the mutable backend buffer for an exclusive owner borrow.
10058 #[doc(hidden)]
10059 pub fn backend_buffer_mut(&mut self) -> Option<&mut dyn BackendStorage<T>>
10060 where
10061 T: 'static,
10062 {
10063 let DynamicStorage::Group(core) = &mut self.storage else {
10064 return None;
10065 };
10066 match core.group.backend_buffer_mut::<T>()? {
10067 StorageBuffer::Host(_) => None,
10068 StorageBuffer::Backend(buffer) => Some(buffer.as_mut()),
10069 }
10070 }
10071
10072 /// Prepare this backend tensor for one provider-native read binding.
10073 #[doc(hidden)]
10074 pub fn prepare_device_read(
10075 &self,
10076 op: &'static str,
10077 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
10078 where
10079 T: TensorScalar + 'static,
10080 {
10081 let DynamicStorage::Group(core) = &self.storage else {
10082 return Err(crate::Error::runtime_state_source(
10083 op,
10084 crate::AccessError::Unsupported { backend: "host" },
10085 ));
10086 };
10087 let layout = self.physical_layout();
10088 core.group
10089 .prepare_device_read_for_layout::<T>(&layout)
10090 .map_err(|error| crate::Error::runtime_state_source(op, error))
10091 }
10092
10093 /// Prepare this backend tensor for one provider-native write binding.
10094 #[doc(hidden)]
10095 pub fn prepare_device_write(
10096 &mut self,
10097 op: &'static str,
10098 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
10099 where
10100 T: TensorScalar + 'static,
10101 {
10102 let layout = self.physical_layout();
10103 let DynamicStorage::Group(core) = &mut self.storage else {
10104 return Err(crate::Error::runtime_state_source(
10105 op,
10106 crate::AccessError::Unsupported { backend: "host" },
10107 ));
10108 };
10109 core.group
10110 .prepare_device_write_for_layout::<T>(&layout)
10111 .map_err(|error| crate::Error::runtime_state_source(op, error))
10112 }
10113
10114 /// Element count of the tensor's backing storage.
10115 ///
10116 /// A root-preserving owner reports its whole backing root, not just the
10117 /// logical view extent, so layout validation for views and reshapes bounds
10118 /// against the real allocation.
10119 pub(crate) fn buffer_len(&self) -> usize
10120 where
10121 T: 'static,
10122 {
10123 match &self.storage {
10124 DynamicStorage::Host(HostStorage { data }) => data.as_slice().len(),
10125 DynamicStorage::Group(core) => core
10126 .group
10127 .group
10128 .descriptor_backing_len(core.group.slot)
10129 .unwrap_or_else(|| unreachable!("typed tensor group descriptor mismatch")),
10130 }
10131 }
10132
10133 /// Return the shared-allocation domain carried by the backend buffer.
10134 ///
10135 /// # Examples
10136 ///
10137 /// ```rust
10138 /// use tenferro_tensor::TypedTensor;
10139 ///
10140 /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
10141 /// assert_eq!(tensor.allocation_domain(), None);
10142 /// # Ok::<(), tenferro_tensor::Error>(())
10143 /// ```
10144 pub fn allocation_domain(&self) -> Option<AllocationDomainId>
10145 where
10146 T: 'static,
10147 {
10148 match &self.storage {
10149 DynamicStorage::Host(..) => None,
10150 DynamicStorage::Group(core) => core
10151 .group
10152 .group
10153 .backend_identity(core.group.slot)
10154 .map(|(domain, _)| domain),
10155 }
10156 }
10157
10158 /// Return the stable physical backend allocation identity.
10159 ///
10160 /// # Examples
10161 ///
10162 /// ```rust
10163 /// use tenferro_tensor::TypedTensor;
10164 ///
10165 /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
10166 /// assert_eq!(tensor.allocation_id(), None);
10167 /// # Ok::<(), tenferro_tensor::Error>(())
10168 /// ```
10169 pub fn allocation_id(&self) -> Option<AllocationId>
10170 where
10171 T: 'static,
10172 {
10173 match &self.storage {
10174 DynamicStorage::Host(..) => None,
10175 DynamicStorage::Group(core) => core
10176 .group
10177 .group
10178 .backend_identity(core.group.slot)
10179 .map(|(_, allocation)| allocation),
10180 }
10181 }
10182
10183 /// Borrow this tensor as a typed view preserving rank and layout metadata.
10184 ///
10185 /// The view carries the descriptor's physical layout, so a root-preserving
10186 /// extraction exposes its full backing allocation with the logical slice at
10187 /// the descriptor offset.
10188 ///
10189 /// # Panics
10190 ///
10191 /// Panics only if the typed descriptor and its single group owner are
10192 /// internally inconsistent.
10193 ///
10194 /// # Examples
10195 ///
10196 /// ```rust
10197 /// use tenferro_tensor::{Rank, TypedTensor};
10198 ///
10199 /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
10200 /// let view = tensor.as_view();
10201 /// assert_eq!(view.strides(), &[1, 2]);
10202 /// ```
10203 pub fn as_view(&self) -> TypedTensorView<'_, T, R>
10204 where
10205 T: 'static,
10206 {
10207 let layout = self.physical_layout();
10208 let placement = self.placement.clone();
10209 match &self.storage {
10210 DynamicStorage::Host(HostStorage { data }) => TypedTensorView {
10211 buffer: TensorStorageRef::Host(data.as_slice()),
10212 root: None,
10213 layout,
10214 placement,
10215 _representation: std::marker::PhantomData,
10216 },
10217 DynamicStorage::Group(core) => {
10218 // Managed owners are constructed only with their matching preset T.
10219 let root = core
10220 .group
10221 .group
10222 .view_raw::<T, R>(core.group.slot)
10223 .unwrap_or_else(|error| {
10224 unreachable!("typed tensor group descriptor mismatch: {error}")
10225 });
10226 let buffer = if let Some(allocation) = root.backend_allocation() {
10227 TensorStorageRef::Root(allocation)
10228 } else {
10229 TensorStorageRef::Host(core.group.host_slice::<T>().unwrap_or_default())
10230 };
10231 TypedTensorView {
10232 buffer,
10233 root: Some(root),
10234 layout,
10235 placement,
10236 _representation: std::marker::PhantomData,
10237 }
10238 }
10239 }
10240 }
10241
10242 /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
10243 ///
10244 /// # Panics
10245 ///
10246 /// Panics only if the typed descriptor and its single group owner are
10247 /// internally inconsistent.
10248 ///
10249 /// # Examples
10250 ///
10251 /// ```rust
10252 /// use tenferro_tensor::TypedTensor;
10253 ///
10254 /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
10255 /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
10256 /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
10257 /// ```
10258 pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
10259 where
10260 T: TensorScalar + 'static,
10261 {
10262 let layout = self.physical_layout();
10263 let placement = self.placement.clone();
10264 match &mut self.storage {
10265 DynamicStorage::Host(HostStorage { data }) => TypedTensorViewMut {
10266 buffer: TensorStorageRefMut::Host(data.as_mut_slice()),
10267 root: None,
10268 layout,
10269 placement,
10270 _representation: std::marker::PhantomData,
10271 },
10272 DynamicStorage::Group(core) => {
10273 let mut root = core.group.view_mut::<T>().unwrap_or_else(|error| {
10274 unreachable!("typed tensor group descriptor mismatch: {error}")
10275 });
10276 let buffer = if let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut()
10277 {
10278 TensorStorageRefMut::Backend(buffer.as_mut())
10279 } else {
10280 TensorStorageRefMut::Host(root.host_slice_mut().unwrap_or_else(|error| {
10281 unreachable!("typed tensor group descriptor is not host-backed: {error}")
10282 }))
10283 };
10284 TypedTensorViewMut {
10285 buffer,
10286 root: Some(root),
10287 layout,
10288 placement,
10289 _representation: std::marker::PhantomData,
10290 }
10291 }
10292 }
10293 }
10294
10295 /// Borrow a read-only strided region view over this tensor's backend
10296 /// (device) buffer from explicit layout metadata.
10297 ///
10298 /// This is a metadata-only view: no data is copied or transferred. The
10299 /// layout's reachable element span is validated against the backend
10300 /// buffer's physical length. Host-backed tensors are rejected with an
10301 /// explicit backend error; host regions are expressed with
10302 /// [`TypedTensorView::from_slice`] over host storage instead.
10303 ///
10304 /// # Examples
10305 ///
10306 /// ```rust
10307 /// use tenferro_tensor::TypedTensor;
10308 ///
10309 /// // Host tensors are rejected: this constructor is for backend buffers.
10310 /// let host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
10311 /// let err = host.backend_region_view(vec![2, 2], vec![1, 2], 0).unwrap_err();
10312 /// assert!(err.to_string().contains("backend"));
10313 /// ```
10314 /// # Errors
10315 ///
10316 /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
10317 /// backend region views require a backend buffer. It returns
10318 /// [`crate::Error::Validation`] with
10319 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
10320 /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
10321 /// when the region exceeds the backend buffer, or
10322 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
10323 /// arithmetic overflow.
10324 pub fn backend_region_view(
10325 &self,
10326 shape: Vec<usize>,
10327 strides: Vec<isize>,
10328 offset: isize,
10329 ) -> crate::Result<TypedTensorView<'_, T, DynRank>>
10330 where
10331 T: TensorScalar + 'static,
10332 {
10333 let op = "TypedTensor::backend_region_view";
10334 let DynamicStorage::Group(core) = &self.storage else {
10335 return Err(crate::Error::runtime_state(op, "expected a backend (device) allocation; host tensors use TypedTensorView::from_slice over host storage"));
10336 };
10337 let root = core.group.view_dyn::<T>()?;
10338 let Some(allocation) = root.backend_allocation() else {
10339 return Err(crate::Error::runtime_state(
10340 op,
10341 "expected a backend (device) allocation; host tensors use \
10342 TypedTensorView::from_slice over host storage",
10343 ));
10344 };
10345 let element_len = allocation
10346 .root_extent()
10347 .byte_len()
10348 .checked_div(size_of::<T>())
10349 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
10350 let layout =
10351 TensorLayout::from_parts(shape_vec(&shape), stride_vec(&strides), offset, element_len)
10352 .map_err(|err| tensor_layout_error(op, err))?;
10353 Ok(TypedTensorView {
10354 buffer: TensorStorageRef::Root(allocation),
10355 root: Some(root),
10356 layout,
10357 placement: self.placement.clone(),
10358 _representation: std::marker::PhantomData,
10359 })
10360 }
10361
10362 /// Borrow a mutable strided region view over this tensor's backend
10363 /// (device) buffer from explicit layout metadata.
10364 ///
10365 /// This is the mutable counterpart of
10366 /// [`TypedTensor::backend_region_view`]. The layout's reachable element
10367 /// span is validated against the backend buffer's physical length, and
10368 /// layouts whose logical elements alias the same physical element are
10369 /// rejected. Host-backed tensors are rejected with an explicit backend
10370 /// error; mutable host regions must go through
10371 /// [`TypedTensorViewMut::try_multi_slice_mut`] or host constructors.
10372 ///
10373 /// The returned view borrows the tensor's backend owner exclusively for its
10374 /// lifetime. This keeps write authority tied to the owner; a second mutable
10375 /// region view must be created only after the first borrow ends.
10376 ///
10377 /// # Examples
10378 ///
10379 /// ```rust
10380 /// use tenferro_tensor::TypedTensor;
10381 ///
10382 /// // Host tensors are rejected: this constructor is for backend buffers.
10383 /// let mut host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
10384 /// let err = host.backend_region_view_mut(vec![2, 2], vec![1, 2], 0).unwrap_err();
10385 /// assert!(err.to_string().contains("backend"));
10386 /// ```
10387 /// # Errors
10388 ///
10389 /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
10390 /// mutable backend region views require a backend buffer. It returns
10391 /// [`crate::Error::Validation`] with
10392 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
10393 /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
10394 /// when the region exceeds the backend buffer,
10395 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
10396 /// logical elements alias, or
10397 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
10398 /// arithmetic overflow.
10399 pub fn backend_region_view_mut(
10400 &mut self,
10401 shape: Vec<usize>,
10402 strides: Vec<isize>,
10403 offset: isize,
10404 ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>>
10405 where
10406 T: TensorScalar + 'static,
10407 {
10408 let op = "TypedTensor::backend_region_view_mut";
10409 let placement = self.placement.clone();
10410 let DynamicStorage::Group(core) = &mut self.storage else {
10411 return Err(crate::Error::runtime_state(op, "expected a backend (device) buffer; mutable host regions use TypedTensorViewMut host constructors or try_multi_slice_mut"));
10412 };
10413 let mut root = core.group.view_mut_dyn::<T>()?;
10414 let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut() else {
10415 return Err(crate::Error::runtime_state(
10416 op,
10417 "expected a backend (device) buffer; mutable host regions use \
10418 TypedTensorViewMut host constructors or try_multi_slice_mut",
10419 ));
10420 };
10421 let layout = TensorLayout::from_parts(
10422 shape_vec(&shape),
10423 stride_vec(&strides),
10424 offset,
10425 buffer.len(),
10426 )
10427 .map_err(|err| tensor_layout_error(op, err))?;
10428 layout
10429 .validate_mutable_no_overlap()
10430 .map_err(|err| tensor_layout_error(op, err))?;
10431 Ok(TypedTensorViewMut {
10432 buffer: TensorStorageRefMut::Backend(buffer.as_mut()),
10433 root: Some(root),
10434 layout,
10435 placement,
10436 _representation: std::marker::PhantomData,
10437 })
10438 }
10439
10440 /// Consume this tensor and return its logical host storage, layout, and
10441 /// placement.
10442 ///
10443 /// A root-preserving owner exports only its compact logical values, exactly
10444 /// like [`TypedTensor::into_host_vec`].
10445 ///
10446 /// # Examples
10447 ///
10448 /// ```
10449 /// use tenferro_tensor::{StorageBuffer, TypedTensor};
10450 ///
10451 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
10452 /// let Ok((buffer, layout, placement)) = t.into_parts() else {
10453 /// panic!("a plain host owner extracts")
10454 /// };
10455 /// assert!(matches!(buffer, StorageBuffer::Host(_)));
10456 /// assert_eq!(layout.shape(), &[2]);
10457 /// assert!(placement.device.is_none());
10458 /// # Ok::<(), tenferro_tensor::Error>(())
10459 /// ```
10460 ///
10461 /// # Errors
10462 ///
10463 /// Returns [`ReinterpretError`] carrying the unchanged tensor when it uses
10464 /// backend storage (download it before extracting host storage) or when a
10465 /// group-backed descriptor is not compact column-major
10466 /// ([`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]).
10467 pub fn into_parts(
10468 self,
10469 ) -> std::result::Result<(StorageBuffer<T>, TensorLayout<R>, Placement), ReinterpretError<Self>>
10470 where
10471 T: TensorScalar,
10472 {
10473 let placement = self.placement().clone();
10474 let layout = TensorLayout::<R>::compact(
10475 R::shape_from_vec(shape_vec(self.shape()))
10476 .unwrap_or_else(|err| unreachable!("validated owned shape: {err:?}")),
10477 )
10478 .unwrap_or_else(|err| unreachable!("validated owned shape: {err}"));
10479 // The export contract is identical to `into_host_vec`; reuse it rather
10480 // than repeating the descriptor rebase and its failure recovery.
10481 match self.into_host_vec() {
10482 Ok(data) => Ok((StorageBuffer::Host(data), layout, placement)),
10483 Err(error) => Err(error),
10484 }
10485 }
10486}
10487
10488impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R> {
10489 fn into_managed(self) -> Self {
10490 let TypedTensor {
10491 shape,
10492 placement,
10493 layout,
10494 storage,
10495 } = self;
10496 match storage {
10497 DynamicStorage::Host(HostStorage { data }) => {
10498 let group = promote_host_group::<T, R>(shape.clone(), data)
10499 .unwrap_or_else(|err| unreachable!("a validated host owner promotes: {err}"));
10500 Self {
10501 shape,
10502 placement,
10503 layout,
10504 storage: DynamicStorage::Group(GroupStorage {
10505 group: Box::new(group),
10506 }),
10507 }
10508 }
10509 DynamicStorage::Group(core) => Self {
10510 shape,
10511 placement,
10512 layout,
10513 storage: DynamicStorage::Group(core),
10514 },
10515 }
10516 }
10517
10518 fn into_group_parts(self) -> (AllocationGroup, DescriptorSlot) {
10519 let TypedTensor {
10520 placement, storage, ..
10521 } = self.into_managed();
10522 let DynamicStorage::Group(GroupStorage { group }) = storage else {
10523 unreachable!("explicit group promotion produces managed storage")
10524 };
10525 let (mut group, slot) = group.into_parts();
10526 group.publish_live_descriptor_placement(slot, placement);
10527 (group, slot)
10528 }
10529
10530 /// Construct a backend-pooled host tensor with weak final-owner reclamation.
10531 /// Explicit Vec extraction disarms reclamation; aliases and retained groups
10532 /// keep the original root alive. The recycler must not acquire session locks.
10533 ///
10534 /// # Errors
10535 /// Returns validation errors for invalid shape/length and runtime-state errors
10536 /// if the freshly created host root cannot attach its recycler.
10537 #[doc(hidden)]
10538 pub fn from_vec_col_major_with_recycler(
10539 shape: impl tenferro_tensor_core::IntoRankShape<R>,
10540 data: Vec<T>,
10541 recycler: std::sync::Weak<dyn crate::HostBufferRecycler<T>>,
10542 ) -> crate::Result<Self> {
10543 let mut tensor = Self::from_vec_col_major(shape, data)?;
10544 if let DynamicStorage::Host(HostStorage { data }) = &mut tensor.storage {
10545 data.recycler = Some(recycler);
10546 }
10547 Ok(tensor)
10548 }
10549
10550 /// Borrow compact host-visible storage through one synchronization guard.
10551 #[doc(hidden)]
10552 pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
10553 where
10554 T: TensorScalar + 'static,
10555 {
10556 self.as_view().with_host_read(f)
10557 }
10558
10559 /// Borrow compact host-visible storage through one exclusive write guard.
10560 #[doc(hidden)]
10561 pub fn with_host_write<U>(&mut self, f: impl FnOnce(&mut [T]) -> U) -> crate::Result<U>
10562 where
10563 T: TensorScalar + 'static,
10564 {
10565 match &mut self.storage {
10566 DynamicStorage::Host(HostStorage { data }) => Ok(f(data.as_mut_slice())),
10567 DynamicStorage::Group(core) => {
10568 let slot = core.group.slot;
10569 let mut view = core
10570 .group
10571 .group
10572 .view_mut::<T, R>(slot)
10573 .map_err(|error| group_error("TypedTensor::with_host_write", error))?;
10574 let mut prepared = view.prepare_host_write().map_err(|error| {
10575 crate::Error::runtime_state("TypedTensor::with_host_write", error.to_string())
10576 })?;
10577 let slice = prepared.as_slice_mut().ok_or_else(|| {
10578 crate::Error::unsupported(
10579 "TypedTensor::with_host_write",
10580 "host guard access requires a compact descriptor",
10581 )
10582 })?;
10583 Ok(f(slice))
10584 }
10585 }
10586 }
10587
10588 /// Whole backing host buffer, including bytes outside a root-preserving
10589 /// extraction's logical extent (#2023).
10590 ///
10591 /// A mutable reinterpretation rebases the descriptor over the whole root,
10592 /// so it must borrow that root rather than the exported logical slice.
10593 fn backing_host_slice_mut(&mut self) -> crate::Result<&mut [T]> {
10594 match &mut self.storage {
10595 DynamicStorage::Host(HostStorage { data }) => Ok(data.as_mut_slice()),
10596 DynamicStorage::Group(core) => core.group.host_slice_mut::<T>(),
10597 }
10598 }
10599
10600 fn group_host_slice(&self) -> &[T] {
10601 let DynamicStorage::Group(core) = &self.storage else {
10602 return &[];
10603 };
10604 core.group
10605 .view::<T>()
10606 .ok()
10607 .and_then(|view| view.host_slice().ok())
10608 .unwrap_or_default()
10609 }
10610}
10611
10612/// Layout builder used by an owning representation reinterpretation.
10613type ReinterpretLayoutFn =
10614 fn(&[usize], &[isize], isize, usize, &'static str) -> crate::Result<TensorLayout<DynRank>>;
10615
10616fn reinterpret_owned<S: TensorScalar, U: TensorScalar, R: TensorRank>(
10617 owner: TypedTensor<S, R>,
10618 from: DType,
10619 to: DType,
10620 make_layout: ReinterpretLayoutFn,
10621 op: &'static str,
10622) -> Result<TypedTensor<U>, ReinterpretError<TypedTensor<S, R>>> {
10623 if let Err(error) = validate_representation_pair(op, from, to) {
10624 return Err(ReinterpretError::new(owner, error));
10625 }
10626 if matches!(to, DType::C32 | DType::C64) && !owner.buffer_len().is_multiple_of(2) {
10627 return Err(ReinterpretError::new(
10628 owner,
10629 crate::Error::invalid_argument(
10630 op,
10631 "buffer",
10632 "the owned real buffer must contain an even number of elements",
10633 ),
10634 ));
10635 }
10636 let source_layout = owner.physical_layout();
10637 let target_layout = match make_layout(
10638 source_layout.shape(),
10639 source_layout.strides(),
10640 source_layout.offset(),
10641 owner.buffer_len(),
10642 op,
10643 ) {
10644 Ok(layout) => layout,
10645 Err(error) => return Err(ReinterpretError::new(owner, error)),
10646 };
10647 // Only an explicit owning representation conversion promotes a plain owner;
10648 // borrowed session dispatch never passes through a group.
10649 let TypedTensor {
10650 shape,
10651 placement,
10652 layout,
10653 storage,
10654 } = owner.into_managed();
10655 let DynamicStorage::Group(GroupStorage { group }) = storage else {
10656 unreachable!("explicit group promotion produces managed storage")
10657 };
10658 match group.reinterpret::<S, U>(
10659 target_layout.shape().to_vec(),
10660 target_layout.strides().to_vec(),
10661 target_layout.offset(),
10662 ) {
10663 Ok(group) => Ok(TypedTensor {
10664 shape: shape_vec(target_layout.shape()),
10665 placement,
10666 layout: stride_vec(target_layout.strides()),
10667 storage: DynamicStorage::Group(GroupStorage {
10668 group: Box::new(group),
10669 }),
10670 }),
10671 Err((group, error)) => Err(ReinterpretError::new(
10672 TypedTensor {
10673 shape,
10674 placement,
10675 layout,
10676 storage: DynamicStorage::Group(GroupStorage {
10677 group: Box::new(group),
10678 }),
10679 },
10680 error,
10681 )),
10682 }
10683}
10684
10685impl<R: TensorRank> TypedTensor<Complex32, R> {
10686 /// Borrow this tensor as an interleaved `f32` view without copying.
10687 ///
10688 /// # Errors
10689 ///
10690 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10691 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10692 /// invalid tensor layout.
10693 pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f32, DynRank>> {
10694 self.as_view().as_real_view()
10695 }
10696
10697 /// Borrow this tensor mutably as an interleaved `f32` view without copying.
10698 ///
10699 /// # Errors
10700 ///
10701 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10702 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10703 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10704 /// invalid representation metadata.
10705 pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
10706 let op = "TypedTensor::as_real_view_mut";
10707 validate_representation_pair(op, DType::C32, DType::F32)?;
10708 let source_layout = self.physical_layout();
10709 let layout = reinterpret_complex_to_real_layout(
10710 self.shape(),
10711 source_layout.strides(),
10712 source_layout.offset(),
10713 self.buffer_len(),
10714 op,
10715 )?;
10716 layout
10717 .validate_mutable_no_overlap()
10718 .map_err(|err| tensor_layout_error(op, err))?;
10719 if self.backend_buffer().is_some() {
10720 return Err(crate::Error::unsupported(
10721 op,
10722 "backend representation reinterpretation is enabled by the provider phases",
10723 ));
10724 }
10725 let placement = self.placement().clone();
10726 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(
10727 self.backing_host_slice_mut()?,
10728 op,
10729 )?);
10730 Ok(TypedTensorViewMut {
10731 buffer,
10732 root: None,
10733 layout,
10734 placement,
10735 _representation: std::marker::PhantomData,
10736 })
10737 }
10738
10739 /// Consume this tensor and reinterpret its owner as `f32` without copying.
10740 ///
10741 /// A failed operation returns the unchanged owner through
10742 /// [`ReinterpretError::into_owner`].
10743 ///
10744 /// # Errors
10745 ///
10746 /// Returns [`ReinterpretError::error`] containing
10747 /// [`ValidationError::InvalidArgument`] or
10748 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10749 /// owner.
10750 pub fn into_real(self) -> Result<TypedTensor<f32, DynRank>, ReinterpretError<Self>> {
10751 reinterpret_owned(
10752 self,
10753 DType::C32,
10754 DType::F32,
10755 reinterpret_complex_to_real_layout,
10756 "TypedTensor::into_real",
10757 )
10758 }
10759}
10760
10761impl<R: TensorRank> TypedTensor<Complex64, R> {
10762 /// Borrow this tensor as an interleaved `f64` view without copying.
10763 ///
10764 /// # Errors
10765 ///
10766 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10767 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10768 /// invalid tensor layout.
10769 pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f64, DynRank>> {
10770 self.as_view().as_real_view()
10771 }
10772
10773 /// Borrow this tensor mutably as an interleaved `f64` view without copying.
10774 ///
10775 /// # Errors
10776 ///
10777 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10778 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10779 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10780 /// invalid representation metadata.
10781 pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
10782 let op = "TypedTensor::as_real_view_mut";
10783 validate_representation_pair(op, DType::C64, DType::F64)?;
10784 let source_layout = self.physical_layout();
10785 let layout = reinterpret_complex_to_real_layout(
10786 self.shape(),
10787 source_layout.strides(),
10788 source_layout.offset(),
10789 self.buffer_len(),
10790 op,
10791 )?;
10792 layout
10793 .validate_mutable_no_overlap()
10794 .map_err(|err| tensor_layout_error(op, err))?;
10795 if self.backend_buffer().is_some() {
10796 return Err(crate::Error::unsupported(
10797 op,
10798 "backend representation reinterpretation is enabled by the provider phases",
10799 ));
10800 }
10801 let placement = self.placement().clone();
10802 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(
10803 self.backing_host_slice_mut()?,
10804 op,
10805 )?);
10806 Ok(TypedTensorViewMut {
10807 buffer,
10808 root: None,
10809 layout,
10810 placement,
10811 _representation: std::marker::PhantomData,
10812 })
10813 }
10814
10815 /// Consume this tensor and reinterpret its owner as `f64` without copying.
10816 ///
10817 /// A failed operation returns the unchanged owner through
10818 /// [`ReinterpretError::into_owner`].
10819 ///
10820 /// # Errors
10821 ///
10822 /// Returns [`ReinterpretError::error`] containing
10823 /// [`ValidationError::InvalidArgument`] or
10824 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10825 /// owner.
10826 pub fn into_real(self) -> Result<TypedTensor<f64, DynRank>, ReinterpretError<Self>> {
10827 reinterpret_owned(
10828 self,
10829 DType::C64,
10830 DType::F64,
10831 reinterpret_complex_to_real_layout,
10832 "TypedTensor::into_real",
10833 )
10834 }
10835}
10836
10837impl<R: TensorRank> TypedTensor<f32, R> {
10838 /// Borrow this tensor as a complex view without copying.
10839 ///
10840 /// # Errors
10841 ///
10842 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10843 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10844 /// invalid tensor layout.
10845 pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex32, DynRank>> {
10846 self.as_view().as_complex_view()
10847 }
10848
10849 /// Borrow this tensor mutably as a complex view without copying.
10850 ///
10851 /// # Errors
10852 ///
10853 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10854 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10855 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10856 /// invalid representation metadata.
10857 pub fn as_complex_view_mut(
10858 &mut self,
10859 ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
10860 let op = "TypedTensor::as_complex_view_mut";
10861 validate_representation_pair(op, DType::F32, DType::C32)?;
10862 let source_layout = self.physical_layout();
10863 let layout = reinterpret_real_to_complex_layout(
10864 self.shape(),
10865 source_layout.strides(),
10866 source_layout.offset(),
10867 self.buffer_len(),
10868 op,
10869 )?;
10870 layout
10871 .validate_mutable_no_overlap()
10872 .map_err(|err| tensor_layout_error(op, err))?;
10873 if self.backend_buffer().is_some() {
10874 return Err(crate::Error::unsupported(
10875 op,
10876 "backend representation reinterpretation is enabled by the provider phases",
10877 ));
10878 }
10879 let placement = self.placement().clone();
10880 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(
10881 self.backing_host_slice_mut()?,
10882 op,
10883 )?);
10884 Ok(TypedTensorViewMut {
10885 buffer,
10886 root: None,
10887 layout,
10888 placement,
10889 _representation: std::marker::PhantomData,
10890 })
10891 }
10892
10893 /// Consume this tensor and reinterpret its owner as `Complex32` without copying.
10894 ///
10895 /// The compact source must have an even physical element count. A failed
10896 /// operation returns the unchanged owner through
10897 /// [`ReinterpretError::into_owner`].
10898 ///
10899 /// # Errors
10900 ///
10901 /// Returns [`ReinterpretError::error`] containing
10902 /// [`ValidationError::InvalidArgument`] or
10903 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10904 /// owner.
10905 pub fn into_complex(self) -> Result<TypedTensor<Complex32, DynRank>, ReinterpretError<Self>> {
10906 reinterpret_owned(
10907 self,
10908 DType::F32,
10909 DType::C32,
10910 reinterpret_real_to_complex_layout,
10911 "TypedTensor::into_complex",
10912 )
10913 }
10914}
10915
10916impl<R: TensorRank> TypedTensor<f64, R> {
10917 /// Borrow this tensor as a complex view without copying.
10918 ///
10919 /// # Errors
10920 ///
10921 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10922 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10923 /// invalid tensor layout.
10924 pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex64, DynRank>> {
10925 self.as_view().as_complex_view()
10926 }
10927
10928 /// Borrow this tensor mutably as a complex view without copying.
10929 ///
10930 /// # Errors
10931 ///
10932 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10933 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10934 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10935 /// invalid representation metadata.
10936 pub fn as_complex_view_mut(
10937 &mut self,
10938 ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
10939 let op = "TypedTensor::as_complex_view_mut";
10940 validate_representation_pair(op, DType::F64, DType::C64)?;
10941 let source_layout = self.physical_layout();
10942 let layout = reinterpret_real_to_complex_layout(
10943 self.shape(),
10944 source_layout.strides(),
10945 source_layout.offset(),
10946 self.buffer_len(),
10947 op,
10948 )?;
10949 layout
10950 .validate_mutable_no_overlap()
10951 .map_err(|err| tensor_layout_error(op, err))?;
10952 if self.backend_buffer().is_some() {
10953 return Err(crate::Error::unsupported(
10954 op,
10955 "backend representation reinterpretation is enabled by the provider phases",
10956 ));
10957 }
10958 let placement = self.placement().clone();
10959 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(
10960 self.backing_host_slice_mut()?,
10961 op,
10962 )?);
10963 Ok(TypedTensorViewMut {
10964 buffer,
10965 root: None,
10966 layout,
10967 placement,
10968 _representation: std::marker::PhantomData,
10969 })
10970 }
10971
10972 /// Consume this tensor and reinterpret its owner as `Complex64` without copying.
10973 ///
10974 /// The compact source must have an even physical element count. A failed
10975 /// operation returns the unchanged owner through
10976 /// [`ReinterpretError::into_owner`].
10977 ///
10978 /// # Errors
10979 ///
10980 /// Returns [`ReinterpretError::error`] containing
10981 /// [`ValidationError::InvalidArgument`] or
10982 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10983 /// owner.
10984 pub fn into_complex(self) -> Result<TypedTensor<Complex64, DynRank>, ReinterpretError<Self>> {
10985 reinterpret_owned(
10986 self,
10987 DType::F64,
10988 DType::C64,
10989 reinterpret_real_to_complex_layout,
10990 "TypedTensor::into_complex",
10991 )
10992 }
10993}
10994
10995impl Tensor {
10996 /// Borrow a complex tensor as its sealed interleaved real representation.
10997 ///
10998 /// # Errors
10999 ///
11000 /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
11001 /// [`ValidationError::ViewOutOfBounds`] or
11002 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
11003 pub fn as_real_view(&self) -> crate::Result<TensorView<'_>> {
11004 match self.dtype() {
11005 DType::C32 => self
11006 .as_typed::<Complex<f32>>()
11007 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11008 .as_real_view()
11009 .map(TensorView::F32),
11010 DType::C64 => self
11011 .as_typed::<Complex<f64>>()
11012 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11013 .as_real_view()
11014 .map(TensorView::F64),
11015 other => Err(crate::Error::unsupported_dtype_conversion(
11016 "Tensor::as_real_view",
11017 other,
11018 DType::F32,
11019 "only complex tensors have a sealed real representation view",
11020 )),
11021 }
11022 }
11023
11024 /// Borrow a complex tensor mutably as its sealed interleaved real representation.
11025 ///
11026 /// # Errors
11027 ///
11028 /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
11029 /// [`ValidationError::ViewOutOfBounds`] or
11030 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
11031 pub fn as_real_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
11032 match self.dtype() {
11033 DType::C32 => self
11034 .as_typed_mut::<Complex<f32>>()
11035 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11036 .as_real_view_mut()
11037 .map(TensorViewMut::F32),
11038 DType::C64 => self
11039 .as_typed_mut::<Complex<f64>>()
11040 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11041 .as_real_view_mut()
11042 .map(TensorViewMut::F64),
11043 other => Err(crate::Error::unsupported_dtype_conversion(
11044 "Tensor::as_real_view_mut",
11045 other,
11046 DType::F32,
11047 "only complex tensors have a sealed real representation view",
11048 )),
11049 }
11050 }
11051
11052 /// Consume a complex tensor and reinterpret its owner as real without copying.
11053 ///
11054 /// # Errors
11055 ///
11056 /// Returns [`ReinterpretError::error`] containing
11057 /// [`ValidationError::InvalidArgument`] or
11058 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
11059 /// owner.
11060 pub fn into_real(self) -> Result<Self, ReinterpretError<Self>> {
11061 let dtype = self.dtype();
11062 match dtype {
11063 DType::C32 => {
11064 let tensor = <Complex<f32> as TensorScalar>::into_typed(self)
11065 .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
11066 tensor
11067 .into_real()
11068 .map(Tensor::from_typed::<f32>)
11069 .map_err(|error| {
11070 let (owner, error) = error.into_parts();
11071 ReinterpretError::new(Tensor::from_typed::<Complex<f32>>(owner), error)
11072 })
11073 }
11074 DType::C64 => {
11075 let tensor = <Complex<f64> as TensorScalar>::into_typed(self)
11076 .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
11077 tensor
11078 .into_real()
11079 .map(Tensor::from_typed::<f64>)
11080 .map_err(|error| {
11081 let (owner, error) = error.into_parts();
11082 ReinterpretError::new(Tensor::from_typed::<Complex<f64>>(owner), error)
11083 })
11084 }
11085 _ => Err(ReinterpretError::new(
11086 self,
11087 crate::Error::unsupported(
11088 "Tensor::into_real",
11089 "only complex tensors have a sealed real representation",
11090 ),
11091 )),
11092 }
11093 }
11094
11095 /// Borrow an interleaved real tensor as its sealed complex representation.
11096 ///
11097 /// # Errors
11098 ///
11099 /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
11100 /// [`ValidationError::ViewOutOfBounds`] or
11101 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
11102 pub fn as_complex_view(&self) -> crate::Result<TensorView<'_>> {
11103 match self.dtype() {
11104 DType::F32 => self
11105 .as_typed::<f32>()
11106 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11107 .as_complex_view()
11108 .map(TensorView::C32),
11109 DType::F64 => self
11110 .as_typed::<f64>()
11111 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11112 .as_complex_view()
11113 .map(TensorView::C64),
11114 other => Err(crate::Error::unsupported_dtype_conversion(
11115 "Tensor::as_complex_view",
11116 other,
11117 DType::C32,
11118 "only real tensors can have a sealed complex representation view",
11119 )),
11120 }
11121 }
11122
11123 /// Borrow an interleaved real tensor mutably as its sealed complex representation.
11124 ///
11125 /// # Errors
11126 ///
11127 /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
11128 /// [`ValidationError::ViewOutOfBounds`] or
11129 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
11130 pub fn as_complex_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
11131 match self.dtype() {
11132 DType::F32 => self
11133 .as_typed_mut::<f32>()
11134 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11135 .as_complex_view_mut()
11136 .map(TensorViewMut::C32),
11137 DType::F64 => self
11138 .as_typed_mut::<f64>()
11139 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
11140 .as_complex_view_mut()
11141 .map(TensorViewMut::C64),
11142 other => Err(crate::Error::unsupported_dtype_conversion(
11143 "Tensor::as_complex_view_mut",
11144 other,
11145 DType::C32,
11146 "only real tensors can have a sealed complex representation view",
11147 )),
11148 }
11149 }
11150
11151 /// Consume a real tensor and reinterpret its owner as complex without copying.
11152 ///
11153 /// # Errors
11154 ///
11155 /// Returns [`ReinterpretError::error`] containing
11156 /// [`ValidationError::InvalidArgument`] or
11157 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
11158 /// owner.
11159 pub fn into_complex(self) -> Result<Self, ReinterpretError<Self>> {
11160 let dtype = self.dtype();
11161 match dtype {
11162 DType::F32 => {
11163 let tensor = <f32 as TensorScalar>::into_typed(self)
11164 .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
11165 tensor
11166 .into_complex()
11167 .map(Tensor::from_typed::<Complex<f32>>)
11168 .map_err(|error| {
11169 let (owner, error) = error.into_parts();
11170 ReinterpretError::new(Tensor::from_typed::<f32>(owner), error)
11171 })
11172 }
11173 DType::F64 => {
11174 let tensor = <f64 as TensorScalar>::into_typed(self)
11175 .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
11176 tensor
11177 .into_complex()
11178 .map(Tensor::from_typed::<Complex<f64>>)
11179 .map_err(|error| {
11180 let (owner, error) = error.into_parts();
11181 ReinterpretError::new(Tensor::from_typed::<f64>(owner), error)
11182 })
11183 }
11184 _ => Err(ReinterpretError::new(
11185 self,
11186 crate::Error::unsupported(
11187 "Tensor::into_complex",
11188 "only real tensors have a sealed complex representation",
11189 ),
11190 )),
11191 }
11192 }
11193
11194 /// Make an explicit owning copy of this dtype-erased tensor.
11195 ///
11196 /// `Tensor` deliberately does not implement [`Clone`]: copying can fail
11197 /// (device-only storage is rejected) and allocates a new, independent
11198 /// owner, so it is an explicit fallible call rather than an infallible
11199 /// `clone()`. The copy shares nothing with `self`; mutating one never
11200 /// affects the other. To share one tensor between several users without
11201 /// copying, wrap it in [`std::sync::Arc`] or borrow views of it.
11202 ///
11203 /// # Examples
11204 ///
11205 /// ```
11206 /// use std::sync::Arc;
11207 /// use tenferro_tensor::Tensor;
11208 ///
11209 /// let weights = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11210 /// // Two independent owners of the same values.
11211 /// let copy = weights.duplicate()?;
11212 /// assert_eq!(copy.as_slice::<f64>()?, weights.as_slice::<f64>()?);
11213 ///
11214 /// // A shared, read-only handle instead of a copy.
11215 /// let shared = Arc::new(weights);
11216 /// let other = Arc::clone(&shared);
11217 /// assert_eq!(other.shape(), &[2]);
11218 /// # Ok::<(), tenferro_tensor::Error>(())
11219 /// ```
11220 ///
11221 /// # Errors
11222 ///
11223 /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
11224 /// when the selected backend/storage owner cannot be duplicated.
11225 pub fn duplicate(&self) -> crate::Result<Self> {
11226 match &self.payload {
11227 // A caller-owned payload is copied through its own entry point, which
11228 // keeps its element type and its view; sharing the payload instead
11229 // would alias the caller's storage.
11230 TensorPayload::External(payload, placement) => Ok(Self {
11231 payload: TensorPayload::External(payload.duplicate(), placement.clone()),
11232 }),
11233 TensorPayload::Native(_) => match self.dtype() {
11234 DType::F32 => self.duplicate_typed::<f32>(),
11235 DType::F64 => self.duplicate_typed::<f64>(),
11236 DType::I32 => self.duplicate_typed::<i32>(),
11237 DType::I64 => self.duplicate_typed::<i64>(),
11238 DType::Bool => self.duplicate_typed::<bool>(),
11239 DType::C32 => self.duplicate_typed::<Complex<f32>>(),
11240 DType::C64 => self.duplicate_typed::<Complex<f64>>(),
11241 DType::External(_) => unreachable!("a native payload carries a preset dtype"),
11242 },
11243 }
11244 }
11245
11246 /// Duplicate a preset tensor through its typed owner.
11247 fn duplicate_typed<T: TensorScalar>(&self) -> crate::Result<Self> {
11248 self.as_typed::<T>()
11249 .unwrap_or_else(|| unreachable!("the caller selected this arm from the dtype"))
11250 .duplicate()
11251 .map(Self::from_typed)
11252 }
11253
11254 /// Create a tensor from a shape and column-major flat data.
11255 ///
11256 /// This is the `Tensor`-level equivalent of
11257 /// `TypedTensor::<T>::from_vec_col_major`.
11258 ///
11259 /// # Examples
11260 ///
11261 /// ```
11262 /// use tenferro_tensor::Tensor;
11263 ///
11264 /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
11265 /// assert_eq!(t.shape(), &[2, 2]);
11266 /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
11267 /// ```
11268 /// # Errors
11269 ///
11270 /// Returns [`crate::Error::Validation`] with
11271 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
11272 /// the shape product differs from `data.len()`, or
11273 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
11274 /// arithmetic overflows.
11275 pub fn from_vec_col_major<T: TensorScalar>(
11276 shape: impl tenferro_tensor_core::IntoShapeVec,
11277 data: Vec<T>,
11278 ) -> crate::Result<Self> {
11279 // Build the typed owner once and wrap it: routing the shape through
11280 // `T::into_tensor(Vec<usize>, _)` costs an exact-size `Vec` per construction
11281 // that the destination cannot keep, because the erased payload stores the
11282 // inline shape the typed constructor already produces (#2177).
11283 Ok(Self::from_typed(TypedTensor::<T>::from_vec_col_major(
11284 shape, data,
11285 )?))
11286 }
11287
11288 /// Create a tensor from a shape and row-major (C-order) flat data.
11289 ///
11290 /// The values are reordered once into tenferro's column-major storage;
11291 /// no row-major owner is created. Use this for buffers authored in
11292 /// PyTorch/NumPy/C order instead of passing them to
11293 /// [`Self::from_vec_col_major`], which would reinterpret them silently.
11294 /// This is the `Tensor`-level equivalent of
11295 /// `TypedTensor::<T>::from_vec_row_major`.
11296 ///
11297 /// # Examples
11298 ///
11299 /// ```
11300 /// use tenferro_tensor::Tensor;
11301 ///
11302 /// // Row-major [[1, 2, 3], [4, 5, 6]].
11303 /// let t = Tensor::from_vec_row_major(vec![2, 3], vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0])?;
11304 /// assert_eq!(t.shape(), &[2, 3]);
11305 /// assert_eq!(t.as_slice::<f64>()?, &[1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
11306 /// # Ok::<(), tenferro_tensor::Error>(())
11307 /// ```
11308 /// # Errors
11309 ///
11310 /// Returns [`crate::Error::Validation`] with
11311 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
11312 /// the shape product differs from `data.len()`, or
11313 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
11314 /// arithmetic overflows.
11315 pub fn from_vec_row_major<T: TensorScalar>(
11316 shape: impl tenferro_tensor_core::IntoShapeVec,
11317 data: Vec<T>,
11318 ) -> crate::Result<Self> {
11319 // The reorder keeps its own `op` name, and the shape comes back as the inline
11320 // vector the typed constructor takes, so neither `.to_vec()` in the erased
11321 // route is needed (#2177).
11322 let (shape, reordered) = row_major_reorder::<T, tenferro_tensor_core::DynRank>(
11323 shape,
11324 data,
11325 "Tensor::from_vec_row_major",
11326 )?;
11327 Ok(Self::from_typed(TypedTensor::<T>::from_vec_col_major(
11328 shape, reordered,
11329 )?))
11330 }
11331
11332 /// Tensor shape.
11333 ///
11334 /// # Examples
11335 ///
11336 /// ```rust
11337 /// use tenferro_tensor::{Tensor, TypedTensor};
11338 ///
11339 /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
11340 /// assert_eq!(t.shape(), &[2]);
11341 /// ```
11342 pub fn shape(&self) -> &[usize] {
11343 match &self.payload {
11344 TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.shape()),
11345 // The payload keeps its shape from construction.
11346 TensorPayload::External(payload, _) => payload.shape(),
11347 }
11348 }
11349
11350 /// Tensor dtype tag.
11351 ///
11352 /// # Examples
11353 ///
11354 /// ```rust
11355 /// use tenferro_tensor::{DType, Tensor, TypedTensor};
11356 ///
11357 /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
11358 /// assert_eq!(t.dtype(), DType::F64);
11359 /// ```
11360 pub fn dtype(&self) -> DType {
11361 match &self.payload {
11362 TensorPayload::Native(preset) => match preset {
11363 PresetTensor::F32(_) => DType::F32,
11364 PresetTensor::F64(_) => DType::F64,
11365 PresetTensor::I32(_) => DType::I32,
11366 PresetTensor::I64(_) => DType::I64,
11367 PresetTensor::Bool(_) => DType::Bool,
11368 PresetTensor::C32(_) => DType::C32,
11369 PresetTensor::C64(_) => DType::C64,
11370 },
11371 // The payload carries its own element identity.
11372 TensorPayload::External(payload, _) => DType::External(payload.element_type_id()),
11373 }
11374 }
11375
11376 /// Return placement metadata for this dtype-erased tensor.
11377 ///
11378 /// # Examples
11379 ///
11380 /// ```rust
11381 /// use tenferro_tensor::{MemoryKind, Tensor};
11382 ///
11383 /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
11384 /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
11385 /// ```
11386 pub fn placement(&self) -> &Placement {
11387 match &self.payload {
11388 TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.placement()),
11389 // The placement is stored with the payload.
11390 TensorPayload::External(_, placement) => placement,
11391 }
11392 }
11393
11394 /// Return whether this tensor is backed by backend-native storage.
11395 ///
11396 /// # Examples
11397 ///
11398 /// ```rust
11399 /// use tenferro_tensor::Tensor;
11400 ///
11401 /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
11402 /// assert!(!t.is_backend_buffer());
11403 /// ```
11404 pub fn is_backend_buffer(&self) -> bool {
11405 match &self.payload {
11406 TensorPayload::Native(preset) => with_preset!(preset, |typed| {
11407 matches!(&typed.storage, DynamicStorage::Group(core) if core.group.is_backend_buffer())
11408 }),
11409 // A caller-owned payload is host memory, never a backend buffer.
11410 TensorPayload::External(..) => false,
11411 }
11412 }
11413
11414 /// Compute the physical element offset for a logical index.
11415 ///
11416 /// # Examples
11417 ///
11418 /// ```rust
11419 /// use tenferro_tensor::Tensor;
11420 ///
11421 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11422 /// assert_eq!(t.layout_linear_offset(&[1])?, 1);
11423 /// # Ok::<(), tenferro_tensor::Error>(())
11424 /// ```
11425 /// # Errors
11426 ///
11427 /// Returns [`crate::Error::Validation`] with
11428 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
11429 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
11430 /// when an index is outside its axis extent, or
11431 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
11432 /// arithmetic overflows.
11433 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
11434 match &self.payload {
11435 TensorPayload::Native(_) => {
11436 let layout = tensor_layout(self);
11437 checked_view_offset_result(
11438 layout.shape(),
11439 layout.strides(),
11440 layout.offset(),
11441 indices,
11442 "Tensor::layout_linear_offset",
11443 )
11444 }
11445 // A caller-owned payload's owner resolves its own offsets.
11446 TensorPayload::External(..) => Err(crate::Error::unsupported_dtype(
11447 "layout_linear_offset",
11448 self.dtype(),
11449 "an externally defined payload resolves its own offsets",
11450 )),
11451 }
11452 }
11453
11454 /// Return whether this tensor is compact column-major.
11455 ///
11456 /// # Examples
11457 ///
11458 /// ```rust
11459 /// use tenferro_tensor::Tensor;
11460 ///
11461 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11462 /// assert!(t.is_col_major_contiguous()?);
11463 /// # Ok::<(), tenferro_tensor::Error>(())
11464 /// ```
11465 /// # Errors
11466 ///
11467 /// Returns [`crate::Error::Validation`] with
11468 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
11469 /// compactness arithmetic overflows.
11470 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
11471 match &self.payload {
11472 TensorPayload::Native(_) => tensor_layout(self)
11473 .is_compact_col_major()
11474 .map_err(|err| tensor_layout_error("Tensor::is_col_major_contiguous", err)),
11475 // The payload is a compact column-major host tensor by construction.
11476 TensorPayload::External(..) => Ok(true),
11477 }
11478 }
11479
11480 /// Return a compact string summary of this tensor's layout metadata.
11481 ///
11482 /// # Examples
11483 ///
11484 /// ```rust
11485 /// use tenferro_tensor::Tensor;
11486 ///
11487 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11488 /// assert!(t.layout_summary().contains("shape=[2]"));
11489 /// # Ok::<(), tenferro_tensor::Error>(())
11490 /// ```
11491 pub fn layout_summary(&self) -> String {
11492 let layout = tensor_layout(self);
11493 layout_summary(layout.shape(), layout.strides(), layout.offset())
11494 }
11495
11496 /// Assert this tensor is compact column-major.
11497 ///
11498 /// # Examples
11499 ///
11500 /// ```rust
11501 /// use tenferro_tensor::Tensor;
11502 ///
11503 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11504 /// t.assert_col_major_contiguous()?;
11505 /// # Ok::<(), tenferro_tensor::Error>(())
11506 /// ```
11507 /// # Errors
11508 ///
11509 /// Returns [`crate::Error::Validation`] with
11510 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
11511 /// compactness arithmetic overflows, or
11512 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
11513 /// tensor is not compact column-major.
11514 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
11515 let layout = tensor_layout(self);
11516 assert_layout_col_major_contiguous(
11517 self.is_col_major_contiguous()?,
11518 layout.shape(),
11519 layout.strides(),
11520 layout.offset(),
11521 "Tensor::assert_col_major_contiguous",
11522 )
11523 }
11524
11525 /// Try to borrow the host data as a typed slice.
11526 ///
11527 /// Returns an error if the tensor dtype does not match `T`.
11528 ///
11529 /// # Examples
11530 ///
11531 /// ```
11532 /// use tenferro_tensor::{Tensor, TypedTensor};
11533 ///
11534 /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
11535 /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
11536 /// assert!(t.as_slice::<f32>().is_err());
11537 /// ```
11538 /// # Errors
11539 ///
11540 /// Returns [`crate::Error::Validation`] with
11541 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
11542 /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
11543 /// matching tensor uses backend storage that has not been downloaded.
11544 pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
11545 T::as_slice(self)
11546 }
11547
11548 /// Borrow the typed tensor when the requested scalar matches this tensor's dtype.
11549 ///
11550 /// This is the accessor tag-based dispatch needs: it recovers the typed tensor — and with it the
11551 /// device buffer — from a value whose element type is only known at run time, so a caller can
11552 /// dispatch on [`Tensor::dtype`] instead of matching every variant. An externally defined scalar
11553 /// is not a typed tensor, so it returns `None` rather than guessing a representation.
11554 ///
11555 /// # Examples
11556 ///
11557 /// ```
11558 /// use tenferro_tensor::Tensor;
11559 ///
11560 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11561 /// assert!(tensor.as_typed::<f64>().is_some());
11562 /// assert!(tensor.as_typed::<f32>().is_none());
11563 /// assert_eq!(tensor.as_typed::<f64>().unwrap().shape(), &[2]);
11564 /// # Ok::<(), tenferro_tensor::Error>(())
11565 /// ```
11566 #[must_use]
11567 pub fn as_typed<T: TensorScalar>(&self) -> Option<&TypedTensor<T>> {
11568 if self.dtype() != T::dtype() {
11569 return None;
11570 }
11571 match &self.payload {
11572 TensorPayload::Native(preset) => with_preset!(preset, |typed| {
11573 (typed as &dyn Any).downcast_ref::<TypedTensor<T>>()
11574 }),
11575 TensorPayload::External(..) => None,
11576 }
11577 }
11578
11579 /// Mutably borrow the typed tensor when the requested scalar matches this tensor's dtype.
11580 ///
11581 /// This is the mutable half of [`Tensor::as_typed`], for the tables whose arm calls a method that
11582 /// needs `&mut`, such as marking a freshly allocated output with its placement. An externally
11583 /// defined scalar is not a typed tensor, so it returns `None` for the same reason.
11584 ///
11585 /// # Examples
11586 ///
11587 /// ```
11588 /// use tenferro_tensor::Tensor;
11589 ///
11590 /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11591 /// assert!(tensor.as_typed_mut::<f64>().is_some());
11592 /// assert!(tensor.as_typed_mut::<f32>().is_none());
11593 /// # Ok::<(), tenferro_tensor::Error>(())
11594 /// ```
11595 #[must_use]
11596 pub fn as_typed_mut<T: TensorScalar>(&mut self) -> Option<&mut TypedTensor<T>> {
11597 if self.dtype() != T::dtype() {
11598 return None;
11599 }
11600 match &mut self.payload {
11601 TensorPayload::Native(preset) => with_preset!(preset, |typed| {
11602 (typed as &mut dyn Any).downcast_mut::<TypedTensor<T>>()
11603 }),
11604 TensorPayload::External(..) => None,
11605 }
11606 }
11607
11608 /// Consume this tensor and return the owned typed tensor when the dtype matches.
11609 ///
11610 /// This is the consuming counterpart of [`Tensor::as_typed`], for the tables whose arm hands the typed
11611 /// tensor to a function that takes it by value — reusing its buffer rather than copying it.
11612 ///
11613 /// # Examples
11614 ///
11615 /// ```
11616 /// use tenferro_tensor::Tensor;
11617 ///
11618 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11619 /// assert!(tensor.into_typed::<f64>().is_ok());
11620 ///
11621 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0])?;
11622 /// assert!(tensor.into_typed::<f64>().is_err());
11623 /// # Ok::<(), tenferro_tensor::Error>(())
11624 /// ```
11625 /// # Errors
11626 ///
11627 /// Returns [`ReinterpretError`] carrying the unchanged tensor when `T` is
11628 /// not this tensor's dtype, with [`crate::Error::Validation`] and
11629 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] as the cause.
11630 /// A matching tensor is handed over as it is, including one whose storage
11631 /// lives in a backend buffer.
11632 pub fn into_typed<T: TensorScalar>(
11633 self,
11634 ) -> std::result::Result<TypedTensor<T>, ReinterpretError<Self>> {
11635 T::into_typed(self)
11636 }
11637
11638 /// Consume this tensor and return its owned column-major buffer when the
11639 /// dtype matches.
11640 ///
11641 /// # Examples
11642 ///
11643 /// ```
11644 /// use tenferro_tensor::Tensor;
11645 ///
11646 /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
11647 /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
11648 /// ```
11649 /// # Errors
11650 ///
11651 /// Returns [`ReinterpretError`] carrying the unchanged tensor when `T` does
11652 /// not match the tensor dtype or when the matching tensor uses backend
11653 /// storage that has not been downloaded.
11654 pub fn into_vec_col_major<T: TensorScalar>(
11655 self,
11656 ) -> std::result::Result<(Vec<usize>, Vec<T>), ReinterpretError<Self>> {
11657 let typed = T::into_typed(self)?;
11658 match typed.into_vec_col_major() {
11659 Ok(parts) => Ok(parts),
11660 Err(failure) => {
11661 let (owner, error) = failure.into_parts();
11662 Err(ReinterpretError::new(Tensor::from_typed(owner), error))
11663 }
11664 }
11665 }
11666}
11667
11668// INVARIANT: retained for crate-local layout tests while tensor indexing
11669// helpers remain split across the tensor and CPU crates.
11670#[allow(dead_code)]
11671pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
11672 for i in 0..shape.len() {
11673 if shape[i] == 0 {
11674 out[i] = 0;
11675 } else {
11676 out[i] = flat % shape[i];
11677 flat /= shape[i];
11678 }
11679 }
11680}