#![allow(clippy::elidable_lifetime_names)]
#![allow(clippy::too_many_arguments)]
use super::shape::{Shape, Strides};
use smallvec::SmallVec;
pub struct TensorView<'a> {
data: &'a [f64],
shape: Shape,
strides: Strides,
storage_offset: usize,
}
impl<'a> TensorView<'a> {
#[must_use]
pub fn new(data: &'a [f64], shape: Shape, storage_offset: usize) -> Self {
let strides = shape.row_major_strides();
let max_idx = if shape.numel() > 0 {
strides.max_flat_index(&shape)
} else {
0
};
let required_len = storage_offset + max_idx + usize::from(shape.numel() > 0);
assert!(
data.len() >= required_len,
"Buffer too small for contiguous shape: need {} elements, got {}",
required_len,
data.len()
);
Self {
data,
shape,
strides,
storage_offset,
}
}
#[must_use]
pub fn new_default(data: &'a [f64], shape: Shape) -> Self {
Self::new(data, shape, 0)
}
#[must_use]
pub fn with_strides(
data: &'a [f64],
shape: Shape,
strides: Strides,
storage_offset: usize,
) -> Self {
assert_eq!(
shape.rank(),
strides.as_slice().len(),
"Rank mismatch between shape and strides"
);
let max_idx = if shape.numel() > 0 {
strides.max_flat_index(&shape)
} else {
0
};
let required_len = storage_offset + max_idx + usize::from(shape.numel() > 0);
assert!(
data.len() >= required_len,
"Buffer too small for strided layout: need space for index {} (required size {}), got buffer of size {}",
max_idx,
required_len,
data.len()
);
Self {
data,
shape,
strides,
storage_offset,
}
}
#[must_use]
pub fn with_strides_default(data: &'a [f64], shape: Shape, strides: Strides) -> Self {
Self::with_strides(data, shape, strides, 0)
}
#[must_use]
pub const fn shape(&self) -> &Shape {
&self.shape
}
#[must_use]
pub const fn strides(&self) -> &Strides {
&self.strides
}
#[must_use]
pub fn get(&self, idx: &[usize]) -> Option<f64> {
if idx.len() != self.shape.rank() {
return None;
}
for (&i, &d) in idx.iter().zip(self.shape.dims()) {
if i >= d {
return None;
}
}
let flat = self.strides.flat_index(idx);
self.data.get(self.storage_offset + flat).copied()
}
#[must_use]
pub unsafe fn get_unchecked(&self, idx: &[usize]) -> f64 {
let flat = self.strides.flat_index(idx);
unsafe { *self.data.get_unchecked(self.storage_offset + flat) }
}
#[must_use]
pub fn rank(&self) -> usize {
self.shape.rank()
}
#[must_use]
pub fn numel(&self) -> usize {
self.shape.numel()
}
#[must_use]
pub fn is_contiguous(&self) -> bool {
if self.shape.numel() <= 1 {
return true;
}
self.strides == self.shape.row_major_strides()
}
#[must_use]
pub const fn as_raw_slice(&self) -> &[f64] {
self.data
}
#[must_use]
pub fn as_slice(&self) -> &[f64] {
assert!(
self.is_contiguous(),
"View is not contiguous, cannot convert to slice."
);
&self.data[self.storage_offset..self.storage_offset + self.numel()]
}
#[must_use]
pub fn iter_elements(&self) -> ElementIter<'_> {
if self.is_contiguous() {
ElementIter::Contiguous(self.as_slice().iter())
} else {
let rank = self.shape.rank();
let idx = SmallVec::<[usize; 8]>::from_elem(0, rank);
let done = self.shape.dims().contains(&0);
ElementIter::Strided(StridedElementIter {
data: self.data,
strides: self.strides.clone(),
dims: self.shape.clone(),
idx,
done,
flat_offset: self.storage_offset,
elements_yielded: 0,
})
}
}
}
impl<'a, const N: usize> core::ops::Index<[usize; N]> for TensorView<'a> {
type Output = f64;
#[inline]
fn index(&self, index: [usize; N]) -> &Self::Output {
assert_eq!(N, self.shape.rank(), "Index rank mismatch");
for (&i, &d) in index.iter().zip(self.shape.dims()) {
assert!(i < d, "Index {i} out of bounds for dimension size {d}");
}
let flat = self.strides.flat_index(&index);
&self.data[self.storage_offset + flat]
}
}
impl<'a> core::ops::Index<&[usize]> for TensorView<'a> {
type Output = f64;
#[inline]
fn index(&self, index: &[usize]) -> &Self::Output {
assert_eq!(index.len(), self.shape.rank(), "Index rank mismatch");
for (&i, &d) in index.iter().zip(self.shape.dims()) {
assert!(i < d, "Index {i} out of bounds for dimension size {d}");
}
let flat = self.strides.flat_index(index);
&self.data[self.storage_offset + flat]
}
}
impl<'a> core::ops::Index<(usize,)> for TensorView<'a> {
type Output = f64;
#[inline]
fn index(&self, index: (usize,)) -> &Self::Output {
&self[[index.0]]
}
}
impl<'a> core::ops::Index<(usize, usize)> for TensorView<'a> {
type Output = f64;
#[inline]
fn index(&self, index: (usize, usize)) -> &Self::Output {
&self[[index.0, index.1]]
}
}
impl<'a> core::ops::Index<(usize, usize, usize)> for TensorView<'a> {
type Output = f64;
#[inline]
fn index(&self, index: (usize, usize, usize)) -> &Self::Output {
&self[[index.0, index.1, index.2]]
}
}
pub struct TensorViewMut<'a> {
data: &'a mut [f64],
shape: Shape,
strides: Strides,
storage_offset: usize,
}
impl<'a> TensorViewMut<'a> {
#[must_use]
pub fn new(data: &'a mut [f64], shape: Shape, storage_offset: usize) -> Self {
let strides = shape.row_major_strides();
let max_idx = if shape.numel() > 0 {
strides.max_flat_index(&shape)
} else {
0
};
let required_len = storage_offset + max_idx + usize::from(shape.numel() > 0);
assert!(
data.len() >= required_len,
"Buffer too small for shape and offset: need {} elements, got {}",
required_len,
data.len(),
);
Self {
data,
shape,
strides,
storage_offset,
}
}
#[must_use]
pub fn new_default(data: &'a mut [f64], shape: Shape) -> Self {
Self::new(data, shape, 0)
}
#[must_use]
pub const fn shape(&self) -> &Shape {
&self.shape
}
#[must_use]
pub const fn strides(&self) -> &Strides {
&self.strides
}
#[must_use]
pub fn is_contiguous(&self) -> bool {
if self.shape.numel() <= 1 {
return true;
}
self.strides == self.shape.row_major_strides()
}
pub fn get_mut(&mut self, idx: &[usize]) -> Option<&mut f64> {
if idx.len() != self.shape.rank() {
return None;
}
for (&i, &d) in idx.iter().zip(self.shape.dims()) {
if i >= d {
return None;
}
}
let flat = self.strides.flat_index(idx);
self.data.get_mut(self.storage_offset + flat)
}
#[must_use]
pub unsafe fn get_unchecked_mut(&mut self, idx: &[usize]) -> &mut f64 {
let flat = self.strides.flat_index(idx);
unsafe { self.data.get_unchecked_mut(self.storage_offset + flat) }
}
pub fn set(&mut self, idx: &[usize], val: f64) -> bool {
self.get_mut(idx).is_some_and(|slot| {
*slot = val;
true
})
}
#[must_use]
pub const fn as_raw_slice(&self) -> &[f64] {
self.data
}
pub const fn as_raw_slice_mut(&mut self) -> &mut [f64] {
self.data
}
#[must_use]
pub fn as_slice(&self) -> &[f64] {
assert!(
self.is_contiguous(),
"View is not contiguous, cannot convert to slice."
);
&self.data[self.storage_offset..self.storage_offset + self.shape.numel()]
}
pub fn as_slice_mut(&mut self) -> &mut [f64] {
assert!(
self.is_contiguous(),
"View is not contiguous, cannot convert to mutable slice."
);
let numel = self.shape.numel();
&mut self.data[self.storage_offset..self.storage_offset + numel]
}
#[must_use]
pub fn as_view(&self) -> TensorView<'_> {
TensorView {
data: self.data,
shape: self.shape.clone(),
strides: self.strides.clone(),
storage_offset: self.storage_offset,
}
}
}
impl<'a, const N: usize> core::ops::Index<[usize; N]> for TensorViewMut<'a> {
type Output = f64;
#[inline]
fn index(&self, index: [usize; N]) -> &Self::Output {
assert_eq!(N, self.shape.rank(), "Index rank mismatch");
for (&i, &d) in index.iter().zip(self.shape.dims()) {
assert!(i < d, "Index {i} out of bounds for dimension size {d}");
}
let flat = self.strides.flat_index(&index);
&self.data[self.storage_offset + flat]
}
}
impl<'a, const N: usize> core::ops::IndexMut<[usize; N]> for TensorViewMut<'a> {
#[inline]
fn index_mut(&mut self, index: [usize; N]) -> &mut Self::Output {
assert_eq!(N, self.shape.rank(), "Index rank mismatch");
for (&i, &d) in index.iter().zip(self.shape.dims()) {
assert!(i < d, "Index {i} out of bounds for dimension size {d}");
}
let flat = self.strides.flat_index(&index);
&mut self.data[self.storage_offset + flat]
}
}
impl<'a> core::ops::IndexMut<&[usize]> for TensorViewMut<'a> {
#[inline]
fn index_mut(&mut self, index: &[usize]) -> &mut Self::Output {
assert_eq!(index.len(), self.shape.rank(), "Index rank mismatch");
for (&i, &d) in index.iter().zip(self.shape.dims()) {
assert!(i < d, "Index {i} out of bounds for dimension size {d}");
}
let flat = self.strides.flat_index(index);
&mut self.data[self.storage_offset + flat]
}
}
impl<'a> core::ops::Index<&[usize]> for TensorViewMut<'a> {
type Output = f64;
#[inline]
fn index(&self, index: &[usize]) -> &Self::Output {
assert_eq!(index.len(), self.shape.rank(), "Index rank mismatch");
for (&i, &d) in index.iter().zip(self.shape.dims()) {
assert!(i < d, "Index {i} out of bounds for dimension size {d}");
}
let flat = self.strides.flat_index(index);
&self.data[self.storage_offset + flat]
}
}
impl<'a> core::ops::Index<(usize,)> for TensorViewMut<'a> {
type Output = f64;
#[inline]
fn index(&self, index: (usize,)) -> &Self::Output {
&self[[index.0]]
}
}
impl<'a> core::ops::Index<(usize, usize)> for TensorViewMut<'a> {
type Output = f64;
#[inline]
fn index(&self, index: (usize, usize)) -> &Self::Output {
&self[[index.0, index.1]]
}
}
impl<'a> core::ops::Index<(usize, usize, usize)> for TensorViewMut<'a> {
type Output = f64;
#[inline]
fn index(&self, index: (usize, usize, usize)) -> &Self::Output {
&self[[index.0, index.1, index.2]]
}
}
impl<'a> core::ops::IndexMut<(usize,)> for TensorViewMut<'a> {
#[inline]
fn index_mut(&mut self, index: (usize,)) -> &mut Self::Output {
&mut self[[index.0]]
}
}
impl<'a> core::ops::IndexMut<(usize, usize)> for TensorViewMut<'a> {
#[inline]
fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output {
&mut self[[index.0, index.1]]
}
}
impl<'a> core::ops::IndexMut<(usize, usize, usize)> for TensorViewMut<'a> {
#[inline]
fn index_mut(&mut self, index: (usize, usize, usize)) -> &mut Self::Output {
&mut self[[index.0, index.1, index.2]]
}
}
pub fn broadcast_elementwise<F>(
a: &TensorView<'_>,
b: &TensorView<'_>,
out: &mut TensorViewMut<'_>,
op: F,
) -> Result<(), String>
where
F: Fn(f64, f64) -> f64 + Copy,
{
let out_shape = a.shape().broadcast_output(b.shape()).ok_or_else(|| {
format!(
"Shapes {} and {} are not broadcast-compatible",
a.shape(),
b.shape(),
)
})?;
if out.shape() != &out_shape {
return Err(format!(
"Output shape {} doesn't match expected broadcast shape {}",
out.shape(),
out_shape,
));
}
let is_contiguous_fast_path = a.shape() == b.shape()
&& a.shape() == out.shape()
&& a.is_contiguous()
&& b.is_contiguous()
&& out.is_contiguous();
let a_slice = a.as_raw_slice();
let b_slice = b.as_raw_slice();
if is_contiguous_fast_path {
let out_slice = out.as_slice_mut();
let len = a.numel();
let a_sub = &a_slice[a.storage_offset..a.storage_offset + len];
let b_sub = &b_slice[b.storage_offset..b.storage_offset + len];
let out_sub = &mut out_slice[..len];
for i in 0..len {
out_sub[i] = op(a_sub[i], b_sub[i]);
}
return Ok(());
}
let rank = out_shape.rank();
let a_offset = rank.saturating_sub(a.rank());
let b_offset = rank.saturating_sub(b.rank());
let a_strides = a.strides().as_slice();
let b_strides = b.strides().as_slice();
let mut a_strides_padded = SmallVec::<[usize; 8]>::from_elem(0, rank);
let mut b_strides_padded = SmallVec::<[usize; 8]>::from_elem(0, rank);
let a_dims = a.shape().dims();
for i in a_offset..rank {
if a_dims[i - a_offset] > 1 {
a_strides_padded[i] = a_strides[i - a_offset];
}
}
let b_dims = b.shape().dims();
for i in b_offset..rank {
if b_dims[i - b_offset] > 1 {
b_strides_padded[i] = b_strides[i - b_offset];
}
}
let out_strides_cloned = out.strides().clone();
let output_iter = OutputFlatIndexIterator::new(
out_shape,
out_strides_cloned.clone(),
a_strides_padded.clone(),
b_strides_padded.clone(),
);
let a_step = if rank > 0 {
a_strides_padded[rank - 1]
} else {
0
};
let b_step = if rank > 0 {
b_strides_padded[rank - 1]
} else {
0
};
let out_step = if rank > 0 {
out_strides_cloned.as_slice()[rank - 1]
} else {
0
};
let a_ptr = unsafe { a_slice.as_ptr().add(a.storage_offset) };
let b_ptr = unsafe { b_slice.as_ptr().add(b.storage_offset) };
let out_ptr = unsafe { out.as_raw_slice_mut().as_mut_ptr().add(out.storage_offset) };
debug_assert!(
a_ptr.cast::<()>() != out_ptr as *const (),
"Tensor A and Output alias!"
);
debug_assert!(
b_ptr.cast::<()>() != out_ptr as *const (),
"Tensor B and Output alias!"
);
unsafe {
broadcast_elementwise_kernel(
output_iter,
a_ptr,
b_ptr,
out_ptr,
a_step,
b_step,
out_step,
op,
);
}
Ok(())
}
#[inline(always)]
unsafe fn process_row_contiguous<F>(
a_ptr: *const f64,
b_ptr: *const f64,
out_ptr: *mut f64,
mut current_a: usize,
mut current_b: usize,
mut current_out: usize,
row_len: usize,
op: F,
) where
F: Fn(f64, f64) -> f64,
{
for _ in 0..row_len {
let va = unsafe { *a_ptr.add(current_a) };
let vb = unsafe { *b_ptr.add(current_b) };
unsafe { *out_ptr.add(current_out) = op(va, vb) };
current_a += 1;
current_b += 1;
current_out += 1;
}
}
#[inline(always)]
#[allow(clippy::similar_names)]
unsafe fn process_row_broadcast_a<OpF>(
a_ptr: *const f64,
b_ptr: *const f64,
out_ptr: *mut f64,
current_a_fixed: usize,
mut current_b: usize,
mut current_out: usize,
b_step: usize,
out_step: usize,
row_len: usize,
op: OpF,
) where
OpF: Fn(f64, f64) -> f64,
{
let va = unsafe { *a_ptr.add(current_a_fixed) };
for _ in 0..row_len {
let vb = unsafe { *b_ptr.add(current_b) };
unsafe { *out_ptr.add(current_out) = op(va, vb) };
current_b += b_step;
current_out += out_step;
}
}
#[inline(always)]
#[allow(clippy::similar_names)]
unsafe fn process_row_broadcast_b<OpF>(
a_ptr: *const f64,
b_ptr: *const f64,
out_ptr: *mut f64,
mut current_a: usize,
current_b_fixed: usize,
mut current_out: usize,
a_step: usize,
out_step: usize,
row_len: usize,
op: OpF,
) where
OpF: Fn(f64, f64) -> f64,
{
let vb = unsafe { *b_ptr.add(current_b_fixed) };
for _ in 0..row_len {
let va = unsafe { *a_ptr.add(current_a) };
unsafe { *out_ptr.add(current_out) = op(va, vb) };
current_a += a_step;
current_out += out_step;
}
}
#[inline(always)]
#[allow(clippy::similar_names)]
unsafe fn process_row_broadcast_ab<OpF>(
a_ptr: *const f64,
b_ptr: *const f64,
out_ptr: *mut f64,
current_a_fixed: usize,
current_b_fixed: usize,
mut current_out: usize,
out_step: usize,
row_len: usize,
op: OpF,
) where
OpF: Fn(f64, f64) -> f64,
{
let va = unsafe { *a_ptr.add(current_a_fixed) };
let vb = unsafe { *b_ptr.add(current_b_fixed) };
let result = op(va, vb);
for _ in 0..row_len {
unsafe { *out_ptr.add(current_out) = result };
current_out += out_step;
}
}
#[inline(always)]
unsafe fn process_row_general<F>(
a_ptr: *const f64,
b_ptr: *const f64,
out_ptr: *mut f64,
mut current_a: usize,
mut current_b: usize,
mut current_out: usize,
a_step: usize,
b_step: usize,
out_step: usize,
row_len: usize,
op: F,
) where
F: Fn(f64, f64) -> f64,
{
for _ in 0..row_len {
let va = unsafe { *a_ptr.add(current_a) };
let vb = unsafe { *b_ptr.add(current_b) };
unsafe { *out_ptr.add(current_out) = op(va, vb) };
current_a += a_step;
current_b += b_step;
current_out += out_step;
}
}
#[inline(always)]
unsafe fn broadcast_elementwise_kernel<F>(
output_iter: OutputFlatIndexIterator,
a_ptr: *const f64,
b_ptr: *const f64,
out_ptr: *mut f64,
a_step: usize,
b_step: usize,
out_step: usize,
op: F,
) where
F: Fn(f64, f64) -> f64 + Copy,
{
for (a_flat, b_flat, out_flat, row_len) in output_iter {
if a_step == 1 && b_step == 1 && out_step == 1 {
unsafe {
process_row_contiguous(
a_ptr, b_ptr, out_ptr, a_flat, b_flat, out_flat, row_len, op,
);
}
} else if a_step == 0 && b_step == 0 {
unsafe {
process_row_broadcast_ab(
a_ptr, b_ptr, out_ptr, a_flat, b_flat, out_flat, out_step, row_len, op,
);
}
} else if a_step == 0 {
unsafe {
process_row_broadcast_a(
a_ptr, b_ptr, out_ptr, a_flat, b_flat, out_flat, b_step, out_step, row_len, op,
);
}
} else if b_step == 0 {
unsafe {
process_row_broadcast_b(
a_ptr, b_ptr, out_ptr, a_flat, b_flat, out_flat, a_step, out_step, row_len, op,
);
}
} else {
unsafe {
process_row_general(
a_ptr, b_ptr, out_ptr, a_flat, b_flat, out_flat, a_step, b_step, out_step,
row_len, op,
);
}
}
}
}
#[allow(clippy::large_enum_variant)]
pub enum ElementIter<'a> {
Contiguous(std::slice::Iter<'a, f64>),
Strided(StridedElementIter<'a>),
}
impl<'a> Iterator for ElementIter<'a> {
type Item = f64;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Contiguous(it) => it.next().copied(),
Self::Strided(it) => it.next(),
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Self::Contiguous(it) => it.size_hint(),
Self::Strided(it) => it.size_hint(),
}
}
}
pub struct StridedElementIter<'a> {
data: &'a [f64],
strides: Strides,
dims: Shape,
idx: SmallVec<[usize; 8]>,
done: bool,
flat_offset: usize,
elements_yielded: usize,
}
impl<'a> Iterator for StridedElementIter<'a> {
type Item = f64;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let val = unsafe { *self.data.get_unchecked(self.flat_offset) };
self.elements_yielded += 1;
let rank = self.dims.rank();
let dims = self.dims.dims();
let strides_slice = self.strides.as_slice();
let mut carry = true;
for i in (0..rank).rev() {
self.idx[i] += 1;
self.flat_offset += strides_slice[i];
if self.idx[i] >= dims[i] {
let steps_taken = self.idx[i];
self.flat_offset -= strides_slice[i] * steps_taken;
self.idx[i] = 0;
} else {
carry = false;
break;
}
}
if carry {
self.done = true;
}
Some(val)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if self.done {
(0, Some(0))
} else {
let total_numel = self.dims.numel();
let remaining = total_numel.saturating_sub(self.elements_yielded);
(remaining, Some(remaining))
}
}
}
pub struct OutputFlatIndexIterator {
dims: Shape,
strides: Strides,
idx: SmallVec<[usize; 8]>,
done: bool,
a_strides_padded: SmallVec<[usize; 8]>,
b_strides_padded: SmallVec<[usize; 8]>,
current_a: usize,
current_b: usize,
current_out: usize,
}
impl OutputFlatIndexIterator {
#[must_use]
pub fn new(
output_shape: Shape,
output_strides: Strides,
a_strides_padded: SmallVec<[usize; 8]>,
b_strides_padded: SmallVec<[usize; 8]>,
) -> Self {
let rank = output_shape.rank();
let idx = SmallVec::<[usize; 8]>::from_elem(0, rank);
let done = output_shape.numel() == 0;
let mut current_a = 0;
let mut current_b = 0;
let mut current_out = 0;
if rank > 0 && !done {
let out_strides_slice = output_strides.as_slice();
for i in 0..rank {
current_a += idx[i] * a_strides_padded[i];
current_b += idx[i] * b_strides_padded[i];
current_out += idx[i] * out_strides_slice[i];
}
}
Self {
dims: output_shape,
strides: output_strides,
idx,
done,
a_strides_padded,
b_strides_padded,
current_a,
current_b,
current_out,
}
}
}
impl Iterator for OutputFlatIndexIterator {
type Item = (usize, usize, usize, usize);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let rank = self.dims.rank();
let dims = self.dims.dims();
let out_strides_slice = self.strides.as_slice();
if rank == 0 {
self.done = true;
return Some((0, 0, 0, 1));
}
let innermost_dim_idx = rank - 1;
let row_len = dims[innermost_dim_idx];
let res = (self.current_a, self.current_b, self.current_out, row_len);
let mut carry = true;
let mut i = rank;
while carry && i > 0 {
i -= 1;
if i == innermost_dim_idx {
self.idx[i] += row_len;
self.current_a += self.a_strides_padded[i] * row_len;
self.current_b += self.b_strides_padded[i] * row_len;
self.current_out += out_strides_slice[i] * row_len;
} else {
self.idx[i] += 1;
self.current_a += self.a_strides_padded[i];
self.current_b += self.b_strides_padded[i];
self.current_out += out_strides_slice[i];
}
if self.idx[i] >= dims[i] {
let steps_taken = self.idx[i];
self.current_a -= self.a_strides_padded[i] * steps_taken;
self.current_b -= self.b_strides_padded[i] * steps_taken;
self.current_out -= out_strides_slice[i] * steps_taken;
self.idx[i] = 0;
} else {
carry = false;
}
}
if carry {
self.done = true;
}
Some(res)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tensor_view_indexing() {
let data = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let view = TensorView::new_default(&data, Shape::matrix(2, 3));
assert_eq!(view.get(&[0, 0]), Some(0.0));
assert_eq!(view.get(&[0, 2]), Some(2.0));
assert_eq!(view.get(&[1, 1]), Some(4.0));
assert_eq!(view.get(&[2, 0]), None);
assert_eq!(view[[0, 0]], 0.0);
assert_eq!(view[[0, 2]], 2.0);
assert_eq!(view[[1, 1]], 4.0);
}
#[test]
fn test_tensor_view_mut_indexing() {
let mut data = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let mut view_mut = TensorViewMut::new_default(&mut data, Shape::matrix(2, 3));
assert_eq!(view_mut[[1, 1]], 4.0);
view_mut[[1, 1]] = 42.0;
assert_eq!(view_mut[[1, 1]], 42.0);
assert!(view_mut.set(&[0, 1], 99.0));
assert_eq!(view_mut[[0, 1]], 99.0);
}
}