use burn::{
Tensor,
tensor::{DType, Device, Element, Float, Int, TensorData, kind::Basic},
};
use itertools::Itertools as _;
use crate::{
Array, EvaluateRowMajorArray, RlstError, RlstResult, StridedDynArray, ValueArrayImpl,
dense::{
data_container::VectorContainer, layout::row_major_stride_from_shape,
strided_base_array::StridedBaseArray,
},
};
pub trait TensorIntoRlstArray<const NDIM: usize> {
fn into_array<Item: RlstAsTensorDtype + Element>(
self,
) -> RlstResult<StridedDynArray<Item, NDIM>>;
}
pub trait RlstArrayIntoBurnTensor<Item, const NDIM: usize, K: Basic> {
fn into_tensor_on(self, device: &Device) -> RlstResult<Tensor<NDIM, K>>;
fn into_tensor(self) -> RlstResult<Tensor<NDIM, K>>
where
Self: Sized,
{
self.into_tensor_on(&Device::default())
}
}
pub trait RlstAsTensorDtype {
fn burn_type() -> DType;
}
macro_rules! impl_rlst_as_tensor_type {
($dtype:ty, $tensor:expr) => {
impl RlstAsTensorDtype for $dtype {
fn burn_type() -> DType {
$tensor
}
}
};
}
impl_rlst_as_tensor_type!(f64, DType::F64);
impl_rlst_as_tensor_type!(f32, DType::F32);
impl_rlst_as_tensor_type!(u8, DType::U8);
impl_rlst_as_tensor_type!(u16, DType::U16);
impl_rlst_as_tensor_type!(u32, DType::U32);
impl_rlst_as_tensor_type!(u64, DType::U64);
impl_rlst_as_tensor_type!(i8, DType::I8);
impl_rlst_as_tensor_type!(i16, DType::I16);
impl_rlst_as_tensor_type!(i32, DType::I32);
impl_rlst_as_tensor_type!(i64, DType::I64);
impl<const NDIM: usize, K: Basic> TensorIntoRlstArray<NDIM> for Tensor<NDIM, K> {
fn into_array<Item: RlstAsTensorDtype + Element>(
self,
) -> RlstResult<StridedDynArray<Item, NDIM>> {
if self.dtype() != Item::burn_type() {
return Err(RlstError::GeneralError(format!(
"Array type {:?} does not match tensor type {:?}",
Item::burn_type(),
self.dtype()
)));
}
let tensor_data = self
.try_into_data()
.map_err(|err| RlstError::GeneralError(format!("Cannot read tensor: {err}")))?;
let shape = tensor_data
.shape()
.iter()
.copied()
.collect_array::<NDIM>()
.ok_or_else(|| {
RlstError::GeneralError(format!(
"Tensor data shape {:?} does not have {NDIM} dimensions",
tensor_data.shape()
))
})?;
let data = tensor_data
.try_into_vec::<Item>()
.map_err(|err| RlstError::GeneralError(format!("Cannot read tensor data: {err}")))?;
Ok(Array::new(StridedBaseArray::new(
VectorContainer::from_vec(data),
shape,
row_major_stride_from_shape(shape),
)))
}
}
fn array_into_tensor<Item, ArrayImpl, K, const NDIM: usize>(
arr: Array<ArrayImpl, NDIM>,
device: &Device,
kind: &str,
kind_matches: fn(&DType) -> bool,
) -> RlstResult<Tensor<NDIM, K>>
where
Item: RlstAsTensorDtype + Element,
ArrayImpl: ValueArrayImpl<Item, NDIM>,
K: Basic,
{
let dtype = Item::burn_type();
if !kind_matches(&dtype) {
return Err(RlstError::GeneralError(format!(
"Type {dtype:?} cannot be stored in {kind} tensor"
)));
}
if !device.supports_dtype(dtype) {
return Err(RlstError::GeneralError(format!(
"Type {dtype:?} not supported by device {device:?}"
)));
}
let arr_row_major = arr.eval_row_major();
let data = TensorData::new(arr_row_major.data().unwrap().to_vec(), arr.shape());
Ok(Tensor::<NDIM, K>::from_data(data, (device, dtype)))
}
impl<Item, ArrayImpl, const NDIM: usize> RlstArrayIntoBurnTensor<Item, NDIM, Float>
for Array<ArrayImpl, NDIM>
where
Item: RlstAsTensorDtype + Element,
ArrayImpl: ValueArrayImpl<Item, NDIM>,
{
fn into_tensor_on(self, device: &Device) -> RlstResult<Tensor<NDIM, Float>> {
array_into_tensor(self, device, "a float", DType::is_float)
}
}
impl<Item, ArrayImpl, const NDIM: usize> RlstArrayIntoBurnTensor<Item, NDIM, Int>
for Array<ArrayImpl, NDIM>
where
Item: RlstAsTensorDtype + Element,
ArrayImpl: ValueArrayImpl<Item, NDIM>,
{
fn into_tensor_on(self, device: &Device) -> RlstResult<Tensor<NDIM, Int>> {
array_into_tensor(self, device, "an integer", |dtype| {
dtype.is_int() || dtype.is_uint()
})
}
}