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