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;