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