use std::fmt::Debug;
use scirs2_core::numeric::{Float, NumCast};
use scirs2_core::error::ErrorContext;
use crate::error::{OptimError, Result};
use super::buffer::TPUBuffer;
use super::types::MemoryLayout;
pub(super) const ENERGY_PER_BYTE_NANOJOULE: f64 = 0.05;
#[derive(Debug, Clone)]
pub(super) struct RefTensor {
pub(super) shape: Vec<usize>,
pub(super) data: Vec<f64>,
}
pub(super) fn encode_ref_tensors(tensors: &[RefTensor]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&(tensors.len() as u32).to_le_bytes());
for tensor in tensors {
out.extend_from_slice(&(tensor.shape.len() as u32).to_le_bytes());
for &dim in &tensor.shape {
out.extend_from_slice(&(dim as u64).to_le_bytes());
}
out.extend_from_slice(&(tensor.data.len() as u64).to_le_bytes());
for &value in &tensor.data {
out.extend_from_slice(&value.to_le_bytes());
}
}
out
}
struct ByteCursor<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> ByteCursor<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self { bytes, pos: 0 }
}
fn take(&mut self, n: usize) -> Result<&'a [u8]> {
let end = self.pos.checked_add(n).ok_or_else(|| {
OptimError::InvalidInput(ErrorContext::new(
"reference tensor payload length overflow".to_string(),
))
})?;
if end > self.bytes.len() {
return Err(OptimError::InvalidInput(ErrorContext::new(format!(
"truncated reference tensor payload: need {} bytes, have {}",
end,
self.bytes.len()
))));
}
let slice = &self.bytes[self.pos..end];
self.pos = end;
Ok(slice)
}
fn read_u32(&mut self) -> Result<u32> {
let arr: [u8; 4] = self.take(4)?.try_into().map_err(|_| {
OptimError::InvalidInput(ErrorContext::new(
"invalid u32 in reference tensor payload".to_string(),
))
})?;
Ok(u32::from_le_bytes(arr))
}
fn read_u64(&mut self) -> Result<u64> {
let arr: [u8; 8] = self.take(8)?.try_into().map_err(|_| {
OptimError::InvalidInput(ErrorContext::new(
"invalid u64 in reference tensor payload".to_string(),
))
})?;
Ok(u64::from_le_bytes(arr))
}
fn read_f64(&mut self) -> Result<f64> {
let arr: [u8; 8] = self.take(8)?.try_into().map_err(|_| {
OptimError::InvalidInput(ErrorContext::new(
"invalid f64 in reference tensor payload".to_string(),
))
})?;
Ok(f64::from_le_bytes(arr))
}
}
pub(super) fn decode_ref_tensors(bytes: &[u8]) -> Result<Vec<RefTensor>> {
let mut cursor = ByteCursor::new(bytes);
let tensor_count = cursor.read_u32()? as usize;
let mut tensors = Vec::with_capacity(tensor_count.min(bytes.len()));
for _ in 0..tensor_count {
let rank = cursor.read_u32()? as usize;
let mut shape = Vec::with_capacity(rank.min(bytes.len()));
for _ in 0..rank {
shape.push(cursor.read_u64()? as usize);
}
let len = cursor.read_u64()? as usize;
let mut data = Vec::with_capacity(len.min(bytes.len()));
for _ in 0..len {
data.push(cursor.read_f64()?);
}
tensors.push(RefTensor { shape, data });
}
Ok(tensors)
}
pub(super) fn serialize_tpu_buffers<T: Float + Debug + Send + Sync + 'static>(
buffers: &[TPUBuffer<T>],
) -> Result<Vec<u8>> {
let mut tensors = Vec::with_capacity(buffers.len());
for buffer in buffers {
let mut data = Vec::with_capacity(buffer.data.len());
for &value in &buffer.data {
let as_f64 = <f64 as NumCast>::from(value).ok_or_else(|| {
OptimError::TypeError(ErrorContext::new(
"failed to convert TPU buffer element to f64".to_string(),
))
})?;
data.push(as_f64);
}
tensors.push(RefTensor {
shape: buffer.shape.clone(),
data,
});
}
Ok(encode_ref_tensors(&tensors))
}
pub(super) fn deserialize_tpu_buffers<T: Float + Debug + Send + Sync + 'static>(
bytes: &[u8],
) -> Result<Vec<TPUBuffer<T>>> {
let tensors = decode_ref_tensors(bytes)?;
let mut buffers = Vec::with_capacity(tensors.len());
for tensor in tensors {
let mut data = Vec::with_capacity(tensor.data.len());
for value in tensor.data {
let typed = <T as NumCast>::from(value).ok_or_else(|| {
OptimError::TypeError(ErrorContext::new(
"failed to convert reference tensor element to target type".to_string(),
))
})?;
data.push(typed);
}
buffers.push(TPUBuffer::new(data, tensor.shape, MemoryLayout::RowMajor));
}
Ok(buffers)
}