tenferro_tensor/types.rs
1use num_complex::{Complex, Complex32, Complex64};
2use num_traits::{One, Zero};
3use std::any::Any;
4use std::fmt::Debug;
5use std::marker::PhantomData;
6use std::mem::{align_of, needs_drop, offset_of, size_of};
7use std::num::NonZeroUsize;
8use std::ops::Deref;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11use crate::config::SliceConfig;
12use crate::error::ReinterpretError;
13pub use tenferro_tensor_core::{DType, DynRank, Rank, TensorLayout, TensorRank};
14use tenferro_tensor_core::{ShapeVec, StrideVec};
15use tenferro_tensor_core::{SliceSpec as CoreSliceSpec, ValidationError};
16
17use crate::storage::{
18 AllocationGroup, AllocationSlot, BackendAllocation, DescriptorSlot, GroupError, GroupReadView,
19 GroupWriteView,
20};
21
22mod accessors;
23mod col_major;
24mod shape_packing;
25mod strided_view;
26#[cfg(test)]
27mod tests;
28
29pub use col_major::{ColMajorView, ColMajorViewMut};
30pub use strided_view::StridedSliceSpec;
31
32fn shape_vec(shape: &[usize]) -> ShapeVec {
33 shape.iter().copied().collect()
34}
35
36fn stride_vec(strides: &[isize]) -> StrideVec {
37 strides.iter().copied().collect()
38}
39
40fn representation_pair_error(
41 op: &'static str,
42 from: DType,
43 to: DType,
44 message: impl Into<String>,
45) -> crate::Error {
46 crate::Error::unsupported_dtype_conversion(op, from, to, message)
47}
48
49fn validate_representation_pair(op: &'static str, from: DType, to: DType) -> crate::Result<()> {
50 let valid = match (from, to) {
51 (DType::C32, DType::F32) | (DType::F32, DType::C32) => {
52 size_of::<Complex32>() == 2 * size_of::<f32>()
53 && align_of::<Complex32>() == align_of::<f32>()
54 && offset_of!(Complex32, re) == 0
55 && offset_of!(Complex32, im) == size_of::<f32>()
56 && !needs_drop::<Complex32>()
57 && !needs_drop::<f32>()
58 }
59 (DType::C64, DType::F64) | (DType::F64, DType::C64) => {
60 size_of::<Complex64>() == 2 * size_of::<f64>()
61 && align_of::<Complex64>() == align_of::<f64>()
62 && offset_of!(Complex64, re) == 0
63 && offset_of!(Complex64, im) == size_of::<f64>()
64 && !needs_drop::<Complex64>()
65 && !needs_drop::<f64>()
66 }
67 _ => false,
68 };
69 if valid {
70 Ok(())
71 } else {
72 Err(representation_pair_error(
73 op,
74 from,
75 to,
76 "only the sealed Complex<f32><->f32 and Complex<f64><->f64 representations are supported",
77 ))
78 }
79}
80
81fn reinterpret_complex_to_real_layout(
82 shape: &[usize],
83 strides: &[isize],
84 offset: isize,
85 complex_buffer_len: usize,
86 op: &'static str,
87) -> crate::Result<TensorLayout<DynRank>> {
88 let real_buffer_len = complex_buffer_len
89 .checked_mul(2)
90 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
91 let rank = shape
92 .len()
93 .checked_add(1)
94 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
95 let mut real_shape = ShapeVec::with_capacity(rank);
96 real_shape.push(2);
97 real_shape.extend_from_slice(shape);
98 let mut real_strides = StrideVec::with_capacity(rank);
99 real_strides.push(1);
100 for &stride in strides {
101 real_strides.push(
102 stride
103 .checked_mul(2)
104 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
105 );
106 }
107 let real_offset = offset
108 .checked_mul(2)
109 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
110 TensorLayout::from_parts(real_shape, real_strides, real_offset, real_buffer_len)
111 .map_err(|err| tensor_layout_error(op, err))
112}
113
114fn reinterpret_real_to_complex_layout(
115 shape: &[usize],
116 strides: &[isize],
117 offset: isize,
118 real_buffer_len: usize,
119 op: &'static str,
120) -> crate::Result<TensorLayout<DynRank>> {
121 if shape.first().copied() != Some(2) {
122 return Err(crate::Error::invalid_argument(
123 op,
124 "shape",
125 "the leading extent must be 2 for a complex reinterpretation",
126 ));
127 }
128 if strides.first().copied() != Some(1) {
129 return Err(crate::Error::invalid_argument(
130 op,
131 "strides",
132 "the leading stride must be 1 for a complex reinterpretation",
133 ));
134 }
135 if offset % 2 != 0 {
136 return Err(crate::Error::invalid_argument(
137 op,
138 "offset",
139 "the offset must be divisible by 2 for a complex reinterpretation",
140 ));
141 }
142 let mut complex_strides = StrideVec::with_capacity(strides.len() - 1);
143 for &stride in &strides[1..] {
144 if stride % 2 != 0 {
145 return Err(crate::Error::invalid_argument(
146 op,
147 "strides",
148 "all non-leading strides must be divisible by 2",
149 ));
150 }
151 complex_strides.push(stride / 2);
152 }
153 let complex_buffer_len = real_buffer_len / 2;
154 TensorLayout::from_parts(
155 shape[1..].iter().copied().collect(),
156 complex_strides,
157 offset / 2,
158 complex_buffer_len,
159 )
160 .map_err(|err| tensor_layout_error(op, err))
161}
162
163fn reinterpret_host_slice<'a, T: TensorScalar, U: TensorScalar>(
164 data: &'a [T],
165 op: &'static str,
166) -> crate::Result<&'a [U]> {
167 let byte_len = data
168 .len()
169 .checked_mul(size_of::<T>())
170 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
171 if !byte_len.is_multiple_of(size_of::<U>()) {
172 return Err(crate::Error::validation(
173 op,
174 ValidationError::ViewOutOfBounds,
175 ));
176 }
177 if data.as_ptr().align_offset(align_of::<U>()) != 0 {
178 return Err(crate::Error::invalid_argument(
179 op,
180 "alignment",
181 "the source allocation is not aligned for the target representation",
182 ));
183 }
184 // SAFETY: `validate_representation_pair` seals the only supported pairs;
185 // sizes, alignment, field order, and drop properties are checked before
186 // exposing the borrowed target slice.
187 Ok(unsafe { std::slice::from_raw_parts(data.as_ptr().cast::<U>(), byte_len / size_of::<U>()) })
188}
189
190fn reinterpret_host_slice_mut<'a, T: TensorScalar, U: TensorScalar>(
191 data: &'a mut [T],
192 op: &'static str,
193) -> crate::Result<&'a mut [U]> {
194 let byte_len = data
195 .len()
196 .checked_mul(size_of::<T>())
197 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
198 if !byte_len.is_multiple_of(size_of::<U>()) {
199 return Err(crate::Error::validation(
200 op,
201 ValidationError::ViewOutOfBounds,
202 ));
203 }
204 if data.as_mut_ptr().align_offset(align_of::<U>()) != 0 {
205 return Err(crate::Error::invalid_argument(
206 op,
207 "alignment",
208 "the source allocation is not aligned for the target representation",
209 ));
210 }
211 // SAFETY: the mutable source borrow is unique and the sealed pair has no
212 // padding or drop glue, so the target slice covers the same bytes exactly.
213 Ok(unsafe {
214 std::slice::from_raw_parts_mut(data.as_mut_ptr().cast::<U>(), byte_len / size_of::<U>())
215 })
216}
217
218/// Memory location for tensor storage.
219///
220/// # Examples
221///
222/// ```rust
223/// use tenferro_tensor::MemoryKind;
224///
225/// let kind = MemoryKind::UnpinnedHost;
226/// ```
227#[derive(Clone, Debug, PartialEq, Eq, Hash)]
228pub enum MemoryKind {
229 Device,
230 PinnedHost,
231 UnpinnedHost,
232 Managed,
233 Other(String),
234}
235
236/// Compute device family.
237///
238/// # Examples
239///
240/// ```rust
241/// use tenferro_tensor::DeviceKind;
242///
243/// let kind = DeviceKind::Cpu;
244/// ```
245#[derive(Clone, Debug, PartialEq, Eq, Hash)]
246pub enum DeviceKind {
247 Cpu,
248 Gpu(GpuBackendKind),
249 Other(String),
250}
251
252/// GPU backend family used by placement metadata.
253///
254/// # Examples
255///
256/// ```rust
257/// use tenferro_tensor::GpuBackendKind;
258///
259/// let kind = GpuBackendKind::Cuda;
260/// let webgpu = GpuBackendKind::WebGpu;
261/// assert_ne!(kind, webgpu);
262/// ```
263#[derive(Clone, Debug, PartialEq, Eq, Hash)]
264pub enum GpuBackendKind {
265 Cuda,
266 WebGpu,
267 Rocm,
268 Other(String),
269}
270
271/// Concrete compute device identifier.
272///
273/// # Examples
274///
275/// ```rust
276/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind};
277///
278/// let device = DeviceId {
279/// kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
280/// ordinal: 0,
281/// };
282/// ```
283#[derive(Clone, Debug, PartialEq, Eq, Hash)]
284pub struct DeviceId {
285 pub kind: DeviceKind,
286 pub ordinal: usize,
287}
288
289/// Caller-stable identity for a CPU execution domain.
290///
291/// Domain IDs are metadata supplied by the caller or execution coordinator;
292/// creating an ID does not allocate a process-global identity.
293///
294/// # Examples
295///
296/// ```rust
297/// use tenferro_tensor::CpuDomainId;
298///
299/// let domain = CpuDomainId::new(17);
300/// assert_eq!(domain.as_u64(), 17);
301/// ```
302#[repr(transparent)]
303#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
304pub struct CpuDomainId(u64);
305
306impl CpuDomainId {
307 /// Create a caller-stable CPU domain identity.
308 ///
309 /// # Examples
310 ///
311 /// ```rust
312 /// use tenferro_tensor::CpuDomainId;
313 ///
314 /// assert_eq!(CpuDomainId::new(3), CpuDomainId::new(3));
315 /// ```
316 pub const fn new(id: u64) -> Self {
317 Self(id)
318 }
319
320 /// Return the caller-supplied integer identity.
321 ///
322 /// # Examples
323 ///
324 /// ```rust
325 /// use tenferro_tensor::CpuDomainId;
326 ///
327 /// assert_eq!(CpuDomainId::new(9).as_u64(), 9);
328 /// ```
329 pub const fn as_u64(self) -> u64 {
330 self.0
331 }
332}
333
334/// Placement metadata for a tensor buffer.
335///
336/// # Examples
337///
338/// ```rust
339/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind, MemoryKind, Placement};
340///
341/// let placement = Placement {
342/// memory_kind: MemoryKind::Device,
343/// device: Some(DeviceId {
344/// kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
345/// ordinal: 0,
346/// }),
347/// cpu_affinity: None,
348/// };
349/// assert!(placement.cpu_affinity.is_none());
350/// ```
351#[derive(Clone, Debug, PartialEq, Eq, Hash)]
352pub struct Placement {
353 /// Storage memory class, independent of execution routing metadata.
354 pub memory_kind: MemoryKind,
355 /// Device that owns or addresses the storage, when applicable.
356 pub device: Option<DeviceId>,
357 /// Preferred or producing CPU execution domain for routing and locality.
358 ///
359 /// This tag is not proof of allocation ownership, page residency, NUMA
360 /// pinning, or worker-affinity enforcement. Backend allocation-domain
361 /// metadata remains attached to the buffer independently.
362 pub cpu_affinity: Option<CpuDomainId>,
363}
364
365impl Default for Placement {
366 fn default() -> Self {
367 default_placement()
368 }
369}
370
371/// Backend-owned buffer handle.
372///
373/// `BackendStorageHandle::new` creates an empty opaque handle. Use
374/// [`BackendStorageHandle::new_with_len`] when test or adapter code needs to model a
375/// non-empty backend allocation.
376///
377/// # Examples
378///
379/// ```rust
380/// use tenferro_tensor::BackendStorageHandle;
381///
382/// let handle = BackendStorageHandle::<f64>::new(7);
383/// ```
384pub struct BackendStorageHandle<T> {
385 id: u64,
386 len: usize,
387 allocation_domain: AllocationDomainId,
388 _phantom: std::marker::PhantomData<T>,
389}
390
391/// Identity of a backend-owned allocation domain.
392///
393/// Domains let cooperating backends accept shared allocations without treating
394/// another context's physically similar buffer as compatible.
395///
396/// # Examples
397///
398/// ```rust
399/// use tenferro_tensor::AllocationDomainId;
400///
401/// assert_ne!(AllocationDomainId::fresh(), AllocationDomainId::fresh());
402/// ```
403#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
404pub struct AllocationDomainId(u64);
405
406impl AllocationDomainId {
407 /// Create a process-unique allocation-domain identity.
408 ///
409 /// # Examples
410 ///
411 /// ```rust
412 /// use tenferro_tensor::AllocationDomainId;
413 ///
414 /// let domain = AllocationDomainId::fresh();
415 /// assert_eq!(domain, domain);
416 /// ```
417 pub fn fresh() -> Self {
418 static NEXT_DOMAIN_ID: AtomicU64 = AtomicU64::new(1);
419 Self(NEXT_DOMAIN_ID.fetch_add(1, Ordering::Relaxed))
420 }
421}
422
423/// Stable physical identity of one backend allocation.
424///
425/// # Examples
426///
427/// ```rust
428/// use tenferro_tensor::AllocationId;
429///
430/// assert_eq!(AllocationId::from_backend_id(7), AllocationId::from_backend_id(7));
431/// ```
432#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
433pub struct AllocationId(u64);
434
435impl AllocationId {
436 /// Wrap an allocation identity supplied by the owning backend.
437 ///
438 /// # Examples
439 ///
440 /// ```rust
441 /// use tenferro_tensor::AllocationId;
442 ///
443 /// let id = AllocationId::from_backend_id(3);
444 /// assert_eq!(id, AllocationId::from_backend_id(3));
445 /// ```
446 pub const fn from_backend_id(id: u64) -> Self {
447 Self(id)
448 }
449}
450
451/// Typed failure returned by guarded backend host access.
452///
453/// # Examples
454///
455/// ```rust
456/// use tenferro_tensor::HostAccessError;
457///
458/// let error = HostAccessError::Unsupported { backend: "opaque" };
459/// assert!(error.to_string().contains("opaque"));
460/// ```
461#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
462#[non_exhaustive]
463pub enum HostAccessError {
464 /// The backend does not expose guarded host access for this allocation.
465 #[error("backend `{backend}` does not support guarded host access")]
466 Unsupported { backend: &'static str },
467 /// The allocation belongs to another shared-allocation domain.
468 #[error("allocation belongs to domain {actual:?}, expected {expected:?}")]
469 ForeignDomain {
470 expected: AllocationDomainId,
471 actual: AllocationDomainId,
472 },
473 /// Another host mapping overlaps this allocation.
474 #[error("the allocation already has an active host mapping")]
475 OverlappingHostMapping,
476 /// GPU work currently owns or has reserved the allocation.
477 #[error("GPU access is in progress for the allocation")]
478 GpuAccessInProgress,
479 /// Host mapping is active while GPU access was requested.
480 #[error("the allocation is mapped for host access")]
481 MappedForHost,
482 /// The backend failed to complete the map operation.
483 #[error("backend host mapping failed: {message}")]
484 BackendFailure { message: String },
485 /// The source did not cover the full write-only mapping.
486 #[error("host write length mismatch: expected {expected}, got {actual}")]
487 LengthMismatch { expected: usize, actual: usize },
488}
489
490/// Metadata sealed at the tensor/root boundary before a provider launch.
491///
492/// Providers receive this request exactly once for a prepared access. Binding
493/// code consumes the resulting opaque state and does not receive replacement
494/// storage, ranges, or raw pointers.
495#[doc(hidden)]
496#[derive(Clone, Copy, Debug)]
497pub struct DeviceAccessRequest<'a> {
498 allocation_domain: AllocationDomainId,
499 allocation_id: AllocationId,
500 byte_len: usize,
501 element_size: usize,
502 shape: &'a [usize],
503 strides: &'a [isize],
504 offset: isize,
505}
506
507impl<'a> DeviceAccessRequest<'a> {
508 pub(crate) fn new(
509 allocation_domain: AllocationDomainId,
510 allocation_id: AllocationId,
511 byte_len: usize,
512 element_size: usize,
513 shape: &'a [usize],
514 strides: &'a [isize],
515 offset: isize,
516 ) -> Self {
517 Self {
518 allocation_domain,
519 allocation_id,
520 byte_len,
521 element_size,
522 shape,
523 strides,
524 offset,
525 }
526 }
527
528 pub fn allocation_domain(&self) -> AllocationDomainId {
529 self.allocation_domain
530 }
531
532 pub fn allocation_id(&self) -> AllocationId {
533 self.allocation_id
534 }
535
536 pub fn byte_len(&self) -> usize {
537 self.byte_len
538 }
539
540 pub fn element_size(&self) -> usize {
541 self.element_size
542 }
543
544 pub fn shape(&self) -> &[usize] {
545 self.shape
546 }
547
548 pub fn strides(&self) -> &[isize] {
549 self.strides
550 }
551
552 pub fn offset(&self) -> isize {
553 self.offset
554 }
555}
556
557/// Typed failure returned while preparing a provider-native device access.
558#[doc(hidden)]
559#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
560pub enum DeviceAccessError {
561 #[error("backend `{backend}` does not support prepared device access")]
562 Unsupported { backend: &'static str },
563 #[error("prepared device access request is invalid: {message}")]
564 InvalidRequest { message: String },
565 #[error("provider device preparation failed: {message}")]
566 ProviderFailure { message: String },
567}
568
569/// Opaque provider-prepared state retained for one device binding.
570#[doc(hidden)]
571pub trait PreparedDeviceAccess: Debug {
572 fn as_any(&self) -> &dyn Any;
573
574 fn into_any(self: Box<Self>) -> Box<dyn Any>;
575}
576
577trait ReadGuardAccess<T> {
578 fn as_slice(&self) -> &[T];
579}
580
581impl<T, G> ReadGuardAccess<T> for G
582where
583 G: Deref,
584 G::Target: AsRef<[T]>,
585{
586 fn as_slice(&self) -> &[T] {
587 self.deref().as_ref()
588 }
589}
590
591/// Closure-scoped read mapping of a backend allocation.
592///
593/// # Examples
594///
595/// ```rust
596/// use tenferro_tensor::HostReadGuard;
597///
598/// let guard = HostReadGuard::new(vec![1_u32, 2]);
599/// assert_eq!(&*guard, &[1, 2]);
600/// ```
601pub struct HostReadGuard<'a, T> {
602 access: Box<dyn ReadGuardAccess<T> + 'a>,
603}
604
605impl<T> Debug for HostReadGuard<'_, T> {
606 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
607 formatter
608 .debug_struct("HostReadGuard")
609 .field("len", &self.len())
610 .finish_non_exhaustive()
611 }
612}
613
614impl<'a, T> HostReadGuard<'a, T> {
615 /// Wrap a backend-native read guard without exposing its concrete type.
616 ///
617 /// # Examples
618 ///
619 /// ```rust
620 /// use tenferro_tensor::HostReadGuard;
621 ///
622 /// let guard = HostReadGuard::new(vec![3_i32]);
623 /// assert_eq!(guard[0], 3);
624 /// ```
625 pub fn new<G>(guard: G) -> Self
626 where
627 G: Deref + 'a,
628 G::Target: AsRef<[T]>,
629 T: 'a,
630 {
631 Self {
632 access: Box::new(guard),
633 }
634 }
635
636 /// Borrow the mapped elements as a rank-1 host view.
637 ///
638 /// The view lends the mapping's shared borrow, so the guard stays alive for
639 /// as long as the view is used and no copy or transfer happens.
640 ///
641 /// # Examples
642 ///
643 /// ```rust
644 /// use tenferro_tensor::HostReadGuard;
645 ///
646 /// let guard = HostReadGuard::new(vec![3_i32, 4]);
647 /// let view = guard.as_view()?;
648 /// assert_eq!(view.shape(), &[2]);
649 /// assert_eq!(view.get(&[1]), Some(&4));
650 /// # Ok::<(), tenferro_tensor::Error>(())
651 /// ```
652 ///
653 /// # Errors
654 ///
655 /// Returns [`crate::Error::Validation`] with
656 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] or
657 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
658 /// mapped length cannot be represented as a rank-1 layout.
659 pub fn as_view(&self) -> crate::Result<TypedTensorView<'_, T, DynRank>>
660 where
661 T: 'static,
662 {
663 let data: &[T] = self;
664 TypedTensorView::from_slice(vec![data.len()], vec![1], 0, data)
665 }
666}
667
668impl<T> Deref for HostReadGuard<'_, T> {
669 type Target = [T];
670
671 fn deref(&self) -> &Self::Target {
672 self.access.as_slice()
673 }
674}
675
676/// Backend-neutral owner of one shared tensor allocation domain.
677///
678/// CPU operation crates use this object-safe boundary to allocate results in
679/// the same managed domain without depending on a GPU provider crate.
680///
681/// # Examples
682///
683/// ```rust
684/// use std::sync::Arc;
685/// use tenferro_tensor::SharedTensorAllocationDomain;
686///
687/// let _domain: Option<Arc<dyn SharedTensorAllocationDomain>> = None;
688/// ```
689pub trait SharedTensorAllocationDomain: Debug + Send + Sync + 'static {
690 /// Return the stable identity shared by every allocation from this owner.
691 fn id(&self) -> AllocationDomainId;
692
693 /// Allocate an uninitialized compact column-major tensor in this domain.
694 ///
695 /// # Errors
696 ///
697 /// Returns a typed validation, unsupported-dtype, or backend allocation error.
698 fn allocate(&self, dtype: DType, shape: &[usize]) -> crate::Result<Tensor>;
699}
700
701type HostWriteCopy<'a, T> = dyn FnMut(&[T]) -> Result<(), HostAccessError> + 'a;
702
703/// Closure-scoped write-only mapping of a backend allocation.
704///
705/// # Examples
706///
707/// ```rust
708/// use tenferro_tensor::{HostAccessError, HostWriteGuard};
709///
710/// let mut written = Vec::new();
711/// {
712/// let mut guard = HostWriteGuard::new(2, |source: &[u32]| {
713/// written.extend_from_slice(source);
714/// Ok::<(), HostAccessError>(())
715/// });
716/// guard.copy_from_slice(&[4, 5]).unwrap();
717/// }
718/// assert_eq!(written, [4, 5]);
719/// ```
720pub struct HostWriteGuard<'a, T> {
721 len: usize,
722 copy: Box<HostWriteCopy<'a, T>>,
723}
724
725impl<T> Debug for HostWriteGuard<'_, T> {
726 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
727 formatter
728 .debug_struct("HostWriteGuard")
729 .field("len", &self.len)
730 .finish_non_exhaustive()
731 }
732}
733
734impl<'a, T> HostWriteGuard<'a, T> {
735 /// Wrap a backend-native write guard without exposing its concrete type.
736 ///
737 /// # Examples
738 ///
739 /// ```rust
740 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
741 ///
742 /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
743 /// assert!(guard.is_empty());
744 /// ```
745 ///
746 /// # Errors
747 ///
748 /// Construction is infallible. A callback failure such as
749 /// [`HostAccessError::BackendFailure`] is returned later by
750 /// [`Self::copy_from_slice`].
751 pub fn new<F>(len: usize, copy: F) -> Self
752 where
753 F: FnMut(&[T]) -> Result<(), HostAccessError> + 'a,
754 T: 'a,
755 {
756 Self {
757 len,
758 copy: Box::new(copy),
759 }
760 }
761
762 /// Number of elements covered by this write-only mapping.
763 ///
764 /// # Examples
765 ///
766 /// ```rust
767 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
768 ///
769 /// let guard = HostWriteGuard::new(2, |_source: &[f32]| Ok::<(), HostAccessError>(()));
770 /// assert_eq!(guard.len(), 2);
771 /// ```
772 pub fn len(&self) -> usize {
773 self.len
774 }
775
776 /// Returns `true` when this mapping covers no elements.
777 ///
778 /// # Examples
779 ///
780 /// ```rust
781 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
782 ///
783 /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
784 /// assert!(guard.is_empty());
785 /// ```
786 pub fn is_empty(&self) -> bool {
787 self.len == 0
788 }
789
790 /// Replace the full mapped allocation contents.
791 ///
792 /// # Examples
793 ///
794 /// ```rust
795 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
796 ///
797 /// let mut guard = HostWriteGuard::new(1, |_source: &[f32]| Ok::<(), HostAccessError>(()));
798 /// guard.copy_from_slice(&[1.0]).unwrap();
799 /// ```
800 ///
801 /// # Errors
802 ///
803 /// Returns [`HostAccessError::LengthMismatch`] when `source` does not cover
804 /// the complete mapping, or the typed backend error returned by the owning
805 /// write guard.
806 pub fn copy_from_slice(&mut self, source: &[T]) -> Result<(), HostAccessError> {
807 if source.len() != self.len {
808 return Err(HostAccessError::LengthMismatch {
809 expected: self.len,
810 actual: source.len(),
811 });
812 }
813 (self.copy)(source)
814 }
815}
816
817impl<T> Debug for BackendStorageHandle<T> {
818 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
819 f.debug_struct("BackendStorageHandle")
820 .field("id", &self.id)
821 .finish()
822 }
823}
824
825impl<T> BackendStorageHandle<T> {
826 /// Create a new backend buffer handle.
827 ///
828 /// # Examples
829 ///
830 /// ```rust
831 /// use tenferro_tensor::BackendStorageHandle;
832 ///
833 /// let handle = BackendStorageHandle::<f64>::new(1);
834 /// assert_eq!(tenferro_tensor::BackendStorage::len(&handle), 0);
835 /// ```
836 pub fn new(id: u64) -> Self {
837 Self::new_with_len(id, 0)
838 }
839
840 /// Create a new backend buffer handle with a logical element count.
841 ///
842 /// # Examples
843 ///
844 /// ```rust
845 /// use tenferro_tensor::{BackendStorage, BackendStorageHandle};
846 ///
847 /// let handle = BackendStorageHandle::<f64>::new_with_len(1, 4);
848 /// assert_eq!(BackendStorage::len(&handle), 4);
849 /// ```
850 pub fn new_with_len(id: u64, len: usize) -> Self {
851 Self {
852 id,
853 len,
854 // Synthetic opaque handles are test/adapter allocations. Give
855 // each one an explicit domain so root import never fabricates
856 // identity from a missing provider field.
857 allocation_domain: AllocationDomainId::fresh(),
858 _phantom: std::marker::PhantomData,
859 }
860 }
861}
862
863/// Opaque backend-owned tensor buffer.
864///
865/// Tensor core never inspects backend-native allocations directly. Backend
866/// crates store their own concrete handle types behind this trait and
867/// downcast inside the owning backend only.
868///
869/// # Examples
870///
871/// ```rust
872/// use std::sync::Arc;
873/// use tenferro_tensor::{BackendStorage, BackendStorageHandle};
874///
875/// let buffer: Arc<dyn BackendStorage<f64>> = Arc::new(BackendStorageHandle::<f64>::new_with_len(7, 2));
876/// assert_eq!(buffer.backend_family(), "opaque");
877/// assert_eq!(buffer.len(), 2);
878/// ```
879pub trait BackendStorage<T>: Debug + Send + Sync + 'static {
880 /// Stable backend family identifier.
881 fn backend_family(&self) -> &'static str;
882
883 /// Number of logical elements in the backend allocation.
884 fn len(&self) -> usize;
885
886 /// Returns `true` when the backend allocation is empty.
887 fn is_empty(&self) -> bool {
888 self.len() == 0
889 }
890
891 /// Return the shared-allocation domain, when this buffer belongs to one.
892 fn allocation_domain(&self) -> Option<AllocationDomainId> {
893 None
894 }
895
896 /// Return the stable physical allocation identity, when available.
897 fn allocation_id(&self) -> Option<AllocationId> {
898 None
899 }
900
901 /// Prepare one provider-native device access from the root-owned buffer.
902 ///
903 /// The returned state is consumed by the provider binding path. Providers
904 /// that do not expose device launches return [`DeviceAccessError::Unsupported`].
905 #[doc(hidden)]
906 fn prepare_device_access(
907 &self,
908 _request: DeviceAccessRequest<'_>,
909 ) -> Result<Box<dyn PreparedDeviceAccess>, DeviceAccessError> {
910 Err(DeviceAccessError::Unsupported {
911 backend: self.backend_family(),
912 })
913 }
914
915 /// Map the allocation for closure-scoped host reads.
916 ///
917 /// # Errors
918 ///
919 /// The default returns [`HostAccessError::Unsupported`]. Host-visible
920 /// backends return typed overlap, pending-GPU, or backend mapping failures.
921 fn map_read(&self) -> Result<HostReadGuard<'_, T>, HostAccessError> {
922 Err(HostAccessError::Unsupported {
923 backend: self.backend_family(),
924 })
925 }
926
927 /// Map the allocation for closure-scoped host writes.
928 ///
929 /// The mutable receiver keeps write authority with the owning tensor or
930 /// provider object; borrowed views do not clone or share that authority.
931 ///
932 /// # Errors
933 ///
934 /// The default returns [`HostAccessError::Unsupported`]. Host-visible
935 /// backends return typed overlap, pending-GPU, or backend mapping failures.
936 fn map_write(&mut self) -> Result<HostWriteGuard<'_, T>, HostAccessError> {
937 Err(HostAccessError::Unsupported {
938 backend: self.backend_family(),
939 })
940 }
941
942 /// Type-erased access for the backend crate that owns the concrete handle.
943 fn as_any(&self) -> &dyn Any;
944}
945
946impl<T: Send + Sync + 'static> BackendStorage<T> for BackendStorageHandle<T> {
947 fn backend_family(&self) -> &'static str {
948 "opaque"
949 }
950
951 fn len(&self) -> usize {
952 self.len
953 }
954
955 fn allocation_domain(&self) -> Option<AllocationDomainId> {
956 Some(self.allocation_domain)
957 }
958
959 fn allocation_id(&self) -> Option<AllocationId> {
960 Some(AllocationId::from_backend_id(self.id))
961 }
962
963 fn as_any(&self) -> &dyn Any {
964 self
965 }
966}
967
968/// Tensor storage.
969///
970/// # Examples
971///
972/// ```rust
973/// use tenferro_tensor::StorageBuffer;
974///
975/// let host = StorageBuffer::Host(vec![1.0_f64, 2.0]);
976/// ```
977#[derive(Debug)]
978pub enum StorageBuffer<T> {
979 Host(Vec<T>),
980 Backend(Box<dyn BackendStorage<T>>),
981}
982
983impl<T: 'static> StorageBuffer<T> {
984 /// Return the physical element count in this buffer.
985 ///
986 /// # Examples
987 ///
988 /// ```rust
989 /// use tenferro_tensor::StorageBuffer;
990 ///
991 /// assert_eq!(StorageBuffer::Host(vec![1_i32, 2]).len(), 2);
992 /// ```
993 pub fn len(&self) -> usize {
994 match self {
995 Self::Host(data) => data.len(),
996 Self::Backend(buffer) => buffer.len(),
997 }
998 }
999
1000 /// Return whether this buffer has no physical elements.
1001 ///
1002 /// # Examples
1003 ///
1004 /// ```rust
1005 /// use tenferro_tensor::StorageBuffer;
1006 ///
1007 /// assert!(StorageBuffer::<i32>::Host(Vec::new()).is_empty());
1008 /// ```
1009 pub fn is_empty(&self) -> bool {
1010 self.len() == 0
1011 }
1012
1013 /// Return whether the storage is backend-owned rather than host-owned.
1014 ///
1015 /// # Examples
1016 ///
1017 /// ```rust
1018 /// use tenferro_tensor::StorageBuffer;
1019 ///
1020 /// assert!(!StorageBuffer::Host(vec![1_i32]).is_backend());
1021 /// ```
1022 pub fn is_backend(&self) -> bool {
1023 matches!(self, Self::Backend(_))
1024 }
1025}
1026
1027/// Sealed marker for the plain and pooled host payload of a [`TypedTensor`].
1028///
1029/// `TypedTensor<T, R, Host>` owns host elements directly, so it offers
1030/// infallible host access (`as_slice`, `get`, `get_mut`, `Index`/`IndexMut`)
1031/// and [`Clone`] without a runtime device check. `T` needs no `Copy`,
1032/// [`TensorScalar`] or arithmetic bound for that.
1033///
1034/// # Examples
1035///
1036/// ```
1037/// use tenferro_tensor::{DynRank, Host, TypedTensor};
1038///
1039/// let tensor: TypedTensor<f64, DynRank, Host> =
1040/// TypedTensor::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1041/// assert_eq!(tensor.as_slice(), &[1.0, 2.0]);
1042/// assert_eq!(tensor[&[1]], 2.0);
1043/// # Ok::<(), tenferro_tensor::Error>(())
1044/// ```
1045#[derive(Debug)]
1046pub struct Host;
1047
1048/// Sealed marker for the group-backed payload of a [`TypedTensor`].
1049///
1050/// `TypedTensor<T, R, Gpu>` carries provider allocation authority, identity
1051/// and retirement state through its allocation group, so host access stays
1052/// fallible and must be prepared or mapped explicitly. The marker is not proof
1053/// of a particular device ordinal or provider.
1054///
1055/// # Examples
1056///
1057/// ```
1058/// use tenferro_tensor::{DynRank, Gpu, Host, TypedTensor};
1059///
1060/// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1061/// let gpu: TypedTensor<f64, DynRank, Gpu> = host.promote()?;
1062/// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
1063/// # Ok::<(), tenferro_tensor::Error>(())
1064/// ```
1065#[derive(Debug)]
1066pub struct Gpu;
1067
1068/// Sealed marker for the runtime union of the host and group-backed payloads.
1069///
1070/// This is the default representation: one type for ordinary tensors that may
1071/// be host-resident or backend-resident at runtime.
1072///
1073/// # Examples
1074///
1075/// ```
1076/// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1077///
1078/// let host = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1079/// let dynamic: TypedTensor<i32, DynRank, Dynamic> = host.into_dynamic();
1080/// assert_eq!(dynamic.host_data()?, &[1, 2]);
1081/// # Ok::<(), tenferro_tensor::Error>(())
1082/// ```
1083#[derive(Debug)]
1084pub struct Dynamic;
1085
1086mod representation_sealed {
1087 pub trait Sealed {}
1088 impl Sealed for super::Host {}
1089 impl Sealed for super::Gpu {}
1090 impl Sealed for super::Dynamic {}
1091}
1092
1093/// Sealed selector for the owned payload representation of a [`TypedTensor`].
1094///
1095/// The three markers are [`Host`], [`Gpu`] and [`Dynamic`]. The trait is
1096/// sealed: downstream crates select an existing representation, they do not
1097/// define one.
1098///
1099/// # Examples
1100///
1101/// ```
1102/// use tenferro_tensor::{Dynamic, DynRank, Host, Representation, TypedTensor};
1103///
1104/// fn extent<D: Representation>(tensor: &TypedTensor<f64, DynRank, D>) -> usize {
1105/// tensor.shape()[0]
1106/// }
1107/// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![3], vec![0.0; 3])?;
1108/// assert_eq!(extent(&host), 3);
1109/// let dynamic: TypedTensor<f64, DynRank, Dynamic> = host.into_dynamic();
1110/// assert_eq!(extent(&dynamic), 3);
1111/// # Ok::<(), tenferro_tensor::Error>(())
1112/// ```
1113pub trait Representation: representation_sealed::Sealed + 'static {
1114 /// Owned payload stored for this representation.
1115 #[doc(hidden)]
1116 type Storage<T, R: TensorRank>;
1117}
1118
1119impl Representation for Host {
1120 #[doc(hidden)]
1121 type Storage<T, R: TensorRank> = HostStorage<T>;
1122}
1123
1124impl Representation for Gpu {
1125 #[doc(hidden)]
1126 type Storage<T, R: TensorRank> = GroupStorage<R>;
1127}
1128
1129impl Representation for Dynamic {
1130 #[doc(hidden)]
1131 type Storage<T, R: TensorRank> = DynamicStorage<T, R>;
1132}
1133
1134/// Directly owned plain or pooled host elements.
1135#[doc(hidden)]
1136pub struct HostStorage<T> {
1137 data: HostData<T>,
1138}
1139
1140/// Group-backed payload: provider allocation authority, identity and retirement.
1141#[doc(hidden)]
1142pub struct GroupStorage<R: TensorRank> {
1143 group: Box<OwnedTensorGroup<R>>,
1144}
1145
1146/// Runtime union of the [`Host`] and [`Gpu`] owned payloads.
1147#[doc(hidden)]
1148pub enum DynamicStorage<T, R: TensorRank> {
1149 Host(HostStorage<T>),
1150 Group(GroupStorage<R>),
1151}
1152
1153impl<T> std::fmt::Debug for HostStorage<T> {
1154 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1155 self.data.fmt(formatter)
1156 }
1157}
1158
1159impl<R: TensorRank> std::fmt::Debug for GroupStorage<R> {
1160 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1161 formatter
1162 .debug_struct("GroupStorage")
1163 .field("group", &self.group)
1164 .finish()
1165 }
1166}
1167
1168impl<T, R: TensorRank> std::fmt::Debug for DynamicStorage<T, R> {
1169 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1170 match self {
1171 Self::Host(host) => formatter.debug_tuple("Host").field(host).finish(),
1172 Self::Group(group) => formatter.debug_tuple("Group").field(group).finish(),
1173 }
1174 }
1175}
1176
1177/// Owned compact column-major typed tensor.
1178///
1179/// `T` is the element type, `R` the rank metadata (default [`DynRank`]) and `D`
1180/// the owned representation (default [`Dynamic`]). Shape and placement live on
1181/// the tensor itself, once, never inside a storage arm.
1182///
1183/// No bound is imposed on `T` by the type: plain host adoption only needs the
1184/// constructor's own requirements, and `T: Clone` is required only by the
1185/// copying constructors. A preset [`TensorScalar`] appears only where a dtype
1186/// identity or a numerical capability is actually used, never for plain host
1187/// ownership.
1188///
1189/// Owned tensors are compact column-major. Arbitrary strides and metadata-only
1190/// layout changes are represented by [`TypedTensorView`] and
1191/// [`TypedTensorViewMut`].
1192///
1193/// # Examples
1194///
1195/// ```
1196/// use tenferro_tensor::{DynRank, Host, Rank, Tensor, TypedTensor};
1197///
1198/// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
1199/// assert_eq!(t.shape(), &[2, 2]);
1200///
1201/// let static_rank = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
1202/// assert_eq!(static_rank.rank(), 2);
1203///
1204/// let host: TypedTensor<i32, DynRank, Host> =
1205/// TypedTensor::from_host_vec_col_major(vec![2], vec![1, 2]).unwrap();
1206/// assert_eq!(host.get(&[1])?, &2);
1207///
1208/// let dynamic = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64; 4]).unwrap();
1209/// assert_eq!(dynamic.shape(), &[2, 2]);
1210/// # Ok::<(), tenferro_tensor::Error>(())
1211/// ```
1212pub struct TypedTensor<T, R: TensorRank = DynRank, D: Representation = Dynamic> {
1213 shape: R::Shape,
1214 placement: Placement,
1215 storage: D::Storage<T, R>,
1216}
1217
1218/// Validate and adopt a column-major host vector as a statically host-owned tensor.
1219fn typed_host_tensor_from_vec_col_major<T, R: TensorRank>(
1220 shape: impl tenferro_tensor_core::IntoRankShape<R>,
1221 data: Vec<T>,
1222 op: &'static str,
1223) -> crate::Result<TypedTensor<T, R, Host>> {
1224 let shape = shape
1225 .into_rank_shape()
1226 .map_err(|err| tensor_layout_error(op, err))?;
1227 tenferro_tensor_core::col_major_strides(shape.as_ref())
1228 .map_err(|err| tensor_layout_error(op, err))?;
1229 try_checked_shape_len(shape.as_ref(), data.len(), op)?;
1230 Ok(TypedTensor {
1231 shape,
1232 placement: default_placement(),
1233 storage: HostStorage {
1234 data: HostData::new(data),
1235 },
1236 })
1237}
1238
1239/// Reorder explicit row-major host values into column-major order.
1240fn row_major_reorder<T: Clone, R: TensorRank>(
1241 shape: impl tenferro_tensor_core::IntoRankShape<R>,
1242 data: Vec<T>,
1243 op: &'static str,
1244) -> crate::Result<(R::Shape, Vec<T>)> {
1245 let shape = shape
1246 .into_rank_shape()
1247 .map_err(|err| tensor_layout_error(op, err))?;
1248 tenferro_tensor_core::col_major_strides(shape.as_ref())
1249 .map_err(|err| tensor_layout_error(op, err))?;
1250 try_checked_shape_len(shape.as_ref(), data.len(), op)?;
1251 let mut row_strides = ShapeVec::from_elem(0, shape.as_ref().len());
1252 let mut stride = 1usize;
1253 for axis in (0..row_strides.len()).rev() {
1254 row_strides[axis] = stride;
1255 stride = stride
1256 .checked_mul(shape.as_ref()[axis])
1257 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
1258 }
1259 let mut coordinates = ShapeVec::from_elem(0, row_strides.len());
1260 let mut source_offset = 0usize;
1261 let mut reordered = Vec::with_capacity(data.len());
1262 for _ in 0..data.len() {
1263 reordered.push(data[source_offset].clone());
1264 for axis in 0..coordinates.len() {
1265 coordinates[axis] += 1;
1266 if coordinates[axis] < shape.as_ref()[axis] {
1267 source_offset += row_strides[axis];
1268 break;
1269 }
1270 coordinates[axis] = 0;
1271 source_offset -= row_strides[axis] * (shape.as_ref()[axis] - 1);
1272 }
1273 }
1274 Ok((shape, reordered))
1275}
1276
1277impl<T, R: TensorRank> TypedTensor<T, R, Host> {
1278 /// Adopt a column-major host `Vec<T>` as a statically host-owned tensor.
1279 ///
1280 /// The payload is the caller's vector itself; `T` needs no `Copy`,
1281 /// [`TensorScalar`] or arithmetic bound, and no group, session or device is
1282 /// involved.
1283 ///
1284 /// # Examples
1285 /// ```
1286 /// use tenferro_tensor::{Host, Rank, TypedTensor};
1287 /// struct Custom(String);
1288 /// let tensor = TypedTensor::<Custom, Rank<2>, Host>::from_host_vec_col_major(
1289 /// [1, 2], vec![Custom("a".into()), Custom("b".into())],
1290 /// )?;
1291 /// assert_eq!(tensor[&[0, 1]].0.as_str(), "b");
1292 /// # Ok::<(), tenferro_tensor::Error>(())
1293 /// ```
1294 /// # Errors
1295 /// Returns [`crate::Error::Validation`] for a rank mismatch, shape/data
1296 /// length mismatch or shape/stride arithmetic overflow.
1297 pub fn from_host_vec_col_major(
1298 shape: impl tenferro_tensor_core::IntoRankShape<R>,
1299 data: Vec<T>,
1300 ) -> crate::Result<Self> {
1301 typed_host_tensor_from_vec_col_major(shape, data, "from_host_vec_col_major")
1302 }
1303
1304 /// Explicitly import row-major host values into column-major storage.
1305 ///
1306 /// Clones each input element once.
1307 ///
1308 /// # Examples
1309 /// ```
1310 /// use tenferro_tensor::{Host, Rank, TypedTensor};
1311 /// let tensor = TypedTensor::<i32, Rank<2>, Host>::from_host_vec_row_major(
1312 /// [2, 3], vec![1, 2, 3, 4, 5, 6],
1313 /// )?;
1314 /// assert_eq!(tensor[&[1, 0]], 4);
1315 /// assert_eq!(tensor[&[0, 2]], 3);
1316 /// # Ok::<(), tenferro_tensor::Error>(())
1317 /// ```
1318 /// # Errors
1319 /// Returns [`crate::Error::Validation`] for a rank, shape-length or stride
1320 /// overflow, or when the shape product disagrees with the input length.
1321 pub fn from_host_vec_row_major(
1322 shape: impl tenferro_tensor_core::IntoRankShape<R>,
1323 data: Vec<T>,
1324 ) -> crate::Result<Self>
1325 where
1326 T: Clone,
1327 {
1328 let (shape, data) = row_major_reorder(shape, data, "from_host_vec_row_major")?;
1329 typed_host_tensor_from_vec_col_major(shape, data, "from_host_vec_row_major")
1330 }
1331
1332 /// Borrow the owned host elements.
1333 ///
1334 /// # Examples
1335 /// ```
1336 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1337 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1338 /// assert_eq!(tensor.as_slice(), &[1, 2]);
1339 /// # Ok::<(), tenferro_tensor::Error>(())
1340 /// ```
1341 pub fn as_slice(&self) -> &[T] {
1342 self.storage.data.as_slice()
1343 }
1344
1345 /// Alias of [`Self::as_slice`].
1346 ///
1347 /// # Examples
1348 ///
1349 /// ```
1350 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1351 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1352 /// assert_eq!(tensor.host_data(), &[1, 2]);
1353 /// # Ok::<(), tenferro_tensor::Error>(())
1354 /// ```
1355 pub fn host_data(&self) -> &[T] {
1356 self.as_slice()
1357 }
1358
1359 /// Exclusively borrow the owned host elements.
1360 ///
1361 /// # Examples
1362 ///
1363 /// ```
1364 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1365 /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1366 /// tensor.host_data_mut()[0] = 5;
1367 /// assert_eq!(tensor.as_slice(), &[5, 2]);
1368 /// # Ok::<(), tenferro_tensor::Error>(())
1369 /// ```
1370 pub fn host_data_mut(&mut self) -> &mut [T] {
1371 self.storage.data.as_mut_slice()
1372 }
1373
1374 /// Borrow one element by checked column-major multi-index.
1375 ///
1376 /// # Examples
1377 ///
1378 /// ```
1379 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1380 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2, 2], vec![1, 2, 3, 4])?;
1381 /// assert_eq!(tensor.get(&[1, 1])?, &4);
1382 /// assert!(tensor.get(&[2, 0]).is_err());
1383 /// # Ok::<(), tenferro_tensor::Error>(())
1384 /// ```
1385 ///
1386 /// # Errors
1387 /// Returns [`crate::Error::Validation`] for a wrong rank, an out-of-range
1388 /// coordinate or offset arithmetic overflow.
1389 pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
1390 let offset = self.linear_offset(indices)?;
1391 self.as_slice().get(offset).ok_or_else(|| {
1392 crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
1393 })
1394 }
1395
1396 /// Exclusively borrow one element by checked column-major multi-index.
1397 ///
1398 /// # Examples
1399 ///
1400 /// ```
1401 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1402 /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1403 /// *tensor.get_mut(&[1])? = 9;
1404 /// assert_eq!(tensor.as_slice(), &[1, 9]);
1405 /// # Ok::<(), tenferro_tensor::Error>(())
1406 /// ```
1407 ///
1408 /// # Errors
1409 /// Returns [`crate::Error::Validation`] for a wrong rank, an out-of-range
1410 /// coordinate or offset arithmetic overflow.
1411 pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
1412 let offset = self.linear_offset(indices)?;
1413 self.host_data_mut().get_mut(offset).ok_or_else(|| {
1414 crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
1415 })
1416 }
1417
1418 /// Consume this tensor and return the original host vector.
1419 ///
1420 /// # Examples
1421 ///
1422 /// ```
1423 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1424 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1425 /// assert_eq!(tensor.into_host_vec(), vec![1, 2]);
1426 /// # Ok::<(), tenferro_tensor::Error>(())
1427 /// ```
1428 pub fn into_host_vec(self) -> Vec<T> {
1429 self.storage.data.into_vec()
1430 }
1431
1432 /// Consume this tensor and return its shape and column-major host vector.
1433 ///
1434 /// # Examples
1435 ///
1436 /// ```
1437 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1438 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2, 1], vec![1, 2])?;
1439 /// let (shape, data) = tensor.into_vec_col_major();
1440 /// assert_eq!(shape, vec![2, 1]);
1441 /// assert_eq!(data, vec![1, 2]);
1442 /// # Ok::<(), tenferro_tensor::Error>(())
1443 /// ```
1444 pub fn into_vec_col_major(self) -> (Vec<usize>, Vec<T>) {
1445 let shape = self.shape().to_vec();
1446 (shape, self.into_host_vec())
1447 }
1448
1449 /// Make an explicit independent host copy with the same placement.
1450 ///
1451 /// # Examples
1452 /// ```
1453 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1454 /// let tensor = TypedTensor::<String, DynRank, Host>::from_host_vec_col_major(vec![1], vec!["a".into()])?;
1455 /// let copy = tensor.clone();
1456 /// assert_eq!(copy[&[0]], "a");
1457 /// # Ok::<(), tenferro_tensor::Error>(())
1458 /// ```
1459 pub fn duplicate(&self) -> Self
1460 where
1461 T: Clone,
1462 {
1463 let mut copy = typed_host_tensor_from_vec_col_major::<T, R>(
1464 self.shape.clone(),
1465 self.as_slice().to_vec(),
1466 "TypedTensor::duplicate",
1467 )
1468 .unwrap_or_else(|err| unreachable!("a validated host owner re-copies: {err}"));
1469 copy.placement = self.placement.clone();
1470 copy
1471 }
1472
1473 /// Move this host owner into the runtime union without copying or regrouping.
1474 ///
1475 /// # Examples
1476 /// ```
1477 /// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1478 /// let host = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7])?;
1479 /// let dynamic: TypedTensor<i32, DynRank, Dynamic> = host.into_dynamic();
1480 /// assert_eq!(dynamic.host_data()?, &[7]);
1481 /// # Ok::<(), tenferro_tensor::Error>(())
1482 /// ```
1483 pub fn into_dynamic(self) -> TypedTensor<T, R, Dynamic> {
1484 TypedTensor {
1485 shape: self.shape,
1486 placement: self.placement,
1487 storage: DynamicStorage::Host(self.storage),
1488 }
1489 }
1490
1491 /// Borrow this host owner as a typed view.
1492 ///
1493 /// # Examples
1494 ///
1495 /// ```
1496 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1497 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1498 /// let view = tensor.as_view();
1499 /// assert_eq!(view.shape(), &[2]);
1500 /// assert_eq!(view.as_host_slice(), &[1, 2]);
1501 /// # Ok::<(), tenferro_tensor::Error>(())
1502 /// ```
1503 pub fn as_view(&self) -> TypedTensorView<'_, T, R, Host> {
1504 TypedTensorView {
1505 buffer: TensorStorageRef::Host(self.as_slice()),
1506 root: None,
1507 layout: self.layout(),
1508 placement: self.placement.clone(),
1509 _representation: std::marker::PhantomData,
1510 }
1511 }
1512
1513 /// Exclusively borrow this host owner as a mutable typed view.
1514 ///
1515 /// # Examples
1516 ///
1517 /// ```
1518 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1519 /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1520 /// tensor.as_view_mut().as_host_slice_mut()[1] = 4;
1521 /// assert_eq!(tensor.as_slice(), &[1, 4]);
1522 /// # Ok::<(), tenferro_tensor::Error>(())
1523 /// ```
1524 pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R, Host> {
1525 let layout = self.layout();
1526 let placement = self.placement.clone();
1527 TypedTensorViewMut {
1528 buffer: TensorStorageRefMut::Host(self.storage.data.as_mut_slice()),
1529 root: None,
1530 layout,
1531 placement,
1532 _representation: std::marker::PhantomData,
1533 }
1534 }
1535}
1536
1537impl<T: Clone, R: TensorRank> Clone for TypedTensor<T, R, Host> {
1538 fn clone(&self) -> Self {
1539 self.duplicate()
1540 }
1541}
1542
1543impl<T, R: TensorRank> std::ops::Index<&[usize]> for TypedTensor<T, R, Host> {
1544 type Output = T;
1545
1546 /// # Panics
1547 ///
1548 /// Panics when the index has the wrong rank or is out of bounds, like any
1549 /// other slice indexing. Use [`TypedTensor::get`] for the checked form.
1550 fn index(&self, indices: &[usize]) -> &T {
1551 match self.get(indices) {
1552 Ok(value) => value,
1553 Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1554 }
1555 }
1556}
1557
1558impl<T, R: TensorRank> std::ops::IndexMut<&[usize]> for TypedTensor<T, R, Host> {
1559 /// # Panics
1560 ///
1561 /// Panics when the index has the wrong rank or is out of bounds, like any
1562 /// other slice indexing. Use [`TypedTensor::get_mut`] for the checked form.
1563 fn index_mut(&mut self, indices: &[usize]) -> &mut T {
1564 match self.get_mut(indices) {
1565 Ok(value) => value,
1566 Err(err) => panic!("TypedTensor host index {indices:?} is invalid: {err}"),
1567 }
1568 }
1569}
1570
1571/// Read mapping over a host slice, as one owning guard.
1572fn host_read_guard<T>(data: &[T]) -> HostReadGuard<'_, T> {
1573 HostReadGuard::new(data)
1574}
1575
1576/// Write mapping over an exclusive host slice, as one owning guard.
1577fn host_write_guard<T: Clone>(data: &mut [T]) -> HostWriteGuard<'_, T> {
1578 let len = data.len();
1579 HostWriteGuard::new(len, move |source| {
1580 data.clone_from_slice(source);
1581 Ok(())
1582 })
1583}
1584
1585/// Promote a plain host payload into a group-backed root without copying elements.
1586fn promote_host_group<T: TensorScalar, R: TensorRank>(
1587 shape: R::Shape,
1588 host: HostData<T>,
1589) -> crate::Result<OwnedTensorGroup<R>> {
1590 let recycler = host.recycler.clone();
1591 let mut group = OwnedTensorGroup::from_host_vec(shape, host.into_vec())?;
1592 if let Some(recycler) = recycler {
1593 group
1594 .group
1595 .set_host_recycler(group.allocation_index.index(), recycler)
1596 .map_err(|error| crate::Error::runtime_state_source("TypedTensor::promote", error))?;
1597 }
1598 Ok(group)
1599}
1600
1601impl<T, R: TensorRank> TypedTensor<T, R, Host> {
1602 /// Borrow the owned host elements through one owning read mapping.
1603 ///
1604 /// The guard retains the exclusive borrow of this owner, so no other access
1605 /// can overlap it while the mapping is alive.
1606 ///
1607 /// # Examples
1608 /// ```
1609 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1610 /// let tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1611 /// let guard = tensor.map_read();
1612 /// assert_eq!(&guard[..], &[1, 2]);
1613 /// # Ok::<(), tenferro_tensor::Error>(())
1614 /// ```
1615 pub fn map_read(&self) -> HostReadGuard<'_, T> {
1616 host_read_guard(self.as_slice())
1617 }
1618
1619 /// Exclusively borrow the owned host elements through one owning write mapping.
1620 ///
1621 /// Publish data with [`HostWriteGuard::copy_from_slice`]; the mapping is
1622 /// released when the guard is dropped.
1623 ///
1624 /// # Examples
1625 /// ```
1626 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1627 /// let mut tensor = TypedTensor::<i32, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1, 2])?;
1628 /// tensor.map_write().copy_from_slice(&[3, 4]).unwrap();
1629 /// assert_eq!(tensor.as_slice(), &[3, 4]);
1630 /// # Ok::<(), tenferro_tensor::Error>(())
1631 /// ```
1632 pub fn map_write(&mut self) -> HostWriteGuard<'_, T>
1633 where
1634 T: Clone,
1635 {
1636 host_write_guard(self.host_data_mut())
1637 }
1638}
1639
1640impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R, Host> {
1641 /// Promote this plain host owner into a group-backed owner.
1642 ///
1643 /// The payload is adopted as a provider root: no element is copied and a
1644 /// pooled return target survives the promotion.
1645 ///
1646 /// # Examples
1647 /// ```
1648 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1649 /// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?;
1650 /// let gpu = host.promote()?;
1651 /// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
1652 /// # Ok::<(), tenferro_tensor::Error>(())
1653 /// ```
1654 /// # Errors
1655 /// Returns [`crate::Error::RuntimeState`] when the new allocation group
1656 /// rejects the promoted root or its recycler.
1657 pub fn promote(self) -> crate::Result<TypedTensor<T, R, Gpu>> {
1658 let TypedTensor {
1659 shape,
1660 placement,
1661 storage,
1662 } = self;
1663 let group = promote_host_group(shape.clone(), storage.data)?;
1664 Ok(TypedTensor {
1665 shape,
1666 placement,
1667 storage: GroupStorage {
1668 group: Box::new(group),
1669 },
1670 })
1671 }
1672}
1673
1674impl<T, R: TensorRank> TypedTensor<T, R, Dynamic> {
1675 /// Borrow host elements through one owning read mapping.
1676 ///
1677 /// # Examples
1678 ///
1679 /// ```
1680 /// use tenferro_tensor::TypedTensor;
1681 /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
1682 /// let guard = tensor.map_read()?;
1683 /// assert_eq!(&guard[..], &[1, 2]);
1684 /// # Ok::<(), tenferro_tensor::Error>(())
1685 /// ```
1686 ///
1687 /// # Errors
1688 /// Returns [`crate::Error::HostAccess`] with
1689 /// [`HostAccessError::Unsupported`] when the group's allocation is not
1690 /// host-accessible.
1691 pub fn map_read(&self) -> crate::Result<HostReadGuard<'_, T>> {
1692 match &self.storage {
1693 DynamicStorage::Host(host) => Ok(host_read_guard(host.data.as_slice())),
1694 DynamicStorage::Group(core) => core.group.host_slice::<T>().map(host_read_guard),
1695 }
1696 }
1697
1698 /// Exclusively borrow host elements through one owning write mapping.
1699 ///
1700 /// Publish data with [`HostWriteGuard::copy_from_slice`].
1701 ///
1702 /// # Examples
1703 ///
1704 /// ```
1705 /// use tenferro_tensor::{Error, TypedTensor};
1706 /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
1707 /// tensor
1708 /// .map_write()?
1709 /// .copy_from_slice(&[3, 4])
1710 /// .map_err(|err| Error::host_access("map_write", err))?;
1711 /// assert_eq!(tensor.host_data()?, &[3, 4]);
1712 /// # Ok::<(), tenferro_tensor::Error>(())
1713 /// ```
1714 ///
1715 /// # Errors
1716 /// Returns [`crate::Error::HostAccess`] with
1717 /// [`HostAccessError::Unsupported`] when the group's allocation is not
1718 /// host-writable.
1719 pub fn map_write(&mut self) -> crate::Result<HostWriteGuard<'_, T>>
1720 where
1721 T: Clone,
1722 {
1723 match &mut self.storage {
1724 DynamicStorage::Host(host) => Ok(host_write_guard(host.data.as_mut_slice())),
1725 DynamicStorage::Group(core) => core.group.host_slice_mut::<T>().map(host_write_guard),
1726 }
1727 }
1728}
1729
1730impl<T, R: TensorRank> TypedTensor<T, R, Gpu> {
1731 /// Adopt a backend-owned buffer as a statically group-backed owner.
1732 ///
1733 /// A host buffer belongs to the [`Host`] representation; pass it to
1734 /// `TypedTensor::<T, R, Host>` instead of silently regrouping it here.
1735 ///
1736 /// # Examples
1737 /// ```
1738 /// use tenferro_tensor::{BackendStorageHandle, Placement, StorageBuffer, TypedTensor};
1739 /// let handle = BackendStorageHandle::<f32>::new_with_len(1, 2);
1740 /// let gpu = TypedTensor::<f32, tenferro_tensor::DynRank, tenferro_tensor::Gpu>::from_backend_buffer_col_major(
1741 /// vec![2],
1742 /// StorageBuffer::Backend(Box::new(handle)),
1743 /// Placement::default(),
1744 /// )?;
1745 /// assert!(gpu.is_backend_buffer());
1746 /// # Ok::<(), tenferro_tensor::Error>(())
1747 /// ```
1748 /// # Errors
1749 /// Returns [`crate::Error::Validation`] when the compact shape disagrees
1750 /// with the buffer length, [`crate::Error::RuntimeState`] for a host buffer,
1751 /// or a runtime-state error when the allocation group cannot be built.
1752 pub fn from_backend_buffer_col_major(
1753 shape: impl tenferro_tensor_core::IntoRankShape<R>,
1754 buffer: StorageBuffer<T>,
1755 placement: Placement,
1756 ) -> crate::Result<Self>
1757 where
1758 T: TensorScalar + Send + Sync + 'static,
1759 {
1760 let op = "TypedTensor::from_backend_buffer_col_major";
1761 let layout = try_compact_layout(shape, op)?;
1762 try_checked_shape_len(layout.shape(), buffer.len(), op)?;
1763 let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
1764 .map_err(|err| tensor_layout_error(op, err))?;
1765 match buffer {
1766 StorageBuffer::Host(_) => Err(crate::Error::runtime_state(
1767 op,
1768 "a host buffer is plain host storage; use TypedTensor::<T, R, Host>::from_vec_col_major",
1769 )),
1770 StorageBuffer::Backend(buffer) => {
1771 let group = OwnedTensorGroup::from_backend_buffer(
1772 group_shape.clone(),
1773 StorageBuffer::Backend(buffer),
1774 placement.clone(),
1775 )?;
1776 Ok(TypedTensor {
1777 shape: group_shape,
1778 placement,
1779 storage: GroupStorage {
1780 group: Box::new(group),
1781 },
1782 })
1783 }
1784 }
1785 }
1786
1787 /// Move this group-backed owner into the runtime union without rewriting it.
1788 ///
1789 /// # Examples
1790 ///
1791 /// ```
1792 /// use tenferro_tensor::{Dynamic, DynRank, Host, TypedTensor};
1793 /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7.0])?.promote()?;
1794 /// let dynamic: TypedTensor<f64, DynRank, Dynamic> = gpu.into_dynamic();
1795 /// assert_eq!(dynamic.host_data()?, &[7.0]);
1796 /// # Ok::<(), tenferro_tensor::Error>(())
1797 /// ```
1798 pub fn into_dynamic(self) -> TypedTensor<T, R, Dynamic> {
1799 TypedTensor {
1800 shape: self.shape,
1801 placement: self.placement,
1802 storage: DynamicStorage::Group(self.storage),
1803 }
1804 }
1805
1806 /// Whether this group's descriptor names a non-CPU provider.
1807 ///
1808 /// # Examples
1809 ///
1810 /// ```
1811 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1812 /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7.0])?.promote()?;
1813 /// // A promoted host payload stays on the CPU provider.
1814 /// assert!(!gpu.is_backend_buffer());
1815 /// # Ok::<(), tenferro_tensor::Error>(())
1816 /// ```
1817 pub fn is_backend_buffer(&self) -> bool {
1818 self.storage.group.is_backend_buffer()
1819 }
1820
1821 /// Borrow host elements when the group's allocation is host-accessible.
1822 ///
1823 /// # Examples
1824 ///
1825 /// ```
1826 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1827 /// let gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?.promote()?;
1828 /// assert_eq!(gpu.host_data()?, &[1.0, 2.0]);
1829 /// # Ok::<(), tenferro_tensor::Error>(())
1830 /// ```
1831 ///
1832 /// # Errors
1833 /// Returns [`crate::Error::RuntimeState`] for a device-only allocation.
1834 pub fn host_data(&self) -> crate::Result<&[T]> {
1835 self.storage.group.host_slice::<T>()
1836 }
1837
1838 /// Exclusively borrow host elements when the group's allocation is host-accessible.
1839 ///
1840 /// # Examples
1841 ///
1842 /// ```
1843 /// use tenferro_tensor::{DynRank, Host, TypedTensor};
1844 /// let mut gpu = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0, 2.0])?.promote()?;
1845 /// gpu.host_data_mut()?[0] = 3.0;
1846 /// assert_eq!(gpu.host_data()?, &[3.0, 2.0]);
1847 /// # Ok::<(), tenferro_tensor::Error>(())
1848 /// ```
1849 ///
1850 /// # Errors
1851 /// Returns [`crate::Error::RuntimeState`] for a device-only allocation.
1852 pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
1853 self.storage.group.host_slice_mut::<T>()
1854 }
1855
1856 /// Prepare this group-backed owner for one provider-native read binding.
1857 ///
1858 /// # Errors
1859 /// Returns a provider preparation error when the group has no device-read
1860 /// path for this layout.
1861 #[doc(hidden)]
1862 pub fn prepare_device_read(
1863 &self,
1864 op: &'static str,
1865 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
1866 where
1867 T: TensorScalar + 'static,
1868 {
1869 let layout = self.layout();
1870 self.storage
1871 .group
1872 .prepare_device_read_for_layout::<T>(&layout)
1873 .map_err(|error| crate::Error::runtime_state_source(op, error))
1874 }
1875
1876 /// Prepare this group-backed owner for one provider-native write binding.
1877 ///
1878 /// # Errors
1879 /// Returns a provider preparation error when the group has no device-write
1880 /// path for this layout.
1881 #[doc(hidden)]
1882 pub fn prepare_device_write(
1883 &mut self,
1884 op: &'static str,
1885 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
1886 where
1887 T: TensorScalar + 'static,
1888 {
1889 let layout = self.layout();
1890 self.storage
1891 .group
1892 .prepare_device_write_for_layout::<T>(&layout)
1893 .map_err(|error| crate::Error::runtime_state_source(op, error))
1894 }
1895}
1896
1897impl<T, R: TensorRank> TypedTensor<T, R, Dynamic> {
1898 /// Checked narrowing to the statically host-owned representation.
1899 ///
1900 /// Fails without consuming ownership of the source tensor.
1901 ///
1902 /// # Examples
1903 /// ```
1904 /// use tenferro_tensor::TypedTensor;
1905 /// let dynamic = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![7])?;
1906 /// let host = dynamic.into_host().unwrap();
1907 /// assert_eq!(host.as_slice(), &[7]);
1908 /// # Ok::<(), tenferro_tensor::Error>(())
1909 /// ```
1910 /// # Errors
1911 /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is
1912 /// group-backed rather than plain host storage.
1913 #[allow(clippy::result_large_err)]
1914 pub fn into_host(self) -> Result<TypedTensor<T, R, Host>, ReinterpretError<Self>> {
1915 let TypedTensor {
1916 shape,
1917 placement,
1918 storage,
1919 } = self;
1920 match storage {
1921 DynamicStorage::Host(host) => Ok(TypedTensor {
1922 shape,
1923 placement,
1924 storage: host,
1925 }),
1926 DynamicStorage::Group(group) => Err(ReinterpretError::new(
1927 TypedTensor {
1928 shape,
1929 placement,
1930 storage: DynamicStorage::Group(group),
1931 },
1932 crate::Error::runtime_state(
1933 "TypedTensor::into_host",
1934 "the tensor is group-backed rather than plain host storage",
1935 ),
1936 )),
1937 }
1938 }
1939
1940 /// Checked narrowing to the group-backed representation.
1941 ///
1942 /// Fails without consuming ownership of the source tensor.
1943 ///
1944 /// # Examples
1945 ///
1946 /// ```
1947 /// use tenferro_tensor::{DynRank, Gpu, Host, TypedTensor};
1948 /// let host = TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![2.0])?;
1949 /// let dynamic = host.promote()?.into_dynamic();
1950 /// let gpu: TypedTensor<f64, DynRank, Gpu> = dynamic.into_gpu().map_err(|f| f.into_parts().1)?;
1951 /// assert_eq!(gpu.host_data()?, &[2.0]);
1952 /// let plain = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![2.0])?;
1953 /// assert!(plain.into_gpu().is_err());
1954 /// # Ok::<(), tenferro_tensor::Error>(())
1955 /// ```
1956 ///
1957 /// # Errors
1958 /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is a
1959 /// plain host owner rather than a group-backed one.
1960 #[allow(clippy::result_large_err)]
1961 pub fn into_gpu(self) -> Result<TypedTensor<T, R, Gpu>, ReinterpretError<Self>> {
1962 let TypedTensor {
1963 shape,
1964 placement,
1965 storage,
1966 } = self;
1967 match storage {
1968 DynamicStorage::Group(group) => Ok(TypedTensor {
1969 shape,
1970 placement,
1971 storage: group,
1972 }),
1973 DynamicStorage::Host(host) => Err(ReinterpretError::new(
1974 TypedTensor {
1975 shape,
1976 placement,
1977 storage: DynamicStorage::Host(host),
1978 },
1979 crate::Error::runtime_state(
1980 "TypedTensor::into_gpu",
1981 "the tensor is a plain host owner rather than group-backed storage",
1982 ),
1983 )),
1984 }
1985 }
1986}
1987
1988impl<T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensor<T, R, D> {
1989 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1990 formatter
1991 .debug_struct("TypedTensor")
1992 .field("shape", &self.shape.as_ref())
1993 .field("placement", &self.placement)
1994 .finish_non_exhaustive()
1995 }
1996}
1997
1998impl<T, R: TensorRank, D: Representation> TypedTensor<T, R, D> {
1999 /// Number of elements in the tensor.
2000 ///
2001 /// # Examples
2002 ///
2003 /// ```rust
2004 /// use tenferro_tensor::TypedTensor;
2005 ///
2006 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2007 /// assert_eq!(t.n_elements(), 6);
2008 /// ```
2009 pub fn n_elements(&self) -> usize {
2010 // Invariant: owned tensor constructors validate compact shape length against buffer length.
2011 match try_shape_product(self.shape(), "TypedTensor::n_elements") {
2012 Ok(n) => n,
2013 Err(err) => {
2014 unreachable!("TypedTensor compact shape is validated at construction: {err}")
2015 }
2016 }
2017 }
2018
2019 /// Tensor shape.
2020 ///
2021 /// # Examples
2022 ///
2023 /// ```
2024 /// use tenferro_tensor::TypedTensor;
2025 ///
2026 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
2027 /// assert_eq!(t.shape(), &[2]);
2028 /// ```
2029 pub fn shape(&self) -> &[usize] {
2030 self.shape.as_ref()
2031 }
2032
2033 /// Tensor rank.
2034 ///
2035 /// # Examples
2036 ///
2037 /// ```
2038 /// use tenferro_tensor::TypedTensor;
2039 ///
2040 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2041 /// assert_eq!(t.rank(), 2);
2042 /// ```
2043 pub fn rank(&self) -> usize {
2044 self.shape().len()
2045 }
2046
2047 /// Tensor layout metadata.
2048 ///
2049 /// Owned typed tensors are always compact column-major layouts.
2050 ///
2051 /// # Examples
2052 ///
2053 /// ```
2054 /// use tenferro_tensor::TypedTensor;
2055 ///
2056 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
2057 /// assert_eq!(t.layout().strides(), &[1, 2]);
2058 /// ```
2059 pub fn layout(&self) -> TensorLayout<R> {
2060 TensorLayout::compact(self.shape.clone())
2061 .unwrap_or_else(|err| unreachable!("validated owned shape: {err}"))
2062 }
2063
2064 /// Return placement metadata for this tensor.
2065 ///
2066 /// # Examples
2067 ///
2068 /// ```
2069 /// use tenferro_tensor::{MemoryKind, TypedTensor};
2070 ///
2071 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
2072 /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
2073 /// ```
2074 pub fn placement(&self) -> &Placement {
2075 &self.placement
2076 }
2077
2078 /// Replace placement metadata without changing the storage buffer.
2079 ///
2080 /// # Examples
2081 ///
2082 /// ```
2083 /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
2084 ///
2085 /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
2086 /// t.set_placement(Placement {
2087 /// memory_kind: MemoryKind::PinnedHost,
2088 /// device: None,
2089 /// cpu_affinity: None,
2090 /// });
2091 /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
2092 /// ```
2093 pub fn set_placement(&mut self, placement: Placement) {
2094 self.placement = placement;
2095 }
2096
2097 /// Replace only CPU routing/locality metadata without changing storage.
2098 ///
2099 /// Device, memory kind, backend allocation domain, and allocation identity
2100 /// remain unchanged.
2101 ///
2102 /// # Examples
2103 ///
2104 /// ```rust
2105 /// use tenferro_tensor::{CpuDomainId, TypedTensor};
2106 ///
2107 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0])?;
2108 /// tensor.set_cpu_affinity(Some(CpuDomainId::new(4)));
2109 /// assert_eq!(tensor.placement().cpu_affinity, Some(CpuDomainId::new(4)));
2110 /// # Ok::<(), tenferro_tensor::Error>(())
2111 /// ```
2112 pub fn set_cpu_affinity(&mut self, cpu_affinity: Option<CpuDomainId>) {
2113 self.placement.cpu_affinity = cpu_affinity;
2114 }
2115
2116 /// Compute the checked compact column-major offset of an element.
2117 ///
2118 /// # Examples
2119 /// ```
2120 /// use tenferro_tensor::TypedTensor;
2121 /// let tensor = TypedTensor::<String>::from_vec_col_major([2, 3], vec![String::new(); 6])?;
2122 /// assert_eq!(tensor.linear_offset(&[1, 2])?, 5);
2123 /// # Ok::<(), tenferro_tensor::Error>(())
2124 /// ```
2125 /// # Errors
2126 /// Wrong rank, out-of-range coordinates or arithmetic overflow return
2127 /// [`crate::Error::Validation`].
2128 pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2129 try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
2130 }
2131
2132 /// Consume this tensor and return its layout metadata.
2133 ///
2134 /// # Examples
2135 ///
2136 /// ```
2137 /// use tenferro_tensor::TypedTensor;
2138 ///
2139 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
2140 /// assert!(t.into_layout().is_compact_col_major().unwrap());
2141 /// ```
2142 pub fn into_layout(self) -> TensorLayout<R> {
2143 self.layout()
2144 }
2145
2146 /// Compute the physical element offset for a logical index.
2147 ///
2148 /// # Examples
2149 ///
2150 /// ```rust
2151 /// use tenferro_tensor::TypedTensor;
2152 ///
2153 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
2154 /// assert_eq!(t.layout_linear_offset(&[1, 2])?, 5);
2155 /// # Ok::<(), tenferro_tensor::Error>(())
2156 /// ```
2157 /// # Errors
2158 ///
2159 /// Returns [`crate::Error::Validation`] with
2160 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
2161 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
2162 /// when an index is outside its axis extent, or
2163 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
2164 /// arithmetic overflows.
2165 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
2166 try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::layout_linear_offset")
2167 }
2168
2169 /// Return whether this owned tensor is compact column-major.
2170 ///
2171 /// # Examples
2172 ///
2173 /// ```rust
2174 /// use tenferro_tensor::TypedTensor;
2175 ///
2176 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2177 /// assert!(t.is_col_major_contiguous()?);
2178 /// # Ok::<(), tenferro_tensor::Error>(())
2179 /// ```
2180 /// # Errors
2181 ///
2182 /// Returns [`crate::Error::Validation`] with
2183 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
2184 /// compactness arithmetic overflows.
2185 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
2186 self.layout()
2187 .is_compact_col_major()
2188 .map_err(|err| tensor_layout_error("TypedTensor::is_col_major_contiguous", err))
2189 }
2190
2191 /// Return a compact string summary of this tensor's layout metadata.
2192 ///
2193 /// # Examples
2194 ///
2195 /// ```rust
2196 /// use tenferro_tensor::TypedTensor;
2197 ///
2198 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2199 /// assert!(t.layout_summary().contains("shape=[2]"));
2200 /// # Ok::<(), tenferro_tensor::Error>(())
2201 /// ```
2202 pub fn layout_summary(&self) -> String {
2203 let layout = self.layout();
2204 layout_summary(self.shape(), layout.strides(), layout.offset())
2205 }
2206
2207 /// Assert this tensor is compact column-major.
2208 ///
2209 /// # Examples
2210 ///
2211 /// ```rust
2212 /// use tenferro_tensor::TypedTensor;
2213 ///
2214 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
2215 /// t.assert_col_major_contiguous()?;
2216 /// # Ok::<(), tenferro_tensor::Error>(())
2217 /// ```
2218 /// # Errors
2219 ///
2220 /// Returns [`crate::Error::Validation`] with
2221 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
2222 /// compactness arithmetic overflows, or
2223 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
2224 /// tensor is not compact column-major.
2225 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
2226 let layout = self.layout();
2227 assert_layout_col_major_contiguous(
2228 self.is_col_major_contiguous()?,
2229 self.shape(),
2230 layout.strides(),
2231 layout.offset(),
2232 "TypedTensor::assert_col_major_contiguous",
2233 )
2234 }
2235}
2236
2237struct HostData<T> {
2238 buffer: StorageBuffer<T>,
2239 recycler: Option<std::sync::Weak<dyn crate::HostBufferRecycler<T>>>,
2240}
2241
2242impl<T> std::fmt::Debug for HostData<T> {
2243 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2244 formatter
2245 .debug_struct("HostData")
2246 .field("pooled", &self.recycler.is_some())
2247 .finish_non_exhaustive()
2248 }
2249}
2250
2251impl<T> HostData<T> {
2252 fn new(data: Vec<T>) -> Self {
2253 Self {
2254 buffer: StorageBuffer::Host(data),
2255 recycler: None,
2256 }
2257 }
2258
2259 fn into_vec(mut self) -> Vec<T> {
2260 self.recycler = None;
2261 match std::mem::replace(&mut self.buffer, StorageBuffer::Host(Vec::new())) {
2262 StorageBuffer::Host(data) => data,
2263 StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2264 }
2265 }
2266
2267 fn as_slice(&self) -> &[T] {
2268 match &self.buffer {
2269 StorageBuffer::Host(data) => data,
2270 StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2271 }
2272 }
2273
2274 fn as_mut_slice(&mut self) -> &mut [T] {
2275 match &mut self.buffer {
2276 StorageBuffer::Host(data) => data,
2277 StorageBuffer::Backend(_) => unreachable!("plain host storage is host-owned"),
2278 }
2279 }
2280}
2281
2282impl<T> Drop for HostData<T> {
2283 fn drop(&mut self) {
2284 let Some(recycler) = self.recycler.take().and_then(|weak| weak.upgrade()) else {
2285 return;
2286 };
2287 if let StorageBuffer::Host(data) = &mut self.buffer {
2288 recycler.recycle(std::mem::take(data));
2289 }
2290 }
2291}
2292
2293/// The sole owner handle for host tensors. The allocation group owns the
2294/// provider root; the descriptor slot carries only the logical view metadata.
2295pub(crate) struct OwnedTensorGroup<R: TensorRank> {
2296 group: AllocationGroup,
2297 slot: DescriptorSlot,
2298 allocation_index: AllocationSlot,
2299 // INVARIANT: this non-owning address points into the group root, whose host
2300 // vector cannot resize while the owning tensor is borrowed. It is stored as
2301 // `NonZeroUsize` rather than `NonNull<u8>`: the niche keeps the field at one
2302 // word, and unlike `NonNull` it preserves the `Send`/`Sync` auto traits the
2303 // typed tensor contract requires.
2304 host_ptr: Option<NonZeroUsize>,
2305 host_byte_len: usize,
2306 _rank: PhantomData<R>,
2307}
2308
2309impl<R: TensorRank> Debug for OwnedTensorGroup<R> {
2310 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2311 formatter
2312 .debug_struct("OwnedTensorGroup")
2313 .field("slot", &self.slot)
2314 .finish_non_exhaustive()
2315 }
2316}
2317
2318impl<R: TensorRank> OwnedTensorGroup<R> {
2319 fn from_host_vec<T: TensorScalar>(shape: R::Shape, data: Vec<T>) -> crate::Result<Self> {
2320 let (group, slot) = AllocationGroup::from_host_vec::<T, R>(shape, data)
2321 .map_err(|error| group_error("TypedTensor::from_host_vec", error))?;
2322 let allocation_index = group
2323 .allocation_index(slot)
2324 .map_err(|error| group_error("TypedTensor::from_host_vec", error))?;
2325 let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
2326 Ok(Self {
2327 group,
2328 slot,
2329 allocation_index,
2330 host_ptr,
2331 host_byte_len,
2332 _rank: PhantomData,
2333 })
2334 }
2335
2336 fn from_backend_buffer<T: TensorScalar + Send + Sync + 'static>(
2337 shape: R::Shape,
2338 buffer: StorageBuffer<T>,
2339 placement: Placement,
2340 ) -> crate::Result<Self> {
2341 let (mut group, slot) = AllocationGroup::from_backend_buffer::<T, R>(shape, buffer)
2342 .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2343 group
2344 .set_descriptor_placement(slot, placement)
2345 .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2346 let allocation_index = group
2347 .allocation_index(slot)
2348 .map_err(|error| group_error("TypedTensor::from_backend_buffer", error))?;
2349 let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
2350 Ok(Self {
2351 group,
2352 slot,
2353 allocation_index,
2354 host_ptr,
2355 host_byte_len,
2356 _rank: PhantomData,
2357 })
2358 }
2359
2360 fn view<T: TensorScalar>(&self) -> crate::Result<GroupReadView<'_, T, R>> {
2361 self.group
2362 .view(self.slot)
2363 .map_err(|error| group_error("TypedTensor::group_view", error))
2364 }
2365
2366 fn view_dyn<T: TensorScalar>(&self) -> crate::Result<GroupReadView<'_, T, DynRank>> {
2367 self.group
2368 .view(self.slot)
2369 .map_err(|error| group_error("TypedTensor::group_view", error))
2370 }
2371
2372 fn view_mut<T: TensorScalar>(&mut self) -> crate::Result<GroupWriteView<'_, T, R>> {
2373 self.group
2374 .view_mut(self.slot)
2375 .map_err(|error| group_error("TypedTensor::group_view_mut", error))
2376 }
2377
2378 fn view_mut_dyn<T: TensorScalar>(&mut self) -> crate::Result<GroupWriteView<'_, T, DynRank>> {
2379 self.group
2380 .view_mut(self.slot)
2381 .map_err(|error| group_error("TypedTensor::group_view_mut", error))
2382 }
2383
2384 fn prepare_device_read_for_layout<T: TensorScalar>(
2385 &self,
2386 layout: &TensorLayout<R>,
2387 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>> {
2388 self.group
2389 .prepare_device_read_for_layout::<T, R>(self.slot, layout)
2390 .map_err(|error| {
2391 crate::Error::runtime_state_source("TypedTensor::prepare_device_read", error)
2392 })
2393 }
2394
2395 fn prepare_device_write_for_layout<T: TensorScalar>(
2396 &mut self,
2397 layout: &TensorLayout<R>,
2398 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>> {
2399 self.group
2400 .prepare_device_write_for_layout::<T, R>(self.slot, layout)
2401 .map_err(|error| {
2402 crate::Error::runtime_state_source("TypedTensor::prepare_device_write", error)
2403 })
2404 }
2405
2406 fn host_buffer<T: 'static>(&self) -> Option<&StorageBuffer<T>> {
2407 self.group.host_buffer_at::<T>(self.allocation_index)
2408 }
2409
2410 fn host_slice<T>(&self) -> crate::Result<&[T]> {
2411 let Some(pointer) = self.host_ptr else {
2412 return Err(crate::Error::runtime_state(
2413 "TypedTensor::host_data",
2414 "backend storage cannot be borrowed as host data; download explicitly first",
2415 ));
2416 };
2417 let element_size = size_of::<T>();
2418 let Some(element_count) = self.host_byte_len.checked_div(element_size) else {
2419 return Err(crate::Error::runtime_state(
2420 "TypedTensor::host_data",
2421 "host allocation byte length is not aligned to the requested dtype",
2422 ));
2423 };
2424 // SAFETY: the pointer and byte length were captured from the unique
2425 // root's full host allocation; the root cannot resize while borrowed.
2426 Ok(unsafe { std::slice::from_raw_parts(pointer.get() as *const T, element_count) })
2427 }
2428
2429 fn host_slice_mut<T>(&mut self) -> crate::Result<&mut [T]> {
2430 let Some(pointer) = self.host_ptr else {
2431 return Err(crate::Error::runtime_state(
2432 "TypedTensor::host_data_mut",
2433 "backend storage cannot be borrowed as host data; download explicitly first",
2434 ));
2435 };
2436 let element_size = size_of::<T>();
2437 let Some(element_count) = self.host_byte_len.checked_div(element_size) else {
2438 return Err(crate::Error::runtime_state(
2439 "TypedTensor::host_data_mut",
2440 "host allocation byte length is not aligned to the requested dtype",
2441 ));
2442 };
2443 // SAFETY: the pointer and byte length were captured from the unique
2444 // root; this method has the only mutable borrow of that root.
2445 Ok(unsafe { std::slice::from_raw_parts_mut(pointer.get() as *mut T, element_count) })
2446 }
2447
2448 fn backend_buffer<T: 'static>(&self) -> Option<&StorageBuffer<T>> {
2449 self.group.backend_buffer::<T>(self.slot)
2450 }
2451
2452 fn backend_buffer_mut<T: 'static>(&mut self) -> Option<&mut StorageBuffer<T>> {
2453 self.group.backend_buffer_mut::<T>(self.slot)
2454 }
2455
2456 // INVARIANT: the failure carrier must return the unchanged group owner, so
2457 // the wide `(Self, Error)` pair is deliberate rather than a boxing bug.
2458 #[allow(clippy::result_large_err)]
2459 fn into_host_vec<T: 'static>(self) -> std::result::Result<Vec<T>, (Self, crate::Error)> {
2460 if self.backend_buffer::<T>().is_some() {
2461 return Err((
2462 self,
2463 crate::Error::runtime_state(
2464 "TypedTensor::into_host_vec",
2465 "backend buffers cannot be exported as host Vec; download the tensor first",
2466 ),
2467 ));
2468 }
2469 let OwnedTensorGroup {
2470 group,
2471 slot,
2472 allocation_index,
2473 host_ptr,
2474 host_byte_len,
2475 ..
2476 } = self;
2477 match group.into_host_vec::<T>(slot) {
2478 Ok(data) => Ok(data),
2479 Err((group, error)) => Err((
2480 OwnedTensorGroup {
2481 group,
2482 slot,
2483 allocation_index,
2484 host_ptr,
2485 host_byte_len,
2486 _rank: PhantomData,
2487 },
2488 crate::Error::runtime_state("TypedTensor::into_host_vec", error),
2489 )),
2490 }
2491 }
2492
2493 fn into_parts(self) -> (AllocationGroup, DescriptorSlot) {
2494 (self.group, self.slot)
2495 }
2496
2497 #[allow(clippy::result_large_err)]
2498 fn reinterpret<T: TensorScalar, U: TensorScalar>(
2499 self,
2500 shape: Vec<usize>,
2501 strides: Vec<isize>,
2502 offset: isize,
2503 ) -> Result<OwnedTensorGroup<DynRank>, (Self, crate::Error)> {
2504 let OwnedTensorGroup {
2505 group,
2506 slot,
2507 allocation_index,
2508 host_ptr,
2509 host_byte_len,
2510 _rank: _,
2511 } = self;
2512 match group.reinterpret_descriptor::<T, U>(slot, shape, strides, offset) {
2513 Ok(group) => Ok(OwnedTensorGroup {
2514 group,
2515 slot,
2516 allocation_index,
2517 host_ptr,
2518 host_byte_len,
2519 _rank: PhantomData,
2520 }),
2521 Err((group, error)) => Err((
2522 OwnedTensorGroup {
2523 group,
2524 slot,
2525 allocation_index,
2526 host_ptr,
2527 host_byte_len,
2528 _rank: PhantomData,
2529 },
2530 group_error("TypedTensor::reinterpret", error),
2531 )),
2532 }
2533 }
2534}
2535
2536fn host_metadata<T: 'static>(
2537 group: &AllocationGroup,
2538 slot: DescriptorSlot,
2539) -> (Option<NonZeroUsize>, usize) {
2540 group
2541 .host_root_metadata::<T>(slot)
2542 .map_or((None, 0), |(pointer, byte_len)| {
2543 // INVARIANT: `host_root_metadata` reports the base address of the group's live
2544 // host allocation, which is non-null for any slice, empty included.
2545 (NonZeroUsize::new(pointer), byte_len)
2546 })
2547}
2548
2549fn group_error(op: &'static str, error: GroupError) -> crate::Error {
2550 crate::Error::runtime_state(op, error.to_string())
2551}
2552
2553/// Borrowed tensor buffer reference used by read-only typed views.
2554///
2555/// # Examples
2556///
2557/// ```rust
2558/// use tenferro_tensor::TensorStorageRef;
2559///
2560/// let data = [1_i32, 2];
2561/// let buffer = TensorStorageRef::Host(&data);
2562/// assert_eq!(buffer.len(), 2);
2563/// ```
2564#[derive(Debug)]
2565pub enum TensorStorageRef<'a, T> {
2566 Host(&'a [T]),
2567 Backend(&'a dyn BackendStorage<T>),
2568 #[doc(hidden)]
2569 Root(&'a dyn BackendAllocation),
2570}
2571
2572impl<T> Clone for TensorStorageRef<'_, T> {
2573 fn clone(&self) -> Self {
2574 match self {
2575 Self::Host(data) => Self::Host(data),
2576 Self::Backend(buffer) => Self::Backend(*buffer),
2577 Self::Root(allocation) => Self::Root(*allocation),
2578 }
2579 }
2580}
2581
2582impl<T: 'static> TensorStorageRef<'_, T> {
2583 /// Return the logical length of the backing allocation.
2584 ///
2585 /// # Examples
2586 ///
2587 /// ```rust
2588 /// use tenferro_tensor::TensorStorageRef;
2589 ///
2590 /// let data = [1_i32, 2, 3];
2591 /// assert_eq!(TensorStorageRef::Host(&data).len(), 3);
2592 /// ```
2593 pub fn len(&self) -> usize {
2594 match self {
2595 Self::Host(data) => data.len(),
2596 Self::Backend(buffer) => buffer.len(),
2597 Self::Root(allocation) => allocation
2598 .root_extent()
2599 .byte_len()
2600 .checked_div(std::mem::size_of::<T>())
2601 .unwrap_or(0),
2602 }
2603 }
2604
2605 /// Return whether the backing allocation is empty.
2606 ///
2607 /// # Examples
2608 ///
2609 /// ```rust
2610 /// use tenferro_tensor::TensorStorageRef;
2611 ///
2612 /// let data: [f64; 0] = [];
2613 /// assert!(TensorStorageRef::Host(&data).is_empty());
2614 /// ```
2615 pub fn is_empty(&self) -> bool {
2616 self.len() == 0
2617 }
2618}
2619
2620/// Borrowed tensor buffer reference used by mutable typed views.
2621///
2622/// Backend buffers can be represented for residency metadata, but this crate
2623/// does not expose host mutation for backend-native allocations.
2624///
2625/// # Examples
2626///
2627/// ```rust
2628/// use tenferro_tensor::TensorStorageRefMut;
2629///
2630/// let mut data = [1_i32, 2];
2631/// let buffer = TensorStorageRefMut::Host(&mut data);
2632/// assert_eq!(buffer.len(), 2);
2633/// ```
2634#[derive(Debug)]
2635pub enum TensorStorageRefMut<'a, T> {
2636 Host(&'a mut [T]),
2637 Backend(&'a mut dyn BackendStorage<T>),
2638}
2639
2640impl<T: 'static> TensorStorageRefMut<'_, T> {
2641 /// Return the logical length of the backing allocation.
2642 ///
2643 /// # Examples
2644 ///
2645 /// ```rust
2646 /// use tenferro_tensor::TensorStorageRefMut;
2647 ///
2648 /// let mut data = [1_i32, 2, 3];
2649 /// assert_eq!(TensorStorageRefMut::Host(&mut data).len(), 3);
2650 /// ```
2651 pub fn len(&self) -> usize {
2652 match self {
2653 Self::Host(data) => data.len(),
2654 Self::Backend(buffer) => buffer.len(),
2655 }
2656 }
2657
2658 /// Return whether the backing allocation is empty.
2659 ///
2660 /// # Examples
2661 ///
2662 /// ```rust
2663 /// use tenferro_tensor::TensorStorageRefMut;
2664 ///
2665 /// let mut data: [f64; 0] = [];
2666 /// assert!(TensorStorageRefMut::Host(&mut data).is_empty());
2667 /// ```
2668 pub fn is_empty(&self) -> bool {
2669 self.len() == 0
2670 }
2671}
2672
2673/// Read-only borrowed view of typed tensor storage with arbitrary strides.
2674///
2675/// `TypedTensorView` is the typed representation for layout-only tensor
2676/// transformations. It borrows an existing host or backend allocation and
2677/// carries a logical shape, strides, and an offset. Slicing, reshaping when
2678/// stride-compatible, and [`transpose_view`](TypedTensorView::transpose_view)
2679/// update only metadata and do not copy storage.
2680///
2681/// Materialize through [`TensorStructural::to_contiguous_read`](crate::TensorStructural::to_contiguous_read)
2682/// on the active backend session when a compact owned [`TypedTensor`] is
2683/// required. Use [`TypedTensorView::as_slice`] only when the current view is
2684/// contiguous in the requested layout.
2685///
2686/// # Examples
2687///
2688/// ```rust
2689/// use tenferro_tensor::{Rank, TypedTensorView};
2690///
2691/// let data = [1_i32, 2, 3, 4];
2692/// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
2693/// assert_eq!(view.get(&[1, 1]), Some(&4));
2694/// # Ok::<(), tenferro_tensor::Error>(())
2695/// ```
2696/// Borrowed typed view of one tensor representation.
2697///
2698/// The buffer stays concrete on purpose: a generic-associated buffer would make
2699/// the projection invariant in `'a`, and the borrowed-read surface relies on
2700/// `TensorRead<'long>` shortening to `TensorRead<'short>`. `D` therefore marks
2701/// which representation produced the view - `Host` is only ever built from host
2702/// slices, with no retained region.
2703pub struct TypedTensorView<'a, T, R: TensorRank = DynRank, D: Representation = Dynamic> {
2704 buffer: TensorStorageRef<'a, T>,
2705 root: Option<GroupReadView<'a, T, R>>,
2706 layout: TensorLayout<R>,
2707 placement: Placement,
2708 _representation: std::marker::PhantomData<D>,
2709}
2710
2711impl<'a, T, R: TensorRank, D: Representation> Clone for TypedTensorView<'a, T, R, D> {
2712 fn clone(&self) -> Self {
2713 Self {
2714 buffer: self.buffer.clone(),
2715 root: self.root.clone(),
2716 layout: self.layout.clone(),
2717 placement: self.placement.clone(),
2718 _representation: std::marker::PhantomData,
2719 }
2720 }
2721}
2722
2723impl<'a, T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensorView<'a, T, R, D> {
2724 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2725 formatter
2726 .debug_struct("TypedTensorView")
2727 .field("shape", &self.layout.shape())
2728 .field("placement", &self.placement)
2729 .finish_non_exhaustive()
2730 }
2731}
2732
2733impl<'a, T: 'static> TypedTensorView<'a, T, DynRank> {
2734 /// Create a borrowed dynamic-rank view over compact column-major host data.
2735 ///
2736 /// # Examples
2737 ///
2738 /// ```rust
2739 /// use tenferro_tensor::TypedTensorView;
2740 ///
2741 /// let data = [1_i32, 2, 3, 4];
2742 /// let view = TypedTensorView::from_col_major(&[2, 2], &data)?;
2743 /// assert_eq!(view.strides(), &[1, 2]);
2744 /// # Ok::<(), tenferro_tensor::Error>(())
2745 /// ```
2746 ///
2747 /// # Errors
2748 ///
2749 /// Returns [`crate::Error::Validation`] with
2750 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when compact
2751 /// strides or reachable bounds overflow, or
2752 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2753 /// requested shape reaches beyond `data`.
2754 pub fn from_col_major(shape: &[usize], data: &'a [T]) -> crate::Result<Self> {
2755 let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
2756 .map_err(|err| tensor_layout_error("TypedTensorView::from_col_major", err))?;
2757 Self::from_buffer_ref(
2758 shape_vec(layout.shape()),
2759 stride_vec(layout.strides()),
2760 layout.offset(),
2761 TensorStorageRef::Host(data),
2762 default_placement(),
2763 "TypedTensorView::from_col_major",
2764 )
2765 }
2766
2767 /// Create a borrowed host view from explicit layout metadata.
2768 ///
2769 /// # Examples
2770 ///
2771 /// ```rust
2772 /// use tenferro_tensor::TypedTensorView;
2773 ///
2774 /// let data = [1_i32, 2, 3];
2775 /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
2776 /// assert_eq!(view.get(&[2]), Some(&1));
2777 /// # Ok::<(), tenferro_tensor::Error>(())
2778 /// ```
2779 ///
2780 /// # Errors
2781 ///
2782 /// Returns [`crate::Error::Validation`] with
2783 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
2784 /// `strides` have different ranks,
2785 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2786 /// reachable layout exceeds `data`, or
2787 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
2788 /// arithmetic overflows.
2789 pub fn from_slice(
2790 shape: impl AsRef<[usize]>,
2791 strides: impl AsRef<[isize]>,
2792 offset: isize,
2793 data: &'a [T],
2794 ) -> crate::Result<Self> {
2795 Self::from_buffer_ref(
2796 shape_vec(shape.as_ref()),
2797 stride_vec(strides.as_ref()),
2798 offset,
2799 TensorStorageRef::Host(data),
2800 default_placement(),
2801 "TypedTensorView::from_slice",
2802 )
2803 }
2804}
2805
2806impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
2807 /// Create a rank-generic borrowed host view from explicit layout metadata.
2808 ///
2809 /// # Examples
2810 ///
2811 /// ```rust
2812 /// use tenferro_tensor::{Rank, TypedTensorView};
2813 ///
2814 /// let data = [1_i32, 2, 3, 4];
2815 /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
2816 /// assert_eq!(view.get(&[1, 1]), Some(&4));
2817 /// # Ok::<(), tenferro_tensor::Error>(())
2818 /// ```
2819 ///
2820 /// # Errors
2821 ///
2822 /// Returns [`crate::Error::Validation`] with
2823 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
2824 /// rank does not match `shape` or `strides`,
2825 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2826 /// reachable layout exceeds `data`, or
2827 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
2828 /// arithmetic overflows.
2829 pub fn from_slice_ranked(
2830 shape: impl Into<R::Shape>,
2831 strides: impl Into<R::Strides>,
2832 offset: isize,
2833 data: &'a [T],
2834 ) -> crate::Result<Self> {
2835 Self::from_buffer_ref(
2836 shape,
2837 strides,
2838 offset,
2839 TensorStorageRef::Host(data),
2840 default_placement(),
2841 "TypedTensorView::from_slice_ranked",
2842 )
2843 }
2844}
2845
2846impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
2847 fn from_buffer_ref(
2848 shape: impl Into<R::Shape>,
2849 strides: impl Into<R::Strides>,
2850 offset: isize,
2851 buffer: TensorStorageRef<'a, T>,
2852 placement: Placement,
2853 op: &'static str,
2854 ) -> crate::Result<Self> {
2855 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
2856 .map_err(|err| tensor_layout_error(op, err))?;
2857 Ok(Self {
2858 buffer,
2859 root: None,
2860 layout,
2861 placement,
2862 _representation: std::marker::PhantomData,
2863 })
2864 }
2865}
2866
2867impl<'a, T: 'static, R: TensorRank> TypedTensorView<'a, T, R, Host> {
2868 /// Create a representation-marked view over an explicit host layout.
2869 ///
2870 /// A `Host`-marked view is only ever built from a host slice, so it carries
2871 /// no retained region and its host slice needs no runtime check.
2872 ///
2873 /// # Examples
2874 ///
2875 /// ```rust
2876 /// use tenferro_tensor::{DynRank, Host, TypedTensorView};
2877 ///
2878 /// let data = [1_i32, 2, 3, 4];
2879 /// let view: TypedTensorView<'_, i32, DynRank, Host> =
2880 /// TypedTensorView::from_host_slice(vec![2, 2], vec![1, 2], 0, &data)?;
2881 /// assert_eq!(view.as_host_slice(), &[1, 2, 3, 4]);
2882 /// # Ok::<(), tenferro_tensor::Error>(())
2883 /// ```
2884 ///
2885 /// # Errors
2886 ///
2887 /// Returns [`crate::Error::Validation`] with
2888 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `strides`
2889 /// has a different rank, or
2890 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] /
2891 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
2892 /// reachable layout leaves `data` or overflows.
2893 pub fn from_host_slice(
2894 shape: impl Into<R::Shape>,
2895 strides: impl Into<R::Strides>,
2896 offset: isize,
2897 data: &'a [T],
2898 ) -> crate::Result<Self> {
2899 // Built here rather than through the shared helper so that the Host
2900 // marker can only ever be paired with a host slice.
2901 let buffer = TensorStorageRef::Host(data);
2902 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
2903 .map_err(|err| tensor_layout_error("TypedTensorView::from_host_slice", err))?;
2904 Ok(Self {
2905 buffer,
2906 root: None,
2907 layout,
2908 placement: default_placement(),
2909 _representation: std::marker::PhantomData,
2910 })
2911 }
2912
2913 /// Borrow the host elements without a runtime representation check.
2914 ///
2915 /// # Examples
2916 ///
2917 /// ```rust
2918 /// use tenferro_tensor::{DynRank, Host, TypedTensorView};
2919 ///
2920 /// let data = [1_i32, 2];
2921 /// let view: TypedTensorView<'_, i32, DynRank, Host> =
2922 /// TypedTensorView::from_host_slice(vec![2], vec![1], 0, &data)?;
2923 /// assert_eq!(view.as_host_slice(), &[1, 2]);
2924 /// # Ok::<(), tenferro_tensor::Error>(())
2925 /// ```
2926 ///
2927 /// # Panics
2928 ///
2929 /// Panics only if a `Host`-marked view was built from non-host storage,
2930 /// which no constructor in this crate does.
2931 pub fn as_host_slice(&self) -> &'a [T] {
2932 match &self.buffer {
2933 TensorStorageRef::Host(data) => data,
2934 // INVARIANT: `Host`-marked views are constructed only by
2935 // `from_host_slice` and by `TypedTensor<_, _, Host>::as_view`.
2936 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
2937 unreachable!("a Host-marked view always borrows host storage")
2938 }
2939 }
2940 }
2941}
2942
2943impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorView<'a, T, R, D> {
2944 /// Erase a borrowed view's rank and dtype for session dispatch without
2945 /// allocating tensor storage or promoting it into an allocation group.
2946 /// Common ranks use the existing inline shape and stride metadata.
2947 ///
2948 /// # Examples
2949 ///
2950 /// ```rust
2951 /// use tenferro_tensor::{Rank, TypedTensorView};
2952 /// let data = [1.0_f64, 2.0];
2953 /// let view = TypedTensorView::<_, Rank<1>>::from_slice_ranked([2], [1], 0, &data)?;
2954 /// let read = view.into_tensor_read()?;
2955 /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
2956 /// # Ok::<(), tenferro_tensor::Error>(())
2957 /// ```
2958 /// # Errors
2959 /// Returns [`crate::Error::Validation`] with
2960 /// [`ValidationError::IntegerOverflow`] for unrepresentable metadata or
2961 /// [`ValidationError::ViewOutOfBounds`] if the layout leaves its allocation.
2962 pub fn into_tensor_read(self) -> crate::Result<TensorRead<'a>>
2963 where
2964 T: TensorScalar,
2965 {
2966 let layout = TensorLayout::<DynRank>::from_parts(
2967 shape_vec(self.layout.shape()),
2968 stride_vec(self.layout.strides()),
2969 self.layout.offset(),
2970 self.buffer.len(),
2971 )
2972 .map_err(|err| tensor_layout_error("TypedTensorView::into_tensor_read", err))?;
2973 let view = TypedTensorView {
2974 buffer: self.buffer,
2975 root: self.root.map(GroupReadView::into_dyn),
2976 layout,
2977 placement: self.placement,
2978 _representation: std::marker::PhantomData,
2979 };
2980 Ok(TensorRead::from_view(T::tensor_view(view)))
2981 }
2982
2983 /// Return the logical shape.
2984 ///
2985 /// # Examples
2986 ///
2987 /// ```rust
2988 /// use tenferro_tensor::TypedTensorView;
2989 ///
2990 /// let data = [0_i32; 2];
2991 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
2992 /// assert_eq!(view.shape(), &[2]);
2993 /// # Ok::<(), tenferro_tensor::Error>(())
2994 /// ```
2995 pub fn shape(&self) -> &[usize] {
2996 self.layout.shape()
2997 }
2998
2999 /// Return the logical rank carried by this view.
3000 ///
3001 /// # Examples
3002 ///
3003 /// ```
3004 /// use tenferro_tensor::TypedTensorView;
3005 ///
3006 /// let data = [0_i32; 6];
3007 /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
3008 /// assert_eq!(view.rank(), 2);
3009 /// # Ok::<(), tenferro_tensor::Error>(())
3010 /// ```
3011 pub fn rank(&self) -> usize {
3012 self.shape().len()
3013 }
3014
3015 /// Return strides in element units.
3016 ///
3017 /// # Examples
3018 ///
3019 /// ```rust
3020 /// use tenferro_tensor::TypedTensorView;
3021 ///
3022 /// let data = [0_i32; 2];
3023 /// let view = TypedTensorView::from_slice(vec![2], vec![-1], 1, &data)?;
3024 /// assert_eq!(view.strides(), &[-1]);
3025 /// # Ok::<(), tenferro_tensor::Error>(())
3026 /// ```
3027 pub fn strides(&self) -> &[isize] {
3028 self.layout.strides()
3029 }
3030
3031 /// Return the physical element offset.
3032 ///
3033 /// # Examples
3034 ///
3035 /// ```rust
3036 /// use tenferro_tensor::TypedTensorView;
3037 ///
3038 /// let data = [1_i32, 2];
3039 /// let view = TypedTensorView::from_slice(vec![1], vec![1], 1, &data)?;
3040 /// assert_eq!(view.offset(), 1);
3041 /// # Ok::<(), tenferro_tensor::Error>(())
3042 /// ```
3043 pub fn offset(&self) -> isize {
3044 self.layout.offset()
3045 }
3046
3047 /// Return the borrowed host storage backing this view.
3048 ///
3049 /// This exposes the entire backing host allocation, not just the logical
3050 /// slice covered by this view. Use [`TypedTensorView::as_slice`] when the
3051 /// caller needs the contiguous logical region instead.
3052 ///
3053 /// # Examples
3054 ///
3055 /// ```rust
3056 /// use tenferro_tensor::TypedTensorView;
3057 ///
3058 /// let data = [1_i32, 2];
3059 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3060 /// assert_eq!(view.host_storage()?, &[1, 2]);
3061 /// # Ok::<(), tenferro_tensor::Error>(())
3062 /// ```
3063 ///
3064 /// # Errors
3065 ///
3066 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
3067 /// buffer; backend storage must be downloaded before host inspection.
3068 pub fn host_storage(&self) -> crate::Result<&'a [T]> {
3069 match &self.buffer {
3070 TensorStorageRef::Host(data) => Ok(data),
3071 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3072 Err(crate::Error::runtime_state(
3073 "TypedTensorView::host_storage",
3074 "backend buffers cannot expose host storage; download explicitly first",
3075 ))
3076 }
3077 }
3078 }
3079
3080 /// Return the number of logical elements in this view.
3081 ///
3082 /// # Examples
3083 ///
3084 /// ```rust
3085 /// use tenferro_tensor::TypedTensorView;
3086 ///
3087 /// let data = [0_i32; 6];
3088 /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
3089 /// assert_eq!(view.n_elements(), 6);
3090 /// # Ok::<(), tenferro_tensor::Error>(())
3091 /// ```
3092 pub fn n_elements(&self) -> usize {
3093 // Invariant: public view constructors validate logical element count.
3094 match checked_view_element_count(self.shape(), "TypedTensorView::n_elements") {
3095 Ok(n) => n,
3096 Err(err) => {
3097 unreachable!("TypedTensorView layout shape is validated at construction: {err}")
3098 }
3099 }
3100 }
3101
3102 /// Return layout metadata for this view.
3103 ///
3104 /// # Examples
3105 ///
3106 /// ```rust
3107 /// use tenferro_tensor::TypedTensorView;
3108 ///
3109 /// let data = [1_i32, 2];
3110 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3111 /// assert!(view.layout().is_compact_col_major().unwrap());
3112 /// # Ok::<(), tenferro_tensor::Error>(())
3113 /// ```
3114 pub fn layout(&self) -> &TensorLayout<R> {
3115 &self.layout
3116 }
3117
3118 /// Return placement metadata for this view.
3119 ///
3120 /// # Examples
3121 ///
3122 /// ```rust
3123 /// use tenferro_tensor::{MemoryKind, TypedTensorView};
3124 ///
3125 /// let data = [1_i32];
3126 /// let view = TypedTensorView::from_slice(vec![1], vec![1], 0, &data)?;
3127 /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
3128 /// # Ok::<(), tenferro_tensor::Error>(())
3129 /// ```
3130 pub fn placement(&self) -> &Placement {
3131 &self.placement
3132 }
3133
3134 /// Return the backend allocation for backend integrations.
3135 #[doc(hidden)]
3136 pub fn backing_len(&self) -> usize {
3137 self.buffer.len()
3138 }
3139
3140 /// Return the backend allocation for backend integrations.
3141 #[doc(hidden)]
3142 pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>> {
3143 match &self.buffer {
3144 TensorStorageRef::Host(_) => None,
3145 TensorStorageRef::Backend(buffer) => Some(*buffer),
3146 TensorStorageRef::Root(_) => {
3147 self.root
3148 .as_ref()?
3149 .backend_buffer()
3150 .and_then(|buffer| match buffer {
3151 StorageBuffer::Host(_) => None,
3152 StorageBuffer::Backend(buffer) => Some(buffer.as_ref()),
3153 })
3154 }
3155 }
3156 }
3157
3158 /// Return the provider family for this view when it is backend-owned.
3159 #[doc(hidden)]
3160 pub fn backend_family(&self) -> Option<&'static str>
3161 where
3162 T: TensorScalar + 'static,
3163 {
3164 self.root
3165 .as_ref()
3166 .and_then(|root| {
3167 root.backend_allocation()
3168 .map(|_| root.provider_kind().as_str())
3169 })
3170 .or_else(|| self.backend_buffer().map(|buffer| buffer.backend_family()))
3171 }
3172
3173 /// Return the shared allocation domain for this view when backend-owned.
3174 #[doc(hidden)]
3175 pub fn allocation_domain(&self) -> Option<AllocationDomainId>
3176 where
3177 T: TensorScalar + 'static,
3178 {
3179 self.root
3180 .as_ref()
3181 .and_then(|root| root.backend_identity().map(|(domain, _)| domain))
3182 .or_else(|| {
3183 self.backend_buffer()
3184 .and_then(|buffer| buffer.allocation_domain())
3185 })
3186 }
3187
3188 /// Return the physical allocation identity for this view when backend-owned.
3189 #[doc(hidden)]
3190 pub fn allocation_id(&self) -> Option<AllocationId>
3191 where
3192 T: TensorScalar + 'static,
3193 {
3194 self.root
3195 .as_ref()
3196 .and_then(|root| root.backend_identity().map(|(_, id)| id))
3197 .or_else(|| {
3198 self.backend_buffer()
3199 .and_then(|buffer| buffer.allocation_id())
3200 })
3201 }
3202
3203 /// Prepare this backend view for one provider-native read binding.
3204 #[doc(hidden)]
3205 pub fn prepare_device_read(
3206 &self,
3207 op: &'static str,
3208 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
3209 where
3210 T: TensorScalar + 'static,
3211 {
3212 if let Some(root) = &self.root {
3213 return root
3214 .prepare_device_read_for_layout(&self.layout)
3215 .map_err(|error| crate::Error::runtime_state_source(op, error));
3216 }
3217 let buffer = self.backend_buffer().ok_or_else(|| {
3218 crate::Error::runtime_state_source(
3219 op,
3220 crate::AccessError::Unsupported { backend: "host" },
3221 )
3222 })?;
3223 prepare_backend_access(buffer, &self.layout, op)
3224 }
3225
3226 /// Compute the physical element offset for a logical index.
3227 ///
3228 /// # Examples
3229 ///
3230 /// ```rust
3231 /// use tenferro_tensor::TypedTensorView;
3232 ///
3233 /// let data = [1_i32, 2, 3];
3234 /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
3235 /// assert_eq!(view.linear_offset(&[2]), Some(0));
3236 /// # Ok::<(), tenferro_tensor::Error>(())
3237 /// ```
3238 pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
3239 checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
3240 }
3241
3242 /// Compute the physical element offset for a logical index, returning a typed error.
3243 ///
3244 /// # Examples
3245 ///
3246 /// ```rust
3247 /// use tenferro_tensor::TypedTensorView;
3248 ///
3249 /// let data = [1_i32, 2, 3];
3250 /// let view = TypedTensorView::from_slice([3], [-1], 2, &data)?;
3251 /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
3252 /// # Ok::<(), tenferro_tensor::Error>(())
3253 /// ```
3254 ///
3255 /// # Errors
3256 ///
3257 /// Returns [`crate::Error::Validation`] with
3258 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
3259 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
3260 /// when an index is outside its axis extent, or
3261 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
3262 /// arithmetic overflows.
3263 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
3264 checked_view_offset_result(
3265 self.shape(),
3266 self.strides(),
3267 self.offset(),
3268 indices,
3269 "TypedTensorView::layout_linear_offset",
3270 )
3271 }
3272
3273 /// Return whether this view is compact column-major.
3274 ///
3275 /// # Examples
3276 ///
3277 /// ```rust
3278 /// use tenferro_tensor::TypedTensorView;
3279 ///
3280 /// let data = [1_i32, 2];
3281 /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3282 /// assert!(view.is_col_major_contiguous()?);
3283 /// # Ok::<(), tenferro_tensor::Error>(())
3284 /// ```
3285 ///
3286 /// # Errors
3287 ///
3288 /// Returns [`crate::Error::Validation`] with
3289 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] if compactness
3290 /// arithmetic overflows.
3291 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
3292 self.layout
3293 .is_compact_col_major()
3294 .map_err(|err| tensor_layout_error("TypedTensorView::is_col_major_contiguous", err))
3295 }
3296
3297 /// Return a compact string summary of this view's layout metadata.
3298 ///
3299 /// # Examples
3300 ///
3301 /// ```rust
3302 /// use tenferro_tensor::TypedTensorView;
3303 ///
3304 /// let data = [1_i32, 2];
3305 /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3306 /// assert!(view.layout_summary().contains("shape=[2]"));
3307 /// # Ok::<(), tenferro_tensor::Error>(())
3308 /// ```
3309 pub fn layout_summary(&self) -> String {
3310 layout_summary(self.shape(), self.strides(), self.offset())
3311 }
3312
3313 /// Assert this view is compact column-major.
3314 ///
3315 /// # Examples
3316 ///
3317 /// ```rust
3318 /// use tenferro_tensor::TypedTensorView;
3319 ///
3320 /// let data = [1_i32, 2];
3321 /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
3322 /// view.assert_col_major_contiguous()?;
3323 /// # Ok::<(), tenferro_tensor::Error>(())
3324 /// ```
3325 ///
3326 /// # Errors
3327 ///
3328 /// Returns [`crate::Error::Validation`] with
3329 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
3330 /// compactness arithmetic overflows, or
3331 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
3332 /// view is not compact column-major.
3333 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
3334 assert_layout_col_major_contiguous(
3335 self.is_col_major_contiguous()?,
3336 self.shape(),
3337 self.strides(),
3338 self.offset(),
3339 "TypedTensorView::assert_col_major_contiguous",
3340 )
3341 }
3342
3343 /// Borrow one host element by logical index.
3344 ///
3345 /// Returns `None` for out-of-bounds indices and backend buffers.
3346 ///
3347 /// # Examples
3348 ///
3349 /// ```rust
3350 /// use tenferro_tensor::TypedTensorView;
3351 ///
3352 /// let data = [1_i32, 2];
3353 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3354 /// assert_eq!(view.get(&[1]), Some(&2));
3355 /// # Ok::<(), tenferro_tensor::Error>(())
3356 /// ```
3357 pub fn get(&self, indices: &[usize]) -> Option<&T> {
3358 let offset = self.linear_offset(indices)?;
3359 match &self.buffer {
3360 TensorStorageRef::Host(data) => data.get(offset),
3361 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => None,
3362 }
3363 }
3364
3365 /// Borrow the contiguous host slice covered by this view.
3366 ///
3367 /// Returns an explicit error for backend buffers and for non-contiguous
3368 /// layouts. This method never downloads or materializes backend data.
3369 ///
3370 /// # Examples
3371 ///
3372 /// ```rust
3373 /// use tenferro_tensor::TypedTensorView;
3374 ///
3375 /// let data = [1_i32, 2, 3];
3376 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?;
3377 /// assert_eq!(view.as_slice()?, &[2, 3]);
3378 /// # Ok::<(), tenferro_tensor::Error>(())
3379 /// ```
3380 ///
3381 /// # Errors
3382 ///
3383 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
3384 /// buffer, [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]
3385 /// when the layout is not compact column-major,
3386 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the view
3387 /// offset is negative, or [`crate::Error::Validation`] with
3388 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] or
3389 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
3390 /// requested host range is invalid.
3391 pub fn as_slice(&self) -> crate::Result<&'a [T]> {
3392 let data = match &self.buffer {
3393 TensorStorageRef::Host(data) => data,
3394 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3395 return Err(crate::Error::runtime_state(
3396 "TypedTensorView::as_slice",
3397 "backend buffers cannot be inspected as host slices; download explicitly first",
3398 ))
3399 }
3400 };
3401 contiguous_layout_slice(self.layout(), data, "TypedTensorView::as_slice")
3402 }
3403
3404 /// Borrow the compact logical region through a scoped host mapping.
3405 ///
3406 /// Backend integrations must validate placement and allocation-domain ownership
3407 /// before mapping. The guard cannot escape the callback; no transfer is performed.
3408 ///
3409 /// # Errors
3410 ///
3411 /// Returns a host-access or provider-preparation error when mapping fails,
3412 /// or a validation/unsupported error for a non-contiguous or invalid layout.
3413 #[doc(hidden)]
3414 pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
3415 where
3416 T: TensorScalar + 'static,
3417 {
3418 const OP: &str = "TypedTensorView::with_host_read";
3419 if !self.is_col_major_contiguous()? {
3420 return Err(crate::Error::unsupported(
3421 OP,
3422 "host guard access requires a compact descriptor",
3423 ));
3424 }
3425 if let Some(buffer) = self.backend_buffer() {
3426 let guard = buffer
3427 .map_read()
3428 .map_err(|error| crate::Error::host_access(OP, error))?;
3429 return Ok(f(contiguous_layout_slice(self.layout(), &guard, OP)?));
3430 }
3431 if let Some(root) = &self.root {
3432 let prepared = root
3433 .prepare_host_read_for_layout(self.layout())
3434 .map_err(|error| crate::Error::runtime_state_source(OP, error))?;
3435 let slice = prepared.as_slice().ok_or_else(|| {
3436 crate::Error::unsupported(OP, "host guard access requires a compact descriptor")
3437 })?;
3438 return Ok(f(slice));
3439 }
3440 Ok(f(self.as_slice()?))
3441 }
3442
3443 /// Explicitly duplicate a compact host view into a new owner.
3444 ///
3445 /// Backend views require an explicit provider canonicalization or download
3446 /// boundary; this method never transfers or materializes them implicitly.
3447 ///
3448 /// # Examples
3449 ///
3450 /// ```
3451 /// use tenferro_tensor::TypedTensorView;
3452 ///
3453 /// let data = [1_i32, 2];
3454 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
3455 /// let copy = view.duplicate()?;
3456 /// assert_eq!(copy.as_slice()?, &[1, 2]);
3457 /// # Ok::<(), tenferro_tensor::Error>(())
3458 /// ```
3459 ///
3460 /// # Errors
3461 ///
3462 /// Returns [`crate::Error::HostAccess`] or
3463 /// [`ValidationError::NonContiguousViewAsSlice`] when the view is backend
3464 /// owned or not contiguous, and [`ValidationError::InvalidArgument`] when
3465 /// the static-rank shape cannot be reconstructed.
3466 pub fn duplicate(&self) -> crate::Result<TypedTensor<T, R>>
3467 where
3468 T: Clone,
3469 {
3470 let data = self.as_slice()?.to_vec();
3471 let shape = R::shape_from_vec(shape_vec(self.shape()))
3472 .map_err(|err| tensor_layout_error("TypedTensorView::duplicate", err))?;
3473 let mut tensor = TypedTensor::from_vec_col_major(shape, data)?;
3474 tensor.set_placement(self.placement.clone());
3475 Ok(tensor)
3476 }
3477
3478 /// Explicitly copy a host view, including strided/offset views, to a compact owner.
3479 ///
3480 /// # Examples
3481 /// ```
3482 /// use tenferro_tensor::{Rank, TypedTensorView};
3483 /// let storage = [1, 4, 2, 5, 3, 6];
3484 /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked(
3485 /// [2, 3], [1, 2], 0, &storage,
3486 /// )?;
3487 /// let transposed = view.transpose_view([1, 0])?;
3488 /// assert_eq!(transposed.to_col_major()?.as_slice()?, &[1, 2, 3, 4, 5, 6]);
3489 /// # Ok::<(), tenferro_tensor::Error>(())
3490 /// ```
3491 /// # Errors
3492 /// Backend-only storage returns [`crate::Error::RuntimeState`]; invalid
3493 /// layout or rank conversion returns [`crate::Error::Validation`].
3494 pub fn to_col_major(&self) -> crate::Result<TypedTensor<T, R>>
3495 where
3496 T: Clone,
3497 {
3498 let data = self.host_storage()?;
3499 let mut coordinates = ShapeVec::from_elem(0, self.shape().len());
3500 let element_count = self.n_elements();
3501 let mut copied = Vec::with_capacity(element_count);
3502 for _ in 0..element_count {
3503 let offset = self.layout_linear_offset(&coordinates)?;
3504 let value = data.get(offset).ok_or_else(|| {
3505 crate::Error::validation(
3506 "TypedTensorView::to_col_major",
3507 ValidationError::ViewOutOfBounds,
3508 )
3509 })?;
3510 copied.push(value.clone());
3511 for axis in 0..coordinates.len() {
3512 coordinates[axis] += 1;
3513 if coordinates[axis] < self.shape()[axis] {
3514 break;
3515 }
3516 coordinates[axis] = 0;
3517 }
3518 }
3519 let shape = R::shape_from_vec(shape_vec(self.shape()))
3520 .map_err(|err| tensor_layout_error("TypedTensorView::to_col_major", err))?;
3521 let mut tensor = TypedTensor::from_vec_col_major(shape, copied)?;
3522 tensor.set_placement(self.placement.clone());
3523 Ok(tensor)
3524 }
3525
3526 /// Return a metadata-only axis permutation.
3527 ///
3528 /// # Examples
3529 ///
3530 /// ```rust
3531 /// use tenferro_tensor::{Rank, TypedTensorView};
3532 ///
3533 /// let data = [1_i32, 2, 3, 4, 5, 6];
3534 /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 3], [1, 2], 0, &data)?;
3535 /// let transposed = view.transpose_view([1, 0])?;
3536 /// assert_eq!(transposed.shape(), &[3, 2]);
3537 /// # Ok::<(), tenferro_tensor::Error>(())
3538 /// ```
3539 /// # Errors
3540 ///
3541 /// Returns [`crate::Error::Validation`] with
3542 /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
3543 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
3544 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
3545 /// not a valid permutation of the view rank.
3546 pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
3547 let layout = self
3548 .layout
3549 .transpose_view(axes)
3550 .map_err(|err| tensor_layout_error("TypedTensorView::transpose_view", err))?;
3551 Ok(Self {
3552 buffer: self.buffer.clone(),
3553 root: self.root.clone(),
3554 layout,
3555 placement: self.placement.clone(),
3556 _representation: std::marker::PhantomData,
3557 })
3558 }
3559
3560 /// Return a metadata-only slice using one [`StridedSliceSpec`] per axis.
3561 ///
3562 /// # Examples
3563 ///
3564 /// ```rust
3565 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
3566 ///
3567 /// let data = [1_i32, 2, 3];
3568 /// let view = TypedTensorView::from_slice(vec![3], vec![1], 0, &data)?;
3569 /// let reversed = view.slice_view(&[StridedSliceSpec::reverse()])?;
3570 /// assert_eq!(reversed.get(&[0]), Some(&3));
3571 /// # Ok::<(), tenferro_tensor::Error>(())
3572 /// ```
3573 /// # Errors
3574 ///
3575 /// Returns [`crate::Error::Validation`] with
3576 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
3577 /// count differs from the view rank,
3578 /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
3579 /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
3580 /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3581 /// for slice arithmetic overflow, or
3582 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3583 /// resulting layout exceeds the backing buffer.
3584 pub fn slice_view(&self, slices: &[StridedSliceSpec]) -> crate::Result<Self> {
3585 let specs = core_slice_specs(slices, self.shape(), "TypedTensorView::slice_view")?;
3586 let layout = self
3587 .layout
3588 .slice_view(specs, self.buffer.len())
3589 .map_err(|err| tensor_layout_error("TypedTensorView::slice_view", err))?;
3590 Ok(Self {
3591 buffer: self.buffer.clone(),
3592 root: self.root.clone(),
3593 layout,
3594 placement: self.placement.clone(),
3595 _representation: std::marker::PhantomData,
3596 })
3597 }
3598
3599 /// Return a metadata-only slice along one axis.
3600 ///
3601 /// # Examples
3602 ///
3603 /// ```rust
3604 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
3605 ///
3606 /// let data = [1_i32, 2, 3, 4];
3607 /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
3608 /// assert_eq!(view.slice_axis_view(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
3609 /// # Ok::<(), tenferro_tensor::Error>(())
3610 /// ```
3611 /// # Errors
3612 ///
3613 /// Returns [`crate::Error::Validation`] with
3614 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
3615 /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
3616 /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
3617 /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3618 /// for slice arithmetic overflow, or
3619 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3620 /// resulting layout exceeds the backing buffer.
3621 pub fn slice_axis_view(&self, axis: usize, slice: StridedSliceSpec) -> crate::Result<Self> {
3622 let slices = slice_axis_specs(
3623 self.shape().len(),
3624 axis,
3625 slice,
3626 "TypedTensorView::slice_axis_view",
3627 )?;
3628 self.slice_view(&slices)
3629 }
3630
3631 /// Return a metadata-only dynamic-rank reshape for contiguous column-major views.
3632 ///
3633 /// # Examples
3634 ///
3635 /// ```rust
3636 /// use tenferro_tensor::TypedTensorView;
3637 ///
3638 /// let data = [1_i32, 2, 3, 4];
3639 /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
3640 /// assert_eq!(view.reshape_view(&[4])?.shape(), &[4]);
3641 /// # Ok::<(), tenferro_tensor::Error>(())
3642 /// ```
3643 /// # Errors
3644 ///
3645 /// Returns [`crate::Error::Validation`] with
3646 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
3647 /// the source is not compact column-major,
3648 /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
3649 /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
3650 /// records the counts) when element counts differ,
3651 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
3652 /// arithmetic overflow, or
3653 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3654 /// reshaped view exceeds the backing buffer.
3655 pub fn reshape_view(
3656 &self,
3657 shape: &[usize],
3658 ) -> crate::Result<TypedTensorView<'a, T, DynRank, D>> {
3659 let layout = reshape_layout_dyn(
3660 &self.layout,
3661 shape,
3662 self.buffer.len(),
3663 "TypedTensorView::reshape_view",
3664 )?;
3665 Ok(TypedTensorView {
3666 buffer: self.buffer.clone(),
3667 root: self.root.as_ref().map(GroupReadView::clone_dyn),
3668 layout,
3669 placement: self.placement.clone(),
3670 _representation: std::marker::PhantomData,
3671 })
3672 }
3673}
3674
3675impl<'a, R: TensorRank> TypedTensorView<'a, Complex32, R> {
3676 /// Borrow this complex view as an interleaved real view without copying.
3677 ///
3678 /// The result has dynamic rank because reinterpretation prepends the
3679 /// component axis `[2, ...]`. Only `Complex32 <-> f32` is sealed in this
3680 /// API; this is representation reinterpretation, not numeric conversion.
3681 ///
3682 /// # Examples
3683 ///
3684 /// ```
3685 /// use tenferro_tensor::{Complex32, TypedTensorView};
3686 ///
3687 /// let data = [Complex32::new(1.0, 2.0)];
3688 /// let view = TypedTensorView::from_col_major(&[1], &data)?;
3689 /// let real = view.as_real_view()?;
3690 /// assert_eq!(real.shape(), &[2, 1]);
3691 /// assert_eq!(real.as_slice()?, &[1.0_f32, 2.0]);
3692 /// # Ok::<(), tenferro_tensor::Error>(())
3693 /// ```
3694 ///
3695 /// # Errors
3696 ///
3697 /// Returns an error when the view layout is not a valid sealed
3698 /// representation or when backend reinterpretation is unsupported.
3699 pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'a, f32, DynRank>> {
3700 let op = "TypedTensorView::as_real_view";
3701 validate_representation_pair(op, DType::C32, DType::F32)?;
3702 let layout = reinterpret_complex_to_real_layout(
3703 self.shape(),
3704 self.strides(),
3705 self.offset(),
3706 self.buffer.len(),
3707 op,
3708 )?;
3709 let buffer = match &self.buffer {
3710 TensorStorageRef::Host(data) => {
3711 TensorStorageRef::Host(reinterpret_host_slice::<Complex32, f32>(data, op)?)
3712 }
3713 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3714 return Err(crate::Error::unsupported(
3715 op,
3716 "backend representation reinterpretation is enabled by the provider phases",
3717 ))
3718 }
3719 };
3720 Ok(TypedTensorView {
3721 buffer,
3722 root: None,
3723 layout,
3724 placement: self.placement.clone(),
3725 _representation: std::marker::PhantomData,
3726 })
3727 }
3728}
3729
3730impl<'a, R: TensorRank> TypedTensorView<'a, Complex64, R> {
3731 /// Borrow this complex view as an interleaved real view without copying.
3732 ///
3733 /// The result has dynamic rank because reinterpretation prepends the
3734 /// component axis `[2, ...]`. Only `Complex64 <-> f64` is sealed in this
3735 /// API; this is representation reinterpretation, not numeric conversion.
3736 ///
3737 /// # Examples
3738 ///
3739 /// ```
3740 /// use tenferro_tensor::{Complex64, TypedTensorView};
3741 ///
3742 /// let data = [Complex64::new(1.0, 2.0)];
3743 /// let view = TypedTensorView::from_col_major(&[1], &data)?;
3744 /// let real = view.as_real_view()?;
3745 /// assert_eq!(real.shape(), &[2, 1]);
3746 /// assert_eq!(real.as_slice()?, &[1.0_f64, 2.0]);
3747 /// # Ok::<(), tenferro_tensor::Error>(())
3748 /// ```
3749 ///
3750 /// # Errors
3751 ///
3752 /// Returns an error when the view layout is not a valid sealed
3753 /// representation or when backend reinterpretation is unsupported.
3754 pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'a, f64, DynRank>> {
3755 let op = "TypedTensorView::as_real_view";
3756 validate_representation_pair(op, DType::C64, DType::F64)?;
3757 let layout = reinterpret_complex_to_real_layout(
3758 self.shape(),
3759 self.strides(),
3760 self.offset(),
3761 self.buffer.len(),
3762 op,
3763 )?;
3764 let buffer = match &self.buffer {
3765 TensorStorageRef::Host(data) => {
3766 TensorStorageRef::Host(reinterpret_host_slice::<Complex64, f64>(data, op)?)
3767 }
3768 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3769 return Err(crate::Error::unsupported(
3770 op,
3771 "backend representation reinterpretation is enabled by the provider phases",
3772 ))
3773 }
3774 };
3775 Ok(TypedTensorView {
3776 buffer,
3777 root: None,
3778 layout,
3779 placement: self.placement.clone(),
3780 _representation: std::marker::PhantomData,
3781 })
3782 }
3783}
3784
3785impl<'a, R: TensorRank> TypedTensorView<'a, f32, R> {
3786 /// Borrow this interleaved real view as a complex view without copying.
3787 ///
3788 /// The source must have a leading extent and stride of `2` and `1`, and
3789 /// every remaining stride plus the offset must be divisible by `2`.
3790 ///
3791 /// # Examples
3792 ///
3793 /// ```
3794 /// use tenferro_tensor::{Complex32, TypedTensorView};
3795 ///
3796 /// let data = [1.0_f32, 2.0];
3797 /// let view = TypedTensorView::from_col_major(&[2, 1], &data)?;
3798 /// let complex = view.as_complex_view()?;
3799 /// assert_eq!(complex.shape(), &[1]);
3800 /// assert_eq!(complex.as_slice()?, &[Complex32::new(1.0, 2.0)]);
3801 /// # Ok::<(), tenferro_tensor::Error>(())
3802 /// ```
3803 ///
3804 /// # Errors
3805 ///
3806 /// Returns an error when the view layout is not a valid sealed
3807 /// representation or when backend reinterpretation is unsupported.
3808 pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'a, Complex32, DynRank>> {
3809 let op = "TypedTensorView::as_complex_view";
3810 validate_representation_pair(op, DType::F32, DType::C32)?;
3811 let layout = reinterpret_real_to_complex_layout(
3812 self.shape(),
3813 self.strides(),
3814 self.offset(),
3815 self.buffer.len(),
3816 op,
3817 )?;
3818 let buffer = match &self.buffer {
3819 TensorStorageRef::Host(data) => {
3820 TensorStorageRef::Host(reinterpret_host_slice::<f32, Complex32>(data, op)?)
3821 }
3822 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3823 return Err(crate::Error::unsupported(
3824 op,
3825 "backend representation reinterpretation is enabled by the provider phases",
3826 ))
3827 }
3828 };
3829 Ok(TypedTensorView {
3830 buffer,
3831 root: None,
3832 layout,
3833 placement: self.placement.clone(),
3834 _representation: std::marker::PhantomData,
3835 })
3836 }
3837}
3838
3839impl<'a, R: TensorRank> TypedTensorView<'a, f64, R> {
3840 /// Borrow this interleaved real view as a complex view without copying.
3841 ///
3842 /// The source must have a leading extent and stride of `2` and `1`, and
3843 /// every remaining stride plus the offset must be divisible by `2`.
3844 ///
3845 /// # Examples
3846 ///
3847 /// ```
3848 /// use tenferro_tensor::{Complex64, TypedTensorView};
3849 ///
3850 /// let data = [1.0_f64, 2.0];
3851 /// let view = TypedTensorView::from_col_major(&[2, 1], &data)?;
3852 /// let complex = view.as_complex_view()?;
3853 /// assert_eq!(complex.shape(), &[1]);
3854 /// assert_eq!(complex.as_slice()?, &[Complex64::new(1.0, 2.0)]);
3855 /// # Ok::<(), tenferro_tensor::Error>(())
3856 /// ```
3857 ///
3858 /// # Errors
3859 ///
3860 /// Returns an error when the view layout is not a valid sealed
3861 /// representation or when backend reinterpretation is unsupported.
3862 pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'a, Complex64, DynRank>> {
3863 let op = "TypedTensorView::as_complex_view";
3864 validate_representation_pair(op, DType::F64, DType::C64)?;
3865 let layout = reinterpret_real_to_complex_layout(
3866 self.shape(),
3867 self.strides(),
3868 self.offset(),
3869 self.buffer.len(),
3870 op,
3871 )?;
3872 let buffer = match &self.buffer {
3873 TensorStorageRef::Host(data) => {
3874 TensorStorageRef::Host(reinterpret_host_slice::<f64, Complex64>(data, op)?)
3875 }
3876 TensorStorageRef::Backend(_) | TensorStorageRef::Root(_) => {
3877 return Err(crate::Error::unsupported(
3878 op,
3879 "backend representation reinterpretation is enabled by the provider phases",
3880 ))
3881 }
3882 };
3883 Ok(TypedTensorView {
3884 buffer,
3885 root: None,
3886 layout,
3887 placement: self.placement.clone(),
3888 _representation: std::marker::PhantomData,
3889 })
3890 }
3891}
3892
3893/// Mutable borrowed view of typed tensor storage with arbitrary strides.
3894///
3895/// # Examples
3896///
3897/// ```rust
3898/// use tenferro_tensor::TypedTensorViewMut;
3899///
3900/// let mut data = [1_i32, 2, 3];
3901/// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
3902/// *view.get_mut(&[2]).unwrap() = 10;
3903/// assert_eq!(view.as_read_only().get(&[2]), Some(&10));
3904/// # Ok::<(), tenferro_tensor::Error>(())
3905/// ```
3906/// Exclusive typed view of one tensor representation.
3907///
3908/// As with [`TypedTensorView`], the buffer stays concrete so the view keeps its
3909/// lifetime covariance, and `D` marks which representation produced the view.
3910pub struct TypedTensorViewMut<'a, T, R: TensorRank = DynRank, D: Representation = Dynamic> {
3911 buffer: TensorStorageRefMut<'a, T>,
3912 root: Option<GroupWriteView<'a, T, R>>,
3913 layout: TensorLayout<R>,
3914 placement: Placement,
3915 _representation: std::marker::PhantomData<D>,
3916}
3917
3918impl<'a, T, R: TensorRank, D: Representation> std::fmt::Debug for TypedTensorViewMut<'a, T, R, D> {
3919 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3920 formatter
3921 .debug_struct("TypedTensorViewMut")
3922 .field("shape", &self.layout.shape())
3923 .field("placement", &self.placement)
3924 .finish_non_exhaustive()
3925 }
3926}
3927
3928/// Pair of mutable tensor views returned by disjoint multi-slice operations.
3929///
3930/// # Examples
3931///
3932/// ```rust
3933/// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut, TypedTensorViewMutSplit};
3934///
3935/// let mut data = [1_i32, 2, 3, 4];
3936/// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
3937/// let pair: TypedTensorViewMutSplit<'_, i32> = view
3938/// .try_multi_slice_mut(
3939/// &[StridedSliceSpec::new(0, Some(2), 1)],
3940/// &[StridedSliceSpec::new(2, Some(4), 1)],
3941/// )
3942/// ?
3943/// .unwrap();
3944/// assert_eq!(pair.0.shape(), &[2]);
3945/// assert_eq!(pair.1.shape(), &[2]);
3946/// # Ok::<(), tenferro_tensor::Error>(())
3947/// ```
3948pub type TypedTensorViewMutSplit<'a, T, R = DynRank> =
3949 (TypedTensorViewMut<'a, T, R>, TypedTensorViewMut<'a, T, R>);
3950
3951impl<'a, T: 'static> TypedTensorViewMut<'a, T, DynRank> {
3952 /// Create a mutable dynamic-rank view over compact column-major host data.
3953 ///
3954 /// # Examples
3955 ///
3956 /// ```rust
3957 /// use tenferro_tensor::TypedTensorViewMut;
3958 ///
3959 /// let mut data = [1_i32, 2, 3, 4];
3960 /// let view = TypedTensorViewMut::from_col_major(&[2, 2], &mut data)?;
3961 /// assert_eq!(view.strides(), &[1, 2]);
3962 /// # Ok::<(), tenferro_tensor::Error>(())
3963 /// ```
3964 /// # Errors
3965 ///
3966 /// Returns [`crate::Error::Validation`] with
3967 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
3968 /// shape or offset arithmetic overflow, or
3969 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3970 /// compact shape reaches beyond `data`.
3971 pub fn from_col_major(shape: &[usize], data: &'a mut [T]) -> crate::Result<Self> {
3972 let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
3973 .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_col_major", err))?;
3974 Self::from_buffer_ref_mut(
3975 shape_vec(layout.shape()),
3976 stride_vec(layout.strides()),
3977 layout.offset(),
3978 TensorStorageRefMut::Host(data),
3979 default_placement(),
3980 "TypedTensorViewMut::from_col_major",
3981 )
3982 }
3983
3984 /// Create a mutable host view from explicit layout metadata.
3985 ///
3986 /// Layouts where distinct logical elements can alias the same physical
3987 /// element are rejected.
3988 ///
3989 /// # Examples
3990 ///
3991 /// ```rust
3992 /// use tenferro_tensor::TypedTensorViewMut;
3993 ///
3994 /// let mut data = [1_i32, 2];
3995 /// assert!(TypedTensorViewMut::from_slice(vec![2], vec![0], 0, &mut data).is_err());
3996 /// ```
3997 /// # Errors
3998 ///
3999 /// Returns [`crate::Error::Validation`] with
4000 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
4001 /// `strides` have different ranks,
4002 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4003 /// layout reaches beyond `data`,
4004 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4005 /// logical elements alias, or
4006 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4007 /// arithmetic overflow.
4008 pub fn from_slice(
4009 shape: impl AsRef<[usize]>,
4010 strides: impl AsRef<[isize]>,
4011 offset: isize,
4012 data: &'a mut [T],
4013 ) -> crate::Result<Self> {
4014 Self::from_buffer_ref_mut(
4015 shape_vec(shape.as_ref()),
4016 stride_vec(strides.as_ref()),
4017 offset,
4018 TensorStorageRefMut::Host(data),
4019 default_placement(),
4020 "TypedTensorViewMut::from_slice",
4021 )
4022 }
4023}
4024
4025impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4026 /// Create a rank-generic mutable host view from explicit layout metadata.
4027 ///
4028 /// # Examples
4029 ///
4030 /// ```rust
4031 /// use tenferro_tensor::{Rank, TypedTensorViewMut};
4032 ///
4033 /// let mut data = [1_i32, 2, 3, 4];
4034 /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
4035 /// assert_eq!(view.shape(), &[2, 2]);
4036 /// # Ok::<(), tenferro_tensor::Error>(())
4037 /// ```
4038 /// # Errors
4039 ///
4040 /// Returns [`crate::Error::Validation`] with
4041 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
4042 /// rank does not match `shape` or `strides`,
4043 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4044 /// layout reaches beyond `data`,
4045 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4046 /// logical elements alias, or
4047 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4048 /// arithmetic overflow.
4049 pub fn from_slice_ranked(
4050 shape: impl Into<R::Shape>,
4051 strides: impl Into<R::Strides>,
4052 offset: isize,
4053 data: &'a mut [T],
4054 ) -> crate::Result<Self> {
4055 Self::from_buffer_ref_mut(
4056 shape,
4057 strides,
4058 offset,
4059 TensorStorageRefMut::Host(data),
4060 default_placement(),
4061 "TypedTensorViewMut::from_slice_ranked",
4062 )
4063 }
4064}
4065
4066impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4067 fn from_buffer_ref_mut(
4068 shape: impl Into<R::Shape>,
4069 strides: impl Into<R::Strides>,
4070 offset: isize,
4071 buffer: TensorStorageRefMut<'a, T>,
4072 placement: Placement,
4073 op: &'static str,
4074 ) -> crate::Result<Self> {
4075 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
4076 .map_err(|err| tensor_layout_error(op, err))?;
4077 layout
4078 .validate_mutable_no_overlap()
4079 .map_err(|err| tensor_layout_error(op, err))?;
4080 Ok(Self {
4081 buffer,
4082 root: None,
4083 layout,
4084 placement,
4085 _representation: std::marker::PhantomData,
4086 })
4087 }
4088}
4089
4090impl<'a, T: 'static, R: TensorRank> TypedTensorViewMut<'a, T, R, Host> {
4091 /// Create a representation-marked mutable view over an explicit host layout.
4092 ///
4093 /// # Examples
4094 ///
4095 /// ```rust
4096 /// use tenferro_tensor::{DynRank, Host, TypedTensorViewMut};
4097 ///
4098 /// let mut data = [1_i32, 2];
4099 /// let mut view: TypedTensorViewMut<'_, i32, DynRank, Host> =
4100 /// TypedTensorViewMut::from_host_slice(vec![2], vec![1], 0, &mut data)?;
4101 /// view.slice_view(&[tenferro_tensor::StridedSliceSpec::all()])?;
4102 /// # Ok::<(), tenferro_tensor::Error>(())
4103 /// ```
4104 ///
4105 /// # Errors
4106 ///
4107 /// Returns [`crate::Error::Validation`] with
4108 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `strides`
4109 /// has a different rank, [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`]
4110 /// when logical elements alias, or
4111 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] /
4112 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
4113 /// reachable layout leaves `data` or overflows.
4114 pub fn from_host_slice(
4115 shape: impl Into<R::Shape>,
4116 strides: impl Into<R::Strides>,
4117 offset: isize,
4118 data: &'a mut [T],
4119 ) -> crate::Result<Self> {
4120 // Built here rather than through the shared helper so that the Host
4121 // marker can only ever be paired with a host slice.
4122 let buffer = TensorStorageRefMut::Host(data);
4123 let layout =
4124 TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
4125 .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_host_slice", err))?;
4126 layout
4127 .validate_mutable_no_overlap()
4128 .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_host_slice", err))?;
4129 Ok(Self {
4130 buffer,
4131 root: None,
4132 layout,
4133 placement: default_placement(),
4134 _representation: std::marker::PhantomData,
4135 })
4136 }
4137
4138 /// Exclusively borrow the host elements without a runtime check.
4139 ///
4140 /// # Examples
4141 ///
4142 /// ```
4143 /// use tenferro_tensor::{DynRank, Host, TypedTensorViewMut};
4144 ///
4145 /// let mut data = [1_i32, 2];
4146 /// let mut view: TypedTensorViewMut<'_, i32, DynRank, Host> =
4147 /// TypedTensorViewMut::from_host_slice(vec![2], vec![1], 0, &mut data)?;
4148 /// view.as_host_slice_mut()[0] = 5;
4149 /// assert_eq!(data, [5, 2]);
4150 /// # Ok::<(), tenferro_tensor::Error>(())
4151 /// ```
4152 ///
4153 /// # Panics
4154 ///
4155 /// Panics only if a `Host`-marked view was built from non-host storage,
4156 /// which no constructor in this crate does.
4157 pub fn as_host_slice_mut(&mut self) -> &mut [T] {
4158 match &mut self.buffer {
4159 TensorStorageRefMut::Host(data) => data,
4160 // INVARIANT: `Host`-marked views are constructed only by
4161 // `from_host_slice` and by `TypedTensor<_, _, Host>::as_view_mut`.
4162 TensorStorageRefMut::Backend(_) => {
4163 unreachable!("a Host-marked view always borrows host storage")
4164 }
4165 }
4166 }
4167}
4168
4169impl<'a, T: 'static, R: TensorRank, D: Representation> TypedTensorViewMut<'a, T, R, D> {
4170 /// Return the logical shape.
4171 ///
4172 /// # Examples
4173 ///
4174 /// ```rust
4175 /// use tenferro_tensor::TypedTensorViewMut;
4176 ///
4177 /// let mut data = [0_i32; 2];
4178 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4179 /// assert_eq!(view.shape(), &[2]);
4180 /// # Ok::<(), tenferro_tensor::Error>(())
4181 /// ```
4182 pub fn shape(&self) -> &[usize] {
4183 self.layout.shape()
4184 }
4185
4186 /// Return the logical rank carried by this mutable view.
4187 ///
4188 /// # Examples
4189 ///
4190 /// ```
4191 /// use tenferro_tensor::TypedTensorViewMut;
4192 ///
4193 /// let mut data = [0_i32; 6];
4194 /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
4195 /// assert_eq!(view.rank(), 2);
4196 /// # Ok::<(), tenferro_tensor::Error>(())
4197 /// ```
4198 pub fn rank(&self) -> usize {
4199 self.shape().len()
4200 }
4201
4202 /// Return strides in element units.
4203 ///
4204 /// # Examples
4205 ///
4206 /// ```rust
4207 /// use tenferro_tensor::TypedTensorViewMut;
4208 ///
4209 /// let mut data = [0_i32; 2];
4210 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![-1], 1, &mut data)?;
4211 /// assert_eq!(view.strides(), &[-1]);
4212 /// # Ok::<(), tenferro_tensor::Error>(())
4213 /// ```
4214 pub fn strides(&self) -> &[isize] {
4215 self.layout.strides()
4216 }
4217
4218 /// Return the physical element offset.
4219 ///
4220 /// # Examples
4221 ///
4222 /// ```rust
4223 /// use tenferro_tensor::TypedTensorViewMut;
4224 ///
4225 /// let mut data = [1_i32, 2];
4226 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 1, &mut data)?;
4227 /// assert_eq!(view.offset(), 1);
4228 /// # Ok::<(), tenferro_tensor::Error>(())
4229 /// ```
4230 pub fn offset(&self) -> isize {
4231 self.layout.offset()
4232 }
4233
4234 /// Return the borrowed host storage backing this view.
4235 ///
4236 /// This exposes the entire backing host allocation, not just the logical
4237 /// slice covered by this view. Use [`TypedTensorViewMut::as_read_only`]
4238 /// with [`TypedTensorView::as_slice`] when the caller needs the contiguous
4239 /// logical region instead.
4240 ///
4241 /// # Examples
4242 ///
4243 /// ```rust
4244 /// use tenferro_tensor::TypedTensorViewMut;
4245 ///
4246 /// let mut data = [1_i32, 2];
4247 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4248 /// assert_eq!(view.host_storage()?, &[1, 2]);
4249 /// # Ok::<(), tenferro_tensor::Error>(())
4250 /// ```
4251 /// # Errors
4252 ///
4253 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
4254 /// buffer; backend storage must be downloaded before host inspection.
4255 pub fn host_storage(&self) -> crate::Result<&[T]> {
4256 match &self.buffer {
4257 TensorStorageRefMut::Host(data) => Ok(data),
4258 TensorStorageRefMut::Backend(_) => Err(crate::Error::runtime_state(
4259 "TypedTensorViewMut::host_storage",
4260 "backend buffers cannot expose host storage; download explicitly first",
4261 )),
4262 }
4263 }
4264
4265 /// Mutably borrow the host storage backing this view.
4266 ///
4267 /// This exposes the entire backing host allocation, not just the logical
4268 /// slice covered by this view. Prefer scalar element accessors when mutating
4269 /// a logical region; tensor-sized copies belong to an active backend.
4270 ///
4271 /// # Examples
4272 ///
4273 /// ```rust
4274 /// use tenferro_tensor::TypedTensorViewMut;
4275 ///
4276 /// let mut data = [1_i32, 2];
4277 /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4278 /// view.host_storage_mut()?[0] = 3;
4279 /// assert_eq!(view.get(&[0]), Some(&3));
4280 /// # Ok::<(), tenferro_tensor::Error>(())
4281 /// ```
4282 /// # Errors
4283 ///
4284 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
4285 /// buffer; backend storage must be downloaded before host inspection.
4286 pub fn host_storage_mut(&mut self) -> crate::Result<&mut [T]> {
4287 match &mut self.buffer {
4288 TensorStorageRefMut::Host(data) => Ok(data),
4289 TensorStorageRefMut::Backend(_) => Err(crate::Error::runtime_state(
4290 "TypedTensorViewMut::host_storage_mut",
4291 "backend buffers cannot expose mutable host storage; download explicitly first",
4292 )),
4293 }
4294 }
4295
4296 /// Return the number of logical elements in this view.
4297 ///
4298 /// # Examples
4299 ///
4300 /// ```rust
4301 /// use tenferro_tensor::TypedTensorViewMut;
4302 ///
4303 /// let mut data = [0_i32; 6];
4304 /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
4305 /// assert_eq!(view.n_elements(), 6);
4306 /// # Ok::<(), tenferro_tensor::Error>(())
4307 /// ```
4308 pub fn n_elements(&self) -> usize {
4309 // Invariant: public mutable view constructors validate logical element count.
4310 match checked_view_element_count(self.shape(), "TypedTensorViewMut::n_elements") {
4311 Ok(n) => n,
4312 Err(err) => {
4313 unreachable!("TypedTensorViewMut layout shape is validated at construction: {err}")
4314 }
4315 }
4316 }
4317
4318 /// Return layout metadata for this view.
4319 ///
4320 /// # Examples
4321 ///
4322 /// ```rust
4323 /// use tenferro_tensor::TypedTensorViewMut;
4324 ///
4325 /// let mut data = [1_i32, 2];
4326 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4327 /// assert!(view.layout().is_compact_col_major().unwrap());
4328 /// # Ok::<(), tenferro_tensor::Error>(())
4329 /// ```
4330 pub fn layout(&self) -> &TensorLayout<R> {
4331 &self.layout
4332 }
4333
4334 /// Return placement metadata for this view.
4335 ///
4336 /// # Examples
4337 ///
4338 /// ```rust
4339 /// use tenferro_tensor::{MemoryKind, TypedTensorViewMut};
4340 ///
4341 /// let mut data = [1_i32];
4342 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4343 /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
4344 /// # Ok::<(), tenferro_tensor::Error>(())
4345 /// ```
4346 pub fn placement(&self) -> &Placement {
4347 &self.placement
4348 }
4349
4350 /// Return the backend allocation for backend integrations.
4351 #[doc(hidden)]
4352 pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>> {
4353 match &self.buffer {
4354 TensorStorageRefMut::Host(_) => None,
4355 TensorStorageRefMut::Backend(buffer) => Some(&**buffer),
4356 }
4357 }
4358
4359 /// Prepare this backend view for one provider-native write binding.
4360 #[doc(hidden)]
4361 pub fn prepare_device_write(
4362 &mut self,
4363 op: &'static str,
4364 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
4365 where
4366 T: TensorScalar + 'static,
4367 {
4368 let layout = self.layout.clone();
4369 if self.root.is_none() {
4370 let buffer = self.backend_buffer().ok_or_else(|| {
4371 crate::Error::runtime_state_source(
4372 op,
4373 crate::AccessError::Unsupported { backend: "host" },
4374 )
4375 })?;
4376 return prepare_backend_access(buffer, &self.layout, op);
4377 }
4378 let root = self
4379 .root
4380 .as_mut()
4381 .ok_or_else(|| crate::Error::runtime_state(op, "expected a root-backed tensor view"))?;
4382 root.prepare_device_write_for_layout(&layout)
4383 .map_err(|error| crate::Error::runtime_state_source(op, error))
4384 }
4385
4386 /// Compute the physical element offset for a logical index.
4387 ///
4388 /// # Examples
4389 ///
4390 /// ```rust
4391 /// use tenferro_tensor::TypedTensorViewMut;
4392 ///
4393 /// let mut data = [1_i32, 2, 3];
4394 /// let view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
4395 /// assert_eq!(view.linear_offset(&[2]), Some(0));
4396 /// # Ok::<(), tenferro_tensor::Error>(())
4397 /// ```
4398 pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
4399 checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
4400 }
4401
4402 /// Compute the physical element offset for a logical index, returning a typed error.
4403 ///
4404 /// # Examples
4405 ///
4406 /// ```rust
4407 /// use tenferro_tensor::TypedTensorViewMut;
4408 ///
4409 /// let mut data = [1_i32, 2, 3];
4410 /// let view = TypedTensorViewMut::from_slice([3], [-1], 2, &mut data)?;
4411 /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
4412 /// # Ok::<(), tenferro_tensor::Error>(())
4413 /// ```
4414 /// # Errors
4415 ///
4416 /// Returns [`crate::Error::Validation`] with
4417 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
4418 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
4419 /// when an index is outside its axis extent, or
4420 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
4421 /// arithmetic overflows.
4422 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
4423 checked_view_offset_result(
4424 self.shape(),
4425 self.strides(),
4426 self.offset(),
4427 indices,
4428 "TypedTensorViewMut::layout_linear_offset",
4429 )
4430 }
4431
4432 /// Return whether this mutable view is compact column-major.
4433 ///
4434 /// # Examples
4435 ///
4436 /// ```rust
4437 /// use tenferro_tensor::TypedTensorViewMut;
4438 ///
4439 /// let mut data = [1_i32, 2];
4440 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4441 /// assert!(view.is_col_major_contiguous()?);
4442 /// # Ok::<(), tenferro_tensor::Error>(())
4443 /// ```
4444 /// # Errors
4445 ///
4446 /// Returns [`crate::Error::Validation`] with
4447 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4448 /// compactness arithmetic overflows.
4449 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
4450 self.layout
4451 .is_compact_col_major()
4452 .map_err(|err| tensor_layout_error("TypedTensorViewMut::is_col_major_contiguous", err))
4453 }
4454
4455 /// Return a compact string summary of this mutable view's layout metadata.
4456 ///
4457 /// # Examples
4458 ///
4459 /// ```rust
4460 /// use tenferro_tensor::TypedTensorViewMut;
4461 ///
4462 /// let mut data = [1_i32, 2];
4463 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4464 /// assert!(view.layout_summary().contains("shape=[2]"));
4465 /// # Ok::<(), tenferro_tensor::Error>(())
4466 /// ```
4467 pub fn layout_summary(&self) -> String {
4468 layout_summary(self.shape(), self.strides(), self.offset())
4469 }
4470
4471 /// Assert this mutable view is compact column-major.
4472 ///
4473 /// # Examples
4474 ///
4475 /// ```rust
4476 /// use tenferro_tensor::TypedTensorViewMut;
4477 ///
4478 /// let mut data = [1_i32, 2];
4479 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
4480 /// view.assert_col_major_contiguous()?;
4481 /// # Ok::<(), tenferro_tensor::Error>(())
4482 /// ```
4483 /// # Errors
4484 ///
4485 /// Returns [`crate::Error::Validation`] with
4486 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4487 /// compactness arithmetic overflows, or
4488 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
4489 /// view is not compact column-major.
4490 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
4491 assert_layout_col_major_contiguous(
4492 self.is_col_major_contiguous()?,
4493 self.shape(),
4494 self.strides(),
4495 self.offset(),
4496 "TypedTensorViewMut::assert_col_major_contiguous",
4497 )
4498 }
4499
4500 /// Borrow one host element by logical index.
4501 ///
4502 /// # Examples
4503 ///
4504 /// ```rust
4505 /// use tenferro_tensor::TypedTensorViewMut;
4506 ///
4507 /// let mut data = [1_i32, 2];
4508 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4509 /// assert_eq!(view.get(&[1]), Some(&2));
4510 /// # Ok::<(), tenferro_tensor::Error>(())
4511 /// ```
4512 pub fn get(&self, indices: &[usize]) -> Option<&T> {
4513 let offset = self.linear_offset(indices)?;
4514 match &self.buffer {
4515 TensorStorageRefMut::Host(data) => data.get(offset),
4516 TensorStorageRefMut::Backend(_) => None,
4517 }
4518 }
4519
4520 /// Mutably borrow one host element by logical index.
4521 ///
4522 /// # Examples
4523 ///
4524 /// ```rust
4525 /// use tenferro_tensor::TypedTensorViewMut;
4526 ///
4527 /// let mut data = [1_i32, 2];
4528 /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4529 /// *view.get_mut(&[1]).unwrap() = 20;
4530 /// assert_eq!(view.get(&[1]), Some(&20));
4531 /// # Ok::<(), tenferro_tensor::Error>(())
4532 /// ```
4533 pub fn get_mut(&mut self, indices: &[usize]) -> Option<&mut T> {
4534 let offset = self.linear_offset(indices)?;
4535 match &mut self.buffer {
4536 TensorStorageRefMut::Host(data) => data.get_mut(offset),
4537 TensorStorageRefMut::Backend(_) => None,
4538 }
4539 }
4540
4541 /// Borrow this mutable view as a read-only view.
4542 ///
4543 /// # Examples
4544 ///
4545 /// ```rust
4546 /// use tenferro_tensor::TypedTensorViewMut;
4547 ///
4548 /// let mut data = [1_i32];
4549 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4550 /// assert_eq!(view.as_read_only().get(&[0]), Some(&1));
4551 /// # Ok::<(), tenferro_tensor::Error>(())
4552 /// ```
4553 /// Explicitly duplicate the compact host data visible through this
4554 /// mutable view into a new owner.
4555 ///
4556 /// # Errors
4557 ///
4558 /// Returns [`crate::Error::HostAccess`] or
4559 /// [`ValidationError::NonContiguousViewAsSlice`] when the view is backend
4560 /// owned or not contiguous, and [`ValidationError::InvalidArgument`] when
4561 /// the static-rank shape cannot be reconstructed.
4562 ///
4563 /// # Examples
4564 ///
4565 /// ```
4566 /// use tenferro_tensor::TypedTensorViewMut;
4567 ///
4568 /// let mut data = [1_i32, 2];
4569 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4570 /// let copy = view.duplicate()?;
4571 /// assert_eq!(copy.as_slice()?, &[1, 2]);
4572 /// # Ok::<(), tenferro_tensor::Error>(())
4573 /// ```
4574 pub fn duplicate(&self) -> crate::Result<TypedTensor<T, R>>
4575 where
4576 T: Clone,
4577 {
4578 self.as_read_only().duplicate()
4579 }
4580
4581 /// Explicitly copy this host view to a compact column-major owner.
4582 ///
4583 /// # Examples
4584 /// ```
4585 /// use tenferro_tensor::TypedTensorViewMut;
4586 /// let mut values = ["a".to_owned(), "b".to_owned()];
4587 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut values)?;
4588 /// assert_eq!(view.to_col_major()?.as_slice()?, &["a", "b"]);
4589 /// # Ok::<(), tenferro_tensor::Error>(())
4590 /// ```
4591 /// # Errors
4592 /// Backend-only storage returns [`crate::Error::RuntimeState`]; invalid
4593 /// layout or rank conversion returns [`crate::Error::Validation`].
4594 pub fn to_col_major(&self) -> crate::Result<TypedTensor<T, R>>
4595 where
4596 T: Clone,
4597 {
4598 self.as_read_only().to_col_major()
4599 }
4600
4601 /// Borrow this mutable view as a read-only typed view over the same storage.
4602 ///
4603 /// # Examples
4604 ///
4605 /// ```
4606 /// use tenferro_tensor::TypedTensorViewMut;
4607 ///
4608 /// let mut data = [1_i32, 2];
4609 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
4610 /// assert_eq!(view.as_read_only().as_slice()?, &[1, 2]);
4611 /// # Ok::<(), tenferro_tensor::Error>(())
4612 /// ```
4613 pub fn as_read_only(&self) -> TypedTensorView<'_, T, R, D> {
4614 let buffer = match &self.buffer {
4615 TensorStorageRefMut::Host(data) => TensorStorageRef::Host(data),
4616 TensorStorageRefMut::Backend(buffer) => TensorStorageRef::Backend(&**buffer),
4617 };
4618 TypedTensorView {
4619 buffer,
4620 root: None,
4621 layout: self.layout.clone(),
4622 placement: self.placement.clone(),
4623 _representation: std::marker::PhantomData,
4624 }
4625 }
4626
4627 /// Convert this mutable view into a read-only view.
4628 ///
4629 /// # Examples
4630 ///
4631 /// ```rust
4632 /// use tenferro_tensor::TypedTensorViewMut;
4633 ///
4634 /// let mut data = [1_i32];
4635 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
4636 /// assert_eq!(view.into_read_only().get(&[0]), Some(&1));
4637 /// # Ok::<(), tenferro_tensor::Error>(())
4638 /// ```
4639 pub fn into_read_only(self) -> TypedTensorView<'a, T, R, D> {
4640 let buffer = match self.buffer {
4641 TensorStorageRefMut::Host(data) => TensorStorageRef::Host(data),
4642 TensorStorageRefMut::Backend(buffer) => TensorStorageRef::Backend(buffer),
4643 };
4644 TypedTensorView {
4645 buffer,
4646 root: None,
4647 layout: self.layout,
4648 placement: self.placement,
4649 _representation: std::marker::PhantomData,
4650 }
4651 }
4652
4653 /// Consume this mutable view and return a metadata-only axis permutation.
4654 ///
4655 /// # Examples
4656 ///
4657 /// ```rust
4658 /// use tenferro_tensor::{Rank, TypedTensorViewMut};
4659 ///
4660 /// let mut data = [1_i32, 2, 3, 4];
4661 /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
4662 /// let transposed = view.transpose_view([1, 0])?;
4663 /// assert_eq!(transposed.strides(), &[2, 1]);
4664 /// # Ok::<(), tenferro_tensor::Error>(())
4665 /// ```
4666 /// # Errors
4667 ///
4668 /// Returns [`crate::Error::Validation`] with
4669 /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
4670 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
4671 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
4672 /// not a valid permutation, [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`]
4673 /// when the permutation creates aliases, or
4674 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4675 /// arithmetic overflow.
4676 pub fn transpose_view(
4677 self,
4678 axes: impl AsRef<[usize]>,
4679 ) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
4680 let Self {
4681 buffer,
4682 root,
4683 layout,
4684 placement,
4685 _representation: std::marker::PhantomData,
4686 } = self;
4687 let layout = layout
4688 .transpose_view(axes)
4689 .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
4690 layout
4691 .validate_mutable_no_overlap()
4692 .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
4693 match buffer {
4694 TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
4695 buffer: TensorStorageRefMut::Host(data),
4696 root,
4697 layout,
4698 placement,
4699 _representation: std::marker::PhantomData,
4700 }),
4701 TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
4702 buffer: TensorStorageRefMut::Backend(buffer),
4703 root,
4704 layout,
4705 placement,
4706 _representation: std::marker::PhantomData,
4707 }),
4708 }
4709 }
4710
4711 /// Return a mutable metadata-only slice using one [`StridedSliceSpec`] per axis.
4712 ///
4713 /// # Examples
4714 ///
4715 /// ```rust
4716 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
4717 ///
4718 /// let mut data = [1_i32, 2, 3];
4719 /// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![1], 0, &mut data)?;
4720 /// *view.slice_view(&[StridedSliceSpec::reverse()])?.get_mut(&[0]).unwrap() = 30;
4721 /// assert_eq!(view.get(&[2]), Some(&30));
4722 /// # Ok::<(), tenferro_tensor::Error>(())
4723 /// ```
4724 /// # Errors
4725 ///
4726 /// Returns [`crate::Error::Validation`] with
4727 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
4728 /// count differs from the view rank,
4729 /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
4730 /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
4731 /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
4732 /// when the result exceeds the backing buffer,
4733 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4734 /// logical elements alias, or
4735 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4736 /// arithmetic overflow.
4737 pub fn slice_view(
4738 &mut self,
4739 slices: &[StridedSliceSpec],
4740 ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
4741 let specs = core_slice_specs(slices, self.shape(), "TypedTensorViewMut::slice_view")?;
4742 let layout = self
4743 .layout
4744 .slice_view(specs, self.buffer.len())
4745 .map_err(|err| tensor_layout_error("TypedTensorViewMut::slice_view", err))?;
4746 layout
4747 .validate_mutable_no_overlap()
4748 .map_err(|err| tensor_layout_error("TypedTensorViewMut::slice_view", err))?;
4749 let placement = self.placement.clone();
4750 match &mut self.buffer {
4751 TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
4752 buffer: TensorStorageRefMut::Host(data),
4753 root: None,
4754 layout,
4755 placement,
4756 _representation: std::marker::PhantomData,
4757 }),
4758 TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
4759 buffer: TensorStorageRefMut::Backend(*buffer),
4760 root: None,
4761 layout,
4762 placement,
4763 _representation: std::marker::PhantomData,
4764 }),
4765 }
4766 }
4767
4768 /// Return a mutable metadata-only slice along one axis.
4769 ///
4770 /// # Examples
4771 ///
4772 /// ```rust
4773 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
4774 ///
4775 /// let mut data = [1_i32, 2, 3, 4];
4776 /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
4777 /// assert_eq!(view.slice_axis_view(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
4778 /// # Ok::<(), tenferro_tensor::Error>(())
4779 /// ```
4780 /// # Errors
4781 ///
4782 /// Returns [`crate::Error::Validation`] with
4783 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
4784 /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
4785 /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
4786 /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
4787 /// when the result exceeds the backing buffer,
4788 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4789 /// logical elements alias, or
4790 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4791 /// arithmetic overflow.
4792 pub fn slice_axis_view(
4793 &mut self,
4794 axis: usize,
4795 slice: StridedSliceSpec,
4796 ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
4797 let slices = slice_axis_specs(
4798 self.shape().len(),
4799 axis,
4800 slice,
4801 "TypedTensorViewMut::slice_axis_view",
4802 )?;
4803 self.slice_view(&slices)
4804 }
4805
4806 /// Return two mutable metadata-only slices when their physical ranges are disjoint.
4807 ///
4808 /// # Examples
4809 ///
4810 /// ```rust
4811 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
4812 ///
4813 /// let mut data = [1_i32, 2, 3, 4];
4814 /// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
4815 /// let (left, right) = view
4816 /// .try_multi_slice_mut(
4817 /// &[StridedSliceSpec::new(0, Some(2), 1)],
4818 /// &[StridedSliceSpec::new(2, Some(4), 1)],
4819 /// )
4820 /// ?
4821 /// .unwrap();
4822 /// assert_eq!(left.shape(), &[2]);
4823 /// assert_eq!(right.shape(), &[2]);
4824 /// # Ok::<(), tenferro_tensor::Error>(())
4825 /// ```
4826 /// # Errors
4827 ///
4828 /// Returns [`crate::Error::Validation`] with
4829 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for either
4830 /// slice count, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
4831 /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for
4832 /// invalid parameters, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
4833 /// when a result exceeds the backing buffer,
4834 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4835 /// a result aliases, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
4836 /// for a negative reachable offset, or
4837 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
4838 /// arithmetic overflow. The method returns `Ok(None)` when the two ranges
4839 /// overlap or the view uses backend storage.
4840 pub fn try_multi_slice_mut(
4841 &mut self,
4842 first: &[StridedSliceSpec],
4843 second: &[StridedSliceSpec],
4844 ) -> crate::Result<Option<TypedTensorViewMutSplit<'_, T, R>>> {
4845 let op = "TypedTensorViewMut::try_multi_slice_mut";
4846 let first_specs = core_slice_specs(first, self.shape(), op)?;
4847 let second_specs = core_slice_specs(second, self.shape(), op)?;
4848 let buffer_len = self.buffer.len();
4849 let first_layout = self
4850 .layout
4851 .slice_view(first_specs, buffer_len)
4852 .map_err(|err| tensor_layout_error(op, err))?;
4853 let second_layout = self
4854 .layout
4855 .slice_view(second_specs, buffer_len)
4856 .map_err(|err| tensor_layout_error(op, err))?;
4857 first_layout
4858 .validate_mutable_no_overlap()
4859 .map_err(|err| tensor_layout_error(op, err))?;
4860 second_layout
4861 .validate_mutable_no_overlap()
4862 .map_err(|err| tensor_layout_error(op, err))?;
4863
4864 match (
4865 reachable_layout_span(
4866 first_layout.shape(),
4867 first_layout.strides(),
4868 first_layout.offset(),
4869 )?,
4870 reachable_layout_span(
4871 second_layout.shape(),
4872 second_layout.strides(),
4873 second_layout.offset(),
4874 )?,
4875 ) {
4876 (Some(first_span), Some(second_span)) => {
4877 let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
4878 let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
4879 let (first_data, second_data) = match &mut self.buffer {
4880 TensorStorageRefMut::Host(data) => {
4881 match split_two_mut_ranges(data, first_span, second_span) {
4882 Some(ranges) => ranges,
4883 None => return Ok(None),
4884 }
4885 }
4886 TensorStorageRefMut::Backend(_) => return Ok(None),
4887 };
4888 let first_view = view_mut_from_layout_and_slice(
4889 &first_layout,
4890 first_offset,
4891 first_data,
4892 self.placement.clone(),
4893 )?;
4894 let second_view = view_mut_from_layout_and_slice(
4895 &second_layout,
4896 second_offset,
4897 second_data,
4898 self.placement.clone(),
4899 )?;
4900 Ok(Some((first_view, second_view)))
4901 }
4902 (None, Some(second_span)) => {
4903 let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
4904 let (_, after_start) = match &mut self.buffer {
4905 TensorStorageRefMut::Host(data) => data.split_at_mut(second_span.0),
4906 TensorStorageRefMut::Backend(_) => return Ok(None),
4907 };
4908 let (second_data, _) = after_start.split_at_mut(second_span.1 - second_span.0 + 1);
4909 let first_view = view_mut_from_layout_and_slice(
4910 &first_layout,
4911 0,
4912 &mut [],
4913 self.placement.clone(),
4914 )?;
4915 let second_view = view_mut_from_layout_and_slice(
4916 &second_layout,
4917 second_offset,
4918 second_data,
4919 self.placement.clone(),
4920 )?;
4921 Ok(Some((first_view, second_view)))
4922 }
4923 (Some(first_span), None) => {
4924 let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
4925 let (_, after_start) = match &mut self.buffer {
4926 TensorStorageRefMut::Host(data) => data.split_at_mut(first_span.0),
4927 TensorStorageRefMut::Backend(_) => return Ok(None),
4928 };
4929 let (first_data, _) = after_start.split_at_mut(first_span.1 - first_span.0 + 1);
4930 let first_view = view_mut_from_layout_and_slice(
4931 &first_layout,
4932 first_offset,
4933 first_data,
4934 self.placement.clone(),
4935 )?;
4936 let second_view = view_mut_from_layout_and_slice(
4937 &second_layout,
4938 0,
4939 &mut [],
4940 self.placement.clone(),
4941 )?;
4942 Ok(Some((first_view, second_view)))
4943 }
4944 (None, None) => {
4945 let first_view = view_mut_from_layout_and_slice(
4946 &first_layout,
4947 0,
4948 &mut [],
4949 self.placement.clone(),
4950 )?;
4951 let second_view = view_mut_from_layout_and_slice(
4952 &second_layout,
4953 0,
4954 &mut [],
4955 self.placement.clone(),
4956 )?;
4957 Ok(Some((first_view, second_view)))
4958 }
4959 }
4960 }
4961
4962 /// Return a mutable metadata-only dynamic-rank reshape for contiguous views.
4963 ///
4964 /// # Examples
4965 ///
4966 /// ```rust
4967 /// use tenferro_tensor::TypedTensorViewMut;
4968 ///
4969 /// let mut data = [1_i32, 2, 3, 4];
4970 /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
4971 /// assert_eq!(view.reshape_view(&[4])?.shape(), &[4]);
4972 /// # Ok::<(), tenferro_tensor::Error>(())
4973 /// ```
4974 /// # Errors
4975 ///
4976 /// Returns [`crate::Error::Validation`] with
4977 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
4978 /// the source is not compact column-major,
4979 /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
4980 /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
4981 /// records the counts) when element counts differ,
4982 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
4983 /// the reshaped layout aliases, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
4984 /// for shape or layout arithmetic overflow, or
4985 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4986 /// reshaped view exceeds the backing buffer.
4987 pub fn reshape_view(
4988 &mut self,
4989 shape: &[usize],
4990 ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank, D>> {
4991 let layout = reshape_layout_dyn(
4992 &self.layout,
4993 shape,
4994 self.buffer.len(),
4995 "TypedTensorViewMut::reshape_view",
4996 )?;
4997 layout
4998 .validate_mutable_no_overlap()
4999 .map_err(|err| tensor_layout_error("TypedTensorViewMut::reshape_view", err))?;
5000 let placement = self.placement.clone();
5001 match &mut self.buffer {
5002 TensorStorageRefMut::Host(data) => Ok(TypedTensorViewMut {
5003 buffer: TensorStorageRefMut::Host(data),
5004 root: None,
5005 layout,
5006 placement,
5007 _representation: std::marker::PhantomData,
5008 }),
5009 TensorStorageRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
5010 buffer: TensorStorageRefMut::Backend(*buffer),
5011 root: None,
5012 layout,
5013 placement,
5014 _representation: std::marker::PhantomData,
5015 }),
5016 }
5017 }
5018}
5019
5020impl<'a, R: TensorRank> TypedTensorViewMut<'a, Complex32, R> {
5021 /// Borrow this mutable complex view as an interleaved real view.
5022 ///
5023 /// This changes only the typed descriptor and borrows the same host
5024 /// allocation. The result has dynamic rank because the component axis is
5025 /// prepended. Backend-native buffers are rejected until their provider
5026 /// phase supplies the corresponding mapping capability.
5027 ///
5028 /// # Examples
5029 ///
5030 /// ```
5031 /// use tenferro_tensor::{Complex32, TypedTensorViewMut};
5032 ///
5033 /// let mut data = [Complex32::new(1.0, 2.0)];
5034 /// let mut view = TypedTensorViewMut::from_col_major(&[1], &mut data)?;
5035 /// let real = view.as_real_view_mut()?;
5036 /// assert_eq!(real.shape(), &[2, 1]);
5037 /// assert_eq!(real.as_read_only().as_slice()?, &[1.0_f32, 2.0]);
5038 /// # Ok::<(), tenferro_tensor::Error>(())
5039 /// ```
5040 ///
5041 /// # Errors
5042 ///
5043 /// Returns an error when the view layout is not injective, the sealed
5044 /// representation is invalid, or backend reinterpretation is unsupported.
5045 pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
5046 let op = "TypedTensorViewMut::as_real_view_mut";
5047 validate_representation_pair(op, DType::C32, DType::F32)?;
5048 let layout = reinterpret_complex_to_real_layout(
5049 self.shape(),
5050 self.strides(),
5051 self.offset(),
5052 self.buffer.len(),
5053 op,
5054 )?;
5055 layout
5056 .validate_mutable_no_overlap()
5057 .map_err(|err| tensor_layout_error(op, err))?;
5058 let buffer = match &mut self.buffer {
5059 TensorStorageRefMut::Host(data) => {
5060 TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(data, op)?)
5061 }
5062 TensorStorageRefMut::Backend(_) => {
5063 return Err(crate::Error::unsupported(
5064 op,
5065 "backend representation reinterpretation is enabled by the provider phases",
5066 ))
5067 }
5068 };
5069 Ok(TypedTensorViewMut {
5070 buffer,
5071 root: None,
5072 layout,
5073 placement: self.placement.clone(),
5074 _representation: std::marker::PhantomData,
5075 })
5076 }
5077}
5078
5079impl<'a, R: TensorRank> TypedTensorViewMut<'a, Complex64, R> {
5080 /// Borrow this mutable complex view as an interleaved real view.
5081 ///
5082 /// This changes only the typed descriptor and borrows the same host
5083 /// allocation. The result has dynamic rank because the component axis is
5084 /// prepended. Backend-native buffers are rejected until their provider
5085 /// phase supplies the corresponding mapping capability.
5086 ///
5087 /// # Examples
5088 ///
5089 /// ```
5090 /// use tenferro_tensor::{Complex64, TypedTensorViewMut};
5091 ///
5092 /// let mut data = [Complex64::new(1.0, 2.0)];
5093 /// let mut view = TypedTensorViewMut::from_col_major(&[1], &mut data)?;
5094 /// let real = view.as_real_view_mut()?;
5095 /// assert_eq!(real.shape(), &[2, 1]);
5096 /// assert_eq!(real.as_read_only().as_slice()?, &[1.0_f64, 2.0]);
5097 /// # Ok::<(), tenferro_tensor::Error>(())
5098 /// ```
5099 ///
5100 /// # Errors
5101 ///
5102 /// Returns an error when the view layout is not injective, the sealed
5103 /// representation is invalid, or backend reinterpretation is unsupported.
5104 pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
5105 let op = "TypedTensorViewMut::as_real_view_mut";
5106 validate_representation_pair(op, DType::C64, DType::F64)?;
5107 let layout = reinterpret_complex_to_real_layout(
5108 self.shape(),
5109 self.strides(),
5110 self.offset(),
5111 self.buffer.len(),
5112 op,
5113 )?;
5114 layout
5115 .validate_mutable_no_overlap()
5116 .map_err(|err| tensor_layout_error(op, err))?;
5117 let buffer = match &mut self.buffer {
5118 TensorStorageRefMut::Host(data) => {
5119 TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(data, op)?)
5120 }
5121 TensorStorageRefMut::Backend(_) => {
5122 return Err(crate::Error::unsupported(
5123 op,
5124 "backend representation reinterpretation is enabled by the provider phases",
5125 ))
5126 }
5127 };
5128 Ok(TypedTensorViewMut {
5129 buffer,
5130 root: None,
5131 layout,
5132 placement: self.placement.clone(),
5133 _representation: std::marker::PhantomData,
5134 })
5135 }
5136}
5137
5138impl<'a, R: TensorRank> TypedTensorViewMut<'a, f32, R> {
5139 /// Borrow this mutable interleaved real view as a complex view.
5140 ///
5141 /// The source must have a leading extent and stride of `2` and `1`, and
5142 /// all remaining strides plus the offset must be divisible by `2`.
5143 ///
5144 /// # Examples
5145 ///
5146 /// ```
5147 /// use tenferro_tensor::{Complex32, TypedTensorViewMut};
5148 ///
5149 /// let mut data = [1.0_f32, 2.0];
5150 /// let mut view = TypedTensorViewMut::from_col_major(&[2, 1], &mut data)?;
5151 /// let complex = view.as_complex_view_mut()?;
5152 /// assert_eq!(complex.shape(), &[1]);
5153 /// assert_eq!(complex.as_read_only().as_slice()?, &[Complex32::new(1.0, 2.0)]);
5154 /// # Ok::<(), tenferro_tensor::Error>(())
5155 /// ```
5156 ///
5157 /// # Errors
5158 ///
5159 /// Returns an error when the view layout is not injective, the sealed
5160 /// representation is invalid, or backend reinterpretation is unsupported.
5161 pub fn as_complex_view_mut(
5162 &mut self,
5163 ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
5164 let op = "TypedTensorViewMut::as_complex_view_mut";
5165 validate_representation_pair(op, DType::F32, DType::C32)?;
5166 let layout = reinterpret_real_to_complex_layout(
5167 self.shape(),
5168 self.strides(),
5169 self.offset(),
5170 self.buffer.len(),
5171 op,
5172 )?;
5173 layout
5174 .validate_mutable_no_overlap()
5175 .map_err(|err| tensor_layout_error(op, err))?;
5176 let buffer = match &mut self.buffer {
5177 TensorStorageRefMut::Host(data) => {
5178 TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(data, op)?)
5179 }
5180 TensorStorageRefMut::Backend(_) => {
5181 return Err(crate::Error::unsupported(
5182 op,
5183 "backend representation reinterpretation is enabled by the provider phases",
5184 ))
5185 }
5186 };
5187 Ok(TypedTensorViewMut {
5188 buffer,
5189 root: None,
5190 layout,
5191 placement: self.placement.clone(),
5192 _representation: std::marker::PhantomData,
5193 })
5194 }
5195}
5196
5197impl<'a, R: TensorRank> TypedTensorViewMut<'a, f64, R> {
5198 /// Borrow this mutable interleaved real view as a complex view.
5199 ///
5200 /// The source must have a leading extent and stride of `2` and `1`, and
5201 /// all remaining strides plus the offset must be divisible by `2`.
5202 ///
5203 /// # Examples
5204 ///
5205 /// ```
5206 /// use tenferro_tensor::{Complex64, TypedTensorViewMut};
5207 ///
5208 /// let mut data = [1.0_f64, 2.0];
5209 /// let mut view = TypedTensorViewMut::from_col_major(&[2, 1], &mut data)?;
5210 /// let complex = view.as_complex_view_mut()?;
5211 /// assert_eq!(complex.shape(), &[1]);
5212 /// assert_eq!(complex.as_read_only().as_slice()?, &[Complex64::new(1.0, 2.0)]);
5213 /// # Ok::<(), tenferro_tensor::Error>(())
5214 /// ```
5215 ///
5216 /// # Errors
5217 ///
5218 /// Returns an error when the view layout is not injective, the sealed
5219 /// representation is invalid, or backend reinterpretation is unsupported.
5220 pub fn as_complex_view_mut(
5221 &mut self,
5222 ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
5223 let op = "TypedTensorViewMut::as_complex_view_mut";
5224 validate_representation_pair(op, DType::F64, DType::C64)?;
5225 let layout = reinterpret_real_to_complex_layout(
5226 self.shape(),
5227 self.strides(),
5228 self.offset(),
5229 self.buffer.len(),
5230 op,
5231 )?;
5232 layout
5233 .validate_mutable_no_overlap()
5234 .map_err(|err| tensor_layout_error(op, err))?;
5235 let buffer = match &mut self.buffer {
5236 TensorStorageRefMut::Host(data) => {
5237 TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(data, op)?)
5238 }
5239 TensorStorageRefMut::Backend(_) => {
5240 return Err(crate::Error::unsupported(
5241 op,
5242 "backend representation reinterpretation is enabled by the provider phases",
5243 ))
5244 }
5245 };
5246 Ok(TypedTensorViewMut {
5247 buffer,
5248 root: None,
5249 layout,
5250 placement: self.placement.clone(),
5251 _representation: std::marker::PhantomData,
5252 })
5253 }
5254}
5255
5256/// Sealed trait for scalar types that can be stored in a [`Tensor`].
5257///
5258/// This trait is implemented for `f64`, `f32`, `i32`, `i64`, `bool`,
5259/// [`Complex64`], and [`Complex32`].
5260///
5261/// # Examples
5262///
5263/// ```
5264/// use tenferro_tensor::TensorScalar;
5265///
5266/// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
5267/// assert_eq!(tensor.as_slice::<f64>()?, [1.0, 2.0].as_slice());
5268/// # Ok::<(), tenferro_tensor::Error>(())
5269/// ```
5270pub trait TensorScalar: Copy + Clone + Send + Sync + 'static + private::Sealed {
5271 /// Real-valued counterpart of this scalar type.
5272 type Real: TensorScalar;
5273
5274 /// The [`DType`] tag corresponding to this scalar type.
5275 ///
5276 /// # Examples
5277 ///
5278 /// ```
5279 /// use tenferro_tensor::{DType, TensorScalar};
5280 ///
5281 /// assert_eq!(f64::dtype(), DType::F64);
5282 /// assert_eq!(f32::dtype(), DType::F32);
5283 /// ```
5284 fn dtype() -> DType;
5285
5286 /// Build the crate's default scalar set from validated column-major data.
5287 ///
5288 /// # Examples
5289 ///
5290 /// ```
5291 /// use tenferro_tensor::{DType, TensorScalar};
5292 ///
5293 /// let set = <f64 as TensorScalar>::into_default_scalars(vec![2], vec![1.0, 2.0])?;
5294 /// assert_eq!(set.dtype(), DType::F64);
5295 /// # Ok::<(), tenferro_tensor::Error>(())
5296 /// ```
5297 ///
5298 /// # Errors
5299 ///
5300 /// Returns a validation error carrying
5301 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when the shape product
5302 /// differs from `data.len()`, or [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
5303 /// when shape arithmetic overflows.
5304 fn into_default_scalars(
5305 shape: Vec<usize>,
5306 data: Vec<Self>,
5307 ) -> crate::Result<crate::DefaultScalars>;
5308
5309 /// Borrow the default scalar set's values when it holds this scalar type.
5310 ///
5311 /// Returns `None` when the set currently holds another member.
5312 ///
5313 /// # Examples
5314 ///
5315 /// ```
5316 /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5317 ///
5318 /// let set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5319 /// assert_eq!(<f64 as TensorScalar>::default_scalars_slice(&set), Some(&[1.0, 2.0][..]));
5320 /// assert!(<f32 as TensorScalar>::default_scalars_slice(&set).is_none());
5321 /// # Ok::<(), tenferro_tensor::Error>(())
5322 /// ```
5323 fn default_scalars_slice(set: &crate::DefaultScalars) -> Option<&[Self]>;
5324
5325 /// Exclusively borrow the default scalar set's values when it holds this scalar type.
5326 ///
5327 /// Returns `None` when the set currently holds another member.
5328 ///
5329 /// # Examples
5330 ///
5331 /// ```
5332 /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5333 ///
5334 /// let mut set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5335 /// if let Some(values) = <f64 as TensorScalar>::default_scalars_slice_mut(&mut set) {
5336 /// values[0] = 5.0;
5337 /// }
5338 /// assert_eq!(set.as_slice::<f64>()?, &[5.0, 2.0]);
5339 /// # Ok::<(), tenferro_tensor::Error>(())
5340 /// ```
5341 fn default_scalars_slice_mut(set: &mut crate::DefaultScalars) -> Option<&mut [Self]>;
5342
5343 /// Move the host tensor out of the default scalar set when it holds this scalar type.
5344 ///
5345 /// Returns `None` when the set currently holds another member.
5346 ///
5347 /// # Examples
5348 ///
5349 /// ```
5350 /// use tenferro_tensor::{DefaultScalars, TensorScalar};
5351 ///
5352 /// let set = DefaultScalars::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5353 /// let host = <f64 as TensorScalar>::from_default_scalars(set);
5354 /// assert_eq!(host.as_ref().map(|t| t.shape()), Some(&[2][..]));
5355 /// assert_eq!(host.as_ref().map(|t| t.as_slice()), Some(&[1.0, 2.0][..]));
5356 /// # Ok::<(), tenferro_tensor::Error>(())
5357 /// ```
5358 fn from_default_scalars(set: crate::DefaultScalars)
5359 -> Option<TypedTensor<Self, DynRank, Host>>;
5360
5361 /// Wrap typed column-major data into a [`Tensor`] enum variant.
5362 ///
5363 /// # Examples
5364 ///
5365 /// ```
5366 /// use tenferro_tensor::{DType, TensorScalar};
5367 ///
5368 /// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
5369 /// assert_eq!(tensor.dtype(), DType::F64);
5370 /// assert_eq!(tensor.shape(), &[2]);
5371 /// assert!(<f64 as TensorScalar>::into_tensor(vec![3], vec![1.0]).is_err());
5372 /// # Ok::<(), tenferro_tensor::Error>(())
5373 /// ```
5374 ///
5375 /// # Errors
5376 ///
5377 /// Returns [`crate::Error::Validation`] with
5378 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
5379 /// the shape product differs from `data.len()`, or
5380 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
5381 /// arithmetic overflows.
5382 fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor>;
5383
5384 /// Wrap a typed tensor into its dynamic [`Tensor`] enum variant.
5385 ///
5386 /// # Examples
5387 ///
5388 /// ```
5389 /// use tenferro_tensor::{DType, Tensor, TensorScalar, TypedTensor};
5390 ///
5391 /// let typed = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![3.0])?;
5392 /// let tensor = <f64 as TensorScalar>::typed_tensor_into_tensor(typed);
5393 /// assert!(matches!(tensor.dtype(), DType::F64));
5394 /// # Ok::<(), tenferro_tensor::Error>(())
5395 /// ```
5396 fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor;
5397
5398 /// Borrow a typed tensor as a dtype-erased [`TensorRead`] view.
5399 ///
5400 /// This keeps the typed tensor borrowed instead of copying host data into
5401 /// a new dynamic tensor.
5402 ///
5403 /// # Examples
5404 ///
5405 /// ```
5406 /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
5407 ///
5408 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5409 /// let read = f64::tensor_read(&tensor);
5410 /// assert_eq!(read.dtype(), DType::F64);
5411 /// assert_eq!(read.shape(), &[2]);
5412 /// ```
5413 fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_>;
5414
5415 /// Wrap a typed borrowed view as a dtype-erased [`TensorView`].
5416 ///
5417 /// # Examples
5418 ///
5419 /// ```
5420 /// use tenferro_tensor::{DType, TensorScalar, TypedTensorView};
5421 ///
5422 /// let data = [1.0_f64];
5423 /// let view = TypedTensorView::from_col_major(&[1], &data)?;
5424 /// assert_eq!(f64::tensor_view(view).dtype(), DType::F64);
5425 /// # Ok::<(), tenferro_tensor::Error>(())
5426 /// ```
5427 fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a>;
5428
5429 /// Wrap a typed mutable borrowed view as a dtype-erased [`TensorViewMut`].
5430 ///
5431 /// # Examples
5432 ///
5433 /// ```
5434 /// use tenferro_tensor::{DType, TensorScalar, TypedTensorViewMut};
5435 ///
5436 /// let mut data = [1.0_f64, 2.0];
5437 /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
5438 /// let erased = f64::tensor_view_mut(view);
5439 /// assert_eq!(erased.dtype(), DType::F64);
5440 /// assert_eq!(erased.shape(), &[2]);
5441 /// # Ok::<(), tenferro_tensor::Error>(())
5442 /// ```
5443 fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a>;
5444
5445 /// Mutably borrow a typed tensor as a dtype-erased [`TensorWrite`] view.
5446 ///
5447 /// This keeps the typed output borrowed instead of wrapping it in a
5448 /// temporary dynamic tensor.
5449 ///
5450 /// # Examples
5451 ///
5452 /// ```
5453 /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
5454 ///
5455 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0]).unwrap();
5456 /// let write = f64::tensor_write(&mut tensor);
5457 /// assert_eq!(write.dtype(), DType::F64);
5458 /// ```
5459 fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_>;
5460
5461 /// Borrow the host data from a [`Tensor`].
5462 ///
5463 /// # Examples
5464 ///
5465 /// ```
5466 /// use tenferro_tensor::{Tensor, TensorScalar};
5467 ///
5468 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5469 /// assert_eq!(<f64 as TensorScalar>::as_slice(&tensor)?, &[1.0, 2.0]);
5470 /// assert!(<f32 as TensorScalar>::as_slice(&tensor).is_err());
5471 /// # Ok::<(), tenferro_tensor::Error>(())
5472 /// ```
5473 ///
5474 /// # Errors
5475 ///
5476 /// Returns [`crate::Error::Validation`] with
5477 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
5478 /// is not the scalar type represented by this implementation, or
5479 /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
5480 /// storage that has not been downloaded.
5481 fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]>;
5482
5483 /// Mutably borrow the host data from a [`Tensor`].
5484 ///
5485 /// # Examples
5486 ///
5487 /// ```
5488 /// use tenferro_tensor::{Tensor, TensorScalar};
5489 ///
5490 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
5491 /// <f64 as TensorScalar>::as_slice_mut(&mut tensor)?[0] = 3.0;
5492 ///
5493 /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
5494 /// # Ok::<(), tenferro_tensor::Error>(())
5495 /// ```
5496 /// # Errors
5497 ///
5498 /// Returns [`crate::Error::Validation`] with
5499 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
5500 /// is not the scalar type represented by this implementation, or
5501 /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
5502 /// storage that has not been downloaded.
5503 fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]>;
5504
5505 /// Extract a [`TypedTensor<Self>`] from a dynamic [`Tensor`].
5506 ///
5507 /// # Examples
5508 ///
5509 /// ```
5510 /// use tenferro_tensor::{Tensor, TensorScalar};
5511 ///
5512 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5513 /// let Ok(typed) = <f64 as TensorScalar>::into_typed(tensor) else {
5514 /// panic!("the dtype matches by construction")
5515 /// };
5516 ///
5517 /// assert_eq!(typed.as_slice()?, &[1.0, 2.0]);
5518 /// # Ok::<(), tenferro_tensor::Error>(())
5519 /// ```
5520 /// # Errors
5521 ///
5522 /// Returns [`ReinterpretError`] carrying the unchanged tensor when it is
5523 /// not the scalar type represented by this implementation, with
5524 /// [`crate::Error::Validation`] and
5525 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] as the cause.
5526 fn into_typed(
5527 tensor: Tensor,
5528 ) -> std::result::Result<TypedTensor<Self>, ReinterpretError<Tensor>>;
5529}
5530
5531mod private {
5532 pub trait Sealed {}
5533
5534 impl Sealed for f64 {}
5535 impl Sealed for f32 {}
5536 impl Sealed for i32 {}
5537 impl Sealed for i64 {}
5538 impl Sealed for bool {}
5539 impl Sealed for num_complex::Complex64 {}
5540 impl Sealed for num_complex::Complex32 {}
5541}
5542
5543macro_rules! impl_tensor_scalar {
5544 ($ty:ty, $real:ty, $dtype:ident, $variant:ident) => {
5545 impl TensorScalar for $ty {
5546 type Real = $real;
5547
5548 #[inline]
5549 fn dtype() -> DType {
5550 DType::$dtype
5551 }
5552
5553 fn into_default_scalars(
5554 shape: Vec<usize>,
5555 data: Vec<Self>,
5556 ) -> crate::Result<crate::DefaultScalars> {
5557 TypedTensor::<Self, DynRank, Host>::from_host_vec_col_major(shape, data).map(
5558 |tensor| {
5559 crate::DefaultScalars::from_payload(
5560 crate::default_scalars::DefaultScalarsValue::$variant(tensor),
5561 )
5562 },
5563 )
5564 }
5565
5566 fn default_scalars_slice(set: &crate::DefaultScalars) -> Option<&[Self]> {
5567 match set.payload() {
5568 crate::default_scalars::DefaultScalarsValue::$variant(tensor) => {
5569 Some(tensor.as_slice())
5570 }
5571 _ => None,
5572 }
5573 }
5574
5575 fn default_scalars_slice_mut(set: &mut crate::DefaultScalars) -> Option<&mut [Self]> {
5576 match set.payload_mut() {
5577 crate::default_scalars::DefaultScalarsValue::$variant(tensor) => {
5578 Some(tensor.host_data_mut())
5579 }
5580 _ => None,
5581 }
5582 }
5583
5584 fn from_default_scalars(
5585 set: crate::DefaultScalars,
5586 ) -> Option<TypedTensor<Self, DynRank, Host>> {
5587 match set.into_payload() {
5588 crate::default_scalars::DefaultScalarsValue::$variant(tensor) => Some(tensor),
5589 _ => None,
5590 }
5591 }
5592
5593 fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor> {
5594 TypedTensor::from_vec_col_major(shape, data).map(Self::typed_tensor_into_tensor)
5595 }
5596
5597 fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor {
5598 Tensor {
5599 payload: TensorPayload::Native(PresetTensor::$variant(tensor)),
5600 }
5601 }
5602
5603 fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_> {
5604 TensorRead::from_view(TensorView::$variant(tensor.as_view()))
5605 }
5606
5607 #[inline]
5608 fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a> {
5609 TensorView::$variant(view)
5610 }
5611
5612 #[inline]
5613 fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a> {
5614 TensorViewMut::$variant(view)
5615 }
5616
5617 fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_> {
5618 TensorWrite::from_view(TensorViewMut::$variant(tensor.as_view_mut()))
5619 }
5620
5621 fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]> {
5622 tensor
5623 .as_typed::<Self>()
5624 .ok_or_else(|| {
5625 crate::Error::validation(
5626 "Tensor::as_slice",
5627 ValidationError::DTypeMismatch {
5628 expected: Self::dtype(),
5629 actual: tensor.dtype(),
5630 },
5631 )
5632 })?
5633 .host_data()
5634 }
5635
5636 fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]> {
5637 let actual = tensor.dtype();
5638 let typed = tensor.as_typed_mut::<Self>().ok_or_else(|| {
5639 crate::Error::validation(
5640 "Tensor::as_slice_mut",
5641 ValidationError::DTypeMismatch {
5642 expected: Self::dtype(),
5643 actual,
5644 },
5645 )
5646 })?;
5647 typed.host_data_mut()
5648 }
5649
5650 fn into_typed(
5651 tensor: Tensor,
5652 ) -> std::result::Result<TypedTensor<Self>, ReinterpretError<Tensor>> {
5653 let actual = tensor.dtype();
5654 match tensor.payload {
5655 TensorPayload::Native(PresetTensor::$variant(typed)) => Ok(typed),
5656 payload => Err(ReinterpretError::new(
5657 Tensor { payload },
5658 crate::Error::validation(
5659 "TensorScalar::into_typed",
5660 ValidationError::DTypeMismatch {
5661 expected: Self::dtype(),
5662 actual,
5663 },
5664 ),
5665 )),
5666 }
5667 }
5668 }
5669 };
5670}
5671
5672impl_tensor_scalar!(f64, f64, F64, F64);
5673impl_tensor_scalar!(f32, f32, F32, F32);
5674impl_tensor_scalar!(i64, i64, I64, I64);
5675impl_tensor_scalar!(i32, i32, I32, I32);
5676impl_tensor_scalar!(bool, bool, Bool, Bool);
5677impl_tensor_scalar!(Complex64, f64, C64, C64);
5678impl_tensor_scalar!(Complex32, f32, C32, C32);
5679
5680/// Dynamic tensor enum over the supported scalar types.
5681///
5682/// The enum keeps dtype dynamic and rank dynamic. Use
5683/// [`TypedTensor<T, R>`](TypedTensor) directly when the scalar type or rank
5684/// should be represented in Rust's type system.
5685///
5686/// # Examples
5687///
5688/// ```rust
5689/// use tenferro_tensor::{Tensor, TypedTensor};
5690///
5691/// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
5692/// assert_eq!(t.shape(), &[2]);
5693///
5694/// let erased = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 2.0]).unwrap();
5695/// assert_eq!(erased.shape().len(), 2);
5696/// ```
5697/// The owning payload behind the erased [`Tensor`].
5698///
5699/// `Native` stores a preset scalar owner directly; `External` retains its
5700/// existing explicit caller-owned host payload.
5701#[derive(Debug)]
5702enum TensorPayload {
5703 Native(PresetTensor),
5704 External(crate::ErasedHostTensor, Placement),
5705}
5706
5707#[derive(Debug)]
5708enum PresetTensor {
5709 F32(TypedTensor<f32>),
5710 F64(TypedTensor<f64>),
5711 I32(TypedTensor<i32>),
5712 I64(TypedTensor<i64>),
5713 Bool(TypedTensor<bool>),
5714 C32(TypedTensor<Complex32>),
5715 C64(TypedTensor<Complex64>),
5716}
5717
5718macro_rules! with_preset {
5719 ($preset:expr, |$value:ident| $body:expr) => {
5720 match $preset {
5721 PresetTensor::F32($value) => $body,
5722 PresetTensor::F64($value) => $body,
5723 PresetTensor::I32($value) => $body,
5724 PresetTensor::I64($value) => $body,
5725 PresetTensor::Bool($value) => $body,
5726 PresetTensor::C32($value) => $body,
5727 PresetTensor::C64($value) => $body,
5728 }
5729 };
5730}
5731
5732/// Dynamic tensor over the supported scalar types.
5733///
5734/// The erased tensor keeps dtype and rank dynamic: each preset scalar retains its
5735/// typed owner without constructing a group, while caller-owned scalars remain
5736/// `External` payloads recovered by their own type. Use [`TypedTensor<T, R>`](TypedTensor)
5737/// directly when the scalar type or rank should be represented in Rust's type system.
5738///
5739/// # Examples
5740///
5741/// ```
5742/// use tenferro_tensor::{DType, Tensor};
5743///
5744/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5745/// assert_eq!(tensor.dtype(), DType::F64);
5746/// # Ok::<(), tenferro_tensor::Error>(())
5747/// ```
5748#[derive(Debug)]
5749pub struct Tensor {
5750 payload: TensorPayload,
5751}
5752
5753impl<R: TensorRank> OwnedTensorGroup<R> {
5754 /// Whether this group's descriptor names a non-CPU provider.
5755 fn is_backend_buffer(&self) -> bool {
5756 !matches!(
5757 self.group.provider_kind(self.slot),
5758 None | Some(crate::storage::ProviderKind::Cpu)
5759 )
5760 }
5761}
5762
5763impl Tensor {
5764 /// Carry an externally defined scalar as a caller-owned payload.
5765 ///
5766 /// The payload keeps its own element type and is recovered by that type, so no
5767 /// bytes are reinterpreted. Placement defaults to unpinned host memory, which
5768 /// is where a caller-owned payload lives.
5769 ///
5770 /// # Examples
5771 ///
5772 /// ```rust
5773 /// use tenferro_tensor::{DType, Tensor};
5774 /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
5775 ///
5776 /// let payload = ErasedHostTensor::new(
5777 /// TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
5778 /// );
5779 /// let element = payload.element_type_id();
5780 /// let tensor = Tensor::external(payload);
5781 /// assert_eq!(tensor.dtype(), DType::External(element));
5782 /// assert_eq!(tensor.shape(), &[1]);
5783 /// # Ok::<(), tenferro_tensor::Error>(())
5784 /// ```
5785 #[must_use]
5786 pub fn external(payload: crate::ErasedHostTensor) -> Self {
5787 Self {
5788 payload: TensorPayload::External(payload, Placement::default()),
5789 }
5790 }
5791
5792 /// Build a tensor from a typed one, without naming its variant.
5793 ///
5794 /// A call site that constructs a tensor from a typed tensor should use this rather than a variant, so
5795 /// that changing how the erased representation is stored changes this function and not its 1290 call
5796 /// sites. The variants remain until the removal's last step, so both forms currently produce the same
5797 /// value.
5798 ///
5799 /// # Examples
5800 ///
5801 /// ```
5802 /// use tenferro_tensor::Tensor;
5803 ///
5804 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5805 /// let typed = tensor.into_typed::<f64>().unwrap();
5806 /// let rebuilt = Tensor::from_typed(typed);
5807 /// assert_eq!(rebuilt.as_typed::<f64>().unwrap().shape(), &[2]);
5808 /// # Ok::<(), tenferro_tensor::Error>(())
5809 /// ```
5810 #[must_use]
5811 pub fn from_typed<T: TensorScalar>(typed: TypedTensor<T>) -> Self {
5812 T::typed_tensor_into_tensor(typed)
5813 }
5814
5815 /// Borrow the erased payload of an externally defined tensor.
5816 ///
5817 /// This is the counterpart of [`Tensor::external`] for dispatch: a table that matches on
5818 /// [`Tensor::dtype`] reaches the externally defined tag and needs the payload that tag stands
5819 /// for, just as the typed tags reach theirs through [`Tensor::as_typed`]. Every other tag
5820 /// returns `None`.
5821 ///
5822 /// # Examples
5823 ///
5824 /// ```
5825 /// use tenferro_tensor::Tensor;
5826 /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
5827 ///
5828 /// let payload = ErasedHostTensor::new(
5829 /// TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
5830 /// );
5831 /// let tensor = Tensor::external(payload);
5832 /// assert!(tensor.external_payload().is_some());
5833 /// # Ok::<(), tenferro_tensor::Error>(())
5834 /// ```
5835 #[must_use]
5836 pub fn external_payload(&self) -> Option<&crate::ErasedHostTensor> {
5837 match &self.payload {
5838 TensorPayload::External(payload, _) => Some(payload),
5839 TensorPayload::Native(_) => None,
5840 }
5841 }
5842
5843 /// Carry an externally defined payload with an explicit placement.
5844 ///
5845 /// [`Tensor::external`] defaults the placement to unpinned host memory, which is where
5846 /// a caller-owned payload normally lives; this entry point is for a caller that knows
5847 /// the placement it wants.
5848 ///
5849 /// # Examples
5850 ///
5851 /// ```
5852 /// use tenferro_tensor::{Placement, Tensor};
5853 /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
5854 ///
5855 /// let payload = ErasedHostTensor::new(
5856 /// TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
5857 /// );
5858 /// let tensor = Tensor::external_with_placement(payload, Placement::default());
5859 /// assert!(tensor.external_payload().is_some());
5860 /// # Ok::<(), tenferro_tensor::Error>(())
5861 /// ```
5862 #[must_use]
5863 pub fn external_with_placement(payload: crate::ErasedHostTensor, placement: Placement) -> Self {
5864 Self {
5865 payload: TensorPayload::External(payload, placement),
5866 }
5867 }
5868
5869 /// Mutably borrow the erased payload of an externally defined tensor.
5870 ///
5871 /// The counterpart of [`Tensor::external_payload`] for callers that update the payload
5872 /// in place, such as a mutation test that checks the copy boundary.
5873 ///
5874 /// # Examples
5875 ///
5876 /// ```
5877 /// use tenferro_tensor::Tensor;
5878 /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
5879 ///
5880 /// let mut tensor = Tensor::external(ErasedHostTensor::new(
5881 /// TypedTensor::<f64, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?,
5882 /// ));
5883 /// assert!(tensor.external_payload_mut().is_some());
5884 /// # Ok::<(), tenferro_tensor::Error>(())
5885 /// ```
5886 #[must_use]
5887 pub fn external_payload_mut(&mut self) -> Option<&mut crate::ErasedHostTensor> {
5888 match &mut self.payload {
5889 TensorPayload::External(payload, _) => Some(payload),
5890 TensorPayload::Native(_) => None,
5891 }
5892 }
5893
5894 pub(crate) fn into_group_parts(self) -> (AllocationGroup, DescriptorSlot) {
5895 match self.payload {
5896 TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.into_group_parts()),
5897 // INVARIANT: a caller-owned payload has no allocation group.
5898 TensorPayload::External(..) => {
5899 unreachable!("an externally defined payload has no allocation group")
5900 }
5901 }
5902 }
5903}
5904
5905/// Dynamic read-only borrowed tensor view.
5906///
5907/// `TensorView` keeps dtype erased while borrowing typed view metadata and
5908/// storage. Use [`TypedTensorView`] directly when the scalar type is statically
5909/// known.
5910///
5911/// # Examples
5912///
5913/// ```
5914/// use tenferro_tensor::{DType, TensorView, TypedTensorView};
5915///
5916/// let data = [1_i32, 2, 3, 4];
5917/// let typed = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data)?;
5918/// let view = TensorView::I32(typed);
5919///
5920/// assert_eq!(view.dtype(), DType::I32);
5921/// assert_eq!(view.shape(), &[2, 2]);
5922/// # Ok::<(), tenferro_tensor::Error>(())
5923/// ```
5924#[derive(Clone, Debug)]
5925pub enum TensorView<'a> {
5926 F32(TypedTensorView<'a, f32>),
5927 F64(TypedTensorView<'a, f64>),
5928 I32(TypedTensorView<'a, i32>),
5929 I64(TypedTensorView<'a, i64>),
5930 Bool(TypedTensorView<'a, bool>),
5931 C32(TypedTensorView<'a, Complex<f32>>),
5932 C64(TypedTensorView<'a, Complex<f64>>),
5933}
5934
5935/// Dynamic mutable borrowed tensor view.
5936///
5937/// `TensorViewMut` is the mutable counterpart to [`TensorView`]. It keeps the
5938/// dtype erased while preserving the typed mutable view's shape, strides, and
5939/// offset metadata.
5940///
5941/// # Examples
5942///
5943/// ```
5944/// use tenferro_tensor::{DType, TensorViewMut, TypedTensorViewMut};
5945///
5946/// let mut data = [1.0_f64, 2.0];
5947/// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?);
5948/// assert_eq!(view.dtype(), DType::F64);
5949/// # Ok::<(), tenferro_tensor::Error>(())
5950/// ```
5951#[allow(clippy::large_enum_variant)]
5952#[derive(Debug)]
5953pub enum TensorViewMut<'a> {
5954 F32(TypedTensorViewMut<'a, f32>),
5955 F64(TypedTensorViewMut<'a, f64>),
5956 I32(TypedTensorViewMut<'a, i32>),
5957 I64(TypedTensorViewMut<'a, i64>),
5958 Bool(TypedTensorViewMut<'a, bool>),
5959 C32(TypedTensorViewMut<'a, Complex<f32>>),
5960 C64(TypedTensorViewMut<'a, Complex<f64>>),
5961}
5962
5963/// Read-only tensor input accepted by synchronous eager kernels.
5964///
5965/// `TensorRead` lets kernels accept either an owned tensor reference or a
5966/// borrowed [`TensorView`] without forcing callers to materialize first.
5967/// The `View` variant preserves arbitrary strides and offsets, so kernels that
5968/// support strided reads can consume transposes, slices, and broadcasts directly.
5969///
5970/// `TensorRead` is intentionally borrowed. It is an input-dispatch type, not an
5971/// owned lazy tensor value. APIs that need to store a lazy layout result should
5972/// keep an owned base tensor plus layout metadata, then expose a `TensorRead`
5973/// only for the duration of kernel dispatch.
5974///
5975/// # Examples
5976///
5977/// ```
5978/// use tenferro_tensor::{DType, Tensor, TensorRead};
5979///
5980/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
5981/// let read = TensorRead::from_tensor(&tensor);
5982///
5983/// assert_eq!(read.dtype(), DType::F64);
5984/// assert_eq!(read.shape(), &[2]);
5985/// ```
5986// Keep borrowed views inline to avoid allocation on read-only tensor dispatch paths.
5987#[allow(clippy::large_enum_variant)]
5988#[derive(Clone, Debug)]
5989pub enum TensorRead<'a> {
5990 Tensor(&'a Tensor),
5991 View(TensorView<'a>),
5992}
5993
5994/// Mutable typed tensor output accepted by synchronous eager kernels.
5995///
5996/// `TypedTensorWrite` is the typed counterpart to [`TensorWrite`]. It accepts
5997/// either an owned compact [`TypedTensor`] or an arbitrary-strided mutable
5998/// [`TypedTensorViewMut`] without erasing the scalar type at the public API
5999/// boundary.
6000///
6001/// # Examples
6002///
6003/// ```
6004/// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
6005///
6006/// let mut data = [0.0_f64, 1.0, 0.0, 2.0];
6007/// let view = TypedTensorViewMut::from_slice([2], [2], 1, &mut data)?;
6008/// let write = TypedTensorWrite::from_view(view).into_tensor_write();
6009/// assert_eq!(write.shape(), &[2]);
6010/// assert_eq!(write.strides()?, [2]);
6011/// # Ok::<(), tenferro_tensor::Error>(())
6012/// ```
6013#[allow(clippy::large_enum_variant)]
6014#[derive(Debug)]
6015pub enum TypedTensorWrite<'a, T> {
6016 /// An owned compact typed tensor borrowed mutably for the write.
6017 Tensor(&'a mut TypedTensor<T>),
6018 /// An arbitrary-strided mutable typed tensor view.
6019 View(TypedTensorViewMut<'a, T>),
6020}
6021
6022impl<'a, T> TypedTensorWrite<'a, T> {
6023 /// Create a writable target from an owned typed tensor.
6024 ///
6025 /// # Examples
6026 ///
6027 /// ```
6028 /// use tenferro_tensor::{TypedTensor, TypedTensorWrite};
6029 ///
6030 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0])?;
6031 /// let write = TypedTensorWrite::from_tensor(&mut tensor);
6032 /// assert!(matches!(write, TypedTensorWrite::Tensor(_)));
6033 /// # Ok::<(), tenferro_tensor::Error>(())
6034 /// ```
6035 pub fn from_tensor(tensor: &'a mut TypedTensor<T>) -> Self {
6036 Self::Tensor(tensor)
6037 }
6038
6039 /// Create a writable target from a mutable typed tensor view.
6040 ///
6041 /// # Examples
6042 ///
6043 /// ```
6044 /// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
6045 ///
6046 /// let mut data = [0.0_f64, 1.0];
6047 /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
6048 /// let write = TypedTensorWrite::from_view(view);
6049 /// assert!(matches!(write, TypedTensorWrite::View(_)));
6050 /// # Ok::<(), tenferro_tensor::Error>(())
6051 /// ```
6052 pub fn from_view(view: TypedTensorViewMut<'a, T>) -> Self {
6053 Self::View(view)
6054 }
6055}
6056
6057impl<'a, T: TensorScalar> TypedTensorWrite<'a, T> {
6058 /// Erase the scalar type while preserving the output layout.
6059 ///
6060 /// # Examples
6061 ///
6062 /// ```
6063 /// use tenferro_tensor::{DType, TypedTensor, TypedTensorWrite};
6064 ///
6065 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![0.0; 2])?;
6066 /// let write = TypedTensorWrite::from_tensor(&mut tensor).into_tensor_write();
6067 /// assert_eq!(write.dtype(), DType::F64);
6068 /// assert_eq!(write.shape(), &[2]);
6069 /// # Ok::<(), tenferro_tensor::Error>(())
6070 /// ```
6071 pub fn into_tensor_write(self) -> TensorWrite<'a> {
6072 match self {
6073 Self::Tensor(tensor) => T::tensor_write(tensor),
6074 Self::View(view) => TensorWrite::from_view(T::tensor_view_mut(view)),
6075 }
6076 }
6077}
6078
6079impl<'a, T> From<&'a mut TypedTensor<T>> for TypedTensorWrite<'a, T> {
6080 fn from(tensor: &'a mut TypedTensor<T>) -> Self {
6081 Self::from_tensor(tensor)
6082 }
6083}
6084
6085impl<'a, T> From<TypedTensorViewMut<'a, T>> for TypedTensorWrite<'a, T> {
6086 fn from(view: TypedTensorViewMut<'a, T>) -> Self {
6087 Self::from_view(view)
6088 }
6089}
6090
6091/// Mutable tensor output accepted by synchronous eager kernels.
6092///
6093/// `TensorWrite` mirrors [`TensorRead`] for output dispatch: it can target an
6094/// owned compact [`Tensor`] or a borrowed mutable [`TensorViewMut`]. The target
6095/// is never resized.
6096///
6097/// # Examples
6098///
6099/// ```
6100/// use tenferro_tensor::{Tensor, TensorWrite};
6101///
6102/// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
6103/// let write = TensorWrite::from_tensor(&mut tensor);
6104/// assert_eq!(write.shape(), &[1]);
6105/// # Ok::<(), tenferro_tensor::Error>(())
6106/// ```
6107#[allow(clippy::large_enum_variant)]
6108#[derive(Debug)]
6109pub enum TensorWrite<'a> {
6110 Tensor(&'a mut Tensor),
6111 View(TensorViewMut<'a>),
6112}
6113
6114/// Owned tensor value with one move-only physical owner and metadata-only layout.
6115///
6116/// `TensorValue` is intentionally not cloneable. View transformations consume
6117/// the value and move its existing owner; [`TensorValue::duplicate`] is the
6118/// explicit boundary for creating another physical allocation.
6119///
6120/// # Examples
6121///
6122/// ```
6123/// use tenferro_tensor::{Tensor, TensorValue};
6124/// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6125/// let view = value.transpose_view([1, 0])?;
6126/// assert_eq!(view.strides(), &[2, 1]);
6127/// assert!(view.is_view());
6128/// # Ok::<(), tenferro_tensor::Error>(())
6129/// ```
6130#[derive(Debug)]
6131pub struct TensorValue {
6132 owner: Tensor,
6133 layout: TensorLayout<DynRank>,
6134}
6135
6136/// A consuming view transformation failed while retaining its unchanged owner.
6137///
6138/// # Examples
6139///
6140/// ```
6141/// use tenferro_tensor::{Tensor, TensorValue};
6142/// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6143/// let failure = value.try_reshape_view([3]).unwrap_err();
6144/// let (recovered, cause) = failure.into_parts();
6145/// assert_eq!(recovered.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6146/// assert!(matches!(cause, tenferro_tensor::Error::Validation { .. }));
6147/// # Ok::<(), tenferro_tensor::Error>(())
6148/// ```
6149#[derive(Debug)]
6150pub struct TensorValueViewError {
6151 value: TensorValue,
6152 source: crate::Error,
6153}
6154
6155impl TensorValueViewError {
6156 /// Return the unchanged value and the typed validation/backend error.
6157 ///
6158 /// # Examples
6159 ///
6160 /// ```
6161 /// use tenferro_tensor::{Tensor, TensorValue};
6162 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([1], vec![7.])?);
6163 /// let (value, error) = value.try_reshape_view([2]).unwrap_err().into_parts();
6164 /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[7.]);
6165 /// assert!(matches!(error, tenferro_tensor::Error::Validation { .. }));
6166 /// # Ok::<(), tenferro_tensor::Error>(())
6167 /// ```
6168 pub fn into_parts(self) -> (TensorValue, crate::Error) {
6169 (self.value, self.source)
6170 }
6171
6172 fn new(value: TensorValue, source: crate::Error) -> Self {
6173 Self { value, source }
6174 }
6175}
6176
6177impl std::fmt::Display for TensorValueViewError {
6178 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6179 std::fmt::Display::fmt(&self.source, formatter)
6180 }
6181}
6182
6183impl std::error::Error for TensorValueViewError {
6184 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
6185 Some(&self.source)
6186 }
6187}
6188
6189impl TensorValue {
6190 /// Explicitly duplicate the physical owner represented by this value.
6191 ///
6192 /// # Examples
6193 ///
6194 /// ```
6195 /// use tenferro_tensor::{Tensor, TensorValue};
6196 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6197 /// let copy = value.duplicate()?.into_tensor()?;
6198 /// assert_eq!(copy.as_slice::<f64>()?, &[3., 4.]);
6199 /// assert_eq!(value.shape(), &[2]);
6200 /// # Ok::<(), tenferro_tensor::Error>(())
6201 /// ```
6202 ///
6203 /// # Errors
6204 ///
6205 /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
6206 /// when the backend/storage owner cannot be duplicated.
6207 pub fn duplicate(&self) -> crate::Result<Self> {
6208 let tensor = self.owner.duplicate()?;
6209 Self::from_parts(
6210 tensor,
6211 self.shape().to_vec(),
6212 self.strides().to_vec(),
6213 self.offset(),
6214 )
6215 }
6216
6217 /// Retain a compact tensor as a move-only value.
6218 ///
6219 /// # Examples
6220 ///
6221 /// ```
6222 /// use tenferro_tensor::{Tensor, TensorValue};
6223 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6224 /// assert!(!value.is_view());
6225 /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6226 /// # Ok::<(), tenferro_tensor::Error>(())
6227 /// ```
6228 pub fn from_tensor(tensor: Tensor) -> Self {
6229 let layout = tensor_layout(&tensor);
6230 Self {
6231 owner: tensor,
6232 layout,
6233 }
6234 }
6235
6236 /// # Errors
6237 ///
6238 /// Returns [`ValidationError::InvalidArgument`] or
6239 /// [`ValidationError::IntegerOverflow`] when the supplied layout is
6240 /// invalid for the tensor's physical buffer.
6241 ///
6242 /// # Examples
6243 ///
6244 /// ```
6245 /// use tenferro_tensor::{Tensor, TensorValue};
6246 /// let tensor = Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?;
6247 /// let view = TensorValue::from_parts(tensor, vec![2], vec![1], 2)?;
6248 /// assert_eq!(view.shape(), &[2]);
6249 /// assert_eq!(view.offset(), 2);
6250 /// # Ok::<(), tenferro_tensor::Error>(())
6251 /// ```
6252 pub fn from_parts(
6253 tensor: Tensor,
6254 shape: Vec<usize>,
6255 strides: Vec<isize>,
6256 offset: isize,
6257 ) -> crate::Result<Self> {
6258 let layout = TensorLayout::from_parts(
6259 shape.into(),
6260 strides.into(),
6261 offset,
6262 tensor_buffer_len(&tensor),
6263 )
6264 .map_err(|err| tensor_layout_error("TensorValue::from_parts", err))?;
6265 Ok(Self {
6266 owner: tensor,
6267 layout,
6268 })
6269 }
6270
6271 // INVARIANT: unchanged-owner recovery is part of the consuming ownership
6272 // contract, so this intentionally carries the large move-only error.
6273 #[doc(hidden)]
6274 /// Move the value's sole physical owner into an allocation group.
6275 ///
6276 /// This preserves metadata-only views without copying. The consumed value
6277 /// always contains one unique owner, so no compatibility fallback exists.
6278 ///
6279 /// # Errors
6280 ///
6281 /// Returns the unchanged value when descriptor publication fails because
6282 /// of [`ValidationError::InvalidArgument`] or
6283 /// [`ValidationError::IntegerOverflow`].
6284 #[allow(clippy::result_large_err)]
6285 pub fn try_into_group_parts(
6286 self,
6287 ) -> std::result::Result<(AllocationGroup, DescriptorSlot, DType, Vec<usize>), Self> {
6288 let Self { owner, layout } = self;
6289 if owner.external_payload().is_some() {
6290 // A caller-owned payload owns no allocation group, so it is returned
6291 // unchanged instead of being forced into one.
6292 return Err(Self { owner, layout });
6293 }
6294 let dtype = owner.dtype();
6295 let shape = layout.shape().to_vec();
6296 let strides = layout.strides().to_vec();
6297 let offset = layout.offset();
6298 let (group, slot) = owner.into_group_parts();
6299 match group.update_descriptor_layout(slot, shape, strides, offset) {
6300 Ok(group) => Ok((group, slot, dtype, layout.shape().to_vec())),
6301 Err((_group, _error)) => {
6302 unreachable!("TensorValue layout was validated before group ownership transfer")
6303 }
6304 }
6305 }
6306
6307 /// Consume a value with its owner's original layout and return that owner.
6308 ///
6309 /// # Examples
6310 ///
6311 /// ```
6312 /// use tenferro_tensor::{Tensor, TensorValue};
6313 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6314 /// assert_eq!(value.into_tensor()?.as_slice::<f64>()?, &[3., 4.]);
6315 /// # Ok::<(), tenferro_tensor::Error>(())
6316 /// ```
6317 ///
6318 /// # Errors
6319 ///
6320 /// Returns [`crate::Error::Unsupported`] when the value's metadata-only
6321 /// view differs from its physical owner's layout.
6322 pub fn into_tensor(self) -> crate::Result<Tensor> {
6323 if self.layout != tensor_layout(&self.owner) {
6324 return Err(crate::Error::unsupported(
6325 "TensorValue::into_tensor",
6326 "a metadata-only view has no compact tensor owner",
6327 ));
6328 }
6329 Ok(self.owner)
6330 }
6331
6332 ///
6333 /// # Examples
6334 ///
6335 /// ```
6336 /// use tenferro_tensor::{Tensor, TensorValue};
6337 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6338 /// assert_eq!(value.as_tensor().unwrap().as_slice::<f64>()?, &[1., 2., 3., 4.]);
6339 /// assert!(value.transpose_view([1, 0])?.as_tensor().is_none());
6340 /// # Ok::<(), tenferro_tensor::Error>(())
6341 /// ```
6342 pub fn as_tensor(&self) -> Option<&Tensor> {
6343 (self.layout == tensor_layout(&self.owner)).then_some(&self.owner)
6344 }
6345
6346 ///
6347 /// # Examples
6348 ///
6349 /// ```
6350 /// use tenferro_tensor::{Tensor, TensorValue};
6351 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6352 /// assert!(!value.is_view());
6353 /// assert!(value.transpose_view([1, 0])?.is_view());
6354 /// # Ok::<(), tenferro_tensor::Error>(())
6355 /// ```
6356 pub fn is_view(&self) -> bool {
6357 self.as_tensor().is_none()
6358 }
6359
6360 ///
6361 /// # Examples
6362 ///
6363 /// ```
6364 /// use tenferro_tensor::{Tensor, TensorValue};
6365 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6366 /// assert_eq!(value.dtype(), tenferro_tensor::DType::F64);
6367 /// # Ok::<(), tenferro_tensor::Error>(())
6368 /// ```
6369 pub fn dtype(&self) -> DType {
6370 self.owner.dtype()
6371 }
6372
6373 ///
6374 /// # Examples
6375 ///
6376 /// ```
6377 /// use tenferro_tensor::{Tensor, TensorValue};
6378 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6379 /// assert_eq!(value.reshape_view([4])?.shape(), &[4]);
6380 /// # Ok::<(), tenferro_tensor::Error>(())
6381 /// ```
6382 pub fn shape(&self) -> &[usize] {
6383 self.layout.shape()
6384 }
6385
6386 ///
6387 /// # Examples
6388 ///
6389 /// ```
6390 /// use tenferro_tensor::{Tensor, TensorValue};
6391 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6392 /// assert_eq!(value.transpose_view([1, 0])?.strides(), &[2, 1]);
6393 /// # Ok::<(), tenferro_tensor::Error>(())
6394 /// ```
6395 pub fn strides(&self) -> &[isize] {
6396 self.layout.strides()
6397 }
6398
6399 ///
6400 /// # Examples
6401 ///
6402 /// ```
6403 /// use tenferro_tensor::{Tensor, TensorValue};
6404 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6405 /// assert_eq!(value.offset(), 0);
6406 /// # Ok::<(), tenferro_tensor::Error>(())
6407 /// ```
6408 pub fn offset(&self) -> isize {
6409 self.layout.offset()
6410 }
6411
6412 ///
6413 /// # Examples
6414 ///
6415 /// ```
6416 /// use tenferro_tensor::{Tensor, TensorValue};
6417 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6418 /// let view = value.tensor_view();
6419 /// assert_eq!(view.shape(), &[2, 2]);
6420 /// assert_eq!(view.dtype(), tenferro_tensor::DType::F64);
6421 /// # Ok::<(), tenferro_tensor::Error>(())
6422 /// ```
6423 pub fn tensor_view(&self) -> TensorView<'_> {
6424 tensor_view_with_layout(&self.owner, self.layout.clone())
6425 }
6426
6427 ///
6428 /// # Examples
6429 ///
6430 /// ```
6431 /// use tenferro_tensor::{Tensor, TensorValue};
6432 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6433 /// let read = value.tensor_read();
6434 /// assert_eq!(read.shape(), &[2, 2]);
6435 /// assert_eq!(read.dtype(), tenferro_tensor::DType::F64);
6436 /// # Ok::<(), tenferro_tensor::Error>(())
6437 /// ```
6438 pub fn tensor_read(&self) -> TensorRead<'_> {
6439 self.as_tensor()
6440 .map(TensorRead::from_tensor)
6441 .unwrap_or_else(|| TensorRead::from_view(self.tensor_view()))
6442 }
6443
6444 /// # Errors
6445 ///
6446 /// Returns [`crate::Error::Validation`] with
6447 /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
6448 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
6449 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
6450 /// not a valid permutation of the value rank.
6451 ///
6452 /// # Examples
6453 ///
6454 /// ```
6455 /// use tenferro_tensor::{Tensor, TensorValue};
6456 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6457 /// let view = value.transpose_view([1, 0])?;
6458 /// assert_eq!(view.strides(), &[2, 1]);
6459 /// assert!(view.is_view());
6460 /// # Ok::<(), tenferro_tensor::Error>(())
6461 /// ```
6462 pub fn transpose_view(self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
6463 let layout = self
6464 .layout
6465 .transpose_view(axes)
6466 .map_err(|err| tensor_layout_error("TensorValue::transpose_view", err))?;
6467 Ok(Self {
6468 owner: self.owner,
6469 layout,
6470 })
6471 }
6472
6473 // INVARIANT: unchanged-owner recovery is part of the consuming view
6474 // contract, so this intentionally carries the large move-only error.
6475 /// # Errors
6476 ///
6477 /// Returns [`crate::Error::Validation`] with
6478 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
6479 /// the source is not compact column-major,
6480 /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
6481 /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
6482 /// records the counts) when element counts differ,
6483 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
6484 /// arithmetic overflow, or
6485 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6486 /// reshaped view exceeds the backing buffer.
6487 #[allow(clippy::result_large_err)]
6488 ///
6489 /// # Examples
6490 ///
6491 /// ```
6492 /// use tenferro_tensor::{Tensor, TensorValue};
6493 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6494 /// let (recovered, error) = value.try_reshape_view([3]).unwrap_err().into_parts();
6495 /// assert_eq!(recovered.into_tensor()?.as_slice::<f64>()?, &[1., 2., 3., 4.]);
6496 /// assert!(matches!(error, tenferro_tensor::Error::Validation { .. }));
6497 /// # Ok::<(), tenferro_tensor::Error>(())
6498 /// ```
6499 pub fn try_reshape_view(
6500 self,
6501 shape: impl tenferro_tensor_core::IntoShapeVec,
6502 ) -> std::result::Result<Self, TensorValueViewError> {
6503 let shape = shape.into_shape_vec();
6504 let layout = match reshape_layout_dyn(
6505 &self.layout,
6506 &shape,
6507 tensor_buffer_len(&self.owner),
6508 "TensorValue::reshape_view",
6509 ) {
6510 Ok(layout) => layout,
6511 Err(error) => return Err(TensorValueViewError::new(self, error)),
6512 };
6513 Ok(Self {
6514 owner: self.owner,
6515 layout,
6516 })
6517 }
6518
6519 /// # Errors
6520 ///
6521 /// Returns [`crate::Error::Validation`] with
6522 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`]
6523 /// when the source is not compact, [`tenferro_tensor_core::ValidationError::ShapeMismatch`]
6524 /// when element counts differ, or [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
6525 /// / [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] for invalid
6526 /// target-shape arithmetic or bounds.
6527 ///
6528 /// # Examples
6529 ///
6530 /// ```
6531 /// use tenferro_tensor::{Tensor, TensorValue};
6532 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6533 /// let view = value.reshape_view([4])?;
6534 /// assert_eq!(view.shape(), &[4]);
6535 /// assert_eq!(view.strides(), &[1]);
6536 /// # Ok::<(), tenferro_tensor::Error>(())
6537 /// ```
6538 pub fn reshape_view(
6539 self,
6540 shape: impl tenferro_tensor_core::IntoShapeVec,
6541 ) -> crate::Result<Self> {
6542 self.try_reshape_view(shape).map_err(|error| error.source)
6543 }
6544
6545 /// # Errors
6546 ///
6547 /// Returns [`crate::Error::Validation`] with
6548 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a slice
6549 /// vector does not match the value rank,
6550 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when a bound
6551 /// or stride cannot be represented or is invalid,
6552 /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
6553 /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for slice
6554 /// parameters, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
6555 /// for slice arithmetic overflow, or
6556 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6557 /// result exceeds the backing buffer.
6558 ///
6559 /// # Examples
6560 ///
6561 /// ```
6562 /// use tenferro_tensor::{Tensor, TensorValue};
6563 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2, 2], vec![1., 2., 3., 4.])?);
6564 /// let view = value.slice_view(&tenferro_tensor::SliceConfig {
6565 /// starts: vec![0, 1], limits: vec![2, 2], strides: vec![1, 1],
6566 /// })?;
6567 /// assert_eq!(view.shape(), &[2, 1]);
6568 /// assert_eq!(view.offset(), 2);
6569 /// # Ok::<(), tenferro_tensor::Error>(())
6570 /// ```
6571 pub fn slice_view(self, config: &SliceConfig) -> crate::Result<Self> {
6572 let op = "TensorValue::slice_view";
6573 if config.starts.len() != self.shape().len()
6574 || config.limits.len() != self.shape().len()
6575 || config.strides.len() != self.shape().len()
6576 {
6577 return Err(crate::Error::validation(
6578 op,
6579 ValidationError::RankMismatch {
6580 expected: self.shape().len(),
6581 actual: config.starts.len(),
6582 },
6583 ));
6584 }
6585 let mut slices = Vec::with_capacity(self.shape().len());
6586 for ((&start, &limit), &stride) in config
6587 .starts
6588 .iter()
6589 .zip(config.limits.iter())
6590 .zip(config.strides.iter())
6591 {
6592 let start = isize::try_from(start).map_err(|_| {
6593 crate::Error::invalid_argument(
6594 op,
6595 "slice start",
6596 "slice start does not fit in isize",
6597 )
6598 })?;
6599 let limit = isize::try_from(limit).map_err(|_| {
6600 crate::Error::invalid_argument(
6601 op,
6602 "slice limit",
6603 "slice limit does not fit in isize",
6604 )
6605 })?;
6606 let stride = isize::try_from(stride).map_err(|_| {
6607 crate::Error::invalid_argument(
6608 op,
6609 "slice stride",
6610 "slice stride does not fit in isize",
6611 )
6612 })?;
6613 slices.push(StridedSliceSpec::new(start, Some(limit), stride));
6614 }
6615 let specs = core_slice_specs(&slices, self.shape(), op)?;
6616 let layout = self
6617 .layout
6618 .slice_view(&specs, tensor_buffer_len(&self.owner))
6619 .map_err(|err| tensor_layout_error(op, err))?;
6620 Ok(Self {
6621 owner: self.owner,
6622 layout,
6623 })
6624 }
6625
6626 /// # Errors
6627 ///
6628 /// Returns [`crate::Error::Validation`] with
6629 /// [`tenferro_tensor_core::ValidationError::RankMismatch`],
6630 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
6631 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] for invalid
6632 /// dimension mappings,
6633 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] for
6634 /// incompatible extents,
6635 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6636 /// result exceeds the backing buffer, or
6637 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
6638 /// arithmetic overflow.
6639 ///
6640 /// # Examples
6641 ///
6642 /// ```
6643 /// use tenferro_tensor::{Tensor, TensorValue};
6644 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major([2], vec![3., 4.])?);
6645 /// let view = value.broadcast_in_dim_view([2, 3], [0])?;
6646 /// assert_eq!(view.shape(), &[2, 3]);
6647 /// assert_eq!(view.strides(), &[1, 0]);
6648 /// # Ok::<(), tenferro_tensor::Error>(())
6649 /// ```
6650 pub fn broadcast_in_dim_view(
6651 self,
6652 shape: impl tenferro_tensor_core::IntoShapeVec,
6653 dims: impl AsRef<[usize]>,
6654 ) -> crate::Result<Self> {
6655 let shape = shape.into_shape_vec();
6656 let layout = self
6657 .layout
6658 .broadcast_in_dim_view::<DynRank>(shape, dims, tensor_buffer_len(&self.owner))
6659 .map_err(|err| tensor_layout_error("TensorValue::broadcast_in_dim_view", err))?;
6660 Ok(Self {
6661 owner: self.owner,
6662 layout,
6663 })
6664 }
6665}
6666
6667fn tensor_layout(tensor: &Tensor) -> TensorLayout<DynRank> {
6668 match &tensor.payload {
6669 // A caller-owned payload carries its own view layout, so strides and offset
6670 // come from the payload rather than from a compact assumption.
6671 TensorPayload::External(payload, _) => TensorLayout::<DynRank>::from_parts(
6672 tenferro_tensor_core::ShapeVec::from_slice(payload.shape()),
6673 tenferro_tensor_core::StrideVec::from_slice(payload.strides()),
6674 payload.offset(),
6675 payload.payload_element_count(),
6676 )
6677 .unwrap_or_else(|_| {
6678 // INVARIANT: a payload is built from a validated host tensor, and the
6679 // only layout change is a permutation, so its strides stay inside the
6680 // payload it was created from and this construction cannot fail.
6681 unreachable!("a validated payload yields a layout inside its own storage")
6682 }),
6683 TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.layout()),
6684 }
6685}
6686
6687fn tensor_buffer_len(tensor: &Tensor) -> usize {
6688 match &tensor.payload {
6689 // A caller-owned payload stores its element count directly.
6690 TensorPayload::External(payload, _) => payload.element_count(),
6691 TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.buffer_len()),
6692 }
6693}
6694
6695fn prepare_backend_access<'a, T: 'static, R: TensorRank>(
6696 buffer: &'a dyn BackendStorage<T>,
6697 layout: &'a TensorLayout<R>,
6698 op: &'static str,
6699) -> crate::Result<Box<dyn PreparedDeviceAccess + 'a>> {
6700 let domain = buffer.allocation_domain().ok_or_else(|| {
6701 crate::Error::runtime_state(op, "backend buffer is missing an allocation domain")
6702 })?;
6703 let allocation_id = buffer.allocation_id().ok_or_else(|| {
6704 crate::Error::runtime_state(op, "backend buffer is missing an allocation identity")
6705 })?;
6706 let byte_len = buffer
6707 .len()
6708 .checked_mul(size_of::<T>())
6709 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6710 let request = DeviceAccessRequest::new(
6711 domain,
6712 allocation_id,
6713 byte_len,
6714 size_of::<T>(),
6715 layout.shape(),
6716 layout.strides(),
6717 layout.offset(),
6718 );
6719 buffer
6720 .prepare_device_access(request)
6721 .map_err(|error| crate::Error::runtime_state_source(op, error))
6722}
6723
6724fn cast_view_slice<S: 'static, T: TensorScalar>(source: &[S]) -> crate::Result<&[T]> {
6725 if size_of::<S>() != size_of::<T>() || align_of::<S>() != align_of::<T>() {
6726 return Err(crate::Error::invalid_argument(
6727 "TensorView::as_slice",
6728 "dtype",
6729 "matching dtypes must have identical scalar layout",
6730 ));
6731 }
6732 // SAFETY: the dtype check above is exhaustive over the sealed scalar set;
6733 // equal size/alignment preserve the element boundaries and the source
6734 // slice remains borrowed for the returned lifetime.
6735 Ok(unsafe { std::slice::from_raw_parts(source.as_ptr().cast::<T>(), source.len()) })
6736}
6737
6738fn tensor_view_with_layout(tensor: &Tensor, layout: TensorLayout<DynRank>) -> TensorView<'_> {
6739 match tensor.dtype() {
6740 DType::F32 => TensorView::F32(typed_view_with_layout(
6741 tensor
6742 .as_typed::<f32>()
6743 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6744 layout,
6745 )),
6746 DType::F64 => TensorView::F64(typed_view_with_layout(
6747 tensor
6748 .as_typed::<f64>()
6749 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6750 layout,
6751 )),
6752 DType::I32 => TensorView::I32(typed_view_with_layout(
6753 tensor
6754 .as_typed::<i32>()
6755 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6756 layout,
6757 )),
6758 DType::I64 => TensorView::I64(typed_view_with_layout(
6759 tensor
6760 .as_typed::<i64>()
6761 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6762 layout,
6763 )),
6764 DType::Bool => TensorView::Bool(typed_view_with_layout(
6765 tensor
6766 .as_typed::<bool>()
6767 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6768 layout,
6769 )),
6770 DType::C32 => TensorView::C32(typed_view_with_layout(
6771 tensor
6772 .as_typed::<Complex<f32>>()
6773 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6774 layout,
6775 )),
6776 DType::C64 => TensorView::C64(typed_view_with_layout(
6777 tensor
6778 .as_typed::<Complex<f64>>()
6779 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm")),
6780 layout,
6781 )),
6782 // INVARIANT: `TensorView` has no externally defined variant, and a view is
6783 // never requested for a caller-owned payload.
6784 DType::External(_) => unreachable!("views cover the preset scalars"),
6785 }
6786}
6787
6788fn typed_view_with_layout<T: TensorScalar + 'static>(
6789 tensor: &TypedTensor<T>,
6790 layout: TensorLayout<DynRank>,
6791) -> TypedTensorView<'_, T> {
6792 match &tensor.storage {
6793 DynamicStorage::Host(HostStorage { data }) => TypedTensorView {
6794 buffer: TensorStorageRef::Host(data.as_slice()),
6795 root: None,
6796 layout,
6797 placement: tensor.placement.clone(),
6798 _representation: std::marker::PhantomData,
6799 },
6800 DynamicStorage::Group(core) => {
6801 let root = core.group.view::<T>().unwrap_or_else(|error| {
6802 unreachable!("typed tensor group descriptor mismatch: {error}")
6803 });
6804 let buffer = if let Some(allocation) = root.backend_allocation() {
6805 TensorStorageRef::Root(allocation)
6806 } else {
6807 TensorStorageRef::Host(tensor.group_host_slice())
6808 };
6809 TypedTensorView {
6810 buffer,
6811 root: Some(root),
6812 layout,
6813 placement: tensor.placement.clone(),
6814 _representation: std::marker::PhantomData,
6815 }
6816 }
6817 }
6818}
6819
6820pub(crate) fn tensor_view_from_group<'a, T: TensorScalar>(
6821 view: GroupReadView<'a, T, DynRank>,
6822) -> crate::Result<TensorView<'a>> {
6823 let buffer = if let Some(allocation) = view.backend_allocation() {
6824 TensorStorageRef::Root(allocation)
6825 } else {
6826 let storage = view.storage_buffer().ok_or_else(|| {
6827 crate::Error::runtime_state(
6828 "AllocationGroup::tensor_read",
6829 "group descriptor has no backing storage",
6830 )
6831 })?;
6832 match storage {
6833 StorageBuffer::Host(data) => TensorStorageRef::Host(data),
6834 StorageBuffer::Backend(buffer) => TensorStorageRef::Backend(buffer.as_ref()),
6835 }
6836 };
6837 let layout = view.descriptor().layout().clone();
6838 let placement = view.descriptor().placement().clone();
6839 let typed = TypedTensorView {
6840 buffer,
6841 root: Some(view.clone()),
6842 layout,
6843 placement,
6844 _representation: std::marker::PhantomData,
6845 };
6846 Ok(T::tensor_view(typed))
6847}
6848
6849pub(crate) fn tensor_from_group(
6850 group: AllocationGroup,
6851 slot: DescriptorSlot,
6852 allocation_index: AllocationSlot,
6853 dtype: DType,
6854 layout: TensorLayout<DynRank>,
6855 placement: Placement,
6856) -> Tensor {
6857 fn typed<T: TensorScalar>(
6858 group: AllocationGroup,
6859 slot: DescriptorSlot,
6860 allocation_index: AllocationSlot,
6861 layout: TensorLayout<DynRank>,
6862 placement: Placement,
6863 ) -> TypedTensor<T> {
6864 let descriptor_dtype = group
6865 .descriptor_dtype(slot)
6866 .unwrap_or_else(|| unreachable!("tensor_from_group requires a live typed descriptor"));
6867 assert_eq!(
6868 descriptor_dtype,
6869 T::dtype(),
6870 "managed typed owner must match its descriptor dtype",
6871 );
6872 let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
6873 TypedTensor {
6874 shape: shape_vec(layout.shape()),
6875 placement,
6876 storage: DynamicStorage::Group(GroupStorage {
6877 group: Box::new(OwnedTensorGroup {
6878 group,
6879 slot,
6880 allocation_index,
6881 host_ptr,
6882 host_byte_len,
6883 _rank: PhantomData,
6884 }),
6885 }),
6886 }
6887 }
6888
6889 match dtype {
6890 DType::F32 => <f32 as TensorScalar>::typed_tensor_into_tensor(typed::<f32>(
6891 group,
6892 slot,
6893 allocation_index,
6894 layout,
6895 placement,
6896 )),
6897 DType::F64 => <f64 as TensorScalar>::typed_tensor_into_tensor(typed::<f64>(
6898 group,
6899 slot,
6900 allocation_index,
6901 layout,
6902 placement,
6903 )),
6904 DType::I32 => <i32 as TensorScalar>::typed_tensor_into_tensor(typed::<i32>(
6905 group,
6906 slot,
6907 allocation_index,
6908 layout,
6909 placement,
6910 )),
6911 DType::I64 => <i64 as TensorScalar>::typed_tensor_into_tensor(typed::<i64>(
6912 group,
6913 slot,
6914 allocation_index,
6915 layout,
6916 placement,
6917 )),
6918 DType::Bool => <bool as TensorScalar>::typed_tensor_into_tensor(typed::<bool>(
6919 group,
6920 slot,
6921 allocation_index,
6922 layout,
6923 placement,
6924 )),
6925 DType::C32 => <Complex<f32> as TensorScalar>::typed_tensor_into_tensor(
6926 typed::<Complex<f32>>(group, slot, allocation_index, layout, placement),
6927 ),
6928 DType::C64 => <Complex<f64> as TensorScalar>::typed_tensor_into_tensor(
6929 typed::<Complex<f64>>(group, slot, allocation_index, layout, placement),
6930 ),
6931 // INVARIANT: an allocation group is created from a sealed preset scalar,
6932 // so it can never carry an externally defined one. External payloads are
6933 // caller-owned and do not enter a group.
6934 DType::External(_) => unreachable!("allocation groups are preset-typed"),
6935 }
6936}
6937
6938/// Wrap an `f64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
6939///
6940/// # Examples
6941///
6942/// ```
6943/// use tenferro_tensor::{Tensor, TypedTensor};
6944///
6945/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
6946/// let tensor: Tensor = typed.into();
6947/// assert_eq!(tensor.shape(), &[2]);
6948/// ```
6949impl From<TypedTensor<f64>> for Tensor {
6950 fn from(t: TypedTensor<f64>) -> Self {
6951 Tensor::from_typed(t)
6952 }
6953}
6954
6955/// Wrap an `f32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
6956///
6957/// # Examples
6958///
6959/// ```
6960/// use tenferro_tensor::{Tensor, TypedTensor};
6961///
6962/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
6963/// let tensor: Tensor = typed.into();
6964/// assert_eq!(tensor.shape(), &[2]);
6965/// ```
6966impl From<TypedTensor<f32>> for Tensor {
6967 fn from(t: TypedTensor<f32>) -> Self {
6968 Tensor::from_typed(t)
6969 }
6970}
6971
6972/// Wrap an `i64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
6973///
6974/// # Examples
6975///
6976/// ```
6977/// use tenferro_tensor::{DType, Tensor, TypedTensor};
6978///
6979/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i64, 2]).unwrap();
6980/// let tensor: Tensor = typed.into();
6981/// assert_eq!(tensor.dtype(), DType::I64);
6982/// assert_eq!(tensor.shape(), &[2]);
6983/// ```
6984impl From<TypedTensor<i64>> for Tensor {
6985 fn from(t: TypedTensor<i64>) -> Self {
6986 Tensor::from_typed(t)
6987 }
6988}
6989
6990/// Wrap an `i32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
6991///
6992/// # Examples
6993///
6994/// ```
6995/// use tenferro_tensor::{DType, Tensor, TypedTensor};
6996///
6997/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i32, 2]).unwrap();
6998/// let tensor: Tensor = typed.into();
6999/// assert_eq!(tensor.dtype(), DType::I32);
7000/// assert_eq!(tensor.shape(), &[2]);
7001/// ```
7002impl From<TypedTensor<i32>> for Tensor {
7003 fn from(t: TypedTensor<i32>) -> Self {
7004 Tensor::from_typed(t)
7005 }
7006}
7007
7008/// Wrap a `bool` [`TypedTensor`] into the corresponding [`Tensor`] variant.
7009///
7010/// # Examples
7011///
7012/// ```
7013/// use tenferro_tensor::{DType, Tensor, TypedTensor};
7014///
7015/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![true, false]).unwrap();
7016/// let tensor: Tensor = typed.into();
7017/// assert_eq!(tensor.dtype(), DType::Bool);
7018/// assert_eq!(tensor.shape(), &[2]);
7019/// ```
7020impl From<TypedTensor<bool>> for Tensor {
7021 fn from(t: TypedTensor<bool>) -> Self {
7022 Tensor::from_typed(t)
7023 }
7024}
7025
7026/// Wrap a [`Complex64`] [`TypedTensor`] into the corresponding [`Tensor`]
7027/// variant.
7028///
7029/// # Examples
7030///
7031/// ```
7032/// use num_complex::Complex64;
7033/// use tenferro_tensor::{Tensor, TypedTensor};
7034///
7035/// let typed = TypedTensor::from_vec_col_major(
7036/// vec![1],
7037/// vec![Complex64::new(1.0, 2.0)],
7038/// ).unwrap();
7039/// let tensor: Tensor = typed.into();
7040/// assert_eq!(tensor.shape(), &[1]);
7041/// ```
7042impl From<TypedTensor<Complex<f64>>> for Tensor {
7043 fn from(t: TypedTensor<Complex<f64>>) -> Self {
7044 Tensor::from_typed(t)
7045 }
7046}
7047
7048/// Wrap a [`Complex32`] [`TypedTensor`] into the corresponding [`Tensor`]
7049/// variant.
7050///
7051/// # Examples
7052///
7053/// ```
7054/// use num_complex::Complex32;
7055/// use tenferro_tensor::{Tensor, TypedTensor};
7056///
7057/// let typed = TypedTensor::from_vec_col_major(
7058/// vec![1],
7059/// vec![Complex32::new(1.0, 2.0)],
7060/// ).unwrap();
7061/// let tensor: Tensor = typed.into();
7062/// assert_eq!(tensor.shape(), &[1]);
7063/// ```
7064impl From<TypedTensor<Complex<f32>>> for Tensor {
7065 fn from(t: TypedTensor<Complex<f32>>) -> Self {
7066 Tensor::from_typed(t)
7067 }
7068}
7069
7070impl<'a> TensorView<'a> {
7071 /// Create a dynamic `f32` view over compact column-major host data.
7072 ///
7073 /// # Examples
7074 ///
7075 /// ```
7076 /// use tenferro_tensor::{DType, TensorView};
7077 ///
7078 /// let data = [1.0_f32, 2.0];
7079 /// let view = TensorView::f32(&[2], &data)?;
7080 /// assert_eq!(view.dtype(), DType::F32);
7081 /// # Ok::<(), tenferro_tensor::Error>(())
7082 /// ```
7083 /// # Errors
7084 ///
7085 /// Returns [`crate::Error::Validation`] with
7086 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7087 /// shape or offset arithmetic overflow, or
7088 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7089 /// compact shape reaches beyond `data`.
7090 pub fn f32(shape: &'a [usize], data: &'a [f32]) -> crate::Result<Self> {
7091 Ok(Self::F32(TypedTensorView::from_col_major(shape, data)?))
7092 }
7093
7094 /// Create a dynamic `f64` view over compact column-major host data.
7095 ///
7096 /// # Examples
7097 ///
7098 /// ```
7099 /// use tenferro_tensor::{DType, TensorView};
7100 ///
7101 /// let data = [1.0_f64, 2.0];
7102 /// let view = TensorView::f64(&[2], &data)?;
7103 /// assert_eq!(view.dtype(), DType::F64);
7104 /// # Ok::<(), tenferro_tensor::Error>(())
7105 /// ```
7106 /// # Errors
7107 ///
7108 /// Returns [`crate::Error::Validation`] with
7109 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7110 /// shape or offset arithmetic overflow, or
7111 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7112 /// compact shape reaches beyond `data`.
7113 pub fn f64(shape: &'a [usize], data: &'a [f64]) -> crate::Result<Self> {
7114 Ok(Self::F64(TypedTensorView::from_col_major(shape, data)?))
7115 }
7116
7117 /// Create a dynamic `i64` view over compact column-major host data.
7118 ///
7119 /// # Examples
7120 ///
7121 /// ```
7122 /// use tenferro_tensor::{DType, TensorView};
7123 ///
7124 /// let data = [1_i64, 2];
7125 /// let view = TensorView::i64(&[2], &data)?;
7126 /// assert_eq!(view.dtype(), DType::I64);
7127 /// # Ok::<(), tenferro_tensor::Error>(())
7128 /// ```
7129 /// # Errors
7130 ///
7131 /// Returns [`crate::Error::Validation`] with
7132 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7133 /// shape or offset arithmetic overflow, or
7134 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7135 /// compact shape reaches beyond `data`.
7136 pub fn i64(shape: &'a [usize], data: &'a [i64]) -> crate::Result<Self> {
7137 Ok(Self::I64(TypedTensorView::from_col_major(shape, data)?))
7138 }
7139
7140 /// Create a dynamic `i32` view over compact column-major host data.
7141 ///
7142 /// # Examples
7143 ///
7144 /// ```
7145 /// use tenferro_tensor::{DType, TensorView};
7146 ///
7147 /// let data = [1_i32, 2];
7148 /// let view = TensorView::i32(&[2], &data)?;
7149 /// assert_eq!(view.dtype(), DType::I32);
7150 /// # Ok::<(), tenferro_tensor::Error>(())
7151 /// ```
7152 /// # Errors
7153 ///
7154 /// Returns [`crate::Error::Validation`] with
7155 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7156 /// shape or offset arithmetic overflow, or
7157 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7158 /// compact shape reaches beyond `data`.
7159 pub fn i32(shape: &'a [usize], data: &'a [i32]) -> crate::Result<Self> {
7160 Ok(Self::I32(TypedTensorView::from_col_major(shape, data)?))
7161 }
7162
7163 /// Create a dynamic `bool` view over compact column-major host data.
7164 ///
7165 /// # Examples
7166 ///
7167 /// ```
7168 /// use tenferro_tensor::{DType, TensorView};
7169 ///
7170 /// let data = [true, false];
7171 /// let view = TensorView::bool(&[2], &data)?;
7172 /// assert_eq!(view.dtype(), DType::Bool);
7173 /// # Ok::<(), tenferro_tensor::Error>(())
7174 /// ```
7175 /// # Errors
7176 ///
7177 /// Returns [`crate::Error::Validation`] with
7178 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7179 /// shape or offset arithmetic overflow, or
7180 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7181 /// compact shape reaches beyond `data`.
7182 pub fn bool(shape: &'a [usize], data: &'a [bool]) -> crate::Result<Self> {
7183 Ok(Self::Bool(TypedTensorView::from_col_major(shape, data)?))
7184 }
7185
7186 /// Create a dynamic `Complex32` view over compact column-major host data.
7187 ///
7188 /// # Examples
7189 ///
7190 /// ```
7191 /// use num_complex::Complex32;
7192 /// use tenferro_tensor::{DType, TensorView};
7193 ///
7194 /// let data = [Complex32::new(1.0, 2.0)];
7195 /// let view = TensorView::c32(&[1], &data)?;
7196 /// assert_eq!(view.dtype(), DType::C32);
7197 /// # Ok::<(), tenferro_tensor::Error>(())
7198 /// ```
7199 /// # Errors
7200 ///
7201 /// Returns [`crate::Error::Validation`] with
7202 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7203 /// shape or offset arithmetic overflow, or
7204 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7205 /// compact shape reaches beyond `data`.
7206 pub fn c32(shape: &'a [usize], data: &'a [Complex32]) -> crate::Result<Self> {
7207 Ok(Self::C32(TypedTensorView::from_col_major(shape, data)?))
7208 }
7209
7210 /// Create a dynamic `Complex64` view over compact column-major host data.
7211 ///
7212 /// # Examples
7213 ///
7214 /// ```
7215 /// use num_complex::Complex64;
7216 /// use tenferro_tensor::{DType, TensorView};
7217 ///
7218 /// let data = [Complex64::new(1.0, 2.0)];
7219 /// let view = TensorView::c64(&[1], &data)?;
7220 /// assert_eq!(view.dtype(), DType::C64);
7221 /// # Ok::<(), tenferro_tensor::Error>(())
7222 /// ```
7223 /// # Errors
7224 ///
7225 /// Returns [`crate::Error::Validation`] with
7226 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
7227 /// shape or offset arithmetic overflow, or
7228 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7229 /// compact shape reaches beyond `data`.
7230 pub fn c64(shape: &'a [usize], data: &'a [Complex64]) -> crate::Result<Self> {
7231 Ok(Self::C64(TypedTensorView::from_col_major(shape, data)?))
7232 }
7233
7234 /// Return the element dtype of this borrowed view.
7235 ///
7236 /// # Examples
7237 ///
7238 /// ```
7239 /// use tenferro_tensor::{DType, TensorView};
7240 ///
7241 /// let view = TensorView::f64(&[2], &[1.0, 2.0])?;
7242 /// assert_eq!(view.dtype(), DType::F64);
7243 /// # Ok::<(), tenferro_tensor::Error>(())
7244 /// ```
7245 pub fn dtype(&self) -> DType {
7246 match self {
7247 Self::F32(_) => DType::F32,
7248 Self::F64(_) => DType::F64,
7249 Self::I32(_) => DType::I32,
7250 Self::I64(_) => DType::I64,
7251 Self::Bool(_) => DType::Bool,
7252 Self::C32(_) => DType::C32,
7253 Self::C64(_) => DType::C64,
7254 }
7255 }
7256
7257 /// Return the logical shape of this borrowed view.
7258 ///
7259 /// # Examples
7260 ///
7261 /// ```
7262 /// use tenferro_tensor::TensorView;
7263 ///
7264 /// let view = TensorView::i32(&[2, 1], &[1, 2])?;
7265 /// assert_eq!(view.shape(), &[2, 1]);
7266 /// # Ok::<(), tenferro_tensor::Error>(())
7267 /// ```
7268 pub fn shape(&self) -> &[usize] {
7269 match self {
7270 Self::F32(t) => t.shape(),
7271 Self::F64(t) => t.shape(),
7272 Self::I32(t) => t.shape(),
7273 Self::I64(t) => t.shape(),
7274 Self::Bool(t) => t.shape(),
7275 Self::C32(t) => t.shape(),
7276 Self::C64(t) => t.shape(),
7277 }
7278 }
7279
7280 /// Borrow a contiguous host slice when the requested scalar matches this view's dtype.
7281 ///
7282 /// Backend buffers and non-contiguous views return an explicit error. No
7283 /// download or materialization is performed.
7284 ///
7285 /// # Errors
7286 ///
7287 /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
7288 /// view dtype, [`ValidationError::NonContiguousViewAsSlice`] for a
7289 /// non-contiguous layout, or [`crate::Error::HostAccess`] for unavailable
7290 /// backend host access.
7291 pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
7292 if self.dtype() != T::dtype() {
7293 return Err(crate::Error::validation(
7294 "TensorView::as_slice",
7295 ValidationError::DTypeMismatch {
7296 expected: T::dtype(),
7297 actual: self.dtype(),
7298 },
7299 ));
7300 }
7301 match self {
7302 Self::F32(view) => cast_view_slice(view.as_slice()?),
7303 Self::F64(view) => cast_view_slice(view.as_slice()?),
7304 Self::I32(view) => cast_view_slice(view.as_slice()?),
7305 Self::I64(view) => cast_view_slice(view.as_slice()?),
7306 Self::Bool(view) => cast_view_slice(view.as_slice()?),
7307 Self::C32(view) => cast_view_slice(view.as_slice()?),
7308 Self::C64(view) => cast_view_slice(view.as_slice()?),
7309 }
7310 }
7311
7312 /// Reinterpret a complex view as its sealed real representation.
7313 ///
7314 /// # Examples
7315 ///
7316 /// ```
7317 /// use tenferro_tensor::{Complex64, DType, TensorView};
7318 ///
7319 /// let data = [Complex64::new(1.0, 2.0)];
7320 /// let real = TensorView::c64(&[1], &data)?.as_real_view()?;
7321 /// assert_eq!(real.dtype(), DType::F64);
7322 /// assert_eq!(real.as_slice::<f64>()?, &[1.0, 2.0]);
7323 /// # Ok::<(), tenferro_tensor::Error>(())
7324 /// ```
7325 ///
7326 /// # Errors
7327 ///
7328 /// Returns [`crate::Error::Unsupported`] for the wrong dtype pair and
7329 /// [`ValidationError::InvalidArgument`] or
7330 /// [`ValidationError::ViewOutOfBounds`] for invalid layout metadata.
7331 pub fn as_real_view(&self) -> crate::Result<Self> {
7332 match self {
7333 Self::C32(t) => t.as_real_view().map(Self::F32),
7334 Self::C64(t) => t.as_real_view().map(Self::F64),
7335 _ => Err(crate::Error::unsupported(
7336 "TensorView::as_real_view",
7337 "only complex views have a sealed real representation",
7338 )),
7339 }
7340 }
7341
7342 /// Reinterpret a real view as its sealed complex representation.
7343 ///
7344 /// # Examples
7345 ///
7346 /// ```
7347 /// use tenferro_tensor::{Complex64, DType, TensorView};
7348 ///
7349 /// let data = [1.0_f64, 2.0];
7350 /// let complex = TensorView::f64(&[2, 1], &data)?.as_complex_view()?;
7351 /// assert_eq!(complex.dtype(), DType::C64);
7352 /// assert_eq!(complex.as_slice::<Complex64>()?, &[Complex64::new(1.0, 2.0)]);
7353 /// # Ok::<(), tenferro_tensor::Error>(())
7354 /// ```
7355 ///
7356 /// # Errors
7357 ///
7358 /// Returns [`crate::Error::Unsupported`] for the wrong dtype pair and
7359 /// [`ValidationError::InvalidArgument`] or
7360 /// [`ValidationError::ViewOutOfBounds`] for invalid layout metadata.
7361 pub fn as_complex_view(&self) -> crate::Result<Self> {
7362 match self {
7363 Self::F32(t) => t.as_complex_view().map(Self::C32),
7364 Self::F64(t) => t.as_complex_view().map(Self::C64),
7365 _ => Err(crate::Error::unsupported(
7366 "TensorView::as_complex_view",
7367 "only real views have a sealed complex representation",
7368 )),
7369 }
7370 }
7371
7372 /// Return the placement metadata carried by this borrowed view.
7373 ///
7374 /// # Examples
7375 ///
7376 /// ```
7377 /// use tenferro_tensor::{MemoryKind, TensorView};
7378 ///
7379 /// let view = TensorView::f64(&[1], &[1.0])?;
7380 /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
7381 /// # Ok::<(), tenferro_tensor::Error>(())
7382 /// ```
7383 pub fn placement(&self) -> &Placement {
7384 match self {
7385 Self::F32(t) => t.placement(),
7386 Self::F64(t) => t.placement(),
7387 Self::I32(t) => t.placement(),
7388 Self::I64(t) => t.placement(),
7389 Self::Bool(t) => t.placement(),
7390 Self::C32(t) => t.placement(),
7391 Self::C64(t) => t.placement(),
7392 }
7393 }
7394
7395 /// Return the physical backend family, when this view is backend-owned.
7396 ///
7397 /// # Examples
7398 ///
7399 /// ```
7400 /// use tenferro_tensor::TensorView;
7401 ///
7402 /// let view = TensorView::f64(&[1], &[1.0])?;
7403 /// assert_eq!(view.backend_family(), None);
7404 /// # Ok::<(), tenferro_tensor::Error>(())
7405 /// ```
7406 pub fn backend_family(&self) -> Option<&'static str> {
7407 match self {
7408 Self::F32(t) => t.backend_family(),
7409 Self::F64(t) => t.backend_family(),
7410 Self::I32(t) => t.backend_family(),
7411 Self::I64(t) => t.backend_family(),
7412 Self::Bool(t) => t.backend_family(),
7413 Self::C32(t) => t.backend_family(),
7414 Self::C64(t) => t.backend_family(),
7415 }
7416 }
7417
7418 /// Return the shared allocation domain, when this view has one.
7419 ///
7420 /// # Examples
7421 ///
7422 /// ```
7423 /// use tenferro_tensor::TensorView;
7424 ///
7425 /// let view = TensorView::f64(&[1], &[1.0])?;
7426 /// assert_eq!(view.allocation_domain(), None);
7427 /// # Ok::<(), tenferro_tensor::Error>(())
7428 /// ```
7429 pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
7430 match self {
7431 Self::F32(t) => t.allocation_domain(),
7432 Self::F64(t) => t.allocation_domain(),
7433 Self::I32(t) => t.allocation_domain(),
7434 Self::I64(t) => t.allocation_domain(),
7435 Self::Bool(t) => t.allocation_domain(),
7436 Self::C32(t) => t.allocation_domain(),
7437 Self::C64(t) => t.allocation_domain(),
7438 }
7439 }
7440
7441 /// Return strides in element units.
7442 ///
7443 /// # Examples
7444 ///
7445 /// ```
7446 /// use tenferro_tensor::TensorView;
7447 ///
7448 /// let data = [0.0_f64; 6];
7449 /// let view = TensorView::f64(&[2, 3], &data)?;
7450 /// assert_eq!(view.strides(), &[1, 2]);
7451 /// # Ok::<(), tenferro_tensor::Error>(())
7452 /// ```
7453 pub fn strides(&self) -> &[isize] {
7454 match self {
7455 Self::F32(t) => t.strides(),
7456 Self::F64(t) => t.strides(),
7457 Self::I32(t) => t.strides(),
7458 Self::I64(t) => t.strides(),
7459 Self::Bool(t) => t.strides(),
7460 Self::C32(t) => t.strides(),
7461 Self::C64(t) => t.strides(),
7462 }
7463 }
7464
7465 /// Return the physical element offset.
7466 pub fn offset(&self) -> isize {
7467 match self {
7468 Self::F32(t) => t.offset(),
7469 Self::F64(t) => t.offset(),
7470 Self::I32(t) => t.offset(),
7471 Self::I64(t) => t.offset(),
7472 Self::Bool(t) => t.offset(),
7473 Self::C32(t) => t.offset(),
7474 Self::C64(t) => t.offset(),
7475 }
7476 }
7477
7478 /// Compute the physical element offset for a logical index.
7479 /// # Errors
7480 ///
7481 /// Returns [`crate::Error::Validation`] with
7482 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7483 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7484 /// when an index is outside its axis extent, or
7485 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7486 /// arithmetic overflows.
7487 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7488 match self {
7489 Self::F32(t) => t.layout_linear_offset(indices),
7490 Self::F64(t) => t.layout_linear_offset(indices),
7491 Self::I32(t) => t.layout_linear_offset(indices),
7492 Self::I64(t) => t.layout_linear_offset(indices),
7493 Self::Bool(t) => t.layout_linear_offset(indices),
7494 Self::C32(t) => t.layout_linear_offset(indices),
7495 Self::C64(t) => t.layout_linear_offset(indices),
7496 }
7497 }
7498
7499 /// Return whether this view is compact column-major.
7500 /// # Errors
7501 ///
7502 /// Returns [`crate::Error::Validation`] with
7503 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7504 /// compactness arithmetic overflows.
7505 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
7506 match self {
7507 Self::F32(t) => t.is_col_major_contiguous(),
7508 Self::F64(t) => t.is_col_major_contiguous(),
7509 Self::I32(t) => t.is_col_major_contiguous(),
7510 Self::I64(t) => t.is_col_major_contiguous(),
7511 Self::Bool(t) => t.is_col_major_contiguous(),
7512 Self::C32(t) => t.is_col_major_contiguous(),
7513 Self::C64(t) => t.is_col_major_contiguous(),
7514 }
7515 }
7516
7517 /// Return a compact string summary of this view's layout metadata.
7518 pub fn layout_summary(&self) -> String {
7519 layout_summary(self.shape(), self.strides(), self.offset())
7520 }
7521
7522 /// Assert this view is compact column-major.
7523 /// # Errors
7524 ///
7525 /// Returns [`crate::Error::Validation`] with
7526 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7527 /// compactness arithmetic overflows, or
7528 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7529 /// view is not compact column-major.
7530 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
7531 assert_layout_col_major_contiguous(
7532 self.is_col_major_contiguous()?,
7533 self.shape(),
7534 self.strides(),
7535 self.offset(),
7536 "TensorView::assert_col_major_contiguous",
7537 )
7538 }
7539
7540 /// Explicitly duplicate a compact host view into a fresh tensor.
7541 ///
7542 /// Backend views and non-contiguous layouts return a typed error; this
7543 /// operation never downloads or silently canonicalizes a view.
7544 ///
7545 /// # Examples
7546 ///
7547 /// ```
7548 /// use tenferro_tensor::{TensorView, TypedTensorView};
7549 ///
7550 /// let data = [1_i32, 2];
7551 /// let view = TensorView::I32(TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?);
7552 /// let copy = view.duplicate()?;
7553 /// assert_eq!(copy.shape(), &[2]);
7554 /// # Ok::<(), tenferro_tensor::Error>(())
7555 /// ```
7556 ///
7557 /// # Errors
7558 ///
7559 /// Returns [`crate::Error::HostAccess`] for backend-owned views,
7560 /// [`ValidationError::NonContiguousViewAsSlice`] for non-contiguous views,
7561 /// or [`ValidationError::InvalidArgument`] for invalid layout metadata.
7562 pub fn duplicate(&self) -> crate::Result<Tensor> {
7563 fn duplicate_typed<T: TensorScalar>(
7564 view: &TypedTensorView<'_, T>,
7565 ) -> crate::Result<TypedTensor<T>> {
7566 let mut tensor = TypedTensor::<T>::from_vec_col_major(
7567 view.shape().to_vec(),
7568 view.as_slice()?.to_vec(),
7569 )?;
7570 tensor.set_placement(view.placement().clone());
7571 Ok(tensor)
7572 }
7573
7574 match self {
7575 Self::F32(view) => duplicate_typed(view).map(Tensor::from_typed),
7576 Self::F64(view) => duplicate_typed(view).map(Tensor::from_typed),
7577 Self::I32(view) => duplicate_typed(view).map(Tensor::from_typed),
7578 Self::I64(view) => duplicate_typed(view).map(Tensor::from_typed),
7579 Self::Bool(view) => duplicate_typed(view).map(Tensor::from_typed),
7580 Self::C32(view) => duplicate_typed(view).map(Tensor::from_typed),
7581 Self::C64(view) => duplicate_typed(view).map(Tensor::from_typed),
7582 }
7583 }
7584}
7585
7586macro_rules! tensor_view_mut_constructor {
7587 ($name:ident, $variant:ident, $scalar:ty) => {
7588 /// Create a dynamic mutable view over compact column-major host data.
7589 ///
7590 /// # Errors
7591 ///
7592 /// Returns [`crate::Error::Validation`] with
7593 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7594 /// compact layout arithmetic overflows, or
7595 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7596 /// requested shape exceeds `data`.
7597 pub fn $name(shape: &'a [usize], data: &'a mut [$scalar]) -> crate::Result<Self> {
7598 Ok(Self::$variant(TypedTensorViewMut::from_col_major(
7599 shape, data,
7600 )?))
7601 }
7602 };
7603}
7604
7605impl<'a> TensorViewMut<'a> {
7606 tensor_view_mut_constructor!(f32, F32, f32);
7607 tensor_view_mut_constructor!(f64, F64, f64);
7608 tensor_view_mut_constructor!(i32, I32, i32);
7609 tensor_view_mut_constructor!(i64, I64, i64);
7610 tensor_view_mut_constructor!(bool, Bool, bool);
7611 tensor_view_mut_constructor!(c32, C32, Complex32);
7612 tensor_view_mut_constructor!(c64, C64, Complex64);
7613
7614 pub fn dtype(&self) -> DType {
7615 match self {
7616 Self::F32(_) => DType::F32,
7617 Self::F64(_) => DType::F64,
7618 Self::I32(_) => DType::I32,
7619 Self::I64(_) => DType::I64,
7620 Self::Bool(_) => DType::Bool,
7621 Self::C32(_) => DType::C32,
7622 Self::C64(_) => DType::C64,
7623 }
7624 }
7625
7626 pub fn shape(&self) -> &[usize] {
7627 match self {
7628 Self::F32(t) => t.shape(),
7629 Self::F64(t) => t.shape(),
7630 Self::I32(t) => t.shape(),
7631 Self::I64(t) => t.shape(),
7632 Self::Bool(t) => t.shape(),
7633 Self::C32(t) => t.shape(),
7634 Self::C64(t) => t.shape(),
7635 }
7636 }
7637
7638 /// Return strides in element units.
7639 ///
7640 /// # Examples
7641 ///
7642 /// ```
7643 /// use tenferro_tensor::TensorViewMut;
7644 ///
7645 /// let mut data = [0.0_f64; 6];
7646 /// let view = TensorViewMut::f64(&[2, 3], &mut data)?;
7647 /// assert_eq!(view.strides(), &[1, 2]);
7648 /// # Ok::<(), tenferro_tensor::Error>(())
7649 /// ```
7650 pub fn strides(&self) -> &[isize] {
7651 match self {
7652 Self::F32(t) => t.strides(),
7653 Self::F64(t) => t.strides(),
7654 Self::I32(t) => t.strides(),
7655 Self::I64(t) => t.strides(),
7656 Self::Bool(t) => t.strides(),
7657 Self::C32(t) => t.strides(),
7658 Self::C64(t) => t.strides(),
7659 }
7660 }
7661
7662 pub fn offset(&self) -> isize {
7663 match self {
7664 Self::F32(t) => t.offset(),
7665 Self::F64(t) => t.offset(),
7666 Self::I32(t) => t.offset(),
7667 Self::I64(t) => t.offset(),
7668 Self::Bool(t) => t.offset(),
7669 Self::C32(t) => t.offset(),
7670 Self::C64(t) => t.offset(),
7671 }
7672 }
7673
7674 /// Compute the physical element offset for a logical index.
7675 ///
7676 /// # Errors
7677 ///
7678 /// Returns [`crate::Error::Validation`] with
7679 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7680 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7681 /// when an index is outside its axis extent, or
7682 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7683 /// arithmetic overflows.
7684 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7685 match self {
7686 Self::F32(t) => t.layout_linear_offset(indices),
7687 Self::F64(t) => t.layout_linear_offset(indices),
7688 Self::I32(t) => t.layout_linear_offset(indices),
7689 Self::I64(t) => t.layout_linear_offset(indices),
7690 Self::Bool(t) => t.layout_linear_offset(indices),
7691 Self::C32(t) => t.layout_linear_offset(indices),
7692 Self::C64(t) => t.layout_linear_offset(indices),
7693 }
7694 }
7695
7696 /// Return whether this view is compact column-major.
7697 ///
7698 /// # Errors
7699 ///
7700 /// Returns [`crate::Error::Validation`] with
7701 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7702 /// compactness arithmetic overflows.
7703 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
7704 match self {
7705 Self::F32(t) => t.is_col_major_contiguous(),
7706 Self::F64(t) => t.is_col_major_contiguous(),
7707 Self::I32(t) => t.is_col_major_contiguous(),
7708 Self::I64(t) => t.is_col_major_contiguous(),
7709 Self::Bool(t) => t.is_col_major_contiguous(),
7710 Self::C32(t) => t.is_col_major_contiguous(),
7711 Self::C64(t) => t.is_col_major_contiguous(),
7712 }
7713 }
7714
7715 pub fn layout_summary(&self) -> String {
7716 layout_summary(self.shape(), self.strides(), self.offset())
7717 }
7718
7719 /// Assert this view is compact column-major.
7720 ///
7721 /// # Errors
7722 ///
7723 /// Returns [`crate::Error::Validation`] with
7724 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7725 /// compactness arithmetic overflows, or
7726 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7727 /// view is not compact column-major.
7728 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
7729 assert_layout_col_major_contiguous(
7730 self.is_col_major_contiguous()?,
7731 self.shape(),
7732 self.strides(),
7733 self.offset(),
7734 "TensorViewMut::assert_col_major_contiguous",
7735 )
7736 }
7737
7738 /// Explicitly duplicate the compact host data visible through this
7739 /// mutable view into a new tensor owner.
7740 ///
7741 /// # Examples
7742 ///
7743 /// ```
7744 /// use tenferro_tensor::{TensorViewMut, TypedTensorViewMut};
7745 ///
7746 /// let mut data = [1_i32, 2];
7747 /// let view = TensorViewMut::I32(TypedTensorViewMut::from_slice(
7748 /// vec![2], vec![1], 0, &mut data,
7749 /// )?);
7750 /// let copy = view.duplicate()?;
7751 /// assert_eq!(copy.shape(), &[2]);
7752 /// # Ok::<(), tenferro_tensor::Error>(())
7753 /// ```
7754 ///
7755 /// # Errors
7756 ///
7757 /// Returns [`crate::Error::HostAccess`] for backend-owned views,
7758 /// [`ValidationError::NonContiguousViewAsSlice`] for non-contiguous views,
7759 /// or [`ValidationError::InvalidArgument`] for invalid layout metadata.
7760 pub fn duplicate(&self) -> crate::Result<Tensor> {
7761 self.as_read_only().duplicate()
7762 }
7763
7764 /// Borrow this mutable view as a read-only dtype-erased view.
7765 ///
7766 /// # Examples
7767 ///
7768 /// ```
7769 /// use tenferro_tensor::TensorViewMut;
7770 ///
7771 /// let mut data = [1.0_f64, 2.0];
7772 /// let view = TensorViewMut::f64(&[2], &mut data)?;
7773 /// assert_eq!(view.as_read_only().as_slice::<f64>()?, &[1.0, 2.0]);
7774 /// # Ok::<(), tenferro_tensor::Error>(())
7775 /// ```
7776 pub fn as_read_only(&self) -> TensorView<'_> {
7777 match self {
7778 Self::F32(t) => TensorView::F32(t.as_read_only()),
7779 Self::F64(t) => TensorView::F64(t.as_read_only()),
7780 Self::I32(t) => TensorView::I32(t.as_read_only()),
7781 Self::I64(t) => TensorView::I64(t.as_read_only()),
7782 Self::Bool(t) => TensorView::Bool(t.as_read_only()),
7783 Self::C32(t) => TensorView::C32(t.as_read_only()),
7784 Self::C64(t) => TensorView::C64(t.as_read_only()),
7785 }
7786 }
7787}
7788
7789impl<'a> TensorRead<'a> {
7790 /// Borrow an owned tensor as a read target without copying it.
7791 ///
7792 /// # Examples
7793 ///
7794 /// ```
7795 /// use tenferro_tensor::{Tensor, TensorRead};
7796 ///
7797 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
7798 /// let read = TensorRead::from_tensor(&tensor);
7799 /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
7800 /// # Ok::<(), tenferro_tensor::Error>(())
7801 /// ```
7802 pub fn from_tensor(tensor: &'a Tensor) -> Self {
7803 Self::Tensor(tensor)
7804 }
7805
7806 /// Wrap a borrowed dtype-erased view as a read target.
7807 ///
7808 /// # Examples
7809 ///
7810 /// ```
7811 /// use tenferro_tensor::{TensorRead, TensorView};
7812 ///
7813 /// let read = TensorRead::from_view(TensorView::i32(&[2], &[3, 4])?);
7814 /// assert_eq!(read.as_slice::<i32>()?, &[3, 4]);
7815 /// assert!(read.as_tensor().is_none());
7816 /// # Ok::<(), tenferro_tensor::Error>(())
7817 /// ```
7818 #[inline]
7819 pub fn from_view(view: TensorView<'a>) -> Self {
7820 Self::View(view)
7821 }
7822
7823 /// Convert this read target into a dtype-erased tensor view.
7824 ///
7825 /// Owned tensors are borrowed without copying their storage. Existing
7826 /// views preserve their layout and placement metadata.
7827 /// # Examples
7828 ///
7829 /// ```rust
7830 /// # use tenferro_tensor::{Tensor, TensorRead};
7831 /// # let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
7832 /// let view = TensorRead::from_tensor(&tensor).tensor_view();
7833 /// assert_eq!(view.shape(), &[2]);
7834 /// assert_eq!(view.as_slice::<f64>()?, &[1.0, 2.0]);
7835 /// # Ok::<(), tenferro_tensor::Error>(())
7836 /// ```
7837 pub fn tensor_view(self) -> TensorView<'a> {
7838 match self {
7839 Self::Tensor(tensor) => tensor_view_with_layout(tensor, tensor_layout(tensor)),
7840 Self::View(view) => view,
7841 }
7842 }
7843
7844 /// Borrow this read target as a typed compact host slice without allocation.
7845 ///
7846 /// This delegates to [`TensorView::as_slice`]. Cloning a `TensorRead` is a
7847 /// shallow clone of its borrowed reference or view metadata, so the returned
7848 /// slice retains the original `'a` storage lifetime and never outlives the
7849 /// storage borrowed by this read target. This method does not materialize,
7850 /// transfer, or otherwise canonicalize the input.
7851 ///
7852 /// # Errors
7853 ///
7854 /// Returns [`ValidationError::DTypeMismatch`] when `T` does not match the
7855 /// input dtype, [`ValidationError::NonContiguousViewAsSlice`] for a
7856 /// noncompact view, or a typed runtime-state host-access error for
7857 /// backend-owned storage.
7858 /// Backend-owned
7859 /// inputs are never downloaded implicitly.
7860 ///
7861 /// # Examples
7862 ///
7863 /// ```
7864 /// use tenferro_tensor::{Tensor, TensorRead};
7865 ///
7866 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
7867 /// let read = TensorRead::from_tensor(&tensor);
7868 /// assert_eq!(read.as_slice::<f64>()?, &[1.0, 2.0]);
7869 /// # Ok::<(), tenferro_tensor::Error>(())
7870 /// ```
7871 pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&'a [T]> {
7872 self.clone().tensor_view().as_slice()
7873 }
7874
7875 /// Return the element dtype of this read target.
7876 ///
7877 /// # Examples
7878 ///
7879 /// ```
7880 /// use tenferro_tensor::{DType, Tensor, TensorRead};
7881 ///
7882 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1_i64])?;
7883 /// assert_eq!(TensorRead::from_tensor(&tensor).dtype(), DType::I64);
7884 /// # Ok::<(), tenferro_tensor::Error>(())
7885 /// ```
7886 pub fn dtype(&self) -> DType {
7887 match self {
7888 Self::Tensor(tensor) => tensor.dtype(),
7889 Self::View(view) => view.dtype(),
7890 }
7891 }
7892
7893 /// Return the logical shape of this read target.
7894 ///
7895 /// # Examples
7896 ///
7897 /// ```
7898 /// use tenferro_tensor::{Tensor, TensorRead};
7899 ///
7900 /// let tensor = Tensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
7901 /// assert_eq!(TensorRead::from_tensor(&tensor).shape(), &[2, 1]);
7902 /// # Ok::<(), tenferro_tensor::Error>(())
7903 /// ```
7904 pub fn shape(&self) -> &[usize] {
7905 match self {
7906 Self::Tensor(tensor) => tensor.shape(),
7907 Self::View(view) => view.shape(),
7908 }
7909 }
7910
7911 /// Return the placement metadata carried by this read target.
7912 ///
7913 /// # Examples
7914 ///
7915 /// ```
7916 /// use tenferro_tensor::{MemoryKind, Tensor, TensorRead};
7917 ///
7918 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
7919 /// let read = TensorRead::from_tensor(&tensor);
7920 /// assert_eq!(read.placement().memory_kind, MemoryKind::UnpinnedHost);
7921 /// # Ok::<(), tenferro_tensor::Error>(())
7922 /// ```
7923 pub fn placement(&self) -> &Placement {
7924 match self {
7925 Self::Tensor(tensor) => tensor.placement(),
7926 Self::View(view) => view.placement(),
7927 }
7928 }
7929
7930 /// Return the physical backend family of this read target, when backend-owned.
7931 ///
7932 /// # Examples
7933 ///
7934 /// ```
7935 /// use tenferro_tensor::{Tensor, TensorRead};
7936 ///
7937 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
7938 /// assert_eq!(TensorRead::from_tensor(&tensor).backend_family(), None);
7939 /// # Ok::<(), tenferro_tensor::Error>(())
7940 /// ```
7941 pub fn backend_family(&self) -> Option<&'static str> {
7942 match self {
7943 Self::Tensor(tensor) => match tensor.dtype() {
7944 // A caller-owned payload is host memory without backend family.
7945 DType::External(_) => None,
7946 DType::F32 => tensor.as_typed::<f32>().and_then(|t| t.backend_family()),
7947 DType::F64 => tensor.as_typed::<f64>().and_then(|t| t.backend_family()),
7948 DType::I32 => tensor.as_typed::<i32>().and_then(|t| t.backend_family()),
7949 DType::I64 => tensor.as_typed::<i64>().and_then(|t| t.backend_family()),
7950 DType::Bool => tensor.as_typed::<bool>().and_then(|t| t.backend_family()),
7951 DType::C32 => tensor
7952 .as_typed::<Complex<f32>>()
7953 .and_then(|t| t.backend_family()),
7954 DType::C64 => tensor
7955 .as_typed::<Complex<f64>>()
7956 .and_then(|t| t.backend_family()),
7957 },
7958 Self::View(view) => view.backend_family(),
7959 }
7960 }
7961
7962 /// Return the shared allocation domain of this read target, when present.
7963 ///
7964 /// # Examples
7965 ///
7966 /// ```
7967 /// use tenferro_tensor::{Tensor, TensorRead};
7968 ///
7969 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
7970 /// assert_eq!(TensorRead::from_tensor(&tensor).allocation_domain(), None);
7971 /// # Ok::<(), tenferro_tensor::Error>(())
7972 /// ```
7973 pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
7974 match self {
7975 Self::Tensor(tensor) => match tensor.dtype() {
7976 // A caller-owned payload has no allocation domain.
7977 DType::External(_) => None,
7978 DType::F32 => tensor.as_typed::<f32>().and_then(|t| t.allocation_domain()),
7979 DType::F64 => tensor.as_typed::<f64>().and_then(|t| t.allocation_domain()),
7980 DType::I32 => tensor.as_typed::<i32>().and_then(|t| t.allocation_domain()),
7981 DType::I64 => tensor.as_typed::<i64>().and_then(|t| t.allocation_domain()),
7982 DType::Bool => tensor
7983 .as_typed::<bool>()
7984 .and_then(|t| t.allocation_domain()),
7985 DType::C32 => tensor
7986 .as_typed::<Complex<f32>>()
7987 .and_then(|t| t.allocation_domain()),
7988 DType::C64 => tensor
7989 .as_typed::<Complex<f64>>()
7990 .and_then(|t| t.allocation_domain()),
7991 },
7992 Self::View(view) => view.allocation_domain(),
7993 }
7994 }
7995
7996 /// Return strides in element units; owned tensors report compact column-major strides.
7997 ///
7998 /// # Examples
7999 ///
8000 /// ```
8001 /// use tenferro_tensor::{Tensor, TensorRead};
8002 ///
8003 /// let tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8004 /// assert_eq!(TensorRead::from_tensor(&tensor).strides()?, vec![1, 2]);
8005 /// # Ok::<(), tenferro_tensor::Error>(())
8006 /// ```
8007 ///
8008 /// # Errors
8009 ///
8010 /// Returns [`crate::Error::Validation`] with
8011 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8012 /// column-major stride arithmetic overflows.
8013 pub fn strides(&self) -> crate::Result<Vec<isize>> {
8014 match self {
8015 Self::Tensor(tensor) => col_major_strides(tensor.shape()),
8016 Self::View(view) => Ok(view.strides().to_vec()),
8017 }
8018 }
8019
8020 /// Return the physical element offset; owned tensors always start at `0`.
8021 ///
8022 /// # Examples
8023 ///
8024 /// ```
8025 /// use tenferro_tensor::{TensorRead, TensorView, TypedTensorView};
8026 ///
8027 /// let data = [1.0_f64, 2.0, 3.0];
8028 /// let view = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?);
8029 /// assert_eq!(TensorRead::from_view(view).offset(), 1);
8030 /// # Ok::<(), tenferro_tensor::Error>(())
8031 /// ```
8032 pub fn offset(&self) -> isize {
8033 match self {
8034 Self::Tensor(_) => 0,
8035 Self::View(view) => view.offset(),
8036 }
8037 }
8038
8039 /// Compute the physical element offset for a logical index.
8040 ///
8041 /// # Examples
8042 ///
8043 /// ```
8044 /// use tenferro_tensor::{Tensor, TensorRead};
8045 ///
8046 /// let tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8047 /// let read = TensorRead::from_tensor(&tensor);
8048 /// assert_eq!(read.layout_linear_offset(&[1, 2])?, 5);
8049 /// assert!(read.layout_linear_offset(&[2, 0]).is_err());
8050 /// # Ok::<(), tenferro_tensor::Error>(())
8051 /// ```
8052 ///
8053 /// # Errors
8054 ///
8055 /// Returns [`crate::Error::Validation`] with
8056 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8057 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8058 /// when an index is outside its axis extent, or
8059 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8060 /// arithmetic overflows.
8061 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8062 match self {
8063 Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
8064 Self::View(view) => view.layout_linear_offset(indices),
8065 }
8066 }
8067
8068 /// Return whether this read target is compact column-major.
8069 ///
8070 /// # Examples
8071 ///
8072 /// ```
8073 /// use tenferro_tensor::{Tensor, TensorRead, TensorView, TypedTensorView};
8074 ///
8075 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8076 /// assert!(TensorRead::from_tensor(&tensor).is_col_major_contiguous()?);
8077 /// let data = [1.0_f64, 2.0, 3.0];
8078 /// let strided = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![2], 0, &data)?);
8079 /// assert!(!TensorRead::from_view(strided).is_col_major_contiguous()?);
8080 /// # Ok::<(), tenferro_tensor::Error>(())
8081 /// ```
8082 ///
8083 /// # Errors
8084 ///
8085 /// Returns [`crate::Error::Validation`] with
8086 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8087 /// compactness arithmetic overflows.
8088 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8089 match self {
8090 Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
8091 Self::View(view) => view.is_col_major_contiguous(),
8092 }
8093 }
8094
8095 /// Return a compact string summary of this read target's layout metadata.
8096 ///
8097 /// # Examples
8098 ///
8099 /// ```
8100 /// use tenferro_tensor::{Tensor, TensorRead};
8101 ///
8102 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8103 /// assert_eq!(
8104 /// TensorRead::from_tensor(&tensor).layout_summary(),
8105 /// "shape=[2] strides=[1] offset=0"
8106 /// );
8107 /// # Ok::<(), tenferro_tensor::Error>(())
8108 /// ```
8109 pub fn layout_summary(&self) -> String {
8110 let strides = match self.strides() {
8111 Ok(strides) => strides,
8112 Err(err) => return format!("layout unavailable: {err}"),
8113 };
8114 layout_summary(self.shape(), &strides, self.offset())
8115 }
8116
8117 /// Assert this read target is compact column-major.
8118 ///
8119 /// # Examples
8120 ///
8121 /// ```
8122 /// use tenferro_tensor::{Tensor, TensorRead, TensorView, TypedTensorView};
8123 ///
8124 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8125 /// TensorRead::from_tensor(&tensor).assert_col_major_contiguous()?;
8126 /// let data = [1.0_f64, 2.0, 3.0];
8127 /// let strided = TensorView::F64(TypedTensorView::from_slice(vec![2], vec![2], 0, &data)?);
8128 /// assert!(TensorRead::from_view(strided).assert_col_major_contiguous().is_err());
8129 /// # Ok::<(), tenferro_tensor::Error>(())
8130 /// ```
8131 ///
8132 /// # Errors
8133 ///
8134 /// Returns [`crate::Error::Validation`] with
8135 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8136 /// compactness arithmetic overflows, or
8137 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8138 /// view is not compact column-major.
8139 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8140 let strides = self.strides()?;
8141 assert_layout_col_major_contiguous(
8142 self.is_col_major_contiguous()?,
8143 self.shape(),
8144 &strides,
8145 self.offset(),
8146 "TensorRead::assert_col_major_contiguous",
8147 )
8148 }
8149
8150 /// Return the borrowed owned tensor, or `None` when this read target is a view.
8151 ///
8152 /// # Examples
8153 ///
8154 /// ```
8155 /// use tenferro_tensor::{Tensor, TensorRead, TensorView};
8156 ///
8157 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
8158 /// assert!(TensorRead::from_tensor(&tensor).as_tensor().is_some());
8159 /// assert!(TensorRead::from_view(TensorView::f64(&[1], &[1.0])?).as_tensor().is_none());
8160 /// # Ok::<(), tenferro_tensor::Error>(())
8161 /// ```
8162 pub fn as_tensor(&self) -> Option<&'a Tensor> {
8163 match self {
8164 Self::Tensor(tensor) => Some(*tensor),
8165 Self::View(_) => None,
8166 }
8167 }
8168}
8169
8170impl<'a> TensorWrite<'a> {
8171 /// Borrow an owned tensor as a writable target without copying it.
8172 ///
8173 /// # Examples
8174 ///
8175 /// ```
8176 /// use tenferro_tensor::{Tensor, TensorWrite};
8177 ///
8178 /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8179 /// let write = TensorWrite::from_tensor(&mut tensor);
8180 /// assert_eq!(write.shape(), &[2]);
8181 /// # Ok::<(), tenferro_tensor::Error>(())
8182 /// ```
8183 pub fn from_tensor(tensor: &'a mut Tensor) -> Self {
8184 Self::Tensor(tensor)
8185 }
8186
8187 /// Wrap a borrowed mutable dtype-erased view as a writable target.
8188 ///
8189 /// # Examples
8190 ///
8191 /// ```
8192 /// use tenferro_tensor::{DType, TensorViewMut, TensorWrite};
8193 ///
8194 /// let mut data = [1.0_f64, 2.0];
8195 /// let write = TensorWrite::from_view(TensorViewMut::f64(&[2], &mut data)?);
8196 /// assert_eq!(write.dtype(), DType::F64);
8197 /// assert_eq!(write.as_read().as_slice::<f64>()?, &[1.0, 2.0]);
8198 /// # Ok::<(), tenferro_tensor::Error>(())
8199 /// ```
8200 pub fn from_view(view: TensorViewMut<'a>) -> Self {
8201 Self::View(view)
8202 }
8203
8204 /// Borrow this writable target as a read-only tensor input.
8205 ///
8206 /// This is useful for explicit read-modify-write kernels such as
8207 /// accumulation updates. The returned view borrows through `&self`, so it
8208 /// cannot outlive the current read-only borrow of the writable target.
8209 ///
8210 /// # Examples
8211 ///
8212 /// ```rust
8213 /// use tenferro_tensor::{DType, Tensor, TensorWrite};
8214 ///
8215 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
8216 /// let write = TensorWrite::from_tensor(&mut tensor);
8217 /// let read = write.as_read();
8218 /// assert_eq!(read.dtype(), DType::F64);
8219 /// # Ok::<(), tenferro_tensor::Error>(())
8220 /// ```
8221 pub fn as_read(&self) -> TensorRead<'_> {
8222 match self {
8223 Self::Tensor(tensor) => TensorRead::from_tensor(tensor),
8224 Self::View(view) => TensorRead::from_view(view.as_read_only()),
8225 }
8226 }
8227
8228 /// Return the element dtype of this writable target.
8229 ///
8230 /// # Examples
8231 ///
8232 /// ```
8233 /// use tenferro_tensor::{DType, Tensor, TensorWrite};
8234 ///
8235 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![1_i32])?;
8236 /// assert_eq!(TensorWrite::from_tensor(&mut tensor).dtype(), DType::I32);
8237 /// # Ok::<(), tenferro_tensor::Error>(())
8238 /// ```
8239 pub fn dtype(&self) -> DType {
8240 match self {
8241 Self::Tensor(tensor) => tensor.dtype(),
8242 Self::View(view) => view.dtype(),
8243 }
8244 }
8245
8246 /// Return the logical shape of this writable target.
8247 ///
8248 /// # Examples
8249 ///
8250 /// ```
8251 /// use tenferro_tensor::{Tensor, TensorWrite};
8252 ///
8253 /// let mut tensor = Tensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
8254 /// assert_eq!(TensorWrite::from_tensor(&mut tensor).shape(), &[2, 1]);
8255 /// # Ok::<(), tenferro_tensor::Error>(())
8256 /// ```
8257 pub fn shape(&self) -> &[usize] {
8258 match self {
8259 Self::Tensor(tensor) => tensor.shape(),
8260 Self::View(view) => view.shape(),
8261 }
8262 }
8263
8264 /// Return strides in element units; owned tensors report compact column-major strides.
8265 ///
8266 /// # Examples
8267 ///
8268 /// ```
8269 /// use tenferro_tensor::{Tensor, TensorWrite};
8270 ///
8271 /// let mut tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8272 /// assert_eq!(TensorWrite::from_tensor(&mut tensor).strides()?, vec![1, 2]);
8273 /// # Ok::<(), tenferro_tensor::Error>(())
8274 /// ```
8275 ///
8276 /// # Errors
8277 ///
8278 /// Returns [`crate::Error::Validation`] with
8279 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8280 /// column-major stride arithmetic overflows.
8281 pub fn strides(&self) -> crate::Result<Vec<isize>> {
8282 match self {
8283 Self::Tensor(tensor) => col_major_strides(tensor.shape()),
8284 Self::View(view) => Ok(view.strides().to_vec()),
8285 }
8286 }
8287
8288 /// Return the physical element offset; owned tensors always start at `0`.
8289 ///
8290 /// # Examples
8291 ///
8292 /// ```
8293 /// use tenferro_tensor::{TensorViewMut, TensorWrite, TypedTensorViewMut};
8294 ///
8295 /// let mut data = [1.0_f64, 2.0, 3.0];
8296 /// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![1], 1, &mut data)?);
8297 /// assert_eq!(TensorWrite::from_view(view).offset(), 1);
8298 /// # Ok::<(), tenferro_tensor::Error>(())
8299 /// ```
8300 pub fn offset(&self) -> isize {
8301 match self {
8302 Self::Tensor(_) => 0,
8303 Self::View(view) => view.offset(),
8304 }
8305 }
8306
8307 /// Compute the physical element offset for a logical index.
8308 ///
8309 /// # Examples
8310 ///
8311 /// ```
8312 /// use tenferro_tensor::{Tensor, TensorWrite};
8313 ///
8314 /// let mut tensor = Tensor::from_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?;
8315 /// let write = TensorWrite::from_tensor(&mut tensor);
8316 /// assert_eq!(write.layout_linear_offset(&[1, 2])?, 5);
8317 /// assert!(write.layout_linear_offset(&[0]).is_err());
8318 /// # Ok::<(), tenferro_tensor::Error>(())
8319 /// ```
8320 ///
8321 /// # Errors
8322 ///
8323 /// Returns [`crate::Error::Validation`] with
8324 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8325 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8326 /// when an index is outside its axis extent, or
8327 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8328 /// arithmetic overflows.
8329 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8330 match self {
8331 Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
8332 Self::View(view) => view.layout_linear_offset(indices),
8333 }
8334 }
8335
8336 /// Return whether this writable target is compact column-major.
8337 ///
8338 /// # Examples
8339 ///
8340 /// ```
8341 /// use tenferro_tensor::{Tensor, TensorViewMut, TensorWrite, TypedTensorViewMut};
8342 ///
8343 /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8344 /// assert!(TensorWrite::from_tensor(&mut tensor).is_col_major_contiguous()?);
8345 /// let mut data = [1.0_f64, 2.0, 3.0];
8346 /// let strided = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![2], 0, &mut data)?);
8347 /// assert!(!TensorWrite::from_view(strided).is_col_major_contiguous()?);
8348 /// # Ok::<(), tenferro_tensor::Error>(())
8349 /// ```
8350 ///
8351 /// # Errors
8352 ///
8353 /// Returns [`crate::Error::Validation`] with
8354 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8355 /// compactness arithmetic overflows.
8356 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8357 match self {
8358 Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
8359 Self::View(view) => view.is_col_major_contiguous(),
8360 }
8361 }
8362
8363 /// Return a compact string summary of this writable target's layout metadata.
8364 ///
8365 /// # Examples
8366 ///
8367 /// ```
8368 /// use tenferro_tensor::{Tensor, TensorWrite};
8369 ///
8370 /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8371 /// assert_eq!(
8372 /// TensorWrite::from_tensor(&mut tensor).layout_summary(),
8373 /// "shape=[2] strides=[1] offset=0"
8374 /// );
8375 /// # Ok::<(), tenferro_tensor::Error>(())
8376 /// ```
8377 pub fn layout_summary(&self) -> String {
8378 let strides = match self.strides() {
8379 Ok(strides) => strides,
8380 Err(err) => return format!("layout unavailable: {err}"),
8381 };
8382 layout_summary(self.shape(), &strides, self.offset())
8383 }
8384
8385 /// Assert this writable target is compact column-major.
8386 ///
8387 /// # Examples
8388 ///
8389 /// ```
8390 /// use tenferro_tensor::{Tensor, TensorViewMut, TensorWrite, TypedTensorViewMut};
8391 ///
8392 /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8393 /// TensorWrite::from_tensor(&mut tensor).assert_col_major_contiguous()?;
8394 /// let mut data = [1.0_f64, 2.0, 3.0];
8395 /// let strided = TensorViewMut::F64(TypedTensorViewMut::from_slice(vec![2], vec![2], 0, &mut data)?);
8396 /// assert!(TensorWrite::from_view(strided).assert_col_major_contiguous().is_err());
8397 /// # Ok::<(), tenferro_tensor::Error>(())
8398 /// ```
8399 ///
8400 /// # Errors
8401 ///
8402 /// Returns [`crate::Error::Validation`] with
8403 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8404 /// compactness arithmetic overflows, or
8405 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8406 /// view is not compact column-major.
8407 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8408 let strides = self.strides()?;
8409 assert_layout_col_major_contiguous(
8410 self.is_col_major_contiguous()?,
8411 self.shape(),
8412 &strides,
8413 self.offset(),
8414 "TensorWrite::assert_col_major_contiguous",
8415 )
8416 }
8417}
8418
8419/// Column-major strides derived from a shape.
8420///
8421/// # Examples
8422///
8423/// ```rust
8424/// use tenferro_tensor::col_major_strides;
8425///
8426/// assert_eq!(col_major_strides(&[2, 3])?, vec![1, 2]);
8427/// # Ok::<(), tenferro_tensor::Error>(())
8428/// ```
8429/// # Errors
8430///
8431/// Returns [`crate::Error::Validation`] with
8432/// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when a
8433/// column-major stride product overflows.
8434pub fn col_major_strides(shape: &[usize]) -> crate::Result<Vec<isize>> {
8435 let mut strides = Vec::with_capacity(shape.len());
8436 let mut stride = 1isize;
8437 for &extent in shape {
8438 strides.push(stride);
8439 let extent = isize::try_from(extent).map_err(|_| {
8440 crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
8441 })?;
8442 stride = stride.checked_mul(extent).ok_or_else(|| {
8443 crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
8444 })?;
8445 }
8446 Ok(strides)
8447}
8448
8449fn try_linear_offset_for_shape(
8450 shape: &[usize],
8451 indices: &[usize],
8452 op: &'static str,
8453) -> crate::Result<usize> {
8454 if indices.len() != shape.len() {
8455 return Err(crate::Error::validation(
8456 op,
8457 ValidationError::RankMismatch {
8458 expected: shape.len(),
8459 actual: indices.len(),
8460 },
8461 ));
8462 }
8463 let mut offset = 0usize;
8464 let mut stride = 1usize;
8465 for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
8466 if idx >= extent {
8467 return Err(crate::Error::invalid_argument(
8468 op,
8469 "index",
8470 format!("index {idx} out of bounds for axis {axis} extent {extent}"),
8471 ));
8472 }
8473 offset =
8474 offset
8475 .checked_add(idx.checked_mul(stride).ok_or_else(|| {
8476 crate::Error::validation(op, ValidationError::IntegerOverflow)
8477 })?)
8478 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8479 stride = stride
8480 .checked_mul(extent)
8481 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8482 }
8483 Ok(offset)
8484}
8485
8486fn checked_view_offset_result(
8487 shape: &[usize],
8488 strides: &[isize],
8489 base_offset: isize,
8490 indices: &[usize],
8491 op: &'static str,
8492) -> crate::Result<usize> {
8493 if indices.len() != shape.len() {
8494 return Err(crate::Error::validation(
8495 op,
8496 ValidationError::RankMismatch {
8497 expected: shape.len(),
8498 actual: indices.len(),
8499 },
8500 ));
8501 }
8502 for (axis, (&index, &extent)) in indices.iter().zip(shape).enumerate() {
8503 if index >= extent {
8504 return Err(crate::Error::invalid_argument(
8505 op,
8506 "index",
8507 format!("index {index} out of bounds for axis {axis} extent {extent}"),
8508 ));
8509 }
8510 }
8511 checked_view_offset(shape, strides, base_offset, indices)
8512 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
8513}
8514
8515fn layout_summary(shape: &[usize], strides: &[isize], offset: isize) -> String {
8516 format!("shape={shape:?} strides={strides:?} offset={offset}")
8517}
8518
8519fn assert_layout_col_major_contiguous(
8520 is_contiguous: bool,
8521 shape: &[usize],
8522 strides: &[isize],
8523 offset: isize,
8524 op: &'static str,
8525) -> crate::Result<()> {
8526 if is_contiguous {
8527 Ok(())
8528 } else {
8529 Err(crate::Error::invalid_argument(
8530 op,
8531 "layout",
8532 format!(
8533 "expected compact column-major layout, got {}",
8534 layout_summary(shape, strides, offset)
8535 ),
8536 ))
8537 }
8538}
8539
8540fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
8541 shape.iter().try_fold(1usize, |acc, &dim| {
8542 acc.checked_mul(dim)
8543 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
8544 })
8545}
8546
8547fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
8548 let n = try_shape_product(shape, op)?;
8549 if data_len != n {
8550 return Err(crate::Error::validation(
8551 op,
8552 ValidationError::ShapeDataLengthMismatch {
8553 expected: n,
8554 actual: data_len,
8555 },
8556 ));
8557 }
8558 Ok(())
8559}
8560
8561fn try_compact_layout<R: TensorRank>(
8562 shape: impl tenferro_tensor_core::IntoRankShape<R>,
8563 op: &'static str,
8564) -> crate::Result<TensorLayout<R>> {
8565 let shape = shape
8566 .into_rank_shape()
8567 .map_err(|err| tensor_layout_error(op, err))?;
8568 TensorLayout::compact(shape).map_err(|err| tensor_layout_error(op, err))
8569}
8570
8571fn tensor_layout_error(
8572 op: &'static str,
8573 err: tenferro_tensor_core::ValidationError,
8574) -> crate::Error {
8575 crate::Error::validation(op, err)
8576}
8577
8578fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
8579 if shape.contains(&0) {
8580 return Ok(0);
8581 }
8582 shape.iter().try_fold(1usize, |product, &dim| {
8583 product
8584 .checked_mul(dim)
8585 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
8586 })
8587}
8588
8589fn checked_view_offset(
8590 shape: &[usize],
8591 strides: &[isize],
8592 base_offset: isize,
8593 indices: &[usize],
8594) -> Option<usize> {
8595 if indices.len() != shape.len() {
8596 return None;
8597 }
8598
8599 let mut offset = base_offset;
8600 for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
8601 if index >= extent {
8602 return None;
8603 }
8604 let index = isize::try_from(index).ok()?;
8605 let delta = index.checked_mul(stride)?;
8606 offset = offset.checked_add(delta)?;
8607 }
8608
8609 usize::try_from(offset).ok()
8610}
8611
8612fn reachable_layout_span(
8613 shape: &[usize],
8614 strides: &[isize],
8615 offset: isize,
8616) -> crate::Result<Option<(usize, usize)>> {
8617 if shape.contains(&0) {
8618 return Ok(None);
8619 }
8620
8621 let mut min_offset = offset;
8622 let mut max_offset = offset;
8623 for (&extent, &stride) in shape.iter().zip(strides) {
8624 let steps = isize::try_from(extent.saturating_sub(1)).map_err(|_| {
8625 crate::Error::validation(
8626 "TypedTensorViewMut::try_multi_slice_mut",
8627 ValidationError::IntegerOverflow,
8628 )
8629 })?;
8630 let end = stride.checked_mul(steps).ok_or_else(|| {
8631 crate::Error::validation(
8632 "TypedTensorViewMut::try_multi_slice_mut",
8633 ValidationError::IntegerOverflow,
8634 )
8635 })?;
8636 let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
8637 min_offset = min_offset.checked_add(axis_min).ok_or_else(|| {
8638 crate::Error::validation(
8639 "TypedTensorViewMut::try_multi_slice_mut",
8640 ValidationError::IntegerOverflow,
8641 )
8642 })?;
8643 max_offset = max_offset.checked_add(axis_max).ok_or_else(|| {
8644 crate::Error::validation(
8645 "TypedTensorViewMut::try_multi_slice_mut",
8646 ValidationError::IntegerOverflow,
8647 )
8648 })?;
8649 }
8650
8651 let min_offset = usize::try_from(min_offset).map_err(|_| {
8652 crate::Error::invalid_argument(
8653 "TypedTensorViewMut::try_multi_slice_mut",
8654 "layout",
8655 "minimum reachable offset is negative",
8656 )
8657 })?;
8658 let max_offset = usize::try_from(max_offset).map_err(|_| {
8659 crate::Error::invalid_argument(
8660 "TypedTensorViewMut::try_multi_slice_mut",
8661 "layout",
8662 "maximum reachable offset is negative",
8663 )
8664 })?;
8665 Ok(Some((min_offset, max_offset)))
8666}
8667
8668fn split_two_mut_ranges<T>(
8669 data: &mut [T],
8670 first: (usize, usize),
8671 second: (usize, usize),
8672) -> Option<(&mut [T], &mut [T])> {
8673 if first.1 < second.0 {
8674 let (_, after_first_start) = data.split_at_mut(first.0);
8675 let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
8676 let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
8677 let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
8678 Some((first_slice, second_slice))
8679 } else if second.1 < first.0 {
8680 let (_, after_second_start) = data.split_at_mut(second.0);
8681 let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
8682 let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
8683 let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
8684 Some((first_slice, second_slice))
8685 } else {
8686 None
8687 }
8688}
8689
8690fn adjusted_view_offset(offset: isize, span_start: usize) -> crate::Result<isize> {
8691 let span_start = isize::try_from(span_start).map_err(|_| {
8692 crate::Error::validation(
8693 "TypedTensorViewMut::try_multi_slice_mut",
8694 ValidationError::IntegerOverflow,
8695 )
8696 })?;
8697 offset.checked_sub(span_start).ok_or_else(|| {
8698 crate::Error::validation(
8699 "TypedTensorViewMut::try_multi_slice_mut",
8700 ValidationError::IntegerOverflow,
8701 )
8702 })
8703}
8704
8705fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
8706 layout: &TensorLayout<R>,
8707 offset: isize,
8708 data: &'a mut [T],
8709 placement: Placement,
8710) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
8711 let shape = R::shape_from_vec(shape_vec(layout.shape()))
8712 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
8713 let strides = R::strides_from_vec(stride_vec(layout.strides()))
8714 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
8715 TypedTensorViewMut::from_buffer_ref_mut(
8716 shape,
8717 strides,
8718 offset,
8719 TensorStorageRefMut::Host(data),
8720 placement,
8721 "TypedTensorViewMut::try_multi_slice_mut",
8722 )
8723}
8724
8725fn contiguous_layout_slice<'a, T, R: TensorRank>(
8726 layout: &TensorLayout<R>,
8727 data: &'a [T],
8728 op: &'static str,
8729) -> crate::Result<&'a [T]> {
8730 if !layout
8731 .is_compact_col_major()
8732 .map_err(|err| tensor_layout_error(op, err))?
8733 {
8734 return Err(crate::Error::validation(
8735 op,
8736 ValidationError::NonContiguousViewAsSlice,
8737 ));
8738 }
8739 let len = checked_view_element_count(layout.shape(), op)?;
8740 let start = usize::try_from(layout.offset())
8741 .map_err(|_| crate::Error::invalid_argument(op, "layout", "view offset is negative"))?;
8742 let end = start
8743 .checked_add(len)
8744 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8745 data.get(start..end)
8746 .ok_or_else(|| crate::Error::validation(op, ValidationError::ViewOutOfBounds))
8747}
8748
8749fn relaxed_col_major_contiguous(
8750 shape: &[usize],
8751 strides: &[isize],
8752 op: &'static str,
8753) -> crate::Result<bool> {
8754 if shape.contains(&0) {
8755 return Ok(true);
8756 }
8757
8758 let mut expected = 1isize;
8759 for (&extent, &stride) in shape.iter().zip(strides) {
8760 if extent <= 1 {
8761 continue;
8762 }
8763 if stride != expected {
8764 return Ok(false);
8765 }
8766 let extent = isize::try_from(extent)
8767 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8768 expected = expected
8769 .checked_mul(extent)
8770 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8771 }
8772 Ok(true)
8773}
8774
8775fn reshape_layout_dyn<R: TensorRank>(
8776 layout: &TensorLayout<R>,
8777 shape: &[usize],
8778 buffer_len: usize,
8779 op: &'static str,
8780) -> crate::Result<TensorLayout<DynRank>> {
8781 match layout.reshape_view_as::<DynRank>(shape_vec(shape), buffer_len) {
8782 Ok(layout) => Ok(layout),
8783 Err(err) => {
8784 if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
8785 return Err(tensor_layout_error(op, err));
8786 }
8787 let from = checked_view_element_count(layout.shape(), op)?;
8788 let to = checked_view_element_count(shape, op)?;
8789 if from != to {
8790 return Err(tensor_layout_error(
8791 op,
8792 tenferro_tensor_core::ShapeMismatch::ReshapeElementCount { from, to }.into(),
8793 ));
8794 }
8795 TensorLayout::<DynRank>::compact(shape_vec(shape))
8796 .and_then(|compact| {
8797 TensorLayout::from_parts(
8798 shape_vec(compact.shape()),
8799 stride_vec(compact.strides()),
8800 layout.offset(),
8801 buffer_len,
8802 )
8803 })
8804 .map_err(|err| tensor_layout_error(op, err))
8805 }
8806 }
8807}
8808
8809fn core_slice_specs(
8810 slices: &[StridedSliceSpec],
8811 shape: &[usize],
8812 op: &'static str,
8813) -> crate::Result<Vec<CoreSliceSpec>> {
8814 if slices.len() != shape.len() {
8815 return Err(crate::Error::validation(
8816 op,
8817 ValidationError::RankMismatch {
8818 expected: shape.len(),
8819 actual: slices.len(),
8820 },
8821 ));
8822 }
8823
8824 let mut specs = Vec::with_capacity(slices.len());
8825 for (slice, &axis_len) in slices.iter().zip(shape) {
8826 specs.push(core_slice_spec(*slice, axis_len, op)?);
8827 }
8828 Ok(specs)
8829}
8830
8831fn core_slice_spec(
8832 slice: StridedSliceSpec,
8833 axis_len: usize,
8834 op: &'static str,
8835) -> crate::Result<CoreSliceSpec> {
8836 if slice.step() == 0 {
8837 return Err(crate::Error::validation(
8838 op,
8839 ValidationError::InvalidSliceStep { step: slice.step() },
8840 ));
8841 }
8842
8843 let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
8844 let end = match slice.end() {
8845 Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
8846 None => isize::try_from(axis_len)
8847 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
8848 };
8849
8850 if slice.step() > 0 {
8851 return Ok(CoreSliceSpec {
8852 start,
8853 end,
8854 step: slice.step(),
8855 });
8856 }
8857
8858 if start >= end {
8859 return Ok(CoreSliceSpec {
8860 start,
8861 end: start,
8862 step: slice.step(),
8863 });
8864 }
8865
8866 Ok(CoreSliceSpec {
8867 start: end
8868 .checked_sub(1)
8869 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
8870 end: start
8871 .checked_sub(1)
8872 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
8873 step: slice.step(),
8874 })
8875}
8876
8877fn normalize_strided_bound(
8878 bound: isize,
8879 axis_len: usize,
8880 op: &'static str,
8881 role: &'static str,
8882) -> crate::Result<isize> {
8883 let original_axis_len = axis_len;
8884 let axis_len = isize::try_from(axis_len)
8885 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
8886 let bound = if bound < 0 {
8887 axis_len
8888 .checked_add(bound)
8889 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?
8890 } else {
8891 bound
8892 };
8893 if !(0..=axis_len).contains(&bound) {
8894 let (start, end) = if role == "slice start" {
8895 (bound, bound)
8896 } else {
8897 (0, bound)
8898 };
8899 return Err(crate::Error::validation(
8900 op,
8901 ValidationError::InvalidSliceBounds {
8902 start,
8903 end,
8904 axis_len: original_axis_len,
8905 },
8906 ));
8907 }
8908 Ok(bound)
8909}
8910
8911fn slice_axis_specs(
8912 rank: usize,
8913 axis: usize,
8914 slice: StridedSliceSpec,
8915 op: &'static str,
8916) -> crate::Result<Vec<StridedSliceSpec>> {
8917 if axis >= rank {
8918 return Err(crate::Error::validation(
8919 op,
8920 ValidationError::AxisOutOfBounds { axis, rank },
8921 ));
8922 }
8923
8924 let mut slices = vec![StridedSliceSpec::all(); rank];
8925 slices[axis] = slice;
8926 Ok(slices)
8927}
8928
8929pub(crate) fn default_placement() -> Placement {
8930 Placement {
8931 memory_kind: MemoryKind::UnpinnedHost,
8932 device: None,
8933 cpu_affinity: None,
8934 }
8935}
8936
8937fn typed_tensor_from_vec_col_major<T, R: TensorRank>(
8938 shape: impl tenferro_tensor_core::IntoRankShape<R>,
8939 data: Vec<T>,
8940 op: &'static str,
8941) -> crate::Result<TypedTensor<T, R>> {
8942 try_typed_tensor_from_vec_col_major(shape, data, op)
8943}
8944
8945fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
8946 shape: impl tenferro_tensor_core::IntoRankShape<R>,
8947 data: Vec<T>,
8948 op: &'static str,
8949) -> crate::Result<TypedTensor<T, R>> {
8950 let shape = shape
8951 .into_rank_shape()
8952 .map_err(|err| tensor_layout_error(op, err))?;
8953 tenferro_tensor_core::col_major_strides(shape.as_ref())
8954 .map_err(|err| tensor_layout_error(op, err))?;
8955 try_checked_shape_len(shape.as_ref(), data.len(), op)?;
8956 Ok(TypedTensor {
8957 shape,
8958 placement: default_placement(),
8959 storage: DynamicStorage::Host(HostStorage {
8960 data: HostData::new(data),
8961 }),
8962 })
8963}
8964
8965fn typed_tensor_zeros<T: TensorScalar + Zero, R: TensorRank>(
8966 shape: impl tenferro_tensor_core::IntoRankShape<R>,
8967) -> crate::Result<TypedTensor<T, R>> {
8968 try_typed_tensor_zeros(shape)
8969}
8970
8971fn try_typed_tensor_zeros<T: TensorScalar + Clone + Zero, R: TensorRank>(
8972 shape: impl tenferro_tensor_core::IntoRankShape<R>,
8973) -> crate::Result<TypedTensor<T, R>> {
8974 let layout = try_compact_layout(shape, "zeros")?;
8975 let n = try_shape_product(layout.shape(), "zeros")?;
8976 typed_tensor_from_vec_col_major(
8977 R::shape_from_vec(shape_vec(layout.shape()))
8978 .map_err(|err| tensor_layout_error("zeros", err))?,
8979 vec![T::zero(); n],
8980 "zeros",
8981 )
8982}
8983
8984fn typed_tensor_ones<T: TensorScalar + One + Zero, R: TensorRank>(
8985 shape: impl tenferro_tensor_core::IntoRankShape<R>,
8986) -> crate::Result<TypedTensor<T, R>> {
8987 try_typed_tensor_ones(shape)
8988}
8989
8990fn try_typed_tensor_ones<T: TensorScalar + Clone + One + Zero, R: TensorRank>(
8991 shape: impl tenferro_tensor_core::IntoRankShape<R>,
8992) -> crate::Result<TypedTensor<T, R>> {
8993 let layout = try_compact_layout(shape, "ones")?;
8994 let n = try_shape_product(layout.shape(), "ones")?;
8995 typed_tensor_from_vec_col_major(
8996 R::shape_from_vec(shape_vec(layout.shape()))
8997 .map_err(|err| tensor_layout_error("ones", err))?,
8998 vec![T::one(); n],
8999 "ones",
9000 )
9001}
9002
9003fn typed_tensor_from_buffer_col_major<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
9004 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9005 buffer: StorageBuffer<T>,
9006 placement: Placement,
9007) -> crate::Result<TypedTensor<T, R>> {
9008 try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
9009}
9010
9011#[doc(hidden)]
9012fn typed_tensor_from_backend_allocation<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
9013 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9014 allocation: Box<dyn crate::BackendAllocation>,
9015 placement: Placement,
9016) -> crate::Result<TypedTensor<T, R>> {
9017 let layout = try_compact_layout(shape, "from_backend_allocation")?;
9018 let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
9019 .map_err(|err| tensor_layout_error("from_backend_allocation", err))?;
9020 let shape = group_shape.clone();
9021 let (mut group, slot) =
9022 AllocationGroup::from_backend_allocation::<T, R>(group_shape, allocation)
9023 .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9024 group
9025 .set_descriptor_placement(slot, placement.clone())
9026 .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9027 let allocation_index = group
9028 .allocation_index(slot)
9029 .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
9030 let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
9031 Ok(TypedTensor {
9032 shape,
9033 placement,
9034 storage: DynamicStorage::Group(GroupStorage {
9035 group: Box::new(OwnedTensorGroup {
9036 group,
9037 slot,
9038 allocation_index,
9039 host_ptr,
9040 host_byte_len,
9041 _rank: PhantomData,
9042 }),
9043 }),
9044 })
9045}
9046
9047fn try_typed_tensor_from_buffer_col_major<
9048 T: TensorScalar + Send + Sync + 'static,
9049 R: TensorRank,
9050>(
9051 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9052 buffer: StorageBuffer<T>,
9053 placement: Placement,
9054) -> crate::Result<TypedTensor<T, R>> {
9055 let layout = try_compact_layout(shape, "from_buffer_col_major")?;
9056 let len = buffer.len();
9057 try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
9058 let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
9059 .map_err(|err| tensor_layout_error("from_buffer_col_major", err))?;
9060 match buffer {
9061 StorageBuffer::Host(data) => Ok(TypedTensor {
9062 shape: group_shape,
9063 placement,
9064 storage: DynamicStorage::Host(HostStorage {
9065 data: HostData::new(data),
9066 }),
9067 }),
9068 StorageBuffer::Backend(buffer) => {
9069 let group = OwnedTensorGroup::from_backend_buffer(
9070 group_shape.clone(),
9071 StorageBuffer::Backend(buffer),
9072 placement.clone(),
9073 )?;
9074 Ok(TypedTensor {
9075 shape: group_shape,
9076 placement,
9077 storage: DynamicStorage::Group(GroupStorage {
9078 group: Box::new(group),
9079 }),
9080 })
9081 }
9082 }
9083}
9084
9085impl<T: TensorScalar + Zero, R: TensorRank> TypedTensor<T, R> {
9086 /// Allocate a zero-filled tensor.
9087 ///
9088 /// # Examples
9089 ///
9090 /// ```rust
9091 /// use tenferro_tensor::TypedTensor;
9092 ///
9093 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
9094 /// assert_eq!(t.n_elements(), 6);
9095 /// ```
9096 /// # Errors
9097 ///
9098 /// Returns [`crate::Error::Validation`] with
9099 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
9100 /// product or compact-stride arithmetic overflows.
9101 /// A vector or slice with a different static rank returns
9102 /// [`tenferro_tensor_core::ValidationError::RankMismatch`].
9103 pub fn zeros(shape: impl tenferro_tensor_core::IntoRankShape<R>) -> crate::Result<Self> {
9104 typed_tensor_zeros(shape)
9105 }
9106}
9107
9108impl<T: TensorScalar + One + Zero, R: TensorRank> TypedTensor<T, R> {
9109 /// Allocate a one-filled tensor.
9110 ///
9111 /// # Examples
9112 ///
9113 /// ```rust
9114 /// use tenferro_tensor::TypedTensor;
9115 ///
9116 /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
9117 /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
9118 /// ```
9119 /// # Errors
9120 ///
9121 /// Returns [`crate::Error::Validation`] with
9122 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
9123 /// product or compact-stride arithmetic overflows.
9124 /// A vector or slice with a different static rank returns
9125 /// [`tenferro_tensor_core::ValidationError::RankMismatch`].
9126 pub fn ones(shape: impl tenferro_tensor_core::IntoRankShape<R>) -> crate::Result<Self> {
9127 typed_tensor_ones(shape)
9128 }
9129}
9130
9131impl<T, R: TensorRank> TypedTensor<T, R> {
9132 /// Adopt a column-major host `Vec<T>` with no scalar, copy or thread-safety bound.
9133 ///
9134 /// # Examples
9135 /// ```
9136 /// use tenferro_tensor::{Rank, TypedTensor};
9137 /// struct Custom(String);
9138 /// let tensor = TypedTensor::<Custom, Rank<2>>::from_vec_col_major(
9139 /// [1, 2], vec![Custom("a".into()), Custom("b".into())],
9140 /// )?;
9141 /// assert_eq!(tensor.get(&[0, 1])?.0.as_str(), "b");
9142 /// # Ok::<(), tenferro_tensor::Error>(())
9143 /// ```
9144 /// # Errors
9145 /// Returns [`crate::Error::Validation`] for a rank mismatch, shape/data
9146 /// length mismatch or shape/stride arithmetic overflow.
9147 pub fn from_vec_col_major(
9148 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9149 data: Vec<T>,
9150 ) -> crate::Result<Self> {
9151 typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
9152 }
9153
9154 /// Explicitly import row-major host values into column-major storage.
9155 ///
9156 /// Clones each input element once; no backend or scalar registration is used.
9157 ///
9158 /// # Examples
9159 /// ```
9160 /// use tenferro_tensor::{Rank, TypedTensor};
9161 /// let tensor = TypedTensor::<i32, Rank<2>>::from_vec_row_major(
9162 /// [2, 3], vec![1, 2, 3, 4, 5, 6],
9163 /// )?;
9164 /// assert_eq!(tensor.get(&[1, 0])?, &4);
9165 /// assert_eq!(tensor.get(&[0, 2])?, &3);
9166 /// # Ok::<(), tenferro_tensor::Error>(())
9167 /// ```
9168 /// # Errors
9169 /// Returns [`crate::Error::Validation`] for a rank, shape-length or stride overflow,
9170 /// or when the shape product disagrees with the input length.
9171 pub fn from_vec_row_major(
9172 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9173 data: Vec<T>,
9174 ) -> crate::Result<Self>
9175 where
9176 T: Clone,
9177 {
9178 let op = "from_vec_row_major";
9179 let (shape, reordered) = row_major_reorder(shape, data, op)?;
9180 typed_tensor_from_vec_col_major(shape, reordered, op)
9181 }
9182
9183 /// Consume this compact tensor and return the original host `Vec<T>`.
9184 ///
9185 /// # Examples
9186 ///
9187 /// ```
9188 /// use tenferro_tensor::TypedTensor;
9189 /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2], vec![1, 2])?;
9190 /// let data = tensor.into_host_vec().map_err(|failure| failure.into_parts().1)?;
9191 /// assert_eq!(data, vec![1, 2]);
9192 /// # Ok::<(), tenferro_tensor::Error>(())
9193 /// ```
9194 ///
9195 /// # Errors
9196 /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9197 /// storage is device-only or the managed root cannot export a host vector.
9198 pub fn into_host_vec(self) -> std::result::Result<Vec<T>, ReinterpretError<Self>>
9199 where
9200 T: 'static,
9201 {
9202 let TypedTensor {
9203 shape,
9204 placement,
9205 storage,
9206 } = self;
9207 match storage {
9208 DynamicStorage::Host(HostStorage { data }) => Ok(data.into_vec()),
9209 DynamicStorage::Group(GroupStorage { group }) => match group.into_host_vec::<T>() {
9210 Ok(data) => Ok(data),
9211 Err((group, error)) => Err(ReinterpretError::new(
9212 TypedTensor {
9213 shape,
9214 placement,
9215 storage: DynamicStorage::Group(GroupStorage {
9216 group: Box::new(group),
9217 }),
9218 },
9219 error,
9220 )),
9221 },
9222 }
9223 }
9224
9225 /// Consume the original host vector along with its column-major shape.
9226 ///
9227 /// # Examples
9228 ///
9229 /// ```
9230 /// use tenferro_tensor::TypedTensor;
9231 /// let tensor = TypedTensor::<i32>::from_vec_col_major(vec![2, 1], vec![1, 2])?;
9232 /// let (shape, data) = tensor.into_vec_col_major().map_err(|failure| failure.into_parts().1)?;
9233 /// assert_eq!(shape, vec![2, 1]);
9234 /// assert_eq!(data, vec![1, 2]);
9235 /// # Ok::<(), tenferro_tensor::Error>(())
9236 /// ```
9237 ///
9238 /// # Errors
9239 /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9240 /// storage is device-only or the managed root cannot export a host vector.
9241 pub fn into_vec_col_major(
9242 self,
9243 ) -> std::result::Result<(Vec<usize>, Vec<T>), ReinterpretError<Self>>
9244 where
9245 T: 'static,
9246 {
9247 let shape = self.shape().to_vec();
9248 match self.into_host_vec() {
9249 Ok(data) => Ok((shape, data)),
9250 Err(failure) => Err(failure),
9251 }
9252 }
9253
9254 /// Borrow the plain host values (or an existing managed host root).
9255 ///
9256 /// # Examples
9257 ///
9258 /// ```
9259 /// use tenferro_tensor::TypedTensor;
9260 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
9261 /// assert_eq!(tensor.host_data()?, &[1.0, 2.0]);
9262 /// # Ok::<(), tenferro_tensor::Error>(())
9263 /// ```
9264 ///
9265 /// # Errors
9266 /// Returns [`crate::Error::RuntimeState`] for device-only storage.
9267 pub fn host_data(&self) -> crate::Result<&[T]> {
9268 match &self.storage {
9269 DynamicStorage::Host(HostStorage { data }) => Ok(data.as_slice()),
9270 DynamicStorage::Group(core) => core.group.host_slice::<T>(),
9271 }
9272 }
9273
9274 /// Borrow compact host storage as a flat column-major slice without copying.
9275 ///
9276 /// # Examples
9277 /// ```
9278 /// use tenferro_tensor::TypedTensor;
9279 /// let tensor = TypedTensor::<String>::from_vec_col_major([2], vec!["a".into(), "b".into()])?;
9280 /// assert_eq!(tensor.as_slice()?, &["a", "b"]);
9281 /// # Ok::<(), tenferro_tensor::Error>(())
9282 /// ```
9283 /// # Errors
9284 /// Device-only storage returns [`crate::Error::RuntimeState`].
9285 pub fn as_slice(&self) -> crate::Result<&[T]> {
9286 self.host_data()
9287 }
9288
9289 /// Mutably borrow the plain host values (or an existing managed host root).
9290 ///
9291 /// # Examples
9292 ///
9293 /// ```
9294 /// use tenferro_tensor::TypedTensor;
9295 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
9296 /// tensor.host_data_mut()?[0] = 5.0;
9297 /// assert_eq!(tensor.host_data()?, &[5.0, 2.0]);
9298 /// # Ok::<(), tenferro_tensor::Error>(())
9299 /// ```
9300 ///
9301 /// # Errors
9302 /// Returns [`crate::Error::RuntimeState`] for device-only storage.
9303 pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
9304 match &mut self.storage {
9305 DynamicStorage::Host(HostStorage { data }) => Ok(data.as_mut_slice()),
9306 DynamicStorage::Group(core) => core.group.host_slice_mut::<T>(),
9307 }
9308 }
9309
9310 /// Borrow an element by checked column-major multi-index.
9311 ///
9312 /// # Examples
9313 /// ```
9314 /// use tenferro_tensor::TypedTensor;
9315 /// let tensor = TypedTensor::<String>::from_vec_col_major([2], vec!["a".into(), "b".into()])?;
9316 /// assert_eq!(tensor.get(&[1])?, "b");
9317 /// # Ok::<(), tenferro_tensor::Error>(())
9318 /// ```
9319 /// # Errors
9320 /// Invalid rank, coordinates or offset return [`crate::Error::Validation`];
9321 /// device-only storage returns [`crate::Error::RuntimeState`].
9322 pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
9323 let offset = self.linear_offset(indices)?;
9324 self.host_data()?.get(offset).ok_or_else(|| {
9325 crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
9326 })
9327 }
9328
9329 /// Exclusively borrow an element by checked column-major multi-index.
9330 ///
9331 /// # Examples
9332 /// ```
9333 /// use tenferro_tensor::TypedTensor;
9334 /// let mut tensor = TypedTensor::<String>::from_vec_col_major([1], vec!["a".into()])?;
9335 /// *tensor.get_mut(&[0])? = "b".into();
9336 /// assert_eq!(tensor.get(&[0])?, "b");
9337 /// # Ok::<(), tenferro_tensor::Error>(())
9338 /// ```
9339 /// # Errors
9340 /// Invalid rank, coordinates or offset return [`crate::Error::Validation`];
9341 /// device-only storage returns [`crate::Error::RuntimeState`].
9342 pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
9343 let offset = self.linear_offset(indices)?;
9344 self.host_data_mut()?.get_mut(offset).ok_or_else(|| {
9345 crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
9346 })
9347 }
9348
9349 /// Make an explicit independent host copy with the same shape and placement.
9350 ///
9351 /// The dynamic `TypedTensor` does not implement [`Clone`] (only the
9352 /// host-only representation does): the copy reads host data, rejects
9353 /// device-only storage, and creates a new owner, so it is fallible. See
9354 /// [`Tensor::duplicate`] for the sharing alternatives.
9355 ///
9356 /// # Examples
9357 /// ```
9358 /// use tenferro_tensor::TypedTensor;
9359 /// let tensor = TypedTensor::<String>::from_vec_col_major([1], vec!["a".into()])?;
9360 /// assert_eq!(tensor.duplicate()?.get(&[0])?, "a");
9361 /// # Ok::<(), tenferro_tensor::Error>(())
9362 /// ```
9363 /// # Errors
9364 /// Device-only storage returns [`crate::Error::RuntimeState`]; invalid
9365 /// host metadata returns [`crate::Error::Validation`].
9366 pub fn duplicate(&self) -> crate::Result<Self>
9367 where
9368 T: Clone,
9369 {
9370 let mut copy = Self::from_vec_col_major(
9371 R::shape_from_vec(shape_vec(self.shape()))
9372 .map_err(|err| tensor_layout_error("TypedTensor::duplicate", err))?,
9373 self.host_data()?.to_vec(),
9374 )?;
9375 copy.set_placement(self.placement().clone());
9376 Ok(copy)
9377 }
9378
9379 /// Create a tensor from an existing buffer and compact column-major layout.
9380 ///
9381 /// This preserves the owned tensor invariant that layout metadata is
9382 /// compact column-major, including for backend-owned buffers.
9383 ///
9384 /// # Examples
9385 ///
9386 /// ```
9387 /// use tenferro_tensor::{StorageBuffer, Placement, TypedTensor};
9388 ///
9389 /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
9390 /// vec![2],
9391 /// StorageBuffer::Host(vec![1.0, 2.0]),
9392 /// Placement {
9393 /// memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
9394 /// device: None,
9395 /// cpu_affinity: None,
9396 /// },
9397 /// )
9398 /// .unwrap();
9399 /// assert_eq!(tensor.shape(), &[2]);
9400 /// ```
9401 /// # Errors
9402 ///
9403 /// Returns [`crate::Error::Validation`] with
9404 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
9405 /// the shape product differs from the buffer length,
9406 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape or
9407 /// stride arithmetic overflows, or
9408 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a supplied
9409 /// rank-specific shape cannot be represented.
9410 pub fn from_buffer_col_major(
9411 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9412 buffer: StorageBuffer<T>,
9413 placement: Placement,
9414 ) -> crate::Result<Self>
9415 where
9416 T: TensorScalar + Send + Sync + 'static,
9417 {
9418 typed_tensor_from_buffer_col_major(shape, buffer, placement)
9419 }
9420
9421 /// Consume a scalar-independent provider root into one compact tensor.
9422 #[doc(hidden)]
9423 pub fn from_backend_allocation(
9424 shape: impl tenferro_tensor_core::IntoRankShape<R>,
9425 allocation: Box<dyn crate::BackendAllocation>,
9426 placement: Placement,
9427 ) -> crate::Result<Self>
9428 where
9429 T: TensorScalar + Send + Sync + 'static,
9430 {
9431 typed_tensor_from_backend_allocation(shape, allocation, placement)
9432 }
9433
9434 /// Convert this tensor into static rank metadata after validating its rank.
9435 ///
9436 /// The buffer and placement are preserved. This method changes only the
9437 /// compile-time rank marker on the owned compact column-major tensor.
9438 ///
9439 /// # Examples
9440 ///
9441 /// ```
9442 /// use tenferro_tensor::{Rank, TypedTensor};
9443 ///
9444 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
9445 /// let Ok(ranked) = tensor.try_into_rank::<2>() else {
9446 /// panic!("a rank-2 tensor converts to two axes")
9447 /// };
9448 /// assert_eq!(ranked.shape(), &[2, 3]);
9449 /// # Ok::<(), tenferro_tensor::Error>(())
9450 /// ```
9451 /// # Errors
9452 ///
9453 /// Returns [`ReinterpretError`] carrying the unchanged tensor when the
9454 /// typed rank does not match the existing shape, with
9455 /// [`crate::Error::Validation`] and
9456 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] as the cause,
9457 /// or when the compact rank layout overflows.
9458 pub fn try_into_rank<const N: usize>(
9459 self,
9460 ) -> std::result::Result<TypedTensor<T, Rank<N>>, ReinterpretError<Self>> {
9461 let op = "TypedTensor::try_into_rank";
9462 let actual = self.shape().len();
9463 let shape: [usize; N] = match self.shape().try_into() {
9464 Ok(shape) => shape,
9465 Err(_) => {
9466 return Err(ReinterpretError::new(
9467 self,
9468 tensor_layout_error(
9469 op,
9470 ValidationError::RankMismatch {
9471 expected: N,
9472 actual,
9473 },
9474 ),
9475 ))
9476 }
9477 };
9478 if let Err(error) =
9479 TensorLayout::<Rank<N>>::compact(shape).map_err(|err| tensor_layout_error(op, err))
9480 {
9481 return Err(ReinterpretError::new(self, error));
9482 }
9483 let TypedTensor {
9484 placement, storage, ..
9485 } = self;
9486 let storage = match storage {
9487 DynamicStorage::Host(host) => DynamicStorage::Host(host),
9488 DynamicStorage::Group(GroupStorage { group }) => DynamicStorage::Group(GroupStorage {
9489 group: Box::new(OwnedTensorGroup {
9490 group: group.group,
9491 slot: group.slot,
9492 allocation_index: group.allocation_index,
9493 host_ptr: group.host_ptr,
9494 host_byte_len: group.host_byte_len,
9495 _rank: PhantomData,
9496 }),
9497 }),
9498 };
9499 Ok(TypedTensor {
9500 shape,
9501 placement,
9502 storage,
9503 })
9504 }
9505
9506 /// Return the storage backing this tensor.
9507 ///
9508 /// This is an explicit storage-inspection API for backend glue and tests.
9509 /// Host value inspection should prefer [`TypedTensor::host_data`] when the
9510 /// caller requires host storage.
9511 ///
9512 /// # Panics
9513 ///
9514 /// Panics only if the typed descriptor and its single group owner are
9515 /// internally inconsistent.
9516 ///
9517 /// # Examples
9518 ///
9519 /// ```
9520 /// use tenferro_tensor::{StorageBuffer, TypedTensor};
9521 ///
9522 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
9523 /// assert!(matches!(t.buffer(), StorageBuffer::Host(_)));
9524 /// ```
9525 pub fn buffer(&self) -> &StorageBuffer<T>
9526 where
9527 T: 'static,
9528 {
9529 match &self.storage {
9530 DynamicStorage::Host(HostStorage { data }) => &data.buffer,
9531 DynamicStorage::Group(core) => core
9532 .group
9533 .host_buffer::<T>()
9534 .or_else(|| core.group.backend_buffer::<T>())
9535 .unwrap_or_else(|| unreachable!("typed tensor group storage mismatch")),
9536 }
9537 }
9538
9539 /// Return the provider family for this tensor when backend-owned.
9540 #[doc(hidden)]
9541 pub fn backend_family(&self) -> Option<&'static str>
9542 where
9543 T: TensorScalar + 'static,
9544 {
9545 self.as_view().backend_family()
9546 }
9547
9548 /// Return the opaque backend buffer for backend-owned tensors.
9549 #[doc(hidden)]
9550 pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>>
9551 where
9552 T: 'static,
9553 {
9554 match &self.storage {
9555 DynamicStorage::Host(..) => None,
9556 DynamicStorage::Group(core) => match core.group.backend_buffer::<T>() {
9557 Some(StorageBuffer::Backend(buffer)) => Some(buffer.as_ref()),
9558 Some(StorageBuffer::Host(_)) | None => None,
9559 },
9560 }
9561 }
9562
9563 /// Return the mutable backend buffer for an exclusive owner borrow.
9564 #[doc(hidden)]
9565 pub fn backend_buffer_mut(&mut self) -> Option<&mut dyn BackendStorage<T>>
9566 where
9567 T: 'static,
9568 {
9569 let DynamicStorage::Group(core) = &mut self.storage else {
9570 return None;
9571 };
9572 match core.group.backend_buffer_mut::<T>()? {
9573 StorageBuffer::Host(_) => None,
9574 StorageBuffer::Backend(buffer) => Some(buffer.as_mut()),
9575 }
9576 }
9577
9578 /// Prepare this backend tensor for one provider-native read binding.
9579 #[doc(hidden)]
9580 pub fn prepare_device_read(
9581 &self,
9582 op: &'static str,
9583 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
9584 where
9585 T: TensorScalar + 'static,
9586 {
9587 let DynamicStorage::Group(core) = &self.storage else {
9588 return Err(crate::Error::runtime_state_source(
9589 op,
9590 crate::AccessError::Unsupported { backend: "host" },
9591 ));
9592 };
9593 let layout = self.layout();
9594 core.group
9595 .prepare_device_read_for_layout::<T>(&layout)
9596 .map_err(|error| crate::Error::runtime_state_source(op, error))
9597 }
9598
9599 /// Prepare this backend tensor for one provider-native write binding.
9600 #[doc(hidden)]
9601 pub fn prepare_device_write(
9602 &mut self,
9603 op: &'static str,
9604 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
9605 where
9606 T: TensorScalar + 'static,
9607 {
9608 let layout = self.layout();
9609 let DynamicStorage::Group(core) = &mut self.storage else {
9610 return Err(crate::Error::runtime_state_source(
9611 op,
9612 crate::AccessError::Unsupported { backend: "host" },
9613 ));
9614 };
9615 core.group
9616 .prepare_device_write_for_layout::<T>(&layout)
9617 .map_err(|error| crate::Error::runtime_state_source(op, error))
9618 }
9619
9620 pub(crate) fn buffer_len(&self) -> usize
9621 where
9622 T: 'static,
9623 {
9624 match &self.storage {
9625 DynamicStorage::Host(HostStorage { data }) => data.as_slice().len(),
9626 DynamicStorage::Group(core) => core
9627 .group
9628 .group
9629 .descriptor_len(core.group.slot)
9630 .unwrap_or_else(|| unreachable!("typed tensor group descriptor mismatch")),
9631 }
9632 }
9633
9634 /// Return the shared-allocation domain carried by the backend buffer.
9635 ///
9636 /// # Examples
9637 ///
9638 /// ```rust
9639 /// use tenferro_tensor::TypedTensor;
9640 ///
9641 /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
9642 /// assert_eq!(tensor.allocation_domain(), None);
9643 /// # Ok::<(), tenferro_tensor::Error>(())
9644 /// ```
9645 pub fn allocation_domain(&self) -> Option<AllocationDomainId>
9646 where
9647 T: 'static,
9648 {
9649 match &self.storage {
9650 DynamicStorage::Host(..) => None,
9651 DynamicStorage::Group(core) => core
9652 .group
9653 .group
9654 .backend_identity(core.group.slot)
9655 .map(|(domain, _)| domain),
9656 }
9657 }
9658
9659 /// Return the stable physical backend allocation identity.
9660 ///
9661 /// # Examples
9662 ///
9663 /// ```rust
9664 /// use tenferro_tensor::TypedTensor;
9665 ///
9666 /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
9667 /// assert_eq!(tensor.allocation_id(), None);
9668 /// # Ok::<(), tenferro_tensor::Error>(())
9669 /// ```
9670 pub fn allocation_id(&self) -> Option<AllocationId>
9671 where
9672 T: 'static,
9673 {
9674 match &self.storage {
9675 DynamicStorage::Host(..) => None,
9676 DynamicStorage::Group(core) => core
9677 .group
9678 .group
9679 .backend_identity(core.group.slot)
9680 .map(|(_, allocation)| allocation),
9681 }
9682 }
9683
9684 /// Borrow this tensor as a typed view preserving rank and layout metadata.
9685 ///
9686 /// # Panics
9687 ///
9688 /// Panics only if the typed descriptor and its single group owner are
9689 /// internally inconsistent.
9690 ///
9691 /// # Examples
9692 ///
9693 /// ```rust
9694 /// use tenferro_tensor::{Rank, TypedTensor};
9695 ///
9696 /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
9697 /// let view = tensor.as_view();
9698 /// assert_eq!(view.strides(), &[1, 2]);
9699 /// ```
9700 pub fn as_view(&self) -> TypedTensorView<'_, T, R>
9701 where
9702 T: 'static,
9703 {
9704 let layout = self.layout();
9705 let placement = self.placement.clone();
9706 match &self.storage {
9707 DynamicStorage::Host(HostStorage { data }) => TypedTensorView {
9708 buffer: TensorStorageRef::Host(data.as_slice()),
9709 root: None,
9710 layout,
9711 placement,
9712 _representation: std::marker::PhantomData,
9713 },
9714 DynamicStorage::Group(core) => {
9715 // Managed owners are constructed only with their matching preset T.
9716 let root = core
9717 .group
9718 .group
9719 .view_raw::<T, R>(core.group.slot)
9720 .unwrap_or_else(|error| {
9721 unreachable!("typed tensor group descriptor mismatch: {error}")
9722 });
9723 let buffer = if let Some(allocation) = root.backend_allocation() {
9724 TensorStorageRef::Root(allocation)
9725 } else {
9726 TensorStorageRef::Host(core.group.host_slice::<T>().unwrap_or_default())
9727 };
9728 TypedTensorView {
9729 buffer,
9730 root: Some(root),
9731 layout,
9732 placement,
9733 _representation: std::marker::PhantomData,
9734 }
9735 }
9736 }
9737 }
9738
9739 /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
9740 ///
9741 /// # Panics
9742 ///
9743 /// Panics only if the typed descriptor and its single group owner are
9744 /// internally inconsistent.
9745 ///
9746 /// # Examples
9747 ///
9748 /// ```rust
9749 /// use tenferro_tensor::TypedTensor;
9750 ///
9751 /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
9752 /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
9753 /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
9754 /// ```
9755 pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
9756 where
9757 T: TensorScalar + 'static,
9758 {
9759 let layout = self.layout();
9760 let placement = self.placement.clone();
9761 match &mut self.storage {
9762 DynamicStorage::Host(HostStorage { data }) => TypedTensorViewMut {
9763 buffer: TensorStorageRefMut::Host(data.as_mut_slice()),
9764 root: None,
9765 layout,
9766 placement,
9767 _representation: std::marker::PhantomData,
9768 },
9769 DynamicStorage::Group(core) => {
9770 let mut root = core.group.view_mut::<T>().unwrap_or_else(|error| {
9771 unreachable!("typed tensor group descriptor mismatch: {error}")
9772 });
9773 let buffer = if let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut()
9774 {
9775 TensorStorageRefMut::Backend(buffer.as_mut())
9776 } else {
9777 TensorStorageRefMut::Host(root.host_slice_mut().unwrap_or_else(|error| {
9778 unreachable!("typed tensor group descriptor is not host-backed: {error}")
9779 }))
9780 };
9781 TypedTensorViewMut {
9782 buffer,
9783 root: Some(root),
9784 layout,
9785 placement,
9786 _representation: std::marker::PhantomData,
9787 }
9788 }
9789 }
9790 }
9791
9792 /// Borrow a read-only strided region view over this tensor's backend
9793 /// (device) buffer from explicit layout metadata.
9794 ///
9795 /// This is a metadata-only view: no data is copied or transferred. The
9796 /// layout's reachable element span is validated against the backend
9797 /// buffer's physical length. Host-backed tensors are rejected with an
9798 /// explicit backend error; host regions are expressed with
9799 /// [`TypedTensorView::from_slice`] over host storage instead.
9800 ///
9801 /// # Examples
9802 ///
9803 /// ```rust
9804 /// use tenferro_tensor::TypedTensor;
9805 ///
9806 /// // Host tensors are rejected: this constructor is for backend buffers.
9807 /// let host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
9808 /// let err = host.backend_region_view(vec![2, 2], vec![1, 2], 0).unwrap_err();
9809 /// assert!(err.to_string().contains("backend"));
9810 /// ```
9811 /// # Errors
9812 ///
9813 /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
9814 /// backend region views require a backend buffer. It returns
9815 /// [`crate::Error::Validation`] with
9816 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
9817 /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
9818 /// when the region exceeds the backend buffer, or
9819 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
9820 /// arithmetic overflow.
9821 pub fn backend_region_view(
9822 &self,
9823 shape: Vec<usize>,
9824 strides: Vec<isize>,
9825 offset: isize,
9826 ) -> crate::Result<TypedTensorView<'_, T, DynRank>>
9827 where
9828 T: TensorScalar + 'static,
9829 {
9830 let op = "TypedTensor::backend_region_view";
9831 let DynamicStorage::Group(core) = &self.storage else {
9832 return Err(crate::Error::runtime_state(op, "expected a backend (device) allocation; host tensors use TypedTensorView::from_slice over host storage"));
9833 };
9834 let root = core.group.view_dyn::<T>()?;
9835 let Some(allocation) = root.backend_allocation() else {
9836 return Err(crate::Error::runtime_state(
9837 op,
9838 "expected a backend (device) allocation; host tensors use \
9839 TypedTensorView::from_slice over host storage",
9840 ));
9841 };
9842 let element_len = allocation
9843 .root_extent()
9844 .byte_len()
9845 .checked_div(size_of::<T>())
9846 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
9847 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, element_len)
9848 .map_err(|err| tensor_layout_error(op, err))?;
9849 Ok(TypedTensorView {
9850 buffer: TensorStorageRef::Root(allocation),
9851 root: Some(root),
9852 layout,
9853 placement: self.placement.clone(),
9854 _representation: std::marker::PhantomData,
9855 })
9856 }
9857
9858 /// Borrow a mutable strided region view over this tensor's backend
9859 /// (device) buffer from explicit layout metadata.
9860 ///
9861 /// This is the mutable counterpart of
9862 /// [`TypedTensor::backend_region_view`]. The layout's reachable element
9863 /// span is validated against the backend buffer's physical length, and
9864 /// layouts whose logical elements alias the same physical element are
9865 /// rejected. Host-backed tensors are rejected with an explicit backend
9866 /// error; mutable host regions must go through
9867 /// [`TypedTensorViewMut::try_multi_slice_mut`] or host constructors.
9868 ///
9869 /// The returned view borrows the tensor's backend owner exclusively for its
9870 /// lifetime. This keeps write authority tied to the owner; a second mutable
9871 /// region view must be created only after the first borrow ends.
9872 ///
9873 /// # Examples
9874 ///
9875 /// ```rust
9876 /// use tenferro_tensor::TypedTensor;
9877 ///
9878 /// // Host tensors are rejected: this constructor is for backend buffers.
9879 /// let mut host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
9880 /// let err = host.backend_region_view_mut(vec![2, 2], vec![1, 2], 0).unwrap_err();
9881 /// assert!(err.to_string().contains("backend"));
9882 /// ```
9883 /// # Errors
9884 ///
9885 /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
9886 /// mutable backend region views require a backend buffer. It returns
9887 /// [`crate::Error::Validation`] with
9888 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
9889 /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
9890 /// when the region exceeds the backend buffer,
9891 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
9892 /// logical elements alias, or
9893 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
9894 /// arithmetic overflow.
9895 pub fn backend_region_view_mut(
9896 &mut self,
9897 shape: Vec<usize>,
9898 strides: Vec<isize>,
9899 offset: isize,
9900 ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>>
9901 where
9902 T: TensorScalar + 'static,
9903 {
9904 let op = "TypedTensor::backend_region_view_mut";
9905 let placement = self.placement.clone();
9906 let DynamicStorage::Group(core) = &mut self.storage else {
9907 return Err(crate::Error::runtime_state(op, "expected a backend (device) buffer; mutable host regions use TypedTensorViewMut host constructors or try_multi_slice_mut"));
9908 };
9909 let mut root = core.group.view_mut_dyn::<T>()?;
9910 let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut() else {
9911 return Err(crate::Error::runtime_state(
9912 op,
9913 "expected a backend (device) buffer; mutable host regions use \
9914 TypedTensorViewMut host constructors or try_multi_slice_mut",
9915 ));
9916 };
9917 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
9918 .map_err(|err| tensor_layout_error(op, err))?;
9919 layout
9920 .validate_mutable_no_overlap()
9921 .map_err(|err| tensor_layout_error(op, err))?;
9922 Ok(TypedTensorViewMut {
9923 buffer: TensorStorageRefMut::Backend(buffer.as_mut()),
9924 root: Some(root),
9925 layout,
9926 placement,
9927 _representation: std::marker::PhantomData,
9928 })
9929 }
9930
9931 /// Consume this tensor and return its storage, layout, and placement.
9932 ///
9933 /// # Examples
9934 ///
9935 /// ```
9936 /// use tenferro_tensor::{StorageBuffer, TypedTensor};
9937 ///
9938 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0])?;
9939 /// let Ok((buffer, layout, placement)) = t.into_parts() else {
9940 /// panic!("a plain host owner extracts")
9941 /// };
9942 /// assert!(matches!(buffer, StorageBuffer::Host(_)));
9943 /// assert_eq!(layout.shape(), &[2]);
9944 /// assert!(placement.device.is_none());
9945 /// # Ok::<(), tenferro_tensor::Error>(())
9946 /// ```
9947 ///
9948 /// # Errors
9949 ///
9950 /// Returns [`ReinterpretError`] carrying the unchanged tensor when it uses
9951 /// backend storage; download it before extracting host storage.
9952 pub fn into_parts(
9953 self,
9954 ) -> std::result::Result<(StorageBuffer<T>, TensorLayout<R>, Placement), ReinterpretError<Self>>
9955 where
9956 T: TensorScalar,
9957 {
9958 let TypedTensor {
9959 shape,
9960 placement,
9961 storage,
9962 } = self;
9963 let layout = TensorLayout::compact(shape.clone())
9964 .unwrap_or_else(|err| unreachable!("validated owned shape: {err}"));
9965 match storage {
9966 DynamicStorage::Host(HostStorage { data }) => {
9967 Ok((StorageBuffer::Host(data.into_vec()), layout, placement))
9968 }
9969 DynamicStorage::Group(GroupStorage { group }) => match group.into_host_vec::<T>() {
9970 Ok(data) => Ok((StorageBuffer::Host(data), layout, placement)),
9971 Err((group, error)) => Err(ReinterpretError::new(
9972 TypedTensor {
9973 shape,
9974 placement,
9975 storage: DynamicStorage::Group(GroupStorage {
9976 group: Box::new(group),
9977 }),
9978 },
9979 error,
9980 )),
9981 },
9982 }
9983 }
9984}
9985
9986impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R> {
9987 fn into_managed(self) -> Self {
9988 let TypedTensor {
9989 shape,
9990 placement,
9991 storage,
9992 } = self;
9993 match storage {
9994 DynamicStorage::Host(HostStorage { data }) => {
9995 let group = promote_host_group::<T, R>(shape.clone(), data)
9996 .unwrap_or_else(|err| unreachable!("a validated host owner promotes: {err}"));
9997 Self {
9998 shape,
9999 placement,
10000 storage: DynamicStorage::Group(GroupStorage {
10001 group: Box::new(group),
10002 }),
10003 }
10004 }
10005 DynamicStorage::Group(core) => Self {
10006 shape,
10007 placement,
10008 storage: DynamicStorage::Group(core),
10009 },
10010 }
10011 }
10012
10013 fn into_group_parts(self) -> (AllocationGroup, DescriptorSlot) {
10014 let TypedTensor {
10015 placement, storage, ..
10016 } = self.into_managed();
10017 let DynamicStorage::Group(GroupStorage { group }) = storage else {
10018 unreachable!("explicit group promotion produces managed storage")
10019 };
10020 let (mut group, slot) = group.into_parts();
10021 group.publish_live_descriptor_placement(slot, placement);
10022 (group, slot)
10023 }
10024
10025 /// Construct a backend-pooled host tensor with weak final-owner reclamation.
10026 /// Explicit Vec extraction disarms reclamation; aliases and retained groups
10027 /// keep the original root alive. The recycler must not acquire session locks.
10028 ///
10029 /// # Errors
10030 /// Returns validation errors for invalid shape/length and runtime-state errors
10031 /// if the freshly created host root cannot attach its recycler.
10032 #[doc(hidden)]
10033 pub fn from_vec_col_major_with_recycler(
10034 shape: impl tenferro_tensor_core::IntoRankShape<R>,
10035 data: Vec<T>,
10036 recycler: std::sync::Weak<dyn crate::HostBufferRecycler<T>>,
10037 ) -> crate::Result<Self> {
10038 let mut tensor = Self::from_vec_col_major(shape, data)?;
10039 if let DynamicStorage::Host(HostStorage { data }) = &mut tensor.storage {
10040 data.recycler = Some(recycler);
10041 }
10042 Ok(tensor)
10043 }
10044
10045 /// Borrow compact host-visible storage through one synchronization guard.
10046 #[doc(hidden)]
10047 pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
10048 where
10049 T: TensorScalar + 'static,
10050 {
10051 self.as_view().with_host_read(f)
10052 }
10053
10054 /// Borrow compact host-visible storage through one exclusive write guard.
10055 #[doc(hidden)]
10056 pub fn with_host_write<U>(&mut self, f: impl FnOnce(&mut [T]) -> U) -> crate::Result<U>
10057 where
10058 T: TensorScalar + 'static,
10059 {
10060 match &mut self.storage {
10061 DynamicStorage::Host(HostStorage { data }) => Ok(f(data.as_mut_slice())),
10062 DynamicStorage::Group(core) => {
10063 let slot = core.group.slot;
10064 let mut view = core
10065 .group
10066 .group
10067 .view_mut::<T, R>(slot)
10068 .map_err(|error| group_error("TypedTensor::with_host_write", error))?;
10069 let mut prepared = view.prepare_host_write().map_err(|error| {
10070 crate::Error::runtime_state("TypedTensor::with_host_write", error.to_string())
10071 })?;
10072 let slice = prepared.as_slice_mut().ok_or_else(|| {
10073 crate::Error::unsupported(
10074 "TypedTensor::with_host_write",
10075 "host guard access requires a compact descriptor",
10076 )
10077 })?;
10078 Ok(f(slice))
10079 }
10080 }
10081 }
10082
10083 fn group_host_slice(&self) -> &[T] {
10084 let DynamicStorage::Group(core) = &self.storage else {
10085 return &[];
10086 };
10087 core.group
10088 .view::<T>()
10089 .ok()
10090 .and_then(|view| view.host_slice().ok())
10091 .unwrap_or_default()
10092 }
10093}
10094
10095/// Layout builder used by an owning representation reinterpretation.
10096type ReinterpretLayoutFn =
10097 fn(&[usize], &[isize], isize, usize, &'static str) -> crate::Result<TensorLayout<DynRank>>;
10098
10099fn reinterpret_owned<S: TensorScalar, U: TensorScalar, R: TensorRank>(
10100 owner: TypedTensor<S, R>,
10101 from: DType,
10102 to: DType,
10103 make_layout: ReinterpretLayoutFn,
10104 op: &'static str,
10105) -> Result<TypedTensor<U>, ReinterpretError<TypedTensor<S, R>>> {
10106 if let Err(error) = validate_representation_pair(op, from, to) {
10107 return Err(ReinterpretError::new(owner, error));
10108 }
10109 if matches!(to, DType::C32 | DType::C64) && !owner.buffer_len().is_multiple_of(2) {
10110 return Err(ReinterpretError::new(
10111 owner,
10112 crate::Error::invalid_argument(
10113 op,
10114 "buffer",
10115 "the owned real buffer must contain an even number of elements",
10116 ),
10117 ));
10118 }
10119 let source_layout = owner.layout();
10120 let target_layout = match make_layout(
10121 source_layout.shape(),
10122 source_layout.strides(),
10123 source_layout.offset(),
10124 owner.buffer_len(),
10125 op,
10126 ) {
10127 Ok(layout) => layout,
10128 Err(error) => return Err(ReinterpretError::new(owner, error)),
10129 };
10130 // Only an explicit owning representation conversion promotes a plain owner;
10131 // borrowed session dispatch never passes through a group.
10132 let TypedTensor {
10133 shape,
10134 placement,
10135 storage,
10136 } = owner.into_managed();
10137 let DynamicStorage::Group(GroupStorage { group }) = storage else {
10138 unreachable!("explicit group promotion produces managed storage")
10139 };
10140 match group.reinterpret::<S, U>(
10141 target_layout.shape().to_vec(),
10142 target_layout.strides().to_vec(),
10143 target_layout.offset(),
10144 ) {
10145 Ok(group) => Ok(TypedTensor {
10146 shape: shape_vec(target_layout.shape()),
10147 placement,
10148 storage: DynamicStorage::Group(GroupStorage {
10149 group: Box::new(group),
10150 }),
10151 }),
10152 Err((group, error)) => Err(ReinterpretError::new(
10153 TypedTensor {
10154 shape,
10155 placement,
10156 storage: DynamicStorage::Group(GroupStorage {
10157 group: Box::new(group),
10158 }),
10159 },
10160 error,
10161 )),
10162 }
10163}
10164
10165impl<R: TensorRank> TypedTensor<Complex32, R> {
10166 /// Borrow this tensor as an interleaved `f32` view without copying.
10167 ///
10168 /// # Errors
10169 ///
10170 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10171 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10172 /// invalid tensor layout.
10173 pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f32, DynRank>> {
10174 self.as_view().as_real_view()
10175 }
10176
10177 /// Borrow this tensor mutably as an interleaved `f32` view without copying.
10178 ///
10179 /// # Errors
10180 ///
10181 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10182 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10183 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10184 /// invalid representation metadata.
10185 pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
10186 let op = "TypedTensor::as_real_view_mut";
10187 validate_representation_pair(op, DType::C32, DType::F32)?;
10188 let source_layout = self.layout();
10189 let layout = reinterpret_complex_to_real_layout(
10190 self.shape(),
10191 source_layout.strides(),
10192 source_layout.offset(),
10193 self.buffer_len(),
10194 op,
10195 )?;
10196 layout
10197 .validate_mutable_no_overlap()
10198 .map_err(|err| tensor_layout_error(op, err))?;
10199 if self.backend_buffer().is_some() {
10200 return Err(crate::Error::unsupported(
10201 op,
10202 "backend representation reinterpretation is enabled by the provider phases",
10203 ));
10204 }
10205 let placement = self.placement().clone();
10206 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(
10207 self.host_data_mut()?,
10208 op,
10209 )?);
10210 Ok(TypedTensorViewMut {
10211 buffer,
10212 root: None,
10213 layout,
10214 placement,
10215 _representation: std::marker::PhantomData,
10216 })
10217 }
10218
10219 /// Consume this tensor and reinterpret its owner as `f32` without copying.
10220 ///
10221 /// A failed operation returns the unchanged owner through
10222 /// [`ReinterpretError::into_owner`].
10223 ///
10224 /// # Errors
10225 ///
10226 /// Returns [`ReinterpretError::error`] containing
10227 /// [`ValidationError::InvalidArgument`] or
10228 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10229 /// owner.
10230 pub fn into_real(self) -> Result<TypedTensor<f32, DynRank>, ReinterpretError<Self>> {
10231 reinterpret_owned(
10232 self,
10233 DType::C32,
10234 DType::F32,
10235 reinterpret_complex_to_real_layout,
10236 "TypedTensor::into_real",
10237 )
10238 }
10239}
10240
10241impl<R: TensorRank> TypedTensor<Complex64, R> {
10242 /// Borrow this tensor as an interleaved `f64` view without copying.
10243 ///
10244 /// # Errors
10245 ///
10246 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10247 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10248 /// invalid tensor layout.
10249 pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f64, DynRank>> {
10250 self.as_view().as_real_view()
10251 }
10252
10253 /// Borrow this tensor mutably as an interleaved `f64` view without copying.
10254 ///
10255 /// # Errors
10256 ///
10257 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10258 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10259 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10260 /// invalid representation metadata.
10261 pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
10262 let op = "TypedTensor::as_real_view_mut";
10263 validate_representation_pair(op, DType::C64, DType::F64)?;
10264 let source_layout = self.layout();
10265 let layout = reinterpret_complex_to_real_layout(
10266 self.shape(),
10267 source_layout.strides(),
10268 source_layout.offset(),
10269 self.buffer_len(),
10270 op,
10271 )?;
10272 layout
10273 .validate_mutable_no_overlap()
10274 .map_err(|err| tensor_layout_error(op, err))?;
10275 if self.backend_buffer().is_some() {
10276 return Err(crate::Error::unsupported(
10277 op,
10278 "backend representation reinterpretation is enabled by the provider phases",
10279 ));
10280 }
10281 let placement = self.placement().clone();
10282 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(
10283 self.host_data_mut()?,
10284 op,
10285 )?);
10286 Ok(TypedTensorViewMut {
10287 buffer,
10288 root: None,
10289 layout,
10290 placement,
10291 _representation: std::marker::PhantomData,
10292 })
10293 }
10294
10295 /// Consume this tensor and reinterpret its owner as `f64` without copying.
10296 ///
10297 /// A failed operation returns the unchanged owner through
10298 /// [`ReinterpretError::into_owner`].
10299 ///
10300 /// # Errors
10301 ///
10302 /// Returns [`ReinterpretError::error`] containing
10303 /// [`ValidationError::InvalidArgument`] or
10304 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10305 /// owner.
10306 pub fn into_real(self) -> Result<TypedTensor<f64, DynRank>, ReinterpretError<Self>> {
10307 reinterpret_owned(
10308 self,
10309 DType::C64,
10310 DType::F64,
10311 reinterpret_complex_to_real_layout,
10312 "TypedTensor::into_real",
10313 )
10314 }
10315}
10316
10317impl<R: TensorRank> TypedTensor<f32, R> {
10318 /// Borrow this tensor as a complex view without copying.
10319 ///
10320 /// # Errors
10321 ///
10322 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10323 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10324 /// invalid tensor layout.
10325 pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex32, DynRank>> {
10326 self.as_view().as_complex_view()
10327 }
10328
10329 /// Borrow this tensor mutably as a complex view without copying.
10330 ///
10331 /// # Errors
10332 ///
10333 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10334 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10335 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10336 /// invalid representation metadata.
10337 pub fn as_complex_view_mut(
10338 &mut self,
10339 ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
10340 let op = "TypedTensor::as_complex_view_mut";
10341 validate_representation_pair(op, DType::F32, DType::C32)?;
10342 let source_layout = self.layout();
10343 let layout = reinterpret_real_to_complex_layout(
10344 self.shape(),
10345 source_layout.strides(),
10346 source_layout.offset(),
10347 self.buffer_len(),
10348 op,
10349 )?;
10350 layout
10351 .validate_mutable_no_overlap()
10352 .map_err(|err| tensor_layout_error(op, err))?;
10353 if self.backend_buffer().is_some() {
10354 return Err(crate::Error::unsupported(
10355 op,
10356 "backend representation reinterpretation is enabled by the provider phases",
10357 ));
10358 }
10359 let placement = self.placement().clone();
10360 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(
10361 self.host_data_mut()?,
10362 op,
10363 )?);
10364 Ok(TypedTensorViewMut {
10365 buffer,
10366 root: None,
10367 layout,
10368 placement,
10369 _representation: std::marker::PhantomData,
10370 })
10371 }
10372
10373 /// Consume this tensor and reinterpret its owner as `Complex32` without copying.
10374 ///
10375 /// The compact source must have an even physical element count. A failed
10376 /// operation returns the unchanged owner through
10377 /// [`ReinterpretError::into_owner`].
10378 ///
10379 /// # Errors
10380 ///
10381 /// Returns [`ReinterpretError::error`] containing
10382 /// [`ValidationError::InvalidArgument`] or
10383 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10384 /// owner.
10385 pub fn into_complex(self) -> Result<TypedTensor<Complex32, DynRank>, ReinterpretError<Self>> {
10386 reinterpret_owned(
10387 self,
10388 DType::F32,
10389 DType::C32,
10390 reinterpret_real_to_complex_layout,
10391 "TypedTensor::into_complex",
10392 )
10393 }
10394}
10395
10396impl<R: TensorRank> TypedTensor<f64, R> {
10397 /// Borrow this tensor as a complex view without copying.
10398 ///
10399 /// # Errors
10400 ///
10401 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10402 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
10403 /// invalid tensor layout.
10404 pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex64, DynRank>> {
10405 self.as_view().as_complex_view()
10406 }
10407
10408 /// Borrow this tensor mutably as a complex view without copying.
10409 ///
10410 /// # Errors
10411 ///
10412 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
10413 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
10414 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
10415 /// invalid representation metadata.
10416 pub fn as_complex_view_mut(
10417 &mut self,
10418 ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
10419 let op = "TypedTensor::as_complex_view_mut";
10420 validate_representation_pair(op, DType::F64, DType::C64)?;
10421 let source_layout = self.layout();
10422 let layout = reinterpret_real_to_complex_layout(
10423 self.shape(),
10424 source_layout.strides(),
10425 source_layout.offset(),
10426 self.buffer_len(),
10427 op,
10428 )?;
10429 layout
10430 .validate_mutable_no_overlap()
10431 .map_err(|err| tensor_layout_error(op, err))?;
10432 if self.backend_buffer().is_some() {
10433 return Err(crate::Error::unsupported(
10434 op,
10435 "backend representation reinterpretation is enabled by the provider phases",
10436 ));
10437 }
10438 let placement = self.placement().clone();
10439 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(
10440 self.host_data_mut()?,
10441 op,
10442 )?);
10443 Ok(TypedTensorViewMut {
10444 buffer,
10445 root: None,
10446 layout,
10447 placement,
10448 _representation: std::marker::PhantomData,
10449 })
10450 }
10451
10452 /// Consume this tensor and reinterpret its owner as `Complex64` without copying.
10453 ///
10454 /// The compact source must have an even physical element count. A failed
10455 /// operation returns the unchanged owner through
10456 /// [`ReinterpretError::into_owner`].
10457 ///
10458 /// # Errors
10459 ///
10460 /// Returns [`ReinterpretError::error`] containing
10461 /// [`ValidationError::InvalidArgument`] or
10462 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10463 /// owner.
10464 pub fn into_complex(self) -> Result<TypedTensor<Complex64, DynRank>, ReinterpretError<Self>> {
10465 reinterpret_owned(
10466 self,
10467 DType::F64,
10468 DType::C64,
10469 reinterpret_real_to_complex_layout,
10470 "TypedTensor::into_complex",
10471 )
10472 }
10473}
10474
10475impl Tensor {
10476 /// Borrow a complex tensor as its sealed interleaved real representation.
10477 ///
10478 /// # Errors
10479 ///
10480 /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
10481 /// [`ValidationError::ViewOutOfBounds`] or
10482 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
10483 pub fn as_real_view(&self) -> crate::Result<TensorView<'_>> {
10484 match self.dtype() {
10485 DType::C32 => self
10486 .as_typed::<Complex<f32>>()
10487 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10488 .as_real_view()
10489 .map(TensorView::F32),
10490 DType::C64 => self
10491 .as_typed::<Complex<f64>>()
10492 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10493 .as_real_view()
10494 .map(TensorView::F64),
10495 other => Err(crate::Error::unsupported_dtype_conversion(
10496 "Tensor::as_real_view",
10497 other,
10498 DType::F32,
10499 "only complex tensors have a sealed real representation view",
10500 )),
10501 }
10502 }
10503
10504 /// Borrow a complex tensor mutably as its sealed interleaved real representation.
10505 ///
10506 /// # Errors
10507 ///
10508 /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
10509 /// [`ValidationError::ViewOutOfBounds`] or
10510 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
10511 pub fn as_real_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
10512 match self.dtype() {
10513 DType::C32 => self
10514 .as_typed_mut::<Complex<f32>>()
10515 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10516 .as_real_view_mut()
10517 .map(TensorViewMut::F32),
10518 DType::C64 => self
10519 .as_typed_mut::<Complex<f64>>()
10520 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10521 .as_real_view_mut()
10522 .map(TensorViewMut::F64),
10523 other => Err(crate::Error::unsupported_dtype_conversion(
10524 "Tensor::as_real_view_mut",
10525 other,
10526 DType::F32,
10527 "only complex tensors have a sealed real representation view",
10528 )),
10529 }
10530 }
10531
10532 /// Consume a complex tensor and reinterpret its owner as real without copying.
10533 ///
10534 /// # Errors
10535 ///
10536 /// Returns [`ReinterpretError::error`] containing
10537 /// [`ValidationError::InvalidArgument`] or
10538 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10539 /// owner.
10540 pub fn into_real(self) -> Result<Self, ReinterpretError<Self>> {
10541 let dtype = self.dtype();
10542 match dtype {
10543 DType::C32 => {
10544 let tensor = <Complex<f32> as TensorScalar>::into_typed(self)
10545 .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
10546 tensor
10547 .into_real()
10548 .map(Tensor::from_typed::<f32>)
10549 .map_err(|error| {
10550 let (owner, error) = error.into_parts();
10551 ReinterpretError::new(Tensor::from_typed::<Complex<f32>>(owner), error)
10552 })
10553 }
10554 DType::C64 => {
10555 let tensor = <Complex<f64> as TensorScalar>::into_typed(self)
10556 .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
10557 tensor
10558 .into_real()
10559 .map(Tensor::from_typed::<f64>)
10560 .map_err(|error| {
10561 let (owner, error) = error.into_parts();
10562 ReinterpretError::new(Tensor::from_typed::<Complex<f64>>(owner), error)
10563 })
10564 }
10565 _ => Err(ReinterpretError::new(
10566 self,
10567 crate::Error::unsupported(
10568 "Tensor::into_real",
10569 "only complex tensors have a sealed real representation",
10570 ),
10571 )),
10572 }
10573 }
10574
10575 /// Borrow an interleaved real tensor as its sealed complex representation.
10576 ///
10577 /// # Errors
10578 ///
10579 /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
10580 /// [`ValidationError::ViewOutOfBounds`] or
10581 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
10582 pub fn as_complex_view(&self) -> crate::Result<TensorView<'_>> {
10583 match self.dtype() {
10584 DType::F32 => self
10585 .as_typed::<f32>()
10586 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10587 .as_complex_view()
10588 .map(TensorView::C32),
10589 DType::F64 => self
10590 .as_typed::<f64>()
10591 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10592 .as_complex_view()
10593 .map(TensorView::C64),
10594 other => Err(crate::Error::unsupported_dtype_conversion(
10595 "Tensor::as_complex_view",
10596 other,
10597 DType::C32,
10598 "only real tensors can have a sealed complex representation view",
10599 )),
10600 }
10601 }
10602
10603 /// Borrow an interleaved real tensor mutably as its sealed complex representation.
10604 ///
10605 /// # Errors
10606 ///
10607 /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
10608 /// [`ValidationError::ViewOutOfBounds`] or
10609 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
10610 pub fn as_complex_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
10611 match self.dtype() {
10612 DType::F32 => self
10613 .as_typed_mut::<f32>()
10614 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10615 .as_complex_view_mut()
10616 .map(TensorViewMut::C32),
10617 DType::F64 => self
10618 .as_typed_mut::<f64>()
10619 .unwrap_or_else(|| unreachable!("the dtype guard selects this arm"))
10620 .as_complex_view_mut()
10621 .map(TensorViewMut::C64),
10622 other => Err(crate::Error::unsupported_dtype_conversion(
10623 "Tensor::as_complex_view_mut",
10624 other,
10625 DType::C32,
10626 "only real tensors can have a sealed complex representation view",
10627 )),
10628 }
10629 }
10630
10631 /// Consume a real tensor and reinterpret its owner as complex without copying.
10632 ///
10633 /// # Errors
10634 ///
10635 /// Returns [`ReinterpretError::error`] containing
10636 /// [`ValidationError::InvalidArgument`] or
10637 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
10638 /// owner.
10639 pub fn into_complex(self) -> Result<Self, ReinterpretError<Self>> {
10640 let dtype = self.dtype();
10641 match dtype {
10642 DType::F32 => {
10643 let tensor = <f32 as TensorScalar>::into_typed(self)
10644 .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
10645 tensor
10646 .into_complex()
10647 .map(Tensor::from_typed::<Complex<f32>>)
10648 .map_err(|error| {
10649 let (owner, error) = error.into_parts();
10650 ReinterpretError::new(Tensor::from_typed::<f32>(owner), error)
10651 })
10652 }
10653 DType::F64 => {
10654 let tensor = <f64 as TensorScalar>::into_typed(self)
10655 .unwrap_or_else(|_| unreachable!("the dtype guard selects this arm"));
10656 tensor
10657 .into_complex()
10658 .map(Tensor::from_typed::<Complex<f64>>)
10659 .map_err(|error| {
10660 let (owner, error) = error.into_parts();
10661 ReinterpretError::new(Tensor::from_typed::<f64>(owner), error)
10662 })
10663 }
10664 _ => Err(ReinterpretError::new(
10665 self,
10666 crate::Error::unsupported(
10667 "Tensor::into_complex",
10668 "only real tensors have a sealed complex representation",
10669 ),
10670 )),
10671 }
10672 }
10673
10674 /// Make an explicit owning copy of this dtype-erased tensor.
10675 ///
10676 /// `Tensor` deliberately does not implement [`Clone`]: copying can fail
10677 /// (device-only storage is rejected) and allocates a new, independent
10678 /// owner, so it is an explicit fallible call rather than an infallible
10679 /// `clone()`. The copy shares nothing with `self`; mutating one never
10680 /// affects the other. To share one tensor between several users without
10681 /// copying, wrap it in [`std::sync::Arc`] or borrow views of it.
10682 ///
10683 /// # Examples
10684 ///
10685 /// ```
10686 /// use std::sync::Arc;
10687 /// use tenferro_tensor::Tensor;
10688 ///
10689 /// let weights = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
10690 /// // Two independent owners of the same values.
10691 /// let copy = weights.duplicate()?;
10692 /// assert_eq!(copy.as_slice::<f64>()?, weights.as_slice::<f64>()?);
10693 ///
10694 /// // A shared, read-only handle instead of a copy.
10695 /// let shared = Arc::new(weights);
10696 /// let other = Arc::clone(&shared);
10697 /// assert_eq!(other.shape(), &[2]);
10698 /// # Ok::<(), tenferro_tensor::Error>(())
10699 /// ```
10700 ///
10701 /// # Errors
10702 ///
10703 /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
10704 /// when the selected backend/storage owner cannot be duplicated.
10705 pub fn duplicate(&self) -> crate::Result<Self> {
10706 match &self.payload {
10707 // A caller-owned payload is copied through its own entry point, which
10708 // keeps its element type and its view; sharing the payload instead
10709 // would alias the caller's storage.
10710 TensorPayload::External(payload, placement) => Ok(Self {
10711 payload: TensorPayload::External(payload.duplicate(), placement.clone()),
10712 }),
10713 TensorPayload::Native(_) => match self.dtype() {
10714 DType::F32 => self.duplicate_typed::<f32>(),
10715 DType::F64 => self.duplicate_typed::<f64>(),
10716 DType::I32 => self.duplicate_typed::<i32>(),
10717 DType::I64 => self.duplicate_typed::<i64>(),
10718 DType::Bool => self.duplicate_typed::<bool>(),
10719 DType::C32 => self.duplicate_typed::<Complex<f32>>(),
10720 DType::C64 => self.duplicate_typed::<Complex<f64>>(),
10721 DType::External(_) => unreachable!("a native payload carries a preset dtype"),
10722 },
10723 }
10724 }
10725
10726 /// Duplicate a preset tensor through its typed owner.
10727 fn duplicate_typed<T: TensorScalar>(&self) -> crate::Result<Self> {
10728 self.as_typed::<T>()
10729 .unwrap_or_else(|| unreachable!("the caller selected this arm from the dtype"))
10730 .duplicate()
10731 .map(Self::from_typed)
10732 }
10733
10734 /// Create a tensor from a shape and column-major flat data.
10735 ///
10736 /// This is the `Tensor`-level equivalent of
10737 /// `TypedTensor::<T>::from_vec_col_major`.
10738 ///
10739 /// # Examples
10740 ///
10741 /// ```
10742 /// use tenferro_tensor::Tensor;
10743 ///
10744 /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
10745 /// assert_eq!(t.shape(), &[2, 2]);
10746 /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
10747 /// ```
10748 /// # Errors
10749 ///
10750 /// Returns [`crate::Error::Validation`] with
10751 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
10752 /// the shape product differs from `data.len()`, or
10753 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
10754 /// arithmetic overflows.
10755 pub fn from_vec_col_major<T: TensorScalar>(
10756 shape: impl tenferro_tensor_core::IntoShapeVec,
10757 data: Vec<T>,
10758 ) -> crate::Result<Self> {
10759 T::into_tensor(shape.into_shape_vec().to_vec(), data)
10760 }
10761
10762 /// Create a tensor from a shape and row-major (C-order) flat data.
10763 ///
10764 /// The values are reordered once into tenferro's column-major storage;
10765 /// no row-major owner is created. Use this for buffers authored in
10766 /// PyTorch/NumPy/C order instead of passing them to
10767 /// [`Self::from_vec_col_major`], which would reinterpret them silently.
10768 /// This is the `Tensor`-level equivalent of
10769 /// `TypedTensor::<T>::from_vec_row_major`.
10770 ///
10771 /// # Examples
10772 ///
10773 /// ```
10774 /// use tenferro_tensor::Tensor;
10775 ///
10776 /// // Row-major [[1, 2, 3], [4, 5, 6]].
10777 /// let t = Tensor::from_vec_row_major(vec![2, 3], vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0])?;
10778 /// assert_eq!(t.shape(), &[2, 3]);
10779 /// assert_eq!(t.as_slice::<f64>()?, &[1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
10780 /// # Ok::<(), tenferro_tensor::Error>(())
10781 /// ```
10782 /// # Errors
10783 ///
10784 /// Returns [`crate::Error::Validation`] with
10785 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
10786 /// the shape product differs from `data.len()`, or
10787 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
10788 /// arithmetic overflows.
10789 pub fn from_vec_row_major<T: TensorScalar>(
10790 shape: impl tenferro_tensor_core::IntoShapeVec,
10791 data: Vec<T>,
10792 ) -> crate::Result<Self> {
10793 let (shape, reordered) = row_major_reorder::<T, tenferro_tensor_core::DynRank>(
10794 shape.into_shape_vec().to_vec(),
10795 data,
10796 "Tensor::from_vec_row_major",
10797 )?;
10798 T::into_tensor(shape.as_ref().to_vec(), reordered)
10799 }
10800
10801 /// Tensor shape.
10802 ///
10803 /// # Examples
10804 ///
10805 /// ```rust
10806 /// use tenferro_tensor::{Tensor, TypedTensor};
10807 ///
10808 /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
10809 /// assert_eq!(t.shape(), &[2]);
10810 /// ```
10811 pub fn shape(&self) -> &[usize] {
10812 match &self.payload {
10813 TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.shape()),
10814 // The payload keeps its shape from construction.
10815 TensorPayload::External(payload, _) => payload.shape(),
10816 }
10817 }
10818
10819 /// Tensor dtype tag.
10820 ///
10821 /// # Examples
10822 ///
10823 /// ```rust
10824 /// use tenferro_tensor::{DType, Tensor, TypedTensor};
10825 ///
10826 /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
10827 /// assert_eq!(t.dtype(), DType::F64);
10828 /// ```
10829 pub fn dtype(&self) -> DType {
10830 match &self.payload {
10831 TensorPayload::Native(preset) => match preset {
10832 PresetTensor::F32(_) => DType::F32,
10833 PresetTensor::F64(_) => DType::F64,
10834 PresetTensor::I32(_) => DType::I32,
10835 PresetTensor::I64(_) => DType::I64,
10836 PresetTensor::Bool(_) => DType::Bool,
10837 PresetTensor::C32(_) => DType::C32,
10838 PresetTensor::C64(_) => DType::C64,
10839 },
10840 // The payload carries its own element identity.
10841 TensorPayload::External(payload, _) => DType::External(payload.element_type_id()),
10842 }
10843 }
10844
10845 /// Return placement metadata for this dtype-erased tensor.
10846 ///
10847 /// # Examples
10848 ///
10849 /// ```rust
10850 /// use tenferro_tensor::{MemoryKind, Tensor};
10851 ///
10852 /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
10853 /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
10854 /// ```
10855 pub fn placement(&self) -> &Placement {
10856 match &self.payload {
10857 TensorPayload::Native(preset) => with_preset!(preset, |typed| typed.placement()),
10858 // The placement is stored with the payload.
10859 TensorPayload::External(_, placement) => placement,
10860 }
10861 }
10862
10863 /// Return whether this tensor is backed by backend-native storage.
10864 ///
10865 /// # Examples
10866 ///
10867 /// ```rust
10868 /// use tenferro_tensor::Tensor;
10869 ///
10870 /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
10871 /// assert!(!t.is_backend_buffer());
10872 /// ```
10873 pub fn is_backend_buffer(&self) -> bool {
10874 match &self.payload {
10875 TensorPayload::Native(preset) => with_preset!(preset, |typed| {
10876 matches!(&typed.storage, DynamicStorage::Group(core) if core.group.is_backend_buffer())
10877 }),
10878 // A caller-owned payload is host memory, never a backend buffer.
10879 TensorPayload::External(..) => false,
10880 }
10881 }
10882
10883 /// Compute the physical element offset for a logical index.
10884 ///
10885 /// # Examples
10886 ///
10887 /// ```rust
10888 /// use tenferro_tensor::Tensor;
10889 ///
10890 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
10891 /// assert_eq!(t.layout_linear_offset(&[1])?, 1);
10892 /// # Ok::<(), tenferro_tensor::Error>(())
10893 /// ```
10894 /// # Errors
10895 ///
10896 /// Returns [`crate::Error::Validation`] with
10897 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
10898 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
10899 /// when an index is outside its axis extent, or
10900 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
10901 /// arithmetic overflows.
10902 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
10903 match &self.payload {
10904 TensorPayload::Native(_) => {
10905 let layout = tensor_layout(self);
10906 checked_view_offset_result(
10907 layout.shape(),
10908 layout.strides(),
10909 layout.offset(),
10910 indices,
10911 "Tensor::layout_linear_offset",
10912 )
10913 }
10914 // A caller-owned payload's owner resolves its own offsets.
10915 TensorPayload::External(..) => Err(crate::Error::unsupported_dtype(
10916 "layout_linear_offset",
10917 self.dtype(),
10918 "an externally defined payload resolves its own offsets",
10919 )),
10920 }
10921 }
10922
10923 /// Return whether this tensor is compact column-major.
10924 ///
10925 /// # Examples
10926 ///
10927 /// ```rust
10928 /// use tenferro_tensor::Tensor;
10929 ///
10930 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
10931 /// assert!(t.is_col_major_contiguous()?);
10932 /// # Ok::<(), tenferro_tensor::Error>(())
10933 /// ```
10934 /// # Errors
10935 ///
10936 /// Returns [`crate::Error::Validation`] with
10937 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
10938 /// compactness arithmetic overflows.
10939 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
10940 match &self.payload {
10941 TensorPayload::Native(_) => tensor_layout(self)
10942 .is_compact_col_major()
10943 .map_err(|err| tensor_layout_error("Tensor::is_col_major_contiguous", err)),
10944 // The payload is a compact column-major host tensor by construction.
10945 TensorPayload::External(..) => Ok(true),
10946 }
10947 }
10948
10949 /// Return a compact string summary of this tensor's layout metadata.
10950 ///
10951 /// # Examples
10952 ///
10953 /// ```rust
10954 /// use tenferro_tensor::Tensor;
10955 ///
10956 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
10957 /// assert!(t.layout_summary().contains("shape=[2]"));
10958 /// # Ok::<(), tenferro_tensor::Error>(())
10959 /// ```
10960 pub fn layout_summary(&self) -> String {
10961 let layout = tensor_layout(self);
10962 layout_summary(layout.shape(), layout.strides(), layout.offset())
10963 }
10964
10965 /// Assert this tensor is compact column-major.
10966 ///
10967 /// # Examples
10968 ///
10969 /// ```rust
10970 /// use tenferro_tensor::Tensor;
10971 ///
10972 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
10973 /// t.assert_col_major_contiguous()?;
10974 /// # Ok::<(), tenferro_tensor::Error>(())
10975 /// ```
10976 /// # Errors
10977 ///
10978 /// Returns [`crate::Error::Validation`] with
10979 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
10980 /// compactness arithmetic overflows, or
10981 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
10982 /// tensor is not compact column-major.
10983 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
10984 let layout = tensor_layout(self);
10985 assert_layout_col_major_contiguous(
10986 self.is_col_major_contiguous()?,
10987 layout.shape(),
10988 layout.strides(),
10989 layout.offset(),
10990 "Tensor::assert_col_major_contiguous",
10991 )
10992 }
10993
10994 /// Try to borrow the host data as a typed slice.
10995 ///
10996 /// Returns an error if the tensor dtype does not match `T`.
10997 ///
10998 /// # Examples
10999 ///
11000 /// ```
11001 /// use tenferro_tensor::{Tensor, TypedTensor};
11002 ///
11003 /// let t = Tensor::from_typed(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
11004 /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
11005 /// assert!(t.as_slice::<f32>().is_err());
11006 /// ```
11007 /// # Errors
11008 ///
11009 /// Returns [`crate::Error::Validation`] with
11010 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
11011 /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
11012 /// matching tensor uses backend storage that has not been downloaded.
11013 pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
11014 T::as_slice(self)
11015 }
11016
11017 /// Borrow the typed tensor when the requested scalar matches this tensor's dtype.
11018 ///
11019 /// This is the accessor tag-based dispatch needs: it recovers the typed tensor — and with it the
11020 /// device buffer — from a value whose element type is only known at run time, so a caller can
11021 /// dispatch on [`Tensor::dtype`] instead of matching every variant. An externally defined scalar
11022 /// is not a typed tensor, so it returns `None` rather than guessing a representation.
11023 ///
11024 /// # Examples
11025 ///
11026 /// ```
11027 /// use tenferro_tensor::Tensor;
11028 ///
11029 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11030 /// assert!(tensor.as_typed::<f64>().is_some());
11031 /// assert!(tensor.as_typed::<f32>().is_none());
11032 /// assert_eq!(tensor.as_typed::<f64>().unwrap().shape(), &[2]);
11033 /// # Ok::<(), tenferro_tensor::Error>(())
11034 /// ```
11035 #[must_use]
11036 pub fn as_typed<T: TensorScalar>(&self) -> Option<&TypedTensor<T>> {
11037 if self.dtype() != T::dtype() {
11038 return None;
11039 }
11040 match &self.payload {
11041 TensorPayload::Native(preset) => with_preset!(preset, |typed| {
11042 (typed as &dyn Any).downcast_ref::<TypedTensor<T>>()
11043 }),
11044 TensorPayload::External(..) => None,
11045 }
11046 }
11047
11048 /// Mutably borrow the typed tensor when the requested scalar matches this tensor's dtype.
11049 ///
11050 /// This is the mutable half of [`Tensor::as_typed`], for the tables whose arm calls a method that
11051 /// needs `&mut`, such as marking a freshly allocated output with its placement. An externally
11052 /// defined scalar is not a typed tensor, so it returns `None` for the same reason.
11053 ///
11054 /// # Examples
11055 ///
11056 /// ```
11057 /// use tenferro_tensor::Tensor;
11058 ///
11059 /// let mut tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11060 /// assert!(tensor.as_typed_mut::<f64>().is_some());
11061 /// assert!(tensor.as_typed_mut::<f32>().is_none());
11062 /// # Ok::<(), tenferro_tensor::Error>(())
11063 /// ```
11064 #[must_use]
11065 pub fn as_typed_mut<T: TensorScalar>(&mut self) -> Option<&mut TypedTensor<T>> {
11066 if self.dtype() != T::dtype() {
11067 return None;
11068 }
11069 match &mut self.payload {
11070 TensorPayload::Native(preset) => with_preset!(preset, |typed| {
11071 (typed as &mut dyn Any).downcast_mut::<TypedTensor<T>>()
11072 }),
11073 TensorPayload::External(..) => None,
11074 }
11075 }
11076
11077 /// Consume this tensor and return the owned typed tensor when the dtype matches.
11078 ///
11079 /// This is the consuming counterpart of [`Tensor::as_typed`], for the tables whose arm hands the typed
11080 /// tensor to a function that takes it by value — reusing its buffer rather than copying it.
11081 ///
11082 /// # Examples
11083 ///
11084 /// ```
11085 /// use tenferro_tensor::Tensor;
11086 ///
11087 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
11088 /// assert!(tensor.into_typed::<f64>().is_ok());
11089 ///
11090 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0])?;
11091 /// assert!(tensor.into_typed::<f64>().is_err());
11092 /// # Ok::<(), tenferro_tensor::Error>(())
11093 /// ```
11094 /// # Errors
11095 ///
11096 /// Returns [`ReinterpretError`] carrying the unchanged tensor when `T` is
11097 /// not this tensor's dtype, with [`crate::Error::Validation`] and
11098 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] as the cause.
11099 /// A matching tensor is handed over as it is, including one whose storage
11100 /// lives in a backend buffer.
11101 pub fn into_typed<T: TensorScalar>(
11102 self,
11103 ) -> std::result::Result<TypedTensor<T>, ReinterpretError<Self>> {
11104 T::into_typed(self)
11105 }
11106
11107 /// Consume this tensor and return its owned column-major buffer when the
11108 /// dtype matches.
11109 ///
11110 /// # Examples
11111 ///
11112 /// ```
11113 /// use tenferro_tensor::Tensor;
11114 ///
11115 /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
11116 /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
11117 /// ```
11118 /// # Errors
11119 ///
11120 /// Returns [`ReinterpretError`] carrying the unchanged tensor when `T` does
11121 /// not match the tensor dtype or when the matching tensor uses backend
11122 /// storage that has not been downloaded.
11123 pub fn into_vec_col_major<T: TensorScalar>(
11124 self,
11125 ) -> std::result::Result<(Vec<usize>, Vec<T>), ReinterpretError<Self>> {
11126 let typed = T::into_typed(self)?;
11127 match typed.into_vec_col_major() {
11128 Ok(parts) => Ok(parts),
11129 Err(failure) => {
11130 let (owner, error) = failure.into_parts();
11131 Err(ReinterpretError::new(Tensor::from_typed(owner), error))
11132 }
11133 }
11134 }
11135}
11136
11137// INVARIANT: retained for crate-local layout tests while tensor indexing
11138// helpers remain split across the tensor and CPU crates.
11139#[allow(dead_code)]
11140pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
11141 for i in 0..shape.len() {
11142 if shape[i] == 0 {
11143 out[i] = 0;
11144 } else {
11145 out[i] = flat % shape[i];
11146 flat /= shape[i];
11147 }
11148 }
11149}