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;