use core::marker::PhantomData;
use super::{TensorError, RowMajor, row_major_strides, compute_offset};
pub struct TensorMut<'a, T, const N: usize, Layout = RowMajor> {
data: &'a mut [T],
shape: [usize; N],
strides: [usize; N],
_layout: PhantomData<Layout>,
}
impl<'a, T, const N: usize> TensorMut<'a, T, N, RowMajor> {
#[inline]
pub fn new(data: &'a mut [T], shape: [usize; N]) -> Result<Self, TensorError> {
let elem_count = shape.iter().product::<usize>();
if elem_count > data.len() {
return Err(TensorError::ShapeMismatch);
}
let strides = row_major_strides(shape);
Ok(Self { data, shape, strides, _layout: PhantomData })
}
}
impl<'a, T, const N: usize, Layout> TensorMut<'a, T, N, Layout> {
#[inline]
pub fn with_strides(
data: &'a mut [T],
shape: [usize; N],
strides: [usize; N],
) -> Result<Self, TensorError> {
let elem_count = shape.iter().product::<usize>();
if elem_count > data.len() {
return Err(TensorError::ShapeMismatch);
}
Ok(Self { data, shape, strides, _layout: PhantomData })
}
#[inline(always)]
pub fn shape(&self) -> [usize; N] { self.shape }
#[inline(always)]
pub fn strides(&self) -> [usize; N] { self.strides }
#[inline]
pub fn num_elements(&self) -> usize { self.shape.iter().product() }
#[inline]
pub fn is_contiguous(&self) -> bool {
let expected = row_major_strides(self.shape);
self.strides == expected
}
#[inline]
pub fn get_mut(&mut self, idx: [usize; N]) -> Result<&mut T, TensorError> {
for i in 0..N {
if idx[i] >= self.shape[i] {
return Err(TensorError::IndexOutOfBounds);
}
}
let offset = compute_offset(&idx, &self.strides);
Ok(&mut self.data[offset])
}
#[inline(always)]
pub unsafe fn get_unchecked_mut(&mut self, idx: [usize; N]) -> &mut T {
let offset = compute_offset(&idx, &self.strides);
self.data.get_unchecked_mut(offset)
}
#[inline(always)]
pub fn as_mut_slice(&mut self) -> &mut [T] { self.data }
#[inline(always)]
pub fn as_slice(&self) -> &[T] { self.data }
#[inline]
pub fn map_inplace(&mut self, mut f: impl FnMut(T) -> T)
where
T: Copy,
{
for x in self.data.iter_mut() {
*x = f(*x);
}
}
#[inline]
pub fn fill(&mut self, value: T)
where
T: Copy,
{
for x in self.data.iter_mut() {
*x = value;
}
}
}
impl<'a, T, Layout> TensorMut<'a, T, 2, Layout> {
#[inline]
pub fn row_mut(&mut self, i: usize) -> Result<&mut [T], TensorError> {
if !self.is_contiguous() {
return Err(TensorError::NotContiguous);
}
if i >= self.shape[0] {
return Err(TensorError::IndexOutOfBounds);
}
let ncols = self.shape[1];
let start = i * ncols;
Ok(&mut self.data[start..start + ncols])
}
#[inline]
pub fn iter_rows_mut(&mut self) -> Result<core::slice::ChunksMut<'_, T>, TensorError> {
if !self.is_contiguous() {
return Err(TensorError::NotContiguous);
}
Ok(self.data.chunks_mut(self.shape[1]))
}
}
impl<'a, T, Layout> TensorMut<'a, T, 3, Layout> {
#[inline]
pub fn matrix_mut(&mut self, b: usize) -> Result<&mut [T], TensorError> {
if !self.is_contiguous() {
return Err(TensorError::NotContiguous);
}
if b >= self.shape[0] {
return Err(TensorError::IndexOutOfBounds);
}
let rows = self.shape[1];
let cols = self.shape[2];
let start = b * rows * cols;
Ok(&mut self.data[start..start + rows * cols])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tensor_mut_new_and_fill() {
let mut data = vec![0i32; 12];
let mut t = TensorMut::<i32, 2>::new(&mut data, [3, 4]).unwrap();
t.fill(7);
assert!(t.as_slice().iter().all(|&x| x == 7));
}
#[test]
fn test_tensor_mut_get_mut() {
let mut data: Vec<i32> = (0..12).collect();
let mut t = TensorMut::<i32, 2>::new(&mut data, [3, 4]).unwrap();
*t.get_mut([1, 2]).unwrap() = 99;
assert_eq!(data[6], 99);
}
#[test]
fn test_tensor_mut_map_inplace() {
let mut data: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
let mut t = TensorMut::<f32, 1>::new(&mut data, [4]).unwrap();
t.map_inplace(|x| x * 2.0);
assert_eq!(data, vec![2.0, 4.0, 6.0, 8.0]);
}
#[test]
fn test_row_mut() {
let mut data: Vec<i32> = (0..9).collect();
let mut t = TensorMut::<i32, 2>::new(&mut data, [3, 3]).unwrap();
let row = t.row_mut(1).unwrap();
row.iter_mut().for_each(|x| *x += 100);
assert_eq!(data[3], 103);
assert_eq!(data[4], 104);
assert_eq!(data[5], 105);
}
}