Skip to main content

tenferro_tensor/
erased_host.rs

1//! Dtype-erased external host value.
2//!
3//! A downstream crate that defines its own scalar stores its data in the
4//! canonical host tensor, `TypedTensor<T, DynRank, Host>`, and hands it to
5//! [`ErasedHostTensor`]. The payload keeps its own element type and is
6//! recovered by that type, so no bytes are reinterpreted. The presented view
7//! is a canonical [`TensorLayout`] over that payload.
8
9use core::any::{Any, TypeId};
10use std::sync::Arc;
11
12use crate::{Host, TypedTensor};
13use tenferro_tensor_core::{DynRank, Scalar, TensorLayout};
14
15/// The canonical host tensor an erased value carries.
16type Payload<T> = TypedTensor<T, DynRank, Host>;
17
18const OP: &str = "ErasedHostTensor";
19
20/// Number of logical elements named by `layout`.
21fn element_count(layout: &TensorLayout) -> usize {
22    layout.shape().iter().product()
23}
24
25/// Whether `layout` is the dense zero-offset column-major layout of its shape.
26///
27/// The check walks the extents in place: this runs on every erased access that
28/// wants the whole payload, so it must not allocate.
29fn is_dense(layout: &TensorLayout) -> bool {
30    if layout.offset() != 0 {
31        return false;
32    }
33    let mut running: isize = 1;
34    for (extent, stride) in layout.shape().iter().zip(layout.strides()) {
35        if *stride != running {
36            return false;
37        }
38        running = running.saturating_mul(*extent as isize);
39    }
40    true
41}
42
43/// Physical element offset of the logical `index`, or `None` out of range.
44fn linear_index(layout: &TensorLayout, index: &[usize]) -> Option<isize> {
45    if index.len() != layout.shape().len() {
46        return None;
47    }
48    let mut linear = layout.offset();
49    for ((position, extent), stride) in index.iter().zip(layout.shape()).zip(layout.strides()) {
50        if position >= extent {
51            return None;
52        }
53        linear += stride * *position as isize;
54    }
55    Some(linear)
56}
57
58/// Object-safe operations the erased value needs from one payload.
59///
60/// The payload is only ever one concrete `TypedTensor<T, DynRank, Host>`, so a payload answers
61/// with its own element type and never by reinterpreting bytes.
62trait ErasedPayload: Send + Sync {
63    /// Copy the payload while keeping its concrete element type.
64    fn clone_payload(&self) -> Box<dyn ErasedPayload>;
65    fn as_any(&self) -> &dyn Any;
66    fn as_any_mut(&mut self) -> &mut dyn Any;
67    /// Copy the elements named by `layout` into a new dense payload.
68    fn gather(&self, layout: &TensorLayout) -> crate::Result<Box<dyn ErasedPayload>>;
69}
70
71impl<T: Scalar> ErasedPayload for Payload<T> {
72    fn clone_payload(&self) -> Box<dyn ErasedPayload> {
73        Box::new(self.clone())
74    }
75
76    fn as_any(&self) -> &dyn Any {
77        self
78    }
79
80    fn as_any_mut(&mut self) -> &mut dyn Any {
81        self
82    }
83
84    fn gather(&self, layout: &TensorLayout) -> crate::Result<Box<dyn ErasedPayload>> {
85        let source = self.as_slice();
86        let expected = element_count(layout);
87        let mut gathered = Vec::with_capacity(expected);
88        let mut index = vec![0usize; layout.shape().len()];
89        for _ in 0..expected {
90            let linear = linear_index(layout, &index)
91                .and_then(|linear| usize::try_from(linear).ok())
92                .filter(|linear| *linear < source.len())
93                .ok_or(crate::Error::validation(
94                    OP,
95                    tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch {
96                        expected,
97                        actual: source.len(),
98                    },
99                ))?;
100            gathered.push(source[linear]);
101            for (position, current) in index.iter_mut().enumerate() {
102                *current += 1;
103                if *current < layout.shape()[position] {
104                    break;
105                }
106                *current = 0;
107            }
108        }
109        Ok(Box::new(Payload::from_host_vec_col_major(
110            layout.shape().to_vec(),
111            gathered,
112        )?))
113    }
114}
115
116/// A host tensor whose element type is recovered at run time.
117///
118/// The payload keeps its own concrete type and is recovered by identity, so no
119/// byte reinterpretation happens and a caller-owned payload is duplicated through
120/// its own entry point rather than by copying bytes.
121///
122/// The value also carries the layout of the view it presents. [`Clone`] shares
123/// that payload, so a metadata-only permutation is cheap; [`duplicate`] copies it,
124/// so a caller that needs its own storage asks for one explicitly. A typed read
125/// applies the layout, and the dense accessors refuse a strided view instead of
126/// presenting the payload as if it were the view.
127///
128/// [`duplicate`]: ErasedHostTensor::duplicate
129///
130/// # Examples
131///
132/// ```rust
133/// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
134///
135/// let value = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0_f64, 2.0])?);
136/// assert_eq!(value.downcast_ref::<f64>().unwrap().as_slice(), &[1.0, 2.0]);
137/// assert_eq!(value.clone().element_count(), 2);
138/// # Ok::<(), tenferro_tensor::Error>(())
139/// ```
140pub struct ErasedHostTensor {
141    payload: Arc<dyn ErasedPayload>,
142    type_id: TypeId,
143    element: TypeId,
144    layout: TensorLayout,
145    payload_elements: usize,
146    /// Whether `layout` is the dense column-major layout of its own shape.
147    ///
148    /// The contiguity check runs on every access that wants the whole payload, so it is
149    /// computed when the layout is set rather than walked per access.
150    dense: bool,
151}
152
153impl Clone for ErasedHostTensor {
154    /// Share the payload and copy only the layout metadata.
155    ///
156    /// This is a metadata-only operation, so it is what a permutation and a
157    /// metadata view use. Use [`ErasedHostTensor::duplicate`] for an independent
158    /// copy of the storage.
159    ///
160    /// # Examples
161    ///
162    /// ```rust
163    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
164    ///
165    /// let value = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7_i64])?);
166    /// let view = value.clone();
167    /// assert!(view.shares_payload_with(&value));
168    /// assert!(!view.duplicate().shares_payload_with(&value));
169    /// # Ok::<(), tenferro_tensor::Error>(())
170    /// ```
171    fn clone(&self) -> Self {
172        Self {
173            payload: Arc::clone(&self.payload),
174            type_id: self.type_id,
175            element: self.element,
176            layout: self.layout.clone(),
177            payload_elements: self.payload_elements,
178            dense: self.dense,
179        }
180    }
181}
182
183impl core::fmt::Debug for ErasedHostTensor {
184    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
185        f.debug_struct("ErasedHostTensor")
186            .field("type_id", &self.type_id)
187            .field("shape", &self.layout.shape())
188            .field("strides", &self.layout.strides())
189            .field("offset", &self.layout.offset())
190            .finish()
191    }
192}
193
194impl ErasedHostTensor {
195    /// Erase a host tensor's element type.
196    ///
197    /// The result presents the tensor's dense column-major layout.
198    ///
199    /// # Examples
200    ///
201    /// ```rust
202    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
203    ///
204    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![7_i32])?);
205    /// assert!(erased.is::<i32>());
206    /// # Ok::<(), tenferro_tensor::Error>(())
207    /// ```
208    pub fn new<T: Scalar>(value: TypedTensor<T, DynRank, Host>) -> Self {
209        // An owned tensor is always compact, so the erased value starts dense.
210        let layout = value.layout();
211        let payload_elements = element_count(&layout);
212        Self {
213            payload: Arc::new(value),
214            type_id: TypeId::of::<Payload<T>>(),
215            element: TypeId::of::<T>(),
216            dense: true,
217            layout,
218            payload_elements,
219        }
220    }
221
222    /// Identity of the stored element type.
223    ///
224    /// # Examples
225    ///
226    /// ```rust
227    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
228    ///
229    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f32])?);
230    /// assert_eq!(erased.type_id(), core::any::TypeId::of::<TypedTensor<f32, DynRank, Host>>());
231    /// # Ok::<(), tenferro_tensor::Error>(())
232    /// ```
233    #[must_use]
234    pub fn type_id(&self) -> TypeId {
235        self.type_id
236    }
237
238    /// Identity of the stored element type, without the tensor wrapper.
239    ///
240    /// This is what a runtime tag reports for an externally defined scalar.
241    ///
242    /// # Examples
243    ///
244    /// ```rust
245    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
246    ///
247    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?);
248    /// assert_eq!(erased.element_type_id(), core::any::TypeId::of::<f64>());
249    /// # Ok::<(), tenferro_tensor::Error>(())
250    /// ```
251    #[must_use]
252    pub fn element_type_id(&self) -> TypeId {
253        self.element
254    }
255
256    /// Shape of the presented view.
257    ///
258    /// # Examples
259    ///
260    /// ```rust
261    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
262    ///
263    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?);
264    /// assert_eq!(erased.shape(), &[2, 3]);
265    /// # Ok::<(), tenferro_tensor::Error>(())
266    /// ```
267    #[must_use]
268    pub fn shape(&self) -> &[usize] {
269        self.layout.shape()
270    }
271
272    /// Element strides of the presented view.
273    ///
274    /// # Examples
275    ///
276    /// ```rust
277    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
278    ///
279    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?);
280    /// assert_eq!(erased.strides(), &[1, 2]);
281    /// # Ok::<(), tenferro_tensor::Error>(())
282    /// ```
283    #[must_use]
284    pub fn strides(&self) -> &[isize] {
285        self.layout.strides()
286    }
287
288    /// Element offset of the presented view.
289    ///
290    /// # Examples
291    ///
292    /// ```rust
293    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
294    ///
295    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2], vec![0.0_f64; 2])?);
296    /// assert_eq!(erased.offset(), 0);
297    /// # Ok::<(), tenferro_tensor::Error>(())
298    /// ```
299    #[must_use]
300    pub fn offset(&self) -> isize {
301        self.layout.offset()
302    }
303
304    /// Whether the presented view is the dense column-major layout of its shape.
305    ///
306    /// # Examples
307    ///
308    /// ```rust
309    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
310    ///
311    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?);
312    /// assert!(erased.is_contiguous());
313    /// # Ok::<(), tenferro_tensor::Error>(())
314    /// ```
315    #[must_use]
316    pub fn is_contiguous(&self) -> bool {
317        self.dense
318    }
319
320    /// Number of elements in the presented view.
321    ///
322    /// # Examples
323    ///
324    /// ```rust
325    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
326    ///
327    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?);
328    /// assert_eq!(erased.element_count(), 6);
329    /// # Ok::<(), tenferro_tensor::Error>(())
330    /// ```
331    #[must_use]
332    pub fn element_count(&self) -> usize {
333        element_count(&self.layout)
334    }
335
336    /// Whether two erased values present the same stored payload.
337    ///
338    /// A metadata-only view answers `true`, and an independent copy answers
339    /// `false`.
340    ///
341    /// # Examples
342    ///
343    /// ```rust
344    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
345    ///
346    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?);
347    /// assert!(erased.shares_payload_with(&erased.clone()));
348    /// assert!(!erased.shares_payload_with(&erased.duplicate()));
349    /// # Ok::<(), tenferro_tensor::Error>(())
350    /// ```
351    #[must_use]
352    pub fn shares_payload_with(&self, other: &Self) -> bool {
353        Arc::ptr_eq(&self.payload, &other.payload)
354    }
355
356    /// Copy the payload into an independent value with the same view.
357    ///
358    /// The copy is the caller's own storage, so later mutation of either value
359    /// leaves the other unchanged.
360    ///
361    /// # Examples
362    ///
363    /// ```rust
364    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
365    ///
366    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?);
367    /// let mut copy = erased.duplicate();
368    /// copy.downcast_mut::<f64>().unwrap().host_data_mut()[0] = 5.0;
369    /// assert_eq!(erased.as_dense::<f64>().unwrap().0, &[1.0]);
370    /// # Ok::<(), tenferro_tensor::Error>(())
371    /// ```
372    #[must_use]
373    pub fn duplicate(&self) -> Self {
374        Self {
375            payload: Arc::from(self.payload.clone_payload()),
376            type_id: self.type_id,
377            element: self.element,
378            layout: self.layout.clone(),
379            payload_elements: self.payload_elements,
380            dense: self.dense,
381        }
382    }
383
384    /// Present the same elements under a permuted axis order.
385    ///
386    /// This is metadata only: the payload is shared, no element is moved, and the
387    /// result's shape and strides follow `axes`.
388    ///
389    /// # Errors
390    ///
391    /// Returns a validation error carrying
392    /// [`tenferro_tensor_core::ValidationError::InvalidPermutationLength`] when `axes` does not have
393    /// one entry per axis, [`tenferro_tensor_core::ValidationError::AxisOutOfBounds`] when an axis is
394    /// out of range, or [`tenferro_tensor_core::ValidationError::DuplicateAxis`] when an axis repeats.
395    ///
396    /// # Examples
397    ///
398    /// ```rust
399    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
400    ///
401    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2, 3], vec![0.0_f64; 6])?);
402    /// let permuted = erased.permuted(&[1, 0])?;
403    /// assert_eq!(permuted.shape(), &[3, 2]);
404    /// assert_eq!(permuted.strides(), &[2, 1]);
405    /// assert!(permuted.shares_payload_with(&erased));
406    /// # Ok::<(), tenferro_tensor::Error>(())
407    /// ```
408    pub fn permuted(&self, axes: &[usize]) -> crate::Result<Self> {
409        let layout = self
410            .layout
411            .transpose_view(axes)
412            .map_err(|source| crate::Error::validation("ErasedHostTensor::permuted", source))?;
413        let dense = is_dense(&layout);
414        Ok(Self {
415            layout,
416            dense,
417            ..self.clone()
418        })
419    }
420
421    /// Materialize the presented view into a dense column-major payload.
422    ///
423    /// The result owns its elements in the view's axis order, so a subsequent
424    /// read with the dense accessors returns the same logical values in a
425    /// contiguous buffer.
426    ///
427    /// # Errors
428    ///
429    /// Returns a validation error carrying
430    /// [`tenferro_tensor_core::ValidationError::ShapeDataLengthMismatch`] when the view names storage
431    /// the payload does not have.
432    ///
433    /// # Examples
434    ///
435    /// ```rust
436    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
437    ///
438    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2, 2], vec![1.0_f64, 2.0, 3.0, 4.0])?);
439    /// let contiguous = erased.permuted(&[1, 0])?.to_contiguous()?;
440    /// assert_eq!(contiguous.shape(), &[2, 2]);
441    /// assert!(contiguous.is_contiguous());
442    /// assert_eq!(contiguous.as_dense::<f64>().unwrap().0, &[1.0, 3.0, 2.0, 4.0]);
443    /// # Ok::<(), tenferro_tensor::Error>(())
444    /// ```
445    pub fn to_contiguous(&self) -> crate::Result<Self> {
446        let payload = self.payload.gather(&self.layout)?;
447        let layout = TensorLayout::compact(self.layout.shape().into()).map_err(|source| {
448            crate::Error::validation("ErasedHostTensor::to_contiguous", source)
449        })?;
450        let payload_elements = element_count(&layout);
451        Ok(Self {
452            payload: Arc::from(payload),
453            type_id: self.type_id,
454            element: self.element,
455            dense: true,
456            layout,
457            payload_elements,
458        })
459    }
460
461    /// Whether the stored tensor has element type `T`.
462    ///
463    /// # Examples
464    ///
465    /// ```rust
466    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
467    ///
468    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1.0_f64])?);
469    /// assert!(erased.is::<f64>());
470    /// # Ok::<(), tenferro_tensor::Error>(())
471    /// ```
472    #[must_use]
473    pub fn is<T: Scalar>(&self) -> bool {
474        self.payload.as_any().is::<Payload<T>>()
475    }
476
477    /// Borrow the whole payload when it is dense and has element type `T`.
478    ///
479    /// A strided view answers `None` rather than presenting the payload as if it
480    /// were the view. Use [`ErasedHostTensor::element_at`] or
481    /// [`ErasedHostTensor::to_contiguous`] for a view.
482    ///
483    /// # Examples
484    ///
485    /// ```rust
486    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
487    ///
488    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2, 2], vec![1.0_f64, 2.0, 3.0, 4.0])?);
489    /// assert_eq!(erased.downcast_ref::<f64>().unwrap().shape(), &[2, 2]);
490    ///
491    /// // A strided view is not the dense payload, so the dense borrow refuses it.
492    /// assert!(erased.permuted(&[1, 0])?.downcast_ref::<f64>().is_none());
493    /// # Ok::<(), tenferro_tensor::Error>(())
494    /// ```
495    #[must_use]
496    pub fn downcast_ref<T: Scalar>(&self) -> Option<&Payload<T>> {
497        if !self.is_contiguous() {
498            return None;
499        }
500        self.payload.as_any().downcast_ref::<Payload<T>>()
501    }
502
503    /// Mutably borrow the whole payload when it is dense, unique, and has
504    /// element type `T`.
505    ///
506    /// A strided view or a payload shared with another value answers `None`, so
507    /// aliasing is never reachable through this entry point. Use
508    /// [`ErasedHostTensor::element_at_mut`] for a view.
509    ///
510    /// # Examples
511    ///
512    /// ```rust
513    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
514    ///
515    /// let mut erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1_i64])?);
516    /// erased.downcast_mut::<i64>().unwrap().host_data_mut()[0] = 9;
517    /// assert_eq!(erased.downcast_ref::<i64>().unwrap().as_slice(), &[9]);
518    /// # Ok::<(), tenferro_tensor::Error>(())
519    /// ```
520    pub fn downcast_mut<T: Scalar>(&mut self) -> Option<&mut Payload<T>> {
521        if !self.is_contiguous() {
522            return None;
523        }
524        Arc::get_mut(&mut self.payload)?
525            .as_any_mut()
526            .downcast_mut::<Payload<T>>()
527    }
528
529    /// Take the whole payload when it is dense and has element type `T`.
530    ///
531    /// # Examples
532    ///
533    /// ```rust
534    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
535    ///
536    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![2.0_f64])?);
537    /// assert_eq!(erased.into_typed::<f64>().unwrap().as_slice(), &[2.0]);
538    /// # Ok::<(), tenferro_tensor::Error>(())
539    /// ```
540    #[must_use]
541    pub fn into_typed<T: Scalar>(mut self) -> Option<Payload<T>> {
542        if !self.dense {
543            return None;
544        }
545        // A shared payload cannot be taken out of its reference count, so taking
546        // the elements requires the caller to be the only holder.
547        let payload = Arc::get_mut(&mut self.payload)?;
548        let tensor = payload.as_any_mut().downcast_mut::<Payload<T>>()?;
549        let empty = Payload::<T>::from_host_vec_col_major(vec![0], Vec::new()).ok()?;
550        Some(core::mem::replace(tensor, empty))
551    }
552
553    /// Number of elements the stored payload holds.
554    ///
555    /// A strided view may reach fewer than all of them, so this is the extent a
556    /// buffer-length check must use rather than the view's own count.
557    ///
558    /// # Examples
559    ///
560    /// ```rust
561    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
562    ///
563    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2, 2], vec![0.0_f64; 4])?);
564    /// assert_eq!(erased.payload_element_count(), 4);
565    /// # Ok::<(), tenferro_tensor::Error>(())
566    /// ```
567    #[must_use]
568    pub fn payload_element_count(&self) -> usize {
569        self.payload_elements
570    }
571
572    /// Borrow the dense element slice and its shape when the view is contiguous
573    /// and has element type `T`.
574    ///
575    /// # Examples
576    ///
577    /// ```rust
578    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
579    ///
580    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2], vec![1.0_f64, 2.0])?);
581    /// assert_eq!(erased.as_dense::<f64>().unwrap().0, &[1.0, 2.0]);
582    /// # Ok::<(), tenferro_tensor::Error>(())
583    /// ```
584    #[must_use]
585    pub fn as_dense<T: Scalar>(&self) -> Option<(&[T], &[usize])> {
586        if !self.is_contiguous() {
587            return None;
588        }
589        let payload = self.payload.as_any().downcast_ref::<Payload<T>>()?;
590        Some((payload.as_slice(), payload.shape()))
591    }
592
593    /// Borrow one element of the presented view by logical index.
594    ///
595    /// This applies the view's strides and offset, so it reads the element the
596    /// view names even when the view is not contiguous.
597    ///
598    /// # Examples
599    ///
600    /// ```rust
601    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
602    ///
603    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2, 2], vec![1.0_f64, 2.0, 3.0, 4.0])?);
604    /// let permuted = erased.permuted(&[1, 0])?;
605    /// assert_eq!(permuted.element_at::<f64>(&[1, 0]), Some(&3.0));
606    /// assert_eq!(permuted.element_at::<f64>(&[2, 0]), None);
607    /// # Ok::<(), tenferro_tensor::Error>(())
608    /// ```
609    #[must_use]
610    pub fn element_at<T: Scalar>(&self, index: &[usize]) -> Option<&T> {
611        let payload = self.payload.as_any().downcast_ref::<Payload<T>>()?;
612        let linear = usize::try_from(linear_index(&self.layout, index)?).ok()?;
613        payload.as_slice().get(linear)
614    }
615
616    /// Mutably borrow one element of the presented view by logical index.
617    ///
618    /// This applies the view's strides and offset. It answers `None` when the
619    /// payload is shared with another value, so two live views never produce two
620    /// mutable borrows of one element. A writer therefore holds the only
621    /// reference: build the view, release the value it came from, and mutate
622    /// through the view, or call [`ErasedHostTensor::duplicate`] for a payload of
623    /// its own.
624    ///
625    /// # Examples
626    ///
627    /// ```rust
628    /// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
629    ///
630    /// let erased = ErasedHostTensor::new(TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2, 2], vec![1.0_f64, 2.0, 3.0, 4.0])?);
631    ///
632    /// // A view that shares its payload refuses a mutable element borrow.
633    /// let mut shared = erased.permuted(&[1, 0])?;
634    /// assert!(shared.element_at_mut::<f64>(&[1, 0]).is_none());
635    ///
636    /// // An independent copy accepts it, and the original stays unchanged.
637    /// let mut owned = erased.permuted(&[1, 0])?.duplicate();
638    /// *owned.element_at_mut::<f64>(&[1, 0]).unwrap() = 20.0;
639    /// assert_eq!(owned.element_at::<f64>(&[1, 0]), Some(&20.0));
640    /// assert_eq!(erased.as_dense::<f64>().unwrap().0, &[1.0, 2.0, 3.0, 4.0]);
641    /// # Ok::<(), tenferro_tensor::Error>(())
642    /// ```
643    pub fn element_at_mut<T: Scalar>(&mut self, index: &[usize]) -> Option<&mut T> {
644        let layout = &self.layout;
645        let linear = usize::try_from(linear_index(layout, index)?).ok()?;
646        let payload = Arc::get_mut(&mut self.payload)?;
647        let payload = payload.as_any_mut().downcast_mut::<Payload<T>>()?;
648        payload.host_data_mut().get_mut(linear)
649    }
650}
651
652#[cfg(test)]
653mod tests;