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::ops::Deref;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::sync::Arc;
8
9use crate::config::SliceConfig;
10pub use tenferro_tensor_core::{DynRank, Rank, TensorLayout, TensorRank};
11use tenferro_tensor_core::{ShapeVec, StrideVec};
12use tenferro_tensor_core::{SliceSpec as CoreSliceSpec, ValidationError};
13
14mod accessors;
15mod shape_packing;
16mod strided_view;
17#[cfg(test)]
18mod tests;
19
20pub use strided_view::StridedSliceSpec;
21
22fn shape_vec(shape: &[usize]) -> ShapeVec {
23 shape.iter().copied().collect()
24}
25
26fn stride_vec(strides: &[isize]) -> StrideVec {
27 strides.iter().copied().collect()
28}
29
30/// Memory location for tensor storage.
31///
32/// # Examples
33///
34/// ```rust
35/// use tenferro_tensor::MemoryKind;
36///
37/// let kind = MemoryKind::UnpinnedHost;
38/// ```
39#[derive(Clone, Debug, PartialEq, Eq, Hash)]
40pub enum MemoryKind {
41 Device,
42 PinnedHost,
43 UnpinnedHost,
44 Managed,
45 Other(String),
46}
47
48/// Compute device family.
49///
50/// # Examples
51///
52/// ```rust
53/// use tenferro_tensor::DeviceKind;
54///
55/// let kind = DeviceKind::Cpu;
56/// ```
57#[derive(Clone, Debug, PartialEq, Eq, Hash)]
58pub enum DeviceKind {
59 Cpu,
60 Gpu(GpuBackendKind),
61 Other(String),
62}
63
64/// GPU backend family used by placement metadata.
65///
66/// # Examples
67///
68/// ```rust
69/// use tenferro_tensor::GpuBackendKind;
70///
71/// let kind = GpuBackendKind::Cuda;
72/// let webgpu = GpuBackendKind::WebGpu;
73/// assert_ne!(kind, webgpu);
74/// ```
75#[derive(Clone, Debug, PartialEq, Eq, Hash)]
76pub enum GpuBackendKind {
77 Cuda,
78 WebGpu,
79 Rocm,
80 Other(String),
81}
82
83/// Concrete compute device identifier.
84///
85/// # Examples
86///
87/// ```rust
88/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind};
89///
90/// let device = DeviceId {
91/// kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
92/// ordinal: 0,
93/// };
94/// ```
95#[derive(Clone, Debug, PartialEq, Eq, Hash)]
96pub struct DeviceId {
97 pub kind: DeviceKind,
98 pub ordinal: usize,
99}
100
101/// Caller-stable identity for a CPU execution domain.
102///
103/// Domain IDs are metadata supplied by the caller or execution coordinator;
104/// creating an ID does not allocate a process-global identity.
105///
106/// # Examples
107///
108/// ```rust
109/// use tenferro_tensor::CpuDomainId;
110///
111/// let domain = CpuDomainId::new(17);
112/// assert_eq!(domain.as_u64(), 17);
113/// ```
114#[repr(transparent)]
115#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
116pub struct CpuDomainId(u64);
117
118impl CpuDomainId {
119 /// Create a caller-stable CPU domain identity.
120 ///
121 /// # Examples
122 ///
123 /// ```rust
124 /// use tenferro_tensor::CpuDomainId;
125 ///
126 /// assert_eq!(CpuDomainId::new(3), CpuDomainId::new(3));
127 /// ```
128 pub const fn new(id: u64) -> Self {
129 Self(id)
130 }
131
132 /// Return the caller-supplied integer identity.
133 ///
134 /// # Examples
135 ///
136 /// ```rust
137 /// use tenferro_tensor::CpuDomainId;
138 ///
139 /// assert_eq!(CpuDomainId::new(9).as_u64(), 9);
140 /// ```
141 pub const fn as_u64(self) -> u64 {
142 self.0
143 }
144}
145
146/// Placement metadata for a tensor buffer.
147///
148/// # Examples
149///
150/// ```rust
151/// use tenferro_tensor::{DeviceId, DeviceKind, GpuBackendKind, MemoryKind, Placement};
152///
153/// let placement = Placement {
154/// memory_kind: MemoryKind::Device,
155/// device: Some(DeviceId {
156/// kind: DeviceKind::Gpu(GpuBackendKind::Cuda),
157/// ordinal: 0,
158/// }),
159/// cpu_affinity: None,
160/// };
161/// assert!(placement.cpu_affinity.is_none());
162/// ```
163#[derive(Clone, Debug, PartialEq, Eq, Hash)]
164pub struct Placement {
165 /// Storage memory class, independent of execution routing metadata.
166 pub memory_kind: MemoryKind,
167 /// Device that owns or addresses the storage, when applicable.
168 pub device: Option<DeviceId>,
169 /// Preferred or producing CPU execution domain for routing and locality.
170 ///
171 /// This tag is not proof of allocation ownership, page residency, NUMA
172 /// pinning, or worker-affinity enforcement. Backend allocation-domain
173 /// metadata remains attached to the buffer independently.
174 pub cpu_affinity: Option<CpuDomainId>,
175}
176
177impl Default for Placement {
178 fn default() -> Self {
179 default_placement()
180 }
181}
182
183/// Backend-owned buffer handle.
184///
185/// `BufferHandle::new` creates an empty opaque handle. Use
186/// [`BufferHandle::new_with_len`] when test or adapter code needs to model a
187/// non-empty backend allocation.
188///
189/// # Examples
190///
191/// ```rust
192/// use tenferro_tensor::BufferHandle;
193///
194/// let handle = BufferHandle::<f64>::new(7);
195/// ```
196#[derive(Clone)]
197pub struct BufferHandle<T> {
198 id: u64,
199 len: usize,
200 _phantom: std::marker::PhantomData<T>,
201}
202
203/// Identity of a backend-owned allocation domain.
204///
205/// Domains let cooperating backends accept shared allocations without treating
206/// another context's physically similar buffer as compatible.
207///
208/// # Examples
209///
210/// ```rust
211/// use tenferro_tensor::AllocationDomainId;
212///
213/// assert_ne!(AllocationDomainId::fresh(), AllocationDomainId::fresh());
214/// ```
215#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
216pub struct AllocationDomainId(u64);
217
218impl AllocationDomainId {
219 /// Create a process-unique allocation-domain identity.
220 ///
221 /// # Examples
222 ///
223 /// ```rust
224 /// use tenferro_tensor::AllocationDomainId;
225 ///
226 /// let domain = AllocationDomainId::fresh();
227 /// assert_eq!(domain, domain);
228 /// ```
229 pub fn fresh() -> Self {
230 static NEXT_DOMAIN_ID: AtomicU64 = AtomicU64::new(1);
231 Self(NEXT_DOMAIN_ID.fetch_add(1, Ordering::Relaxed))
232 }
233}
234
235/// Stable physical identity of one backend allocation.
236///
237/// # Examples
238///
239/// ```rust
240/// use tenferro_tensor::AllocationId;
241///
242/// assert_eq!(AllocationId::from_backend_id(7), AllocationId::from_backend_id(7));
243/// ```
244#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
245pub struct AllocationId(u64);
246
247impl AllocationId {
248 /// Wrap an allocation identity supplied by the owning backend.
249 ///
250 /// # Examples
251 ///
252 /// ```rust
253 /// use tenferro_tensor::AllocationId;
254 ///
255 /// let id = AllocationId::from_backend_id(3);
256 /// assert_eq!(id, AllocationId::from_backend_id(3));
257 /// ```
258 pub const fn from_backend_id(id: u64) -> Self {
259 Self(id)
260 }
261}
262
263/// Typed failure returned by guarded backend host access.
264///
265/// # Examples
266///
267/// ```rust
268/// use tenferro_tensor::HostAccessError;
269///
270/// let error = HostAccessError::Unsupported { backend: "opaque" };
271/// assert!(error.to_string().contains("opaque"));
272/// ```
273#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
274#[non_exhaustive]
275pub enum HostAccessError {
276 /// The backend does not expose guarded host access for this allocation.
277 #[error("backend `{backend}` does not support guarded host access")]
278 Unsupported { backend: &'static str },
279 /// The allocation belongs to another shared-allocation domain.
280 #[error("allocation belongs to domain {actual:?}, expected {expected:?}")]
281 ForeignDomain {
282 expected: AllocationDomainId,
283 actual: AllocationDomainId,
284 },
285 /// Another host mapping overlaps this allocation.
286 #[error("the allocation already has an active host mapping")]
287 OverlappingHostMapping,
288 /// GPU work currently owns or has reserved the allocation.
289 #[error("GPU access is in progress for the allocation")]
290 GpuAccessInProgress,
291 /// Host mapping is active while GPU access was requested.
292 #[error("the allocation is mapped for host access")]
293 MappedForHost,
294 /// The backend failed to complete the map operation.
295 #[error("backend host mapping failed: {message}")]
296 BackendFailure { message: String },
297 /// The source did not cover the full write-only mapping.
298 #[error("host write length mismatch: expected {expected}, got {actual}")]
299 LengthMismatch { expected: usize, actual: usize },
300}
301
302trait ReadGuardAccess<T> {
303 fn as_slice(&self) -> &[T];
304}
305
306impl<T, G> ReadGuardAccess<T> for G
307where
308 G: Deref,
309 G::Target: AsRef<[T]>,
310{
311 fn as_slice(&self) -> &[T] {
312 self.deref().as_ref()
313 }
314}
315
316/// Closure-scoped read mapping of a backend allocation.
317///
318/// # Examples
319///
320/// ```rust
321/// use tenferro_tensor::HostReadGuard;
322///
323/// let guard = HostReadGuard::new(vec![1_u32, 2]);
324/// assert_eq!(&*guard, &[1, 2]);
325/// ```
326pub struct HostReadGuard<'a, T> {
327 access: Box<dyn ReadGuardAccess<T> + 'a>,
328}
329
330impl<T> Debug for HostReadGuard<'_, T> {
331 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332 formatter
333 .debug_struct("HostReadGuard")
334 .field("len", &self.len())
335 .finish_non_exhaustive()
336 }
337}
338
339impl<'a, T> HostReadGuard<'a, T> {
340 /// Wrap a backend-native read guard without exposing its concrete type.
341 ///
342 /// # Examples
343 ///
344 /// ```rust
345 /// use tenferro_tensor::HostReadGuard;
346 ///
347 /// let guard = HostReadGuard::new(vec![3_i32]);
348 /// assert_eq!(guard[0], 3);
349 /// ```
350 pub fn new<G>(guard: G) -> Self
351 where
352 G: Deref + 'a,
353 G::Target: AsRef<[T]>,
354 T: 'a,
355 {
356 Self {
357 access: Box::new(guard),
358 }
359 }
360}
361
362impl<T> Deref for HostReadGuard<'_, T> {
363 type Target = [T];
364
365 fn deref(&self) -> &Self::Target {
366 self.access.as_slice()
367 }
368}
369
370/// Backend-neutral owner of one shared tensor allocation domain.
371///
372/// CPU operation crates use this object-safe boundary to allocate results in
373/// the same managed domain without depending on a GPU provider crate.
374///
375/// # Examples
376///
377/// ```rust
378/// use std::sync::Arc;
379/// use tenferro_tensor::SharedTensorAllocationDomain;
380///
381/// let _domain: Option<Arc<dyn SharedTensorAllocationDomain>> = None;
382/// ```
383pub trait SharedTensorAllocationDomain: Debug + Send + Sync + 'static {
384 /// Return the stable identity shared by every allocation from this owner.
385 fn id(&self) -> AllocationDomainId;
386
387 /// Allocate an uninitialized compact column-major tensor in this domain.
388 ///
389 /// # Errors
390 ///
391 /// Returns a typed validation, unsupported-dtype, or backend allocation error.
392 fn allocate(&self, dtype: DType, shape: &[usize]) -> crate::Result<Tensor>;
393}
394
395type HostWriteCopy<'a, T> = dyn FnMut(&[T]) -> Result<(), HostAccessError> + 'a;
396
397/// Closure-scoped write-only mapping of a backend allocation.
398///
399/// # Examples
400///
401/// ```rust
402/// use tenferro_tensor::{HostAccessError, HostWriteGuard};
403///
404/// let mut written = Vec::new();
405/// {
406/// let mut guard = HostWriteGuard::new(2, |source: &[u32]| {
407/// written.extend_from_slice(source);
408/// Ok::<(), HostAccessError>(())
409/// });
410/// guard.copy_from_slice(&[4, 5]).unwrap();
411/// }
412/// assert_eq!(written, [4, 5]);
413/// ```
414pub struct HostWriteGuard<'a, T> {
415 len: usize,
416 copy: Box<HostWriteCopy<'a, T>>,
417}
418
419impl<T> Debug for HostWriteGuard<'_, T> {
420 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
421 formatter
422 .debug_struct("HostWriteGuard")
423 .field("len", &self.len)
424 .finish_non_exhaustive()
425 }
426}
427
428impl<'a, T> HostWriteGuard<'a, T> {
429 /// Wrap a backend-native write guard without exposing its concrete type.
430 ///
431 /// # Examples
432 ///
433 /// ```rust
434 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
435 ///
436 /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
437 /// assert!(guard.is_empty());
438 /// ```
439 ///
440 /// # Errors
441 ///
442 /// Construction is infallible. A callback failure such as
443 /// [`HostAccessError::BackendFailure`] is returned later by
444 /// [`Self::copy_from_slice`].
445 pub fn new<F>(len: usize, copy: F) -> Self
446 where
447 F: FnMut(&[T]) -> Result<(), HostAccessError> + 'a,
448 T: 'a,
449 {
450 Self {
451 len,
452 copy: Box::new(copy),
453 }
454 }
455
456 /// Number of elements covered by this write-only mapping.
457 ///
458 /// # Examples
459 ///
460 /// ```rust
461 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
462 ///
463 /// let guard = HostWriteGuard::new(2, |_source: &[f32]| Ok::<(), HostAccessError>(()));
464 /// assert_eq!(guard.len(), 2);
465 /// ```
466 pub fn len(&self) -> usize {
467 self.len
468 }
469
470 /// Returns `true` when this mapping covers no elements.
471 ///
472 /// # Examples
473 ///
474 /// ```rust
475 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
476 ///
477 /// let guard = HostWriteGuard::new(0, |_source: &[f32]| Ok::<(), HostAccessError>(()));
478 /// assert!(guard.is_empty());
479 /// ```
480 pub fn is_empty(&self) -> bool {
481 self.len == 0
482 }
483
484 /// Replace the full mapped allocation contents.
485 ///
486 /// # Examples
487 ///
488 /// ```rust
489 /// use tenferro_tensor::{HostAccessError, HostWriteGuard};
490 ///
491 /// let mut guard = HostWriteGuard::new(1, |_source: &[f32]| Ok::<(), HostAccessError>(()));
492 /// guard.copy_from_slice(&[1.0]).unwrap();
493 /// ```
494 ///
495 /// # Errors
496 ///
497 /// Returns [`HostAccessError::LengthMismatch`] when `source` does not cover
498 /// the complete mapping, or the typed backend error returned by the owning
499 /// write guard.
500 pub fn copy_from_slice(&mut self, source: &[T]) -> Result<(), HostAccessError> {
501 if source.len() != self.len {
502 return Err(HostAccessError::LengthMismatch {
503 expected: self.len,
504 actual: source.len(),
505 });
506 }
507 (self.copy)(source)
508 }
509}
510
511impl<T> Debug for BufferHandle<T> {
512 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
513 f.debug_struct("BufferHandle")
514 .field("id", &self.id)
515 .finish()
516 }
517}
518
519impl<T> BufferHandle<T> {
520 /// Create a new backend buffer handle.
521 ///
522 /// # Examples
523 ///
524 /// ```rust
525 /// use tenferro_tensor::BufferHandle;
526 ///
527 /// let handle = BufferHandle::<f64>::new(1);
528 /// assert_eq!(tenferro_tensor::BackendBuffer::len(&handle), 0);
529 /// ```
530 pub fn new(id: u64) -> Self {
531 Self::new_with_len(id, 0)
532 }
533
534 /// Create a new backend buffer handle with a logical element count.
535 ///
536 /// # Examples
537 ///
538 /// ```rust
539 /// use tenferro_tensor::{BackendBuffer, BufferHandle};
540 ///
541 /// let handle = BufferHandle::<f64>::new_with_len(1, 4);
542 /// assert_eq!(BackendBuffer::len(&handle), 4);
543 /// ```
544 pub fn new_with_len(id: u64, len: usize) -> Self {
545 Self {
546 id,
547 len,
548 _phantom: std::marker::PhantomData,
549 }
550 }
551}
552
553/// Opaque backend-owned tensor buffer.
554///
555/// Tensor core never inspects backend-native allocations directly. Backend
556/// crates store their own concrete handle types behind this trait and
557/// downcast inside the owning backend only.
558///
559/// # Examples
560///
561/// ```rust
562/// use std::sync::Arc;
563/// use tenferro_tensor::{BackendBuffer, BufferHandle};
564///
565/// let buffer: Arc<dyn BackendBuffer<f64>> = Arc::new(BufferHandle::<f64>::new_with_len(7, 2));
566/// assert_eq!(buffer.backend_family(), "opaque");
567/// assert_eq!(buffer.len(), 2);
568/// ```
569pub trait BackendBuffer<T>: Debug + Send + Sync + 'static {
570 /// Stable backend family identifier.
571 fn backend_family(&self) -> &'static str;
572
573 /// Number of logical elements in the backend allocation.
574 fn len(&self) -> usize;
575
576 /// Returns `true` when the backend allocation is empty.
577 fn is_empty(&self) -> bool {
578 self.len() == 0
579 }
580
581 /// Return the shared-allocation domain, when this buffer belongs to one.
582 fn allocation_domain(&self) -> Option<AllocationDomainId> {
583 None
584 }
585
586 /// Return the stable physical allocation identity, when available.
587 fn allocation_id(&self) -> Option<AllocationId> {
588 None
589 }
590
591 /// Map the allocation for closure-scoped host reads.
592 ///
593 /// # Errors
594 ///
595 /// The default returns [`HostAccessError::Unsupported`]. Host-visible
596 /// backends return typed overlap, pending-GPU, or backend mapping failures.
597 fn map_read(&self) -> Result<HostReadGuard<'_, T>, HostAccessError> {
598 Err(HostAccessError::Unsupported {
599 backend: self.backend_family(),
600 })
601 }
602
603 /// Map the allocation for closure-scoped host writes.
604 ///
605 /// # Errors
606 ///
607 /// The default returns [`HostAccessError::Unsupported`]. Host-visible
608 /// backends return typed overlap, pending-GPU, or backend mapping failures.
609 fn map_write(&self) -> Result<HostWriteGuard<'_, T>, HostAccessError> {
610 Err(HostAccessError::Unsupported {
611 backend: self.backend_family(),
612 })
613 }
614
615 /// Type-erased access for the backend crate that owns the concrete handle.
616 fn as_any(&self) -> &dyn Any;
617}
618
619impl<T: Send + Sync + 'static> BackendBuffer<T> for BufferHandle<T> {
620 fn backend_family(&self) -> &'static str {
621 "opaque"
622 }
623
624 fn len(&self) -> usize {
625 self.len
626 }
627
628 fn as_any(&self) -> &dyn Any {
629 self
630 }
631}
632
633/// Tensor storage.
634///
635/// # Examples
636///
637/// ```rust
638/// use tenferro_tensor::Buffer;
639///
640/// let host = Buffer::Host(vec![1.0_f64, 2.0]);
641/// ```
642#[derive(Clone, Debug)]
643pub enum Buffer<T> {
644 Host(Vec<T>),
645 Backend(Arc<dyn BackendBuffer<T>>),
646}
647
648impl<T: 'static> Buffer<T> {
649 /// Return the physical element count in this buffer.
650 ///
651 /// # Examples
652 ///
653 /// ```rust
654 /// use tenferro_tensor::Buffer;
655 ///
656 /// assert_eq!(Buffer::Host(vec![1_i32, 2]).len(), 2);
657 /// ```
658 pub fn len(&self) -> usize {
659 match self {
660 Self::Host(data) => data.len(),
661 Self::Backend(buffer) => buffer.len(),
662 }
663 }
664
665 /// Return whether this buffer has no physical elements.
666 ///
667 /// # Examples
668 ///
669 /// ```rust
670 /// use tenferro_tensor::Buffer;
671 ///
672 /// assert!(Buffer::<i32>::Host(Vec::new()).is_empty());
673 /// ```
674 pub fn is_empty(&self) -> bool {
675 self.len() == 0
676 }
677
678 /// Return whether the storage is backend-owned rather than host-owned.
679 ///
680 /// # Examples
681 ///
682 /// ```rust
683 /// use tenferro_tensor::Buffer;
684 ///
685 /// assert!(!Buffer::Host(vec![1_i32]).is_backend());
686 /// ```
687 pub fn is_backend(&self) -> bool {
688 matches!(self, Self::Backend(_))
689 }
690}
691
692/// Runtime typed tensor storage with compile-time scalar type and rank metadata.
693///
694/// Owned tensors are compact column-major. Arbitrary strides and metadata-only
695/// layout changes are represented by [`TypedTensorView`] and
696/// [`TypedTensorViewMut`]. The buffer may be host-backed or backend-backed;
697/// host-inspection methods do not download backend buffers implicitly.
698///
699/// # Examples
700///
701/// ```
702/// use tenferro_tensor::{Rank, Tensor, TypedTensor};
703///
704/// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
705/// assert_eq!(t.shape(), &[2, 2]);
706///
707/// let static_rank = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
708/// assert_eq!(static_rank.rank(), 2);
709///
710/// let dynamic = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64; 4]).unwrap();
711/// assert_eq!(dynamic.shape(), &[2, 2]);
712/// ```
713///
714/// The `R` parameter stores rank metadata. It defaults to dynamic rank
715/// (`DynRank`); use [`Rank<N>`](Rank) for compile-time rank validation.
716/// The dtype-erased [`Tensor`] enum remains dynamic-rank.
717#[derive(Clone, Debug)]
718pub struct TypedTensor<T, R: TensorRank = DynRank> {
719 buffer: Buffer<T>,
720 layout: TensorLayout<R>,
721 placement: Placement,
722}
723
724/// Borrowed tensor buffer reference used by read-only typed views.
725///
726/// # Examples
727///
728/// ```rust
729/// use tenferro_tensor::TensorBufferRef;
730///
731/// let data = [1_i32, 2];
732/// let buffer = TensorBufferRef::Host(&data);
733/// assert_eq!(buffer.len(), 2);
734/// ```
735#[derive(Debug)]
736pub enum TensorBufferRef<'a, T> {
737 Host(&'a [T]),
738 Backend(Arc<dyn BackendBuffer<T>>),
739}
740
741impl<T> Clone for TensorBufferRef<'_, T> {
742 fn clone(&self) -> Self {
743 match self {
744 Self::Host(data) => Self::Host(data),
745 Self::Backend(buffer) => Self::Backend(Arc::clone(buffer)),
746 }
747 }
748}
749
750impl<T: 'static> TensorBufferRef<'_, T> {
751 /// Return the logical length of the backing allocation.
752 ///
753 /// # Examples
754 ///
755 /// ```rust
756 /// use tenferro_tensor::TensorBufferRef;
757 ///
758 /// let data = [1_i32, 2, 3];
759 /// assert_eq!(TensorBufferRef::Host(&data).len(), 3);
760 /// ```
761 pub fn len(&self) -> usize {
762 match self {
763 Self::Host(data) => data.len(),
764 Self::Backend(buffer) => buffer.len(),
765 }
766 }
767
768 /// Return whether the backing allocation is empty.
769 ///
770 /// # Examples
771 ///
772 /// ```rust
773 /// use tenferro_tensor::TensorBufferRef;
774 ///
775 /// let data: [f64; 0] = [];
776 /// assert!(TensorBufferRef::Host(&data).is_empty());
777 /// ```
778 pub fn is_empty(&self) -> bool {
779 self.len() == 0
780 }
781}
782
783/// Borrowed tensor buffer reference used by mutable typed views.
784///
785/// Backend buffers can be represented for residency metadata, but this crate
786/// does not expose host mutation for backend-native allocations.
787///
788/// # Examples
789///
790/// ```rust
791/// use tenferro_tensor::TensorBufferRefMut;
792///
793/// let mut data = [1_i32, 2];
794/// let buffer = TensorBufferRefMut::Host(&mut data);
795/// assert_eq!(buffer.len(), 2);
796/// ```
797#[derive(Debug)]
798pub enum TensorBufferRefMut<'a, T> {
799 Host(&'a mut [T]),
800 Backend(Arc<dyn BackendBuffer<T>>),
801}
802
803impl<T: 'static> TensorBufferRefMut<'_, T> {
804 /// Return the logical length of the backing allocation.
805 ///
806 /// # Examples
807 ///
808 /// ```rust
809 /// use tenferro_tensor::TensorBufferRefMut;
810 ///
811 /// let mut data = [1_i32, 2, 3];
812 /// assert_eq!(TensorBufferRefMut::Host(&mut data).len(), 3);
813 /// ```
814 pub fn len(&self) -> usize {
815 match self {
816 Self::Host(data) => data.len(),
817 Self::Backend(buffer) => buffer.len(),
818 }
819 }
820
821 /// Return whether the backing allocation is empty.
822 ///
823 /// # Examples
824 ///
825 /// ```rust
826 /// use tenferro_tensor::TensorBufferRefMut;
827 ///
828 /// let mut data: [f64; 0] = [];
829 /// assert!(TensorBufferRefMut::Host(&mut data).is_empty());
830 /// ```
831 pub fn is_empty(&self) -> bool {
832 self.len() == 0
833 }
834}
835
836/// Read-only borrowed view of typed tensor storage with arbitrary strides.
837///
838/// `TypedTensorView` is the typed representation for layout-only tensor
839/// transformations. It borrows an existing host or backend allocation and
840/// carries a logical shape, strides, and an offset. Slicing, reshaping when
841/// stride-compatible, and [`transpose_view`](TypedTensorView::transpose_view)
842/// update only metadata and do not copy storage.
843///
844/// Materialize through [`TensorStructural::to_contiguous_read`](crate::TensorStructural::to_contiguous_read)
845/// on the active backend session when a compact owned [`TypedTensor`] is
846/// required. Use [`TypedTensorView::as_slice`] only when the current view is
847/// contiguous in the requested layout.
848///
849/// # Examples
850///
851/// ```rust
852/// use tenferro_tensor::{Rank, TypedTensorView};
853///
854/// let data = [1_i32, 2, 3, 4];
855/// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
856/// assert_eq!(view.get(&[1, 1]), Some(&4));
857/// # Ok::<(), tenferro_tensor::Error>(())
858/// ```
859#[derive(Clone, Debug)]
860pub struct TypedTensorView<'a, T, R: TensorRank = DynRank> {
861 buffer: TensorBufferRef<'a, T>,
862 layout: TensorLayout<R>,
863 placement: Placement,
864}
865
866impl<'a, T: 'static> TypedTensorView<'a, T, DynRank> {
867 /// Create a borrowed dynamic-rank view over compact column-major host data.
868 ///
869 /// # Examples
870 ///
871 /// ```rust
872 /// use tenferro_tensor::TypedTensorView;
873 ///
874 /// let data = [1_i32, 2, 3, 4];
875 /// let view = TypedTensorView::from_col_major(&[2, 2], &data)?;
876 /// assert_eq!(view.strides(), &[1, 2]);
877 /// # Ok::<(), tenferro_tensor::Error>(())
878 /// ```
879 ///
880 /// # Errors
881 ///
882 /// Returns [`crate::Error::Validation`] with
883 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when compact
884 /// strides or reachable bounds overflow, or
885 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
886 /// requested shape reaches beyond `data`.
887 pub fn from_col_major(shape: &[usize], data: &'a [T]) -> crate::Result<Self> {
888 let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
889 .map_err(|err| tensor_layout_error("TypedTensorView::from_col_major", err))?;
890 Self::from_buffer_ref(
891 shape_vec(layout.shape()),
892 stride_vec(layout.strides()),
893 layout.offset(),
894 TensorBufferRef::Host(data),
895 default_placement(),
896 "TypedTensorView::from_col_major",
897 )
898 }
899
900 /// Create a borrowed host view from explicit layout metadata.
901 ///
902 /// # Examples
903 ///
904 /// ```rust
905 /// use tenferro_tensor::TypedTensorView;
906 ///
907 /// let data = [1_i32, 2, 3];
908 /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
909 /// assert_eq!(view.get(&[2]), Some(&1));
910 /// # Ok::<(), tenferro_tensor::Error>(())
911 /// ```
912 ///
913 /// # Errors
914 ///
915 /// Returns [`crate::Error::Validation`] with
916 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
917 /// `strides` have different ranks,
918 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
919 /// reachable layout exceeds `data`, or
920 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
921 /// arithmetic overflows.
922 pub fn from_slice(
923 shape: impl AsRef<[usize]>,
924 strides: impl AsRef<[isize]>,
925 offset: isize,
926 data: &'a [T],
927 ) -> crate::Result<Self> {
928 Self::from_buffer_ref(
929 shape_vec(shape.as_ref()),
930 stride_vec(strides.as_ref()),
931 offset,
932 TensorBufferRef::Host(data),
933 default_placement(),
934 "TypedTensorView::from_slice",
935 )
936 }
937}
938
939impl<'a, T: 'static, R: TensorRank> TypedTensorView<'a, T, R> {
940 /// Create a rank-generic borrowed host view from explicit layout metadata.
941 ///
942 /// # Examples
943 ///
944 /// ```rust
945 /// use tenferro_tensor::{Rank, TypedTensorView};
946 ///
947 /// let data = [1_i32, 2, 3, 4];
948 /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &data)?;
949 /// assert_eq!(view.get(&[1, 1]), Some(&4));
950 /// # Ok::<(), tenferro_tensor::Error>(())
951 /// ```
952 ///
953 /// # Errors
954 ///
955 /// Returns [`crate::Error::Validation`] with
956 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
957 /// rank does not match `shape` or `strides`,
958 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
959 /// reachable layout exceeds `data`, or
960 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when layout
961 /// arithmetic overflows.
962 pub fn from_slice_ranked(
963 shape: impl Into<R::Shape>,
964 strides: impl Into<R::Strides>,
965 offset: isize,
966 data: &'a [T],
967 ) -> crate::Result<Self> {
968 Self::from_buffer_ref(
969 shape,
970 strides,
971 offset,
972 TensorBufferRef::Host(data),
973 default_placement(),
974 "TypedTensorView::from_slice_ranked",
975 )
976 }
977
978 fn from_buffer_ref(
979 shape: impl Into<R::Shape>,
980 strides: impl Into<R::Strides>,
981 offset: isize,
982 buffer: TensorBufferRef<'a, T>,
983 placement: Placement,
984 op: &'static str,
985 ) -> crate::Result<Self> {
986 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
987 .map_err(|err| tensor_layout_error(op, err))?;
988 Ok(Self {
989 buffer,
990 layout,
991 placement,
992 })
993 }
994
995 /// Return the logical shape.
996 ///
997 /// # Examples
998 ///
999 /// ```rust
1000 /// use tenferro_tensor::TypedTensorView;
1001 ///
1002 /// let data = [0_i32; 2];
1003 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
1004 /// assert_eq!(view.shape(), &[2]);
1005 /// # Ok::<(), tenferro_tensor::Error>(())
1006 /// ```
1007 pub fn shape(&self) -> &[usize] {
1008 self.layout.shape()
1009 }
1010
1011 /// Return strides in element units.
1012 ///
1013 /// # Examples
1014 ///
1015 /// ```rust
1016 /// use tenferro_tensor::TypedTensorView;
1017 ///
1018 /// let data = [0_i32; 2];
1019 /// let view = TypedTensorView::from_slice(vec![2], vec![-1], 1, &data)?;
1020 /// assert_eq!(view.strides(), &[-1]);
1021 /// # Ok::<(), tenferro_tensor::Error>(())
1022 /// ```
1023 pub fn strides(&self) -> &[isize] {
1024 self.layout.strides()
1025 }
1026
1027 /// Return the physical element offset.
1028 ///
1029 /// # Examples
1030 ///
1031 /// ```rust
1032 /// use tenferro_tensor::TypedTensorView;
1033 ///
1034 /// let data = [1_i32, 2];
1035 /// let view = TypedTensorView::from_slice(vec![1], vec![1], 1, &data)?;
1036 /// assert_eq!(view.offset(), 1);
1037 /// # Ok::<(), tenferro_tensor::Error>(())
1038 /// ```
1039 pub fn offset(&self) -> isize {
1040 self.layout.offset()
1041 }
1042
1043 /// Return the borrowed host storage backing this view.
1044 ///
1045 /// This exposes the entire backing host allocation, not just the logical
1046 /// slice covered by this view. Use [`TypedTensorView::as_slice`] when the
1047 /// caller needs the contiguous logical region instead.
1048 ///
1049 /// # Examples
1050 ///
1051 /// ```rust
1052 /// use tenferro_tensor::TypedTensorView;
1053 ///
1054 /// let data = [1_i32, 2];
1055 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
1056 /// assert_eq!(view.host_storage()?, &[1, 2]);
1057 /// # Ok::<(), tenferro_tensor::Error>(())
1058 /// ```
1059 ///
1060 /// # Errors
1061 ///
1062 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
1063 /// buffer; backend storage must be downloaded before host inspection.
1064 pub fn host_storage(&self) -> crate::Result<&'a [T]> {
1065 match &self.buffer {
1066 TensorBufferRef::Host(data) => Ok(data),
1067 TensorBufferRef::Backend(_) => Err(crate::Error::runtime_state(
1068 "TypedTensorView::host_storage",
1069 "backend buffers cannot expose host storage; download explicitly first",
1070 )),
1071 }
1072 }
1073
1074 /// Return the number of logical elements in this view.
1075 ///
1076 /// # Examples
1077 ///
1078 /// ```rust
1079 /// use tenferro_tensor::TypedTensorView;
1080 ///
1081 /// let data = [0_i32; 6];
1082 /// let view = TypedTensorView::from_slice(vec![2, 3], vec![1, 2], 0, &data)?;
1083 /// assert_eq!(view.n_elements(), 6);
1084 /// # Ok::<(), tenferro_tensor::Error>(())
1085 /// ```
1086 pub fn n_elements(&self) -> usize {
1087 // Invariant: public view constructors validate logical element count.
1088 match checked_view_element_count(self.shape(), "TypedTensorView::n_elements") {
1089 Ok(n) => n,
1090 Err(err) => {
1091 unreachable!("TypedTensorView layout shape is validated at construction: {err}")
1092 }
1093 }
1094 }
1095
1096 /// Return layout metadata for this view.
1097 ///
1098 /// # Examples
1099 ///
1100 /// ```rust
1101 /// use tenferro_tensor::TypedTensorView;
1102 ///
1103 /// let data = [1_i32, 2];
1104 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
1105 /// assert!(view.layout().is_compact_col_major().unwrap());
1106 /// # Ok::<(), tenferro_tensor::Error>(())
1107 /// ```
1108 pub fn layout(&self) -> &TensorLayout<R> {
1109 &self.layout
1110 }
1111
1112 /// Return placement metadata for this view.
1113 ///
1114 /// # Examples
1115 ///
1116 /// ```rust
1117 /// use tenferro_tensor::{MemoryKind, TypedTensorView};
1118 ///
1119 /// let data = [1_i32];
1120 /// let view = TypedTensorView::from_slice(vec![1], vec![1], 0, &data)?;
1121 /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
1122 /// # Ok::<(), tenferro_tensor::Error>(())
1123 /// ```
1124 pub fn placement(&self) -> &Placement {
1125 &self.placement
1126 }
1127
1128 /// Return the backend allocation for backend integrations.
1129 #[doc(hidden)]
1130 pub fn backend_buffer(&self) -> Option<&Arc<dyn BackendBuffer<T>>> {
1131 match &self.buffer {
1132 TensorBufferRef::Host(_) => None,
1133 TensorBufferRef::Backend(buffer) => Some(buffer),
1134 }
1135 }
1136
1137 /// Compute the physical element offset for a logical index.
1138 ///
1139 /// # Examples
1140 ///
1141 /// ```rust
1142 /// use tenferro_tensor::TypedTensorView;
1143 ///
1144 /// let data = [1_i32, 2, 3];
1145 /// let view = TypedTensorView::from_slice(vec![3], vec![-1], 2, &data)?;
1146 /// assert_eq!(view.linear_offset(&[2]), Some(0));
1147 /// # Ok::<(), tenferro_tensor::Error>(())
1148 /// ```
1149 pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
1150 checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
1151 }
1152
1153 /// Compute the physical element offset for a logical index, returning a typed error.
1154 ///
1155 /// # Examples
1156 ///
1157 /// ```rust
1158 /// use tenferro_tensor::TypedTensorView;
1159 ///
1160 /// let data = [1_i32, 2, 3];
1161 /// let view = TypedTensorView::from_slice([3], [-1], 2, &data)?;
1162 /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
1163 /// # Ok::<(), tenferro_tensor::Error>(())
1164 /// ```
1165 ///
1166 /// # Errors
1167 ///
1168 /// Returns [`crate::Error::Validation`] with
1169 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
1170 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
1171 /// when an index is outside its axis extent, or
1172 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
1173 /// arithmetic overflows.
1174 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
1175 checked_view_offset_result(
1176 self.shape(),
1177 self.strides(),
1178 self.offset(),
1179 indices,
1180 "TypedTensorView::layout_linear_offset",
1181 )
1182 }
1183
1184 /// Return whether this view is compact column-major.
1185 ///
1186 /// # Examples
1187 ///
1188 /// ```rust
1189 /// use tenferro_tensor::TypedTensorView;
1190 ///
1191 /// let data = [1_i32, 2];
1192 /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
1193 /// assert!(view.is_col_major_contiguous()?);
1194 /// # Ok::<(), tenferro_tensor::Error>(())
1195 /// ```
1196 ///
1197 /// # Errors
1198 ///
1199 /// Returns [`crate::Error::Validation`] with
1200 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] if compactness
1201 /// arithmetic overflows.
1202 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
1203 self.layout
1204 .is_compact_col_major()
1205 .map_err(|err| tensor_layout_error("TypedTensorView::is_col_major_contiguous", err))
1206 }
1207
1208 /// Return a compact string summary of this view's layout metadata.
1209 ///
1210 /// # Examples
1211 ///
1212 /// ```rust
1213 /// use tenferro_tensor::TypedTensorView;
1214 ///
1215 /// let data = [1_i32, 2];
1216 /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
1217 /// assert!(view.layout_summary().contains("shape=[2]"));
1218 /// # Ok::<(), tenferro_tensor::Error>(())
1219 /// ```
1220 pub fn layout_summary(&self) -> String {
1221 layout_summary(self.shape(), self.strides(), self.offset())
1222 }
1223
1224 /// Assert this view is compact column-major.
1225 ///
1226 /// # Examples
1227 ///
1228 /// ```rust
1229 /// use tenferro_tensor::TypedTensorView;
1230 ///
1231 /// let data = [1_i32, 2];
1232 /// let view = TypedTensorView::from_slice([2], [1], 0, &data)?;
1233 /// view.assert_col_major_contiguous()?;
1234 /// # Ok::<(), tenferro_tensor::Error>(())
1235 /// ```
1236 ///
1237 /// # Errors
1238 ///
1239 /// Returns [`crate::Error::Validation`] with
1240 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
1241 /// compactness arithmetic overflows, or
1242 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
1243 /// view is not compact column-major.
1244 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
1245 assert_layout_col_major_contiguous(
1246 self.is_col_major_contiguous()?,
1247 self.shape(),
1248 self.strides(),
1249 self.offset(),
1250 "TypedTensorView::assert_col_major_contiguous",
1251 )
1252 }
1253
1254 /// Borrow one host element by logical index.
1255 ///
1256 /// Returns `None` for out-of-bounds indices and backend buffers.
1257 ///
1258 /// # Examples
1259 ///
1260 /// ```rust
1261 /// use tenferro_tensor::TypedTensorView;
1262 ///
1263 /// let data = [1_i32, 2];
1264 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
1265 /// assert_eq!(view.get(&[1]), Some(&2));
1266 /// # Ok::<(), tenferro_tensor::Error>(())
1267 /// ```
1268 pub fn get(&self, indices: &[usize]) -> Option<&T> {
1269 let offset = self.linear_offset(indices)?;
1270 match &self.buffer {
1271 TensorBufferRef::Host(data) => data.get(offset),
1272 TensorBufferRef::Backend(_) => None,
1273 }
1274 }
1275
1276 /// Borrow the contiguous host slice covered by this view.
1277 ///
1278 /// Returns an explicit error for backend buffers and for non-contiguous
1279 /// layouts. This method never downloads or materializes backend data.
1280 ///
1281 /// # Examples
1282 ///
1283 /// ```rust
1284 /// use tenferro_tensor::TypedTensorView;
1285 ///
1286 /// let data = [1_i32, 2, 3];
1287 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?;
1288 /// assert_eq!(view.as_slice()?, &[2, 3]);
1289 /// # Ok::<(), tenferro_tensor::Error>(())
1290 /// ```
1291 ///
1292 /// # Errors
1293 ///
1294 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
1295 /// buffer, [`tenferro_tensor_core::ValidationError::InvalidArgument`] when
1296 /// the layout is not slice-contiguous or has a negative offset, or
1297 /// [`crate::Error::Validation`] with
1298 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] or
1299 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when the
1300 /// requested host range is invalid.
1301 pub fn as_slice(&self) -> crate::Result<&'a [T]> {
1302 let data =
1303 match &self.buffer {
1304 TensorBufferRef::Host(data) => data,
1305 TensorBufferRef::Backend(_) => return Err(crate::Error::runtime_state(
1306 "TypedTensorView::as_slice",
1307 "backend buffers cannot be inspected as host slices; download explicitly first",
1308 )),
1309 };
1310 contiguous_layout_slice(self.layout(), data, "TypedTensorView::as_slice")
1311 }
1312
1313 /// Return a metadata-only axis permutation.
1314 ///
1315 /// # Examples
1316 ///
1317 /// ```rust
1318 /// use tenferro_tensor::{Rank, TypedTensorView};
1319 ///
1320 /// let data = [1_i32, 2, 3, 4, 5, 6];
1321 /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 3], [1, 2], 0, &data)?;
1322 /// let transposed = view.transpose_view([1, 0])?;
1323 /// assert_eq!(transposed.shape(), &[3, 2]);
1324 /// # Ok::<(), tenferro_tensor::Error>(())
1325 /// ```
1326 /// # Errors
1327 ///
1328 /// Returns [`crate::Error::Validation`] with
1329 /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
1330 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
1331 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
1332 /// not a valid permutation of the view rank.
1333 pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
1334 let layout = self
1335 .layout
1336 .transpose_view(axes)
1337 .map_err(|err| tensor_layout_error("TypedTensorView::transpose_view", err))?;
1338 Ok(Self {
1339 buffer: self.buffer.clone(),
1340 layout,
1341 placement: self.placement.clone(),
1342 })
1343 }
1344
1345 /// Return a metadata-only slice using one [`StridedSliceSpec`] per axis.
1346 ///
1347 /// # Examples
1348 ///
1349 /// ```rust
1350 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
1351 ///
1352 /// let data = [1_i32, 2, 3];
1353 /// let view = TypedTensorView::from_slice(vec![3], vec![1], 0, &data)?;
1354 /// let reversed = view.try_slice(&[StridedSliceSpec::reverse()])?;
1355 /// assert_eq!(reversed.get(&[0]), Some(&3));
1356 /// # Ok::<(), tenferro_tensor::Error>(())
1357 /// ```
1358 /// # Errors
1359 ///
1360 /// Returns [`crate::Error::Validation`] with
1361 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
1362 /// count differs from the view rank,
1363 /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
1364 /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
1365 /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
1366 /// for slice arithmetic overflow, or
1367 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
1368 /// resulting layout exceeds the backing buffer.
1369 pub fn try_slice(&self, slices: &[StridedSliceSpec]) -> crate::Result<Self> {
1370 let specs = core_slice_specs(slices, self.shape(), "TypedTensorView::try_slice")?;
1371 let layout = self
1372 .layout
1373 .slice_view(specs, self.buffer.len())
1374 .map_err(|err| tensor_layout_error("TypedTensorView::try_slice", err))?;
1375 Ok(Self {
1376 buffer: self.buffer.clone(),
1377 layout,
1378 placement: self.placement.clone(),
1379 })
1380 }
1381
1382 /// Return a metadata-only slice along one axis.
1383 ///
1384 /// # Examples
1385 ///
1386 /// ```rust
1387 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
1388 ///
1389 /// let data = [1_i32, 2, 3, 4];
1390 /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
1391 /// assert_eq!(view.try_slice_axis(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
1392 /// # Ok::<(), tenferro_tensor::Error>(())
1393 /// ```
1394 /// # Errors
1395 ///
1396 /// Returns [`crate::Error::Validation`] with
1397 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
1398 /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
1399 /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
1400 /// invalid slice, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
1401 /// for slice arithmetic overflow, or
1402 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
1403 /// resulting layout exceeds the backing buffer.
1404 pub fn try_slice_axis(&self, axis: usize, slice: StridedSliceSpec) -> crate::Result<Self> {
1405 let slices = slice_axis_specs(
1406 self.shape().len(),
1407 axis,
1408 slice,
1409 "TypedTensorView::try_slice_axis",
1410 )?;
1411 self.try_slice(&slices)
1412 }
1413
1414 /// Return a metadata-only dynamic-rank reshape for contiguous column-major views.
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_slice(vec![2, 2], vec![1, 2], 0, &data)?;
1423 /// assert_eq!(view.try_reshape(&[4])?.shape(), &[4]);
1424 /// # Ok::<(), tenferro_tensor::Error>(())
1425 /// ```
1426 /// # Errors
1427 ///
1428 /// Returns [`crate::Error::Validation`] with
1429 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
1430 /// the source is not compact column-major,
1431 /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
1432 /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
1433 /// records the counts) when element counts differ,
1434 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
1435 /// arithmetic overflow, or
1436 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
1437 /// reshaped view exceeds the backing buffer.
1438 pub fn try_reshape(&self, shape: &[usize]) -> crate::Result<TypedTensorView<'a, T, DynRank>> {
1439 let layout = reshape_layout_dyn(
1440 &self.layout,
1441 shape,
1442 self.buffer.len(),
1443 "TypedTensorView::try_reshape",
1444 )?;
1445 Ok(TypedTensorView {
1446 buffer: self.buffer.clone(),
1447 layout,
1448 placement: self.placement.clone(),
1449 })
1450 }
1451}
1452
1453/// Mutable borrowed view of typed tensor storage with arbitrary strides.
1454///
1455/// # Examples
1456///
1457/// ```rust
1458/// use tenferro_tensor::TypedTensorViewMut;
1459///
1460/// let mut data = [1_i32, 2, 3];
1461/// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
1462/// *view.get_mut(&[2]).unwrap() = 10;
1463/// assert_eq!(view.as_read_only().get(&[2]), Some(&10));
1464/// # Ok::<(), tenferro_tensor::Error>(())
1465/// ```
1466#[derive(Debug)]
1467pub struct TypedTensorViewMut<'a, T, R: TensorRank = DynRank> {
1468 buffer: TensorBufferRefMut<'a, T>,
1469 layout: TensorLayout<R>,
1470 placement: Placement,
1471}
1472
1473/// Pair of mutable tensor views returned by disjoint multi-slice operations.
1474///
1475/// # Examples
1476///
1477/// ```rust
1478/// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut, TypedTensorViewMutPair};
1479///
1480/// let mut data = [1_i32, 2, 3, 4];
1481/// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
1482/// let pair: TypedTensorViewMutPair<'_, i32> = view
1483/// .try_multi_slice_mut(
1484/// &[StridedSliceSpec::new(0, Some(2), 1)],
1485/// &[StridedSliceSpec::new(2, Some(4), 1)],
1486/// )
1487/// ?
1488/// .unwrap();
1489/// assert_eq!(pair.0.shape(), &[2]);
1490/// assert_eq!(pair.1.shape(), &[2]);
1491/// # Ok::<(), tenferro_tensor::Error>(())
1492/// ```
1493pub type TypedTensorViewMutPair<'a, T, R = DynRank> =
1494 (TypedTensorViewMut<'a, T, R>, TypedTensorViewMut<'a, T, R>);
1495
1496impl<'a, T: 'static> TypedTensorViewMut<'a, T, DynRank> {
1497 /// Create a mutable dynamic-rank view over compact column-major host data.
1498 ///
1499 /// # Examples
1500 ///
1501 /// ```rust
1502 /// use tenferro_tensor::TypedTensorViewMut;
1503 ///
1504 /// let mut data = [1_i32, 2, 3, 4];
1505 /// let view = TypedTensorViewMut::from_col_major(&[2, 2], &mut data)?;
1506 /// assert_eq!(view.strides(), &[1, 2]);
1507 /// # Ok::<(), tenferro_tensor::Error>(())
1508 /// ```
1509 /// # Errors
1510 ///
1511 /// Returns [`crate::Error::Validation`] with
1512 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
1513 /// shape or offset arithmetic overflow, or
1514 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
1515 /// compact shape reaches beyond `data`.
1516 pub fn from_col_major(shape: &[usize], data: &'a mut [T]) -> crate::Result<Self> {
1517 let layout = TensorLayout::<DynRank>::compact(shape_vec(shape))
1518 .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_col_major", err))?;
1519 Self::from_buffer_ref_mut(
1520 shape_vec(layout.shape()),
1521 stride_vec(layout.strides()),
1522 layout.offset(),
1523 TensorBufferRefMut::Host(data),
1524 default_placement(),
1525 "TypedTensorViewMut::from_col_major",
1526 )
1527 }
1528
1529 /// Create a mutable host view from explicit layout metadata.
1530 ///
1531 /// Layouts where distinct logical elements can alias the same physical
1532 /// element are rejected.
1533 ///
1534 /// # Examples
1535 ///
1536 /// ```rust
1537 /// use tenferro_tensor::TypedTensorViewMut;
1538 ///
1539 /// let mut data = [1_i32, 2];
1540 /// assert!(TypedTensorViewMut::from_slice(vec![2], vec![0], 0, &mut data).is_err());
1541 /// ```
1542 /// # Errors
1543 ///
1544 /// Returns [`crate::Error::Validation`] with
1545 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
1546 /// `strides` have different ranks,
1547 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
1548 /// layout reaches beyond `data`,
1549 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
1550 /// logical elements alias, or
1551 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
1552 /// arithmetic overflow.
1553 pub fn from_slice(
1554 shape: impl AsRef<[usize]>,
1555 strides: impl AsRef<[isize]>,
1556 offset: isize,
1557 data: &'a mut [T],
1558 ) -> crate::Result<Self> {
1559 Self::from_buffer_ref_mut(
1560 shape_vec(shape.as_ref()),
1561 stride_vec(strides.as_ref()),
1562 offset,
1563 TensorBufferRefMut::Host(data),
1564 default_placement(),
1565 "TypedTensorViewMut::from_slice",
1566 )
1567 }
1568}
1569
1570impl<'a, T: 'static, R: TensorRank> TypedTensorViewMut<'a, T, R> {
1571 /// Create a rank-generic mutable host view from explicit layout metadata.
1572 ///
1573 /// # Examples
1574 ///
1575 /// ```rust
1576 /// use tenferro_tensor::{Rank, TypedTensorViewMut};
1577 ///
1578 /// let mut data = [1_i32, 2, 3, 4];
1579 /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
1580 /// assert_eq!(view.shape(), &[2, 2]);
1581 /// # Ok::<(), tenferro_tensor::Error>(())
1582 /// ```
1583 /// # Errors
1584 ///
1585 /// Returns [`crate::Error::Validation`] with
1586 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
1587 /// rank does not match `shape` or `strides`,
1588 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
1589 /// layout reaches beyond `data`,
1590 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
1591 /// logical elements alias, or
1592 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
1593 /// arithmetic overflow.
1594 pub fn from_slice_ranked(
1595 shape: impl Into<R::Shape>,
1596 strides: impl Into<R::Strides>,
1597 offset: isize,
1598 data: &'a mut [T],
1599 ) -> crate::Result<Self> {
1600 Self::from_buffer_ref_mut(
1601 shape,
1602 strides,
1603 offset,
1604 TensorBufferRefMut::Host(data),
1605 default_placement(),
1606 "TypedTensorViewMut::from_slice_ranked",
1607 )
1608 }
1609
1610 fn from_buffer_ref_mut(
1611 shape: impl Into<R::Shape>,
1612 strides: impl Into<R::Strides>,
1613 offset: isize,
1614 buffer: TensorBufferRefMut<'a, T>,
1615 placement: Placement,
1616 op: &'static str,
1617 ) -> crate::Result<Self> {
1618 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
1619 .map_err(|err| tensor_layout_error(op, err))?;
1620 layout
1621 .validate_mutable_no_overlap()
1622 .map_err(|err| tensor_layout_error(op, err))?;
1623 Ok(Self {
1624 buffer,
1625 layout,
1626 placement,
1627 })
1628 }
1629
1630 /// Return the logical shape.
1631 ///
1632 /// # Examples
1633 ///
1634 /// ```rust
1635 /// use tenferro_tensor::TypedTensorViewMut;
1636 ///
1637 /// let mut data = [0_i32; 2];
1638 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1639 /// assert_eq!(view.shape(), &[2]);
1640 /// # Ok::<(), tenferro_tensor::Error>(())
1641 /// ```
1642 pub fn shape(&self) -> &[usize] {
1643 self.layout.shape()
1644 }
1645
1646 /// Return strides in element units.
1647 ///
1648 /// # Examples
1649 ///
1650 /// ```rust
1651 /// use tenferro_tensor::TypedTensorViewMut;
1652 ///
1653 /// let mut data = [0_i32; 2];
1654 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![-1], 1, &mut data)?;
1655 /// assert_eq!(view.strides(), &[-1]);
1656 /// # Ok::<(), tenferro_tensor::Error>(())
1657 /// ```
1658 pub fn strides(&self) -> &[isize] {
1659 self.layout.strides()
1660 }
1661
1662 /// Return the physical element offset.
1663 ///
1664 /// # Examples
1665 ///
1666 /// ```rust
1667 /// use tenferro_tensor::TypedTensorViewMut;
1668 ///
1669 /// let mut data = [1_i32, 2];
1670 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 1, &mut data)?;
1671 /// assert_eq!(view.offset(), 1);
1672 /// # Ok::<(), tenferro_tensor::Error>(())
1673 /// ```
1674 pub fn offset(&self) -> isize {
1675 self.layout.offset()
1676 }
1677
1678 /// Return the borrowed host storage backing this view.
1679 ///
1680 /// This exposes the entire backing host allocation, not just the logical
1681 /// slice covered by this view. Use [`TypedTensorViewMut::as_read_only`]
1682 /// with [`TypedTensorView::as_slice`] when the caller needs the contiguous
1683 /// logical region instead.
1684 ///
1685 /// # Examples
1686 ///
1687 /// ```rust
1688 /// use tenferro_tensor::TypedTensorViewMut;
1689 ///
1690 /// let mut data = [1_i32, 2];
1691 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1692 /// assert_eq!(view.host_storage()?, &[1, 2]);
1693 /// # Ok::<(), tenferro_tensor::Error>(())
1694 /// ```
1695 /// # Errors
1696 ///
1697 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
1698 /// buffer; backend storage must be downloaded before host inspection.
1699 pub fn host_storage(&self) -> crate::Result<&[T]> {
1700 match &self.buffer {
1701 TensorBufferRefMut::Host(data) => Ok(data),
1702 TensorBufferRefMut::Backend(_) => Err(crate::Error::runtime_state(
1703 "TypedTensorViewMut::host_storage",
1704 "backend buffers cannot expose host storage; download explicitly first",
1705 )),
1706 }
1707 }
1708
1709 /// Mutably borrow the host storage backing this view.
1710 ///
1711 /// This exposes the entire backing host allocation, not just the logical
1712 /// slice covered by this view. Prefer scalar element accessors when mutating
1713 /// a logical region; tensor-sized copies belong to an active backend.
1714 ///
1715 /// # Examples
1716 ///
1717 /// ```rust
1718 /// use tenferro_tensor::TypedTensorViewMut;
1719 ///
1720 /// let mut data = [1_i32, 2];
1721 /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1722 /// view.host_storage_mut()?[0] = 3;
1723 /// assert_eq!(view.get(&[0]), Some(&3));
1724 /// # Ok::<(), tenferro_tensor::Error>(())
1725 /// ```
1726 /// # Errors
1727 ///
1728 /// Returns [`crate::Error::RuntimeState`] when this view wraps a backend
1729 /// buffer; backend storage must be downloaded before host inspection.
1730 pub fn host_storage_mut(&mut self) -> crate::Result<&mut [T]> {
1731 match &mut self.buffer {
1732 TensorBufferRefMut::Host(data) => Ok(data),
1733 TensorBufferRefMut::Backend(_) => Err(crate::Error::runtime_state(
1734 "TypedTensorViewMut::host_storage_mut",
1735 "backend buffers cannot expose mutable host storage; download explicitly first",
1736 )),
1737 }
1738 }
1739
1740 /// Return the number of logical elements in this view.
1741 ///
1742 /// # Examples
1743 ///
1744 /// ```rust
1745 /// use tenferro_tensor::TypedTensorViewMut;
1746 ///
1747 /// let mut data = [0_i32; 6];
1748 /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
1749 /// assert_eq!(view.n_elements(), 6);
1750 /// # Ok::<(), tenferro_tensor::Error>(())
1751 /// ```
1752 pub fn n_elements(&self) -> usize {
1753 // Invariant: public mutable view constructors validate logical element count.
1754 match checked_view_element_count(self.shape(), "TypedTensorViewMut::n_elements") {
1755 Ok(n) => n,
1756 Err(err) => {
1757 unreachable!("TypedTensorViewMut layout shape is validated at construction: {err}")
1758 }
1759 }
1760 }
1761
1762 /// Return layout metadata for this view.
1763 ///
1764 /// # Examples
1765 ///
1766 /// ```rust
1767 /// use tenferro_tensor::TypedTensorViewMut;
1768 ///
1769 /// let mut data = [1_i32, 2];
1770 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1771 /// assert!(view.layout().is_compact_col_major().unwrap());
1772 /// # Ok::<(), tenferro_tensor::Error>(())
1773 /// ```
1774 pub fn layout(&self) -> &TensorLayout<R> {
1775 &self.layout
1776 }
1777
1778 /// Return placement metadata for this view.
1779 ///
1780 /// # Examples
1781 ///
1782 /// ```rust
1783 /// use tenferro_tensor::{MemoryKind, TypedTensorViewMut};
1784 ///
1785 /// let mut data = [1_i32];
1786 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1787 /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
1788 /// # Ok::<(), tenferro_tensor::Error>(())
1789 /// ```
1790 pub fn placement(&self) -> &Placement {
1791 &self.placement
1792 }
1793
1794 /// Return the backend allocation for backend integrations.
1795 #[doc(hidden)]
1796 pub fn backend_buffer(&self) -> Option<&Arc<dyn BackendBuffer<T>>> {
1797 match &self.buffer {
1798 TensorBufferRefMut::Host(_) => None,
1799 TensorBufferRefMut::Backend(buffer) => Some(buffer),
1800 }
1801 }
1802
1803 /// Compute the physical element offset for a logical index.
1804 ///
1805 /// # Examples
1806 ///
1807 /// ```rust
1808 /// use tenferro_tensor::TypedTensorViewMut;
1809 ///
1810 /// let mut data = [1_i32, 2, 3];
1811 /// let view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
1812 /// assert_eq!(view.linear_offset(&[2]), Some(0));
1813 /// # Ok::<(), tenferro_tensor::Error>(())
1814 /// ```
1815 pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
1816 checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
1817 }
1818
1819 /// Compute the physical element offset for a logical index, returning a typed error.
1820 ///
1821 /// # Examples
1822 ///
1823 /// ```rust
1824 /// use tenferro_tensor::TypedTensorViewMut;
1825 ///
1826 /// let mut data = [1_i32, 2, 3];
1827 /// let view = TypedTensorViewMut::from_slice([3], [-1], 2, &mut data)?;
1828 /// assert_eq!(view.layout_linear_offset(&[2])?, 0);
1829 /// # Ok::<(), tenferro_tensor::Error>(())
1830 /// ```
1831 /// # Errors
1832 ///
1833 /// Returns [`crate::Error::Validation`] with
1834 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
1835 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
1836 /// when an index is outside its axis extent, or
1837 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
1838 /// arithmetic overflows.
1839 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
1840 checked_view_offset_result(
1841 self.shape(),
1842 self.strides(),
1843 self.offset(),
1844 indices,
1845 "TypedTensorViewMut::layout_linear_offset",
1846 )
1847 }
1848
1849 /// Return whether this mutable view is compact column-major.
1850 ///
1851 /// # Examples
1852 ///
1853 /// ```rust
1854 /// use tenferro_tensor::TypedTensorViewMut;
1855 ///
1856 /// let mut data = [1_i32, 2];
1857 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
1858 /// assert!(view.is_col_major_contiguous()?);
1859 /// # Ok::<(), tenferro_tensor::Error>(())
1860 /// ```
1861 /// # Errors
1862 ///
1863 /// Returns [`crate::Error::Validation`] with
1864 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
1865 /// compactness arithmetic overflows.
1866 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
1867 self.layout
1868 .is_compact_col_major()
1869 .map_err(|err| tensor_layout_error("TypedTensorViewMut::is_col_major_contiguous", err))
1870 }
1871
1872 /// Return a compact string summary of this mutable view's layout metadata.
1873 ///
1874 /// # Examples
1875 ///
1876 /// ```rust
1877 /// use tenferro_tensor::TypedTensorViewMut;
1878 ///
1879 /// let mut data = [1_i32, 2];
1880 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
1881 /// assert!(view.layout_summary().contains("shape=[2]"));
1882 /// # Ok::<(), tenferro_tensor::Error>(())
1883 /// ```
1884 pub fn layout_summary(&self) -> String {
1885 layout_summary(self.shape(), self.strides(), self.offset())
1886 }
1887
1888 /// Assert this mutable view is compact column-major.
1889 ///
1890 /// # Examples
1891 ///
1892 /// ```rust
1893 /// use tenferro_tensor::TypedTensorViewMut;
1894 ///
1895 /// let mut data = [1_i32, 2];
1896 /// let view = TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?;
1897 /// view.assert_col_major_contiguous()?;
1898 /// # Ok::<(), tenferro_tensor::Error>(())
1899 /// ```
1900 /// # Errors
1901 ///
1902 /// Returns [`crate::Error::Validation`] with
1903 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
1904 /// compactness arithmetic overflows, or
1905 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
1906 /// view is not compact column-major.
1907 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
1908 assert_layout_col_major_contiguous(
1909 self.is_col_major_contiguous()?,
1910 self.shape(),
1911 self.strides(),
1912 self.offset(),
1913 "TypedTensorViewMut::assert_col_major_contiguous",
1914 )
1915 }
1916
1917 /// Borrow one host element by logical index.
1918 ///
1919 /// # Examples
1920 ///
1921 /// ```rust
1922 /// use tenferro_tensor::TypedTensorViewMut;
1923 ///
1924 /// let mut data = [1_i32, 2];
1925 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1926 /// assert_eq!(view.get(&[1]), Some(&2));
1927 /// # Ok::<(), tenferro_tensor::Error>(())
1928 /// ```
1929 pub fn get(&self, indices: &[usize]) -> Option<&T> {
1930 let offset = self.linear_offset(indices)?;
1931 match &self.buffer {
1932 TensorBufferRefMut::Host(data) => data.get(offset),
1933 TensorBufferRefMut::Backend(_) => None,
1934 }
1935 }
1936
1937 /// Mutably borrow one host element by logical index.
1938 ///
1939 /// # Examples
1940 ///
1941 /// ```rust
1942 /// use tenferro_tensor::TypedTensorViewMut;
1943 ///
1944 /// let mut data = [1_i32, 2];
1945 /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1946 /// *view.get_mut(&[1]).unwrap() = 20;
1947 /// assert_eq!(view.get(&[1]), Some(&20));
1948 /// # Ok::<(), tenferro_tensor::Error>(())
1949 /// ```
1950 pub fn get_mut(&mut self, indices: &[usize]) -> Option<&mut T> {
1951 let offset = self.linear_offset(indices)?;
1952 match &mut self.buffer {
1953 TensorBufferRefMut::Host(data) => data.get_mut(offset),
1954 TensorBufferRefMut::Backend(_) => None,
1955 }
1956 }
1957
1958 /// Borrow this mutable view as a read-only view.
1959 ///
1960 /// # Examples
1961 ///
1962 /// ```rust
1963 /// use tenferro_tensor::TypedTensorViewMut;
1964 ///
1965 /// let mut data = [1_i32];
1966 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1967 /// assert_eq!(view.as_read_only().get(&[0]), Some(&1));
1968 /// # Ok::<(), tenferro_tensor::Error>(())
1969 /// ```
1970 pub fn as_read_only(&self) -> TypedTensorView<'_, T, R> {
1971 let buffer = match &self.buffer {
1972 TensorBufferRefMut::Host(data) => TensorBufferRef::Host(data),
1973 TensorBufferRefMut::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
1974 };
1975 TypedTensorView {
1976 buffer,
1977 layout: self.layout.clone(),
1978 placement: self.placement.clone(),
1979 }
1980 }
1981
1982 /// Convert this mutable view into a read-only view.
1983 ///
1984 /// # Examples
1985 ///
1986 /// ```rust
1987 /// use tenferro_tensor::TypedTensorViewMut;
1988 ///
1989 /// let mut data = [1_i32];
1990 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1991 /// assert_eq!(view.into_read_only().get(&[0]), Some(&1));
1992 /// # Ok::<(), tenferro_tensor::Error>(())
1993 /// ```
1994 pub fn into_read_only(self) -> TypedTensorView<'a, T, R> {
1995 let buffer = match self.buffer {
1996 TensorBufferRefMut::Host(data) => TensorBufferRef::Host(data),
1997 TensorBufferRefMut::Backend(buffer) => TensorBufferRef::Backend(buffer),
1998 };
1999 TypedTensorView {
2000 buffer,
2001 layout: self.layout,
2002 placement: self.placement,
2003 }
2004 }
2005
2006 /// Consume this mutable view and return a metadata-only axis permutation.
2007 ///
2008 /// # Examples
2009 ///
2010 /// ```rust
2011 /// use tenferro_tensor::{Rank, TypedTensorViewMut};
2012 ///
2013 /// let mut data = [1_i32, 2, 3, 4];
2014 /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
2015 /// let transposed = view.transpose_view([1, 0])?;
2016 /// assert_eq!(transposed.strides(), &[2, 1]);
2017 /// # Ok::<(), tenferro_tensor::Error>(())
2018 /// ```
2019 /// # Errors
2020 ///
2021 /// Returns [`crate::Error::Validation`] with
2022 /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
2023 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
2024 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
2025 /// not a valid permutation, [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`]
2026 /// when the permutation creates aliases, or
2027 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
2028 /// arithmetic overflow.
2029 pub fn transpose_view(
2030 self,
2031 axes: impl AsRef<[usize]>,
2032 ) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
2033 let Self {
2034 buffer,
2035 layout,
2036 placement,
2037 } = self;
2038 let layout = layout
2039 .transpose_view(axes)
2040 .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
2041 layout
2042 .validate_mutable_no_overlap()
2043 .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
2044 match buffer {
2045 TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
2046 buffer: TensorBufferRefMut::Host(data),
2047 layout,
2048 placement,
2049 }),
2050 TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
2051 buffer: TensorBufferRefMut::Backend(buffer),
2052 layout,
2053 placement,
2054 }),
2055 }
2056 }
2057
2058 /// Return a mutable metadata-only slice using one [`StridedSliceSpec`] per axis.
2059 ///
2060 /// # Examples
2061 ///
2062 /// ```rust
2063 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
2064 ///
2065 /// let mut data = [1_i32, 2, 3];
2066 /// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![1], 0, &mut data)?;
2067 /// *view.try_slice(&[StridedSliceSpec::reverse()])?.get_mut(&[0]).unwrap() = 30;
2068 /// assert_eq!(view.get(&[2]), Some(&30));
2069 /// # Ok::<(), tenferro_tensor::Error>(())
2070 /// ```
2071 /// # Errors
2072 ///
2073 /// Returns [`crate::Error::Validation`] with
2074 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the slice
2075 /// count differs from the view rank,
2076 /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
2077 /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
2078 /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
2079 /// when the result exceeds the backing buffer,
2080 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
2081 /// logical elements alias, or
2082 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
2083 /// arithmetic overflow.
2084 pub fn try_slice(
2085 &mut self,
2086 slices: &[StridedSliceSpec],
2087 ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
2088 let specs = core_slice_specs(slices, self.shape(), "TypedTensorViewMut::try_slice")?;
2089 let layout = self
2090 .layout
2091 .slice_view(specs, self.buffer.len())
2092 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_slice", err))?;
2093 layout
2094 .validate_mutable_no_overlap()
2095 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_slice", err))?;
2096 let placement = self.placement.clone();
2097 match &mut self.buffer {
2098 TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
2099 buffer: TensorBufferRefMut::Host(data),
2100 layout,
2101 placement,
2102 }),
2103 TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
2104 buffer: TensorBufferRefMut::Backend(Arc::clone(buffer)),
2105 layout,
2106 placement,
2107 }),
2108 }
2109 }
2110
2111 /// Return a mutable metadata-only slice along one axis.
2112 ///
2113 /// # Examples
2114 ///
2115 /// ```rust
2116 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
2117 ///
2118 /// let mut data = [1_i32, 2, 3, 4];
2119 /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
2120 /// assert_eq!(view.try_slice_axis(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
2121 /// # Ok::<(), tenferro_tensor::Error>(())
2122 /// ```
2123 /// # Errors
2124 ///
2125 /// Returns [`crate::Error::Validation`] with
2126 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when `axis`
2127 /// is outside the view rank, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
2128 /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for an
2129 /// invalid slice, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
2130 /// when the result exceeds the backing buffer,
2131 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
2132 /// logical elements alias, or
2133 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
2134 /// arithmetic overflow.
2135 pub fn try_slice_axis(
2136 &mut self,
2137 axis: usize,
2138 slice: StridedSliceSpec,
2139 ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
2140 let slices = slice_axis_specs(
2141 self.shape().len(),
2142 axis,
2143 slice,
2144 "TypedTensorViewMut::try_slice_axis",
2145 )?;
2146 self.try_slice(&slices)
2147 }
2148
2149 /// Return two mutable metadata-only slices when their physical ranges are disjoint.
2150 ///
2151 /// # Examples
2152 ///
2153 /// ```rust
2154 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
2155 ///
2156 /// let mut data = [1_i32, 2, 3, 4];
2157 /// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
2158 /// let (left, right) = view
2159 /// .try_multi_slice_mut(
2160 /// &[StridedSliceSpec::new(0, Some(2), 1)],
2161 /// &[StridedSliceSpec::new(2, Some(4), 1)],
2162 /// )
2163 /// ?
2164 /// .unwrap();
2165 /// assert_eq!(left.shape(), &[2]);
2166 /// assert_eq!(right.shape(), &[2]);
2167 /// # Ok::<(), tenferro_tensor::Error>(())
2168 /// ```
2169 /// # Errors
2170 ///
2171 /// Returns [`crate::Error::Validation`] with
2172 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for either
2173 /// slice count, [`tenferro_tensor_core::ValidationError::InvalidSliceStep`]
2174 /// or [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for
2175 /// invalid parameters, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
2176 /// when a result exceeds the backing buffer,
2177 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
2178 /// a result aliases, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
2179 /// for a negative reachable offset, or
2180 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
2181 /// arithmetic overflow. The method returns `Ok(None)` when the two ranges
2182 /// overlap or the view uses backend storage.
2183 pub fn try_multi_slice_mut(
2184 &mut self,
2185 first: &[StridedSliceSpec],
2186 second: &[StridedSliceSpec],
2187 ) -> crate::Result<Option<TypedTensorViewMutPair<'_, T, R>>> {
2188 let op = "TypedTensorViewMut::try_multi_slice_mut";
2189 let first_specs = core_slice_specs(first, self.shape(), op)?;
2190 let second_specs = core_slice_specs(second, self.shape(), op)?;
2191 let buffer_len = self.buffer.len();
2192 let first_layout = self
2193 .layout
2194 .slice_view(first_specs, buffer_len)
2195 .map_err(|err| tensor_layout_error(op, err))?;
2196 let second_layout = self
2197 .layout
2198 .slice_view(second_specs, buffer_len)
2199 .map_err(|err| tensor_layout_error(op, err))?;
2200 first_layout
2201 .validate_mutable_no_overlap()
2202 .map_err(|err| tensor_layout_error(op, err))?;
2203 second_layout
2204 .validate_mutable_no_overlap()
2205 .map_err(|err| tensor_layout_error(op, err))?;
2206
2207 match (
2208 reachable_layout_span(
2209 first_layout.shape(),
2210 first_layout.strides(),
2211 first_layout.offset(),
2212 )?,
2213 reachable_layout_span(
2214 second_layout.shape(),
2215 second_layout.strides(),
2216 second_layout.offset(),
2217 )?,
2218 ) {
2219 (Some(first_span), Some(second_span)) => {
2220 let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
2221 let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
2222 let (first_data, second_data) = match &mut self.buffer {
2223 TensorBufferRefMut::Host(data) => {
2224 match split_two_mut_ranges(data, first_span, second_span) {
2225 Some(ranges) => ranges,
2226 None => return Ok(None),
2227 }
2228 }
2229 TensorBufferRefMut::Backend(_) => return Ok(None),
2230 };
2231 let first_view = view_mut_from_layout_and_slice(
2232 &first_layout,
2233 first_offset,
2234 first_data,
2235 self.placement.clone(),
2236 )?;
2237 let second_view = view_mut_from_layout_and_slice(
2238 &second_layout,
2239 second_offset,
2240 second_data,
2241 self.placement.clone(),
2242 )?;
2243 Ok(Some((first_view, second_view)))
2244 }
2245 (None, Some(second_span)) => {
2246 let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
2247 let (_, after_start) = match &mut self.buffer {
2248 TensorBufferRefMut::Host(data) => data.split_at_mut(second_span.0),
2249 TensorBufferRefMut::Backend(_) => return Ok(None),
2250 };
2251 let (second_data, _) = after_start.split_at_mut(second_span.1 - second_span.0 + 1);
2252 let first_view = view_mut_from_layout_and_slice(
2253 &first_layout,
2254 0,
2255 &mut [],
2256 self.placement.clone(),
2257 )?;
2258 let second_view = view_mut_from_layout_and_slice(
2259 &second_layout,
2260 second_offset,
2261 second_data,
2262 self.placement.clone(),
2263 )?;
2264 Ok(Some((first_view, second_view)))
2265 }
2266 (Some(first_span), None) => {
2267 let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
2268 let (_, after_start) = match &mut self.buffer {
2269 TensorBufferRefMut::Host(data) => data.split_at_mut(first_span.0),
2270 TensorBufferRefMut::Backend(_) => return Ok(None),
2271 };
2272 let (first_data, _) = after_start.split_at_mut(first_span.1 - first_span.0 + 1);
2273 let first_view = view_mut_from_layout_and_slice(
2274 &first_layout,
2275 first_offset,
2276 first_data,
2277 self.placement.clone(),
2278 )?;
2279 let second_view = view_mut_from_layout_and_slice(
2280 &second_layout,
2281 0,
2282 &mut [],
2283 self.placement.clone(),
2284 )?;
2285 Ok(Some((first_view, second_view)))
2286 }
2287 (None, None) => {
2288 let first_view = view_mut_from_layout_and_slice(
2289 &first_layout,
2290 0,
2291 &mut [],
2292 self.placement.clone(),
2293 )?;
2294 let second_view = view_mut_from_layout_and_slice(
2295 &second_layout,
2296 0,
2297 &mut [],
2298 self.placement.clone(),
2299 )?;
2300 Ok(Some((first_view, second_view)))
2301 }
2302 }
2303 }
2304
2305 /// Return a mutable metadata-only dynamic-rank reshape for contiguous views.
2306 ///
2307 /// # Examples
2308 ///
2309 /// ```rust
2310 /// use tenferro_tensor::TypedTensorViewMut;
2311 ///
2312 /// let mut data = [1_i32, 2, 3, 4];
2313 /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
2314 /// assert_eq!(view.try_reshape(&[4])?.shape(), &[4]);
2315 /// # Ok::<(), tenferro_tensor::Error>(())
2316 /// ```
2317 /// # Errors
2318 ///
2319 /// Returns [`crate::Error::Validation`] with
2320 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
2321 /// the source is not compact column-major,
2322 /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
2323 /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
2324 /// records the counts) when element counts differ,
2325 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
2326 /// the reshaped layout aliases, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
2327 /// for shape or layout arithmetic overflow, or
2328 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2329 /// reshaped view exceeds the backing buffer.
2330 pub fn try_reshape(
2331 &mut self,
2332 shape: &[usize],
2333 ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>> {
2334 let layout = reshape_layout_dyn(
2335 &self.layout,
2336 shape,
2337 self.buffer.len(),
2338 "TypedTensorViewMut::try_reshape",
2339 )?;
2340 layout
2341 .validate_mutable_no_overlap()
2342 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_reshape", err))?;
2343 let placement = self.placement.clone();
2344 match &mut self.buffer {
2345 TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
2346 buffer: TensorBufferRefMut::Host(data),
2347 layout,
2348 placement,
2349 }),
2350 TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
2351 buffer: TensorBufferRefMut::Backend(Arc::clone(buffer)),
2352 layout,
2353 placement,
2354 }),
2355 }
2356 }
2357}
2358
2359/// Runtime scalar dtype tag.
2360///
2361/// # Examples
2362///
2363/// ```rust
2364/// use tenferro_tensor::DType;
2365///
2366/// assert_eq!(DType::F64 as u8, DType::F64 as u8);
2367/// ```
2368#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2369pub enum DType {
2370 F32,
2371 F64,
2372 I32,
2373 I64,
2374 Bool,
2375 C32,
2376 C64,
2377}
2378
2379/// Sealed trait for scalar types that can be stored in a [`Tensor`].
2380///
2381/// This trait is implemented for `f64`, `f32`, `i32`, `i64`, `bool`,
2382/// [`Complex64`], and [`Complex32`].
2383///
2384/// # Examples
2385///
2386/// ```
2387/// use tenferro_tensor::TensorScalar;
2388///
2389/// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
2390/// assert_eq!(tensor.as_slice::<f64>()?, [1.0, 2.0].as_slice());
2391/// # Ok::<(), tenferro_tensor::Error>(())
2392/// ```
2393pub trait TensorScalar: Copy + Clone + Send + Sync + 'static + private::Sealed {
2394 /// Real-valued counterpart of this scalar type.
2395 type Real: TensorScalar;
2396
2397 /// The [`DType`] tag corresponding to this scalar type.
2398 ///
2399 /// # Examples
2400 ///
2401 /// ```
2402 /// use tenferro_tensor::{DType, TensorScalar};
2403 ///
2404 /// assert_eq!(f64::dtype(), DType::F64);
2405 /// assert_eq!(f32::dtype(), DType::F32);
2406 /// ```
2407 fn dtype() -> DType;
2408
2409 /// Wrap typed column-major data into a [`Tensor`] enum variant.
2410 /// # Errors
2411 ///
2412 /// Returns [`crate::Error::Validation`] with
2413 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
2414 /// the shape product differs from `data.len()`, or
2415 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
2416 /// arithmetic overflows.
2417 fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor>;
2418
2419 /// Wrap a typed tensor into its dynamic [`Tensor`] enum variant.
2420 ///
2421 /// # Examples
2422 ///
2423 /// ```
2424 /// use tenferro_tensor::{Tensor, TensorScalar, TypedTensor};
2425 ///
2426 /// let typed = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![3.0])?;
2427 /// let tensor = <f64 as TensorScalar>::typed_tensor_into_tensor(typed);
2428 /// assert!(matches!(tensor, Tensor::F64(_)));
2429 /// # Ok::<(), tenferro_tensor::Error>(())
2430 /// ```
2431 fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor;
2432
2433 /// Borrow a typed tensor as a dtype-erased [`TensorRead`] view.
2434 ///
2435 /// This keeps the typed tensor borrowed instead of copying host data into
2436 /// a new dynamic tensor.
2437 ///
2438 /// # Examples
2439 ///
2440 /// ```
2441 /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
2442 ///
2443 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
2444 /// let read = f64::tensor_read(&tensor);
2445 /// assert_eq!(read.dtype(), DType::F64);
2446 /// assert_eq!(read.shape(), &[2]);
2447 /// ```
2448 fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_>;
2449
2450 /// Wrap a typed borrowed view as a dtype-erased [`TensorView`].
2451 ///
2452 /// # Examples
2453 ///
2454 /// ```
2455 /// use tenferro_tensor::{DType, TensorScalar, TypedTensorView};
2456 ///
2457 /// let data = [1.0_f64];
2458 /// let view = TypedTensorView::from_col_major(&[1], &data)?;
2459 /// assert_eq!(f64::tensor_view(view).dtype(), DType::F64);
2460 /// # Ok::<(), tenferro_tensor::Error>(())
2461 /// ```
2462 fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a>;
2463
2464 /// Wrap a typed mutable borrowed view as a dtype-erased [`TensorViewMut`].
2465 ///
2466 /// # Examples
2467 ///
2468 /// ```
2469 /// use tenferro_tensor::{DType, TensorScalar, TypedTensorViewMut};
2470 ///
2471 /// let mut data = [1.0_f64, 2.0];
2472 /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
2473 /// let erased = f64::tensor_view_mut(view);
2474 /// assert_eq!(erased.dtype(), DType::F64);
2475 /// assert_eq!(erased.shape(), &[2]);
2476 /// # Ok::<(), tenferro_tensor::Error>(())
2477 /// ```
2478 fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a>;
2479
2480 /// Mutably borrow a typed tensor as a dtype-erased [`TensorWrite`] view.
2481 ///
2482 /// This keeps the typed output borrowed instead of wrapping it in a
2483 /// temporary dynamic tensor.
2484 ///
2485 /// # Examples
2486 ///
2487 /// ```
2488 /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
2489 ///
2490 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0]).unwrap();
2491 /// let write = f64::tensor_write(&mut tensor);
2492 /// assert_eq!(write.dtype(), DType::F64);
2493 /// ```
2494 fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_>;
2495
2496 /// Borrow the host data from a [`Tensor`].
2497 /// # Errors
2498 ///
2499 /// Returns [`crate::Error::Validation`] with
2500 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
2501 /// is not the scalar type represented by this implementation, or
2502 /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
2503 /// storage that has not been downloaded.
2504 fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]>;
2505
2506 /// Mutably borrow the host data from a [`Tensor`].
2507 ///
2508 /// # Examples
2509 ///
2510 /// ```
2511 /// use tenferro_tensor::{Tensor, TensorScalar};
2512 ///
2513 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
2514 /// <f64 as TensorScalar>::as_slice_mut(&mut tensor)?[0] = 3.0;
2515 ///
2516 /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
2517 /// # Ok::<(), tenferro_tensor::Error>(())
2518 /// ```
2519 /// # Errors
2520 ///
2521 /// Returns [`crate::Error::Validation`] with
2522 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
2523 /// is not the scalar type represented by this implementation, or
2524 /// [`crate::Error::RuntimeState`] when the matching tensor uses backend
2525 /// storage that has not been downloaded.
2526 fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]>;
2527
2528 /// Extract a [`TypedTensor<Self>`] from a dynamic [`Tensor`].
2529 ///
2530 /// # Examples
2531 ///
2532 /// ```
2533 /// use tenferro_tensor::{Tensor, TensorScalar};
2534 ///
2535 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
2536 /// let typed = <f64 as TensorScalar>::into_typed(tensor)?;
2537 ///
2538 /// assert_eq!(typed.as_slice()?, &[1.0, 2.0]);
2539 /// # Ok::<(), tenferro_tensor::Error>(())
2540 /// ```
2541 /// # Errors
2542 ///
2543 /// Returns [`crate::Error::Validation`] with
2544 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `tensor`
2545 /// is not the scalar type represented by this implementation.
2546 fn into_typed(tensor: Tensor) -> crate::Result<TypedTensor<Self>>;
2547}
2548
2549mod private {
2550 pub trait Sealed {}
2551
2552 impl Sealed for f64 {}
2553 impl Sealed for f32 {}
2554 impl Sealed for i32 {}
2555 impl Sealed for i64 {}
2556 impl Sealed for bool {}
2557 impl Sealed for num_complex::Complex64 {}
2558 impl Sealed for num_complex::Complex32 {}
2559}
2560
2561macro_rules! impl_tensor_scalar {
2562 ($ty:ty, $real:ty, $dtype:ident, $variant:ident) => {
2563 impl TensorScalar for $ty {
2564 type Real = $real;
2565
2566 fn dtype() -> DType {
2567 DType::$dtype
2568 }
2569
2570 fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor> {
2571 TypedTensor::from_vec_col_major(shape, data).map(Tensor::$variant)
2572 }
2573
2574 fn typed_tensor_into_tensor(tensor: TypedTensor<Self>) -> Tensor {
2575 Tensor::$variant(tensor)
2576 }
2577
2578 fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_> {
2579 TensorRead::from_view(TensorView::$variant(tensor.as_view()))
2580 }
2581
2582 fn tensor_view<'a>(view: TypedTensorView<'a, Self>) -> TensorView<'a> {
2583 TensorView::$variant(view)
2584 }
2585
2586 fn tensor_view_mut<'a>(view: TypedTensorViewMut<'a, Self>) -> TensorViewMut<'a> {
2587 TensorViewMut::$variant(view)
2588 }
2589
2590 fn tensor_write(tensor: &mut TypedTensor<Self>) -> TensorWrite<'_> {
2591 TensorWrite::from_view(TensorViewMut::$variant(tensor.as_view_mut()))
2592 }
2593
2594 fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]> {
2595 let actual = tensor.dtype();
2596 match tensor {
2597 Tensor::$variant(t) => t.host_data(),
2598 _ => Err(crate::Error::validation(
2599 "Tensor::as_slice",
2600 ValidationError::DTypeMismatch {
2601 expected: crate::core_dtype(Self::dtype()),
2602 actual: crate::core_dtype(actual),
2603 },
2604 )),
2605 }
2606 }
2607
2608 fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]> {
2609 let actual = tensor.dtype();
2610 match tensor {
2611 Tensor::$variant(t) => t.host_data_mut(),
2612 _ => Err(crate::Error::validation(
2613 "Tensor::as_slice_mut",
2614 ValidationError::DTypeMismatch {
2615 expected: crate::core_dtype(Self::dtype()),
2616 actual: crate::core_dtype(actual),
2617 },
2618 )),
2619 }
2620 }
2621
2622 fn into_typed(tensor: Tensor) -> crate::Result<TypedTensor<Self>> {
2623 let actual = tensor.dtype();
2624 match tensor {
2625 Tensor::$variant(inner) => Ok(inner),
2626 _ => Err(crate::Error::validation(
2627 "TensorScalar::into_typed",
2628 ValidationError::DTypeMismatch {
2629 expected: crate::core_dtype(Self::dtype()),
2630 actual: crate::core_dtype(actual),
2631 },
2632 )),
2633 }
2634 }
2635 }
2636 };
2637}
2638
2639impl_tensor_scalar!(f64, f64, F64, F64);
2640impl_tensor_scalar!(f32, f32, F32, F32);
2641impl_tensor_scalar!(i64, i64, I64, I64);
2642impl_tensor_scalar!(i32, i32, I32, I32);
2643impl_tensor_scalar!(bool, bool, Bool, Bool);
2644impl_tensor_scalar!(Complex64, f64, C64, C64);
2645impl_tensor_scalar!(Complex32, f32, C32, C32);
2646
2647/// Dynamic tensor enum over the supported scalar types.
2648///
2649/// The enum keeps dtype dynamic and rank dynamic. Use
2650/// [`TypedTensor<T, R>`](TypedTensor) directly when the scalar type or rank
2651/// should be represented in Rust's type system.
2652///
2653/// # Examples
2654///
2655/// ```rust
2656/// use tenferro_tensor::{Tensor, TypedTensor};
2657///
2658/// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
2659/// assert_eq!(t.shape(), &[2]);
2660///
2661/// let erased = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 2.0]).unwrap();
2662/// assert_eq!(erased.shape().len(), 2);
2663/// ```
2664#[derive(Clone, Debug)]
2665pub enum Tensor {
2666 F32(TypedTensor<f32>),
2667 F64(TypedTensor<f64>),
2668 I32(TypedTensor<i32>),
2669 I64(TypedTensor<i64>),
2670 Bool(TypedTensor<bool>),
2671 C32(TypedTensor<Complex<f32>>),
2672 C64(TypedTensor<Complex<f64>>),
2673}
2674
2675/// Dynamic read-only borrowed tensor view.
2676///
2677/// `TensorView` keeps dtype erased while borrowing typed view metadata and
2678/// storage. Use [`TypedTensorView`] directly when the scalar type is statically
2679/// known.
2680///
2681/// # Examples
2682///
2683/// ```
2684/// use tenferro_tensor::{DType, TensorView, TypedTensorView};
2685///
2686/// let data = [1_i32, 2, 3, 4];
2687/// let typed = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data)?;
2688/// let view = TensorView::I32(typed);
2689///
2690/// assert_eq!(view.dtype(), DType::I32);
2691/// assert_eq!(view.shape(), &[2, 2]);
2692/// # Ok::<(), tenferro_tensor::Error>(())
2693/// ```
2694#[derive(Clone, Debug)]
2695pub enum TensorView<'a> {
2696 F32(TypedTensorView<'a, f32>),
2697 F64(TypedTensorView<'a, f64>),
2698 I32(TypedTensorView<'a, i32>),
2699 I64(TypedTensorView<'a, i64>),
2700 Bool(TypedTensorView<'a, bool>),
2701 C32(TypedTensorView<'a, Complex<f32>>),
2702 C64(TypedTensorView<'a, Complex<f64>>),
2703}
2704
2705/// Dynamic mutable borrowed tensor view.
2706///
2707/// `TensorViewMut` is the mutable counterpart to [`TensorView`]. It keeps the
2708/// dtype erased while preserving the typed mutable view's shape, strides, and
2709/// offset metadata.
2710///
2711/// # Examples
2712///
2713/// ```
2714/// use tenferro_tensor::{DType, TensorViewMut, TypedTensorViewMut};
2715///
2716/// let mut data = [1.0_f64, 2.0];
2717/// let view = TensorViewMut::F64(TypedTensorViewMut::from_slice([2], [1], 0, &mut data)?);
2718/// assert_eq!(view.dtype(), DType::F64);
2719/// # Ok::<(), tenferro_tensor::Error>(())
2720/// ```
2721#[allow(clippy::large_enum_variant)]
2722#[derive(Debug)]
2723pub enum TensorViewMut<'a> {
2724 F32(TypedTensorViewMut<'a, f32>),
2725 F64(TypedTensorViewMut<'a, f64>),
2726 I32(TypedTensorViewMut<'a, i32>),
2727 I64(TypedTensorViewMut<'a, i64>),
2728 Bool(TypedTensorViewMut<'a, bool>),
2729 C32(TypedTensorViewMut<'a, Complex<f32>>),
2730 C64(TypedTensorViewMut<'a, Complex<f64>>),
2731}
2732
2733/// Read-only tensor input accepted by synchronous eager kernels.
2734///
2735/// `TensorRead` lets kernels accept either an owned tensor reference or a
2736/// borrowed [`TensorView`] without forcing callers to materialize first.
2737/// The `View` variant preserves arbitrary strides and offsets, so kernels that
2738/// support strided reads can consume transposes, slices, and broadcasts directly.
2739///
2740/// `TensorRead` is intentionally borrowed. It is an input-dispatch type, not an
2741/// owned lazy tensor value. APIs that need to store a lazy layout result should
2742/// keep an owned base tensor plus layout metadata, then expose a `TensorRead`
2743/// only for the duration of kernel dispatch.
2744///
2745/// # Examples
2746///
2747/// ```
2748/// use tenferro_tensor::{DType, Tensor, TensorRead};
2749///
2750/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
2751/// let read = TensorRead::from_tensor(&tensor);
2752///
2753/// assert_eq!(read.dtype(), DType::F64);
2754/// assert_eq!(read.shape(), &[2]);
2755/// ```
2756// Keep borrowed views inline to avoid allocation on read-only tensor dispatch paths.
2757#[allow(clippy::large_enum_variant)]
2758#[derive(Clone, Debug)]
2759pub enum TensorRead<'a> {
2760 Tensor(&'a Tensor),
2761 View(TensorView<'a>),
2762}
2763
2764/// Mutable typed tensor output accepted by synchronous eager kernels.
2765///
2766/// `TypedTensorWrite` is the typed counterpart to [`TensorWrite`]. It accepts
2767/// either an owned compact [`TypedTensor`] or an arbitrary-strided mutable
2768/// [`TypedTensorViewMut`] without erasing the scalar type at the public API
2769/// boundary.
2770///
2771/// # Examples
2772///
2773/// ```
2774/// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
2775///
2776/// let mut data = [0.0_f64, 1.0, 0.0, 2.0];
2777/// let view = TypedTensorViewMut::from_slice([2], [2], 1, &mut data)?;
2778/// let write = TypedTensorWrite::from_view(view).into_tensor_write();
2779/// assert_eq!(write.shape(), &[2]);
2780/// assert_eq!(write.strides()?, [2]);
2781/// # Ok::<(), tenferro_tensor::Error>(())
2782/// ```
2783#[allow(clippy::large_enum_variant)]
2784#[derive(Debug)]
2785pub enum TypedTensorWrite<'a, T> {
2786 /// An owned compact typed tensor borrowed mutably for the write.
2787 Tensor(&'a mut TypedTensor<T>),
2788 /// An arbitrary-strided mutable typed tensor view.
2789 View(TypedTensorViewMut<'a, T>),
2790}
2791
2792impl<'a, T> TypedTensorWrite<'a, T> {
2793 /// Create a writable target from an owned typed tensor.
2794 ///
2795 /// # Examples
2796 ///
2797 /// ```
2798 /// use tenferro_tensor::{TypedTensor, TypedTensorWrite};
2799 ///
2800 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![0.0])?;
2801 /// let write = TypedTensorWrite::from_tensor(&mut tensor);
2802 /// assert!(matches!(write, TypedTensorWrite::Tensor(_)));
2803 /// # Ok::<(), tenferro_tensor::Error>(())
2804 /// ```
2805 pub fn from_tensor(tensor: &'a mut TypedTensor<T>) -> Self {
2806 Self::Tensor(tensor)
2807 }
2808
2809 /// Create a writable target from a mutable typed tensor view.
2810 ///
2811 /// # Examples
2812 ///
2813 /// ```
2814 /// use tenferro_tensor::{TypedTensorViewMut, TypedTensorWrite};
2815 ///
2816 /// let mut data = [0.0_f64, 1.0];
2817 /// let view = TypedTensorViewMut::from_col_major(&[2], &mut data)?;
2818 /// let write = TypedTensorWrite::from_view(view);
2819 /// assert!(matches!(write, TypedTensorWrite::View(_)));
2820 /// # Ok::<(), tenferro_tensor::Error>(())
2821 /// ```
2822 pub fn from_view(view: TypedTensorViewMut<'a, T>) -> Self {
2823 Self::View(view)
2824 }
2825}
2826
2827impl<'a, T: TensorScalar> TypedTensorWrite<'a, T> {
2828 /// Erase the scalar type while preserving the output layout.
2829 ///
2830 /// # Examples
2831 ///
2832 /// ```
2833 /// use tenferro_tensor::{DType, TypedTensor, TypedTensorWrite};
2834 ///
2835 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![0.0; 2])?;
2836 /// let write = TypedTensorWrite::from_tensor(&mut tensor).into_tensor_write();
2837 /// assert_eq!(write.dtype(), DType::F64);
2838 /// assert_eq!(write.shape(), &[2]);
2839 /// # Ok::<(), tenferro_tensor::Error>(())
2840 /// ```
2841 pub fn into_tensor_write(self) -> TensorWrite<'a> {
2842 match self {
2843 Self::Tensor(tensor) => T::tensor_write(tensor),
2844 Self::View(view) => TensorWrite::from_view(T::tensor_view_mut(view)),
2845 }
2846 }
2847}
2848
2849impl<'a, T> From<&'a mut TypedTensor<T>> for TypedTensorWrite<'a, T> {
2850 fn from(tensor: &'a mut TypedTensor<T>) -> Self {
2851 Self::from_tensor(tensor)
2852 }
2853}
2854
2855impl<'a, T> From<TypedTensorViewMut<'a, T>> for TypedTensorWrite<'a, T> {
2856 fn from(view: TypedTensorViewMut<'a, T>) -> Self {
2857 Self::from_view(view)
2858 }
2859}
2860
2861/// Mutable tensor output accepted by synchronous eager kernels.
2862///
2863/// `TensorWrite` mirrors [`TensorRead`] for output dispatch: it can target an
2864/// owned compact [`Tensor`] or a borrowed mutable [`TensorViewMut`]. The target
2865/// is never resized.
2866///
2867/// # Examples
2868///
2869/// ```
2870/// use tenferro_tensor::{Tensor, TensorWrite};
2871///
2872/// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
2873/// let write = TensorWrite::from_tensor(&mut tensor);
2874/// assert_eq!(write.shape(), &[1]);
2875/// # Ok::<(), tenferro_tensor::Error>(())
2876/// ```
2877#[allow(clippy::large_enum_variant)]
2878#[derive(Debug)]
2879pub enum TensorWrite<'a> {
2880 Tensor(&'a mut Tensor),
2881 View(TensorViewMut<'a>),
2882}
2883
2884/// Owned lazy tensor view over a shared base tensor.
2885///
2886/// This stores only ownership of the base allocation plus logical layout
2887/// metadata. Borrow it as [`TensorRead`] and materialize it through an active
2888/// backend session when compact storage is required.
2889#[derive(Clone, Debug)]
2890pub struct TensorOwnedView {
2891 base: Arc<Tensor>,
2892 layout: TensorLayout<DynRank>,
2893}
2894
2895/// Owned tensor value that can be compact or a lazy view.
2896///
2897/// `TensorValue` is the owned counterpart to [`TensorRead`]. It is suitable for
2898/// storing eager results that should remain lazy until an operation actually
2899/// requires compact materialized storage.
2900#[derive(Clone, Debug)]
2901pub enum TensorValue {
2902 Tensor(Arc<Tensor>),
2903 View(TensorOwnedView),
2904}
2905
2906impl TensorOwnedView {
2907 /// Create an owned view preserving the base tensor's current layout.
2908 pub fn from_tensor(base: Arc<Tensor>) -> Self {
2909 let layout = tensor_layout(base.as_ref());
2910 Self { base, layout }
2911 }
2912
2913 /// Create an owned view with explicit layout metadata.
2914 /// # Errors
2915 ///
2916 /// Returns [`crate::Error::Validation`] with
2917 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `shape` and
2918 /// `strides` have different ranks,
2919 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2920 /// layout exceeds the base tensor buffer, or
2921 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
2922 /// arithmetic overflow.
2923 pub fn from_parts(
2924 base: Arc<Tensor>,
2925 shape: Vec<usize>,
2926 strides: Vec<isize>,
2927 offset: isize,
2928 ) -> crate::Result<Self> {
2929 let layout = TensorLayout::from_parts(
2930 shape.into(),
2931 strides.into(),
2932 offset,
2933 tensor_buffer_len(&base),
2934 )
2935 .map_err(|err| tensor_layout_error("TensorOwnedView::from_parts", err))?;
2936 Ok(Self { base, layout })
2937 }
2938
2939 pub fn dtype(&self) -> DType {
2940 self.base.dtype()
2941 }
2942
2943 pub fn shape(&self) -> &[usize] {
2944 self.layout.shape()
2945 }
2946
2947 pub fn strides(&self) -> &[isize] {
2948 self.layout.strides()
2949 }
2950
2951 pub fn offset(&self) -> isize {
2952 self.layout.offset()
2953 }
2954
2955 pub fn tensor_view(&self) -> TensorView<'_> {
2956 tensor_view_with_layout(self.base.as_ref(), self.layout.clone())
2957 }
2958
2959 pub fn tensor_read(&self) -> TensorRead<'_> {
2960 TensorRead::from_view(self.tensor_view())
2961 }
2962
2963 /// # Errors
2964 ///
2965 /// Returns [`crate::Error::Validation`] with
2966 /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
2967 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
2968 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
2969 /// not a valid permutation of the view rank.
2970 pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
2971 let layout = self
2972 .layout
2973 .transpose_view(axes)
2974 .map_err(|err| tensor_layout_error("TensorOwnedView::transpose_view", err))?;
2975 Ok(Self {
2976 base: Arc::clone(&self.base),
2977 layout,
2978 })
2979 }
2980
2981 /// # Errors
2982 ///
2983 /// Returns [`crate::Error::Validation`] with
2984 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
2985 /// the source is not compact column-major,
2986 /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
2987 /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
2988 /// records the counts) when element counts differ,
2989 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
2990 /// arithmetic overflow, or
2991 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
2992 /// reshaped view exceeds the base buffer.
2993 pub fn reshape_view(
2994 &self,
2995 shape: impl tenferro_tensor_core::IntoShapeVec,
2996 ) -> crate::Result<Self> {
2997 let shape = shape.into_shape_vec();
2998 let layout = reshape_layout_dyn(
2999 &self.layout,
3000 &shape,
3001 tensor_buffer_len(&self.base),
3002 "TensorOwnedView::reshape_view",
3003 )?;
3004 Ok(Self {
3005 base: Arc::clone(&self.base),
3006 layout,
3007 })
3008 }
3009
3010 /// # Errors
3011 ///
3012 /// Returns [`crate::Error::Validation`] with
3013 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a slice
3014 /// vector does not match the view rank,
3015 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when a bound
3016 /// or stride cannot be represented or is invalid,
3017 /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
3018 /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for slice
3019 /// parameters, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3020 /// for slice arithmetic overflow, or
3021 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3022 /// result exceeds the base buffer.
3023 pub fn slice_view(&self, config: &SliceConfig) -> crate::Result<Self> {
3024 let op = "TensorOwnedView::slice_view";
3025 if config.starts.len() != self.shape().len() {
3026 return Err(crate::Error::validation(
3027 op,
3028 ValidationError::RankMismatch {
3029 expected: self.shape().len(),
3030 actual: config.starts.len(),
3031 },
3032 ));
3033 }
3034 if config.limits.len() != self.shape().len() {
3035 return Err(crate::Error::validation(
3036 op,
3037 ValidationError::RankMismatch {
3038 expected: self.shape().len(),
3039 actual: config.limits.len(),
3040 },
3041 ));
3042 }
3043 if config.strides.len() != self.shape().len() {
3044 return Err(crate::Error::validation(
3045 op,
3046 ValidationError::RankMismatch {
3047 expected: self.shape().len(),
3048 actual: config.strides.len(),
3049 },
3050 ));
3051 }
3052
3053 let mut slices = Vec::with_capacity(self.shape().len());
3054 for ((&start, &limit), &stride) in config
3055 .starts
3056 .iter()
3057 .zip(config.limits.iter())
3058 .zip(config.strides.iter())
3059 {
3060 let start = isize::try_from(start).map_err(|_| {
3061 crate::Error::invalid_argument(
3062 op,
3063 "slice start",
3064 format!("slice start {start} does not fit in isize"),
3065 )
3066 })?;
3067 let limit = isize::try_from(limit).map_err(|_| {
3068 crate::Error::invalid_argument(
3069 op,
3070 "slice limit",
3071 format!("slice limit {limit} does not fit in isize"),
3072 )
3073 })?;
3074 let stride = isize::try_from(stride).map_err(|_| {
3075 crate::Error::invalid_argument(
3076 op,
3077 "slice stride",
3078 format!("slice stride {stride} does not fit in isize"),
3079 )
3080 })?;
3081 slices.push(StridedSliceSpec::new(start, Some(limit), stride));
3082 }
3083
3084 let specs = core_slice_specs(&slices, self.shape(), op)?;
3085 let layout = self
3086 .layout
3087 .slice_view(&specs, tensor_buffer_len(&self.base))
3088 .map_err(|err| tensor_layout_error(op, err))?;
3089 Ok(Self {
3090 base: Arc::clone(&self.base),
3091 layout,
3092 })
3093 }
3094
3095 /// # Errors
3096 ///
3097 /// Returns [`crate::Error::Validation`] with
3098 /// [`tenferro_tensor_core::ValidationError::RankMismatch`],
3099 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
3100 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] for invalid
3101 /// dimension mappings,
3102 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] for
3103 /// incompatible extents,
3104 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3105 /// result exceeds the base buffer, or
3106 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
3107 /// arithmetic overflow.
3108 pub fn broadcast_in_dim_view(
3109 &self,
3110 shape: impl tenferro_tensor_core::IntoShapeVec,
3111 dims: impl AsRef<[usize]>,
3112 ) -> crate::Result<Self> {
3113 let shape = shape.into_shape_vec();
3114 let layout = self
3115 .layout
3116 .broadcast_in_dim_view::<DynRank>(shape, dims, tensor_buffer_len(&self.base))
3117 .map_err(|err| tensor_layout_error("TensorOwnedView::broadcast_in_dim_view", err))?;
3118 Ok(Self {
3119 base: Arc::clone(&self.base),
3120 layout,
3121 })
3122 }
3123}
3124
3125impl TensorValue {
3126 pub fn from_tensor(tensor: Tensor) -> Self {
3127 Self::Tensor(Arc::new(tensor))
3128 }
3129
3130 pub fn from_tensor_arc(tensor: Arc<Tensor>) -> Self {
3131 Self::Tensor(tensor)
3132 }
3133
3134 pub fn as_tensor_arc(&self) -> Option<&Arc<Tensor>> {
3135 match self {
3136 Self::Tensor(tensor) => Some(tensor),
3137 Self::View(_) => None,
3138 }
3139 }
3140
3141 pub fn dtype(&self) -> DType {
3142 match self {
3143 Self::Tensor(tensor) => tensor.dtype(),
3144 Self::View(view) => view.dtype(),
3145 }
3146 }
3147
3148 pub fn shape(&self) -> &[usize] {
3149 match self {
3150 Self::Tensor(tensor) => tensor.shape(),
3151 Self::View(view) => view.shape(),
3152 }
3153 }
3154
3155 pub fn tensor_read(&self) -> TensorRead<'_> {
3156 match self {
3157 Self::Tensor(tensor) => TensorRead::from_tensor(tensor.as_ref()),
3158 Self::View(view) => view.tensor_read(),
3159 }
3160 }
3161
3162 /// # Errors
3163 ///
3164 /// Returns [`crate::Error::Validation`] with
3165 /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`],
3166 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
3167 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when `axes` is
3168 /// not a valid permutation of the value rank.
3169 pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
3170 match self {
3171 Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
3172 .transpose_view(axes)
3173 .map(Self::View),
3174 Self::View(view) => view.transpose_view(axes).map(Self::View),
3175 }
3176 }
3177
3178 /// # Errors
3179 ///
3180 /// Returns [`crate::Error::Validation`] with
3181 /// [`tenferro_tensor_core::ValidationError::NonContiguousViewAsSlice`] when
3182 /// the source is not compact column-major,
3183 /// [`tenferro_tensor_core::ValidationError::ShapeMismatch`] (whose
3184 /// [`tenferro_tensor_core::ShapeMismatch::ReshapeElementCount`] source
3185 /// records the counts) when element counts differ,
3186 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for shape
3187 /// arithmetic overflow, or
3188 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3189 /// reshaped view exceeds the backing buffer.
3190 pub fn reshape_view(
3191 &self,
3192 shape: impl tenferro_tensor_core::IntoShapeVec,
3193 ) -> crate::Result<Self> {
3194 let shape = shape.into_shape_vec();
3195 match self {
3196 Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
3197 .reshape_view(shape.clone())
3198 .map(Self::View),
3199 Self::View(view) => view.reshape_view(shape).map(Self::View),
3200 }
3201 }
3202
3203 /// # Errors
3204 ///
3205 /// Returns [`crate::Error::Validation`] with
3206 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a slice
3207 /// vector does not match the value rank,
3208 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when a bound
3209 /// or stride cannot be represented or is invalid,
3210 /// [`tenferro_tensor_core::ValidationError::InvalidSliceStep`] or
3211 /// [`tenferro_tensor_core::ValidationError::InvalidSliceBounds`] for slice
3212 /// parameters, [`tenferro_tensor_core::ValidationError::IntegerOverflow`]
3213 /// for slice arithmetic overflow, or
3214 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3215 /// result exceeds the backing buffer.
3216 pub fn slice_view(&self, config: &SliceConfig) -> crate::Result<Self> {
3217 match self {
3218 Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
3219 .slice_view(config)
3220 .map(Self::View),
3221 Self::View(view) => view.slice_view(config).map(Self::View),
3222 }
3223 }
3224
3225 /// # Errors
3226 ///
3227 /// Returns [`crate::Error::Validation`] with
3228 /// [`tenferro_tensor_core::ValidationError::RankMismatch`],
3229 /// [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`], or
3230 /// [`tenferro_tensor_core::ValidationError::DuplicateAxis`] for invalid
3231 /// dimension mappings,
3232 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] for
3233 /// incompatible extents,
3234 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3235 /// result exceeds the backing buffer, or
3236 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
3237 /// arithmetic overflow.
3238 pub fn broadcast_in_dim_view(
3239 &self,
3240 shape: impl tenferro_tensor_core::IntoShapeVec,
3241 dims: impl AsRef<[usize]>,
3242 ) -> crate::Result<Self> {
3243 let shape = shape.into_shape_vec();
3244 let dims = dims.as_ref();
3245 match self {
3246 Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
3247 .broadcast_in_dim_view(shape.clone(), dims)
3248 .map(Self::View),
3249 Self::View(view) => view.broadcast_in_dim_view(shape, dims).map(Self::View),
3250 }
3251 }
3252}
3253
3254fn tensor_layout(tensor: &Tensor) -> TensorLayout<DynRank> {
3255 match tensor {
3256 Tensor::F32(tensor) => tensor.layout.clone(),
3257 Tensor::F64(tensor) => tensor.layout.clone(),
3258 Tensor::I32(tensor) => tensor.layout.clone(),
3259 Tensor::I64(tensor) => tensor.layout.clone(),
3260 Tensor::Bool(tensor) => tensor.layout.clone(),
3261 Tensor::C32(tensor) => tensor.layout.clone(),
3262 Tensor::C64(tensor) => tensor.layout.clone(),
3263 }
3264}
3265
3266fn tensor_buffer_len(tensor: &Tensor) -> usize {
3267 match tensor {
3268 Tensor::F32(tensor) => buffer_len(&tensor.buffer),
3269 Tensor::F64(tensor) => buffer_len(&tensor.buffer),
3270 Tensor::I32(tensor) => buffer_len(&tensor.buffer),
3271 Tensor::I64(tensor) => buffer_len(&tensor.buffer),
3272 Tensor::Bool(tensor) => buffer_len(&tensor.buffer),
3273 Tensor::C32(tensor) => buffer_len(&tensor.buffer),
3274 Tensor::C64(tensor) => buffer_len(&tensor.buffer),
3275 }
3276}
3277
3278fn buffer_len<T: 'static>(buffer: &Buffer<T>) -> usize {
3279 match buffer {
3280 Buffer::Host(data) => data.len(),
3281 Buffer::Backend(buffer) => buffer.len(),
3282 }
3283}
3284
3285fn tensor_view_with_layout(tensor: &Tensor, layout: TensorLayout<DynRank>) -> TensorView<'_> {
3286 match tensor {
3287 Tensor::F32(tensor) => TensorView::F32(typed_view_with_layout(tensor, layout)),
3288 Tensor::F64(tensor) => TensorView::F64(typed_view_with_layout(tensor, layout)),
3289 Tensor::I32(tensor) => TensorView::I32(typed_view_with_layout(tensor, layout)),
3290 Tensor::I64(tensor) => TensorView::I64(typed_view_with_layout(tensor, layout)),
3291 Tensor::Bool(tensor) => TensorView::Bool(typed_view_with_layout(tensor, layout)),
3292 Tensor::C32(tensor) => TensorView::C32(typed_view_with_layout(tensor, layout)),
3293 Tensor::C64(tensor) => TensorView::C64(typed_view_with_layout(tensor, layout)),
3294 }
3295}
3296
3297fn typed_view_with_layout<T: 'static>(
3298 tensor: &TypedTensor<T>,
3299 layout: TensorLayout<DynRank>,
3300) -> TypedTensorView<'_, T> {
3301 let buffer = match &tensor.buffer {
3302 Buffer::Host(data) => TensorBufferRef::Host(data),
3303 Buffer::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
3304 };
3305 TypedTensorView {
3306 buffer,
3307 layout,
3308 placement: tensor.placement.clone(),
3309 }
3310}
3311
3312/// Wrap an `f64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
3313///
3314/// # Examples
3315///
3316/// ```
3317/// use tenferro_tensor::{Tensor, TypedTensor};
3318///
3319/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
3320/// let tensor: Tensor = typed.into();
3321/// assert_eq!(tensor.shape(), &[2]);
3322/// ```
3323impl From<TypedTensor<f64>> for Tensor {
3324 fn from(t: TypedTensor<f64>) -> Self {
3325 Tensor::F64(t)
3326 }
3327}
3328
3329/// Wrap an `f32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
3330///
3331/// # Examples
3332///
3333/// ```
3334/// use tenferro_tensor::{Tensor, TypedTensor};
3335///
3336/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
3337/// let tensor: Tensor = typed.into();
3338/// assert_eq!(tensor.shape(), &[2]);
3339/// ```
3340impl From<TypedTensor<f32>> for Tensor {
3341 fn from(t: TypedTensor<f32>) -> Self {
3342 Tensor::F32(t)
3343 }
3344}
3345
3346/// Wrap an `i64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
3347///
3348/// # Examples
3349///
3350/// ```
3351/// use tenferro_tensor::{DType, Tensor, TypedTensor};
3352///
3353/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i64, 2]).unwrap();
3354/// let tensor: Tensor = typed.into();
3355/// assert_eq!(tensor.dtype(), DType::I64);
3356/// assert_eq!(tensor.shape(), &[2]);
3357/// ```
3358impl From<TypedTensor<i64>> for Tensor {
3359 fn from(t: TypedTensor<i64>) -> Self {
3360 Tensor::I64(t)
3361 }
3362}
3363
3364/// Wrap an `i32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
3365///
3366/// # Examples
3367///
3368/// ```
3369/// use tenferro_tensor::{DType, Tensor, TypedTensor};
3370///
3371/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i32, 2]).unwrap();
3372/// let tensor: Tensor = typed.into();
3373/// assert_eq!(tensor.dtype(), DType::I32);
3374/// assert_eq!(tensor.shape(), &[2]);
3375/// ```
3376impl From<TypedTensor<i32>> for Tensor {
3377 fn from(t: TypedTensor<i32>) -> Self {
3378 Tensor::I32(t)
3379 }
3380}
3381
3382/// Wrap a `bool` [`TypedTensor`] into the corresponding [`Tensor`] variant.
3383///
3384/// # Examples
3385///
3386/// ```
3387/// use tenferro_tensor::{DType, Tensor, TypedTensor};
3388///
3389/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![true, false]).unwrap();
3390/// let tensor: Tensor = typed.into();
3391/// assert_eq!(tensor.dtype(), DType::Bool);
3392/// assert_eq!(tensor.shape(), &[2]);
3393/// ```
3394impl From<TypedTensor<bool>> for Tensor {
3395 fn from(t: TypedTensor<bool>) -> Self {
3396 Tensor::Bool(t)
3397 }
3398}
3399
3400/// Wrap a [`Complex64`] [`TypedTensor`] into the corresponding [`Tensor`]
3401/// variant.
3402///
3403/// # Examples
3404///
3405/// ```
3406/// use num_complex::Complex64;
3407/// use tenferro_tensor::{Tensor, TypedTensor};
3408///
3409/// let typed = TypedTensor::from_vec_col_major(
3410/// vec![1],
3411/// vec![Complex64::new(1.0, 2.0)],
3412/// ).unwrap();
3413/// let tensor: Tensor = typed.into();
3414/// assert_eq!(tensor.shape(), &[1]);
3415/// ```
3416impl From<TypedTensor<Complex<f64>>> for Tensor {
3417 fn from(t: TypedTensor<Complex<f64>>) -> Self {
3418 Tensor::C64(t)
3419 }
3420}
3421
3422/// Wrap a [`Complex32`] [`TypedTensor`] into the corresponding [`Tensor`]
3423/// variant.
3424///
3425/// # Examples
3426///
3427/// ```
3428/// use num_complex::Complex32;
3429/// use tenferro_tensor::{Tensor, TypedTensor};
3430///
3431/// let typed = TypedTensor::from_vec_col_major(
3432/// vec![1],
3433/// vec![Complex32::new(1.0, 2.0)],
3434/// ).unwrap();
3435/// let tensor: Tensor = typed.into();
3436/// assert_eq!(tensor.shape(), &[1]);
3437/// ```
3438impl From<TypedTensor<Complex<f32>>> for Tensor {
3439 fn from(t: TypedTensor<Complex<f32>>) -> Self {
3440 Tensor::C32(t)
3441 }
3442}
3443
3444impl<'a> TensorView<'a> {
3445 /// Create a dynamic `f32` view over compact column-major host data.
3446 ///
3447 /// # Examples
3448 ///
3449 /// ```
3450 /// use tenferro_tensor::{DType, TensorView};
3451 ///
3452 /// let data = [1.0_f32, 2.0];
3453 /// let view = TensorView::f32(&[2], &data)?;
3454 /// assert_eq!(view.dtype(), DType::F32);
3455 /// # Ok::<(), tenferro_tensor::Error>(())
3456 /// ```
3457 /// # Errors
3458 ///
3459 /// Returns [`crate::Error::Validation`] with
3460 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
3461 /// shape or offset arithmetic overflow, or
3462 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3463 /// compact shape reaches beyond `data`.
3464 pub fn f32(shape: &'a [usize], data: &'a [f32]) -> crate::Result<Self> {
3465 Ok(Self::F32(TypedTensorView::from_col_major(shape, data)?))
3466 }
3467
3468 /// Create a dynamic `f64` view over compact column-major host data.
3469 ///
3470 /// # Examples
3471 ///
3472 /// ```
3473 /// use tenferro_tensor::{DType, TensorView};
3474 ///
3475 /// let data = [1.0_f64, 2.0];
3476 /// let view = TensorView::f64(&[2], &data)?;
3477 /// assert_eq!(view.dtype(), DType::F64);
3478 /// # Ok::<(), tenferro_tensor::Error>(())
3479 /// ```
3480 /// # Errors
3481 ///
3482 /// Returns [`crate::Error::Validation`] with
3483 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
3484 /// shape or offset arithmetic overflow, or
3485 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3486 /// compact shape reaches beyond `data`.
3487 pub fn f64(shape: &'a [usize], data: &'a [f64]) -> crate::Result<Self> {
3488 Ok(Self::F64(TypedTensorView::from_col_major(shape, data)?))
3489 }
3490
3491 /// Create a dynamic `i64` view over compact column-major host data.
3492 ///
3493 /// # Examples
3494 ///
3495 /// ```
3496 /// use tenferro_tensor::{DType, TensorView};
3497 ///
3498 /// let data = [1_i64, 2];
3499 /// let view = TensorView::i64(&[2], &data)?;
3500 /// assert_eq!(view.dtype(), DType::I64);
3501 /// # Ok::<(), tenferro_tensor::Error>(())
3502 /// ```
3503 /// # Errors
3504 ///
3505 /// Returns [`crate::Error::Validation`] with
3506 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
3507 /// shape or offset arithmetic overflow, or
3508 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3509 /// compact shape reaches beyond `data`.
3510 pub fn i64(shape: &'a [usize], data: &'a [i64]) -> crate::Result<Self> {
3511 Ok(Self::I64(TypedTensorView::from_col_major(shape, data)?))
3512 }
3513
3514 /// Create a dynamic `i32` view over compact column-major host data.
3515 ///
3516 /// # Examples
3517 ///
3518 /// ```
3519 /// use tenferro_tensor::{DType, TensorView};
3520 ///
3521 /// let data = [1_i32, 2];
3522 /// let view = TensorView::i32(&[2], &data)?;
3523 /// assert_eq!(view.dtype(), DType::I32);
3524 /// # Ok::<(), tenferro_tensor::Error>(())
3525 /// ```
3526 /// # Errors
3527 ///
3528 /// Returns [`crate::Error::Validation`] with
3529 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
3530 /// shape or offset arithmetic overflow, or
3531 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3532 /// compact shape reaches beyond `data`.
3533 pub fn i32(shape: &'a [usize], data: &'a [i32]) -> crate::Result<Self> {
3534 Ok(Self::I32(TypedTensorView::from_col_major(shape, data)?))
3535 }
3536
3537 /// Create a dynamic `bool` view over compact column-major host data.
3538 ///
3539 /// # Examples
3540 ///
3541 /// ```
3542 /// use tenferro_tensor::{DType, TensorView};
3543 ///
3544 /// let data = [true, false];
3545 /// let view = TensorView::bool(&[2], &data)?;
3546 /// assert_eq!(view.dtype(), DType::Bool);
3547 /// # Ok::<(), tenferro_tensor::Error>(())
3548 /// ```
3549 /// # Errors
3550 ///
3551 /// Returns [`crate::Error::Validation`] with
3552 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
3553 /// shape or offset arithmetic overflow, or
3554 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3555 /// compact shape reaches beyond `data`.
3556 pub fn bool(shape: &'a [usize], data: &'a [bool]) -> crate::Result<Self> {
3557 Ok(Self::Bool(TypedTensorView::from_col_major(shape, data)?))
3558 }
3559
3560 /// Create a dynamic `Complex32` view over compact column-major host data.
3561 ///
3562 /// # Examples
3563 ///
3564 /// ```
3565 /// use num_complex::Complex32;
3566 /// use tenferro_tensor::{DType, TensorView};
3567 ///
3568 /// let data = [Complex32::new(1.0, 2.0)];
3569 /// let view = TensorView::c32(&[1], &data)?;
3570 /// assert_eq!(view.dtype(), DType::C32);
3571 /// # Ok::<(), tenferro_tensor::Error>(())
3572 /// ```
3573 /// # Errors
3574 ///
3575 /// Returns [`crate::Error::Validation`] with
3576 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
3577 /// shape or offset arithmetic overflow, or
3578 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3579 /// compact shape reaches beyond `data`.
3580 pub fn c32(shape: &'a [usize], data: &'a [Complex32]) -> crate::Result<Self> {
3581 Ok(Self::C32(TypedTensorView::from_col_major(shape, data)?))
3582 }
3583
3584 /// Create a dynamic `Complex64` view over compact column-major host data.
3585 ///
3586 /// # Examples
3587 ///
3588 /// ```
3589 /// use num_complex::Complex64;
3590 /// use tenferro_tensor::{DType, TensorView};
3591 ///
3592 /// let data = [Complex64::new(1.0, 2.0)];
3593 /// let view = TensorView::c64(&[1], &data)?;
3594 /// assert_eq!(view.dtype(), DType::C64);
3595 /// # Ok::<(), tenferro_tensor::Error>(())
3596 /// ```
3597 /// # Errors
3598 ///
3599 /// Returns [`crate::Error::Validation`] with
3600 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for compact
3601 /// shape or offset arithmetic overflow, or
3602 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
3603 /// compact shape reaches beyond `data`.
3604 pub fn c64(shape: &'a [usize], data: &'a [Complex64]) -> crate::Result<Self> {
3605 Ok(Self::C64(TypedTensorView::from_col_major(shape, data)?))
3606 }
3607
3608 pub fn dtype(&self) -> DType {
3609 match self {
3610 Self::F32(_) => DType::F32,
3611 Self::F64(_) => DType::F64,
3612 Self::I32(_) => DType::I32,
3613 Self::I64(_) => DType::I64,
3614 Self::Bool(_) => DType::Bool,
3615 Self::C32(_) => DType::C32,
3616 Self::C64(_) => DType::C64,
3617 }
3618 }
3619
3620 pub fn shape(&self) -> &[usize] {
3621 match self {
3622 Self::F32(t) => t.shape(),
3623 Self::F64(t) => t.shape(),
3624 Self::I32(t) => t.shape(),
3625 Self::I64(t) => t.shape(),
3626 Self::Bool(t) => t.shape(),
3627 Self::C32(t) => t.shape(),
3628 Self::C64(t) => t.shape(),
3629 }
3630 }
3631
3632 /// Return the placement metadata carried by this borrowed view.
3633 ///
3634 /// # Examples
3635 ///
3636 /// ```
3637 /// use tenferro_tensor::{MemoryKind, TensorView};
3638 ///
3639 /// let view = TensorView::f64(&[1], &[1.0])?;
3640 /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
3641 /// # Ok::<(), tenferro_tensor::Error>(())
3642 /// ```
3643 pub fn placement(&self) -> &Placement {
3644 match self {
3645 Self::F32(t) => t.placement(),
3646 Self::F64(t) => t.placement(),
3647 Self::I32(t) => t.placement(),
3648 Self::I64(t) => t.placement(),
3649 Self::Bool(t) => t.placement(),
3650 Self::C32(t) => t.placement(),
3651 Self::C64(t) => t.placement(),
3652 }
3653 }
3654
3655 /// Return the physical backend family, when this view is backend-owned.
3656 ///
3657 /// # Examples
3658 ///
3659 /// ```
3660 /// use tenferro_tensor::TensorView;
3661 ///
3662 /// let view = TensorView::f64(&[1], &[1.0])?;
3663 /// assert_eq!(view.backend_family(), None);
3664 /// # Ok::<(), tenferro_tensor::Error>(())
3665 /// ```
3666 pub fn backend_family(&self) -> Option<&'static str> {
3667 match self {
3668 Self::F32(t) => t.backend_buffer().map(|buffer| buffer.backend_family()),
3669 Self::F64(t) => t.backend_buffer().map(|buffer| buffer.backend_family()),
3670 Self::I32(t) => t.backend_buffer().map(|buffer| buffer.backend_family()),
3671 Self::I64(t) => t.backend_buffer().map(|buffer| buffer.backend_family()),
3672 Self::Bool(t) => t.backend_buffer().map(|buffer| buffer.backend_family()),
3673 Self::C32(t) => t.backend_buffer().map(|buffer| buffer.backend_family()),
3674 Self::C64(t) => t.backend_buffer().map(|buffer| buffer.backend_family()),
3675 }
3676 }
3677
3678 /// Return the shared allocation domain, when this view has one.
3679 ///
3680 /// # Examples
3681 ///
3682 /// ```
3683 /// use tenferro_tensor::TensorView;
3684 ///
3685 /// let view = TensorView::f64(&[1], &[1.0])?;
3686 /// assert_eq!(view.allocation_domain(), None);
3687 /// # Ok::<(), tenferro_tensor::Error>(())
3688 /// ```
3689 pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
3690 match self {
3691 Self::F32(t) => t
3692 .backend_buffer()
3693 .and_then(|buffer| buffer.allocation_domain()),
3694 Self::F64(t) => t
3695 .backend_buffer()
3696 .and_then(|buffer| buffer.allocation_domain()),
3697 Self::I32(t) => t
3698 .backend_buffer()
3699 .and_then(|buffer| buffer.allocation_domain()),
3700 Self::I64(t) => t
3701 .backend_buffer()
3702 .and_then(|buffer| buffer.allocation_domain()),
3703 Self::Bool(t) => t
3704 .backend_buffer()
3705 .and_then(|buffer| buffer.allocation_domain()),
3706 Self::C32(t) => t
3707 .backend_buffer()
3708 .and_then(|buffer| buffer.allocation_domain()),
3709 Self::C64(t) => t
3710 .backend_buffer()
3711 .and_then(|buffer| buffer.allocation_domain()),
3712 }
3713 }
3714
3715 /// Return strides in element units.
3716 pub fn strides(&self) -> &[isize] {
3717 match self {
3718 Self::F32(t) => t.strides(),
3719 Self::F64(t) => t.strides(),
3720 Self::I32(t) => t.strides(),
3721 Self::I64(t) => t.strides(),
3722 Self::Bool(t) => t.strides(),
3723 Self::C32(t) => t.strides(),
3724 Self::C64(t) => t.strides(),
3725 }
3726 }
3727
3728 /// Return the physical element offset.
3729 pub fn offset(&self) -> isize {
3730 match self {
3731 Self::F32(t) => t.offset(),
3732 Self::F64(t) => t.offset(),
3733 Self::I32(t) => t.offset(),
3734 Self::I64(t) => t.offset(),
3735 Self::Bool(t) => t.offset(),
3736 Self::C32(t) => t.offset(),
3737 Self::C64(t) => t.offset(),
3738 }
3739 }
3740
3741 /// Compute the physical element offset for a logical index.
3742 /// # Errors
3743 ///
3744 /// Returns [`crate::Error::Validation`] with
3745 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
3746 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
3747 /// when an index is outside its axis extent, or
3748 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
3749 /// arithmetic overflows.
3750 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
3751 match self {
3752 Self::F32(t) => t.layout_linear_offset(indices),
3753 Self::F64(t) => t.layout_linear_offset(indices),
3754 Self::I32(t) => t.layout_linear_offset(indices),
3755 Self::I64(t) => t.layout_linear_offset(indices),
3756 Self::Bool(t) => t.layout_linear_offset(indices),
3757 Self::C32(t) => t.layout_linear_offset(indices),
3758 Self::C64(t) => t.layout_linear_offset(indices),
3759 }
3760 }
3761
3762 /// Return whether this view is compact column-major.
3763 /// # Errors
3764 ///
3765 /// Returns [`crate::Error::Validation`] with
3766 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
3767 /// compactness arithmetic overflows.
3768 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
3769 match self {
3770 Self::F32(t) => t.is_col_major_contiguous(),
3771 Self::F64(t) => t.is_col_major_contiguous(),
3772 Self::I32(t) => t.is_col_major_contiguous(),
3773 Self::I64(t) => t.is_col_major_contiguous(),
3774 Self::Bool(t) => t.is_col_major_contiguous(),
3775 Self::C32(t) => t.is_col_major_contiguous(),
3776 Self::C64(t) => t.is_col_major_contiguous(),
3777 }
3778 }
3779
3780 /// Return a compact string summary of this view's layout metadata.
3781 pub fn layout_summary(&self) -> String {
3782 layout_summary(self.shape(), self.strides(), self.offset())
3783 }
3784
3785 /// Assert this view is compact column-major.
3786 /// # Errors
3787 ///
3788 /// Returns [`crate::Error::Validation`] with
3789 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
3790 /// compactness arithmetic overflows, or
3791 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
3792 /// view is not compact column-major.
3793 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
3794 assert_layout_col_major_contiguous(
3795 self.is_col_major_contiguous()?,
3796 self.shape(),
3797 self.strides(),
3798 self.offset(),
3799 "TensorView::assert_col_major_contiguous",
3800 )
3801 }
3802}
3803
3804macro_rules! tensor_view_mut_constructor {
3805 ($name:ident, $variant:ident, $scalar:ty) => {
3806 /// Create a dynamic mutable view over compact column-major host data.
3807 ///
3808 /// # Errors
3809 ///
3810 /// Returns [`crate::Error::Validation`] with
3811 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
3812 /// compact layout arithmetic overflows, or
3813 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
3814 /// requested shape exceeds `data`.
3815 pub fn $name(shape: &'a [usize], data: &'a mut [$scalar]) -> crate::Result<Self> {
3816 Ok(Self::$variant(TypedTensorViewMut::from_col_major(
3817 shape, data,
3818 )?))
3819 }
3820 };
3821}
3822
3823impl<'a> TensorViewMut<'a> {
3824 tensor_view_mut_constructor!(f32, F32, f32);
3825 tensor_view_mut_constructor!(f64, F64, f64);
3826 tensor_view_mut_constructor!(i32, I32, i32);
3827 tensor_view_mut_constructor!(i64, I64, i64);
3828 tensor_view_mut_constructor!(bool, Bool, bool);
3829 tensor_view_mut_constructor!(c32, C32, Complex32);
3830 tensor_view_mut_constructor!(c64, C64, Complex64);
3831
3832 pub fn dtype(&self) -> DType {
3833 match self {
3834 Self::F32(_) => DType::F32,
3835 Self::F64(_) => DType::F64,
3836 Self::I32(_) => DType::I32,
3837 Self::I64(_) => DType::I64,
3838 Self::Bool(_) => DType::Bool,
3839 Self::C32(_) => DType::C32,
3840 Self::C64(_) => DType::C64,
3841 }
3842 }
3843
3844 pub fn shape(&self) -> &[usize] {
3845 match self {
3846 Self::F32(t) => t.shape(),
3847 Self::F64(t) => t.shape(),
3848 Self::I32(t) => t.shape(),
3849 Self::I64(t) => t.shape(),
3850 Self::Bool(t) => t.shape(),
3851 Self::C32(t) => t.shape(),
3852 Self::C64(t) => t.shape(),
3853 }
3854 }
3855
3856 pub fn strides(&self) -> &[isize] {
3857 match self {
3858 Self::F32(t) => t.strides(),
3859 Self::F64(t) => t.strides(),
3860 Self::I32(t) => t.strides(),
3861 Self::I64(t) => t.strides(),
3862 Self::Bool(t) => t.strides(),
3863 Self::C32(t) => t.strides(),
3864 Self::C64(t) => t.strides(),
3865 }
3866 }
3867
3868 pub fn offset(&self) -> isize {
3869 match self {
3870 Self::F32(t) => t.offset(),
3871 Self::F64(t) => t.offset(),
3872 Self::I32(t) => t.offset(),
3873 Self::I64(t) => t.offset(),
3874 Self::Bool(t) => t.offset(),
3875 Self::C32(t) => t.offset(),
3876 Self::C64(t) => t.offset(),
3877 }
3878 }
3879
3880 /// Compute the physical element offset for a logical index.
3881 ///
3882 /// # Errors
3883 ///
3884 /// Returns [`crate::Error::Validation`] with
3885 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
3886 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
3887 /// when an index is outside its axis extent, or
3888 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
3889 /// arithmetic overflows.
3890 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
3891 match self {
3892 Self::F32(t) => t.layout_linear_offset(indices),
3893 Self::F64(t) => t.layout_linear_offset(indices),
3894 Self::I32(t) => t.layout_linear_offset(indices),
3895 Self::I64(t) => t.layout_linear_offset(indices),
3896 Self::Bool(t) => t.layout_linear_offset(indices),
3897 Self::C32(t) => t.layout_linear_offset(indices),
3898 Self::C64(t) => t.layout_linear_offset(indices),
3899 }
3900 }
3901
3902 /// Return whether this view is compact column-major.
3903 ///
3904 /// # Errors
3905 ///
3906 /// Returns [`crate::Error::Validation`] with
3907 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
3908 /// compactness arithmetic overflows.
3909 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
3910 match self {
3911 Self::F32(t) => t.is_col_major_contiguous(),
3912 Self::F64(t) => t.is_col_major_contiguous(),
3913 Self::I32(t) => t.is_col_major_contiguous(),
3914 Self::I64(t) => t.is_col_major_contiguous(),
3915 Self::Bool(t) => t.is_col_major_contiguous(),
3916 Self::C32(t) => t.is_col_major_contiguous(),
3917 Self::C64(t) => t.is_col_major_contiguous(),
3918 }
3919 }
3920
3921 pub fn layout_summary(&self) -> String {
3922 layout_summary(self.shape(), self.strides(), self.offset())
3923 }
3924
3925 /// Assert this view is compact column-major.
3926 ///
3927 /// # Errors
3928 ///
3929 /// Returns [`crate::Error::Validation`] with
3930 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
3931 /// compactness arithmetic overflows, or
3932 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
3933 /// view is not compact column-major.
3934 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
3935 assert_layout_col_major_contiguous(
3936 self.is_col_major_contiguous()?,
3937 self.shape(),
3938 self.strides(),
3939 self.offset(),
3940 "TensorViewMut::assert_col_major_contiguous",
3941 )
3942 }
3943
3944 pub fn as_read_only(&self) -> TensorView<'_> {
3945 match self {
3946 Self::F32(t) => TensorView::F32(t.as_read_only()),
3947 Self::F64(t) => TensorView::F64(t.as_read_only()),
3948 Self::I32(t) => TensorView::I32(t.as_read_only()),
3949 Self::I64(t) => TensorView::I64(t.as_read_only()),
3950 Self::Bool(t) => TensorView::Bool(t.as_read_only()),
3951 Self::C32(t) => TensorView::C32(t.as_read_only()),
3952 Self::C64(t) => TensorView::C64(t.as_read_only()),
3953 }
3954 }
3955}
3956
3957impl<'a> TensorRead<'a> {
3958 pub fn from_tensor(tensor: &'a Tensor) -> Self {
3959 Self::Tensor(tensor)
3960 }
3961
3962 pub fn from_view(view: TensorView<'a>) -> Self {
3963 Self::View(view)
3964 }
3965
3966 /// Convert this read target into a dtype-erased tensor view.
3967 ///
3968 /// Owned tensors are borrowed without copying their storage. Existing
3969 /// views preserve their layout and placement metadata.
3970 pub fn tensor_view(self) -> TensorView<'a> {
3971 match self {
3972 Self::Tensor(tensor) => tensor_view_with_layout(tensor, tensor_layout(tensor)),
3973 Self::View(view) => view,
3974 }
3975 }
3976
3977 pub fn dtype(&self) -> DType {
3978 match self {
3979 Self::Tensor(tensor) => tensor.dtype(),
3980 Self::View(view) => view.dtype(),
3981 }
3982 }
3983
3984 pub fn shape(&self) -> &[usize] {
3985 match self {
3986 Self::Tensor(tensor) => tensor.shape(),
3987 Self::View(view) => view.shape(),
3988 }
3989 }
3990
3991 /// Return the placement metadata carried by this read target.
3992 ///
3993 /// # Examples
3994 ///
3995 /// ```
3996 /// use tenferro_tensor::{MemoryKind, Tensor, TensorRead};
3997 ///
3998 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
3999 /// let read = TensorRead::from_tensor(&tensor);
4000 /// assert_eq!(read.placement().memory_kind, MemoryKind::UnpinnedHost);
4001 /// # Ok::<(), tenferro_tensor::Error>(())
4002 /// ```
4003 pub fn placement(&self) -> &Placement {
4004 match self {
4005 Self::Tensor(tensor) => tensor.placement(),
4006 Self::View(view) => view.placement(),
4007 }
4008 }
4009
4010 /// Return the physical backend family of this read target, when backend-owned.
4011 ///
4012 /// # Examples
4013 ///
4014 /// ```
4015 /// use tenferro_tensor::{Tensor, TensorRead};
4016 ///
4017 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
4018 /// assert_eq!(TensorRead::from_tensor(&tensor).backend_family(), None);
4019 /// # Ok::<(), tenferro_tensor::Error>(())
4020 /// ```
4021 pub fn backend_family(&self) -> Option<&'static str> {
4022 match self {
4023 Self::Tensor(tensor) => match tensor {
4024 Tensor::F32(t) => buffer_backend_family(t.buffer()),
4025 Tensor::F64(t) => buffer_backend_family(t.buffer()),
4026 Tensor::I32(t) => buffer_backend_family(t.buffer()),
4027 Tensor::I64(t) => buffer_backend_family(t.buffer()),
4028 Tensor::Bool(t) => buffer_backend_family(t.buffer()),
4029 Tensor::C32(t) => buffer_backend_family(t.buffer()),
4030 Tensor::C64(t) => buffer_backend_family(t.buffer()),
4031 },
4032 Self::View(view) => view.backend_family(),
4033 }
4034 }
4035
4036 /// Return the shared allocation domain of this read target, when present.
4037 ///
4038 /// # Examples
4039 ///
4040 /// ```
4041 /// use tenferro_tensor::{Tensor, TensorRead};
4042 ///
4043 /// let tensor = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
4044 /// assert_eq!(TensorRead::from_tensor(&tensor).allocation_domain(), None);
4045 /// # Ok::<(), tenferro_tensor::Error>(())
4046 /// ```
4047 pub fn allocation_domain(&self) -> Option<AllocationDomainId> {
4048 match self {
4049 Self::Tensor(tensor) => match tensor {
4050 Tensor::F32(t) => t.allocation_domain(),
4051 Tensor::F64(t) => t.allocation_domain(),
4052 Tensor::I32(t) => t.allocation_domain(),
4053 Tensor::I64(t) => t.allocation_domain(),
4054 Tensor::Bool(t) => t.allocation_domain(),
4055 Tensor::C32(t) => t.allocation_domain(),
4056 Tensor::C64(t) => t.allocation_domain(),
4057 },
4058 Self::View(view) => view.allocation_domain(),
4059 }
4060 }
4061
4062 /// # Errors
4063 ///
4064 /// Returns [`crate::Error::Validation`] with
4065 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4066 /// column-major stride arithmetic overflows.
4067 pub fn strides(&self) -> crate::Result<Vec<isize>> {
4068 match self {
4069 Self::Tensor(tensor) => col_major_strides(tensor.shape()),
4070 Self::View(view) => Ok(view.strides().to_vec()),
4071 }
4072 }
4073
4074 pub fn offset(&self) -> isize {
4075 match self {
4076 Self::Tensor(_) => 0,
4077 Self::View(view) => view.offset(),
4078 }
4079 }
4080
4081 /// # Errors
4082 ///
4083 /// Returns [`crate::Error::Validation`] with
4084 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
4085 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
4086 /// when an index is outside its axis extent, or
4087 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
4088 /// arithmetic overflows.
4089 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
4090 match self {
4091 Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
4092 Self::View(view) => view.layout_linear_offset(indices),
4093 }
4094 }
4095
4096 /// # Errors
4097 ///
4098 /// Returns [`crate::Error::Validation`] with
4099 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4100 /// compactness arithmetic overflows.
4101 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
4102 match self {
4103 Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
4104 Self::View(view) => view.is_col_major_contiguous(),
4105 }
4106 }
4107
4108 pub fn layout_summary(&self) -> String {
4109 let strides = match self.strides() {
4110 Ok(strides) => strides,
4111 Err(err) => return format!("layout unavailable: {err}"),
4112 };
4113 layout_summary(self.shape(), &strides, self.offset())
4114 }
4115
4116 /// # Errors
4117 ///
4118 /// Returns [`crate::Error::Validation`] with
4119 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4120 /// compactness arithmetic overflows, or
4121 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
4122 /// view is not compact column-major.
4123 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
4124 let strides = self.strides()?;
4125 assert_layout_col_major_contiguous(
4126 self.is_col_major_contiguous()?,
4127 self.shape(),
4128 &strides,
4129 self.offset(),
4130 "TensorRead::assert_col_major_contiguous",
4131 )
4132 }
4133
4134 pub fn as_tensor(&self) -> Option<&'a Tensor> {
4135 match self {
4136 Self::Tensor(tensor) => Some(*tensor),
4137 Self::View(_) => None,
4138 }
4139 }
4140}
4141
4142fn buffer_backend_family<T: 'static>(buffer: &Buffer<T>) -> Option<&'static str> {
4143 match buffer {
4144 Buffer::Host(_) => None,
4145 Buffer::Backend(buffer) => Some(buffer.backend_family()),
4146 }
4147}
4148
4149impl<'a> TensorWrite<'a> {
4150 pub fn from_tensor(tensor: &'a mut Tensor) -> Self {
4151 Self::Tensor(tensor)
4152 }
4153
4154 pub fn from_view(view: TensorViewMut<'a>) -> Self {
4155 Self::View(view)
4156 }
4157
4158 /// Borrow this writable target as a read-only tensor input.
4159 ///
4160 /// This is useful for explicit read-modify-write kernels such as
4161 /// accumulation updates. The returned view borrows through `&self`, so it
4162 /// cannot outlive the current read-only borrow of the writable target.
4163 ///
4164 /// # Examples
4165 ///
4166 /// ```rust
4167 /// use tenferro_tensor::{DType, Tensor, TensorWrite};
4168 ///
4169 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
4170 /// let write = TensorWrite::from_tensor(&mut tensor);
4171 /// let read = write.as_read();
4172 /// assert_eq!(read.dtype(), DType::F64);
4173 /// # Ok::<(), tenferro_tensor::Error>(())
4174 /// ```
4175 pub fn as_read(&self) -> TensorRead<'_> {
4176 match self {
4177 Self::Tensor(tensor) => TensorRead::from_tensor(tensor),
4178 Self::View(view) => TensorRead::from_view(view.as_read_only()),
4179 }
4180 }
4181
4182 pub fn dtype(&self) -> DType {
4183 match self {
4184 Self::Tensor(tensor) => tensor.dtype(),
4185 Self::View(view) => view.dtype(),
4186 }
4187 }
4188
4189 pub fn shape(&self) -> &[usize] {
4190 match self {
4191 Self::Tensor(tensor) => tensor.shape(),
4192 Self::View(view) => view.shape(),
4193 }
4194 }
4195
4196 /// # Errors
4197 ///
4198 /// Returns [`crate::Error::Validation`] with
4199 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4200 /// column-major stride arithmetic overflows.
4201 pub fn strides(&self) -> crate::Result<Vec<isize>> {
4202 match self {
4203 Self::Tensor(tensor) => col_major_strides(tensor.shape()),
4204 Self::View(view) => Ok(view.strides().to_vec()),
4205 }
4206 }
4207
4208 pub fn offset(&self) -> isize {
4209 match self {
4210 Self::Tensor(_) => 0,
4211 Self::View(view) => view.offset(),
4212 }
4213 }
4214
4215 /// # Errors
4216 ///
4217 /// Returns [`crate::Error::Validation`] with
4218 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
4219 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
4220 /// when an index is outside its axis extent, or
4221 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
4222 /// arithmetic overflows.
4223 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
4224 match self {
4225 Self::Tensor(tensor) => tensor.layout_linear_offset(indices),
4226 Self::View(view) => view.layout_linear_offset(indices),
4227 }
4228 }
4229
4230 /// # Errors
4231 ///
4232 /// Returns [`crate::Error::Validation`] with
4233 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4234 /// compactness arithmetic overflows.
4235 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
4236 match self {
4237 Self::Tensor(tensor) => tensor.is_col_major_contiguous(),
4238 Self::View(view) => view.is_col_major_contiguous(),
4239 }
4240 }
4241
4242 pub fn layout_summary(&self) -> String {
4243 let strides = match self.strides() {
4244 Ok(strides) => strides,
4245 Err(err) => return format!("layout unavailable: {err}"),
4246 };
4247 layout_summary(self.shape(), &strides, self.offset())
4248 }
4249
4250 /// # Errors
4251 ///
4252 /// Returns [`crate::Error::Validation`] with
4253 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
4254 /// compactness arithmetic overflows, or
4255 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
4256 /// view is not compact column-major.
4257 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
4258 let strides = self.strides()?;
4259 assert_layout_col_major_contiguous(
4260 self.is_col_major_contiguous()?,
4261 self.shape(),
4262 &strides,
4263 self.offset(),
4264 "TensorWrite::assert_col_major_contiguous",
4265 )
4266 }
4267}
4268
4269/// Column-major strides derived from a shape.
4270///
4271/// # Examples
4272///
4273/// ```rust
4274/// use tenferro_tensor::col_major_strides;
4275///
4276/// assert_eq!(col_major_strides(&[2, 3])?, vec![1, 2]);
4277/// # Ok::<(), tenferro_tensor::Error>(())
4278/// ```
4279/// # Errors
4280///
4281/// Returns [`crate::Error::Validation`] with
4282/// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when a
4283/// column-major stride product overflows.
4284pub fn col_major_strides(shape: &[usize]) -> crate::Result<Vec<isize>> {
4285 let mut strides = Vec::with_capacity(shape.len());
4286 let mut stride = 1isize;
4287 for &extent in shape {
4288 strides.push(stride);
4289 let extent = isize::try_from(extent).map_err(|_| {
4290 crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
4291 })?;
4292 stride = stride.checked_mul(extent).ok_or_else(|| {
4293 crate::Error::validation("col_major_strides", ValidationError::IntegerOverflow)
4294 })?;
4295 }
4296 Ok(strides)
4297}
4298
4299fn try_linear_offset_for_shape(
4300 shape: &[usize],
4301 indices: &[usize],
4302 op: &'static str,
4303) -> crate::Result<usize> {
4304 if indices.len() != shape.len() {
4305 return Err(crate::Error::validation(
4306 op,
4307 ValidationError::RankMismatch {
4308 expected: shape.len(),
4309 actual: indices.len(),
4310 },
4311 ));
4312 }
4313 let mut offset = 0usize;
4314 let mut stride = 1usize;
4315 for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
4316 if idx >= extent {
4317 return Err(crate::Error::invalid_argument(
4318 op,
4319 "index",
4320 format!("index {idx} out of bounds for axis {axis} extent {extent}"),
4321 ));
4322 }
4323 offset =
4324 offset
4325 .checked_add(idx.checked_mul(stride).ok_or_else(|| {
4326 crate::Error::validation(op, ValidationError::IntegerOverflow)
4327 })?)
4328 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
4329 stride = stride
4330 .checked_mul(extent)
4331 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
4332 }
4333 Ok(offset)
4334}
4335
4336fn checked_view_offset_result(
4337 shape: &[usize],
4338 strides: &[isize],
4339 base_offset: isize,
4340 indices: &[usize],
4341 op: &'static str,
4342) -> crate::Result<usize> {
4343 if indices.len() != shape.len() {
4344 return Err(crate::Error::validation(
4345 op,
4346 ValidationError::RankMismatch {
4347 expected: shape.len(),
4348 actual: indices.len(),
4349 },
4350 ));
4351 }
4352 for (axis, (&index, &extent)) in indices.iter().zip(shape).enumerate() {
4353 if index >= extent {
4354 return Err(crate::Error::invalid_argument(
4355 op,
4356 "index",
4357 format!("index {index} out of bounds for axis {axis} extent {extent}"),
4358 ));
4359 }
4360 }
4361 checked_view_offset(shape, strides, base_offset, indices)
4362 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
4363}
4364
4365fn layout_summary(shape: &[usize], strides: &[isize], offset: isize) -> String {
4366 format!("shape={shape:?} strides={strides:?} offset={offset}")
4367}
4368
4369fn assert_layout_col_major_contiguous(
4370 is_contiguous: bool,
4371 shape: &[usize],
4372 strides: &[isize],
4373 offset: isize,
4374 op: &'static str,
4375) -> crate::Result<()> {
4376 if is_contiguous {
4377 Ok(())
4378 } else {
4379 Err(crate::Error::invalid_argument(
4380 op,
4381 "layout",
4382 format!(
4383 "expected compact column-major layout, got {}",
4384 layout_summary(shape, strides, offset)
4385 ),
4386 ))
4387 }
4388}
4389
4390fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
4391 shape.iter().try_fold(1usize, |acc, &dim| {
4392 acc.checked_mul(dim)
4393 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
4394 })
4395}
4396
4397fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
4398 let n = try_shape_product(shape, op)?;
4399 if data_len != n {
4400 return Err(crate::Error::validation(
4401 op,
4402 ValidationError::ShapeDataLengthMismatch {
4403 expected: n,
4404 actual: data_len,
4405 },
4406 ));
4407 }
4408 Ok(())
4409}
4410
4411fn try_compact_layout<R: TensorRank>(
4412 shape: impl Into<R::Shape>,
4413 op: &'static str,
4414) -> crate::Result<TensorLayout<R>> {
4415 TensorLayout::compact(shape.into()).map_err(|err| tensor_layout_error(op, err))
4416}
4417
4418fn tensor_layout_error(
4419 op: &'static str,
4420 err: tenferro_tensor_core::ValidationError,
4421) -> crate::Error {
4422 crate::Error::validation(op, err)
4423}
4424
4425fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
4426 if shape.contains(&0) {
4427 return Ok(0);
4428 }
4429 shape.iter().try_fold(1usize, |product, &dim| {
4430 product
4431 .checked_mul(dim)
4432 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))
4433 })
4434}
4435
4436fn checked_view_offset(
4437 shape: &[usize],
4438 strides: &[isize],
4439 base_offset: isize,
4440 indices: &[usize],
4441) -> Option<usize> {
4442 if indices.len() != shape.len() {
4443 return None;
4444 }
4445
4446 let mut offset = base_offset;
4447 for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
4448 if index >= extent {
4449 return None;
4450 }
4451 let index = isize::try_from(index).ok()?;
4452 let delta = index.checked_mul(stride)?;
4453 offset = offset.checked_add(delta)?;
4454 }
4455
4456 usize::try_from(offset).ok()
4457}
4458
4459fn reachable_layout_span(
4460 shape: &[usize],
4461 strides: &[isize],
4462 offset: isize,
4463) -> crate::Result<Option<(usize, usize)>> {
4464 if shape.contains(&0) {
4465 return Ok(None);
4466 }
4467
4468 let mut min_offset = offset;
4469 let mut max_offset = offset;
4470 for (&extent, &stride) in shape.iter().zip(strides) {
4471 let steps = isize::try_from(extent.saturating_sub(1)).map_err(|_| {
4472 crate::Error::validation(
4473 "TypedTensorViewMut::try_multi_slice_mut",
4474 ValidationError::IntegerOverflow,
4475 )
4476 })?;
4477 let end = stride.checked_mul(steps).ok_or_else(|| {
4478 crate::Error::validation(
4479 "TypedTensorViewMut::try_multi_slice_mut",
4480 ValidationError::IntegerOverflow,
4481 )
4482 })?;
4483 let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
4484 min_offset = min_offset.checked_add(axis_min).ok_or_else(|| {
4485 crate::Error::validation(
4486 "TypedTensorViewMut::try_multi_slice_mut",
4487 ValidationError::IntegerOverflow,
4488 )
4489 })?;
4490 max_offset = max_offset.checked_add(axis_max).ok_or_else(|| {
4491 crate::Error::validation(
4492 "TypedTensorViewMut::try_multi_slice_mut",
4493 ValidationError::IntegerOverflow,
4494 )
4495 })?;
4496 }
4497
4498 let min_offset = usize::try_from(min_offset).map_err(|_| {
4499 crate::Error::invalid_argument(
4500 "TypedTensorViewMut::try_multi_slice_mut",
4501 "layout",
4502 "minimum reachable offset is negative",
4503 )
4504 })?;
4505 let max_offset = usize::try_from(max_offset).map_err(|_| {
4506 crate::Error::invalid_argument(
4507 "TypedTensorViewMut::try_multi_slice_mut",
4508 "layout",
4509 "maximum reachable offset is negative",
4510 )
4511 })?;
4512 Ok(Some((min_offset, max_offset)))
4513}
4514
4515fn split_two_mut_ranges<T>(
4516 data: &mut [T],
4517 first: (usize, usize),
4518 second: (usize, usize),
4519) -> Option<(&mut [T], &mut [T])> {
4520 if first.1 < second.0 {
4521 let (_, after_first_start) = data.split_at_mut(first.0);
4522 let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
4523 let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
4524 let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
4525 Some((first_slice, second_slice))
4526 } else if second.1 < first.0 {
4527 let (_, after_second_start) = data.split_at_mut(second.0);
4528 let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
4529 let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
4530 let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
4531 Some((first_slice, second_slice))
4532 } else {
4533 None
4534 }
4535}
4536
4537fn adjusted_view_offset(offset: isize, span_start: usize) -> crate::Result<isize> {
4538 let span_start = isize::try_from(span_start).map_err(|_| {
4539 crate::Error::validation(
4540 "TypedTensorViewMut::try_multi_slice_mut",
4541 ValidationError::IntegerOverflow,
4542 )
4543 })?;
4544 offset.checked_sub(span_start).ok_or_else(|| {
4545 crate::Error::validation(
4546 "TypedTensorViewMut::try_multi_slice_mut",
4547 ValidationError::IntegerOverflow,
4548 )
4549 })
4550}
4551
4552fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
4553 layout: &TensorLayout<R>,
4554 offset: isize,
4555 data: &'a mut [T],
4556 placement: Placement,
4557) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
4558 let shape = R::shape_from_vec(shape_vec(layout.shape()))
4559 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
4560 let strides = R::strides_from_vec(stride_vec(layout.strides()))
4561 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
4562 TypedTensorViewMut::from_buffer_ref_mut(
4563 shape,
4564 strides,
4565 offset,
4566 TensorBufferRefMut::Host(data),
4567 placement,
4568 "TypedTensorViewMut::try_multi_slice_mut",
4569 )
4570}
4571
4572fn contiguous_layout_slice<'a, T, R: TensorRank>(
4573 layout: &TensorLayout<R>,
4574 data: &'a [T],
4575 op: &'static str,
4576) -> crate::Result<&'a [T]> {
4577 if !layout
4578 .is_compact_col_major()
4579 .map_err(|err| tensor_layout_error(op, err))?
4580 {
4581 return Err(crate::Error::invalid_argument(
4582 op,
4583 "layout",
4584 "view is not contiguous column-major",
4585 ));
4586 }
4587 let len = checked_view_element_count(layout.shape(), op)?;
4588 let start = usize::try_from(layout.offset())
4589 .map_err(|_| crate::Error::invalid_argument(op, "layout", "view offset is negative"))?;
4590 let end = start
4591 .checked_add(len)
4592 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
4593 data.get(start..end)
4594 .ok_or_else(|| crate::Error::validation(op, ValidationError::ViewOutOfBounds))
4595}
4596
4597fn relaxed_col_major_contiguous(
4598 shape: &[usize],
4599 strides: &[isize],
4600 op: &'static str,
4601) -> crate::Result<bool> {
4602 if shape.contains(&0) {
4603 return Ok(true);
4604 }
4605
4606 let mut expected = 1isize;
4607 for (&extent, &stride) in shape.iter().zip(strides) {
4608 if extent <= 1 {
4609 continue;
4610 }
4611 if stride != expected {
4612 return Ok(false);
4613 }
4614 let extent = isize::try_from(extent)
4615 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
4616 expected = expected
4617 .checked_mul(extent)
4618 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
4619 }
4620 Ok(true)
4621}
4622
4623fn reshape_layout_dyn<R: TensorRank>(
4624 layout: &TensorLayout<R>,
4625 shape: &[usize],
4626 buffer_len: usize,
4627 op: &'static str,
4628) -> crate::Result<TensorLayout<DynRank>> {
4629 match layout.reshape_view_as::<DynRank>(shape_vec(shape), buffer_len) {
4630 Ok(layout) => Ok(layout),
4631 Err(err) => {
4632 if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
4633 return Err(tensor_layout_error(op, err));
4634 }
4635 let from = checked_view_element_count(layout.shape(), op)?;
4636 let to = checked_view_element_count(shape, op)?;
4637 if from != to {
4638 return Err(tensor_layout_error(
4639 op,
4640 tenferro_tensor_core::ShapeMismatch::ReshapeElementCount { from, to }.into(),
4641 ));
4642 }
4643 TensorLayout::<DynRank>::compact(shape_vec(shape))
4644 .and_then(|compact| {
4645 TensorLayout::from_parts(
4646 shape_vec(compact.shape()),
4647 stride_vec(compact.strides()),
4648 layout.offset(),
4649 buffer_len,
4650 )
4651 })
4652 .map_err(|err| tensor_layout_error(op, err))
4653 }
4654 }
4655}
4656
4657fn core_slice_specs(
4658 slices: &[StridedSliceSpec],
4659 shape: &[usize],
4660 op: &'static str,
4661) -> crate::Result<Vec<CoreSliceSpec>> {
4662 if slices.len() != shape.len() {
4663 return Err(crate::Error::validation(
4664 op,
4665 ValidationError::RankMismatch {
4666 expected: shape.len(),
4667 actual: slices.len(),
4668 },
4669 ));
4670 }
4671
4672 let mut specs = Vec::with_capacity(slices.len());
4673 for (slice, &axis_len) in slices.iter().zip(shape) {
4674 specs.push(core_slice_spec(*slice, axis_len, op)?);
4675 }
4676 Ok(specs)
4677}
4678
4679fn core_slice_spec(
4680 slice: StridedSliceSpec,
4681 axis_len: usize,
4682 op: &'static str,
4683) -> crate::Result<CoreSliceSpec> {
4684 if slice.step() == 0 {
4685 return Err(crate::Error::validation(
4686 op,
4687 ValidationError::InvalidSliceStep { step: slice.step() },
4688 ));
4689 }
4690
4691 let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
4692 let end = match slice.end() {
4693 Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
4694 None => isize::try_from(axis_len)
4695 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
4696 };
4697
4698 if slice.step() > 0 {
4699 return Ok(CoreSliceSpec {
4700 start,
4701 end,
4702 step: slice.step(),
4703 });
4704 }
4705
4706 if start >= end {
4707 return Ok(CoreSliceSpec {
4708 start,
4709 end: start,
4710 step: slice.step(),
4711 });
4712 }
4713
4714 Ok(CoreSliceSpec {
4715 start: end
4716 .checked_sub(1)
4717 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
4718 end: start
4719 .checked_sub(1)
4720 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?,
4721 step: slice.step(),
4722 })
4723}
4724
4725fn normalize_strided_bound(
4726 bound: isize,
4727 axis_len: usize,
4728 op: &'static str,
4729 role: &'static str,
4730) -> crate::Result<isize> {
4731 let original_axis_len = axis_len;
4732 let axis_len = isize::try_from(axis_len)
4733 .map_err(|_| crate::Error::validation(op, ValidationError::IntegerOverflow))?;
4734 let bound = if bound < 0 {
4735 axis_len
4736 .checked_add(bound)
4737 .ok_or_else(|| crate::Error::validation(op, ValidationError::IntegerOverflow))?
4738 } else {
4739 bound
4740 };
4741 if !(0..=axis_len).contains(&bound) {
4742 let (start, end) = if role == "slice start" {
4743 (bound, bound)
4744 } else {
4745 (0, bound)
4746 };
4747 return Err(crate::Error::validation(
4748 op,
4749 ValidationError::InvalidSliceBounds {
4750 start,
4751 end,
4752 axis_len: original_axis_len,
4753 },
4754 ));
4755 }
4756 Ok(bound)
4757}
4758
4759fn slice_axis_specs(
4760 rank: usize,
4761 axis: usize,
4762 slice: StridedSliceSpec,
4763 op: &'static str,
4764) -> crate::Result<Vec<StridedSliceSpec>> {
4765 if axis >= rank {
4766 return Err(crate::Error::validation(
4767 op,
4768 ValidationError::AxisOutOfBounds { axis, rank },
4769 ));
4770 }
4771
4772 let mut slices = vec![StridedSliceSpec::all(); rank];
4773 slices[axis] = slice;
4774 Ok(slices)
4775}
4776
4777pub(crate) fn default_placement() -> Placement {
4778 Placement {
4779 memory_kind: MemoryKind::UnpinnedHost,
4780 device: None,
4781 cpu_affinity: None,
4782 }
4783}
4784
4785fn typed_tensor_from_vec_col_major<T, R: TensorRank>(
4786 shape: impl Into<R::Shape>,
4787 data: Vec<T>,
4788 op: &'static str,
4789) -> crate::Result<TypedTensor<T, R>> {
4790 try_typed_tensor_from_vec_col_major(shape, data, op)
4791}
4792
4793fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
4794 shape: impl Into<R::Shape>,
4795 data: Vec<T>,
4796 op: &'static str,
4797) -> crate::Result<TypedTensor<T, R>> {
4798 let layout = try_compact_layout(shape, op)?;
4799 try_checked_shape_len(layout.shape(), data.len(), op)?;
4800 Ok(TypedTensor {
4801 buffer: Buffer::Host(data),
4802 layout,
4803 placement: default_placement(),
4804 })
4805}
4806
4807fn typed_tensor_zeros<T: Clone + Zero, R: TensorRank>(
4808 shape: impl Into<R::Shape>,
4809) -> crate::Result<TypedTensor<T, R>> {
4810 try_typed_tensor_zeros(shape)
4811}
4812
4813fn try_typed_tensor_zeros<T: Clone + Zero, R: TensorRank>(
4814 shape: impl Into<R::Shape>,
4815) -> crate::Result<TypedTensor<T, R>> {
4816 let layout = try_compact_layout(shape, "zeros")?;
4817 let n = try_shape_product(layout.shape(), "zeros")?;
4818 Ok(TypedTensor {
4819 buffer: Buffer::Host(vec![T::zero(); n]),
4820 layout,
4821 placement: default_placement(),
4822 })
4823}
4824
4825fn typed_tensor_ones<T: Clone + One + Zero, R: TensorRank>(
4826 shape: impl Into<R::Shape>,
4827) -> crate::Result<TypedTensor<T, R>> {
4828 try_typed_tensor_ones(shape)
4829}
4830
4831fn try_typed_tensor_ones<T: Clone + One + Zero, R: TensorRank>(
4832 shape: impl Into<R::Shape>,
4833) -> crate::Result<TypedTensor<T, R>> {
4834 let layout = try_compact_layout(shape, "ones")?;
4835 let n = try_shape_product(layout.shape(), "ones")?;
4836 Ok(TypedTensor {
4837 buffer: Buffer::Host(vec![T::one(); n]),
4838 layout,
4839 placement: default_placement(),
4840 })
4841}
4842
4843fn typed_tensor_from_buffer_col_major<T: 'static, R: TensorRank>(
4844 shape: impl Into<R::Shape>,
4845 buffer: Buffer<T>,
4846 placement: Placement,
4847) -> crate::Result<TypedTensor<T, R>> {
4848 try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
4849}
4850
4851fn try_typed_tensor_from_buffer_col_major<T: 'static, R: TensorRank>(
4852 shape: impl Into<R::Shape>,
4853 buffer: Buffer<T>,
4854 placement: Placement,
4855) -> crate::Result<TypedTensor<T, R>> {
4856 let layout = try_compact_layout(shape, "from_buffer_col_major")?;
4857 let len = buffer.len();
4858 try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
4859 Ok(TypedTensor {
4860 buffer,
4861 layout,
4862 placement,
4863 })
4864}
4865
4866impl<T: Clone + Zero, R: TensorRank> TypedTensor<T, R> {
4867 /// Allocate a zero-filled tensor.
4868 ///
4869 /// # Examples
4870 ///
4871 /// ```rust
4872 /// use tenferro_tensor::TypedTensor;
4873 ///
4874 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
4875 /// assert_eq!(t.n_elements(), 6);
4876 /// ```
4877 /// # Errors
4878 ///
4879 /// Returns [`crate::Error::Validation`] with
4880 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
4881 /// product or compact-stride arithmetic overflows.
4882 pub fn zeros(shape: impl Into<R::Shape>) -> crate::Result<Self> {
4883 typed_tensor_zeros(shape)
4884 }
4885}
4886
4887impl<T: Clone + One + Zero, R: TensorRank> TypedTensor<T, R> {
4888 /// Allocate a one-filled tensor.
4889 ///
4890 /// # Examples
4891 ///
4892 /// ```rust
4893 /// use tenferro_tensor::TypedTensor;
4894 ///
4895 /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
4896 /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
4897 /// ```
4898 /// # Errors
4899 ///
4900 /// Returns [`crate::Error::Validation`] with
4901 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
4902 /// product or compact-stride arithmetic overflows.
4903 pub fn ones(shape: impl Into<R::Shape>) -> crate::Result<Self> {
4904 typed_tensor_ones(shape)
4905 }
4906}
4907
4908impl<T, R: TensorRank> TypedTensor<T, R> {
4909 /// Create a tensor from an existing buffer and compact column-major layout.
4910 ///
4911 /// This preserves the owned tensor invariant that layout metadata is
4912 /// compact column-major, including for backend-owned buffers.
4913 ///
4914 /// # Examples
4915 ///
4916 /// ```
4917 /// use tenferro_tensor::{Buffer, Placement, TypedTensor};
4918 ///
4919 /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
4920 /// vec![2],
4921 /// Buffer::Host(vec![1.0, 2.0]),
4922 /// Placement {
4923 /// memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
4924 /// device: None,
4925 /// cpu_affinity: None,
4926 /// },
4927 /// )
4928 /// .unwrap();
4929 /// assert_eq!(tensor.shape(), &[2]);
4930 /// ```
4931 /// # Errors
4932 ///
4933 /// Returns [`crate::Error::Validation`] with
4934 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
4935 /// the shape product differs from the buffer length,
4936 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape or
4937 /// stride arithmetic overflows, or
4938 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when a supplied
4939 /// rank-specific shape cannot be represented.
4940 pub fn from_buffer_col_major(
4941 shape: impl Into<R::Shape>,
4942 buffer: Buffer<T>,
4943 placement: Placement,
4944 ) -> crate::Result<Self>
4945 where
4946 T: 'static,
4947 {
4948 typed_tensor_from_buffer_col_major(shape, buffer, placement)
4949 }
4950
4951 /// Convert this tensor into static rank metadata after validating its rank.
4952 ///
4953 /// The buffer and placement are preserved. This method changes only the
4954 /// compile-time rank marker on the owned compact column-major tensor.
4955 ///
4956 /// # Examples
4957 ///
4958 /// ```rust
4959 /// use tenferro_tensor::{Rank, TypedTensor};
4960 ///
4961 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
4962 /// let ranked: TypedTensor<f64, Rank<2>> = tensor.try_into_rank::<2>()?;
4963 /// assert_eq!(ranked.shape(), &[2, 3]);
4964 /// # Ok::<(), tenferro_tensor::Error>(())
4965 /// ```
4966 /// # Errors
4967 ///
4968 /// Returns [`crate::Error::Validation`] with
4969 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when the typed
4970 /// rank does not match the existing shape,
4971 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when compact
4972 /// strides cannot be computed, or
4973 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
4974 /// preserved buffer cannot hold the rank-converted layout.
4975 pub fn try_into_rank<const N: usize>(self) -> crate::Result<TypedTensor<T, Rank<N>>> {
4976 let op = "TypedTensor::try_into_rank";
4977 let actual = self.shape().len();
4978 let shape: [usize; N] = self.shape().try_into().map_err(|_| {
4979 tensor_layout_error(
4980 op,
4981 ValidationError::RankMismatch {
4982 expected: N,
4983 actual,
4984 },
4985 )
4986 })?;
4987 let layout =
4988 TensorLayout::<Rank<N>>::compact(shape).map_err(|err| tensor_layout_error(op, err))?;
4989 Ok(TypedTensor {
4990 buffer: self.buffer,
4991 layout,
4992 placement: self.placement,
4993 })
4994 }
4995
4996 /// Number of elements in the tensor.
4997 ///
4998 /// # Examples
4999 ///
5000 /// ```rust
5001 /// use tenferro_tensor::TypedTensor;
5002 ///
5003 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
5004 /// assert_eq!(t.n_elements(), 6);
5005 /// ```
5006 pub fn n_elements(&self) -> usize {
5007 // Invariant: owned tensor constructors validate compact shape length against buffer length.
5008 match try_shape_product(self.shape(), "TypedTensor::n_elements") {
5009 Ok(n) => n,
5010 Err(err) => {
5011 unreachable!("TypedTensor compact shape is validated at construction: {err}")
5012 }
5013 }
5014 }
5015
5016 /// Tensor shape.
5017 ///
5018 /// # Examples
5019 ///
5020 /// ```
5021 /// use tenferro_tensor::TypedTensor;
5022 ///
5023 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5024 /// assert_eq!(t.shape(), &[2]);
5025 /// ```
5026 pub fn shape(&self) -> &[usize] {
5027 self.layout.shape()
5028 }
5029
5030 /// Tensor rank.
5031 ///
5032 /// # Examples
5033 ///
5034 /// ```
5035 /// use tenferro_tensor::TypedTensor;
5036 ///
5037 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
5038 /// assert_eq!(t.rank(), 2);
5039 /// ```
5040 pub fn rank(&self) -> usize {
5041 self.shape().len()
5042 }
5043
5044 /// Tensor layout metadata.
5045 ///
5046 /// Owned typed tensors are always compact column-major layouts.
5047 ///
5048 /// # Examples
5049 ///
5050 /// ```
5051 /// use tenferro_tensor::TypedTensor;
5052 ///
5053 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
5054 /// assert_eq!(t.layout().strides(), &[1, 2]);
5055 /// ```
5056 pub fn layout(&self) -> &TensorLayout<R> {
5057 &self.layout
5058 }
5059
5060 /// Return the storage backing this tensor.
5061 ///
5062 /// This is an explicit storage-inspection API for backend glue and tests.
5063 /// Host value inspection should prefer [`TypedTensor::host_data`] when the
5064 /// caller requires host storage.
5065 ///
5066 /// # Examples
5067 ///
5068 /// ```
5069 /// use tenferro_tensor::{Buffer, TypedTensor};
5070 ///
5071 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5072 /// assert!(matches!(t.buffer(), Buffer::Host(_)));
5073 /// ```
5074 pub fn buffer(&self) -> &Buffer<T> {
5075 &self.buffer
5076 }
5077
5078 /// Return the shared-allocation domain carried by the backend buffer.
5079 ///
5080 /// # Examples
5081 ///
5082 /// ```rust
5083 /// use tenferro_tensor::TypedTensor;
5084 ///
5085 /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
5086 /// assert_eq!(tensor.allocation_domain(), None);
5087 /// # Ok::<(), tenferro_tensor::Error>(())
5088 /// ```
5089 pub fn allocation_domain(&self) -> Option<AllocationDomainId>
5090 where
5091 T: 'static,
5092 {
5093 match &self.buffer {
5094 Buffer::Host(_) => None,
5095 Buffer::Backend(buffer) => buffer.allocation_domain(),
5096 }
5097 }
5098
5099 /// Return the stable physical backend allocation identity.
5100 ///
5101 /// # Examples
5102 ///
5103 /// ```rust
5104 /// use tenferro_tensor::TypedTensor;
5105 ///
5106 /// let tensor = TypedTensor::<f32>::from_vec_col_major(vec![1], vec![1.0])?;
5107 /// assert_eq!(tensor.allocation_id(), None);
5108 /// # Ok::<(), tenferro_tensor::Error>(())
5109 /// ```
5110 pub fn allocation_id(&self) -> Option<AllocationId>
5111 where
5112 T: 'static,
5113 {
5114 match &self.buffer {
5115 Buffer::Host(_) => None,
5116 Buffer::Backend(buffer) => buffer.allocation_id(),
5117 }
5118 }
5119
5120 /// Return placement metadata for this tensor.
5121 ///
5122 /// # Examples
5123 ///
5124 /// ```
5125 /// use tenferro_tensor::{MemoryKind, TypedTensor};
5126 ///
5127 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
5128 /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
5129 /// ```
5130 pub fn placement(&self) -> &Placement {
5131 &self.placement
5132 }
5133
5134 /// Replace placement metadata without changing the storage buffer.
5135 ///
5136 /// # Examples
5137 ///
5138 /// ```
5139 /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
5140 ///
5141 /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
5142 /// t.set_placement(Placement {
5143 /// memory_kind: MemoryKind::PinnedHost,
5144 /// device: None,
5145 /// cpu_affinity: None,
5146 /// });
5147 /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
5148 /// ```
5149 pub fn set_placement(&mut self, placement: Placement) {
5150 self.placement = placement;
5151 }
5152
5153 /// Replace only CPU routing/locality metadata without changing storage.
5154 ///
5155 /// Device, memory kind, backend allocation domain, and allocation identity
5156 /// remain unchanged.
5157 ///
5158 /// # Examples
5159 ///
5160 /// ```rust
5161 /// use tenferro_tensor::{CpuDomainId, TypedTensor};
5162 ///
5163 /// let mut tensor = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0])?;
5164 /// tensor.set_cpu_affinity(Some(CpuDomainId::new(4)));
5165 /// assert_eq!(tensor.placement().cpu_affinity, Some(CpuDomainId::new(4)));
5166 /// # Ok::<(), tenferro_tensor::Error>(())
5167 /// ```
5168 pub fn set_cpu_affinity(&mut self, cpu_affinity: Option<CpuDomainId>) {
5169 self.placement.cpu_affinity = cpu_affinity;
5170 }
5171
5172 /// Borrow this tensor as a typed view preserving rank and layout metadata.
5173 ///
5174 /// # Examples
5175 ///
5176 /// ```rust
5177 /// use tenferro_tensor::{Rank, TypedTensor};
5178 ///
5179 /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
5180 /// let view = tensor.as_view();
5181 /// assert_eq!(view.strides(), &[1, 2]);
5182 /// ```
5183 pub fn as_view(&self) -> TypedTensorView<'_, T, R>
5184 where
5185 T: 'static,
5186 {
5187 let buffer = match &self.buffer {
5188 Buffer::Host(data) => TensorBufferRef::Host(data),
5189 Buffer::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
5190 };
5191 TypedTensorView {
5192 buffer,
5193 layout: self.layout.clone(),
5194 placement: self.placement.clone(),
5195 }
5196 }
5197
5198 /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
5199 ///
5200 /// # Examples
5201 ///
5202 /// ```rust
5203 /// use tenferro_tensor::TypedTensor;
5204 ///
5205 /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
5206 /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
5207 /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
5208 /// ```
5209 pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
5210 where
5211 T: 'static,
5212 {
5213 let layout = self.layout.clone();
5214 let placement = self.placement.clone();
5215 let buffer = match &mut self.buffer {
5216 Buffer::Host(data) => TensorBufferRefMut::Host(data),
5217 Buffer::Backend(buffer) => TensorBufferRefMut::Backend(Arc::clone(buffer)),
5218 };
5219 TypedTensorViewMut {
5220 buffer,
5221 layout,
5222 placement,
5223 }
5224 }
5225
5226 /// Borrow a read-only strided region view over this tensor's backend
5227 /// (device) buffer from explicit layout metadata.
5228 ///
5229 /// This is a metadata-only view: no data is copied or transferred. The
5230 /// layout's reachable element span is validated against the backend
5231 /// buffer's physical length. Host-backed tensors are rejected with an
5232 /// explicit backend error; host regions are expressed with
5233 /// [`TypedTensorView::from_slice`] over host storage instead.
5234 ///
5235 /// # Examples
5236 ///
5237 /// ```rust
5238 /// use tenferro_tensor::TypedTensor;
5239 ///
5240 /// // Host tensors are rejected: this constructor is for backend buffers.
5241 /// let host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
5242 /// let err = host.backend_region_view(vec![2, 2], vec![1, 2], 0).unwrap_err();
5243 /// assert!(err.to_string().contains("backend"));
5244 /// ```
5245 /// # Errors
5246 ///
5247 /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
5248 /// backend region views require a backend buffer. It returns
5249 /// [`crate::Error::Validation`] with
5250 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
5251 /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
5252 /// when the region exceeds the backend buffer, or
5253 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
5254 /// arithmetic overflow.
5255 pub fn backend_region_view(
5256 &self,
5257 shape: Vec<usize>,
5258 strides: Vec<isize>,
5259 offset: isize,
5260 ) -> crate::Result<TypedTensorView<'_, T, DynRank>>
5261 where
5262 T: 'static,
5263 {
5264 let op = "TypedTensor::backend_region_view";
5265 let Buffer::Backend(buffer) = &self.buffer else {
5266 return Err(crate::Error::runtime_state(
5267 op,
5268 "expected a backend (device) buffer; host tensors use \
5269 TypedTensorView::from_slice over host storage",
5270 ));
5271 };
5272 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
5273 .map_err(|err| tensor_layout_error(op, err))?;
5274 Ok(TypedTensorView {
5275 buffer: TensorBufferRef::Backend(Arc::clone(buffer)),
5276 layout,
5277 placement: self.placement.clone(),
5278 })
5279 }
5280
5281 /// Borrow a mutable strided region view over this tensor's backend
5282 /// (device) buffer from explicit layout metadata.
5283 ///
5284 /// This is the mutable counterpart of
5285 /// [`TypedTensor::backend_region_view`]. The layout's reachable element
5286 /// span is validated against the backend buffer's physical length, and
5287 /// layouts whose logical elements alias the same physical element are
5288 /// rejected. Host-backed tensors are rejected with an explicit backend
5289 /// error; mutable host regions must go through
5290 /// [`TypedTensorViewMut::try_multi_slice_mut`] or host constructors.
5291 ///
5292 /// Backend buffers are shared handles, so distinct region views over one
5293 /// buffer can coexist; disjointness between regions used concurrently by
5294 /// backend operations is the caller's contract (as with BLAS-style
5295 /// in-place update APIs).
5296 ///
5297 /// # Examples
5298 ///
5299 /// ```rust
5300 /// use tenferro_tensor::TypedTensor;
5301 ///
5302 /// // Host tensors are rejected: this constructor is for backend buffers.
5303 /// let mut host = TypedTensor::<f64>::from_vec_col_major(vec![4], vec![0.0; 4]).unwrap();
5304 /// let err = host.backend_region_view_mut(vec![2, 2], vec![1, 2], 0).unwrap_err();
5305 /// assert!(err.to_string().contains("backend"));
5306 /// ```
5307 /// # Errors
5308 ///
5309 /// Returns [`crate::Error::RuntimeState`] when this tensor is host-backed;
5310 /// mutable backend region views require a backend buffer. It returns
5311 /// [`crate::Error::Validation`] with
5312 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] for incompatible
5313 /// shape/stride ranks, [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`]
5314 /// when the region exceeds the backend buffer,
5315 /// [`tenferro_tensor_core::ValidationError::OverlappingMutableLayout`] when
5316 /// logical elements alias, or
5317 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] for layout
5318 /// arithmetic overflow.
5319 pub fn backend_region_view_mut(
5320 &mut self,
5321 shape: Vec<usize>,
5322 strides: Vec<isize>,
5323 offset: isize,
5324 ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>>
5325 where
5326 T: 'static,
5327 {
5328 let op = "TypedTensor::backend_region_view_mut";
5329 let Buffer::Backend(buffer) = &self.buffer else {
5330 return Err(crate::Error::runtime_state(
5331 op,
5332 "expected a backend (device) buffer; mutable host regions use \
5333 TypedTensorViewMut host constructors or try_multi_slice_mut",
5334 ));
5335 };
5336 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
5337 .map_err(|err| tensor_layout_error(op, err))?;
5338 layout
5339 .validate_mutable_no_overlap()
5340 .map_err(|err| tensor_layout_error(op, err))?;
5341 Ok(TypedTensorViewMut {
5342 buffer: TensorBufferRefMut::Backend(Arc::clone(buffer)),
5343 layout,
5344 placement: self.placement.clone(),
5345 })
5346 }
5347
5348 /// Consume this tensor and return its layout metadata.
5349 ///
5350 /// # Examples
5351 ///
5352 /// ```
5353 /// use tenferro_tensor::TypedTensor;
5354 ///
5355 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5356 /// assert!(t.into_layout().is_compact_col_major().unwrap());
5357 /// ```
5358 pub fn into_layout(self) -> TensorLayout<R> {
5359 self.layout
5360 }
5361
5362 /// Consume this tensor and return its storage, layout, and placement.
5363 ///
5364 /// # Examples
5365 ///
5366 /// ```
5367 /// use tenferro_tensor::{Buffer, TypedTensor};
5368 ///
5369 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5370 /// let (buffer, layout, placement) = t.into_parts();
5371 /// assert!(matches!(buffer, Buffer::Host(_)));
5372 /// assert_eq!(layout.shape(), &[2]);
5373 /// assert!(placement.device.is_none());
5374 /// ```
5375 pub fn into_parts(self) -> (Buffer<T>, TensorLayout<R>, Placement) {
5376 (self.buffer, self.layout, self.placement)
5377 }
5378}
5379
5380impl<T: Clone, R: TensorRank> TypedTensor<T, R> {
5381 /// Create a tensor from a column-major buffer.
5382 ///
5383 /// # Examples
5384 ///
5385 /// ```
5386 /// use tenferro_tensor::TypedTensor;
5387 ///
5388 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
5389 /// assert_eq!(t.get(&[1, 0])?, &2.0);
5390 /// # Ok::<(), tenferro_tensor::Error>(())
5391 /// ```
5392 /// # Errors
5393 ///
5394 /// Returns [`crate::Error::Validation`] with
5395 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
5396 /// the shape product differs from `data.len()`, or
5397 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
5398 /// arithmetic overflows.
5399 pub fn from_vec_col_major(shape: impl Into<R::Shape>, data: Vec<T>) -> crate::Result<Self> {
5400 typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
5401 }
5402
5403 /// Consume this tensor and return its owned column-major host buffer.
5404 ///
5405 /// # Examples
5406 ///
5407 /// ```
5408 /// use tenferro_tensor::TypedTensor;
5409 ///
5410 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5411 /// let (shape, data) = t.into_vec_col_major().unwrap();
5412 /// assert_eq!(shape, vec![2]);
5413 /// assert_eq!(data, vec![1.0, 2.0]);
5414 /// ```
5415 /// # Errors
5416 ///
5417 /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
5418 /// storage; download it before exporting a host `Vec`.
5419 pub fn into_vec_col_major(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
5420 let shape = self.shape().to_vec();
5421 match self.buffer {
5422 Buffer::Host(data) => Ok((shape, data)),
5423 Buffer::Backend(_) => Err(crate::Error::runtime_state(
5424 "into_vec_col_major",
5425 "backend buffers cannot be exported as host Vec",
5426 )),
5427 }
5428 }
5429
5430 /// Borrow the host buffer.
5431 ///
5432 /// # Examples
5433 ///
5434 /// ```rust
5435 /// use tenferro_tensor::TypedTensor;
5436 ///
5437 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5438 /// assert_eq!(t.host_data()?, &[1.0, 2.0]);
5439 /// # Ok::<(), tenferro_tensor::Error>(())
5440 /// ```
5441 /// # Errors
5442 ///
5443 /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
5444 /// storage; download it before borrowing host data.
5445 pub fn host_data(&self) -> crate::Result<&[T]> {
5446 match &self.buffer {
5447 Buffer::Host(v) => Ok(v),
5448 Buffer::Backend(_) => Err(crate::Error::runtime_state(
5449 "TypedTensor::host_data",
5450 "backend buffers cannot be inspected as host slices; download explicitly first",
5451 )),
5452 }
5453 }
5454
5455 /// View the tensor data as a flat slice.
5456 ///
5457 /// This is an alias for `host_data()` for API consistency with
5458 /// `Tensor::as_slice`.
5459 ///
5460 /// # Examples
5461 ///
5462 /// ```
5463 /// use tenferro_tensor::TypedTensor;
5464 ///
5465 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5466 /// assert_eq!(t.as_slice()?, &[1.0, 2.0]);
5467 /// # Ok::<(), tenferro_tensor::Error>(())
5468 /// ```
5469 /// # Errors
5470 ///
5471 /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
5472 /// storage; download it before borrowing it as a host slice.
5473 pub fn as_slice(&self) -> crate::Result<&[T]> {
5474 self.host_data()
5475 }
5476
5477 /// Mutably borrow the host buffer.
5478 ///
5479 /// # Examples
5480 ///
5481 /// ```rust
5482 /// use tenferro_tensor::TypedTensor;
5483 ///
5484 /// let mut t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
5485 /// t.host_data_mut()?[0] = 3.0;
5486 /// assert_eq!(t.host_data()?, &[3.0, 0.0]);
5487 /// # Ok::<(), tenferro_tensor::Error>(())
5488 /// ```
5489 /// # Errors
5490 ///
5491 /// Returns [`crate::Error::RuntimeState`] when this tensor uses backend
5492 /// storage; download it before mutably borrowing host data.
5493 pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
5494 match &mut self.buffer {
5495 Buffer::Host(v) => Ok(v),
5496 Buffer::Backend(_) => Err(crate::Error::runtime_state(
5497 "TypedTensor::host_data_mut",
5498 "backend buffers cannot be mutated as host slices; download explicitly first",
5499 )),
5500 }
5501 }
5502
5503 /// Compute the linear physical-buffer offset for a logical index.
5504 ///
5505 /// # Examples
5506 ///
5507 /// ```rust
5508 /// use tenferro_tensor::TypedTensor;
5509 ///
5510 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
5511 /// assert_eq!(t.linear_offset(&[1, 2])?, 5);
5512 /// # Ok::<(), tenferro_tensor::Error>(())
5513 /// ```
5514 /// # Errors
5515 ///
5516 /// Returns [`crate::Error::Validation`] with
5517 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5518 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5519 /// when an index is outside its axis extent, or
5520 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
5521 /// arithmetic overflows.
5522 pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
5523 try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
5524 }
5525
5526 /// Compute the physical element offset for a logical index.
5527 ///
5528 /// # Examples
5529 ///
5530 /// ```rust
5531 /// use tenferro_tensor::TypedTensor;
5532 ///
5533 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
5534 /// assert_eq!(t.layout_linear_offset(&[1, 2])?, 5);
5535 /// # Ok::<(), tenferro_tensor::Error>(())
5536 /// ```
5537 /// # Errors
5538 ///
5539 /// Returns [`crate::Error::Validation`] with
5540 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5541 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5542 /// when an index is outside its axis extent, or
5543 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
5544 /// arithmetic overflows.
5545 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
5546 try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::layout_linear_offset")
5547 }
5548
5549 /// Return whether this owned tensor is compact column-major.
5550 ///
5551 /// # Examples
5552 ///
5553 /// ```rust
5554 /// use tenferro_tensor::TypedTensor;
5555 ///
5556 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
5557 /// assert!(t.is_col_major_contiguous()?);
5558 /// # Ok::<(), tenferro_tensor::Error>(())
5559 /// ```
5560 /// # Errors
5561 ///
5562 /// Returns [`crate::Error::Validation`] with
5563 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5564 /// compactness arithmetic overflows.
5565 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
5566 self.layout
5567 .is_compact_col_major()
5568 .map_err(|err| tensor_layout_error("TypedTensor::is_col_major_contiguous", err))
5569 }
5570
5571 /// Return a compact string summary of this tensor's layout metadata.
5572 ///
5573 /// # Examples
5574 ///
5575 /// ```rust
5576 /// use tenferro_tensor::TypedTensor;
5577 ///
5578 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
5579 /// assert!(t.layout_summary().contains("shape=[2]"));
5580 /// # Ok::<(), tenferro_tensor::Error>(())
5581 /// ```
5582 pub fn layout_summary(&self) -> String {
5583 layout_summary(self.shape(), self.layout.strides(), self.layout.offset())
5584 }
5585
5586 /// Assert this tensor is compact column-major.
5587 ///
5588 /// # Examples
5589 ///
5590 /// ```rust
5591 /// use tenferro_tensor::TypedTensor;
5592 ///
5593 /// let t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
5594 /// t.assert_col_major_contiguous()?;
5595 /// # Ok::<(), tenferro_tensor::Error>(())
5596 /// ```
5597 /// # Errors
5598 ///
5599 /// Returns [`crate::Error::Validation`] with
5600 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5601 /// compactness arithmetic overflows, or
5602 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
5603 /// tensor is not compact column-major.
5604 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
5605 assert_layout_col_major_contiguous(
5606 self.is_col_major_contiguous()?,
5607 self.shape(),
5608 self.layout.strides(),
5609 self.layout.offset(),
5610 "TypedTensor::assert_col_major_contiguous",
5611 )
5612 }
5613
5614 /// Borrow a single element by multi-index.
5615 ///
5616 /// # Examples
5617 ///
5618 /// ```rust
5619 /// use tenferro_tensor::TypedTensor;
5620 ///
5621 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
5622 /// assert_eq!(t.get(&[1])?, &2.0);
5623 /// # Ok::<(), tenferro_tensor::Error>(())
5624 /// ```
5625 /// # Errors
5626 ///
5627 /// Returns [`crate::Error::Validation`] with
5628 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5629 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5630 /// when an index is outside its axis extent, or
5631 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5632 /// computed offset is outside the host buffer. It returns
5633 /// [`crate::Error::RuntimeState`] when the tensor uses backend storage.
5634 pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
5635 let off = self.linear_offset(indices)?;
5636 self.host_data()?.get(off).ok_or_else(|| {
5637 crate::Error::validation("TypedTensor::get", ValidationError::ViewOutOfBounds)
5638 })
5639 }
5640
5641 /// Mutably borrow a single element by multi-index.
5642 ///
5643 /// # Examples
5644 ///
5645 /// ```rust
5646 /// use tenferro_tensor::TypedTensor;
5647 ///
5648 /// let mut t = TypedTensor::<f64>::zeros(vec![1]).unwrap();
5649 /// *t.get_mut(&[0])? = 7.0;
5650 /// assert_eq!(t.host_data()?, &[7.0]);
5651 /// # Ok::<(), tenferro_tensor::Error>(())
5652 /// ```
5653 /// # Errors
5654 ///
5655 /// Returns [`crate::Error::Validation`] with
5656 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5657 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5658 /// when an index is outside its axis extent, or
5659 /// [`tenferro_tensor_core::ValidationError::ViewOutOfBounds`] when the
5660 /// computed offset is outside the host buffer. It returns
5661 /// [`crate::Error::RuntimeState`] when the tensor uses backend storage.
5662 pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
5663 let off = self.linear_offset(indices)?;
5664 self.host_data_mut()?.get_mut(off).ok_or_else(|| {
5665 crate::Error::validation("TypedTensor::get_mut", ValidationError::ViewOutOfBounds)
5666 })
5667 }
5668}
5669
5670impl Tensor {
5671 /// Create a tensor from a shape and column-major flat data.
5672 ///
5673 /// This is the `Tensor`-level equivalent of
5674 /// `TypedTensor::<T>::from_vec_col_major`.
5675 ///
5676 /// # Examples
5677 ///
5678 /// ```
5679 /// use tenferro_tensor::Tensor;
5680 ///
5681 /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
5682 /// assert_eq!(t.shape(), &[2, 2]);
5683 /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
5684 /// ```
5685 /// # Errors
5686 ///
5687 /// Returns [`crate::Error::Validation`] with
5688 /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when
5689 /// the shape product differs from `data.len()`, or
5690 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when shape
5691 /// arithmetic overflows.
5692 pub fn from_vec_col_major<T: TensorScalar>(
5693 shape: impl tenferro_tensor_core::IntoShapeVec,
5694 data: Vec<T>,
5695 ) -> crate::Result<Self> {
5696 T::into_tensor(shape.into_shape_vec().to_vec(), data)
5697 }
5698
5699 /// Tensor shape.
5700 ///
5701 /// # Examples
5702 ///
5703 /// ```rust
5704 /// use tenferro_tensor::{Tensor, TypedTensor};
5705 ///
5706 /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
5707 /// assert_eq!(t.shape(), &[2]);
5708 /// ```
5709 pub fn shape(&self) -> &[usize] {
5710 match self {
5711 Tensor::F32(t) => t.shape(),
5712 Tensor::F64(t) => t.shape(),
5713 Tensor::I32(t) => t.shape(),
5714 Tensor::I64(t) => t.shape(),
5715 Tensor::Bool(t) => t.shape(),
5716 Tensor::C32(t) => t.shape(),
5717 Tensor::C64(t) => t.shape(),
5718 }
5719 }
5720
5721 /// Tensor dtype tag.
5722 ///
5723 /// # Examples
5724 ///
5725 /// ```rust
5726 /// use tenferro_tensor::{DType, Tensor, TypedTensor};
5727 ///
5728 /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
5729 /// assert_eq!(t.dtype(), DType::F64);
5730 /// ```
5731 pub fn dtype(&self) -> DType {
5732 match self {
5733 Tensor::F32(_) => DType::F32,
5734 Tensor::F64(_) => DType::F64,
5735 Tensor::I32(_) => DType::I32,
5736 Tensor::I64(_) => DType::I64,
5737 Tensor::Bool(_) => DType::Bool,
5738 Tensor::C32(_) => DType::C32,
5739 Tensor::C64(_) => DType::C64,
5740 }
5741 }
5742
5743 /// Return placement metadata for this dtype-erased tensor.
5744 ///
5745 /// # Examples
5746 ///
5747 /// ```rust
5748 /// use tenferro_tensor::{MemoryKind, Tensor};
5749 ///
5750 /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
5751 /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
5752 /// ```
5753 pub fn placement(&self) -> &Placement {
5754 match self {
5755 Tensor::F32(t) => t.placement(),
5756 Tensor::F64(t) => t.placement(),
5757 Tensor::I32(t) => t.placement(),
5758 Tensor::I64(t) => t.placement(),
5759 Tensor::Bool(t) => t.placement(),
5760 Tensor::C32(t) => t.placement(),
5761 Tensor::C64(t) => t.placement(),
5762 }
5763 }
5764
5765 /// Return whether this tensor is backed by backend-native storage.
5766 ///
5767 /// # Examples
5768 ///
5769 /// ```rust
5770 /// use tenferro_tensor::Tensor;
5771 ///
5772 /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
5773 /// assert!(!t.is_backend_buffer());
5774 /// ```
5775 pub fn is_backend_buffer(&self) -> bool {
5776 match self {
5777 Tensor::F32(t) => t.buffer().is_backend(),
5778 Tensor::F64(t) => t.buffer().is_backend(),
5779 Tensor::I32(t) => t.buffer().is_backend(),
5780 Tensor::I64(t) => t.buffer().is_backend(),
5781 Tensor::Bool(t) => t.buffer().is_backend(),
5782 Tensor::C32(t) => t.buffer().is_backend(),
5783 Tensor::C64(t) => t.buffer().is_backend(),
5784 }
5785 }
5786
5787 /// Compute the physical element offset for a logical index.
5788 ///
5789 /// # Examples
5790 ///
5791 /// ```rust
5792 /// use tenferro_tensor::Tensor;
5793 ///
5794 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5795 /// assert_eq!(t.layout_linear_offset(&[1])?, 1);
5796 /// # Ok::<(), tenferro_tensor::Error>(())
5797 /// ```
5798 /// # Errors
5799 ///
5800 /// Returns [`crate::Error::Validation`] with
5801 /// [`tenferro_tensor_core::ValidationError::RankMismatch`] when `indices`
5802 /// has the wrong rank, [`tenferro_tensor_core::ValidationError::InvalidArgument`]
5803 /// when an index is outside its axis extent, or
5804 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when offset
5805 /// arithmetic overflows.
5806 pub fn layout_linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
5807 match self {
5808 Tensor::F32(t) => t.layout_linear_offset(indices),
5809 Tensor::F64(t) => t.layout_linear_offset(indices),
5810 Tensor::I32(t) => t.layout_linear_offset(indices),
5811 Tensor::I64(t) => t.layout_linear_offset(indices),
5812 Tensor::Bool(t) => t.layout_linear_offset(indices),
5813 Tensor::C32(t) => t.layout_linear_offset(indices),
5814 Tensor::C64(t) => t.layout_linear_offset(indices),
5815 }
5816 }
5817
5818 /// Return whether this tensor is compact column-major.
5819 ///
5820 /// # Examples
5821 ///
5822 /// ```rust
5823 /// use tenferro_tensor::Tensor;
5824 ///
5825 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5826 /// assert!(t.is_col_major_contiguous()?);
5827 /// # Ok::<(), tenferro_tensor::Error>(())
5828 /// ```
5829 /// # Errors
5830 ///
5831 /// Returns [`crate::Error::Validation`] with
5832 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5833 /// compactness arithmetic overflows.
5834 pub fn is_col_major_contiguous(&self) -> crate::Result<bool> {
5835 match self {
5836 Tensor::F32(t) => t.is_col_major_contiguous(),
5837 Tensor::F64(t) => t.is_col_major_contiguous(),
5838 Tensor::I32(t) => t.is_col_major_contiguous(),
5839 Tensor::I64(t) => t.is_col_major_contiguous(),
5840 Tensor::Bool(t) => t.is_col_major_contiguous(),
5841 Tensor::C32(t) => t.is_col_major_contiguous(),
5842 Tensor::C64(t) => t.is_col_major_contiguous(),
5843 }
5844 }
5845
5846 /// Return a compact string summary of this tensor's layout metadata.
5847 ///
5848 /// # Examples
5849 ///
5850 /// ```rust
5851 /// use tenferro_tensor::Tensor;
5852 ///
5853 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5854 /// assert!(t.layout_summary().contains("shape=[2]"));
5855 /// # Ok::<(), tenferro_tensor::Error>(())
5856 /// ```
5857 pub fn layout_summary(&self) -> String {
5858 let layout = tensor_layout(self);
5859 layout_summary(layout.shape(), layout.strides(), layout.offset())
5860 }
5861
5862 /// Assert this tensor is compact column-major.
5863 ///
5864 /// # Examples
5865 ///
5866 /// ```rust
5867 /// use tenferro_tensor::Tensor;
5868 ///
5869 /// let t = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
5870 /// t.assert_col_major_contiguous()?;
5871 /// # Ok::<(), tenferro_tensor::Error>(())
5872 /// ```
5873 /// # Errors
5874 ///
5875 /// Returns [`crate::Error::Validation`] with
5876 /// [`tenferro_tensor_core::ValidationError::IntegerOverflow`] when
5877 /// compactness arithmetic overflows, or
5878 /// [`tenferro_tensor_core::ValidationError::InvalidArgument`] when the
5879 /// tensor is not compact column-major.
5880 pub fn assert_col_major_contiguous(&self) -> crate::Result<()> {
5881 let layout = tensor_layout(self);
5882 assert_layout_col_major_contiguous(
5883 self.is_col_major_contiguous()?,
5884 layout.shape(),
5885 layout.strides(),
5886 layout.offset(),
5887 "Tensor::assert_col_major_contiguous",
5888 )
5889 }
5890
5891 /// Try to borrow the host data as a typed slice.
5892 ///
5893 /// Returns an error if the tensor dtype does not match `T`.
5894 ///
5895 /// # Examples
5896 ///
5897 /// ```
5898 /// use tenferro_tensor::{Tensor, TypedTensor};
5899 ///
5900 /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
5901 /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
5902 /// assert!(t.as_slice::<f32>().is_err());
5903 /// ```
5904 /// # Errors
5905 ///
5906 /// Returns [`crate::Error::Validation`] with
5907 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
5908 /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
5909 /// matching tensor uses backend storage that has not been downloaded.
5910 pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
5911 T::as_slice(self)
5912 }
5913
5914 /// Consume this tensor and return its owned column-major buffer when the
5915 /// dtype matches.
5916 ///
5917 /// # Examples
5918 ///
5919 /// ```
5920 /// use tenferro_tensor::Tensor;
5921 ///
5922 /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
5923 /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
5924 /// ```
5925 /// # Errors
5926 ///
5927 /// Returns [`crate::Error::Validation`] with
5928 /// [`tenferro_tensor_core::ValidationError::DTypeMismatch`] when `T` does
5929 /// not match the tensor dtype, or [`crate::Error::RuntimeState`] when the
5930 /// matching tensor uses backend storage that has not been downloaded.
5931 pub fn into_vec_col_major<T: TensorScalar>(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
5932 let typed = T::into_typed(self)?;
5933 typed.into_vec_col_major()
5934 }
5935}
5936
5937// Kept for crate-local layout tests while tensor indexing helpers remain split
5938// across tensor and CPU crates.
5939#[allow(dead_code)]
5940pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
5941 for i in 0..shape.len() {
5942 if shape[i] == 0 {
5943 out[i] = 0;
5944 } else {
5945 out[i] = flat % shape[i];
5946 flat /= shape[i];
5947 }
5948 }
5949}