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