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());
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 /// Borrow one host element by logical index.
704 ///
705 /// Returns `None` for out-of-bounds indices and backend buffers.
706 ///
707 /// # Examples
708 ///
709 /// ```rust
710 /// use tenferro_tensor::TypedTensorView;
711 ///
712 /// let data = [1_i32, 2];
713 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 0, &data)?;
714 /// assert_eq!(view.get(&[1]), Some(&2));
715 /// # Ok::<(), tenferro_tensor::Error>(())
716 /// ```
717 pub fn get(&self, indices: &[usize]) -> Option<&T> {
718 let offset = self.linear_offset(indices)?;
719 match &self.buffer {
720 TensorBufferRef::Host(data) => data.get(offset),
721 TensorBufferRef::Backend(_) => None,
722 }
723 }
724
725 /// Borrow the contiguous host slice covered by this view.
726 ///
727 /// Returns an explicit error for backend buffers and for non-contiguous
728 /// layouts. This method never downloads or materializes backend data.
729 ///
730 /// # Examples
731 ///
732 /// ```rust
733 /// use tenferro_tensor::TypedTensorView;
734 ///
735 /// let data = [1_i32, 2, 3];
736 /// let view = TypedTensorView::from_slice(vec![2], vec![1], 1, &data)?;
737 /// assert_eq!(view.as_slice()?, &[2, 3]);
738 /// # Ok::<(), tenferro_tensor::Error>(())
739 /// ```
740 pub fn as_slice(&self) -> crate::Result<&'a [T]> {
741 let data =
742 match &self.buffer {
743 TensorBufferRef::Host(data) => data,
744 TensorBufferRef::Backend(_) => return Err(crate::Error::backend_failure(
745 "TypedTensorView::as_slice",
746 "backend buffers cannot be inspected as host slices; download explicitly first",
747 )),
748 };
749 contiguous_layout_slice(self.layout(), data, "TypedTensorView::as_slice")
750 }
751
752 /// Materialize this view as compact column-major host tensor storage.
753 ///
754 /// This is an explicit same-placement copy boundary. Host placement
755 /// metadata is preserved on the materialized tensor. Backend buffers return
756 /// an error here instead of being downloaded implicitly; backend-specific
757 /// compacting paths must stay on that backend.
758 ///
759 /// # Examples
760 ///
761 /// ```rust
762 /// use tenferro_tensor::{Rank, TypedTensor};
763 ///
764 /// let tensor = TypedTensor::<i32, Rank<2>>::from_vec_col_major([2, 2], vec![1, 2, 3, 4]).unwrap();
765 /// let transposed = tensor.as_view().transpose_view([1, 0])?;
766 /// let compact = transposed.to_contiguous()?;
767 /// assert_eq!(compact.as_slice()?, &[1, 3, 2, 4]);
768 /// # Ok::<(), tenferro_tensor::Error>(())
769 /// ```
770 pub fn to_contiguous(&self) -> crate::Result<TypedTensor<T, R>>
771 where
772 T: Clone,
773 {
774 let op = "TypedTensorView::to_contiguous";
775 let data = materialize_view_buffer_col_major(
776 self.shape(),
777 self.strides(),
778 self.offset(),
779 &self.buffer,
780 op,
781 )?;
782 let shape = R::shape_from_vec(self.shape().to_vec().into())
783 .map_err(|err| tensor_layout_error(op, err))?;
784 TypedTensor::from_buffer_col_major(shape, Buffer::Host(data), self.placement.clone())
785 }
786
787 /// Return a metadata-only axis permutation.
788 ///
789 /// # Examples
790 ///
791 /// ```rust
792 /// use tenferro_tensor::{Rank, TypedTensorView};
793 ///
794 /// let data = [1_i32, 2, 3, 4, 5, 6];
795 /// let view = TypedTensorView::<_, Rank<2>>::from_slice_ranked([2, 3], [1, 2], 0, &data)?;
796 /// let transposed = view.transpose_view([1, 0])?;
797 /// assert_eq!(transposed.shape(), &[3, 2]);
798 /// # Ok::<(), tenferro_tensor::Error>(())
799 /// ```
800 pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
801 let layout = self
802 .layout
803 .transpose_view(axes)
804 .map_err(|err| tensor_layout_error("TypedTensorView::transpose_view", err))?;
805 Ok(Self {
806 buffer: self.buffer.clone(),
807 layout,
808 placement: self.placement.clone(),
809 })
810 }
811
812 /// Return a metadata-only slice using one [`StridedSliceSpec`] per axis.
813 ///
814 /// # Examples
815 ///
816 /// ```rust
817 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
818 ///
819 /// let data = [1_i32, 2, 3];
820 /// let view = TypedTensorView::from_slice(vec![3], vec![1], 0, &data)?;
821 /// let reversed = view.try_slice(&[StridedSliceSpec::reverse()])?;
822 /// assert_eq!(reversed.get(&[0]), Some(&3));
823 /// # Ok::<(), tenferro_tensor::Error>(())
824 /// ```
825 pub fn try_slice(&self, slices: &[StridedSliceSpec]) -> crate::Result<Self> {
826 let specs = core_slice_specs(slices, self.shape(), "TypedTensorView::try_slice")?;
827 let layout = self
828 .layout
829 .slice_view(specs, self.buffer.len())
830 .map_err(|err| tensor_layout_error("TypedTensorView::try_slice", err))?;
831 Ok(Self {
832 buffer: self.buffer.clone(),
833 layout,
834 placement: self.placement.clone(),
835 })
836 }
837
838 /// Return a metadata-only slice along one axis.
839 ///
840 /// # Examples
841 ///
842 /// ```rust
843 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorView};
844 ///
845 /// let data = [1_i32, 2, 3, 4];
846 /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
847 /// assert_eq!(view.try_slice_axis(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
848 /// # Ok::<(), tenferro_tensor::Error>(())
849 /// ```
850 pub fn try_slice_axis(&self, axis: usize, slice: StridedSliceSpec) -> crate::Result<Self> {
851 let slices = slice_axis_specs(
852 self.shape().len(),
853 axis,
854 slice,
855 "TypedTensorView::try_slice_axis",
856 )?;
857 self.try_slice(&slices)
858 }
859
860 /// Return a metadata-only dynamic-rank reshape for contiguous column-major views.
861 ///
862 /// # Examples
863 ///
864 /// ```rust
865 /// use tenferro_tensor::TypedTensorView;
866 ///
867 /// let data = [1_i32, 2, 3, 4];
868 /// let view = TypedTensorView::from_slice(vec![2, 2], vec![1, 2], 0, &data)?;
869 /// assert_eq!(view.try_reshape(&[4])?.shape(), &[4]);
870 /// # Ok::<(), tenferro_tensor::Error>(())
871 /// ```
872 pub fn try_reshape(&self, shape: &[usize]) -> crate::Result<TypedTensorView<'a, T, DynRank>> {
873 let layout = reshape_layout_dyn(
874 &self.layout,
875 shape,
876 self.buffer.len(),
877 "TypedTensorView::try_reshape",
878 )?;
879 Ok(TypedTensorView {
880 buffer: self.buffer.clone(),
881 layout,
882 placement: self.placement.clone(),
883 })
884 }
885}
886
887/// Mutable borrowed view of typed tensor storage with arbitrary strides.
888///
889/// # Examples
890///
891/// ```rust
892/// use tenferro_tensor::TypedTensorViewMut;
893///
894/// let mut data = [1_i32, 2, 3];
895/// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
896/// *view.get_mut(&[2]).unwrap() = 10;
897/// assert_eq!(view.as_read_only().get(&[2]), Some(&10));
898/// # Ok::<(), tenferro_tensor::Error>(())
899/// ```
900#[derive(Debug)]
901pub struct TypedTensorViewMut<'a, T, R: TensorRank = DynRank> {
902 buffer: TensorBufferRefMut<'a, T>,
903 layout: TensorLayout<R>,
904 placement: Placement,
905}
906
907impl<'a, T: 'static> TypedTensorViewMut<'a, T, DynRank> {
908 /// Create a mutable dynamic-rank view over compact column-major host data.
909 ///
910 /// # Examples
911 ///
912 /// ```rust
913 /// use tenferro_tensor::TypedTensorViewMut;
914 ///
915 /// let mut data = [1_i32, 2, 3, 4];
916 /// let view = TypedTensorViewMut::from_col_major(&[2, 2], &mut data)?;
917 /// assert_eq!(view.strides(), &[1, 2]);
918 /// # Ok::<(), tenferro_tensor::Error>(())
919 /// ```
920 pub fn from_col_major(shape: &[usize], data: &'a mut [T]) -> crate::Result<Self> {
921 let layout = TensorLayout::<DynRank>::compact(shape.to_vec().into())
922 .map_err(|err| tensor_layout_error("TypedTensorViewMut::from_col_major", err))?;
923 Self::from_buffer_ref_mut(
924 layout.shape().to_vec(),
925 layout.strides().to_vec(),
926 layout.offset(),
927 TensorBufferRefMut::Host(data),
928 default_placement(),
929 "TypedTensorViewMut::from_col_major",
930 )
931 }
932
933 /// Create a mutable host view from explicit layout metadata.
934 ///
935 /// Layouts where distinct logical elements can alias the same physical
936 /// element are rejected.
937 ///
938 /// # Examples
939 ///
940 /// ```rust
941 /// use tenferro_tensor::TypedTensorViewMut;
942 ///
943 /// let mut data = [1_i32, 2];
944 /// assert!(TypedTensorViewMut::from_slice(vec![2], vec![0], 0, &mut data).is_err());
945 /// ```
946 pub fn from_slice(
947 shape: impl AsRef<[usize]>,
948 strides: impl AsRef<[isize]>,
949 offset: isize,
950 data: &'a mut [T],
951 ) -> crate::Result<Self> {
952 Self::from_buffer_ref_mut(
953 shape.as_ref().to_vec(),
954 strides.as_ref().to_vec(),
955 offset,
956 TensorBufferRefMut::Host(data),
957 default_placement(),
958 "TypedTensorViewMut::from_slice",
959 )
960 }
961}
962
963impl<'a, T: 'static, R: TensorRank> TypedTensorViewMut<'a, T, R> {
964 /// Create a rank-generic mutable host view from explicit layout metadata.
965 ///
966 /// # Examples
967 ///
968 /// ```rust
969 /// use tenferro_tensor::{Rank, TypedTensorViewMut};
970 ///
971 /// let mut data = [1_i32, 2, 3, 4];
972 /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
973 /// assert_eq!(view.shape(), &[2, 2]);
974 /// # Ok::<(), tenferro_tensor::Error>(())
975 /// ```
976 pub fn from_slice_ranked(
977 shape: impl Into<R::Shape>,
978 strides: impl Into<R::Strides>,
979 offset: isize,
980 data: &'a mut [T],
981 ) -> crate::Result<Self> {
982 Self::from_buffer_ref_mut(
983 shape,
984 strides,
985 offset,
986 TensorBufferRefMut::Host(data),
987 default_placement(),
988 "TypedTensorViewMut::from_slice_ranked",
989 )
990 }
991
992 fn from_buffer_ref_mut(
993 shape: impl Into<R::Shape>,
994 strides: impl Into<R::Strides>,
995 offset: isize,
996 buffer: TensorBufferRefMut<'a, T>,
997 placement: Placement,
998 op: &'static str,
999 ) -> crate::Result<Self> {
1000 let layout = TensorLayout::from_parts(shape.into(), strides.into(), offset, buffer.len())
1001 .map_err(|err| tensor_layout_error(op, err))?;
1002 layout
1003 .validate_mutable_no_overlap()
1004 .map_err(|err| tensor_layout_error(op, err))?;
1005 Ok(Self {
1006 buffer,
1007 layout,
1008 placement,
1009 })
1010 }
1011
1012 /// Return the logical shape.
1013 ///
1014 /// # Examples
1015 ///
1016 /// ```rust
1017 /// use tenferro_tensor::TypedTensorViewMut;
1018 ///
1019 /// let mut data = [0_i32; 2];
1020 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1021 /// assert_eq!(view.shape(), &[2]);
1022 /// # Ok::<(), tenferro_tensor::Error>(())
1023 /// ```
1024 pub fn shape(&self) -> &[usize] {
1025 self.layout.shape()
1026 }
1027
1028 /// Return strides in element units.
1029 ///
1030 /// # Examples
1031 ///
1032 /// ```rust
1033 /// use tenferro_tensor::TypedTensorViewMut;
1034 ///
1035 /// let mut data = [0_i32; 2];
1036 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![-1], 1, &mut data)?;
1037 /// assert_eq!(view.strides(), &[-1]);
1038 /// # Ok::<(), tenferro_tensor::Error>(())
1039 /// ```
1040 pub fn strides(&self) -> &[isize] {
1041 self.layout.strides()
1042 }
1043
1044 /// Return the physical element offset.
1045 ///
1046 /// # Examples
1047 ///
1048 /// ```rust
1049 /// use tenferro_tensor::TypedTensorViewMut;
1050 ///
1051 /// let mut data = [1_i32, 2];
1052 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 1, &mut data)?;
1053 /// assert_eq!(view.offset(), 1);
1054 /// # Ok::<(), tenferro_tensor::Error>(())
1055 /// ```
1056 pub fn offset(&self) -> isize {
1057 self.layout.offset()
1058 }
1059
1060 /// Return the borrowed host storage backing this view.
1061 ///
1062 /// This exposes the entire backing host allocation, not just the logical
1063 /// slice covered by this view. Use [`TypedTensorViewMut::as_read_only`]
1064 /// with [`TypedTensorView::as_slice`] when the caller needs the contiguous
1065 /// logical region instead.
1066 ///
1067 /// # Examples
1068 ///
1069 /// ```rust
1070 /// use tenferro_tensor::TypedTensorViewMut;
1071 ///
1072 /// let mut data = [1_i32, 2];
1073 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1074 /// assert_eq!(view.host_storage()?, &[1, 2]);
1075 /// # Ok::<(), tenferro_tensor::Error>(())
1076 /// ```
1077 pub fn host_storage(&self) -> crate::Result<&[T]> {
1078 match &self.buffer {
1079 TensorBufferRefMut::Host(data) => Ok(data),
1080 TensorBufferRefMut::Backend(_) => Err(crate::Error::backend_failure(
1081 "TypedTensorViewMut::host_storage",
1082 "backend buffers cannot expose host storage; download explicitly first",
1083 )),
1084 }
1085 }
1086
1087 /// Mutably borrow the host storage backing this view.
1088 ///
1089 /// This exposes the entire backing host allocation, not just the logical
1090 /// slice covered by this view. Use [`TypedTensorViewMut::copy_from_contiguous`]
1091 /// or element accessors when mutating the logical region instead.
1092 ///
1093 /// # Examples
1094 ///
1095 /// ```rust
1096 /// use tenferro_tensor::TypedTensorViewMut;
1097 ///
1098 /// let mut data = [1_i32, 2];
1099 /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1100 /// view.host_storage_mut()?[0] = 3;
1101 /// assert_eq!(view.get(&[0]), Some(&3));
1102 /// # Ok::<(), tenferro_tensor::Error>(())
1103 /// ```
1104 pub fn host_storage_mut(&mut self) -> crate::Result<&mut [T]> {
1105 match &mut self.buffer {
1106 TensorBufferRefMut::Host(data) => Ok(data),
1107 TensorBufferRefMut::Backend(_) => Err(crate::Error::backend_failure(
1108 "TypedTensorViewMut::host_storage_mut",
1109 "backend buffers cannot expose mutable host storage; download explicitly first",
1110 )),
1111 }
1112 }
1113
1114 /// Return the number of logical elements in this view.
1115 ///
1116 /// # Examples
1117 ///
1118 /// ```rust
1119 /// use tenferro_tensor::TypedTensorViewMut;
1120 ///
1121 /// let mut data = [0_i32; 6];
1122 /// let view = TypedTensorViewMut::from_slice(vec![2, 3], vec![1, 2], 0, &mut data)?;
1123 /// assert_eq!(view.n_elements(), 6);
1124 /// # Ok::<(), tenferro_tensor::Error>(())
1125 /// ```
1126 pub fn n_elements(&self) -> usize {
1127 // Invariant: public mutable view constructors validate logical element count.
1128 match checked_view_element_count(self.shape(), "TypedTensorViewMut::n_elements") {
1129 Ok(n) => n,
1130 Err(err) => {
1131 unreachable!("TypedTensorViewMut layout shape is validated at construction: {err}")
1132 }
1133 }
1134 }
1135
1136 /// Return layout metadata for this view.
1137 ///
1138 /// # Examples
1139 ///
1140 /// ```rust
1141 /// use tenferro_tensor::TypedTensorViewMut;
1142 ///
1143 /// let mut data = [1_i32, 2];
1144 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1145 /// assert!(view.layout().is_compact_col_major());
1146 /// # Ok::<(), tenferro_tensor::Error>(())
1147 /// ```
1148 pub fn layout(&self) -> &TensorLayout<R> {
1149 &self.layout
1150 }
1151
1152 /// Return placement metadata for this view.
1153 ///
1154 /// # Examples
1155 ///
1156 /// ```rust
1157 /// use tenferro_tensor::{MemoryKind, TypedTensorViewMut};
1158 ///
1159 /// let mut data = [1_i32];
1160 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1161 /// assert_eq!(view.placement().memory_kind, MemoryKind::UnpinnedHost);
1162 /// # Ok::<(), tenferro_tensor::Error>(())
1163 /// ```
1164 pub fn placement(&self) -> &Placement {
1165 &self.placement
1166 }
1167
1168 /// Return the backend allocation for backend integrations.
1169 #[doc(hidden)]
1170 pub fn backend_buffer(&self) -> Option<&Arc<dyn BackendBuffer<T>>> {
1171 match &self.buffer {
1172 TensorBufferRefMut::Host(_) => None,
1173 TensorBufferRefMut::Backend(buffer) => Some(buffer),
1174 }
1175 }
1176
1177 /// Compute the physical element offset for a logical index.
1178 ///
1179 /// # Examples
1180 ///
1181 /// ```rust
1182 /// use tenferro_tensor::TypedTensorViewMut;
1183 ///
1184 /// let mut data = [1_i32, 2, 3];
1185 /// let view = TypedTensorViewMut::from_slice(vec![3], vec![-1], 2, &mut data)?;
1186 /// assert_eq!(view.linear_offset(&[2]), Some(0));
1187 /// # Ok::<(), tenferro_tensor::Error>(())
1188 /// ```
1189 pub fn linear_offset(&self, indices: &[usize]) -> Option<usize> {
1190 checked_view_offset(self.shape(), self.strides(), self.offset(), indices)
1191 }
1192
1193 /// Borrow one host element by logical index.
1194 ///
1195 /// # Examples
1196 ///
1197 /// ```rust
1198 /// use tenferro_tensor::TypedTensorViewMut;
1199 ///
1200 /// let mut data = [1_i32, 2];
1201 /// let view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1202 /// assert_eq!(view.get(&[1]), Some(&2));
1203 /// # Ok::<(), tenferro_tensor::Error>(())
1204 /// ```
1205 pub fn get(&self, indices: &[usize]) -> Option<&T> {
1206 let offset = self.linear_offset(indices)?;
1207 match &self.buffer {
1208 TensorBufferRefMut::Host(data) => data.get(offset),
1209 TensorBufferRefMut::Backend(_) => None,
1210 }
1211 }
1212
1213 /// Mutably borrow one host element by logical index.
1214 ///
1215 /// # Examples
1216 ///
1217 /// ```rust
1218 /// use tenferro_tensor::TypedTensorViewMut;
1219 ///
1220 /// let mut data = [1_i32, 2];
1221 /// let mut view = TypedTensorViewMut::from_slice(vec![2], vec![1], 0, &mut data)?;
1222 /// *view.get_mut(&[1]).unwrap() = 20;
1223 /// assert_eq!(view.get(&[1]), Some(&20));
1224 /// # Ok::<(), tenferro_tensor::Error>(())
1225 /// ```
1226 pub fn get_mut(&mut self, indices: &[usize]) -> Option<&mut T> {
1227 let offset = self.linear_offset(indices)?;
1228 match &mut self.buffer {
1229 TensorBufferRefMut::Host(data) => data.get_mut(offset),
1230 TensorBufferRefMut::Backend(_) => None,
1231 }
1232 }
1233
1234 /// Copy compact column-major host tensor values into this mutable view.
1235 ///
1236 /// This is an explicit copy-back boundary. Backend source or destination
1237 /// buffers return an error instead of transferring data implicitly.
1238 ///
1239 /// # Examples
1240 ///
1241 /// ```rust
1242 /// use tenferro_tensor::{Rank, TypedTensor};
1243 ///
1244 /// let mut tensor = TypedTensor::<i32, Rank<2>>::from_vec_col_major([2, 2], vec![0, 0, 0, 0]).unwrap();
1245 /// let src = TypedTensor::<i32, Rank<2>>::from_vec_col_major([2, 2], vec![1, 2, 3, 4]).unwrap();
1246 /// tensor.as_view_mut().transpose_view([1, 0])?.copy_from_contiguous(&src)?;
1247 /// assert_eq!(tensor.as_slice()?, &[1, 3, 2, 4]);
1248 /// # Ok::<(), tenferro_tensor::Error>(())
1249 /// ```
1250 pub fn copy_from_contiguous(&mut self, src: &TypedTensor<T, R>) -> crate::Result<()>
1251 where
1252 T: Clone,
1253 {
1254 let op = "TypedTensorViewMut::copy_from_contiguous";
1255 if self.shape() != src.shape() {
1256 return Err(crate::Error::InvalidConfig {
1257 op,
1258 message: format!(
1259 "shape mismatch: destination {:?} does not match source {:?}",
1260 self.shape(),
1261 src.shape()
1262 ),
1263 });
1264 }
1265
1266 let src_data = match &src.buffer {
1267 Buffer::Host(data) => contiguous_layout_slice(src.layout(), data, op)?,
1268 Buffer::Backend(_) => {
1269 return Err(crate::Error::backend_failure(
1270 op,
1271 "source backend buffer cannot be copied through host memory; download explicitly first",
1272 ))
1273 }
1274 };
1275
1276 let shape = self.shape().to_vec();
1277 let strides = self.strides().to_vec();
1278 let offset = self.offset();
1279 let dst_data = match &mut self.buffer {
1280 TensorBufferRefMut::Host(data) => data,
1281 TensorBufferRefMut::Backend(_) => {
1282 return Err(crate::Error::backend_failure(
1283 op,
1284 "destination backend buffer cannot be updated through host memory; download explicitly first",
1285 ))
1286 }
1287 };
1288
1289 let mut src_iter = src_data.iter();
1290 for_each_layout_offset_col_major(&shape, &strides, offset, op, |offset| {
1291 let value = src_iter.next().ok_or_else(|| crate::Error::InvalidConfig {
1292 op,
1293 message: "source tensor ended before destination view".to_string(),
1294 })?;
1295 let dst = dst_data
1296 .get_mut(offset)
1297 .ok_or_else(|| crate::Error::InvalidConfig {
1298 op,
1299 message: "destination view offset is outside host buffer".to_string(),
1300 })?;
1301 *dst = value.clone();
1302 Ok(())
1303 })?;
1304 if src_iter.next().is_some() {
1305 return Err(crate::Error::InvalidConfig {
1306 op,
1307 message: "source tensor has elements remaining after destination copy".to_string(),
1308 });
1309 }
1310 Ok(())
1311 }
1312
1313 /// Borrow this mutable view as a read-only view.
1314 ///
1315 /// # Examples
1316 ///
1317 /// ```rust
1318 /// use tenferro_tensor::TypedTensorViewMut;
1319 ///
1320 /// let mut data = [1_i32];
1321 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1322 /// assert_eq!(view.as_read_only().get(&[0]), Some(&1));
1323 /// # Ok::<(), tenferro_tensor::Error>(())
1324 /// ```
1325 pub fn as_read_only(&self) -> TypedTensorView<'_, T, R> {
1326 let buffer = match &self.buffer {
1327 TensorBufferRefMut::Host(data) => TensorBufferRef::Host(data),
1328 TensorBufferRefMut::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
1329 };
1330 TypedTensorView {
1331 buffer,
1332 layout: self.layout.clone(),
1333 placement: self.placement.clone(),
1334 }
1335 }
1336
1337 /// Convert this mutable view into a read-only view.
1338 ///
1339 /// # Examples
1340 ///
1341 /// ```rust
1342 /// use tenferro_tensor::TypedTensorViewMut;
1343 ///
1344 /// let mut data = [1_i32];
1345 /// let view = TypedTensorViewMut::from_slice(vec![1], vec![1], 0, &mut data)?;
1346 /// assert_eq!(view.into_read_only().get(&[0]), Some(&1));
1347 /// # Ok::<(), tenferro_tensor::Error>(())
1348 /// ```
1349 pub fn into_read_only(self) -> TypedTensorView<'a, T, R> {
1350 let buffer = match self.buffer {
1351 TensorBufferRefMut::Host(data) => TensorBufferRef::Host(data),
1352 TensorBufferRefMut::Backend(buffer) => TensorBufferRef::Backend(buffer),
1353 };
1354 TypedTensorView {
1355 buffer,
1356 layout: self.layout,
1357 placement: self.placement,
1358 }
1359 }
1360
1361 /// Consume this mutable view and return a metadata-only axis permutation.
1362 ///
1363 /// # Examples
1364 ///
1365 /// ```rust
1366 /// use tenferro_tensor::{Rank, TypedTensorViewMut};
1367 ///
1368 /// let mut data = [1_i32, 2, 3, 4];
1369 /// let view = TypedTensorViewMut::<_, Rank<2>>::from_slice_ranked([2, 2], [1, 2], 0, &mut data)?;
1370 /// let transposed = view.transpose_view([1, 0])?;
1371 /// assert_eq!(transposed.strides(), &[2, 1]);
1372 /// # Ok::<(), tenferro_tensor::Error>(())
1373 /// ```
1374 pub fn transpose_view(
1375 self,
1376 axes: impl AsRef<[usize]>,
1377 ) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
1378 let Self {
1379 buffer,
1380 layout,
1381 placement,
1382 } = self;
1383 let layout = layout
1384 .transpose_view(axes)
1385 .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
1386 layout
1387 .validate_mutable_no_overlap()
1388 .map_err(|err| tensor_layout_error("TypedTensorViewMut::transpose_view", err))?;
1389 match buffer {
1390 TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
1391 buffer: TensorBufferRefMut::Host(data),
1392 layout,
1393 placement,
1394 }),
1395 TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
1396 buffer: TensorBufferRefMut::Backend(buffer),
1397 layout,
1398 placement,
1399 }),
1400 }
1401 }
1402
1403 /// Return a mutable metadata-only slice using one [`StridedSliceSpec`] per axis.
1404 ///
1405 /// # Examples
1406 ///
1407 /// ```rust
1408 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
1409 ///
1410 /// let mut data = [1_i32, 2, 3];
1411 /// let mut view = TypedTensorViewMut::from_slice(vec![3], vec![1], 0, &mut data)?;
1412 /// *view.try_slice(&[StridedSliceSpec::reverse()])?.get_mut(&[0]).unwrap() = 30;
1413 /// assert_eq!(view.get(&[2]), Some(&30));
1414 /// # Ok::<(), tenferro_tensor::Error>(())
1415 /// ```
1416 pub fn try_slice(
1417 &mut self,
1418 slices: &[StridedSliceSpec],
1419 ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
1420 let specs = core_slice_specs(slices, self.shape(), "TypedTensorViewMut::try_slice")?;
1421 let layout = self
1422 .layout
1423 .slice_view(specs, self.buffer.len())
1424 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_slice", err))?;
1425 layout
1426 .validate_mutable_no_overlap()
1427 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_slice", err))?;
1428 let placement = self.placement.clone();
1429 match &mut self.buffer {
1430 TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
1431 buffer: TensorBufferRefMut::Host(data),
1432 layout,
1433 placement,
1434 }),
1435 TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
1436 buffer: TensorBufferRefMut::Backend(Arc::clone(buffer)),
1437 layout,
1438 placement,
1439 }),
1440 }
1441 }
1442
1443 /// Return a mutable metadata-only slice along one axis.
1444 ///
1445 /// # Examples
1446 ///
1447 /// ```rust
1448 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
1449 ///
1450 /// let mut data = [1_i32, 2, 3, 4];
1451 /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
1452 /// assert_eq!(view.try_slice_axis(1, StridedSliceSpec::reverse())?.get(&[0, 0]), Some(&3));
1453 /// # Ok::<(), tenferro_tensor::Error>(())
1454 /// ```
1455 pub fn try_slice_axis(
1456 &mut self,
1457 axis: usize,
1458 slice: StridedSliceSpec,
1459 ) -> crate::Result<TypedTensorViewMut<'_, T, R>> {
1460 let slices = slice_axis_specs(
1461 self.shape().len(),
1462 axis,
1463 slice,
1464 "TypedTensorViewMut::try_slice_axis",
1465 )?;
1466 self.try_slice(&slices)
1467 }
1468
1469 /// Return two mutable metadata-only slices when their physical ranges are disjoint.
1470 ///
1471 /// # Examples
1472 ///
1473 /// ```rust
1474 /// use tenferro_tensor::{StridedSliceSpec, TypedTensorViewMut};
1475 ///
1476 /// let mut data = [1_i32, 2, 3, 4];
1477 /// let mut view = TypedTensorViewMut::from_slice(vec![4], vec![1], 0, &mut data)?;
1478 /// let (left, right) = view
1479 /// .try_multi_slice_mut(
1480 /// &[StridedSliceSpec::new(0, Some(2), 1)],
1481 /// &[StridedSliceSpec::new(2, Some(4), 1)],
1482 /// )
1483 /// .unwrap();
1484 /// assert_eq!(left.shape(), &[2]);
1485 /// assert_eq!(right.shape(), &[2]);
1486 /// # Ok::<(), tenferro_tensor::Error>(())
1487 /// ```
1488 pub fn try_multi_slice_mut(
1489 &mut self,
1490 first: &[StridedSliceSpec],
1491 second: &[StridedSliceSpec],
1492 ) -> Option<(TypedTensorViewMut<'_, T, R>, TypedTensorViewMut<'_, T, R>)> {
1493 let first_specs = core_slice_specs(
1494 first,
1495 self.shape(),
1496 "TypedTensorViewMut::try_multi_slice_mut",
1497 )
1498 .ok()?;
1499 let second_specs = core_slice_specs(
1500 second,
1501 self.shape(),
1502 "TypedTensorViewMut::try_multi_slice_mut",
1503 )
1504 .ok()?;
1505 let buffer_len = self.buffer.len();
1506 let first_layout = self.layout.slice_view(first_specs, buffer_len).ok()?;
1507 let second_layout = self.layout.slice_view(second_specs, buffer_len).ok()?;
1508 first_layout.validate_mutable_no_overlap().ok()?;
1509 second_layout.validate_mutable_no_overlap().ok()?;
1510
1511 match (
1512 reachable_layout_span(
1513 first_layout.shape(),
1514 first_layout.strides(),
1515 first_layout.offset(),
1516 )
1517 .ok()?,
1518 reachable_layout_span(
1519 second_layout.shape(),
1520 second_layout.strides(),
1521 second_layout.offset(),
1522 )
1523 .ok()?,
1524 ) {
1525 (Some(first_span), Some(second_span)) => {
1526 let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
1527 let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
1528 let (first_data, second_data) = match &mut self.buffer {
1529 TensorBufferRefMut::Host(data) => {
1530 split_two_mut_ranges(data, first_span, second_span)?
1531 }
1532 TensorBufferRefMut::Backend(_) => return None,
1533 };
1534 let first_view = view_mut_from_layout_and_slice(
1535 &first_layout,
1536 first_offset,
1537 first_data,
1538 self.placement.clone(),
1539 )
1540 .ok()?;
1541 let second_view = view_mut_from_layout_and_slice(
1542 &second_layout,
1543 second_offset,
1544 second_data,
1545 self.placement.clone(),
1546 )
1547 .ok()?;
1548 Some((first_view, second_view))
1549 }
1550 (None, Some(second_span)) => {
1551 let second_offset = adjusted_view_offset(second_layout.offset(), second_span.0)?;
1552 let (_, after_start) = match &mut self.buffer {
1553 TensorBufferRefMut::Host(data) => data.split_at_mut(second_span.0),
1554 TensorBufferRefMut::Backend(_) => return None,
1555 };
1556 let (second_data, _) = after_start.split_at_mut(second_span.1 - second_span.0 + 1);
1557 let first_view = view_mut_from_layout_and_slice(
1558 &first_layout,
1559 0,
1560 &mut [],
1561 self.placement.clone(),
1562 )
1563 .ok()?;
1564 let second_view = view_mut_from_layout_and_slice(
1565 &second_layout,
1566 second_offset,
1567 second_data,
1568 self.placement.clone(),
1569 )
1570 .ok()?;
1571 Some((first_view, second_view))
1572 }
1573 (Some(first_span), None) => {
1574 let first_offset = adjusted_view_offset(first_layout.offset(), first_span.0)?;
1575 let (_, after_start) = match &mut self.buffer {
1576 TensorBufferRefMut::Host(data) => data.split_at_mut(first_span.0),
1577 TensorBufferRefMut::Backend(_) => return None,
1578 };
1579 let (first_data, _) = after_start.split_at_mut(first_span.1 - first_span.0 + 1);
1580 let first_view = view_mut_from_layout_and_slice(
1581 &first_layout,
1582 first_offset,
1583 first_data,
1584 self.placement.clone(),
1585 )
1586 .ok()?;
1587 let second_view = view_mut_from_layout_and_slice(
1588 &second_layout,
1589 0,
1590 &mut [],
1591 self.placement.clone(),
1592 )
1593 .ok()?;
1594 Some((first_view, second_view))
1595 }
1596 (None, None) => {
1597 let first_view = view_mut_from_layout_and_slice(
1598 &first_layout,
1599 0,
1600 &mut [],
1601 self.placement.clone(),
1602 )
1603 .ok()?;
1604 let second_view = view_mut_from_layout_and_slice(
1605 &second_layout,
1606 0,
1607 &mut [],
1608 self.placement.clone(),
1609 )
1610 .ok()?;
1611 Some((first_view, second_view))
1612 }
1613 }
1614 }
1615
1616 /// Return a mutable metadata-only dynamic-rank reshape for contiguous views.
1617 ///
1618 /// # Examples
1619 ///
1620 /// ```rust
1621 /// use tenferro_tensor::TypedTensorViewMut;
1622 ///
1623 /// let mut data = [1_i32, 2, 3, 4];
1624 /// let mut view = TypedTensorViewMut::from_slice(vec![2, 2], vec![1, 2], 0, &mut data)?;
1625 /// assert_eq!(view.try_reshape(&[4])?.shape(), &[4]);
1626 /// # Ok::<(), tenferro_tensor::Error>(())
1627 /// ```
1628 pub fn try_reshape(
1629 &mut self,
1630 shape: &[usize],
1631 ) -> crate::Result<TypedTensorViewMut<'_, T, DynRank>> {
1632 let layout = reshape_layout_dyn(
1633 &self.layout,
1634 shape,
1635 self.buffer.len(),
1636 "TypedTensorViewMut::try_reshape",
1637 )?;
1638 layout
1639 .validate_mutable_no_overlap()
1640 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_reshape", err))?;
1641 let placement = self.placement.clone();
1642 match &mut self.buffer {
1643 TensorBufferRefMut::Host(data) => Ok(TypedTensorViewMut {
1644 buffer: TensorBufferRefMut::Host(data),
1645 layout,
1646 placement,
1647 }),
1648 TensorBufferRefMut::Backend(buffer) => Ok(TypedTensorViewMut {
1649 buffer: TensorBufferRefMut::Backend(Arc::clone(buffer)),
1650 layout,
1651 placement,
1652 }),
1653 }
1654 }
1655}
1656
1657/// Runtime scalar dtype tag.
1658///
1659/// # Examples
1660///
1661/// ```rust
1662/// use tenferro_tensor::DType;
1663///
1664/// assert_eq!(DType::F64 as u8, DType::F64 as u8);
1665/// ```
1666#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1667pub enum DType {
1668 F32,
1669 F64,
1670 I32,
1671 I64,
1672 Bool,
1673 C32,
1674 C64,
1675}
1676
1677/// Sealed trait for scalar types that can be stored in a [`Tensor`].
1678///
1679/// This trait is implemented for `f64`, `f32`, `i32`, `i64`, `bool`,
1680/// [`Complex64`], and [`Complex32`].
1681///
1682/// # Examples
1683///
1684/// ```
1685/// use tenferro_tensor::TensorScalar;
1686///
1687/// let tensor = <f64 as TensorScalar>::into_tensor(vec![2], vec![1.0, 2.0])?;
1688/// assert_eq!(tensor.as_slice::<f64>()?, [1.0, 2.0].as_slice());
1689/// # Ok::<(), tenferro_tensor::Error>(())
1690/// ```
1691pub trait TensorScalar: Copy + Clone + Send + Sync + 'static + private::Sealed {
1692 /// Real-valued counterpart of this scalar type.
1693 type Real: TensorScalar;
1694
1695 /// The [`DType`] tag corresponding to this scalar type.
1696 ///
1697 /// # Examples
1698 ///
1699 /// ```
1700 /// use tenferro_tensor::{DType, TensorScalar};
1701 ///
1702 /// assert_eq!(f64::dtype(), DType::F64);
1703 /// assert_eq!(f32::dtype(), DType::F32);
1704 /// ```
1705 fn dtype() -> DType;
1706
1707 /// Wrap typed column-major data into a [`Tensor`] enum variant.
1708 fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor>;
1709
1710 /// Borrow a typed tensor as a dtype-erased [`TensorRead`] view.
1711 ///
1712 /// This keeps the typed tensor borrowed instead of copying host data into
1713 /// a new dynamic tensor.
1714 ///
1715 /// # Examples
1716 ///
1717 /// ```
1718 /// use tenferro_tensor::{DType, TensorScalar, TypedTensor};
1719 ///
1720 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
1721 /// let read = f64::tensor_read(&tensor);
1722 /// assert_eq!(read.dtype(), DType::F64);
1723 /// assert_eq!(read.shape(), &[2]);
1724 /// ```
1725 fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_>;
1726
1727 /// Borrow the host data from a [`Tensor`].
1728 fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]>;
1729
1730 /// Mutably borrow the host data from a [`Tensor`].
1731 ///
1732 /// # Examples
1733 ///
1734 /// ```
1735 /// use tenferro_tensor::{Tensor, TensorScalar};
1736 ///
1737 /// let mut tensor = Tensor::from_vec_col_major(vec![1], vec![2.0_f64])?;
1738 /// <f64 as TensorScalar>::as_slice_mut(&mut tensor)?[0] = 3.0;
1739 ///
1740 /// assert_eq!(tensor.as_slice::<f64>()?, &[3.0]);
1741 /// # Ok::<(), tenferro_tensor::Error>(())
1742 /// ```
1743 fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]>;
1744
1745 /// Extract a [`TypedTensor<Self>`] from a dynamic [`Tensor`].
1746 ///
1747 /// # Examples
1748 ///
1749 /// ```
1750 /// use tenferro_tensor::{Tensor, TensorScalar};
1751 ///
1752 /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
1753 /// let typed = <f64 as TensorScalar>::into_typed(tensor)?;
1754 ///
1755 /// assert_eq!(typed.as_slice()?, &[1.0, 2.0]);
1756 /// # Ok::<(), tenferro_tensor::Error>(())
1757 /// ```
1758 fn into_typed(tensor: Tensor) -> crate::Result<TypedTensor<Self>>;
1759}
1760
1761mod private {
1762 pub trait Sealed {}
1763
1764 impl Sealed for f64 {}
1765 impl Sealed for f32 {}
1766 impl Sealed for i32 {}
1767 impl Sealed for i64 {}
1768 impl Sealed for bool {}
1769 impl Sealed for num_complex::Complex64 {}
1770 impl Sealed for num_complex::Complex32 {}
1771}
1772
1773macro_rules! impl_tensor_scalar {
1774 ($ty:ty, $real:ty, $dtype:ident, $variant:ident) => {
1775 impl TensorScalar for $ty {
1776 type Real = $real;
1777
1778 fn dtype() -> DType {
1779 DType::$dtype
1780 }
1781
1782 fn into_tensor(shape: Vec<usize>, data: Vec<Self>) -> crate::Result<Tensor> {
1783 TypedTensor::from_vec_col_major(shape, data).map(Tensor::$variant)
1784 }
1785
1786 fn tensor_read(tensor: &TypedTensor<Self>) -> TensorRead<'_> {
1787 TensorRead::from_view(TensorView::$variant(tensor.as_view()))
1788 }
1789
1790 fn as_slice(tensor: &Tensor) -> crate::Result<&[Self]> {
1791 let actual = tensor.dtype();
1792 match tensor {
1793 Tensor::$variant(t) => t.host_data(),
1794 _ => Err(crate::Error::DTypeMismatch {
1795 op: "Tensor::as_slice",
1796 lhs: Self::dtype(),
1797 rhs: actual,
1798 }),
1799 }
1800 }
1801
1802 fn as_slice_mut(tensor: &mut Tensor) -> crate::Result<&mut [Self]> {
1803 let actual = tensor.dtype();
1804 match tensor {
1805 Tensor::$variant(t) => t.host_data_mut(),
1806 _ => Err(crate::Error::DTypeMismatch {
1807 op: "Tensor::as_slice_mut",
1808 lhs: Self::dtype(),
1809 rhs: actual,
1810 }),
1811 }
1812 }
1813
1814 fn into_typed(tensor: Tensor) -> crate::Result<TypedTensor<Self>> {
1815 let actual = tensor.dtype();
1816 match tensor {
1817 Tensor::$variant(inner) => Ok(inner),
1818 _ => Err(crate::Error::DTypeMismatch {
1819 op: "TensorScalar::into_typed",
1820 lhs: Self::dtype(),
1821 rhs: actual,
1822 }),
1823 }
1824 }
1825 }
1826 };
1827}
1828
1829impl_tensor_scalar!(f64, f64, F64, F64);
1830impl_tensor_scalar!(f32, f32, F32, F32);
1831impl_tensor_scalar!(i64, i64, I64, I64);
1832impl_tensor_scalar!(i32, i32, I32, I32);
1833impl_tensor_scalar!(bool, bool, Bool, Bool);
1834impl_tensor_scalar!(Complex64, f64, C64, C64);
1835impl_tensor_scalar!(Complex32, f32, C32, C32);
1836
1837/// Dynamic tensor enum over the supported scalar types.
1838///
1839/// The enum keeps dtype dynamic and rank dynamic. Use
1840/// [`TypedTensor<T, R>`](TypedTensor) directly when the scalar type or rank
1841/// should be represented in Rust's type system.
1842///
1843/// # Examples
1844///
1845/// ```rust
1846/// use tenferro_tensor::{Tensor, TypedTensor};
1847///
1848/// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
1849/// assert_eq!(t.shape(), &[2]);
1850///
1851/// let erased = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 2.0]).unwrap();
1852/// assert_eq!(erased.shape().len(), 2);
1853/// ```
1854#[derive(Clone, Debug)]
1855pub enum Tensor {
1856 F32(TypedTensor<f32>),
1857 F64(TypedTensor<f64>),
1858 I32(TypedTensor<i32>),
1859 I64(TypedTensor<i64>),
1860 Bool(TypedTensor<bool>),
1861 C32(TypedTensor<Complex<f32>>),
1862 C64(TypedTensor<Complex<f64>>),
1863}
1864
1865/// Dynamic read-only borrowed tensor view.
1866///
1867/// `TensorView` keeps dtype erased while borrowing typed view metadata and
1868/// storage. Use [`TypedTensorView`] directly when the scalar type is statically
1869/// known.
1870///
1871/// # Examples
1872///
1873/// ```
1874/// use tenferro_tensor::{DType, TensorView, TypedTensorView};
1875///
1876/// let data = [1_i32, 2, 3, 4];
1877/// let typed = TypedTensorView::from_slice([2, 2], [1, 2], 0, &data)?;
1878/// let view = TensorView::I32(typed);
1879///
1880/// assert_eq!(view.dtype(), DType::I32);
1881/// assert_eq!(view.shape(), &[2, 2]);
1882/// # Ok::<(), tenferro_tensor::Error>(())
1883/// ```
1884#[derive(Clone, Debug)]
1885pub enum TensorView<'a> {
1886 F32(TypedTensorView<'a, f32>),
1887 F64(TypedTensorView<'a, f64>),
1888 I32(TypedTensorView<'a, i32>),
1889 I64(TypedTensorView<'a, i64>),
1890 Bool(TypedTensorView<'a, bool>),
1891 C32(TypedTensorView<'a, Complex<f32>>),
1892 C64(TypedTensorView<'a, Complex<f64>>),
1893}
1894
1895/// Read-only tensor input accepted by synchronous eager kernels.
1896///
1897/// `TensorRead` lets kernels accept either an owned tensor reference or a
1898/// borrowed [`TensorView`] without forcing callers to materialize first.
1899/// The `View` variant preserves arbitrary strides and offsets, so kernels that
1900/// support strided reads can consume transposes, slices, and broadcasts directly.
1901///
1902/// `TensorRead` is intentionally borrowed. It is an input-dispatch type, not an
1903/// owned lazy tensor value. APIs that need to store a lazy layout result should
1904/// keep an owned base tensor plus layout metadata, then expose a `TensorRead`
1905/// only for the duration of kernel dispatch.
1906///
1907/// # Examples
1908///
1909/// ```
1910/// use tenferro_tensor::{DType, Tensor, TensorRead};
1911///
1912/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
1913/// let read = TensorRead::from_tensor(&tensor);
1914///
1915/// assert_eq!(read.dtype(), DType::F64);
1916/// assert_eq!(read.shape(), &[2]);
1917/// ```
1918// Keep borrowed views inline to avoid allocation on read-only tensor dispatch paths.
1919#[allow(clippy::large_enum_variant)]
1920#[derive(Clone, Debug)]
1921pub enum TensorRead<'a> {
1922 Tensor(&'a Tensor),
1923 View(TensorView<'a>),
1924}
1925
1926/// Owned lazy tensor view over a shared base tensor.
1927///
1928/// This stores only ownership of the base allocation plus logical layout
1929/// metadata. Borrow it as [`TensorRead`] for kernels that understand strides,
1930/// or materialize it explicitly with [`TensorOwnedView::to_tensor`].
1931#[derive(Clone, Debug)]
1932pub struct TensorOwnedView {
1933 base: Arc<Tensor>,
1934 layout: TensorLayout<DynRank>,
1935}
1936
1937/// Owned tensor value that can be compact or a lazy view.
1938///
1939/// `TensorValue` is the owned counterpart to [`TensorRead`]. It is suitable for
1940/// storing eager results that should remain lazy until an operation actually
1941/// requires compact materialized storage.
1942#[derive(Clone, Debug)]
1943pub enum TensorValue {
1944 Tensor(Arc<Tensor>),
1945 View(TensorOwnedView),
1946}
1947
1948impl TensorOwnedView {
1949 /// Create an owned view preserving the base tensor's current layout.
1950 pub fn from_tensor(base: Arc<Tensor>) -> Self {
1951 let layout = tensor_layout(base.as_ref());
1952 Self { base, layout }
1953 }
1954
1955 /// Create an owned view with explicit layout metadata.
1956 pub fn from_parts(
1957 base: Arc<Tensor>,
1958 shape: Vec<usize>,
1959 strides: Vec<isize>,
1960 offset: isize,
1961 ) -> crate::Result<Self> {
1962 let layout = TensorLayout::from_parts(
1963 shape.into(),
1964 strides.into(),
1965 offset,
1966 tensor_buffer_len(&base),
1967 )
1968 .map_err(|err| tensor_layout_error("TensorOwnedView::from_parts", err))?;
1969 Ok(Self { base, layout })
1970 }
1971
1972 pub fn dtype(&self) -> DType {
1973 self.base.dtype()
1974 }
1975
1976 pub fn shape(&self) -> &[usize] {
1977 self.layout.shape()
1978 }
1979
1980 pub fn strides(&self) -> &[isize] {
1981 self.layout.strides()
1982 }
1983
1984 pub fn offset(&self) -> isize {
1985 self.layout.offset()
1986 }
1987
1988 pub fn tensor_view(&self) -> TensorView<'_> {
1989 tensor_view_with_layout(self.base.as_ref(), self.layout.clone())
1990 }
1991
1992 pub fn tensor_read(&self) -> TensorRead<'_> {
1993 TensorRead::from_view(self.tensor_view())
1994 }
1995
1996 /// Materialize this owned view into an owned compact tensor.
1997 ///
1998 /// This returns an explicit error for backend-backed views because no
1999 /// backend context is available for an implicit download.
2000 ///
2001 /// # Examples
2002 ///
2003 /// ```rust
2004 /// use std::sync::Arc;
2005 /// use tenferro_tensor::{Tensor, TensorOwnedView};
2006 ///
2007 /// let base = Arc::new(Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap());
2008 /// let view = TensorOwnedView::from_tensor(base);
2009 /// let tensor = view.to_tensor()?;
2010 /// assert_eq!(tensor.shape(), &[2]);
2011 /// # Ok::<(), tenferro_tensor::Error>(())
2012 /// ```
2013 pub fn to_tensor(&self) -> crate::Result<Tensor> {
2014 self.tensor_view().to_tensor()
2015 }
2016
2017 pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
2018 let layout = self
2019 .layout
2020 .transpose_view(axes)
2021 .map_err(|err| tensor_layout_error("TensorOwnedView::transpose_view", err))?;
2022 Ok(Self {
2023 base: Arc::clone(&self.base),
2024 layout,
2025 })
2026 }
2027
2028 pub fn reshape_view(&self, shape: &[usize]) -> crate::Result<Self> {
2029 let layout = reshape_layout_dyn(
2030 &self.layout,
2031 shape,
2032 tensor_buffer_len(&self.base),
2033 "TensorOwnedView::reshape_view",
2034 )?;
2035 Ok(Self {
2036 base: Arc::clone(&self.base),
2037 layout,
2038 })
2039 }
2040
2041 pub fn slice_view(&self, config: &SliceConfig) -> crate::Result<Self> {
2042 let op = "TensorOwnedView::slice_view";
2043 if config.starts.len() != self.shape().len() {
2044 return Err(crate::Error::RankMismatch {
2045 op,
2046 expected: self.shape().len(),
2047 actual: config.starts.len(),
2048 });
2049 }
2050 if config.limits.len() != self.shape().len() {
2051 return Err(crate::Error::RankMismatch {
2052 op,
2053 expected: self.shape().len(),
2054 actual: config.limits.len(),
2055 });
2056 }
2057 if config.strides.len() != self.shape().len() {
2058 return Err(crate::Error::RankMismatch {
2059 op,
2060 expected: self.shape().len(),
2061 actual: config.strides.len(),
2062 });
2063 }
2064
2065 let mut slices = Vec::with_capacity(self.shape().len());
2066 for ((&start, &limit), &stride) in config
2067 .starts
2068 .iter()
2069 .zip(config.limits.iter())
2070 .zip(config.strides.iter())
2071 {
2072 let start = isize::try_from(start).map_err(|_| crate::Error::InvalidConfig {
2073 op,
2074 message: format!("slice start {start} does not fit in isize"),
2075 })?;
2076 let limit = isize::try_from(limit).map_err(|_| crate::Error::InvalidConfig {
2077 op,
2078 message: format!("slice limit {limit} does not fit in isize"),
2079 })?;
2080 let stride = isize::try_from(stride).map_err(|_| crate::Error::InvalidConfig {
2081 op,
2082 message: format!("slice stride {stride} does not fit in isize"),
2083 })?;
2084 slices.push(StridedSliceSpec::new(start, Some(limit), stride));
2085 }
2086
2087 let specs = core_slice_specs(&slices, self.shape(), op)?;
2088 let layout = self
2089 .layout
2090 .slice_view(&specs, tensor_buffer_len(&self.base))
2091 .map_err(|err| tensor_layout_error(op, err))?;
2092 Ok(Self {
2093 base: Arc::clone(&self.base),
2094 layout,
2095 })
2096 }
2097
2098 pub fn broadcast_in_dim_view(&self, shape: &[usize], dims: &[usize]) -> crate::Result<Self> {
2099 let layout = self
2100 .layout
2101 .broadcast_in_dim_view::<DynRank>(
2102 shape.to_vec().into(),
2103 dims,
2104 tensor_buffer_len(&self.base),
2105 )
2106 .map_err(|err| tensor_layout_error("TensorOwnedView::broadcast_in_dim_view", err))?;
2107 Ok(Self {
2108 base: Arc::clone(&self.base),
2109 layout,
2110 })
2111 }
2112}
2113
2114impl TensorValue {
2115 pub fn from_tensor(tensor: Tensor) -> Self {
2116 Self::Tensor(Arc::new(tensor))
2117 }
2118
2119 pub fn from_tensor_arc(tensor: Arc<Tensor>) -> Self {
2120 Self::Tensor(tensor)
2121 }
2122
2123 pub fn as_tensor_arc(&self) -> Option<&Arc<Tensor>> {
2124 match self {
2125 Self::Tensor(tensor) => Some(tensor),
2126 Self::View(_) => None,
2127 }
2128 }
2129
2130 pub fn dtype(&self) -> DType {
2131 match self {
2132 Self::Tensor(tensor) => tensor.dtype(),
2133 Self::View(view) => view.dtype(),
2134 }
2135 }
2136
2137 pub fn shape(&self) -> &[usize] {
2138 match self {
2139 Self::Tensor(tensor) => tensor.shape(),
2140 Self::View(view) => view.shape(),
2141 }
2142 }
2143
2144 pub fn tensor_read(&self) -> TensorRead<'_> {
2145 match self {
2146 Self::Tensor(tensor) => TensorRead::from_tensor(tensor.as_ref()),
2147 Self::View(view) => view.tensor_read(),
2148 }
2149 }
2150
2151 /// Materialize this tensor value into an owned compact tensor.
2152 ///
2153 /// Compact tensor values are cloned. Lazy host views are materialized.
2154 /// Backend-backed views return an explicit error instead of panicking.
2155 ///
2156 /// # Examples
2157 ///
2158 /// ```rust
2159 /// use tenferro_tensor::{Tensor, TensorValue};
2160 ///
2161 /// let value = TensorValue::from_tensor(Tensor::from_vec_col_major(
2162 /// vec![2],
2163 /// vec![1.0_f64, 2.0],
2164 /// ).unwrap());
2165 /// let tensor = value.to_tensor()?;
2166 /// assert_eq!(tensor.shape(), &[2]);
2167 /// # Ok::<(), tenferro_tensor::Error>(())
2168 /// ```
2169 pub fn to_tensor(&self) -> crate::Result<Tensor> {
2170 match self {
2171 Self::Tensor(tensor) => Ok(tensor.as_ref().clone()),
2172 Self::View(view) => view.to_tensor(),
2173 }
2174 }
2175
2176 pub fn transpose_view(&self, axes: impl AsRef<[usize]>) -> crate::Result<Self> {
2177 match self {
2178 Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2179 .transpose_view(axes)
2180 .map(Self::View),
2181 Self::View(view) => view.transpose_view(axes).map(Self::View),
2182 }
2183 }
2184
2185 pub fn reshape_view(&self, shape: &[usize]) -> crate::Result<Self> {
2186 match self {
2187 Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2188 .reshape_view(shape)
2189 .map(Self::View),
2190 Self::View(view) => view.reshape_view(shape).map(Self::View),
2191 }
2192 }
2193
2194 pub fn slice_view(&self, config: &SliceConfig) -> crate::Result<Self> {
2195 match self {
2196 Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2197 .slice_view(config)
2198 .map(Self::View),
2199 Self::View(view) => view.slice_view(config).map(Self::View),
2200 }
2201 }
2202
2203 pub fn broadcast_in_dim_view(&self, shape: &[usize], dims: &[usize]) -> crate::Result<Self> {
2204 match self {
2205 Self::Tensor(tensor) => TensorOwnedView::from_tensor(Arc::clone(tensor))
2206 .broadcast_in_dim_view(shape, dims)
2207 .map(Self::View),
2208 Self::View(view) => view.broadcast_in_dim_view(shape, dims).map(Self::View),
2209 }
2210 }
2211}
2212
2213fn tensor_layout(tensor: &Tensor) -> TensorLayout<DynRank> {
2214 match tensor {
2215 Tensor::F32(tensor) => tensor.layout.clone(),
2216 Tensor::F64(tensor) => tensor.layout.clone(),
2217 Tensor::I32(tensor) => tensor.layout.clone(),
2218 Tensor::I64(tensor) => tensor.layout.clone(),
2219 Tensor::Bool(tensor) => tensor.layout.clone(),
2220 Tensor::C32(tensor) => tensor.layout.clone(),
2221 Tensor::C64(tensor) => tensor.layout.clone(),
2222 }
2223}
2224
2225fn tensor_buffer_len(tensor: &Tensor) -> usize {
2226 match tensor {
2227 Tensor::F32(tensor) => buffer_len(&tensor.buffer),
2228 Tensor::F64(tensor) => buffer_len(&tensor.buffer),
2229 Tensor::I32(tensor) => buffer_len(&tensor.buffer),
2230 Tensor::I64(tensor) => buffer_len(&tensor.buffer),
2231 Tensor::Bool(tensor) => buffer_len(&tensor.buffer),
2232 Tensor::C32(tensor) => buffer_len(&tensor.buffer),
2233 Tensor::C64(tensor) => buffer_len(&tensor.buffer),
2234 }
2235}
2236
2237fn buffer_len<T: 'static>(buffer: &Buffer<T>) -> usize {
2238 match buffer {
2239 Buffer::Host(data) => data.len(),
2240 Buffer::Backend(buffer) => buffer.len(),
2241 }
2242}
2243
2244fn tensor_view_with_layout(tensor: &Tensor, layout: TensorLayout<DynRank>) -> TensorView<'_> {
2245 match tensor {
2246 Tensor::F32(tensor) => TensorView::F32(typed_view_with_layout(tensor, layout)),
2247 Tensor::F64(tensor) => TensorView::F64(typed_view_with_layout(tensor, layout)),
2248 Tensor::I32(tensor) => TensorView::I32(typed_view_with_layout(tensor, layout)),
2249 Tensor::I64(tensor) => TensorView::I64(typed_view_with_layout(tensor, layout)),
2250 Tensor::Bool(tensor) => TensorView::Bool(typed_view_with_layout(tensor, layout)),
2251 Tensor::C32(tensor) => TensorView::C32(typed_view_with_layout(tensor, layout)),
2252 Tensor::C64(tensor) => TensorView::C64(typed_view_with_layout(tensor, layout)),
2253 }
2254}
2255
2256fn typed_view_with_layout<T: 'static>(
2257 tensor: &TypedTensor<T>,
2258 layout: TensorLayout<DynRank>,
2259) -> TypedTensorView<'_, T> {
2260 let buffer = match &tensor.buffer {
2261 Buffer::Host(data) => TensorBufferRef::Host(data),
2262 Buffer::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
2263 };
2264 TypedTensorView {
2265 buffer,
2266 layout,
2267 placement: tensor.placement.clone(),
2268 }
2269}
2270
2271/// Wrap an `f64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2272///
2273/// # Examples
2274///
2275/// ```
2276/// use tenferro_tensor::{Tensor, TypedTensor};
2277///
2278/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
2279/// let tensor: Tensor = typed.into();
2280/// assert_eq!(tensor.shape(), &[2]);
2281/// ```
2282impl From<TypedTensor<f64>> for Tensor {
2283 fn from(t: TypedTensor<f64>) -> Self {
2284 Tensor::F64(t)
2285 }
2286}
2287
2288/// Wrap an `f32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2289///
2290/// # Examples
2291///
2292/// ```
2293/// use tenferro_tensor::{Tensor, TypedTensor};
2294///
2295/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1.0_f32, 2.0]).unwrap();
2296/// let tensor: Tensor = typed.into();
2297/// assert_eq!(tensor.shape(), &[2]);
2298/// ```
2299impl From<TypedTensor<f32>> for Tensor {
2300 fn from(t: TypedTensor<f32>) -> Self {
2301 Tensor::F32(t)
2302 }
2303}
2304
2305/// Wrap an `i64` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2306///
2307/// # Examples
2308///
2309/// ```
2310/// use tenferro_tensor::{DType, Tensor, TypedTensor};
2311///
2312/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i64, 2]).unwrap();
2313/// let tensor: Tensor = typed.into();
2314/// assert_eq!(tensor.dtype(), DType::I64);
2315/// assert_eq!(tensor.shape(), &[2]);
2316/// ```
2317impl From<TypedTensor<i64>> for Tensor {
2318 fn from(t: TypedTensor<i64>) -> Self {
2319 Tensor::I64(t)
2320 }
2321}
2322
2323/// Wrap an `i32` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2324///
2325/// # Examples
2326///
2327/// ```
2328/// use tenferro_tensor::{DType, Tensor, TypedTensor};
2329///
2330/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![1_i32, 2]).unwrap();
2331/// let tensor: Tensor = typed.into();
2332/// assert_eq!(tensor.dtype(), DType::I32);
2333/// assert_eq!(tensor.shape(), &[2]);
2334/// ```
2335impl From<TypedTensor<i32>> for Tensor {
2336 fn from(t: TypedTensor<i32>) -> Self {
2337 Tensor::I32(t)
2338 }
2339}
2340
2341/// Wrap a `bool` [`TypedTensor`] into the corresponding [`Tensor`] variant.
2342///
2343/// # Examples
2344///
2345/// ```
2346/// use tenferro_tensor::{DType, Tensor, TypedTensor};
2347///
2348/// let typed = TypedTensor::from_vec_col_major(vec![2], vec![true, false]).unwrap();
2349/// let tensor: Tensor = typed.into();
2350/// assert_eq!(tensor.dtype(), DType::Bool);
2351/// assert_eq!(tensor.shape(), &[2]);
2352/// ```
2353impl From<TypedTensor<bool>> for Tensor {
2354 fn from(t: TypedTensor<bool>) -> Self {
2355 Tensor::Bool(t)
2356 }
2357}
2358
2359/// Wrap a [`Complex64`] [`TypedTensor`] into the corresponding [`Tensor`]
2360/// variant.
2361///
2362/// # Examples
2363///
2364/// ```
2365/// use num_complex::Complex64;
2366/// use tenferro_tensor::{Tensor, TypedTensor};
2367///
2368/// let typed = TypedTensor::from_vec_col_major(
2369/// vec![1],
2370/// vec![Complex64::new(1.0, 2.0)],
2371/// ).unwrap();
2372/// let tensor: Tensor = typed.into();
2373/// assert_eq!(tensor.shape(), &[1]);
2374/// ```
2375impl From<TypedTensor<Complex<f64>>> for Tensor {
2376 fn from(t: TypedTensor<Complex<f64>>) -> Self {
2377 Tensor::C64(t)
2378 }
2379}
2380
2381/// Wrap a [`Complex32`] [`TypedTensor`] into the corresponding [`Tensor`]
2382/// variant.
2383///
2384/// # Examples
2385///
2386/// ```
2387/// use num_complex::Complex32;
2388/// use tenferro_tensor::{Tensor, TypedTensor};
2389///
2390/// let typed = TypedTensor::from_vec_col_major(
2391/// vec![1],
2392/// vec![Complex32::new(1.0, 2.0)],
2393/// ).unwrap();
2394/// let tensor: Tensor = typed.into();
2395/// assert_eq!(tensor.shape(), &[1]);
2396/// ```
2397impl From<TypedTensor<Complex<f32>>> for Tensor {
2398 fn from(t: TypedTensor<Complex<f32>>) -> Self {
2399 Tensor::C32(t)
2400 }
2401}
2402
2403impl<'a> TensorView<'a> {
2404 /// Create a dynamic `f32` view over compact column-major host data.
2405 ///
2406 /// # Examples
2407 ///
2408 /// ```
2409 /// use tenferro_tensor::{DType, TensorView};
2410 ///
2411 /// let data = [1.0_f32, 2.0];
2412 /// let view = TensorView::f32(&[2], &data)?;
2413 /// assert_eq!(view.dtype(), DType::F32);
2414 /// # Ok::<(), tenferro_tensor::Error>(())
2415 /// ```
2416 pub fn f32(shape: &'a [usize], data: &'a [f32]) -> crate::Result<Self> {
2417 Ok(Self::F32(TypedTensorView::from_col_major(shape, data)?))
2418 }
2419
2420 /// Create a dynamic `f64` view over compact column-major host data.
2421 ///
2422 /// # Examples
2423 ///
2424 /// ```
2425 /// use tenferro_tensor::{DType, TensorView};
2426 ///
2427 /// let data = [1.0_f64, 2.0];
2428 /// let view = TensorView::f64(&[2], &data)?;
2429 /// assert_eq!(view.dtype(), DType::F64);
2430 /// # Ok::<(), tenferro_tensor::Error>(())
2431 /// ```
2432 pub fn f64(shape: &'a [usize], data: &'a [f64]) -> crate::Result<Self> {
2433 Ok(Self::F64(TypedTensorView::from_col_major(shape, data)?))
2434 }
2435
2436 /// Create a dynamic `i64` view over compact column-major host data.
2437 ///
2438 /// # Examples
2439 ///
2440 /// ```
2441 /// use tenferro_tensor::{DType, TensorView};
2442 ///
2443 /// let data = [1_i64, 2];
2444 /// let view = TensorView::i64(&[2], &data)?;
2445 /// assert_eq!(view.dtype(), DType::I64);
2446 /// # Ok::<(), tenferro_tensor::Error>(())
2447 /// ```
2448 pub fn i64(shape: &'a [usize], data: &'a [i64]) -> crate::Result<Self> {
2449 Ok(Self::I64(TypedTensorView::from_col_major(shape, data)?))
2450 }
2451
2452 /// Create a dynamic `i32` view over compact column-major host data.
2453 ///
2454 /// # Examples
2455 ///
2456 /// ```
2457 /// use tenferro_tensor::{DType, TensorView};
2458 ///
2459 /// let data = [1_i32, 2];
2460 /// let view = TensorView::i32(&[2], &data)?;
2461 /// assert_eq!(view.dtype(), DType::I32);
2462 /// # Ok::<(), tenferro_tensor::Error>(())
2463 /// ```
2464 pub fn i32(shape: &'a [usize], data: &'a [i32]) -> crate::Result<Self> {
2465 Ok(Self::I32(TypedTensorView::from_col_major(shape, data)?))
2466 }
2467
2468 /// Create a dynamic `bool` view over compact column-major host data.
2469 ///
2470 /// # Examples
2471 ///
2472 /// ```
2473 /// use tenferro_tensor::{DType, TensorView};
2474 ///
2475 /// let data = [true, false];
2476 /// let view = TensorView::bool(&[2], &data)?;
2477 /// assert_eq!(view.dtype(), DType::Bool);
2478 /// # Ok::<(), tenferro_tensor::Error>(())
2479 /// ```
2480 pub fn bool(shape: &'a [usize], data: &'a [bool]) -> crate::Result<Self> {
2481 Ok(Self::Bool(TypedTensorView::from_col_major(shape, data)?))
2482 }
2483
2484 /// Create a dynamic `Complex32` view over compact column-major host data.
2485 ///
2486 /// # Examples
2487 ///
2488 /// ```
2489 /// use num_complex::Complex32;
2490 /// use tenferro_tensor::{DType, TensorView};
2491 ///
2492 /// let data = [Complex32::new(1.0, 2.0)];
2493 /// let view = TensorView::c32(&[1], &data)?;
2494 /// assert_eq!(view.dtype(), DType::C32);
2495 /// # Ok::<(), tenferro_tensor::Error>(())
2496 /// ```
2497 pub fn c32(shape: &'a [usize], data: &'a [Complex32]) -> crate::Result<Self> {
2498 Ok(Self::C32(TypedTensorView::from_col_major(shape, data)?))
2499 }
2500
2501 /// Create a dynamic `Complex64` view over compact column-major host data.
2502 ///
2503 /// # Examples
2504 ///
2505 /// ```
2506 /// use num_complex::Complex64;
2507 /// use tenferro_tensor::{DType, TensorView};
2508 ///
2509 /// let data = [Complex64::new(1.0, 2.0)];
2510 /// let view = TensorView::c64(&[1], &data)?;
2511 /// assert_eq!(view.dtype(), DType::C64);
2512 /// # Ok::<(), tenferro_tensor::Error>(())
2513 /// ```
2514 pub fn c64(shape: &'a [usize], data: &'a [Complex64]) -> crate::Result<Self> {
2515 Ok(Self::C64(TypedTensorView::from_col_major(shape, data)?))
2516 }
2517
2518 pub fn dtype(&self) -> DType {
2519 match self {
2520 Self::F32(_) => DType::F32,
2521 Self::F64(_) => DType::F64,
2522 Self::I32(_) => DType::I32,
2523 Self::I64(_) => DType::I64,
2524 Self::Bool(_) => DType::Bool,
2525 Self::C32(_) => DType::C32,
2526 Self::C64(_) => DType::C64,
2527 }
2528 }
2529
2530 pub fn shape(&self) -> &[usize] {
2531 match self {
2532 Self::F32(t) => t.shape(),
2533 Self::F64(t) => t.shape(),
2534 Self::I32(t) => t.shape(),
2535 Self::I64(t) => t.shape(),
2536 Self::Bool(t) => t.shape(),
2537 Self::C32(t) => t.shape(),
2538 Self::C64(t) => t.shape(),
2539 }
2540 }
2541
2542 /// Materialize this host view into an owned tensor.
2543 ///
2544 /// This method has no backend context and does not download backend
2545 /// buffers. Use a backend-specific `TensorViewCanonicalization` method or
2546 /// an explicit device transfer before materializing backend views on the
2547 /// host.
2548 ///
2549 /// # Examples
2550 ///
2551 /// ```rust
2552 /// use tenferro_tensor::{DType, TensorView};
2553 ///
2554 /// let data = [1.0_f64, 2.0];
2555 /// let view = TensorView::f64(&[2], &data)?;
2556 /// let tensor = view.to_tensor()?;
2557 /// assert_eq!(tensor.dtype(), DType::F64);
2558 /// # Ok::<(), tenferro_tensor::Error>(())
2559 /// ```
2560 pub fn to_tensor(&self) -> crate::Result<Tensor> {
2561 match self {
2562 Self::F32(t) => {
2563 materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::F32)
2564 }
2565 Self::F64(t) => {
2566 materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::F64)
2567 }
2568 Self::I32(t) => {
2569 materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::I32)
2570 }
2571 Self::I64(t) => {
2572 materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::I64)
2573 }
2574 Self::Bool(t) => {
2575 materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::Bool)
2576 }
2577 Self::C32(t) => {
2578 materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::C32)
2579 }
2580 Self::C64(t) => {
2581 materialize_typed_view_col_major(t, "TensorView::to_tensor").map(Tensor::C64)
2582 }
2583 }
2584 }
2585}
2586
2587impl<'a> TensorRead<'a> {
2588 pub fn from_tensor(tensor: &'a Tensor) -> Self {
2589 Self::Tensor(tensor)
2590 }
2591
2592 pub fn from_view(view: TensorView<'a>) -> Self {
2593 Self::View(view)
2594 }
2595
2596 pub fn dtype(&self) -> DType {
2597 match self {
2598 Self::Tensor(tensor) => tensor.dtype(),
2599 Self::View(view) => view.dtype(),
2600 }
2601 }
2602
2603 pub fn shape(&self) -> &[usize] {
2604 match self {
2605 Self::Tensor(tensor) => tensor.shape(),
2606 Self::View(view) => view.shape(),
2607 }
2608 }
2609
2610 pub fn as_tensor(&self) -> Option<&'a Tensor> {
2611 match self {
2612 Self::Tensor(tensor) => Some(*tensor),
2613 Self::View(_) => None,
2614 }
2615 }
2616
2617 /// Convert an owned tensor reference or host view into an owned tensor.
2618 ///
2619 /// This method clones owned tensor inputs and materializes host views. It
2620 /// has no backend context and does not download backend buffers. Use a
2621 /// backend-specific `TensorViewCanonicalization` method or an explicit
2622 /// device transfer before materializing backend views on the host.
2623 ///
2624 /// # Examples
2625 ///
2626 /// ```rust
2627 /// use tenferro_tensor::{TensorRead, TensorView};
2628 ///
2629 /// let data = [1_i32, 2, 3];
2630 /// let read = TensorRead::from_view(TensorView::i32(&[3], &data)?);
2631 /// let tensor = read.to_tensor()?;
2632 /// assert_eq!(tensor.shape(), &[3]);
2633 /// # Ok::<(), tenferro_tensor::Error>(())
2634 /// ```
2635 pub fn to_tensor(&self) -> crate::Result<Tensor> {
2636 match self {
2637 Self::Tensor(tensor) => Ok((*tensor).clone()),
2638 Self::View(view) => view.to_tensor(),
2639 }
2640 }
2641}
2642
2643/// Column-major strides derived from a shape.
2644///
2645/// # Examples
2646///
2647/// ```rust
2648/// use tenferro_tensor::col_major_strides;
2649///
2650/// assert_eq!(col_major_strides(&[2, 3])?, vec![1, 2]);
2651/// # Ok::<(), tenferro_tensor::Error>(())
2652/// ```
2653pub fn col_major_strides(shape: &[usize]) -> crate::Result<Vec<isize>> {
2654 let mut strides = Vec::with_capacity(shape.len());
2655 let mut stride = 1isize;
2656 for &extent in shape {
2657 strides.push(stride);
2658 let extent = isize::try_from(extent).map_err(|_| crate::Error::InvalidConfig {
2659 op: "col_major_strides",
2660 message: format!("shape extent {extent} does not fit in isize"),
2661 })?;
2662 stride = stride
2663 .checked_mul(extent)
2664 .ok_or_else(|| crate::Error::InvalidConfig {
2665 op: "col_major_strides",
2666 message: format!("column-major stride overflows for shape {shape:?}"),
2667 })?;
2668 }
2669 Ok(strides)
2670}
2671
2672fn try_linear_offset_for_shape(
2673 shape: &[usize],
2674 indices: &[usize],
2675 op: &'static str,
2676) -> crate::Result<usize> {
2677 if indices.len() != shape.len() {
2678 return Err(crate::Error::RankMismatch {
2679 op,
2680 expected: shape.len(),
2681 actual: indices.len(),
2682 });
2683 }
2684 let mut offset = 0usize;
2685 let mut stride = 1usize;
2686 for (axis, (&idx, &extent)) in indices.iter().zip(shape).enumerate() {
2687 if idx >= extent {
2688 return Err(crate::Error::InvalidConfig {
2689 op,
2690 message: format!("index {idx} out of bounds for axis {axis} extent {extent}"),
2691 });
2692 }
2693 offset = offset
2694 .checked_add(
2695 idx.checked_mul(stride)
2696 .ok_or_else(|| crate::Error::InvalidConfig {
2697 op,
2698 message: "linear offset multiply overflows".to_string(),
2699 })?,
2700 )
2701 .ok_or_else(|| crate::Error::InvalidConfig {
2702 op,
2703 message: "linear offset add overflows".to_string(),
2704 })?;
2705 stride = stride
2706 .checked_mul(extent)
2707 .ok_or_else(|| crate::Error::InvalidConfig {
2708 op,
2709 message: "linear offset stride overflows".to_string(),
2710 })?;
2711 }
2712 Ok(offset)
2713}
2714
2715fn try_shape_product(shape: &[usize], op: &'static str) -> crate::Result<usize> {
2716 shape.iter().try_fold(1usize, |acc, &dim| {
2717 acc.checked_mul(dim)
2718 .ok_or_else(|| crate::Error::InvalidConfig {
2719 op,
2720 message: format!("shape product overflows for shape {shape:?}"),
2721 })
2722 })
2723}
2724
2725fn try_checked_shape_len(shape: &[usize], data_len: usize, op: &'static str) -> crate::Result<()> {
2726 let n = try_shape_product(shape, op)?;
2727 if data_len != n {
2728 return Err(crate::Error::InvalidConfig {
2729 op,
2730 message: format!("data length {data_len} does not match shape product {n}"),
2731 });
2732 }
2733 Ok(())
2734}
2735
2736fn try_compact_layout<R: TensorRank>(
2737 shape: impl Into<R::Shape>,
2738 op: &'static str,
2739) -> crate::Result<TensorLayout<R>> {
2740 TensorLayout::compact(shape.into()).map_err(|err| tensor_layout_error(op, err))
2741}
2742
2743fn tensor_layout_error(op: &'static str, err: tenferro_tensor_core::Error) -> crate::Error {
2744 match err {
2745 tenferro_tensor_core::Error::RankMismatch { expected, actual } => {
2746 crate::Error::RankMismatch {
2747 op,
2748 expected,
2749 actual,
2750 }
2751 }
2752 tenferro_tensor_core::Error::AxisOutOfBounds { axis, rank } => {
2753 crate::Error::AxisOutOfBounds { op, axis, rank }
2754 }
2755 tenferro_tensor_core::Error::DuplicateAxis { axis } => crate::Error::DuplicateAxis {
2756 op,
2757 axis,
2758 role: "permutation",
2759 },
2760 tenferro_tensor_core::Error::InvalidPermutationLength { expected, actual } => {
2761 crate::Error::RankMismatch {
2762 op,
2763 expected,
2764 actual,
2765 }
2766 }
2767 other => crate::Error::InvalidConfig {
2768 op,
2769 message: other.to_string(),
2770 },
2771 }
2772}
2773
2774fn checked_view_element_count(shape: &[usize], op: &'static str) -> crate::Result<usize> {
2775 shape.iter().try_fold(1usize, |product, &dim| {
2776 if dim == 0 {
2777 Ok(0)
2778 } else {
2779 product
2780 .checked_mul(dim)
2781 .ok_or_else(|| crate::Error::InvalidConfig {
2782 op,
2783 message: format!("shape product overflows for shape {shape:?}"),
2784 })
2785 }
2786 })
2787}
2788
2789fn checked_view_offset(
2790 shape: &[usize],
2791 strides: &[isize],
2792 base_offset: isize,
2793 indices: &[usize],
2794) -> Option<usize> {
2795 if indices.len() != shape.len() {
2796 return None;
2797 }
2798
2799 let mut offset = base_offset;
2800 for ((&index, &extent), &stride) in indices.iter().zip(shape).zip(strides) {
2801 if index >= extent {
2802 return None;
2803 }
2804 let index = isize::try_from(index).ok()?;
2805 let delta = index.checked_mul(stride)?;
2806 offset = offset.checked_add(delta)?;
2807 }
2808
2809 usize::try_from(offset).ok()
2810}
2811
2812fn for_each_layout_offset_col_major(
2813 shape: &[usize],
2814 strides: &[isize],
2815 base_offset: isize,
2816 op: &'static str,
2817 mut f: impl FnMut(usize) -> crate::Result<()>,
2818) -> crate::Result<()> {
2819 if shape.len() != strides.len() {
2820 return Err(crate::Error::InvalidConfig {
2821 op,
2822 message: format!(
2823 "shape rank {} does not match stride rank {}",
2824 shape.len(),
2825 strides.len()
2826 ),
2827 });
2828 }
2829
2830 if shape.contains(&0) {
2831 return Ok(());
2832 }
2833
2834 let mut offset = base_offset;
2835 if shape.is_empty() {
2836 let offset = usize::try_from(offset).map_err(|_| crate::Error::InvalidConfig {
2837 op,
2838 message: "view offset is negative".to_string(),
2839 })?;
2840 return f(offset);
2841 }
2842
2843 let mut index = vec![0usize; shape.len()];
2844 loop {
2845 let physical = usize::try_from(offset).map_err(|_| crate::Error::InvalidConfig {
2846 op,
2847 message: "view offset is negative".to_string(),
2848 })?;
2849 f(physical)?;
2850
2851 let mut advance_axis = None;
2852 for axis in 0..shape.len() {
2853 let next_index =
2854 index[axis]
2855 .checked_add(1)
2856 .ok_or_else(|| crate::Error::InvalidConfig {
2857 op,
2858 message: "logical index overflows".to_string(),
2859 })?;
2860 if next_index < shape[axis] {
2861 advance_axis = Some((axis, next_index));
2862 break;
2863 }
2864 }
2865
2866 let Some((advance_axis, next_index)) = advance_axis else {
2867 return Ok(());
2868 };
2869
2870 for axis in 0..advance_axis {
2871 let steps = isize::try_from(index[axis]).map_err(|_| crate::Error::InvalidConfig {
2872 op,
2873 message: "logical index does not fit in isize".to_string(),
2874 })?;
2875 let rewind =
2876 strides[axis]
2877 .checked_mul(steps)
2878 .ok_or_else(|| crate::Error::InvalidConfig {
2879 op,
2880 message: "stride rewind overflows".to_string(),
2881 })?;
2882 offset = offset
2883 .checked_sub(rewind)
2884 .ok_or_else(|| crate::Error::InvalidConfig {
2885 op,
2886 message: "view offset rewind overflows".to_string(),
2887 })?;
2888 index[axis] = 0;
2889 }
2890
2891 offset = offset.checked_add(strides[advance_axis]).ok_or_else(|| {
2892 crate::Error::InvalidConfig {
2893 op,
2894 message: "view offset overflows".to_string(),
2895 }
2896 })?;
2897 index[advance_axis] = next_index;
2898 }
2899}
2900
2901fn reachable_layout_span(
2902 shape: &[usize],
2903 strides: &[isize],
2904 offset: isize,
2905) -> crate::Result<Option<(usize, usize)>> {
2906 if shape.contains(&0) {
2907 return Ok(None);
2908 }
2909
2910 let mut min_offset = offset;
2911 let mut max_offset = offset;
2912 for (&extent, &stride) in shape.iter().zip(strides) {
2913 let steps =
2914 isize::try_from(extent.saturating_sub(1)).map_err(|_| crate::Error::InvalidConfig {
2915 op: "TypedTensorViewMut::try_multi_slice_mut",
2916 message: "shape extent does not fit in isize".to_string(),
2917 })?;
2918 let end = stride
2919 .checked_mul(steps)
2920 .ok_or_else(|| crate::Error::InvalidConfig {
2921 op: "TypedTensorViewMut::try_multi_slice_mut",
2922 message: "stride span overflows".to_string(),
2923 })?;
2924 let (axis_min, axis_max) = if end < 0 { (end, 0) } else { (0, end) };
2925 min_offset =
2926 min_offset
2927 .checked_add(axis_min)
2928 .ok_or_else(|| crate::Error::InvalidConfig {
2929 op: "TypedTensorViewMut::try_multi_slice_mut",
2930 message: "minimum reachable offset overflows".to_string(),
2931 })?;
2932 max_offset =
2933 max_offset
2934 .checked_add(axis_max)
2935 .ok_or_else(|| crate::Error::InvalidConfig {
2936 op: "TypedTensorViewMut::try_multi_slice_mut",
2937 message: "maximum reachable offset overflows".to_string(),
2938 })?;
2939 }
2940
2941 let min_offset = usize::try_from(min_offset).map_err(|_| crate::Error::InvalidConfig {
2942 op: "TypedTensorViewMut::try_multi_slice_mut",
2943 message: "minimum reachable offset is negative".to_string(),
2944 })?;
2945 let max_offset = usize::try_from(max_offset).map_err(|_| crate::Error::InvalidConfig {
2946 op: "TypedTensorViewMut::try_multi_slice_mut",
2947 message: "maximum reachable offset is negative".to_string(),
2948 })?;
2949 Ok(Some((min_offset, max_offset)))
2950}
2951
2952fn split_two_mut_ranges<T>(
2953 data: &mut [T],
2954 first: (usize, usize),
2955 second: (usize, usize),
2956) -> Option<(&mut [T], &mut [T])> {
2957 if first.1 < second.0 {
2958 let (_, after_first_start) = data.split_at_mut(first.0);
2959 let (first_slice, after_first) = after_first_start.split_at_mut(first.1 - first.0 + 1);
2960 let (_, after_gap) = after_first.split_at_mut(second.0 - first.1 - 1);
2961 let (second_slice, _) = after_gap.split_at_mut(second.1 - second.0 + 1);
2962 Some((first_slice, second_slice))
2963 } else if second.1 < first.0 {
2964 let (_, after_second_start) = data.split_at_mut(second.0);
2965 let (second_slice, after_second) = after_second_start.split_at_mut(second.1 - second.0 + 1);
2966 let (_, after_gap) = after_second.split_at_mut(first.0 - second.1 - 1);
2967 let (first_slice, _) = after_gap.split_at_mut(first.1 - first.0 + 1);
2968 Some((first_slice, second_slice))
2969 } else {
2970 None
2971 }
2972}
2973
2974fn adjusted_view_offset(offset: isize, span_start: usize) -> Option<isize> {
2975 let span_start = isize::try_from(span_start).ok()?;
2976 offset.checked_sub(span_start)
2977}
2978
2979fn view_mut_from_layout_and_slice<'a, T: 'static, R: TensorRank>(
2980 layout: &TensorLayout<R>,
2981 offset: isize,
2982 data: &'a mut [T],
2983 placement: Placement,
2984) -> crate::Result<TypedTensorViewMut<'a, T, R>> {
2985 let shape = R::shape_from_vec(layout.shape().to_vec().into())
2986 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
2987 let strides = R::strides_from_vec(layout.strides().to_vec().into())
2988 .map_err(|err| tensor_layout_error("TypedTensorViewMut::try_multi_slice_mut", err))?;
2989 TypedTensorViewMut::from_buffer_ref_mut(
2990 shape,
2991 strides,
2992 offset,
2993 TensorBufferRefMut::Host(data),
2994 placement,
2995 "TypedTensorViewMut::try_multi_slice_mut",
2996 )
2997}
2998
2999fn contiguous_layout_slice<'a, T, R: TensorRank>(
3000 layout: &TensorLayout<R>,
3001 data: &'a [T],
3002 op: &'static str,
3003) -> crate::Result<&'a [T]> {
3004 if !layout.is_compact_col_major() {
3005 return Err(crate::Error::InvalidConfig {
3006 op,
3007 message: "view is not contiguous column-major".to_string(),
3008 });
3009 }
3010 let len = checked_view_element_count(layout.shape(), op)?;
3011 let start = usize::try_from(layout.offset()).map_err(|_| crate::Error::InvalidConfig {
3012 op,
3013 message: "view offset is negative".to_string(),
3014 })?;
3015 let end = start
3016 .checked_add(len)
3017 .ok_or_else(|| crate::Error::InvalidConfig {
3018 op,
3019 message: "contiguous view range overflows".to_string(),
3020 })?;
3021 data.get(start..end)
3022 .ok_or_else(|| crate::Error::InvalidConfig {
3023 op,
3024 message: "contiguous view range is outside host buffer".to_string(),
3025 })
3026}
3027
3028fn materialize_view_buffer_col_major<T: Clone>(
3029 shape: &[usize],
3030 strides: &[isize],
3031 offset: isize,
3032 buffer: &TensorBufferRef<'_, T>,
3033 op: &'static str,
3034) -> crate::Result<Vec<T>> {
3035 let source = match buffer {
3036 TensorBufferRef::Host(data) => *data,
3037 TensorBufferRef::Backend(_) => return Err(crate::Error::backend_failure(
3038 op,
3039 "backend buffers cannot be materialized through host memory; download explicitly first",
3040 )),
3041 };
3042
3043 let n_elements = checked_view_element_count(shape, op)?;
3044 let mut out = Vec::with_capacity(n_elements);
3045 for_each_layout_offset_col_major(shape, strides, offset, op, |physical| {
3046 let value = source
3047 .get(physical)
3048 .ok_or_else(|| crate::Error::InvalidConfig {
3049 op,
3050 message: "view offset is outside host buffer".to_string(),
3051 })?;
3052 out.push(value.clone());
3053 Ok(())
3054 })?;
3055 Ok(out)
3056}
3057
3058fn relaxed_col_major_contiguous(
3059 shape: &[usize],
3060 strides: &[isize],
3061 op: &'static str,
3062) -> crate::Result<bool> {
3063 let mut expected = 1isize;
3064 for (&extent, &stride) in shape.iter().zip(strides) {
3065 if extent <= 1 {
3066 continue;
3067 }
3068 if stride != expected {
3069 return Ok(false);
3070 }
3071 let extent = isize::try_from(extent).map_err(|_| crate::Error::InvalidConfig {
3072 op,
3073 message: "shape extent does not fit in isize".to_string(),
3074 })?;
3075 expected = expected
3076 .checked_mul(extent)
3077 .ok_or_else(|| crate::Error::InvalidConfig {
3078 op,
3079 message: "contiguous stride overflows".to_string(),
3080 })?;
3081 }
3082 Ok(true)
3083}
3084
3085fn reshape_layout_dyn<R: TensorRank>(
3086 layout: &TensorLayout<R>,
3087 shape: &[usize],
3088 buffer_len: usize,
3089 op: &'static str,
3090) -> crate::Result<TensorLayout<DynRank>> {
3091 match layout.reshape_view_as::<DynRank>(shape.to_vec().into(), buffer_len) {
3092 Ok(layout) => Ok(layout),
3093 Err(err) => {
3094 if !relaxed_col_major_contiguous(layout.shape(), layout.strides(), op)? {
3095 return Err(tensor_layout_error(op, err));
3096 }
3097 let from = checked_view_element_count(layout.shape(), op)?;
3098 let to = checked_view_element_count(shape, op)?;
3099 if from != to {
3100 return Err(tensor_layout_error(
3101 op,
3102 tenferro_tensor_core::Error::ReshapeElementCountMismatch { from, to },
3103 ));
3104 }
3105 TensorLayout::<DynRank>::compact(shape.to_vec().into())
3106 .and_then(|compact| {
3107 TensorLayout::from_parts(
3108 compact.shape().to_vec().into(),
3109 compact.strides().to_vec().into(),
3110 layout.offset(),
3111 buffer_len,
3112 )
3113 })
3114 .map_err(|err| tensor_layout_error(op, err))
3115 }
3116 }
3117}
3118
3119fn core_slice_specs(
3120 slices: &[StridedSliceSpec],
3121 shape: &[usize],
3122 op: &'static str,
3123) -> crate::Result<Vec<CoreSliceSpec>> {
3124 if slices.len() != shape.len() {
3125 return Err(crate::Error::RankMismatch {
3126 op,
3127 expected: shape.len(),
3128 actual: slices.len(),
3129 });
3130 }
3131
3132 let mut specs = Vec::with_capacity(slices.len());
3133 for (slice, &axis_len) in slices.iter().zip(shape) {
3134 specs.push(core_slice_spec(*slice, axis_len, op)?);
3135 }
3136 Ok(specs)
3137}
3138
3139fn core_slice_spec(
3140 slice: StridedSliceSpec,
3141 axis_len: usize,
3142 op: &'static str,
3143) -> crate::Result<CoreSliceSpec> {
3144 if slice.step() == 0 {
3145 return Err(crate::Error::InvalidConfig {
3146 op,
3147 message: "slice step must not be zero".to_string(),
3148 });
3149 }
3150
3151 let start = normalize_strided_bound(slice.start(), axis_len, op, "slice start")?;
3152 let end = match slice.end() {
3153 Some(end) => normalize_strided_bound(end, axis_len, op, "slice end")?,
3154 None => isize::try_from(axis_len).map_err(|_| crate::Error::InvalidConfig {
3155 op,
3156 message: format!("axis length {axis_len} does not fit in isize"),
3157 })?,
3158 };
3159
3160 if slice.step() > 0 {
3161 return Ok(CoreSliceSpec {
3162 start,
3163 end,
3164 step: slice.step(),
3165 });
3166 }
3167
3168 if start >= end {
3169 return Ok(CoreSliceSpec {
3170 start,
3171 end: start,
3172 step: slice.step(),
3173 });
3174 }
3175
3176 Ok(CoreSliceSpec {
3177 start: end
3178 .checked_sub(1)
3179 .ok_or_else(|| crate::Error::InvalidConfig {
3180 op,
3181 message: "negative-step slice start overflows".to_string(),
3182 })?,
3183 end: start
3184 .checked_sub(1)
3185 .ok_or_else(|| crate::Error::InvalidConfig {
3186 op,
3187 message: "negative-step slice end overflows".to_string(),
3188 })?,
3189 step: slice.step(),
3190 })
3191}
3192
3193fn normalize_strided_bound(
3194 bound: isize,
3195 axis_len: usize,
3196 op: &'static str,
3197 role: &'static str,
3198) -> crate::Result<isize> {
3199 let axis_len = isize::try_from(axis_len).map_err(|_| crate::Error::InvalidConfig {
3200 op,
3201 message: format!("axis length {axis_len} does not fit in isize"),
3202 })?;
3203 let bound = if bound < 0 {
3204 axis_len
3205 .checked_add(bound)
3206 .ok_or_else(|| crate::Error::InvalidConfig {
3207 op,
3208 message: format!("{role} {bound} overflows"),
3209 })?
3210 } else {
3211 bound
3212 };
3213 if !(0..=axis_len).contains(&bound) {
3214 return Err(crate::Error::InvalidConfig {
3215 op,
3216 message: format!("{role} {bound} is outside 0..={axis_len}"),
3217 });
3218 }
3219 Ok(bound)
3220}
3221
3222fn slice_axis_specs(
3223 rank: usize,
3224 axis: usize,
3225 slice: StridedSliceSpec,
3226 op: &'static str,
3227) -> crate::Result<Vec<StridedSliceSpec>> {
3228 if axis >= rank {
3229 return Err(crate::Error::AxisOutOfBounds { op, axis, rank });
3230 }
3231
3232 let mut slices = vec![StridedSliceSpec::all(); rank];
3233 slices[axis] = slice;
3234 Ok(slices)
3235}
3236
3237pub(crate) fn materialize_typed_view_col_major<T: Clone + 'static, R: TensorRank>(
3238 view: &TypedTensorView<'_, T, R>,
3239 op: &'static str,
3240) -> crate::Result<TypedTensor<T>> {
3241 let data = materialize_view_buffer_col_major(
3242 view.shape(),
3243 view.strides(),
3244 view.offset(),
3245 &view.buffer,
3246 op,
3247 )?;
3248 TypedTensor::from_vec_col_major(view.shape().to_vec(), data)
3249}
3250
3251pub(crate) fn default_placement() -> Placement {
3252 Placement {
3253 memory_kind: MemoryKind::UnpinnedHost,
3254 device: None,
3255 }
3256}
3257
3258fn typed_tensor_from_vec_col_major<T, R: TensorRank>(
3259 shape: impl Into<R::Shape>,
3260 data: Vec<T>,
3261 op: &'static str,
3262) -> crate::Result<TypedTensor<T, R>> {
3263 try_typed_tensor_from_vec_col_major(shape, data, op)
3264}
3265
3266fn try_typed_tensor_from_vec_col_major<T, R: TensorRank>(
3267 shape: impl Into<R::Shape>,
3268 data: Vec<T>,
3269 op: &'static str,
3270) -> crate::Result<TypedTensor<T, R>> {
3271 let layout = try_compact_layout(shape, op)?;
3272 try_checked_shape_len(layout.shape(), data.len(), op)?;
3273 Ok(TypedTensor {
3274 buffer: Buffer::Host(data),
3275 layout,
3276 placement: default_placement(),
3277 })
3278}
3279
3280fn typed_tensor_zeros<T: Clone + Zero, R: TensorRank>(
3281 shape: impl Into<R::Shape>,
3282) -> crate::Result<TypedTensor<T, R>> {
3283 try_typed_tensor_zeros(shape)
3284}
3285
3286fn try_typed_tensor_zeros<T: Clone + Zero, R: TensorRank>(
3287 shape: impl Into<R::Shape>,
3288) -> crate::Result<TypedTensor<T, R>> {
3289 let layout = try_compact_layout(shape, "zeros")?;
3290 let n = try_shape_product(layout.shape(), "zeros")?;
3291 Ok(TypedTensor {
3292 buffer: Buffer::Host(vec![T::zero(); n]),
3293 layout,
3294 placement: default_placement(),
3295 })
3296}
3297
3298fn typed_tensor_ones<T: Clone + One + Zero, R: TensorRank>(
3299 shape: impl Into<R::Shape>,
3300) -> crate::Result<TypedTensor<T, R>> {
3301 try_typed_tensor_ones(shape)
3302}
3303
3304fn try_typed_tensor_ones<T: Clone + One + Zero, R: TensorRank>(
3305 shape: impl Into<R::Shape>,
3306) -> crate::Result<TypedTensor<T, R>> {
3307 let layout = try_compact_layout(shape, "ones")?;
3308 let n = try_shape_product(layout.shape(), "ones")?;
3309 Ok(TypedTensor {
3310 buffer: Buffer::Host(vec![T::one(); n]),
3311 layout,
3312 placement: default_placement(),
3313 })
3314}
3315
3316fn typed_tensor_from_buffer_col_major<T: 'static, R: TensorRank>(
3317 shape: impl Into<R::Shape>,
3318 buffer: Buffer<T>,
3319 placement: Placement,
3320) -> crate::Result<TypedTensor<T, R>> {
3321 try_typed_tensor_from_buffer_col_major(shape, buffer, placement)
3322}
3323
3324fn try_typed_tensor_from_buffer_col_major<T: 'static, R: TensorRank>(
3325 shape: impl Into<R::Shape>,
3326 buffer: Buffer<T>,
3327 placement: Placement,
3328) -> crate::Result<TypedTensor<T, R>> {
3329 let layout = try_compact_layout(shape, "from_buffer_col_major")?;
3330 let len = buffer.len();
3331 try_checked_shape_len(layout.shape(), len, "from_buffer_col_major")?;
3332 Ok(TypedTensor {
3333 buffer,
3334 layout,
3335 placement,
3336 })
3337}
3338
3339impl<T: Clone + Zero, R: TensorRank> TypedTensor<T, R> {
3340 /// Allocate a zero-filled tensor.
3341 ///
3342 /// # Examples
3343 ///
3344 /// ```rust
3345 /// use tenferro_tensor::TypedTensor;
3346 ///
3347 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
3348 /// assert_eq!(t.n_elements(), 6);
3349 /// ```
3350 pub fn zeros(shape: impl Into<R::Shape>) -> crate::Result<Self> {
3351 typed_tensor_zeros(shape)
3352 }
3353}
3354
3355impl<T: Clone + One + Zero, R: TensorRank> TypedTensor<T, R> {
3356 /// Allocate a one-filled tensor.
3357 ///
3358 /// # Examples
3359 ///
3360 /// ```rust
3361 /// use tenferro_tensor::TypedTensor;
3362 ///
3363 /// let t = TypedTensor::<f64>::ones(vec![2]).unwrap();
3364 /// assert_eq!(t.host_data().unwrap(), &[1.0, 1.0]);
3365 /// ```
3366 pub fn ones(shape: impl Into<R::Shape>) -> crate::Result<Self> {
3367 typed_tensor_ones(shape)
3368 }
3369}
3370
3371impl<T, R: TensorRank> TypedTensor<T, R> {
3372 /// Create a tensor from an existing buffer and compact column-major layout.
3373 ///
3374 /// This preserves the owned tensor invariant that layout metadata is
3375 /// compact column-major, including for backend-owned buffers.
3376 ///
3377 /// # Examples
3378 ///
3379 /// ```
3380 /// use tenferro_tensor::{Buffer, Placement, TypedTensor};
3381 ///
3382 /// let tensor = TypedTensor::<f64>::from_buffer_col_major(
3383 /// vec![2],
3384 /// Buffer::Host(vec![1.0, 2.0]),
3385 /// Placement {
3386 /// memory_kind: tenferro_tensor::MemoryKind::UnpinnedHost,
3387 /// device: None,
3388 /// },
3389 /// )
3390 /// .unwrap();
3391 /// assert_eq!(tensor.shape(), &[2]);
3392 /// ```
3393 pub fn from_buffer_col_major(
3394 shape: impl Into<R::Shape>,
3395 buffer: Buffer<T>,
3396 placement: Placement,
3397 ) -> crate::Result<Self>
3398 where
3399 T: 'static,
3400 {
3401 typed_tensor_from_buffer_col_major(shape, buffer, placement)
3402 }
3403
3404 /// Convert this tensor into static rank metadata after validating its rank.
3405 ///
3406 /// The buffer and placement are preserved. This method changes only the
3407 /// compile-time rank marker on the owned compact column-major tensor.
3408 ///
3409 /// # Examples
3410 ///
3411 /// ```rust
3412 /// use tenferro_tensor::{Rank, TypedTensor};
3413 ///
3414 /// let tensor = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![1.0; 6]).unwrap();
3415 /// let ranked: TypedTensor<f64, Rank<2>> = tensor.try_into_rank::<2>()?;
3416 /// assert_eq!(ranked.shape(), &[2, 3]);
3417 /// # Ok::<(), tenferro_tensor::Error>(())
3418 /// ```
3419 pub fn try_into_rank<const N: usize>(self) -> crate::Result<TypedTensor<T, Rank<N>>> {
3420 let op = "TypedTensor::try_into_rank";
3421 let shape = <Rank<N> as TensorRank>::shape_from_vec(self.shape().to_vec().into())
3422 .map_err(|err| tensor_layout_error(op, err))?;
3423 let layout =
3424 TensorLayout::<Rank<N>>::compact(shape).map_err(|err| tensor_layout_error(op, err))?;
3425 Ok(TypedTensor {
3426 buffer: self.buffer,
3427 layout,
3428 placement: self.placement,
3429 })
3430 }
3431
3432 /// Number of elements in the tensor.
3433 ///
3434 /// # Examples
3435 ///
3436 /// ```rust
3437 /// use tenferro_tensor::TypedTensor;
3438 ///
3439 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
3440 /// assert_eq!(t.n_elements(), 6);
3441 /// ```
3442 pub fn n_elements(&self) -> usize {
3443 // Invariant: owned tensor constructors validate compact shape length against buffer length.
3444 match try_shape_product(self.shape(), "TypedTensor::n_elements") {
3445 Ok(n) => n,
3446 Err(err) => {
3447 unreachable!("TypedTensor compact shape is validated at construction: {err}")
3448 }
3449 }
3450 }
3451
3452 /// Tensor shape.
3453 ///
3454 /// # Examples
3455 ///
3456 /// ```
3457 /// use tenferro_tensor::TypedTensor;
3458 ///
3459 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3460 /// assert_eq!(t.shape(), &[2]);
3461 /// ```
3462 pub fn shape(&self) -> &[usize] {
3463 self.layout.shape()
3464 }
3465
3466 /// Tensor rank.
3467 ///
3468 /// # Examples
3469 ///
3470 /// ```
3471 /// use tenferro_tensor::TypedTensor;
3472 ///
3473 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
3474 /// assert_eq!(t.rank(), 2);
3475 /// ```
3476 pub fn rank(&self) -> usize {
3477 self.shape().len()
3478 }
3479
3480 /// Tensor layout metadata.
3481 ///
3482 /// Owned typed tensors are always compact column-major layouts.
3483 ///
3484 /// # Examples
3485 ///
3486 /// ```
3487 /// use tenferro_tensor::TypedTensor;
3488 ///
3489 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 3], vec![0.0; 6]).unwrap();
3490 /// assert_eq!(t.layout().strides(), &[1, 2]);
3491 /// ```
3492 pub fn layout(&self) -> &TensorLayout<R> {
3493 &self.layout
3494 }
3495
3496 /// Return the storage backing this tensor.
3497 ///
3498 /// This is an explicit storage-inspection API for backend glue and tests.
3499 /// Host value inspection should prefer [`TypedTensor::host_data`] when the
3500 /// caller requires host storage.
3501 ///
3502 /// # Examples
3503 ///
3504 /// ```
3505 /// use tenferro_tensor::{Buffer, TypedTensor};
3506 ///
3507 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3508 /// assert!(matches!(t.buffer(), Buffer::Host(_)));
3509 /// ```
3510 pub fn buffer(&self) -> &Buffer<T> {
3511 &self.buffer
3512 }
3513
3514 /// Return placement metadata for this tensor.
3515 ///
3516 /// # Examples
3517 ///
3518 /// ```
3519 /// use tenferro_tensor::{MemoryKind, TypedTensor};
3520 ///
3521 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
3522 /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
3523 /// ```
3524 pub fn placement(&self) -> &Placement {
3525 &self.placement
3526 }
3527
3528 /// Replace placement metadata without changing the storage buffer.
3529 ///
3530 /// # Examples
3531 ///
3532 /// ```
3533 /// use tenferro_tensor::{MemoryKind, Placement, TypedTensor};
3534 ///
3535 /// let mut t = TypedTensor::<f64>::from_vec_col_major(vec![1], vec![1.0]).unwrap();
3536 /// t.set_placement(Placement {
3537 /// memory_kind: MemoryKind::PinnedHost,
3538 /// device: None,
3539 /// });
3540 /// assert_eq!(t.placement().memory_kind, MemoryKind::PinnedHost);
3541 /// ```
3542 pub fn set_placement(&mut self, placement: Placement) {
3543 self.placement = placement;
3544 }
3545
3546 /// Borrow this tensor as a typed view preserving rank and layout metadata.
3547 ///
3548 /// # Examples
3549 ///
3550 /// ```rust
3551 /// use tenferro_tensor::{Rank, TypedTensor};
3552 ///
3553 /// let tensor = TypedTensor::<f64, Rank<2>>::from_vec_col_major([2, 2], vec![1.0; 4]).unwrap();
3554 /// let view = tensor.as_view();
3555 /// assert_eq!(view.strides(), &[1, 2]);
3556 /// ```
3557 pub fn as_view(&self) -> TypedTensorView<'_, T, R>
3558 where
3559 T: 'static,
3560 {
3561 let buffer = match &self.buffer {
3562 Buffer::Host(data) => TensorBufferRef::Host(data),
3563 Buffer::Backend(buffer) => TensorBufferRef::Backend(Arc::clone(buffer)),
3564 };
3565 TypedTensorView {
3566 buffer,
3567 layout: self.layout.clone(),
3568 placement: self.placement.clone(),
3569 }
3570 }
3571
3572 /// Mutably borrow this tensor as a typed view preserving rank and layout metadata.
3573 ///
3574 /// # Examples
3575 ///
3576 /// ```rust
3577 /// use tenferro_tensor::TypedTensor;
3578 ///
3579 /// let mut tensor = TypedTensor::<i32>::from_vec_col_major(vec![1], vec![1]).unwrap();
3580 /// *tensor.as_view_mut().get_mut(&[0]).unwrap() = 2;
3581 /// assert_eq!(tensor.as_slice().unwrap(), &[2]);
3582 /// ```
3583 pub fn as_view_mut(&mut self) -> TypedTensorViewMut<'_, T, R>
3584 where
3585 T: 'static,
3586 {
3587 let layout = self.layout.clone();
3588 let placement = self.placement.clone();
3589 let buffer = match &mut self.buffer {
3590 Buffer::Host(data) => TensorBufferRefMut::Host(data),
3591 Buffer::Backend(buffer) => TensorBufferRefMut::Backend(Arc::clone(buffer)),
3592 };
3593 TypedTensorViewMut {
3594 buffer,
3595 layout,
3596 placement,
3597 }
3598 }
3599
3600 /// Consume this tensor and return its layout metadata.
3601 ///
3602 /// # Examples
3603 ///
3604 /// ```
3605 /// use tenferro_tensor::TypedTensor;
3606 ///
3607 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3608 /// assert!(t.into_layout().is_compact_col_major());
3609 /// ```
3610 pub fn into_layout(self) -> TensorLayout<R> {
3611 self.layout
3612 }
3613
3614 /// Consume this tensor and return its storage, layout, and placement.
3615 ///
3616 /// # Examples
3617 ///
3618 /// ```
3619 /// use tenferro_tensor::{Buffer, TypedTensor};
3620 ///
3621 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3622 /// let (buffer, layout, placement) = t.into_parts();
3623 /// assert!(matches!(buffer, Buffer::Host(_)));
3624 /// assert_eq!(layout.shape(), &[2]);
3625 /// assert!(placement.device.is_none());
3626 /// ```
3627 pub fn into_parts(self) -> (Buffer<T>, TensorLayout<R>, Placement) {
3628 (self.buffer, self.layout, self.placement)
3629 }
3630}
3631
3632impl<T: Clone, R: TensorRank> TypedTensor<T, R> {
3633 /// Create a tensor from a column-major buffer.
3634 ///
3635 /// # Examples
3636 ///
3637 /// ```
3638 /// use tenferro_tensor::TypedTensor;
3639 ///
3640 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
3641 /// assert_eq!(t.get(&[1, 0])?, &2.0);
3642 /// # Ok::<(), tenferro_tensor::Error>(())
3643 /// ```
3644 pub fn from_vec_col_major(shape: impl Into<R::Shape>, data: Vec<T>) -> crate::Result<Self> {
3645 typed_tensor_from_vec_col_major(shape, data, "from_vec_col_major")
3646 }
3647
3648 /// Consume this tensor and return its owned column-major host buffer.
3649 ///
3650 /// # Examples
3651 ///
3652 /// ```
3653 /// use tenferro_tensor::TypedTensor;
3654 ///
3655 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3656 /// let (shape, data) = t.into_vec_col_major().unwrap();
3657 /// assert_eq!(shape, vec![2]);
3658 /// assert_eq!(data, vec![1.0, 2.0]);
3659 /// ```
3660 pub fn into_vec_col_major(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
3661 let shape = self.shape().to_vec();
3662 match self.buffer {
3663 Buffer::Host(data) => Ok((shape, data)),
3664 Buffer::Backend(_) => Err(crate::Error::backend_failure(
3665 "into_vec_col_major",
3666 "backend buffers cannot be exported as host Vec",
3667 )),
3668 }
3669 }
3670
3671 /// Borrow the host buffer.
3672 ///
3673 /// # Examples
3674 ///
3675 /// ```rust
3676 /// use tenferro_tensor::TypedTensor;
3677 ///
3678 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3679 /// assert_eq!(t.host_data()?, &[1.0, 2.0]);
3680 /// # Ok::<(), tenferro_tensor::Error>(())
3681 /// ```
3682 pub fn host_data(&self) -> crate::Result<&[T]> {
3683 match &self.buffer {
3684 Buffer::Host(v) => Ok(v),
3685 Buffer::Backend(_) => Err(crate::Error::backend_failure(
3686 "TypedTensor::host_data",
3687 "backend buffers cannot be inspected as host slices; download explicitly first",
3688 )),
3689 }
3690 }
3691
3692 /// View the tensor data as a flat slice.
3693 ///
3694 /// This is an alias for `host_data()` for API consistency with
3695 /// `Tensor::as_slice`.
3696 ///
3697 /// # Examples
3698 ///
3699 /// ```
3700 /// use tenferro_tensor::TypedTensor;
3701 ///
3702 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3703 /// assert_eq!(t.as_slice()?, &[1.0, 2.0]);
3704 /// # Ok::<(), tenferro_tensor::Error>(())
3705 /// ```
3706 pub fn as_slice(&self) -> crate::Result<&[T]> {
3707 self.host_data()
3708 }
3709
3710 /// Mutably borrow the host buffer.
3711 ///
3712 /// # Examples
3713 ///
3714 /// ```rust
3715 /// use tenferro_tensor::TypedTensor;
3716 ///
3717 /// let mut t = TypedTensor::<f64>::zeros(vec![2]).unwrap();
3718 /// t.host_data_mut()?[0] = 3.0;
3719 /// assert_eq!(t.host_data()?, &[3.0, 0.0]);
3720 /// # Ok::<(), tenferro_tensor::Error>(())
3721 /// ```
3722 pub fn host_data_mut(&mut self) -> crate::Result<&mut [T]> {
3723 match &mut self.buffer {
3724 Buffer::Host(v) => Ok(v),
3725 Buffer::Backend(_) => Err(crate::Error::backend_failure(
3726 "TypedTensor::host_data_mut",
3727 "backend buffers cannot be mutated as host slices; download explicitly first",
3728 )),
3729 }
3730 }
3731
3732 /// Compute the linear physical-buffer offset for a logical index.
3733 ///
3734 /// # Examples
3735 ///
3736 /// ```rust
3737 /// use tenferro_tensor::TypedTensor;
3738 ///
3739 /// let t = TypedTensor::<f64>::zeros(vec![2, 3]).unwrap();
3740 /// assert_eq!(t.linear_offset(&[1, 2])?, 5);
3741 /// # Ok::<(), tenferro_tensor::Error>(())
3742 /// ```
3743 pub fn linear_offset(&self, indices: &[usize]) -> crate::Result<usize> {
3744 try_linear_offset_for_shape(self.shape(), indices, "TypedTensor::linear_offset")
3745 }
3746
3747 /// Borrow a single element by multi-index.
3748 ///
3749 /// # Examples
3750 ///
3751 /// ```rust
3752 /// use tenferro_tensor::TypedTensor;
3753 ///
3754 /// let t = TypedTensor::<f64>::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap();
3755 /// assert_eq!(t.get(&[1])?, &2.0);
3756 /// # Ok::<(), tenferro_tensor::Error>(())
3757 /// ```
3758 pub fn get(&self, indices: &[usize]) -> crate::Result<&T> {
3759 let off = self.linear_offset(indices)?;
3760 self.host_data()?
3761 .get(off)
3762 .ok_or_else(|| crate::Error::InvalidConfig {
3763 op: "TypedTensor::get",
3764 message: format!("linear offset {off} is outside host buffer"),
3765 })
3766 }
3767
3768 /// Mutably borrow a single element by multi-index.
3769 ///
3770 /// # Examples
3771 ///
3772 /// ```rust
3773 /// use tenferro_tensor::TypedTensor;
3774 ///
3775 /// let mut t = TypedTensor::<f64>::zeros(vec![1]).unwrap();
3776 /// *t.get_mut(&[0])? = 7.0;
3777 /// assert_eq!(t.host_data()?, &[7.0]);
3778 /// # Ok::<(), tenferro_tensor::Error>(())
3779 /// ```
3780 pub fn get_mut(&mut self, indices: &[usize]) -> crate::Result<&mut T> {
3781 let off = self.linear_offset(indices)?;
3782 self.host_data_mut()?
3783 .get_mut(off)
3784 .ok_or_else(|| crate::Error::InvalidConfig {
3785 op: "TypedTensor::get_mut",
3786 message: format!("linear offset {off} is outside host buffer"),
3787 })
3788 }
3789}
3790
3791impl Tensor {
3792 /// Create a tensor from a shape and column-major flat data.
3793 ///
3794 /// This is the `Tensor`-level equivalent of
3795 /// `TypedTensor::<T>::from_vec_col_major`.
3796 ///
3797 /// # Examples
3798 ///
3799 /// ```
3800 /// use tenferro_tensor::Tensor;
3801 ///
3802 /// let t = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap();
3803 /// assert_eq!(t.shape(), &[2, 2]);
3804 /// assert_eq!(t.as_slice::<f64>().unwrap(), &[1.0, 3.0, 2.0, 4.0]);
3805 /// ```
3806 pub fn from_vec_col_major<T: TensorScalar>(
3807 shape: Vec<usize>,
3808 data: Vec<T>,
3809 ) -> crate::Result<Self> {
3810 T::into_tensor(shape, data)
3811 }
3812
3813 /// Tensor shape.
3814 ///
3815 /// # Examples
3816 ///
3817 /// ```rust
3818 /// use tenferro_tensor::{Tensor, TypedTensor};
3819 ///
3820 /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap());
3821 /// assert_eq!(t.shape(), &[2]);
3822 /// ```
3823 pub fn shape(&self) -> &[usize] {
3824 match self {
3825 Tensor::F32(t) => t.shape(),
3826 Tensor::F64(t) => t.shape(),
3827 Tensor::I32(t) => t.shape(),
3828 Tensor::I64(t) => t.shape(),
3829 Tensor::Bool(t) => t.shape(),
3830 Tensor::C32(t) => t.shape(),
3831 Tensor::C64(t) => t.shape(),
3832 }
3833 }
3834
3835 /// Tensor dtype tag.
3836 ///
3837 /// # Examples
3838 ///
3839 /// ```rust
3840 /// use tenferro_tensor::{DType, Tensor, TypedTensor};
3841 ///
3842 /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![], vec![1.0]).unwrap());
3843 /// assert_eq!(t.dtype(), DType::F64);
3844 /// ```
3845 pub fn dtype(&self) -> DType {
3846 match self {
3847 Tensor::F32(_) => DType::F32,
3848 Tensor::F64(_) => DType::F64,
3849 Tensor::I32(_) => DType::I32,
3850 Tensor::I64(_) => DType::I64,
3851 Tensor::Bool(_) => DType::Bool,
3852 Tensor::C32(_) => DType::C32,
3853 Tensor::C64(_) => DType::C64,
3854 }
3855 }
3856
3857 /// Return placement metadata for this dtype-erased tensor.
3858 ///
3859 /// # Examples
3860 ///
3861 /// ```rust
3862 /// use tenferro_tensor::{MemoryKind, Tensor};
3863 ///
3864 /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
3865 /// assert_eq!(t.placement().memory_kind, MemoryKind::UnpinnedHost);
3866 /// ```
3867 pub fn placement(&self) -> &Placement {
3868 match self {
3869 Tensor::F32(t) => t.placement(),
3870 Tensor::F64(t) => t.placement(),
3871 Tensor::I32(t) => t.placement(),
3872 Tensor::I64(t) => t.placement(),
3873 Tensor::Bool(t) => t.placement(),
3874 Tensor::C32(t) => t.placement(),
3875 Tensor::C64(t) => t.placement(),
3876 }
3877 }
3878
3879 /// Return whether this tensor is backed by backend-native storage.
3880 ///
3881 /// # Examples
3882 ///
3883 /// ```rust
3884 /// use tenferro_tensor::Tensor;
3885 ///
3886 /// let t = Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap();
3887 /// assert!(!t.is_backend_buffer());
3888 /// ```
3889 pub fn is_backend_buffer(&self) -> bool {
3890 match self {
3891 Tensor::F32(t) => t.buffer().is_backend(),
3892 Tensor::F64(t) => t.buffer().is_backend(),
3893 Tensor::I32(t) => t.buffer().is_backend(),
3894 Tensor::I64(t) => t.buffer().is_backend(),
3895 Tensor::Bool(t) => t.buffer().is_backend(),
3896 Tensor::C32(t) => t.buffer().is_backend(),
3897 Tensor::C64(t) => t.buffer().is_backend(),
3898 }
3899 }
3900
3901 /// Try to borrow the host data as a typed slice.
3902 ///
3903 /// Returns an error if the tensor dtype does not match `T`.
3904 ///
3905 /// # Examples
3906 ///
3907 /// ```
3908 /// use tenferro_tensor::{Tensor, TypedTensor};
3909 ///
3910 /// let t = Tensor::F64(TypedTensor::from_vec_col_major(vec![3], vec![1.0, 2.0, 3.0]).unwrap());
3911 /// assert_eq!(t.as_slice::<f64>().unwrap(), [1.0, 2.0, 3.0].as_slice());
3912 /// assert!(t.as_slice::<f32>().is_err());
3913 /// ```
3914 pub fn as_slice<T: TensorScalar>(&self) -> crate::Result<&[T]> {
3915 T::as_slice(self)
3916 }
3917
3918 /// Consume this tensor and return its owned column-major buffer when the
3919 /// dtype matches.
3920 ///
3921 /// # Examples
3922 ///
3923 /// ```
3924 /// use tenferro_tensor::Tensor;
3925 ///
3926 /// let t = Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
3927 /// assert_eq!(t.into_vec_col_major::<f64>().unwrap().1, vec![2.0]);
3928 /// ```
3929 pub fn into_vec_col_major<T: TensorScalar>(self) -> crate::Result<(Vec<usize>, Vec<T>)> {
3930 let typed = T::into_typed(self)?;
3931 typed.into_vec_col_major()
3932 }
3933}
3934
3935// Kept for crate-local layout tests while tensor indexing helpers remain split
3936// across tensor and CPU crates.
3937#[allow(dead_code)]
3938pub(crate) fn flat_to_multi(mut flat: usize, shape: &[usize], out: &mut [usize]) {
3939 for i in 0..shape.len() {
3940 out[i] = flat % shape[i];
3941 flat /= shape[i];
3942 }
3943}