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