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 pub fn dtype(&self) -> DType {
5287 match self {
5288 Self::Tensor(tensor) => tensor.dtype(),
5289 Self::View(view) => view.dtype(),
5290 }
5291 }
5292
5293 pub fn shape(&self) -> &[usize] {
5294 match self {
5295 Self::Tensor(tensor) => tensor.shape(),
5296 Self::View(view) => view.shape(),
5297 }
5298 }
5299
5300 /// Return the placement metadata carried by this read target.
5301 ///
5302 /// # Examples
5303 ///
5304 /// ```
5305 /// use tenferro_tensor::{MemoryKind, Tensor, TensorRead};
5306 ///
5307 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
5308 /// let read = TensorRead::from_tensor(&tensor);
5309 /// assert_eq!(read.placement().memory_kind, MemoryKind::UnpinnedHost);
5310 /// # Ok::<(), tenferro_tensor::Error>(())
5311 /// ```
5312 pub fn placement(&self) -> &Placement {
5313 match self {
5314 Self::Tensor(tensor) => tensor.placement(),
5315 Self::View(view) => view.placement(),
5316 }
5317 }
5318
5319 /// Return the physical backend family of this read target, when backend-owned.
5320 ///
5321 /// # Examples
5322 ///
5323 /// ```
5324 /// use tenferro_tensor::{Tensor, TensorRead};
5325 ///
5326 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
5327 /// assert_eq!(TensorRead::from_tensor(&tensor).backend_family(), None);
5328 /// # Ok::<(), tenferro_tensor::Error>(())
5329 /// ```
5330 pub fn backend_family(&self) -> Option<&'static str> {
5331 match self {
5332 Self::Tensor(tensor) => match tensor {
5333 Tensor::F32(t) => t.backend_family(),
5334 Tensor::F64(t) => t.backend_family(),
5335 Tensor::I32(t) => t.backend_family(),
5336 Tensor::I64(t) => t.backend_family(),
5337 Tensor::Bool(t) => t.backend_family(),
5338 Tensor::C32(t) => t.backend_family(),
5339 Tensor::C64(t) => t.backend_family(),
5340 },
5341 Self::View(view) => view.backend_family(),
5342 }
5343 }
5344
5345 /// Return the shared allocation domain of this read target, when present.
5346 ///
5347 /// # Examples
5348 ///
5349 /// ```
5350 /// use tenferro_tensor::{Tensor, TensorRead};
5351 ///
5352 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
5353 /// assert_eq!(TensorRead::from_tensor(&tensor).allocation_domain(), None);
5354 /// # Ok::<(), tenferro_tensor::Error>(())
5355 /// ```
5356 pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
5357 match self {
5358 Self::Tensor(tensor) => match tensor {
5359 Tensor::F32(t) => t.allocation_domain(),
5360 Tensor::F64(t) => t.allocation_domain(),
5361 Tensor::I32(t) => t.allocation_domain(),
5362 Tensor::I64(t) => t.allocation_domain(),
5363 Tensor::Bool(t) => t.allocation_domain(),
5364 Tensor::C32(t) => t.allocation_domain(),
5365 Tensor::C64(t) => t.allocation_domain(),
5366 },
5367 Self::View(view) => view.allocation_domain(),
5368 }
5369 }
5370
5371 /// # Errors
5372 ///
5373 /// Returns [`crate::Error::Validation`] with
5374 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5375 /// column-major stride arithmetic overflows.
5376 pub fn strides(&self) -> crate::Result<Vec<isize>> {
5377 match self {
5378 Self::Tensor(tensor) => col_major_strides(tensor.shape()),
5379 Self::View(view) => Ok(view.strides().to_vec()),
5380 }
5381 }
5382
5383 pub fn offset(&self) -> isize {
5384 match self {
5385 Self::Tensor(_) => 0,
5386 Self::View(view) => view.offset(),
5387 }
5388 }
5389
5390 /// # Errors
5391 ///
5392 /// Returns [`crate::Error::Validation`] with
5393 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5394 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5395 /// when an index is outside its axis extent, or
5396 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
5397 /// arithmetic overflows.
5398 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
5399 match self {
5400 Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
5401 Self::View(view) => view.layout_linear_offset(indices),
5402 }
5403 }
5404
5405 /// # Errors
5406 ///
5407 /// Returns [`crate::Error::Validation`] with
5408 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5409 /// compactness arithmetic overflows.
5410 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
5411 match self {
5412 Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
5413 Self::View(view) => view.is_col_major_contiguous(),
5414 }
5415 }
5416
5417 pub fn layout_summary(&self) -> String {
5418 let strides = match self.strides() {
5419 Ok(strides) => strides,
5420 Err(err) => return format!("layout unavailable: {err}"),
5421 };
5422 layout_summary(self.shape(), &strides, self.offset())
5423 }
5424
5425 /// # Errors
5426 ///
5427 /// Returns [`crate::Error::Validation`] with
5428 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5429 /// compactness arithmetic overflows, or
5430 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
5431 /// view is not compact column-major.
5432 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
5433 let strides = self.strides()?;
5434 assert_layout_col_major_contiguous(
5435 self.is_col_major_contiguous()?,
5436 self.shape(),
5437 &strides,
5438 self.offset(),
5439 "TensorRead::assert_col_major_contiguous",
5440 )
5441 }
5442
5443 pub fn as_tensor(&self) -> Option<&'a Tensor> {
5444 match self {
5445 Self::Tensor(tensor) => Some(*tensor),
5446 Self::View(_) => None,
5447 }
5448 }
5449}
5450
5451impl<'a> TensorWrite<'a> {
5452 pub fn from_tensor(tensor: &'a mut Tensor) -> Self {
5453 Self::Tensor(tensor)
5454 }
5455
5456 pub fn from_view(view: TensorViewMut<'a>) -> Self {
5457 Self::View(view)
5458 }
5459
5460 /// Borrow this writable target as a read-only tensor input.
5461 ///
5462 /// This is useful for explicit read-modify-write kernels such as
5463 /// accumulation updates. The returned view borrows through `&self`, so it
5464 /// cannot outlive the current read-only borrow of the writable target.
5465 ///
5466 /// # Examples
5467 ///
5468 /// ```rust
5469 /// use tenferro_tensor::{DType, Tensor, TensorWrite};
5470 ///
5471 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
5472 /// let write = TensorWrite::from_tensor(&mut tensor);
5473 /// let read = write.as_read();
5474 /// assert_eq!(read.dtype(), DType::F64);
5475 /// # Ok::<(), tenferro_tensor::Error>(())
5476 /// ```
5477 pub fn as_read(&self) -> TensorRead<'_> {
5478 match self {
5479 Self::Tensor(tensor) => TensorRead::from_tensor(tensor),
5480 Self::View(view) => TensorRead::from_view(view.as_read_only()),
5481 }
5482 }
5483
5484 pub fn dtype(&self) -> DType {
5485 match self {
5486 Self::Tensor(tensor) => tensor.dtype(),
5487 Self::View(view) => view.dtype(),
5488 }
5489 }
5490
5491 pub fn shape(&self) -> &[usize] {
5492 match self {
5493 Self::Tensor(tensor) => tensor.shape(),
5494 Self::View(view) => view.shape(),
5495 }
5496 }
5497
5498 /// # Errors
5499 ///
5500 /// Returns [`crate::Error::Validation`] with
5501 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5502 /// column-major stride arithmetic overflows.
5503 pub fn strides(&self) -> crate::Result<Vec<isize>> {
5504 match self {
5505 Self::Tensor(tensor) => col_major_strides(tensor.shape()),
5506 Self::View(view) => Ok(view.strides().to_vec()),
5507 }
5508 }
5509
5510 pub fn offset(&self) -> isize {
5511 match self {
5512 Self::Tensor(_) => 0,
5513 Self::View(view) => view.offset(),
5514 }
5515 }
5516
5517 /// # Errors
5518 ///
5519 /// Returns [`crate::Error::Validation`] with
5520 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5521 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5522 /// when an index is outside its axis extent, or
5523 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
5524 /// arithmetic overflows.
5525 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
5526 match self {
5527 Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
5528 Self::View(view) => view.layout_linear_offset(indices),
5529 }
5530 }
5531
5532 /// # Errors
5533 ///
5534 /// Returns [`crate::Error::Validation`] with
5535 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5536 /// compactness arithmetic overflows.
5537 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
5538 match self {
5539 Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
5540 Self::View(view) => view.is_col_major_contiguous(),
5541 }
5542 }
5543
5544 pub fn layout_summary(&self) -> String {
5545 let strides = match self.strides() {
5546 Ok(strides) => strides,
5547 Err(err) => return format!("layout unavailable: {err}"),
5548 };
5549 layout_summary(self.shape(), &strides, self.offset())
5550 }
5551
5552 /// # Errors
5553 ///
5554 /// Returns [`crate::Error::Validation`] with
5555 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5556 /// compactness arithmetic overflows, or
5557 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
5558 /// view is not compact column-major.
5559 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
5560 let strides = self.strides()?;
5561 assert_layout_col_major_contiguous(
5562 self.is_col_major_contiguous()?,
5563 self.shape(),
5564 &strides,
5565 self.offset(),
5566 "TensorWrite::assert_col_major_contiguous",
5567 )
5568 }
5569}
5570
5571/// Column-major strides derived from a shape.
5572///
5573/// # Examples
5574///
5575/// ```rust
5576/// use tenferro_tensor::col_major_strides;
5577///
5578/// assert_eq!(col_major_strides(&[2, 3])?, vec![1, 2]);
5579/// # Ok::<(), tenferro_tensor::Error>(())
5580/// ```
5581/// # Errors
5582///
5583/// Returns [`crate::Error::Validation`] with
5584/// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when a
5585/// column-major stride product overflows.
5586pub fn col_major_strides(shape: &[usize]) -> crate::Result<Vec<isize>> {
5587 let mut strides = Vec::with_capacity(shape.len());
5588 let mut stride = 1isize;
5589 for &extent in shape {
5590 strides.push(stride);
5591 let extent = isize::try_from(extent).map_err(|_| {
5592 crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
5593 })?;
5594 stride = stride.checked_mul(extent).ok_or_else(|| {
5595 crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
5596 })?;
5597 }
5598 Ok(strides)
5599}
5600
5601fn try_linear_offset_for_shape(
5602 shape: &[usize],
5603 indices: &[usize],
5604 op: &'static str,
5605) -> crate::Result<usize> {
5606 if indices.len() != shape.len() {
5607 return Err(crate::Error::validation(
5608 op,
5609 ValidationError::RankMismatch {
5610 expected: shape.len(),
5611 actual: indices.len(),
5612 },
5613 ));
5614 }
5615 let mut offset = 0usize;
5616 let mut stride = 1usize;
5617 for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
5618 if idx >= extent {
5619 return Err(crate::Error::invalid_argument(
5620 op,
5621 "index",
5622 format!("index {idx} out of bounds for axis {axis} extent {extent}"),
5623 ));
5624 }
5625 offset =
5626 offset
5627 .checked_add(idx.checked_mul(stride).ok_or_else(|| {
5628 crate::Error::validation(op, ValidationError::IntegerOverflow)
5629 })?)
5630 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
5631 stride = stride
5632 .checked_mul(extent)
5633 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
5634 }
5635 Ok(offset)
5636}
5637
5638fn checked_view_offset_result(
5639 shape: &[usize],
5640 strides: &[isize],
5641 base_offset: isize,
5642 indices: &[usize],
5643 op: &'static str,
5644) -> crate::Result<usize> {
5645 if indices.len() != shape.len() {
5646 return Err(crate::Error::validation(
5647 op,
5648 ValidationError::RankMismatch {
5649 expected: shape.len(),
5650 actual: indices.len(),
5651 },
5652 ));
5653 }
5654 for (axis, (&index, &extent)) in indices.iter().zip(shape).enumerate() {
5655 if index >= extent {
5656 return Err(crate::Error::invalid_argument(
5657 op,
5658 "index",
5659 format!("index {index} out of bounds for axis {axis} extent {extent}"),
5660 ));
5661 }
5662 }
5663 checked_view_offset(shape, strides, base_offset, indices)
5664 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
5665}
5666
5667fn layout_summary(shape: &[usize], strides: &[isize], offset: isize) -> String {
5668 format!("shape={shape:?} strides={strides:?} offset={offset}")
5669}
5670
5671fn assert_layout_col_major_contiguous(
5672 is_contiguous: bool,
5673 shape: &[usize],
5674 strides: &[isize],
5675 offset: isize,
5676 op: &'static str,
5677) -> crate::Result<()> {
5678 if is_contiguous {
5679 Ok(())
5680 } else {
5681 Err(crate::Error::invalid_argument(
5682 op,
5683 "layout",
5684 format!(
5685 "expected compact column-major layout, got {}",
5686 layout_summary(shape, strides, offset)
5687 ),
5688 ))
5689 }
5690}
5691
5692fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
5693 shape.iter().try_fold(1usize, |acc, &dim| {
5694 acc.checked_mul(dim)
5695 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
5696 })
5697}
5698
5699fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
5700 let n = try_shape_product(shape, op)?;
5701 if data_len != n {
5702 return Err(crate::Error::validation(
5703 op,
5704 ValidationError::ShapeDataLengthMismatch {
5705 expected: n,
5706 actual: data_len,
5707 },
5708 ));
5709 }
5710 Ok(())
5711}
5712
5713fn try_compact_layout<R: TensorRank>(
5714 shape: impl Into<R::Shape>,
5715 op: &'static str,
5716) -> crate::Result<TensorLayout<R>> {
5717 TensorLayout::compact(shape.into()).map_err(|err| tensor_layout_error(op, err))
5718}
5719
5720fn tensor_layout_error(
5721 op: &'static str,
5722 err: tenferro_tensor_core::ValidationError,
5723) -> crate::Error {
5724 crate::Error::validation(op, err)
5725}
5726
5727fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
5728 if shape.contains(&0) {
5729 return Ok(0);
5730 }
5731 shape.iter().try_fold(1usize, |product, &dim| {
5732 product
5733 .checked_mul(dim)
5734 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
5735 })
5736}
5737
5738fn checked_view_offset(
5739 shape: &[usize],
5740 strides: &[isize],
5741 base_offset: isize,
5742 indices: &[usize],
5743) -> Option<usize> {
5744 if indices.len() != shape.len() {
5745 return None;
5746 }
5747
5748 let mut offset = base_offset;
5749 for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
5750 if index >= extent {
5751 return None;
5752 }
5753 let index = isize::try_from(index).ok()?;
5754 let delta = index.checked_mul(stride)?;
5755 offset = offset.checked_add(delta)?;
5756 }
5757
5758 usize::try_from(offset).ok()
5759}
5760
5761fn reachable_layout_span(
5762 shape: &[usize],
5763 strides: &[isize],
5764 offset: isize,
5765) -> crate::Result<Option<(usize, usize)>> {
5766 if shape.contains(&0) {
5767 return Ok(None);
5768 }
5769
5770 let mut min_offset = offset;
5771 let mut max_offset = offset;
5772 for (&extent, &stride) in shape.iter().zip(strides) {
5773 let steps = isize::try_from(extent.saturating_sub(1)).map_err(|_| {
5774 crate::Error::validation(
5775 "TypedTensorViewMut::try_multi_slice_mut",
5776 ValidationError::IntegerOverflow,
5777 )
5778 })?;
5779 let end = stride.checked_mul(steps).ok_or_else(|| {
5780 crate::Error::validation(
5781 "TypedTensorViewMut::try_multi_slice_mut",
5782 ValidationError::IntegerOverflow,
5783 )
5784 })?;
5785 let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
5786 min_offset = min_offset.checked_add(axis_min).ok_or_else(|| {
5787 crate::Error::validation(
5788 "TypedTensorViewMut::try_multi_slice_mut",
5789 ValidationError::IntegerOverflow,
5790 )
5791 })?;
5792 max_offset = max_offset.checked_add(axis_max).ok_or_else(|| {
5793 crate::Error::validation(
5794 "TypedTensorViewMut::try_multi_slice_mut",
5795 ValidationError::IntegerOverflow,
5796 )
5797 })?;
5798 }
5799
5800 let min_offset = usize::try_from(min_offset).map_err(|_| {
5801 crate::Error::invalid_argument(
5802 "TypedTensorViewMut::try_multi_slice_mut",
5803 "layout",
5804 "minimum reachable offset is negative",
5805 )
5806 })?;
5807 let max_offset = usize::try_from(max_offset).map_err(|_| {
5808 crate::Error::invalid_argument(
5809 "TypedTensorViewMut::try_multi_slice_mut",
5810 "layout",
5811 "maximum reachable offset is negative",
5812 )
5813 })?;
5814 Ok(Some((min_offset, max_offset)))
5815}
5816
5817fn split_two_mut_ranges<T>(
5818 data: &mut [T],
5819 first: (usize, usize),
5820 second: (usize, usize),
5821) -> Option<(&mut [T], &mut [T])> {
5822 if first.1 < second.0 {
5823 let (_, after_first_start) = data.split_at_mut(first.0);
5824 let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
5825 let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
5826 let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
5827 Some((first_slice, second_slice))
5828 } else if second.1 < first.0 {
5829 let (_, after_second_start) = data.split_at_mut(second.0);
5830 let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
5831 let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
5832 let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
5833 Some((first_slice, second_slice))
5834 } else {
5835 None
5836 }
5837}
5838
5839fn adjusted_view_offset(offset: isize, span_start: usize) -> crate::Result<isize> {
5840 let span_start = isize::try_from(span_start).map_err(|_| {
5841 crate::Error::validation(
5842 "TypedTensorViewMut::try_multi_slice_mut",
5843 ValidationError::IntegerOverflow,
5844 )
5845 })?;
5846 offset.checked_sub(span_start).ok_or_else(|| {
5847 crate::Error::validation(
5848 "TypedTensorViewMut::try_multi_slice_mut",
5849 ValidationError::IntegerOverflow,
5850 )
5851 })
5852}
5853
5854fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
5855 layout: &TensorLayout<R>,
5856 offset: isize,
5857 data: &'a mut [T],
5858 placement: Placement,
5859) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
5860 let shape = R::shape_from_vec(shape_vec(layout.shape()))
5861 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
5862 let strides = R::strides_from_vec(stride_vec(layout.strides()))
5863 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
5864 TypedTensorViewMut::from_buffer_ref_mut(
5865 shape,
5866 strides,
5867 offset,
5868 TensorStorageRefMut::Host(data),
5869 placement,
5870 "TypedTensorViewMut::try_multi_slice_mut",
5871 )
5872}
5873
5874fn contiguous_layout_slice<'a, T, R: TensorRank>(
5875 layout: &TensorLayout<R>,
5876 data: &'a [T],
5877 op: &'static str,
5878) -> crate::Result<&'a [T]> {
5879 if !layout
5880 .is_compact_col_major()
5881 .map_err(|err| tensor_layout_error(op, err))?
5882 {
5883 return Err(crate::Error::invalid_argument(
5884 op,
5885 "layout",
5886 "view is not contiguous column-major",
5887 ));
5888 }
5889 let len = checked_view_element_count(layout.shape(), op)?;
5890 let start = usize::try_from(layout.offset())
5891 .map_err(|_| crate::Error::invalid_argument(op, "layout", "view offset is negative"))?;
5892 let end = start
5893 .checked_add(len)
5894 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
5895 data.get(start..end)
5896 .ok_or_else(|| crate::Error::validation(op, ValidationError::ViewOutOfBounds))
5897}
5898
5899fn relaxed_col_major_contiguous(
5900 shape: &[usize],
5901 strides: &[isize],
5902 op: &'static str,
5903) -> crate::Result<bool> {
5904 if shape.contains(&0) {
5905 return Ok(true);
5906 }
5907
5908 let mut expected = 1isize;
5909 for (&extent, &stride) in shape.iter().zip(strides) {
5910 if extent <= 1 {
5911 continue;
5912 }
5913 if stride != expected {
5914 return Ok(false);
5915 }
5916 let extent = isize::try_from(extent)
5917 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
5918 expected = expected
5919 .checked_mul(extent)
5920 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
5921 }
5922 Ok(true)
5923}
5924
5925fn reshape_layout_dyn<R: TensorRank>(
5926 layout: &TensorLayout<R>,
5927 shape: &[usize],
5928 buffer_len: usize,
5929 op: &'static str,
5930) -> crate::Result<TensorLayout<DynRank>> {
5931 match layout.reshape_view_as::<DynRank>(shape_vec(shape), buffer_len) {
5932 Ok(layout) => Ok(layout),
5933 Err(err) => {
5934 if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
5935 return Err(tensor_layout_error(op, err));
5936 }
5937 let from = checked_view_element_count(layout.shape(), op)?;
5938 let to = checked_view_element_count(shape, op)?;
5939 if from != to {
5940 return Err(tensor_layout_error(
5941 op,
5942 tenferro_tensor_core::ShapeMismatch::ReshapeElementCount { from, to }.into(),
5943 ));
5944 }
5945 TensorLayout::<DynRank>::compact(shape_vec(shape))
5946 .and_then(|compact| {
5947 TensorLayout::from_parts(
5948 shape_vec(compact.shape()),
5949 stride_vec(compact.strides()),
5950 layout.offset(),
5951 buffer_len,
5952 )
5953 })
5954 .map_err(|err| tensor_layout_error(op, err))
5955 }
5956 }
5957}
5958
5959fn core_slice_specs(
5960 slices: &[StridedSliceSpec],
5961 shape: &[usize],
5962 op: &'static str,
5963) -> crate::Result<Vec<CoreSliceSpec>> {
5964 if slices.len() != shape.len() {
5965 return Err(crate::Error::validation(
5966 op,
5967 ValidationError::RankMismatch {
5968 expected: shape.len(),
5969 actual: slices.len(),
5970 },
5971 ));
5972 }
5973
5974 let mut specs = Vec::with_capacity(slices.len());
5975 for (slice, &axis_len) in slices.iter().zip(shape) {
5976 specs.push(core_slice_spec(*slice, axis_len, op)?);
5977 }
5978 Ok(specs)
5979}
5980
5981fn core_slice_spec(
5982 slice: StridedSliceSpec,
5983 axis_len: usize,
5984 op: &'static str,
5985) -> crate::Result<CoreSliceSpec> {
5986 if slice.step() == 0 {
5987 return Err(crate::Error::validation(
5988 op,
5989 ValidationError::InvalidSliceStep { step: slice.step() },
5990 ));
5991 }
5992
5993 let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
5994 let end = match slice.end() {
5995 Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
5996 None => isize::try_from(axis_len)
5997 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
5998 };
5999
6000 if slice.step() > 0 {
6001 return Ok(CoreSliceSpec {
6002 start,
6003 end,
6004 step: slice.step(),
6005 });
6006 }
6007
6008 if start >= end {
6009 return Ok(CoreSliceSpec {
6010 start,
6011 end: start,
6012 step: slice.step(),
6013 });
6014 }
6015
6016 Ok(CoreSliceSpec {
6017 start: end
6018 .checked_sub(1)
6019 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
6020 end: start
6021 .checked_sub(1)
6022 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
6023 step: slice.step(),
6024 })
6025}
6026
6027fn normalize_strided_bound(
6028 bound: isize,
6029 axis_len: usize,
6030 op: &'static str,
6031 role: &'static str,
6032) -> crate::Result<isize> {
6033 let original_axis_len = axis_len;
6034 let axis_len = isize::try_from(axis_len)
6035 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6036 let bound = if bound < 0 {
6037 axis_len
6038 .checked_add(bound)
6039 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?
6040 } else {
6041 bound
6042 };
6043 if !(0..=axis_len).contains(&bound) {
6044 let (start, end) = if role == "slice start" {
6045 (bound, bound)
6046 } else {
6047 (0, bound)
6048 };
6049 return Err(crate::Error::validation(
6050 op,
6051 ValidationError::InvalidSliceBounds {
6052 start,
6053 end,
6054 axis_len: original_axis_len,
6055 },
6056 ));
6057 }
6058 Ok(bound)
6059}
6060
6061fn slice_axis_specs(
6062 rank: usize,
6063 axis: usize,
6064 slice: StridedSliceSpec,
6065 op: &'static str,
6066) -> crate::Result<Vec<StridedSliceSpec>> {
6067 if axis >= rank {
6068 return Err(crate::Error::validation(
6069 op,
6070 ValidationError::AxisOutOfBounds { axis, rank },
6071 ));
6072 }
6073
6074 let mut slices = vec![StridedSliceSpec::all(); rank];
6075 slices[axis] = slice;
6076 Ok(slices)
6077}
6078
6079pub(crate) fn default_placement() -> Placement {
6080 Placement {
6081 memory_kind: MemoryKind::UnpinnedHost,
6082 device: None,
6083 cpu_affinity: None,
6084 }
6085}
6086
6087fn typed_tensor_from_vec_col_major<T: TensorScalar, R: TensorRank>(
6088 shape: impl Into<R::Shape>,
6089 data: Vec<T>,
6090 op: &'static str,
6091) -> crate::Result<TypedTensor<T, R>> {
6092 try_typed_tensor_from_vec_col_major(shape, data, op)
6093}
6094
6095fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
6096 shape: impl Into<R::Shape>,
6097 data: Vec<T>,
6098 op: &'static str,
6099) -> crate::Result<TypedTensor<T, R>>
6100where
6101 T: TensorScalar,
6102{
6103 let layout = try_compact_layout(shape, op)?;
6104 try_checked_shape_len(layout.shape(), data.len(), op)?;
6105 let group_shape =
6106 R::shape_from_vec(shape_vec(layout.shape())).map_err(|err| tensor_layout_error(op, err))?;
6107 let group = OwnedTensorGroup::from_host_vec(group_shape, data)?;
6108 Ok(TypedTensor {
6109 group,
6110 layout,
6111 placement: default_placement(),
6112 _scalar: PhantomData,
6113 })
6114}
6115
6116fn typed_tensor_zeros<T: TensorScalar + Zero, R: TensorRank>(
6117 shape: impl Into<R::Shape>,
6118) -> crate::Result<TypedTensor<T, R>> {
6119 try_typed_tensor_zeros(shape)
6120}
6121
6122fn try_typed_tensor_zeros<T: TensorScalar + Clone + Zero, R: TensorRank>(
6123 shape: impl Into<R::Shape>,
6124) -> crate::Result<TypedTensor<T, R>> {
6125 let layout = try_compact_layout(shape, "zeros")?;
6126 let n = try_shape_product(layout.shape(), "zeros")?;
6127 let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6128 .map_err(|err| tensor_layout_error("zeros", err))?;
6129 let group = OwnedTensorGroup::from_host_vec(group_shape, vec![T::zero(); n])?;
6130 Ok(TypedTensor {
6131 group,
6132 layout,
6133 placement: default_placement(),
6134 _scalar: PhantomData,
6135 })
6136}
6137
6138fn typed_tensor_ones<T: TensorScalar + One + Zero, R: TensorRank>(
6139 shape: impl Into<R::Shape>,
6140) -> crate::Result<TypedTensor<T, R>> {
6141 try_typed_tensor_ones(shape)
6142}
6143
6144fn try_typed_tensor_ones<T: TensorScalar + Clone + One + Zero, R: TensorRank>(
6145 shape: impl Into<R::Shape>,
6146) -> crate::Result<TypedTensor<T, R>> {
6147 let layout = try_compact_layout(shape, "ones")?;
6148 let n = try_shape_product(layout.shape(), "ones")?;
6149 let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6150 .map_err(|err| tensor_layout_error("ones", err))?;
6151 let group = OwnedTensorGroup::from_host_vec(group_shape, vec![T::one(); n])?;
6152 Ok(TypedTensor {
6153 group,
6154 layout,
6155 placement: default_placement(),
6156 _scalar: PhantomData,
6157 })
6158}
6159
6160fn typed_tensor_from_buffer_col_major<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
6161 shape: impl Into<R::Shape>,
6162 buffer: StorageBuffer<T>,
6163 placement: Placement,
6164) -> crate::Result<TypedTensor<T, R>> {
6165 try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
6166}
6167
6168#[doc(hidden)]
6169fn typed_tensor_from_backend_allocation<T: TensorScalar + Send + Sync + 'static, R: TensorRank>(
6170 shape: impl Into<R::Shape>,
6171 allocation: Box<dyn crate::BackendAllocation>,
6172 placement: Placement,
6173) -> crate::Result<TypedTensor<T, R>> {
6174 let layout = try_compact_layout(shape, "from_backend_allocation")?;
6175 let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6176 .map_err(|err| tensor_layout_error("from_backend_allocation", err))?;
6177 let (group, slot) =
6178 AllocationGroup::from_backend_allocation::<T, R>(group_shape, allocation)
6179 .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
6180 let allocation_index = group
6181 .allocation_index(slot)
6182 .map_err(|error| group_error("TypedTensor::from_backend_allocation", error))?;
6183 let (host_ptr, host_byte_len) = host_metadata::<T>(&group, slot);
6184 Ok(TypedTensor {
6185 group: OwnedTensorGroup {
6186 group,
6187 slot,
6188 allocation_index,
6189 host_ptr,
6190 host_byte_len,
6191 _rank: PhantomData,
6192 },
6193 layout,
6194 placement,
6195 _scalar: PhantomData,
6196 })
6197}
6198
6199fn try_typed_tensor_from_buffer_col_major<
6200 T: TensorScalar + Send + Sync + 'static,
6201 R: TensorRank,
6202>(
6203 shape: impl Into<R::Shape>,
6204 buffer: StorageBuffer<T>,
6205 placement: Placement,
6206) -> crate::Result<TypedTensor<T, R>> {
6207 let layout = try_compact_layout(shape, "from_buffer_col_major")?;
6208 let len = buffer.len();
6209 try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
6210 let group_shape = R::shape_from_vec(shape_vec(layout.shape()))
6211 .map_err(|err| tensor_layout_error("from_buffer_col_major", err))?;
6212 let group = match buffer {
6213 StorageBuffer::Host(data) => OwnedTensorGroup::from_host_vec(group_shape, data)?,
6214 StorageBuffer::Backend(buffer) => OwnedTensorGroup::from_backend_buffer(
6215 group_shape,
6216 StorageBuffer::Backend(buffer),
6217 placement.clone(),
6218 )?,
6219 };
6220 Ok(TypedTensor {
6221 group,
6222 layout,
6223 placement,
6224 _scalar: PhantomData,
6225 })
6226}
6227
6228impl<T: TensorScalar + Zero, R: TensorRank> TypedTensor<T, R> {
6229 /// Allocate a zero-filled tensor.
6230 ///
6231 /// # Examples
6232 ///
6233 /// ```rust
6234 /// use tenferro_tensor::TypedTensor;
6235 ///
6236 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
6237 /// assert_eq!(t.n_elements(), 6);
6238 /// ```
6239 /// # Errors
6240 ///
6241 /// Returns [`crate::Error::Validation`] with
6242 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
6243 /// product or compact-stride arithmetic overflows.
6244 pub fn zeros(shape: impl Into<R::Shape>) -> crate::Result<Self> {
6245 typed_tensor_zeros(shape)
6246 }
6247}
6248
6249impl<T: TensorScalar + One + Zero, R: TensorRank> TypedTensor<T, R> {
6250 /// Allocate a one-filled tensor.
6251 ///
6252 /// # Examples
6253 ///
6254 /// ```rust
6255 /// use tenferro_tensor::TypedTensor;
6256 ///
6257 /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
6258 /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
6259 /// ```
6260 /// # Errors
6261 ///
6262 /// Returns [`crate::Error::Validation`] with
6263 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
6264 /// product or compact-stride arithmetic overflows.
6265 pub fn ones(shape: impl Into<R::Shape>) -> crate::Result<Self> {
6266 typed_tensor_ones(shape)
6267 }
6268}
6269
6270impl<T, R: TensorRank> TypedTensor<T, R> {
6271 /// Create a tensor from an existing buffer and compact column-major layout.
6272 ///
6273 /// This preserves the owned tensor invariant that layout metadata is
6274 /// compact column-major, including for backend-owned buffers.
6275 ///
6276 /// # Examples
6277 ///
6278 /// ```
6279 /// use tenferro_tensor::{StorageBuffer, Placement, TypedTensor};
6280 ///
6281 /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
6282 /// vec![2],
6283 /// StorageBuffer::Host(vec![1.0, 2.0]),
6284 /// Placement {
6285 /// memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
6286 /// device: None,
6287 /// cpu_affinity: None,
6288 /// },
6289 /// )
6290 /// .unwrap();
6291 /// assert_eq!(tensor.shape(), &[2]);
6292 /// ```
6293 /// # Errors
6294 ///
6295 /// Returns [`crate::Error::Validation`] with
6296 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
6297 /// the shape product differs from the buffer length,
6298 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape or
6299 /// stride arithmetic overflows, or
6300 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a supplied
6301 /// rank-specific shape cannot be represented.
6302 pub fn from_buffer_col_major(
6303 shape: impl Into<R::Shape>,
6304 buffer: StorageBuffer<T>,
6305 placement: Placement,
6306 ) -> crate::Result<Self>
6307 where
6308 T: TensorScalar + Send + Sync + 'static,
6309 {
6310 typed_tensor_from_buffer_col_major(shape, buffer, placement)
6311 }
6312
6313 /// Consume a scalar-independent provider root into one compact tensor.
6314 #[doc(hidden)]
6315 pub fn from_backend_allocation(
6316 shape: impl Into<R::Shape>,
6317 allocation: Box<dyn crate::BackendAllocation>,
6318 placement: Placement,
6319 ) -> crate::Result<Self>
6320 where
6321 T: TensorScalar + Send + Sync + 'static,
6322 {
6323 typed_tensor_from_backend_allocation(shape, allocation, placement)
6324 }
6325
6326 /// Convert this tensor into static rank metadata after validating its rank.
6327 ///
6328 /// The buffer and placement are preserved. This method changes only the
6329 /// compile-time rank marker on the owned compact column-major tensor.
6330 ///
6331 /// # Examples
6332 ///
6333 /// ```rust
6334 /// use tenferro_tensor::{Rank, TypedTensor};
6335 ///
6336 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
6337 /// let ranked: TypedTensor<f64, Rank<2>> = tensor.try_into_rank::<2>()?;
6338 /// assert_eq!(ranked.shape(), &[2, 3]);
6339 /// # Ok::<(), tenferro_tensor::Error>(())
6340 /// ```
6341 /// # Errors
6342 ///
6343 /// Returns [`crate::Error::Validation`] with
6344 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
6345 /// rank does not match the existing shape,
6346 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when compact
6347 /// strides cannot be computed, or
6348 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
6349 /// preserved buffer cannot hold the rank-converted layout.
6350 pub fn try_into_rank<const N: usize>(self) -> crate::Result<TypedTensor<T, Rank<N>>> {
6351 let op = "TypedTensor::try_into_rank";
6352 let actual = self.shape().len();
6353 let shape: [usize; N] = self.shape().try_into().map_err(|_| {
6354 tensor_layout_error(
6355 op,
6356 ValidationError::RankMismatch {
6357 expected: N,
6358 actual,
6359 },
6360 )
6361 })?;
6362 let layout =
6363 TensorLayout::<Rank<N>>::compact(shape).map_err(|err| tensor_layout_error(op, err))?;
6364 let owned = self.group;
6365 Ok(TypedTensor {
6366 group: OwnedTensorGroup {
6367 group: owned.group,
6368 slot: owned.slot,
6369 allocation_index: owned.allocation_index,
6370 host_ptr: owned.host_ptr,
6371 host_byte_len: owned.host_byte_len,
6372 _rank: PhantomData,
6373 },
6374 layout,
6375 placement: self.placement,
6376 _scalar: PhantomData,
6377 })
6378 }
6379
6380 /// Number of elements in the tensor.
6381 ///
6382 /// # Examples
6383 ///
6384 /// ```rust
6385 /// use tenferro_tensor::TypedTensor;
6386 ///
6387 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
6388 /// assert_eq!(t.n_elements(), 6);
6389 /// ```
6390 pub fn n_elements(&self) -> usize {
6391 // Invariant: owned tensor constructors validate compact shape length against buffer length.
6392 match try_shape_product(self.shape(), "TypedTensor::n_elements") {
6393 Ok(n) => n,
6394 Err(err) => {
6395 unreachable!("TypedTensor compact shape is validated at construction: {err}")
6396 }
6397 }
6398 }
6399
6400 /// Tensor shape.
6401 ///
6402 /// # Examples
6403 ///
6404 /// ```
6405 /// use tenferro_tensor::TypedTensor;
6406 ///
6407 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6408 /// assert_eq!(t.shape(), &[2]);
6409 /// ```
6410 pub fn shape(&self) -> &[usize] {
6411 self.layout.shape()
6412 }
6413
6414 /// Tensor rank.
6415 ///
6416 /// # Examples
6417 ///
6418 /// ```
6419 /// use tenferro_tensor::TypedTensor;
6420 ///
6421 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
6422 /// assert_eq!(t.rank(), 2);
6423 /// ```
6424 pub fn rank(&self) -> usize {
6425 self.shape().len()
6426 }
6427
6428 /// Tensor layout metadata.
6429 ///
6430 /// Owned typed tensors are always compact column-major layouts.
6431 ///
6432 /// # Examples
6433 ///
6434 /// ```
6435 /// use tenferro_tensor::TypedTensor;
6436 ///
6437 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
6438 /// assert_eq!(t.layout().strides(), &[1, 2]);
6439 /// ```
6440 pub fn layout(&self) -> &TensorLayout<R> {
6441 &self.layout
6442 }
6443
6444 /// Return the storage backing this tensor.
6445 ///
6446 /// This is an explicit storage-inspection API for backend glue and tests.
6447 /// Host value inspection should prefer [`TypedTensor::host_data`] when the
6448 /// caller requires host storage.
6449 ///
6450 /// # Panics
6451 ///
6452 /// Panics only if the typed descriptor and its single group owner are
6453 /// internally inconsistent.
6454 ///
6455 /// # Examples
6456 ///
6457 /// ```
6458 /// use tenferro_tensor::{StorageBuffer, TypedTensor};
6459 ///
6460 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6461 /// assert!(matches!(t.buffer(), StorageBuffer::Host(_)));
6462 /// ```
6463 pub fn buffer(&self) -> &StorageBuffer<T>
6464 where
6465 T: 'static,
6466 {
6467 match self
6468 .group
6469 .host_buffer::<T>()
6470 .or_else(|| self.group.backend_buffer::<T>())
6471 {
6472 Some(buffer) => buffer,
6473 None => unreachable!("typed tensor group storage mismatch"),
6474 }
6475 }
6476
6477 /// Return the provider family for this tensor when backend-owned.
6478 #[doc(hidden)]
6479 pub fn backend_family(&self) -> Option<&'static str>
6480 where
6481 T: TensorScalar + 'static,
6482 {
6483 self.as_view().backend_family()
6484 }
6485
6486 /// Return the opaque backend buffer for backend-owned tensors.
6487 #[doc(hidden)]
6488 pub fn backend_buffer(&self) -> Option<&dyn BackendStorage<T>>
6489 where
6490 T: 'static,
6491 {
6492 match self.buffer() {
6493 StorageBuffer::Host(_) => None,
6494 StorageBuffer::Backend(buffer) => Some(buffer.as_ref()),
6495 }
6496 }
6497
6498 /// Return the mutable backend buffer for an exclusive owner borrow.
6499 #[doc(hidden)]
6500 pub fn backend_buffer_mut(&mut self) -> Option<&mut dyn BackendStorage<T>>
6501 where
6502 T: 'static,
6503 {
6504 let buffer = self.group.backend_buffer_mut::<T>()?;
6505 match buffer {
6506 StorageBuffer::Host(_) => None,
6507 StorageBuffer::Backend(buffer) => Some(buffer.as_mut()),
6508 }
6509 }
6510
6511 /// Prepare this backend tensor for one provider-native read binding.
6512 #[doc(hidden)]
6513 pub fn prepare_device_read(
6514 &self,
6515 op: &'static str,
6516 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
6517 where
6518 T: TensorScalar + 'static,
6519 {
6520 self.group
6521 .prepare_device_read_for_layout::<T>(&self.layout)
6522 .map_err(|error| crate::Error::runtime_state(op, error.to_string()))
6523 }
6524
6525 /// Prepare this backend tensor for one provider-native write binding.
6526 #[doc(hidden)]
6527 pub fn prepare_device_write(
6528 &mut self,
6529 op: &'static str,
6530 ) -> crate::Result<Box<dyn PreparedDeviceAccess + '_>>
6531 where
6532 T: TensorScalar + 'static,
6533 {
6534 let layout = self.layout.clone();
6535 self.group
6536 .prepare_device_write_for_layout::<T>(&layout)
6537 .map_err(|error| crate::Error::runtime_state(op, error.to_string()))
6538 }
6539
6540 pub(crate) fn buffer_len(&self) -> usize
6541 where
6542 T: 'static,
6543 {
6544 self.group
6545 .group
6546 .descriptor_len(self.group.slot)
6547 .unwrap_or_else(|| unreachable!("typed tensor group descriptor mismatch"))
6548 }
6549
6550 /// Return the shared-allocation domain carried by the backend buffer.
6551 ///
6552 /// # Examples
6553 ///
6554 /// ```rust
6555 /// use tenferro_tensor::TypedTensor;
6556 ///
6557 /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
6558 /// assert_eq!(tensor.allocation_domain(), None);
6559 /// # Ok::<(), tenferro_tensor::Error>(())
6560 /// ```
6561 pub fn allocation_domain(&self) -> Option<AllocationDomainId>
6562 where
6563 T: 'static,
6564 {
6565 self.group
6566 .group
6567 .backend_identity(self.group.slot)
6568 .map(|(domain, _)| domain)
6569 }
6570
6571 /// Return the stable physical backend allocation identity.
6572 ///
6573 /// # Examples
6574 ///
6575 /// ```rust
6576 /// use tenferro_tensor::TypedTensor;
6577 ///
6578 /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
6579 /// assert_eq!(tensor.allocation_id(), None);
6580 /// # Ok::<(), tenferro_tensor::Error>(())
6581 /// ```
6582 pub fn allocation_id(&self) -> Option<AllocationId>
6583 where
6584 T: 'static,
6585 {
6586 self.group
6587 .group
6588 .backend_identity(self.group.slot)
6589 .map(|(_, allocation)| allocation)
6590 }
6591
6592 /// Return placement metadata for this tensor.
6593 ///
6594 /// # Examples
6595 ///
6596 /// ```
6597 /// use tenferro_tensor::{MemoryKind, TypedTensor};
6598 ///
6599 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
6600 /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
6601 /// ```
6602 pub fn placement(&self) -> &Placement {
6603 &self.placement
6604 }
6605
6606 /// Replace placement metadata without changing the storage buffer.
6607 ///
6608 /// # Examples
6609 ///
6610 /// ```
6611 /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
6612 ///
6613 /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
6614 /// t.set_placement(Placement {
6615 /// memory_kind: MemoryKind::PinnedHost,
6616 /// device: None,
6617 /// cpu_affinity: None,
6618 /// });
6619 /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
6620 /// ```
6621 pub fn set_placement(&mut self, placement: Placement) {
6622 self.placement = placement;
6623 }
6624
6625 /// Replace only CPU routing/locality metadata without changing storage.
6626 ///
6627 /// Device, memory kind, backend allocation domain, and allocation identity
6628 /// remain unchanged.
6629 ///
6630 /// # Examples
6631 ///
6632 /// ```rust
6633 /// use tenferro_tensor::{CpuDomainId, TypedTensor};
6634 ///
6635 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0])?;
6636 /// tensor.set_cpu_affinity(Some(CpuDomainId::new(4)));
6637 /// assert_eq!(tensor.placement().cpu_affinity, Some(CpuDomainId::new(4)));
6638 /// # Ok::<(), tenferro_tensor::Error>(())
6639 /// ```
6640 pub fn set_cpu_affinity(&mut self, cpu_affinity: Option<CpuDomainId>) {
6641 self.placement.cpu_affinity = cpu_affinity;
6642 }
6643
6644 /// Borrow this tensor as a typed view preserving rank and layout metadata.
6645 ///
6646 /// # Panics
6647 ///
6648 /// Panics only if the typed descriptor and its single group owner are
6649 /// internally inconsistent.
6650 ///
6651 /// # Examples
6652 ///
6653 /// ```rust
6654 /// use tenferro_tensor::{Rank, TypedTensor};
6655 ///
6656 /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
6657 /// let view = tensor.as_view();
6658 /// assert_eq!(view.strides(), &[1, 2]);
6659 /// ```
6660 pub fn as_view(&self) -> TypedTensorView<'_, T, R>
6661 where
6662 T: TensorScalar + 'static,
6663 {
6664 let root = match self.group.view::<T>() {
6665 Ok(root) => root,
6666 Err(error) => unreachable!("typed tensor group descriptor mismatch: {error}"),
6667 };
6668 let buffer = if let Some(allocation) = root.backend_allocation() {
6669 TensorStorageRef::Root(allocation)
6670 } else {
6671 TensorStorageRef::Host(self.group_host_slice())
6672 };
6673 let root = Some(root);
6674 TypedTensorView {
6675 buffer,
6676 root,
6677 layout: self.layout.clone(),
6678 placement: self.placement.clone(),
6679 }
6680 }
6681
6682 /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
6683 ///
6684 /// # Panics
6685 ///
6686 /// Panics only if the typed descriptor and its single group owner are
6687 /// internally inconsistent.
6688 ///
6689 /// # Examples
6690 ///
6691 /// ```rust
6692 /// use tenferro_tensor::TypedTensor;
6693 ///
6694 /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
6695 /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
6696 /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
6697 /// ```
6698 pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
6699 where
6700 T: TensorScalar + 'static,
6701 {
6702 let layout = self.layout.clone();
6703 let placement = self.placement.clone();
6704 let mut root = match self.group.view_mut::<T>() {
6705 Ok(root) => root,
6706 Err(error) => unreachable!("typed tensor group descriptor mismatch: {error}"),
6707 };
6708 let buffer = if let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut() {
6709 TensorStorageRefMut::Backend(buffer.as_mut())
6710 } else {
6711 TensorStorageRefMut::Host(match root.host_slice_mut() {
6712 Ok(slice) => slice,
6713 Err(error) => {
6714 unreachable!("typed tensor group descriptor is not host-backed: {error}")
6715 }
6716 })
6717 };
6718 TypedTensorViewMut {
6719 buffer,
6720 root: Some(root),
6721 layout,
6722 placement,
6723 }
6724 }
6725
6726 /// Borrow a read-only strided region view over this tensor's backend
6727 /// (device) buffer from explicit layout metadata.
6728 ///
6729 /// This is a metadata-only view: no data is copied or transferred. The
6730 /// layout's reachable element span is validated against the backend
6731 /// buffer's physical length. Host-backed tensors are rejected with an
6732 /// explicit backend error; host regions are expressed with
6733 /// [`TypedTensorView::from_slice`] over host storage instead.
6734 ///
6735 /// # Examples
6736 ///
6737 /// ```rust
6738 /// use tenferro_tensor::TypedTensor;
6739 ///
6740 /// // Host tensors are rejected: this constructor is for backend buffers.
6741 /// let host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
6742 /// let err = host.backend_region_view(vec![2, 2], vec![1, 2], 0).unwrap_err();
6743 /// assert!(err.to_string().contains("backend"));
6744 /// ```
6745 /// # Errors
6746 ///
6747 /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
6748 /// backend region views require a backend buffer. It returns
6749 /// [`crate::Error::Validation`] with
6750 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
6751 /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
6752 /// when the region exceeds the backend buffer, or
6753 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
6754 /// arithmetic overflow.
6755 pub fn backend_region_view(
6756 &self,
6757 shape: Vec<usize>,
6758 strides: Vec<isize>,
6759 offset: isize,
6760 ) -> crate::Result<TypedTensorView<'_, T, DynRank>>
6761 where
6762 T: TensorScalar + 'static,
6763 {
6764 let op = "TypedTensor::backend_region_view";
6765 let root = self.group.view_dyn::<T>()?;
6766 let Some(allocation) = root.backend_allocation() else {
6767 return Err(crate::Error::runtime_state(
6768 op,
6769 "expected a backend (device) allocation; host tensors use \
6770 TypedTensorView::from_slice over host storage",
6771 ));
6772 };
6773 let element_len = allocation
6774 .root_extent()
6775 .byte_len()
6776 .checked_div(size_of::<T>())
6777 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
6778 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, element_len)
6779 .map_err(|err| tensor_layout_error(op, err))?;
6780 Ok(TypedTensorView {
6781 buffer: TensorStorageRef::Root(allocation),
6782 root: Some(root),
6783 layout,
6784 placement: self.placement.clone(),
6785 })
6786 }
6787
6788 /// Borrow a mutable strided region view over this tensor's backend
6789 /// (device) buffer from explicit layout metadata.
6790 ///
6791 /// This is the mutable counterpart of
6792 /// [`TypedTensor::backend_region_view`]. The layout's reachable element
6793 /// span is validated against the backend buffer's physical length, and
6794 /// layouts whose logical elements alias the same physical element are
6795 /// rejected. Host-backed tensors are rejected with an explicit backend
6796 /// error; mutable host regions must go through
6797 /// [`TypedTensorViewMut::try_multi_slice_mut`] or host constructors.
6798 ///
6799 /// The returned view borrows the tensor's backend owner exclusively for its
6800 /// lifetime. This keeps write authority tied to the owner; a second mutable
6801 /// region view must be created only after the first borrow ends.
6802 ///
6803 /// # Examples
6804 ///
6805 /// ```rust
6806 /// use tenferro_tensor::TypedTensor;
6807 ///
6808 /// // Host tensors are rejected: this constructor is for backend buffers.
6809 /// let mut host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
6810 /// let err = host.backend_region_view_mut(vec![2, 2], vec![1, 2], 0).unwrap_err();
6811 /// assert!(err.to_string().contains("backend"));
6812 /// ```
6813 /// # Errors
6814 ///
6815 /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
6816 /// mutable backend region views require a backend buffer. It returns
6817 /// [`crate::Error::Validation`] with
6818 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
6819 /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
6820 /// when the region exceeds the backend buffer,
6821 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
6822 /// logical elements alias, or
6823 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
6824 /// arithmetic overflow.
6825 pub fn backend_region_view_mut(
6826 &mut self,
6827 shape: Vec<usize>,
6828 strides: Vec<isize>,
6829 offset: isize,
6830 ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>>
6831 where
6832 T: TensorScalar + 'static,
6833 {
6834 let op = "TypedTensor::backend_region_view_mut";
6835 let mut root = self.group.view_mut_dyn::<T>()?;
6836 let Some(StorageBuffer::Backend(buffer)) = root.backend_buffer_mut() else {
6837 return Err(crate::Error::runtime_state(
6838 op,
6839 "expected a backend (device) buffer; mutable host regions use \
6840 TypedTensorViewMut host constructors or try_multi_slice_mut",
6841 ));
6842 };
6843 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
6844 .map_err(|err| tensor_layout_error(op, err))?;
6845 layout
6846 .validate_mutable_no_overlap()
6847 .map_err(|err| tensor_layout_error(op, err))?;
6848 Ok(TypedTensorViewMut {
6849 buffer: TensorStorageRefMut::Backend(buffer.as_mut()),
6850 root: Some(root),
6851 layout,
6852 placement: self.placement.clone(),
6853 })
6854 }
6855
6856 /// Consume this tensor and return its layout metadata.
6857 ///
6858 /// # Examples
6859 ///
6860 /// ```
6861 /// use tenferro_tensor::TypedTensor;
6862 ///
6863 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6864 /// assert!(t.into_layout().is_compact_col_major().unwrap());
6865 /// ```
6866 pub fn into_layout(self) -> TensorLayout<R> {
6867 self.layout
6868 }
6869
6870 /// Consume this tensor and return its storage, layout, and placement.
6871 ///
6872 /// # Examples
6873 ///
6874 /// ```
6875 /// use tenferro_tensor::{StorageBuffer, TypedTensor};
6876 ///
6877 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6878 /// let (buffer, layout, placement) = t.into_parts();
6879 /// assert!(matches!(buffer, StorageBuffer::Host(_)));
6880 /// assert_eq!(layout.shape(), &[2]);
6881 /// assert!(placement.device.is_none());
6882 /// ```
6883 pub fn into_parts(self) -> (StorageBuffer<T>, TensorLayout<R>, Placement)
6884 where
6885 T: TensorScalar,
6886 {
6887 let TypedTensor {
6888 group,
6889 layout,
6890 placement,
6891 ..
6892 } = self;
6893 let buffer = match group.into_host_vec::<T>() {
6894 Ok(data) => StorageBuffer::Host(data),
6895 Err(_) => StorageBuffer::Host(Vec::new()),
6896 };
6897 (buffer, layout, placement)
6898 }
6899}
6900
6901impl<T: TensorScalar, R: TensorRank> TypedTensor<T, R> {
6902 /// Create a tensor from a column-major buffer.
6903 ///
6904 /// # Examples
6905 ///
6906 /// ```
6907 /// use tenferro_tensor::TypedTensor;
6908 ///
6909 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
6910 /// assert_eq!(t.get(&[1, 0])?, &2.0);
6911 /// # Ok::<(), tenferro_tensor::Error>(())
6912 /// ```
6913 /// # Errors
6914 ///
6915 /// Returns [`crate::Error::Validation`] with
6916 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
6917 /// the shape product differs from `data.len()`, or
6918 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
6919 /// arithmetic overflows.
6920 pub fn from_vec_col_major(shape: impl Into<R::Shape>, data: Vec<T>) -> crate::Result<Self> {
6921 typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
6922 }
6923
6924 /// Make an explicit owning copy of this tensor.
6925 ///
6926 /// Host storage is copied into a fresh allocation. Backend-owned storage
6927 /// must be duplicated by the active backend, so this generic tensor layer
6928 /// reports that operation as unsupported.
6929 ///
6930 /// # Errors
6931 ///
6932 /// Returns [`crate::Error::RuntimeState`] when host data cannot be
6933 /// borrowed, or [`ValidationError::InvalidArgument`] when the new group
6934 /// cannot be constructed.
6935 pub fn duplicate(&self) -> crate::Result<Self> {
6936 self.as_view().duplicate()
6937 }
6938
6939 /// Consume this tensor and return its owned column-major host buffer.
6940 ///
6941 /// # Examples
6942 ///
6943 /// ```
6944 /// use tenferro_tensor::TypedTensor;
6945 ///
6946 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6947 /// let (shape, data) = t.into_vec_col_major().unwrap();
6948 /// assert_eq!(shape, vec![2]);
6949 /// assert_eq!(data, vec![1.0, 2.0]);
6950 /// ```
6951 /// # Errors
6952 ///
6953 /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
6954 /// storage; download it before exporting a host `Vec`.
6955 pub fn into_vec_col_major(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
6956 let shape = self.shape().to_vec();
6957 if self.group.backend_buffer::<T>().is_some() {
6958 return Err(crate::Error::runtime_state(
6959 "into_vec_col_major",
6960 "backend buffers cannot be exported as host Vec",
6961 ));
6962 }
6963 Ok((shape, self.group.into_host_vec::<T>()?))
6964 }
6965
6966 /// Consume this tensor and return its owned host data without rebuilding
6967 /// shape metadata. This is intended for ownership-preserving buffer-pool
6968 /// handoff; callers that need the shape should use [`Self::into_vec_col_major`].
6969 ///
6970 /// # Errors
6971 ///
6972 /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
6973 /// storage.
6974 pub fn into_host_vec(self) -> crate::Result<Vec<T>> {
6975 if self.group.backend_buffer::<T>().is_some() {
6976 return Err(crate::Error::runtime_state(
6977 "into_host_vec",
6978 "backend buffers cannot be exported as host Vec",
6979 ));
6980 }
6981 self.group.into_host_vec::<T>()
6982 }
6983
6984 /// Borrow the host buffer.
6985 ///
6986 /// # Examples
6987 ///
6988 /// ```rust
6989 /// use tenferro_tensor::TypedTensor;
6990 ///
6991 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
6992 /// assert_eq!(t.host_data()?, &[1.0, 2.0]);
6993 /// # Ok::<(), tenferro_tensor::Error>(())
6994 /// ```
6995 /// # Errors
6996 ///
6997 /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
6998 /// storage; download it before borrowing host data.
6999 pub fn host_data(&self) -> crate::Result<&[T]> {
7000 self.group.host_slice::<T>()
7001 }
7002
7003 /// Borrow compact host-visible storage through one synchronization guard.
7004 #[doc(hidden)]
7005 pub fn with_host_read<U>(&self, f: impl FnOnce(&[T]) -> U) -> crate::Result<U>
7006 where
7007 T: TensorScalar + 'static,
7008 {
7009 let view = self
7010 .group
7011 .group
7012 .view::<T, R>(self.group.slot)
7013 .map_err(|error| group_error("TypedTensor::with_host_read", error))?;
7014 let prepared = view.prepare_host_read().map_err(|error| {
7015 crate::Error::runtime_state("TypedTensor::with_host_read", error.to_string())
7016 })?;
7017 let slice = prepared.as_slice().ok_or_else(|| {
7018 crate::Error::unsupported(
7019 "TypedTensor::with_host_read",
7020 "host guard access requires a compact descriptor",
7021 )
7022 })?;
7023 Ok(f(slice))
7024 }
7025
7026 /// Borrow compact host-visible storage through one exclusive write guard.
7027 #[doc(hidden)]
7028 pub fn with_host_write<U>(&mut self, f: impl FnOnce(&mut [T]) -> U) -> crate::Result<U>
7029 where
7030 T: TensorScalar + 'static,
7031 {
7032 let mut view = self
7033 .group
7034 .group
7035 .view_mut::<T, R>(self.group.slot)
7036 .map_err(|error| group_error("TypedTensor::with_host_write", error))?;
7037 let mut prepared = view.prepare_host_write().map_err(|error| {
7038 crate::Error::runtime_state("TypedTensor::with_host_write", error.to_string())
7039 })?;
7040 let slice = prepared.as_slice_mut().ok_or_else(|| {
7041 crate::Error::unsupported(
7042 "TypedTensor::with_host_write",
7043 "host guard access requires a compact descriptor",
7044 )
7045 })?;
7046 Ok(f(slice))
7047 }
7048
7049 /// View the tensor data as a flat slice.
7050 ///
7051 /// This is an alias for `host_data()` for API consistency with
7052 /// `Tensor::as_slice`.
7053 ///
7054 /// # Examples
7055 ///
7056 /// ```
7057 /// use tenferro_tensor::TypedTensor;
7058 ///
7059 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7060 /// assert_eq!(t.as_slice()?, &[1.0, 2.0]);
7061 /// # Ok::<(), tenferro_tensor::Error>(())
7062 /// ```
7063 /// # Errors
7064 ///
7065 /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7066 /// storage; download it before borrowing it as a host slice.
7067 pub fn as_slice(&self) -> crate::Result<&[T]> {
7068 self.host_data()
7069 }
7070
7071 /// Mutably borrow the host buffer.
7072 ///
7073 /// # Examples
7074 ///
7075 /// ```rust
7076 /// use tenferro_tensor::TypedTensor;
7077 ///
7078 /// let mut t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7079 /// t.host_data_mut()?[0] = 3.0;
7080 /// assert_eq!(t.host_data()?, &[3.0, 0.0]);
7081 /// # Ok::<(), tenferro_tensor::Error>(())
7082 /// ```
7083 /// # Errors
7084 ///
7085 /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
7086 /// storage; download it before mutably borrowing host data.
7087 pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
7088 self.group.host_slice_mut::<T>()
7089 }
7090
7091 fn group_host_slice(&self) -> &[T] {
7092 self.group
7093 .view::<T>()
7094 .ok()
7095 .and_then(|view| view.host_slice().ok())
7096 .unwrap_or_default()
7097 }
7098
7099 fn group_host_slice_mut(&mut self) -> &mut [T] {
7100 self.group
7101 .view_mut::<T>()
7102 .ok()
7103 .and_then(|mut view| view.host_slice_mut().ok())
7104 .unwrap_or_default()
7105 }
7106
7107 /// Compute the linear physical-buffer offset for a logical index.
7108 ///
7109 /// # Examples
7110 ///
7111 /// ```rust
7112 /// use tenferro_tensor::TypedTensor;
7113 ///
7114 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
7115 /// assert_eq!(t.linear_offset(&[1, 2])?, 5);
7116 /// # Ok::<(), tenferro_tensor::Error>(())
7117 /// ```
7118 /// # Errors
7119 ///
7120 /// Returns [`crate::Error::Validation`] with
7121 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7122 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7123 /// when an index is outside its axis extent, or
7124 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7125 /// arithmetic overflows.
7126 pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7127 try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
7128 }
7129
7130 /// Compute the physical element offset for a logical index.
7131 ///
7132 /// # Examples
7133 ///
7134 /// ```rust
7135 /// use tenferro_tensor::TypedTensor;
7136 ///
7137 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
7138 /// assert_eq!(t.layout_linear_offset(&[1, 2])?, 5);
7139 /// # Ok::<(), tenferro_tensor::Error>(())
7140 /// ```
7141 /// # Errors
7142 ///
7143 /// Returns [`crate::Error::Validation`] with
7144 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7145 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7146 /// when an index is outside its axis extent, or
7147 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
7148 /// arithmetic overflows.
7149 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
7150 try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::layout_linear_offset")
7151 }
7152
7153 /// Return whether this owned tensor is compact column-major.
7154 ///
7155 /// # Examples
7156 ///
7157 /// ```rust
7158 /// use tenferro_tensor::TypedTensor;
7159 ///
7160 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7161 /// assert!(t.is_col_major_contiguous()?);
7162 /// # Ok::<(), tenferro_tensor::Error>(())
7163 /// ```
7164 /// # Errors
7165 ///
7166 /// Returns [`crate::Error::Validation`] with
7167 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7168 /// compactness arithmetic overflows.
7169 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
7170 self.layout
7171 .is_compact_col_major()
7172 .map_err(|err| tensor_layout_error("TypedTensor::is_col_major_contiguous", err))
7173 }
7174
7175 /// Return a compact string summary of this tensor's layout metadata.
7176 ///
7177 /// # Examples
7178 ///
7179 /// ```rust
7180 /// use tenferro_tensor::TypedTensor;
7181 ///
7182 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7183 /// assert!(t.layout_summary().contains("shape=[2]"));
7184 /// # Ok::<(), tenferro_tensor::Error>(())
7185 /// ```
7186 pub fn layout_summary(&self) -> String {
7187 layout_summary(self.shape(), self.layout.strides(), self.layout.offset())
7188 }
7189
7190 /// Assert this tensor is compact column-major.
7191 ///
7192 /// # Examples
7193 ///
7194 /// ```rust
7195 /// use tenferro_tensor::TypedTensor;
7196 ///
7197 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
7198 /// t.assert_col_major_contiguous()?;
7199 /// # Ok::<(), tenferro_tensor::Error>(())
7200 /// ```
7201 /// # Errors
7202 ///
7203 /// Returns [`crate::Error::Validation`] with
7204 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
7205 /// compactness arithmetic overflows, or
7206 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
7207 /// tensor is not compact column-major.
7208 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
7209 assert_layout_col_major_contiguous(
7210 self.is_col_major_contiguous()?,
7211 self.shape(),
7212 self.layout.strides(),
7213 self.layout.offset(),
7214 "TypedTensor::assert_col_major_contiguous",
7215 )
7216 }
7217
7218 /// Borrow a single element by multi-index.
7219 ///
7220 /// # Examples
7221 ///
7222 /// ```rust
7223 /// use tenferro_tensor::TypedTensor;
7224 ///
7225 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
7226 /// assert_eq!(t.get(&[1])?, &2.0);
7227 /// # Ok::<(), tenferro_tensor::Error>(())
7228 /// ```
7229 /// # Errors
7230 ///
7231 /// Returns [`crate::Error::Validation`] with
7232 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7233 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7234 /// when an index is outside its axis extent, or
7235 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7236 /// computed offset is outside the host buffer. It returns
7237 /// [`crate::Error::RuntimeState`] when the tensor uses backend storage.
7238 pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
7239 let off = self.linear_offset(indices)?;
7240 self.host_data()?.get(off).ok_or_else(|| {
7241 crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
7242 })
7243 }
7244
7245 /// Mutably borrow a single element by multi-index.
7246 ///
7247 /// # Examples
7248 ///
7249 /// ```rust
7250 /// use tenferro_tensor::TypedTensor;
7251 ///
7252 /// let mut t = TypedTensor::<f64>::zeros(vec![1]).unwrap();
7253 /// *t.get_mut(&[0])? = 7.0;
7254 /// assert_eq!(t.host_data()?, &[7.0]);
7255 /// # Ok::<(), tenferro_tensor::Error>(())
7256 /// ```
7257 /// # Errors
7258 ///
7259 /// Returns [`crate::Error::Validation`] with
7260 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
7261 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
7262 /// when an index is outside its axis extent, or
7263 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
7264 /// computed offset is outside the host buffer. It returns
7265 /// [`crate::Error::RuntimeState`] when the tensor uses backend storage.
7266 pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
7267 let off = self.linear_offset(indices)?;
7268 self.host_data_mut()?.get_mut(off).ok_or_else(|| {
7269 crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
7270 })
7271 }
7272}
7273
7274impl<R: TensorRank> TypedTensor<Complex32, R> {
7275 /// Borrow this tensor as an interleaved `f32` view without copying.
7276 ///
7277 /// # Errors
7278 ///
7279 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7280 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
7281 /// invalid tensor layout.
7282 pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f32, DynRank>> {
7283 self.as_view().as_real_view()
7284 }
7285
7286 /// Borrow this tensor mutably as an interleaved `f32` view without copying.
7287 ///
7288 /// # Errors
7289 ///
7290 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7291 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
7292 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
7293 /// invalid representation metadata.
7294 pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f32, DynRank>> {
7295 let op = "TypedTensor::as_real_view_mut";
7296 validate_representation_pair(op, DType::C32, DType::F32)?;
7297 let layout = reinterpret_complex_to_real_layout(
7298 self.shape(),
7299 self.layout.strides(),
7300 self.layout.offset(),
7301 self.buffer_len(),
7302 op,
7303 )?;
7304 layout
7305 .validate_mutable_no_overlap()
7306 .map_err(|err| tensor_layout_error(op, err))?;
7307 if self.backend_buffer().is_some() {
7308 return Err(crate::Error::unsupported(
7309 op,
7310 "backend representation reinterpretation is enabled by the provider phases",
7311 ));
7312 }
7313 let placement = self.placement.clone();
7314 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex32, f32>(
7315 self.group_host_slice_mut(),
7316 op,
7317 )?);
7318 Ok(TypedTensorViewMut {
7319 buffer,
7320 root: None,
7321 layout,
7322 placement,
7323 })
7324 }
7325
7326 /// Consume this tensor and reinterpret its owner as `f32` without copying.
7327 ///
7328 /// A failed operation returns the unchanged owner through
7329 /// [`ReinterpretError::into_owner`].
7330 ///
7331 /// # Errors
7332 ///
7333 /// Returns [`ReinterpretError::error`] containing
7334 /// [`ValidationError::InvalidArgument`] or
7335 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7336 /// owner.
7337 pub fn into_real(self) -> Result<TypedTensor<f32, DynRank>, ReinterpretError<Self>> {
7338 let op = "TypedTensor::into_real";
7339 if let Err(error) = validate_representation_pair(op, DType::C32, DType::F32) {
7340 return Err(ReinterpretError::new(self, error));
7341 }
7342 let source_shape = self.shape().to_vec();
7343 let source_strides = self.layout.strides().to_vec();
7344 let source_offset = self.layout.offset();
7345 let target_layout = match reinterpret_complex_to_real_layout(
7346 &source_shape,
7347 &source_strides,
7348 source_offset,
7349 self.buffer_len(),
7350 op,
7351 ) {
7352 Ok(layout) => layout,
7353 Err(error) => return Err(ReinterpretError::new(self, error)),
7354 };
7355 let TypedTensor {
7356 group,
7357 layout: source_layout,
7358 placement,
7359 ..
7360 } = self;
7361 match group.reinterpret::<Complex32, f32>(
7362 target_layout.shape().to_vec(),
7363 target_layout.strides().to_vec(),
7364 target_layout.offset(),
7365 ) {
7366 Ok(group) => Ok(TypedTensor {
7367 group,
7368 layout: target_layout,
7369 placement,
7370 _scalar: PhantomData,
7371 }),
7372 Err((group, error)) => Err(ReinterpretError::new(
7373 TypedTensor {
7374 group,
7375 layout: source_layout,
7376 placement,
7377 _scalar: PhantomData,
7378 },
7379 error,
7380 )),
7381 }
7382 }
7383}
7384
7385impl<R: TensorRank> TypedTensor<Complex64, R> {
7386 /// Borrow this tensor as an interleaved `f64` view without copying.
7387 ///
7388 /// # Errors
7389 ///
7390 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7391 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
7392 /// invalid tensor layout.
7393 pub fn as_real_view(&self) -> crate::Result<TypedTensorView<'_, f64, DynRank>> {
7394 self.as_view().as_real_view()
7395 }
7396
7397 /// Borrow this tensor mutably as an interleaved `f64` view without copying.
7398 ///
7399 /// # Errors
7400 ///
7401 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7402 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
7403 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
7404 /// invalid representation metadata.
7405 pub fn as_real_view_mut(&mut self) -> crate::Result<TypedTensorViewMut<'_, f64, DynRank>> {
7406 let op = "TypedTensor::as_real_view_mut";
7407 validate_representation_pair(op, DType::C64, DType::F64)?;
7408 let layout = reinterpret_complex_to_real_layout(
7409 self.shape(),
7410 self.layout.strides(),
7411 self.layout.offset(),
7412 self.buffer_len(),
7413 op,
7414 )?;
7415 layout
7416 .validate_mutable_no_overlap()
7417 .map_err(|err| tensor_layout_error(op, err))?;
7418 if self.backend_buffer().is_some() {
7419 return Err(crate::Error::unsupported(
7420 op,
7421 "backend representation reinterpretation is enabled by the provider phases",
7422 ));
7423 }
7424 let placement = self.placement.clone();
7425 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<Complex64, f64>(
7426 self.group_host_slice_mut(),
7427 op,
7428 )?);
7429 Ok(TypedTensorViewMut {
7430 buffer,
7431 root: None,
7432 layout,
7433 placement,
7434 })
7435 }
7436
7437 /// Consume this tensor and reinterpret its owner as `f64` without copying.
7438 ///
7439 /// A failed operation returns the unchanged owner through
7440 /// [`ReinterpretError::into_owner`].
7441 ///
7442 /// # Errors
7443 ///
7444 /// Returns [`ReinterpretError::error`] containing
7445 /// [`ValidationError::InvalidArgument`] or
7446 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7447 /// owner.
7448 pub fn into_real(self) -> Result<TypedTensor<f64, DynRank>, ReinterpretError<Self>> {
7449 let op = "TypedTensor::into_real";
7450 if let Err(error) = validate_representation_pair(op, DType::C64, DType::F64) {
7451 return Err(ReinterpretError::new(self, error));
7452 }
7453 let source_shape = self.shape().to_vec();
7454 let source_strides = self.layout.strides().to_vec();
7455 let source_offset = self.layout.offset();
7456 let target_layout = match reinterpret_complex_to_real_layout(
7457 &source_shape,
7458 &source_strides,
7459 source_offset,
7460 self.buffer_len(),
7461 op,
7462 ) {
7463 Ok(layout) => layout,
7464 Err(error) => return Err(ReinterpretError::new(self, error)),
7465 };
7466 let TypedTensor {
7467 group,
7468 layout: source_layout,
7469 placement,
7470 ..
7471 } = self;
7472 match group.reinterpret::<Complex64, f64>(
7473 target_layout.shape().to_vec(),
7474 target_layout.strides().to_vec(),
7475 target_layout.offset(),
7476 ) {
7477 Ok(group) => Ok(TypedTensor {
7478 group,
7479 layout: target_layout,
7480 placement,
7481 _scalar: PhantomData,
7482 }),
7483 Err((group, error)) => Err(ReinterpretError::new(
7484 TypedTensor {
7485 group,
7486 layout: source_layout,
7487 placement,
7488 _scalar: PhantomData,
7489 },
7490 error,
7491 )),
7492 }
7493 }
7494}
7495
7496impl<R: TensorRank> TypedTensor<f32, R> {
7497 /// Borrow this tensor as a complex view without copying.
7498 ///
7499 /// # Errors
7500 ///
7501 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7502 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
7503 /// invalid tensor layout.
7504 pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex32, DynRank>> {
7505 self.as_view().as_complex_view()
7506 }
7507
7508 /// Borrow this tensor mutably as a complex view without copying.
7509 ///
7510 /// # Errors
7511 ///
7512 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7513 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
7514 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
7515 /// invalid representation metadata.
7516 pub fn as_complex_view_mut(
7517 &mut self,
7518 ) -> crate::Result<TypedTensorViewMut<'_, Complex32, DynRank>> {
7519 let op = "TypedTensor::as_complex_view_mut";
7520 validate_representation_pair(op, DType::F32, DType::C32)?;
7521 let layout = reinterpret_real_to_complex_layout(
7522 self.shape(),
7523 self.layout.strides(),
7524 self.layout.offset(),
7525 self.buffer_len(),
7526 op,
7527 )?;
7528 layout
7529 .validate_mutable_no_overlap()
7530 .map_err(|err| tensor_layout_error(op, err))?;
7531 if self.backend_buffer().is_some() {
7532 return Err(crate::Error::unsupported(
7533 op,
7534 "backend representation reinterpretation is enabled by the provider phases",
7535 ));
7536 }
7537 let placement = self.placement.clone();
7538 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f32, Complex32>(
7539 self.group_host_slice_mut(),
7540 op,
7541 )?);
7542 Ok(TypedTensorViewMut {
7543 buffer,
7544 root: None,
7545 layout,
7546 placement,
7547 })
7548 }
7549
7550 /// Consume this tensor and reinterpret its owner as `Complex32` without copying.
7551 ///
7552 /// The compact source must have an even physical element count. A failed
7553 /// operation returns the unchanged owner through
7554 /// [`ReinterpretError::into_owner`].
7555 ///
7556 /// # Errors
7557 ///
7558 /// Returns [`ReinterpretError::error`] containing
7559 /// [`ValidationError::InvalidArgument`] or
7560 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7561 /// owner.
7562 pub fn into_complex(self) -> Result<TypedTensor<Complex32, DynRank>, ReinterpretError<Self>> {
7563 let op = "TypedTensor::into_complex";
7564 if let Err(error) = validate_representation_pair(op, DType::F32, DType::C32) {
7565 return Err(ReinterpretError::new(self, error));
7566 }
7567 if !self.buffer_len().is_multiple_of(2) {
7568 return Err(ReinterpretError::new(
7569 self,
7570 crate::Error::invalid_argument(
7571 op,
7572 "buffer",
7573 "the owned real buffer must contain an even number of elements",
7574 ),
7575 ));
7576 }
7577 let source_shape = self.shape().to_vec();
7578 let source_strides = self.layout.strides().to_vec();
7579 let source_offset = self.layout.offset();
7580 let target_layout = match reinterpret_real_to_complex_layout(
7581 &source_shape,
7582 &source_strides,
7583 source_offset,
7584 self.buffer_len(),
7585 op,
7586 ) {
7587 Ok(layout) => layout,
7588 Err(error) => return Err(ReinterpretError::new(self, error)),
7589 };
7590 let TypedTensor {
7591 group,
7592 layout: source_layout,
7593 placement,
7594 ..
7595 } = self;
7596 match group.reinterpret::<f32, Complex32>(
7597 target_layout.shape().to_vec(),
7598 target_layout.strides().to_vec(),
7599 target_layout.offset(),
7600 ) {
7601 Ok(group) => Ok(TypedTensor {
7602 group,
7603 layout: target_layout,
7604 placement,
7605 _scalar: PhantomData,
7606 }),
7607 Err((group, error)) => Err(ReinterpretError::new(
7608 TypedTensor {
7609 group,
7610 layout: source_layout,
7611 placement,
7612 _scalar: PhantomData,
7613 },
7614 error,
7615 )),
7616 }
7617 }
7618}
7619
7620impl<R: TensorRank> TypedTensor<f64, R> {
7621 /// Borrow this tensor as a complex view without copying.
7622 ///
7623 /// # Errors
7624 ///
7625 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7626 /// that is not supported, or [`ValidationError::ViewOutOfBounds`] for an
7627 /// invalid tensor layout.
7628 pub fn as_complex_view(&self) -> crate::Result<TypedTensorView<'_, Complex64, DynRank>> {
7629 self.as_view().as_complex_view()
7630 }
7631
7632 /// Borrow this tensor mutably as a complex view without copying.
7633 ///
7634 /// # Errors
7635 ///
7636 /// Returns [`crate::Error::Unsupported`] for backend reinterpretation
7637 /// that is not supported, [`ValidationError::OverlappingMutableLayout`]
7638 /// for a non-injective layout, or [`ValidationError::ViewOutOfBounds`] for
7639 /// invalid representation metadata.
7640 pub fn as_complex_view_mut(
7641 &mut self,
7642 ) -> crate::Result<TypedTensorViewMut<'_, Complex64, DynRank>> {
7643 let op = "TypedTensor::as_complex_view_mut";
7644 validate_representation_pair(op, DType::F64, DType::C64)?;
7645 let layout = reinterpret_real_to_complex_layout(
7646 self.shape(),
7647 self.layout.strides(),
7648 self.layout.offset(),
7649 self.buffer_len(),
7650 op,
7651 )?;
7652 layout
7653 .validate_mutable_no_overlap()
7654 .map_err(|err| tensor_layout_error(op, err))?;
7655 if self.backend_buffer().is_some() {
7656 return Err(crate::Error::unsupported(
7657 op,
7658 "backend representation reinterpretation is enabled by the provider phases",
7659 ));
7660 }
7661 let placement = self.placement.clone();
7662 let buffer = TensorStorageRefMut::Host(reinterpret_host_slice_mut::<f64, Complex64>(
7663 self.group_host_slice_mut(),
7664 op,
7665 )?);
7666 Ok(TypedTensorViewMut {
7667 buffer,
7668 root: None,
7669 layout,
7670 placement,
7671 })
7672 }
7673
7674 /// Consume this tensor and reinterpret its owner as `Complex64` without copying.
7675 ///
7676 /// The compact source must have an even physical element count. A failed
7677 /// operation returns the unchanged owner through
7678 /// [`ReinterpretError::into_owner`].
7679 ///
7680 /// # Errors
7681 ///
7682 /// Returns [`ReinterpretError::error`] containing
7683 /// [`ValidationError::InvalidArgument`] or
7684 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7685 /// owner.
7686 pub fn into_complex(self) -> Result<TypedTensor<Complex64, DynRank>, ReinterpretError<Self>> {
7687 let op = "TypedTensor::into_complex";
7688 if let Err(error) = validate_representation_pair(op, DType::F64, DType::C64) {
7689 return Err(ReinterpretError::new(self, error));
7690 }
7691 if !self.buffer_len().is_multiple_of(2) {
7692 return Err(ReinterpretError::new(
7693 self,
7694 crate::Error::invalid_argument(
7695 op,
7696 "buffer",
7697 "the owned real buffer must contain an even number of elements",
7698 ),
7699 ));
7700 }
7701 let source_shape = self.shape().to_vec();
7702 let source_strides = self.layout.strides().to_vec();
7703 let source_offset = self.layout.offset();
7704 let target_layout = match reinterpret_real_to_complex_layout(
7705 &source_shape,
7706 &source_strides,
7707 source_offset,
7708 self.buffer_len(),
7709 op,
7710 ) {
7711 Ok(layout) => layout,
7712 Err(error) => return Err(ReinterpretError::new(self, error)),
7713 };
7714 let TypedTensor {
7715 group,
7716 layout: source_layout,
7717 placement,
7718 ..
7719 } = self;
7720 match group.reinterpret::<f64, Complex64>(
7721 target_layout.shape().to_vec(),
7722 target_layout.strides().to_vec(),
7723 target_layout.offset(),
7724 ) {
7725 Ok(group) => Ok(TypedTensor {
7726 group,
7727 layout: target_layout,
7728 placement,
7729 _scalar: PhantomData,
7730 }),
7731 Err((group, error)) => Err(ReinterpretError::new(
7732 TypedTensor {
7733 group,
7734 layout: source_layout,
7735 placement,
7736 _scalar: PhantomData,
7737 },
7738 error,
7739 )),
7740 }
7741 }
7742}
7743
7744impl Tensor {
7745 /// Borrow a complex tensor as its sealed interleaved real representation.
7746 ///
7747 /// # Errors
7748 ///
7749 /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
7750 /// [`ValidationError::ViewOutOfBounds`] or
7751 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
7752 pub fn as_real_view(&self) -> crate::Result<TensorView<'_>> {
7753 match self {
7754 Tensor::C32(tensor) => tensor.as_real_view().map(TensorView::F32),
7755 Tensor::C64(tensor) => tensor.as_real_view().map(TensorView::F64),
7756 other => Err(crate::Error::unsupported_dtype_conversion(
7757 "Tensor::as_real_view",
7758 other.dtype(),
7759 DType::F32,
7760 "only complex tensors have a sealed real representation view",
7761 )),
7762 }
7763 }
7764
7765 /// Borrow a complex tensor mutably as its sealed interleaved real representation.
7766 ///
7767 /// # Errors
7768 ///
7769 /// Returns [`crate::Error::Unsupported`] for a non-complex dtype and
7770 /// [`ValidationError::ViewOutOfBounds`] or
7771 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
7772 pub fn as_real_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
7773 match self {
7774 Tensor::C32(tensor) => tensor.as_real_view_mut().map(TensorViewMut::F32),
7775 Tensor::C64(tensor) => tensor.as_real_view_mut().map(TensorViewMut::F64),
7776 other => Err(crate::Error::unsupported_dtype_conversion(
7777 "Tensor::as_real_view_mut",
7778 other.dtype(),
7779 DType::F32,
7780 "only complex tensors have a sealed real representation view",
7781 )),
7782 }
7783 }
7784
7785 /// Consume a complex tensor and reinterpret its owner as real without copying.
7786 ///
7787 /// # Errors
7788 ///
7789 /// Returns [`ReinterpretError::error`] containing
7790 /// [`ValidationError::InvalidArgument`] or
7791 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7792 /// owner.
7793 pub fn into_real(self) -> Result<Self, ReinterpretError<Self>> {
7794 match self {
7795 Tensor::C32(tensor) => tensor.into_real().map(Tensor::F32).map_err(|error| {
7796 let (owner, error) = error.into_parts();
7797 ReinterpretError::new(Tensor::C32(owner), error)
7798 }),
7799 Tensor::C64(tensor) => tensor.into_real().map(Tensor::F64).map_err(|error| {
7800 let (owner, error) = error.into_parts();
7801 ReinterpretError::new(Tensor::C64(owner), error)
7802 }),
7803 tensor => Err(ReinterpretError::new(
7804 tensor,
7805 crate::Error::unsupported(
7806 "Tensor::into_real",
7807 "only complex tensors have a sealed real representation",
7808 ),
7809 )),
7810 }
7811 }
7812
7813 /// Borrow an interleaved real tensor as its sealed complex representation.
7814 ///
7815 /// # Errors
7816 ///
7817 /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
7818 /// [`ValidationError::ViewOutOfBounds`] or
7819 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
7820 pub fn as_complex_view(&self) -> crate::Result<TensorView<'_>> {
7821 match self {
7822 Tensor::F32(tensor) => tensor.as_complex_view().map(TensorView::C32),
7823 Tensor::F64(tensor) => tensor.as_complex_view().map(TensorView::C64),
7824 other => Err(crate::Error::unsupported_dtype_conversion(
7825 "Tensor::as_complex_view",
7826 other.dtype(),
7827 DType::C32,
7828 "only real tensors can have a sealed complex representation view",
7829 )),
7830 }
7831 }
7832
7833 /// Borrow an interleaved real tensor mutably as its sealed complex representation.
7834 ///
7835 /// # Errors
7836 ///
7837 /// Returns [`crate::Error::Unsupported`] for a non-real dtype and
7838 /// [`ValidationError::ViewOutOfBounds`] or
7839 /// [`ValidationError::InvalidArgument`] for invalid layout metadata.
7840 pub fn as_complex_view_mut(&mut self) -> crate::Result<TensorViewMut<'_>> {
7841 match self {
7842 Tensor::F32(tensor) => tensor.as_complex_view_mut().map(TensorViewMut::C32),
7843 Tensor::F64(tensor) => tensor.as_complex_view_mut().map(TensorViewMut::C64),
7844 other => Err(crate::Error::unsupported_dtype_conversion(
7845 "Tensor::as_complex_view_mut",
7846 other.dtype(),
7847 DType::C32,
7848 "only real tensors can have a sealed complex representation view",
7849 )),
7850 }
7851 }
7852
7853 /// Consume a real tensor and reinterpret its owner as complex without copying.
7854 ///
7855 /// # Errors
7856 ///
7857 /// Returns [`ReinterpretError::error`] containing
7858 /// [`ValidationError::InvalidArgument`] or
7859 /// [`ValidationError::ViewOutOfBounds`] while retaining the unchanged
7860 /// owner.
7861 pub fn into_complex(self) -> Result<Self, ReinterpretError<Self>> {
7862 match self {
7863 Tensor::F32(tensor) => tensor.into_complex().map(Tensor::C32).map_err(|error| {
7864 let (owner, error) = error.into_parts();
7865 ReinterpretError::new(Tensor::F32(owner), error)
7866 }),
7867 Tensor::F64(tensor) => tensor.into_complex().map(Tensor::C64).map_err(|error| {
7868 let (owner, error) = error.into_parts();
7869 ReinterpretError::new(Tensor::F64(owner), error)
7870 }),
7871 tensor => Err(ReinterpretError::new(
7872 tensor,
7873 crate::Error::unsupported(
7874 "Tensor::into_complex",
7875 "only real tensors have a sealed complex representation",
7876 ),
7877 )),
7878 }
7879 }
7880
7881 /// Make an explicit owning copy of this dtype-erased tensor.
7882 ///
7883 /// # Errors
7884 ///
7885 /// Returns [`crate::Error::RuntimeState`] or [`crate::Error::Unsupported`]
7886 /// when the selected backend/storage owner cannot be duplicated.
7887 pub fn duplicate(&self) -> crate::Result<Self> {
7888 match self {
7889 Tensor::F32(t) => t.duplicate().map(Tensor::F32),
7890 Tensor::F64(t) => t.duplicate().map(Tensor::F64),
7891 Tensor::I32(t) => t.duplicate().map(Tensor::I32),
7892 Tensor::I64(t) => t.duplicate().map(Tensor::I64),
7893 Tensor::Bool(t) => t.duplicate().map(Tensor::Bool),
7894 Tensor::C32(t) => t.duplicate().map(Tensor::C32),
7895 Tensor::C64(t) => t.duplicate().map(Tensor::C64),
7896 }
7897 }
7898
7899 /// Create a tensor from a shape and column-major flat data.
7900 ///
7901 /// This is the `Tensor`-level equivalent of
7902 /// `TypedTensor::<T>::from_vec_col_major`.
7903 ///
7904 /// # Examples
7905 ///
7906 /// ```
7907 /// use tenferro_tensor::Tensor;
7908 ///
7909 /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
7910 /// assert_eq!(t.shape(), &[2, 2]);
7911 /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
7912 /// ```
7913 /// # Errors
7914 ///
7915 /// Returns [`crate::Error::Validation`] with
7916 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
7917 /// the shape product differs from `data.len()`, or
7918 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
7919 /// arithmetic overflows.
7920 pub fn from_vec_col_major<T: TensorScalar>(
7921 shape: impl tenferro_tensor_core::IntoShapeVec,
7922 data: Vec<T>,
7923 ) -> crate::Result<Self> {
7924 T::into_tensor(shape.into_shape_vec().to_vec(), data)
7925 }
7926
7927 /// Tensor shape.
7928 ///
7929 /// # Examples
7930 ///
7931 /// ```rust
7932 /// use tenferro_tensor::{Tensor, TypedTensor};
7933 ///
7934 /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
7935 /// assert_eq!(t.shape(), &[2]);
7936 /// ```
7937 pub fn shape(&self) -> &[usize] {
7938 match self {
7939 Tensor::F32(t) => t.shape(),
7940 Tensor::F64(t) => t.shape(),
7941 Tensor::I32(t) => t.shape(),
7942 Tensor::I64(t) => t.shape(),
7943 Tensor::Bool(t) => t.shape(),
7944 Tensor::C32(t) => t.shape(),
7945 Tensor::C64(t) => t.shape(),
7946 }
7947 }
7948
7949 /// Tensor dtype tag.
7950 ///
7951 /// # Examples
7952 ///
7953 /// ```rust
7954 /// use tenferro_tensor::{DType, Tensor, TypedTensor};
7955 ///
7956 /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
7957 /// assert_eq!(t.dtype(), DType::F64);
7958 /// ```
7959 pub fn dtype(&self) -> DType {
7960 match self {
7961 Tensor::F32(_) => DType::F32,
7962 Tensor::F64(_) => DType::F64,
7963 Tensor::I32(_) => DType::I32,
7964 Tensor::I64(_) => DType::I64,
7965 Tensor::Bool(_) => DType::Bool,
7966 Tensor::C32(_) => DType::C32,
7967 Tensor::C64(_) => DType::C64,
7968 }
7969 }
7970
7971 /// Return placement metadata for this dtype-erased tensor.
7972 ///
7973 /// # Examples
7974 ///
7975 /// ```rust
7976 /// use tenferro_tensor::{MemoryKind, Tensor};
7977 ///
7978 /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
7979 /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
7980 /// ```
7981 pub fn placement(&self) -> &Placement {
7982 match self {
7983 Tensor::F32(t) => t.placement(),
7984 Tensor::F64(t) => t.placement(),
7985 Tensor::I32(t) => t.placement(),
7986 Tensor::I64(t) => t.placement(),
7987 Tensor::Bool(t) => t.placement(),
7988 Tensor::C32(t) => t.placement(),
7989 Tensor::C64(t) => t.placement(),
7990 }
7991 }
7992
7993 /// Return whether this tensor is backed by backend-native storage.
7994 ///
7995 /// # Examples
7996 ///
7997 /// ```rust
7998 /// use tenferro_tensor::Tensor;
7999 ///
8000 /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
8001 /// assert!(!t.is_backend_buffer());
8002 /// ```
8003 pub fn is_backend_buffer(&self) -> bool {
8004 match self {
8005 Tensor::F32(t) => t.backend_family().is_some(),
8006 Tensor::F64(t) => t.backend_family().is_some(),
8007 Tensor::I32(t) => t.backend_family().is_some(),
8008 Tensor::I64(t) => t.backend_family().is_some(),
8009 Tensor::Bool(t) => t.backend_family().is_some(),
8010 Tensor::C32(t) => t.backend_family().is_some(),
8011 Tensor::C64(t) => t.backend_family().is_some(),
8012 }
8013 }
8014
8015 /// Compute the physical element offset for a logical index.
8016 ///
8017 /// # Examples
8018 ///
8019 /// ```rust
8020 /// use tenferro_tensor::Tensor;
8021 ///
8022 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8023 /// assert_eq!(t.layout_linear_offset(&[1])?, 1);
8024 /// # Ok::<(), tenferro_tensor::Error>(())
8025 /// ```
8026 /// # Errors
8027 ///
8028 /// Returns [`crate::Error::Validation`] with
8029 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
8030 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
8031 /// when an index is outside its axis extent, or
8032 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
8033 /// arithmetic overflows.
8034 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
8035 match self {
8036 Tensor::F32(t) => t.layout_linear_offset(indices),
8037 Tensor::F64(t) => t.layout_linear_offset(indices),
8038 Tensor::I32(t) => t.layout_linear_offset(indices),
8039 Tensor::I64(t) => t.layout_linear_offset(indices),
8040 Tensor::Bool(t) => t.layout_linear_offset(indices),
8041 Tensor::C32(t) => t.layout_linear_offset(indices),
8042 Tensor::C64(t) => t.layout_linear_offset(indices),
8043 }
8044 }
8045
8046 /// Return whether this tensor is compact column-major.
8047 ///
8048 /// # Examples
8049 ///
8050 /// ```rust
8051 /// use tenferro_tensor::Tensor;
8052 ///
8053 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8054 /// assert!(t.is_col_major_contiguous()?);
8055 /// # Ok::<(), tenferro_tensor::Error>(())
8056 /// ```
8057 /// # Errors
8058 ///
8059 /// Returns [`crate::Error::Validation`] with
8060 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8061 /// compactness arithmetic overflows.
8062 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
8063 match self {
8064 Tensor::F32(t) => t.is_col_major_contiguous(),
8065 Tensor::F64(t) => t.is_col_major_contiguous(),
8066 Tensor::I32(t) => t.is_col_major_contiguous(),
8067 Tensor::I64(t) => t.is_col_major_contiguous(),
8068 Tensor::Bool(t) => t.is_col_major_contiguous(),
8069 Tensor::C32(t) => t.is_col_major_contiguous(),
8070 Tensor::C64(t) => t.is_col_major_contiguous(),
8071 }
8072 }
8073
8074 /// Return a compact string summary of this tensor's layout metadata.
8075 ///
8076 /// # Examples
8077 ///
8078 /// ```rust
8079 /// use tenferro_tensor::Tensor;
8080 ///
8081 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8082 /// assert!(t.layout_summary().contains("shape=[2]"));
8083 /// # Ok::<(), tenferro_tensor::Error>(())
8084 /// ```
8085 pub fn layout_summary(&self) -> String {
8086 let layout = tensor_layout(self);
8087 layout_summary(layout.shape(), layout.strides(), layout.offset())
8088 }
8089
8090 /// Assert this tensor is compact column-major.
8091 ///
8092 /// # Examples
8093 ///
8094 /// ```rust
8095 /// use tenferro_tensor::Tensor;
8096 ///
8097 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
8098 /// t.assert_col_major_contiguous()?;
8099 /// # Ok::<(), tenferro_tensor::Error>(())
8100 /// ```
8101 /// # Errors
8102 ///
8103 /// Returns [`crate::Error::Validation`] with
8104 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
8105 /// compactness arithmetic overflows, or
8106 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
8107 /// tensor is not compact column-major.
8108 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
8109 let layout = tensor_layout(self);
8110 assert_layout_col_major_contiguous(
8111 self.is_col_major_contiguous()?,
8112 layout.shape(),
8113 layout.strides(),
8114 layout.offset(),
8115 "Tensor::assert_col_major_contiguous",
8116 )
8117 }
8118
8119 /// Try to borrow the host data as a typed slice.
8120 ///
8121 /// Returns an error if the tensor dtype does not match `T`.
8122 ///
8123 /// # Examples
8124 ///
8125 /// ```
8126 /// use tenferro_tensor::{Tensor, TypedTensor};
8127 ///
8128 /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
8129 /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
8130 /// assert!(t.as_slice::<f32>().is_err());
8131 /// ```
8132 /// # Errors
8133 ///
8134 /// Returns [`crate::Error::Validation`] with
8135 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
8136 /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
8137 /// matching tensor uses backend storage that has not been downloaded.
8138 pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
8139 T::as_slice(self)
8140 }
8141
8142 /// Consume this tensor and return its owned column-major buffer when the
8143 /// dtype matches.
8144 ///
8145 /// # Examples
8146 ///
8147 /// ```
8148 /// use tenferro_tensor::Tensor;
8149 ///
8150 /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
8151 /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
8152 /// ```
8153 /// # Errors
8154 ///
8155 /// Returns [`crate::Error::Validation`] with
8156 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
8157 /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
8158 /// matching tensor uses backend storage that has not been downloaded.
8159 pub fn into_vec_col_major<T: TensorScalar>(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
8160 let typed = T::into_typed(self)?;
8161 typed.into_vec_col_major()
8162 }
8163}
8164
8165// Kept for crate-local layout tests while tensor indexing helpers remain split
8166// across tensor and CPU crates.
8167#[allow(dead_code)]
8168pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
8169 for i in 0..shape.len() {
8170 if shape[i] == 0 {
8171 out[i] = 0;
8172 } else {
8173 out[i] = flat % shape[i];
8174 flat /= shape[i];
8175 }
8176 }
8177}