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