Skip to main content

matten_ndarray/
convert.rs

1//! `matten::Tensor` ↔ `ndarray::ArrayD<f64>` conversions (RFC-027 §4).
2//!
3//! Both directions copy: `matten::Tensor` owns a contiguous row-major
4//! `Vec<f64>`, and these functions hand over / materialize owned buffers. No
5//! zero-copy is claimed (RFC-025 §3).
6
7use crate::error::MattenNdarrayError;
8use matten::Tensor;
9use ndarray::{ArrayD, IxDyn};
10
11/// Converts a numeric [`Tensor`] into an [`ndarray::ArrayD<f64>`].
12///
13/// The result is standard (row-major) layout. A dynamic tensor returns
14/// [`MattenNdarrayError::DynamicTensor`] rather than panicking. This guard is
15/// unconditional — it does not depend on the companion `dynamic` feature being
16/// enabled (RFC-031).
17///
18/// ```
19/// use matten::Tensor;
20/// use matten_ndarray::to_arrayd;
21///
22/// let t = Tensor::new(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]);
23/// let arr = to_arrayd(&t).unwrap();
24/// assert_eq!(arr.shape(), &[2, 2]);
25/// assert_eq!(arr[[1, 0]], 3.0);
26/// ```
27pub fn to_arrayd(tensor: &Tensor) -> Result<ArrayD<f64>, MattenNdarrayError> {
28    if tensor.is_dynamic() {
29        return Err(MattenNdarrayError::DynamicTensor);
30    }
31
32    let shape = tensor.shape().to_vec();
33    // `to_vec` is row-major; safe here because dynamic tensors were rejected above.
34    let data = tensor.to_vec();
35    ArrayD::from_shape_vec(IxDyn(&shape), data).map_err(MattenNdarrayError::NdarrayShape)
36}
37
38/// Converts an [`ndarray::ArrayD<f64>`] into a [`Tensor`].
39///
40/// Conversion preserves **logical** element order: an `ArrayD` may be in
41/// non-standard (transposed / sliced / non-standard-stride) layout, so the raw
42/// backing buffer is not read directly — that would silently transpose the
43/// data. A shape with any zero-length axis is rejected, because core `matten`
44/// does not support zero-sized dimensions.
45///
46/// ```
47/// use matten_ndarray::from_arrayd;
48/// use ndarray::{ArrayD, IxDyn};
49///
50/// // A transposed (non-standard-layout) array still converts by logical order.
51/// let a = ArrayD::from_shape_vec(IxDyn(&[2, 3]), vec![1., 2., 3., 4., 5., 6.]).unwrap();
52/// let t = from_arrayd(a.t().to_owned()).unwrap(); // logical shape [3, 2]
53/// assert_eq!(t.shape(), &[3, 2]);
54/// assert_eq!(t.as_slice(), &[1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
55/// ```
56pub fn from_arrayd(array: ArrayD<f64>) -> Result<Tensor, MattenNdarrayError> {
57    let shape: Vec<usize> = array.shape().to_vec();
58
59    if shape.contains(&0) {
60        return Err(MattenNdarrayError::ZeroSizedAxis(shape));
61    }
62
63    // `as_standard_layout` yields a row-major view (cloning only if the input
64    // was non-standard layout); iterating it gives logical order.
65    let data: Vec<f64> = array.as_standard_layout().iter().copied().collect();
66
67    Tensor::try_new(data, &shape).map_err(MattenNdarrayError::Matten)
68}