1use crate::error::{NirError, Result};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[non_exhaustive]
15pub enum DType {
16 F32,
18 F64,
20 I64,
22 Bool,
24}
25
26impl DType {
27 #[must_use]
29 pub const fn size_of(self) -> usize {
30 match self {
31 Self::F32 => 4,
32 Self::F64 => 8,
33 Self::I64 => 8,
34 Self::Bool => 1,
35 }
36 }
37}
38
39#[derive(Debug, Clone, PartialEq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub enum TensorData {
43 F32(Vec<f32>),
45 F64(Vec<f64>),
47 I64(Vec<i64>),
49 Bool(Vec<bool>),
51}
52
53impl TensorData {
54 #[must_use]
56 pub fn len(&self) -> usize {
57 match self {
58 Self::F32(v) => v.len(),
59 Self::F64(v) => v.len(),
60 Self::I64(v) => v.len(),
61 Self::Bool(v) => v.len(),
62 }
63 }
64
65 #[must_use]
67 pub fn is_empty(&self) -> bool {
68 self.len() == 0
69 }
70
71 #[must_use]
73 pub const fn dtype(&self) -> DType {
74 match self {
75 Self::F32(_) => DType::F32,
76 Self::F64(_) => DType::F64,
77 Self::I64(_) => DType::I64,
78 Self::Bool(_) => DType::Bool,
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq)]
105pub struct Tensor {
106 shape: Vec<usize>,
108 data: TensorData,
110}
111
112#[cfg(feature = "serde")]
113impl serde::Serialize for Tensor {
114 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
115 where
116 S: serde::Serializer,
117 {
118 #[derive(serde::Serialize)]
119 struct TensorRef<'a> {
120 shape: &'a [usize],
121 data: &'a TensorData,
122 }
123
124 serde::Serialize::serialize(
125 &TensorRef {
126 shape: self.shape(),
127 data: self.data(),
128 },
129 serializer,
130 )
131 }
132}
133
134#[cfg(feature = "serde")]
135impl<'de> serde::Deserialize<'de> for Tensor {
136 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
137 where
138 D: serde::Deserializer<'de>,
139 {
140 #[derive(serde::Deserialize)]
141 struct TensorOwned {
142 shape: Vec<usize>,
143 data: TensorData,
144 }
145
146 let tensor = <TensorOwned as serde::Deserialize>::deserialize(deserializer)?;
147 Self::new(tensor.shape, tensor.data).map_err(serde::de::Error::custom)
148 }
149}
150
151impl Tensor {
152 pub fn new(shape: impl Into<Vec<usize>>, data: TensorData) -> Result<Self> {
154 let shape = shape.into();
155 check_shape_len(&shape, data.len())?;
156 Ok(Self { shape, data })
157 }
158
159 #[must_use]
161 pub const fn dtype(&self) -> DType {
162 self.data.dtype()
163 }
164
165 #[must_use]
167 pub fn shape(&self) -> &[usize] {
168 &self.shape
169 }
170
171 #[must_use]
173 pub fn data(&self) -> &TensorData {
174 &self.data
175 }
176
177 #[must_use]
186 #[cfg(feature = "hdf5")]
187 pub(crate) fn into_data(self) -> TensorData {
188 self.data
189 }
190
191 pub fn from_f32(shape: impl Into<Vec<usize>>, data: impl Into<Vec<f32>>) -> Result<Self> {
193 Self::new(shape, TensorData::F32(data.into()))
194 }
195
196 pub fn from_f64(shape: impl Into<Vec<usize>>, data: impl Into<Vec<f64>>) -> Result<Self> {
198 Self::new(shape, TensorData::F64(data.into()))
199 }
200
201 pub fn from_i64(shape: impl Into<Vec<usize>>, data: impl Into<Vec<i64>>) -> Result<Self> {
203 Self::new(shape, TensorData::I64(data.into()))
204 }
205
206 pub fn from_bool(shape: impl Into<Vec<usize>>, data: impl Into<Vec<bool>>) -> Result<Self> {
208 Self::new(shape, TensorData::Bool(data.into()))
209 }
210
211 #[must_use]
213 pub fn scalar_f32(value: f32) -> Self {
214 Self {
215 shape: vec![],
216 data: TensorData::F32(vec![value]),
217 }
218 }
219
220 #[must_use]
222 pub fn scalar_f64(value: f64) -> Self {
223 Self {
224 shape: vec![],
225 data: TensorData::F64(vec![value]),
226 }
227 }
228
229 #[must_use]
231 pub fn scalar_i64(value: i64) -> Self {
232 Self {
233 shape: vec![],
234 data: TensorData::I64(vec![value]),
235 }
236 }
237
238 #[must_use]
243 pub fn zeros_like(&self) -> Self {
244 let n = self.data.len();
245 let data = match self.dtype() {
246 DType::F32 => TensorData::F32(vec![0.0; n]),
247 DType::F64 => TensorData::F64(vec![0.0; n]),
248 DType::I64 => TensorData::I64(vec![0; n]),
249 DType::Bool => TensorData::Bool(vec![false; n]),
250 };
251 Self {
252 shape: self.shape.clone(),
253 data,
254 }
255 }
256
257 #[must_use]
262 pub fn ones_like(&self) -> Self {
263 let n = self.data.len();
264 let data = match self.dtype() {
265 DType::F32 => TensorData::F32(vec![1.0; n]),
266 DType::F64 => TensorData::F64(vec![1.0; n]),
267 DType::I64 => TensorData::I64(vec![1; n]),
268 DType::Bool => TensorData::Bool(vec![true; n]),
269 };
270 Self {
271 shape: self.shape.clone(),
272 data,
273 }
274 }
275
276 #[must_use]
278 pub fn numel(&self) -> usize {
279 shape_product(&self.shape).expect("tensor shape product overflow")
281 }
282
283 #[must_use]
285 pub fn ndim(&self) -> usize {
286 self.shape.len()
287 }
288}
289
290pub type MetadataMap = std::collections::HashMap<String, MetadataValue>;
292
293#[derive(Debug, Clone, PartialEq)]
295#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
296#[non_exhaustive]
297pub enum MetadataValue {
298 String(String),
300 StringList(Vec<String>),
307 F64(f64),
309 I64(i64),
311 Bool(bool),
313 Tensor(Tensor),
315}
316
317fn shape_product(shape: &[usize]) -> Option<usize> {
320 if shape.is_empty() {
321 Some(1)
322 } else {
323 shape.iter().try_fold(1usize, |acc, &d| acc.checked_mul(d))
324 }
325}
326
327fn check_shape_len(shape: &[usize], len: usize) -> Result<()> {
328 let expected = shape_product(shape).ok_or_else(|| {
329 NirError::InvalidTensor(format!("shape product overflows usize (shape={shape:?})"))
330 })?;
331 if expected != len {
332 return Err(NirError::InvalidTensor(format!(
333 "shape product {expected} != data len {len} (shape={shape:?})"
334 )));
335 }
336 Ok(())
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn from_f32_ok() {
345 let t = Tensor::from_f32(vec![2, 3], vec![1., 2., 3., 4., 5., 6.]).unwrap();
346 assert_eq!(t.dtype(), DType::F32);
347 assert_eq!(t.numel(), 6);
348 assert_eq!(t.ndim(), 2);
349 }
350
351 #[test]
352 fn from_f64_ok() {
353 let t = Tensor::from_f64([2], vec![1.0, 2.0]).unwrap();
354 assert_eq!(t.dtype(), DType::F64);
355 assert_eq!(t.numel(), 2);
356 }
357
358 #[test]
359 fn length_mismatch_f32() {
360 let err = Tensor::from_f32(vec![2, 2], vec![1., 2., 3.]).unwrap_err();
361 assert!(matches!(err, NirError::InvalidTensor(_)));
362 assert!(err.to_string().contains("shape product 4 != data len 3"));
363 }
364
365 #[test]
366 fn length_mismatch_f64() {
367 let err = Tensor::from_f64(vec![3], vec![1.0]).unwrap_err();
368 assert!(matches!(err, NirError::InvalidTensor(_)));
369 }
370
371 #[test]
372 fn scalar_has_empty_shape_one_element() {
373 let t = Tensor::scalar_f64(0.5);
374 assert!(t.shape().is_empty());
375 assert_eq!(t.numel(), 1);
376 assert_eq!(t.data().len(), 1);
377 }
378
379 #[test]
380 fn empty_shape_rejects_wrong_len() {
381 let err = Tensor::from_f32(Vec::<usize>::new(), vec![1., 2.]).unwrap_err();
382 assert!(matches!(err, NirError::InvalidTensor(_)));
383 }
384
385 #[test]
386 fn i64_and_bool_constructors() {
387 let i = Tensor::from_i64([2], vec![1, 2]).unwrap();
388 assert_eq!(i.dtype(), DType::I64);
389 let b = Tensor::from_bool([2], vec![true, false]).unwrap();
390 assert_eq!(b.dtype(), DType::Bool);
391 }
392
393 #[test]
394 fn dtype_size_of() {
395 assert_eq!(DType::F32.size_of(), 4);
396 assert_eq!(DType::F64.size_of(), 8);
397 assert_eq!(DType::I64.size_of(), 8);
398 assert_eq!(DType::Bool.size_of(), 1);
399 }
400
401 #[test]
402 fn shape_product_overflow_rejected() {
403 let err = Tensor::from_f32(vec![usize::MAX, usize::MAX], vec![1.0]).unwrap_err();
404 assert!(matches!(err, NirError::InvalidTensor(_)));
405 assert!(err.to_string().contains("overflows"));
406 }
407
408 #[test]
409 fn zeros_like_preserves_shape_and_dtype() {
410 let t = Tensor::from_f32(vec![2, 2], vec![1., 2., 3., 4.]).unwrap();
411 let z = t.zeros_like();
412 assert_eq!(z.shape(), t.shape());
413 assert_eq!(z.dtype(), DType::F32);
414 assert_eq!(z.data(), &TensorData::F32(vec![0.0; 4]));
415 }
416
417 #[test]
418 fn ones_like_preserves_shape_and_dtype() {
419 let t = Tensor::from_f64(vec![3], vec![7.0, 8.0, 9.0]).unwrap();
420 let o = t.ones_like();
421 assert_eq!(o.shape(), [3]);
422 assert_eq!(o.data(), &TensorData::F64(vec![1.0; 3]));
423 }
424
425 #[test]
426 fn zeros_and_ones_like_cover_int_and_bool() {
427 let i = Tensor::from_i64([2], vec![5, 6]).unwrap();
428 assert_eq!(i.zeros_like().data(), &TensorData::I64(vec![0, 0]));
429 assert_eq!(i.ones_like().data(), &TensorData::I64(vec![1, 1]));
430
431 let b = Tensor::from_bool([2], vec![true, false]).unwrap();
432 assert_eq!(b.zeros_like().data(), &TensorData::Bool(vec![false, false]));
433 assert_eq!(b.ones_like().data(), &TensorData::Bool(vec![true, true]));
434 }
435
436 #[test]
437 fn zeros_like_of_scalar_is_scalar() {
438 let z = Tensor::scalar_f64(3.5).zeros_like();
439 assert!(z.shape().is_empty());
440 assert_eq!(z.numel(), 1);
441 }
442
443 #[test]
444 fn metadata_variants() {
445 let m = MetadataValue::String("note".into());
446 assert!(matches!(m, MetadataValue::String(_)));
447 let _ = MetadataValue::F64(1.0);
448 let _ = MetadataValue::I64(2);
449 let _ = MetadataValue::Bool(true);
450 let _ = MetadataValue::Tensor(Tensor::scalar_f32(0.0));
451 }
452}