Skip to main content

tensor4all_tensorbackend/
tensor_element.rs

1use anyhow::{anyhow, ensure, Result};
2use num_complex::{Complex32, Complex64};
3use tenferro::{DType, Tensor as NativeTensor, TensorScalar};
4
5/// Public scalar element types supported by tensor4all dense/diag constructors.
6///
7/// Implemented for `f32`, `f64`, `Complex32`, and `Complex64`.
8///
9/// # Examples
10///
11/// ```
12/// use tensor4all_tensorbackend::TensorElement;
13///
14/// let t = f64::dense_native_tensor_from_col_major(&[1.0, 2.0], &[2]).unwrap();
15/// assert_eq!(t.shape(), &[2]);
16///
17/// let vals = f64::dense_values_from_native_col_major(&t).unwrap();
18/// assert_eq!(vals, vec![1.0, 2.0]);
19/// ```
20pub trait TensorElement: TensorScalar + Copy + Send + Sync + 'static {
21    /// Build a dense native tensor from column-major data.
22    /// # Errors
23    ///
24    /// Returns an error when the shape product would overflow `usize`, when
25    /// `data.len()` does not match the expected shape, or when the backend
26    /// rejects the tensor.
27    ///
28    fn dense_native_tensor_from_col_major(data: &[Self], dims: &[usize]) -> Result<NativeTensor>;
29
30    /// Build a dense native tensor by moving an owned column-major payload.
31    ///
32    /// This is the allocation-preserving counterpart of
33    /// [`Self::dense_native_tensor_from_col_major`]. The payload must contain
34    /// exactly the product of `dims` elements.
35    ///
36    /// # Errors
37    ///
38    /// Returns an error for a shape mismatch when the data length does not
39    /// equal the dimension product, or for a backend conversion failure.
40    ///
41    /// # Arguments
42    ///
43    /// * `data` - Owned column-major values whose length equals the product of
44    ///   `dims`.
45    /// * `dims` - Logical tensor dimensions in column-major axis order.
46    ///
47    /// # Returns
48    ///
49    /// A native dense tensor that owns the supplied payload without cloning it.
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// use tensor4all_tensorbackend::TensorElement;
55    ///
56    /// let tensor = f64::dense_native_tensor_from_col_major_owned(
57    ///     vec![1.0, 2.0, 3.0, 4.0],
58    ///     &[2, 2],
59    /// )
60    /// .unwrap();
61    /// assert_eq!(tensor.shape(), &[2, 2]);
62    /// assert_eq!(tensor.as_slice::<f64>().unwrap(), &[1.0, 2.0, 3.0, 4.0]);
63    /// ```
64    fn dense_native_tensor_from_col_major_owned(
65        data: Vec<Self>,
66        dims: &[usize],
67    ) -> Result<NativeTensor>;
68
69    /// Build a diagonal native tensor from column-major diagonal payload data.
70    /// # Errors
71    ///
72    /// Returns an error when `logical_rank` is zero, when expanding the
73    /// diagonal would overflow `usize`, or when the backend rejects the
74    /// resulting dense tensor.
75    ///
76    fn diag_native_tensor_from_col_major(
77        data: &[Self],
78        logical_rank: usize,
79    ) -> Result<NativeTensor>;
80
81    /// Build a rank-0 native tensor.
82    /// # Errors
83    ///
84    /// Returns an error when the backend rejects the rank-0 tensor as invalid
85    /// input.
86    ///
87    fn scalar_native_tensor(value: Self) -> Result<NativeTensor>;
88
89    /// Materialize dense column-major values from a native tensor.
90    /// # Errors
91    ///
92    /// Returns an error when `tensor`'s dtype does not match `Self`.
93    ///
94    fn dense_values_from_native_col_major(tensor: &NativeTensor) -> Result<Vec<Self>>;
95
96    /// Materialize dense column-major values while consuming the native tensor.
97    ///
98    /// This is the ownership-preserving counterpart of
99    /// [`Self::dense_values_from_native_col_major`]: a compact column-major host
100    /// result hands its buffer over instead of being copied, so a caller that
101    /// built the tensor only to read it back pays no extra full-size
102    /// allocation.
103    ///
104    /// # Arguments
105    ///
106    /// * `tensor` - Consumed native tensor whose logical values are returned.
107    ///
108    /// # Returns
109    ///
110    /// The tensor's values in column-major order, in `Self`'s scalar type.
111    ///
112    /// # Errors
113    ///
114    /// Returns an error on a dtype mismatch between `tensor` and `Self`, and when
115    /// `tensor`'s storage cannot hand over a compact column-major host buffer
116    /// because it is backend-resident or its layout is not compact, instead of
117    /// copying or silently reordering it.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// use tensor4all_tensorbackend::TensorElement;
123    ///
124    /// let tensor =
125    ///     f64::dense_native_tensor_from_col_major(&[1.0, 3.0, 2.0, 4.0], &[2, 2]).unwrap();
126    /// let values = f64::into_dense_values_from_native_col_major(tensor).unwrap();
127    /// assert_eq!(values, vec![1.0, 3.0, 2.0, 4.0]);
128    /// ```
129    fn into_dense_values_from_native_col_major(tensor: NativeTensor) -> Result<Vec<Self>>;
130
131    /// Materialize diagonal values from a dense native tensor.
132    /// # Errors
133    ///
134    /// Returns an error when `tensor` is rank 0, when its dimensions are not
135    /// all equal, or when its dtype does not match `Self`.
136    ///
137    fn diag_values_from_native_temp(tensor: &NativeTensor) -> Result<Vec<Self>>;
138}
139
140fn tensor_dtype_name(dtype: DType) -> &'static str {
141    match dtype {
142        DType::F32 => "f32",
143        DType::F64 => "f64",
144        DType::I32 => "i32",
145        DType::I64 => "i64",
146        DType::Bool => "bool",
147        DType::C32 => "c32",
148        DType::C64 => "c64",
149        DType::External(_) => "external",
150    }
151}
152
153fn checked_product(dims: &[usize]) -> Result<usize> {
154    dims.iter().try_fold(1usize, |acc, &dim| {
155        acc.checked_mul(dim)
156            .ok_or_else(|| anyhow::anyhow!("dimension product overflow"))
157    })
158}
159
160fn dense_diagonal_values<T: Copy + Default>(diag: &[T], logical_rank: usize) -> Result<Vec<T>> {
161    ensure!(
162        logical_rank >= 1,
163        "diagonal tensor construction requires at least one logical axis"
164    );
165    let diag_len = diag.len();
166    let dims = vec![diag_len; logical_rank];
167    let total_len = checked_product(&dims)?;
168    let mut dense = vec![T::default(); total_len];
169    let diagonal_stride = (0..logical_rank)
170        .scan(1usize, |stride, _| {
171            let current = *stride;
172            *stride = stride.saturating_mul(diag_len);
173            Some(current)
174        })
175        .sum::<usize>();
176    for (i, value) in diag.iter().copied().enumerate() {
177        dense[i * diagonal_stride] = value;
178    }
179    Ok(dense)
180}
181
182macro_rules! impl_tensor_element {
183    ($ty:ty, $dtype:expr) => {
184        impl TensorElement for $ty {
185            fn dense_native_tensor_from_col_major(
186                data: &[Self],
187                dims: &[usize],
188            ) -> Result<NativeTensor> {
189                let expected_len: usize = checked_product(dims)?;
190                ensure!(
191                    data.len() == expected_len,
192                    "dense tensor len {} does not match dims {:?} (expected {})",
193                    data.len(),
194                    dims,
195                    expected_len
196                );
197                Ok(NativeTensor::from_vec_col_major(
198                    dims.to_vec(),
199                    data.to_vec(),
200                )?)
201            }
202
203            fn dense_native_tensor_from_col_major_owned(
204                data: Vec<Self>,
205                dims: &[usize],
206            ) -> Result<NativeTensor> {
207                let expected_len: usize = checked_product(dims)?;
208                ensure!(
209                    data.len() == expected_len,
210                    "dense tensor len {} does not match dims {:?} (expected {})",
211                    data.len(),
212                    dims,
213                    expected_len
214                );
215                Ok(NativeTensor::from_vec_col_major(dims.to_vec(), data)?)
216            }
217
218            fn diag_native_tensor_from_col_major(
219                data: &[Self],
220                logical_rank: usize,
221            ) -> Result<NativeTensor> {
222                let dims = vec![data.len(); logical_rank];
223                let dense = dense_diagonal_values(data, logical_rank)?;
224                Self::dense_native_tensor_from_col_major(&dense, &dims)
225            }
226
227            fn scalar_native_tensor(value: Self) -> Result<NativeTensor> {
228                Ok(NativeTensor::from_vec_col_major(vec![], vec![value])?)
229            }
230
231            fn dense_values_from_native_col_major(tensor: &NativeTensor) -> Result<Vec<Self>> {
232                tensor
233                    .as_slice::<Self>()
234                    .map(|values| values.to_vec())
235                    .map_err(|_| {
236                        anyhow!(
237                            "tensor dtype mismatch: expected {}, got {}",
238                            tensor_dtype_name($dtype),
239                            tensor_dtype_name(tensor.dtype())
240                        )
241                    })
242            }
243
244            fn into_dense_values_from_native_col_major(tensor: NativeTensor) -> Result<Vec<Self>> {
245                let actual = tensor.dtype();
246                let typed = Self::into_typed(tensor).map_err(|failure| {
247                    let (_, error) = failure.into_parts();
248                    anyhow!(
249                        "tensor dtype mismatch: expected {}, got {}: {error}",
250                        tensor_dtype_name($dtype),
251                        tensor_dtype_name(actual)
252                    )
253                })?;
254                // `into_vec_col_major` moves a compact column-major host buffer
255                // and rejects a backend-resident or strided one typed, so this
256                // route never copies and never silently reorders a layout.
257                typed
258                    .into_vec_col_major()
259                    .map(|(_shape, values)| values)
260                    .map_err(|failure| {
261                        let (_, error) = failure.into_parts();
262                        anyhow!(
263                            "native {} tensor buffer cannot be transferred: {error}",
264                            tensor_dtype_name($dtype)
265                        )
266                    })
267            }
268
269            fn diag_values_from_native_temp(tensor: &NativeTensor) -> Result<Vec<Self>> {
270                let shape = tensor.shape();
271                ensure!(
272                    !shape.is_empty(),
273                    "diagonal extraction requires rank >= 1, got scalar tensor"
274                );
275                let diag_len = shape[0];
276                ensure!(
277                    shape.iter().all(|&dim| dim == diag_len),
278                    "expected square/equal dims for diagonal extraction, got {:?}",
279                    shape
280                );
281                let dense = Self::dense_values_from_native_col_major(tensor)?;
282                let diagonal_stride = (0..shape.len())
283                    .scan(1usize, |stride, _| {
284                        let current = *stride;
285                        *stride = stride.saturating_mul(diag_len);
286                        Some(current)
287                    })
288                    .sum::<usize>();
289                Ok((0..diag_len).map(|i| dense[i * diagonal_stride]).collect())
290            }
291        }
292    };
293}
294
295impl_tensor_element!(f32, DType::F32);
296impl_tensor_element!(f64, DType::F64);
297impl_tensor_element!(Complex32, DType::C32);
298impl_tensor_element!(Complex64, DType::C64);
299
300#[cfg(test)]
301mod tests;