use burn_tensor::TensorData;
use serde::{Deserialize, Serialize};
use crate::error::Error;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(into = "TensorData", try_from = "TensorData")]
pub struct Tensor(TensorData);
pub(crate) fn checked_shape_len(shape: &[usize]) -> Option<usize> {
shape
.iter()
.try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
}
impl Tensor {
pub fn new(shape: impl Into<Vec<usize>>, data: impl Into<Vec<f32>>) -> Result<Self, Error> {
let shape = shape.into();
let data = data.into();
let expected = checked_shape_len(&shape)
.ok_or_else(|| Error::validation(format!("tensor shape {shape:?} overflows usize")))?;
if expected != data.len() {
return Err(Error::shape(shape, vec![data.len()]));
}
Ok(Self(TensorData::new(data, shape)))
}
pub fn zeros(shape: impl Into<Vec<usize>>) -> Result<Self, Error> {
let shape = shape.into();
let len = checked_shape_len(&shape)
.ok_or_else(|| Error::validation(format!("tensor shape {shape:?} overflows usize")))?;
Ok(Self(TensorData::new(vec![0.0f32; len], shape)))
}
pub fn vector(data: impl Into<Vec<f32>>) -> Self {
let data = data.into();
let len = data.len();
Self(TensorData::new(data, vec![len]))
}
pub fn shape(&self) -> &[usize] {
&self.0.shape
}
pub fn data(&self) -> &[f32] {
self.0
.as_slice::<f32>()
.expect("helena tensors always store f32")
}
pub fn len(&self) -> usize {
self.shape().iter().product()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn is_finite(&self) -> bool {
self.data().iter().all(|x| x.is_finite())
}
pub fn as_data(&self) -> &TensorData {
&self.0
}
pub fn into_data(self) -> TensorData {
self.0
}
pub fn from_data(data: TensorData) -> Result<Self, Error> {
Self::try_from(data)
}
}
impl TryFrom<TensorData> for Tensor {
type Error = Error;
fn try_from(data: TensorData) -> Result<Self, Error> {
let actual = data
.as_slice::<f32>()
.map_err(|_| Error::validation("helena tensors must store f32 data"))?
.len();
let expected = checked_shape_len(&data.shape).ok_or_else(|| {
Error::validation(format!("tensor shape {:?} overflows usize", data.shape))
})?;
if actual != expected {
return Err(Error::shape(data.shape.clone(), vec![actual]));
}
Ok(Self(data))
}
}
impl From<Tensor> for TensorData {
fn from(tensor: Tensor) -> Self {
tensor.0
}
}
impl PartialEq for Tensor {
fn eq(&self, other: &Self) -> bool {
self.shape() == other.shape() && self.data() == other.data()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_rejects_shape_mismatch() {
let err = Tensor::new([2, 3], vec![1.0, 2.0]).unwrap_err();
assert!(matches!(err, Error::Shape { expected, actual }
if expected == vec![2, 3] && actual == vec![2]));
}
#[test]
fn new_rejects_overflowing_shape() {
let err = Tensor::new([usize::MAX, 2], vec![0.0]).unwrap_err();
assert!(matches!(err, Error::Validation(_)));
}
#[test]
fn zeros_has_expected_len() {
let t = Tensor::zeros([2, 3]).unwrap();
assert_eq!(t.shape(), &[2, 3]);
assert_eq!(t.len(), 6);
assert!(t.data().iter().all(|&x| x == 0.0));
assert!(matches!(
Tensor::zeros([usize::MAX, 2]),
Err(Error::Validation(_))
));
}
#[test]
fn finiteness_is_observable() {
assert!(Tensor::vector([1.0, -2.0]).is_finite());
assert!(!Tensor::vector([1.0, f32::NAN]).is_finite());
assert!(!Tensor::vector([f32::INFINITY]).is_finite());
}
#[test]
fn round_trips_through_tensor_data() {
let t = Tensor::new([2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap();
let back = Tensor::from_data(t.clone().into_data()).unwrap();
assert_eq!(t, back);
}
#[test]
fn from_data_rejects_non_f32() {
let i32_data = TensorData::new(vec![1i32, 2, 3], [3]);
assert!(matches!(
Tensor::from_data(i32_data),
Err(Error::Validation(_))
));
}
#[test]
fn try_from_rejects_length_mismatch() {
let ragged = TensorData::from_bytes_vec(vec![0u8; 4], [5], burn_tensor::DType::F32);
assert!(matches!(Tensor::try_from(ragged), Err(Error::Shape { .. })));
}
#[test]
fn deserialize_rejects_non_f32() {
let json = serde_json::to_string(&TensorData::new(vec![1i32, 2, 3], [3])).unwrap();
assert!(serde_json::from_str::<Tensor>(&json).is_err());
}
#[test]
fn serde_round_trip() {
let t = Tensor::vector([1.0, -2.0, 3.5]);
let json = serde_json::to_string(&t).unwrap();
let back: Tensor = serde_json::from_str(&json).unwrap();
assert_eq!(t, back);
}
}