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