use crate::{
RT, Tensor, ZyxError,
dtype::DType,
shape::{Dim, into_axis},
tensor::Axis,
};
use std::ops::{Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo};
impl<I> std::ops::Index<I> for Tensor {
type Output = Tensor;
fn index(&self, _index: I) -> &Self::Output {
panic!(
"Tensor does not support indexing with `[]` because rust only allows indexing on referece types. \
Use `.slice(...)` instead, which supports ranges, integers, and tuples. \
Example: tensor.slice((0..3, -1))"
);
}
}
impl Tensor {
pub fn slice(&self, index: impl IntoIndex) -> Result<Tensor, ZyxError> {
let shape = self.resolve_shape();
let rank = shape.len();
let mut squeeze_axes: Vec<Axis> = Vec::new();
let index = index.into_index();
let padding_len = index.len();
if rank < padding_len {
return Err(ZyxError::shape_error(format!("Slice with {padding_len} indices, but tensor has rank {rank}").into()));
}
let padding = index
.zip(shape)
.enumerate()
.map(|(axis, (dim_index, dim_size))| {
let dim_size = dim_size as i64;
match dim_index {
DimIndex::Range { start, end } => {
let s = if start < 0 { (start + dim_size).max(0) } else { start };
let s = s.min(dim_size);
let e = if end > dim_size {
dim_size
} else if end < 0 {
(end + dim_size).max(0)
} else {
end
};
let e = e.min(dim_size).max(0);
if e < s {
return Err(ZyxError::shape_error(
format!("Slice range end {e} is less than start {s} for dimension {axis}").into(),
));
}
Ok((-s, -(dim_size - e)))
}
DimIndex::Index(i) => {
squeeze_axes.push(axis as i32);
let i = if i < 0 { i + dim_size } else { i };
if i < 0 || i >= dim_size {
return Err(ZyxError::shape_error(
format!("Index {i} out of bounds for dimension {axis} of size {dim_size}").into(),
));
}
Ok((-i, -(dim_size - i - 1)))
}
DimIndex::RangeFull => Ok((0i64, 0i64)),
DimIndex::RangeFrom { start } => {
let s = if start < 0 { (start + dim_size).max(0) } else { start };
let s = s.min(dim_size);
Ok((-s, 0i64))
}
DimIndex::RangeTo { end } => {
let e = if end > dim_size {
dim_size
} else if end < 0 {
(end + dim_size).max(0)
} else {
end
};
let e = e.min(dim_size).max(0);
Ok((0i64, -(dim_size - e)))
}
}
})
.collect::<Result<Vec<_>, _>>()?;
let mut result = self.pad_zeros(padding)?;
result = result.squeeze(squeeze_axes);
Ok(result)
}
#[allow(clippy::missing_panics_doc)]
pub fn rslice(&self, index: impl IntoIndex) -> Result<Tensor, ZyxError> {
let shape = self.resolve_shape();
let rank = shape.len();
let mut squeeze_axes: Vec<Axis> = Vec::new();
let index = index.into_index();
let padding_len = index.len();
if padding_len > rank {
return Err(ZyxError::shape_error(format!("Index length {padding_len} > rank {rank}").into()));
}
let padding = index
.zip(shape.into_iter().rev())
.enumerate()
.map(|(axis, (dim_index, dim_size))| {
let dim_size = dim_size as i64;
match dim_index {
DimIndex::Range { start, end } => {
let s = if start < 0 { (start + dim_size).max(0) } else { start };
let s = s.min(dim_size);
let e = if end > dim_size {
dim_size
} else if end < 0 {
(end + dim_size).max(0)
} else {
end
};
let e = e.min(dim_size).max(0);
if e < s {
return Err(ZyxError::shape_error(
format!("Slice range end {e} is less than start {s} for dimension {axis}").into(),
));
}
Ok((-s, -(dim_size - e)))
}
DimIndex::Index(i) => {
squeeze_axes.push(axis as i32);
let i = if i < 0 { i + dim_size } else { i };
if i < 0 || i >= dim_size {
return Err(ZyxError::shape_error(
format!("Index {i} out of bounds for dimension {axis} of size {dim_size}").into(),
));
}
Ok((-i, -(dim_size - i - 1)))
}
DimIndex::RangeFull => Ok((0i64, 0i64)),
DimIndex::RangeFrom { start } => {
let s = if start < 0 { (start + dim_size).max(0) } else { start };
let s = s.min(dim_size);
Ok((-s, 0i64))
}
DimIndex::RangeTo { end } => {
let e = if end > dim_size {
dim_size
} else if end < 0 {
(end + dim_size).max(0)
} else {
end
};
let e = e.min(dim_size).max(0);
Ok((0i64, -(dim_size - e)))
}
}
})
.collect::<Result<Vec<_>, _>>()?;
let padding = padding.into_iter().chain(std::iter::repeat_n((0i64, 0i64), rank - padding_len));
let mut padding_vec: Vec<(i64, i64)> = padding.into_iter().collect();
padding_vec.reverse();
let mut result = self.pad_zeros(padding_vec)?;
result = result.squeeze(squeeze_axes);
Ok(result)
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn diagonal(&self) -> Tensor {
let n = *self.resolve_shape().last().expect("Shape in invalid state. Internal bug.");
self.flatten(..)
.unwrap()
.rpad_zeros([(0i64, i64::try_from(n).unwrap())])
.unwrap()
.reshape([n, n + 1])
.unwrap()
.slice((.., 0))
.unwrap()
.flatten(..)
.unwrap()
}
#[allow(clippy::missing_panics_doc)]
pub fn narrow(&self, axis: Axis, start: impl Into<Tensor>, length: impl Into<Tensor>) -> Result<Tensor, ZyxError> {
let rank = self.rank() as usize;
let axis = into_axis(axis, rank)?;
let start = start.into();
let length = length.into();
for (what, tid_dtype) in [("start", start.dtype()), ("length", length.dtype())] {
if tid_dtype != DType::I64 {
return Err(ZyxError::shape_error(
format!("narrow: {what} must be I64 (IDX_T), got {tid_dtype:?} — cast the tensor to I64 at creation").into(),
));
}
}
let mut rt = RT.lock();
let dim = rt.shape(self.id).get(axis as usize).copied().and_then(|d| rt.resolve_symbolic(d));
let st = rt.resolve_symbolic(start.id);
let ln = rt.resolve_symbolic(length.id);
if let (Some(dim), Some(st), Some(ln)) = (dim, st, ln) {
match (dim.as_dim(), st.as_dim(), ln.as_dim()) {
(Some(dim), Some(st), Some(ln)) => {
if st.checked_add(ln).is_none_or(|end| end > dim) {
return Err(ZyxError::shape_error(
format!("narrow: out of bounds: start {st} + length {ln} exceeds dim {dim} on axis {axis}").into(),
));
}
}
_ => {
return Err(ZyxError::shape_error(
format!("narrow: negative bound: dim {dim:?}, start {st:?}, length {ln:?} on axis {axis}").into(),
));
}
}
}
let id = rt.narrow(self.id, axis, start.id, length.id);
Ok(Tensor { id })
}
pub fn gather(&self, axis: Axis, indices: impl Into<Tensor>) -> Result<Tensor, ZyxError> {
let indices = indices.into();
let shape = self.resolve_shape();
let index_shape = indices.resolve_shape();
let dim = into_axis(axis, shape.len())?;
if shape.len() != index_shape.len() {
return Err(ZyxError::shape_error(
format!("self.rank({}) != indices.rank({})", shape.len(), index_shape.len()).into(),
));
}
for (d, (&s, &i)) in shape.iter().zip(index_shape.iter()).enumerate() {
if d != dim && s < i {
return Err(ZyxError::shape_error(
format!("Shape mismatch at dimension {d}: self.shape[{d}] = {s} < indices.shape[{d}] = {i}").into(),
));
}
}
let dim_size = shape[dim];
let is_negative = indices.cmplt(0)?.cast(indices.dtype());
let indices = indices + is_negative * dim_size;
let one_hot = indices.unsqueeze(-1)?.one_hot_along_dim(dim_size, -1)?;
let mut padding = Vec::new();
for d in (0..index_shape.len()).rev() {
if d == dim {
padding.push((0i64, 0i64));
} else {
padding.push((0i64, -(shape[d] as i64 - index_shape[d] as i64)));
}
}
let x = self.rpad_zeros(padding)?.unsqueeze(-1)?.transpose(-1, dim as i32)?;
let result = one_hot.mul(&x).sum_dtype([-1], self.dtype())?;
Ok(result)
}
pub fn scatter(&self, axis: Axis, indices: impl Into<Tensor>, src: impl Into<Tensor>) -> Result<Tensor, ZyxError> {
let indices = indices.into();
let src = src.into();
let shape = self.resolve_shape();
let index_shape = indices.resolve_shape();
let dim = into_axis(axis, shape.len())?;
let dim_size = shape[dim];
if shape.len() != index_shape.len() {
return Err(ZyxError::shape_error(
format!("self.rank({}) != indices.rank({})", shape.len(), index_shape.len()).into(),
));
}
if index_shape != src.resolve_shape() {
return Err(ZyxError::shape_error(
format!("indices shape {:?} != src shape {:?}", index_shape, src.resolve_shape()).into(),
));
}
for (d, (&s, &i)) in shape.iter().zip(index_shape.iter()).enumerate() {
if d != dim && s < i {
return Err(ZyxError::shape_error(
format!("Shape mismatch at dimension {d}: self.shape[{d}] = {s} < indices.shape[{d}] = {i}").into(),
));
}
}
let is_negative = indices.cmplt(0)?.cast(indices.dtype());
let indices = indices + is_negative.mul(dim_size as i32);
let one_hot = indices.unsqueeze(-1)?.one_hot_along_dim(dim_size, -1)?;
let contrib = one_hot.mul(&src.unsqueeze(-1)?);
let contrib = contrib.transpose(-1, dim as i32)?;
let rank = self.rank() as usize;
let mut padding = Vec::new();
for d in (0..=rank).rev() {
if d == dim || d == rank {
padding.push((0i64, 0i64));
} else {
padding.push((0i64, (shape[d] - index_shape[d]) as i64));
}
}
let contrib = contrib.rpad_zeros(padding)?;
let result = contrib.sum_dtype([-1], self.dtype())? + self;
Ok(result)
}
pub fn index_select(&self, dim: Axis, index: impl Into<Tensor>) -> Result<Tensor, ZyxError> {
let index = index.into();
let mut shape = self.resolve_shape();
let rank = shape.len();
let dim = into_axis(dim, rank)?;
shape[dim] = index.resolve_shape()[0];
let mut view_shape: Vec<Dim> = vec![1; rank];
view_shape[dim] = index.resolve_shape()[0];
let index_expanded = index.reshape(view_shape)?.expand(shape)?;
self.gather(dim as Axis, index_expanded)
}
}
#[derive(Clone, Debug)]
pub enum DimIndex {
Index(i64),
Range { start: i64, end: i64 },
RangeFrom { start: i64 },
RangeTo { end: i64 },
RangeFull,
}
pub trait IntoIndex {
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator;
}
impl From<i64> for DimIndex {
fn from(val: i64) -> DimIndex {
DimIndex::Index(val)
}
}
impl From<i32> for DimIndex {
fn from(val: i32) -> DimIndex {
DimIndex::Index(i64::from(val))
}
}
impl From<usize> for DimIndex {
fn from(val: usize) -> DimIndex {
DimIndex::Index(val as i64)
}
}
impl From<u64> for DimIndex {
fn from(val: u64) -> DimIndex {
DimIndex::Index(val as i64)
}
}
impl From<Range<i64>> for DimIndex {
fn from(val: Range<i64>) -> DimIndex {
DimIndex::Range { start: val.start, end: val.end }
}
}
impl From<Range<i32>> for DimIndex {
fn from(val: Range<i32>) -> DimIndex {
DimIndex::Range { start: i64::from(val.start), end: i64::from(val.end) }
}
}
impl From<Range<usize>> for DimIndex {
fn from(val: Range<usize>) -> DimIndex {
DimIndex::Range { start: val.start as i64, end: val.end as i64 }
}
}
impl From<Range<u64>> for DimIndex {
fn from(val: Range<u64>) -> DimIndex {
DimIndex::Range { start: val.start as i64, end: val.end as i64 }
}
}
impl From<RangeInclusive<i64>> for DimIndex {
fn from(val: RangeInclusive<i64>) -> DimIndex {
DimIndex::Range { start: *val.start(), end: val.end() + 1 }
}
}
impl From<RangeInclusive<i32>> for DimIndex {
fn from(val: RangeInclusive<i32>) -> DimIndex {
DimIndex::Range { start: i64::from(*val.start()), end: i64::from(*val.end()) + 1 }
}
}
impl From<RangeInclusive<usize>> for DimIndex {
fn from(val: RangeInclusive<usize>) -> DimIndex {
DimIndex::Range { start: *val.start() as i64, end: *val.end() as i64 + 1 }
}
}
impl From<RangeInclusive<u64>> for DimIndex {
fn from(val: RangeInclusive<u64>) -> DimIndex {
DimIndex::Range { start: *val.start() as i64, end: *val.end() as i64 + 1 }
}
}
impl From<RangeFrom<i64>> for DimIndex {
fn from(val: RangeFrom<i64>) -> DimIndex {
DimIndex::RangeFrom { start: val.start }
}
}
impl From<RangeFrom<i32>> for DimIndex {
fn from(val: RangeFrom<i32>) -> DimIndex {
DimIndex::RangeFrom { start: i64::from(val.start) }
}
}
impl From<RangeFrom<usize>> for DimIndex {
fn from(val: RangeFrom<usize>) -> DimIndex {
DimIndex::RangeFrom { start: val.start as i64 }
}
}
impl From<RangeFrom<u64>> for DimIndex {
fn from(val: RangeFrom<u64>) -> DimIndex {
DimIndex::RangeFrom { start: val.start as i64 }
}
}
impl From<RangeTo<i64>> for DimIndex {
fn from(val: RangeTo<i64>) -> DimIndex {
DimIndex::RangeTo { end: val.end }
}
}
impl From<RangeTo<i32>> for DimIndex {
fn from(val: RangeTo<i32>) -> DimIndex {
DimIndex::RangeTo { end: i64::from(val.end) }
}
}
impl From<RangeTo<usize>> for DimIndex {
fn from(val: RangeTo<usize>) -> DimIndex {
DimIndex::RangeTo { end: val.end as i64 }
}
}
impl From<RangeTo<u64>> for DimIndex {
fn from(val: RangeTo<u64>) -> DimIndex {
DimIndex::RangeTo { end: val.end as i64 }
}
}
impl From<Range<Tensor>> for DimIndex {
fn from(val: Range<Tensor>) -> DimIndex {
DimIndex::Range { start: val.start.item::<i64>(), end: val.end.item::<i64>() }
}
}
impl From<RangeInclusive<Tensor>> for DimIndex {
fn from(val: RangeInclusive<Tensor>) -> DimIndex {
DimIndex::Range { start: val.start().item::<i64>(), end: val.end().item::<i64>() + 1 }
}
}
impl From<RangeFrom<Tensor>> for DimIndex {
fn from(val: RangeFrom<Tensor>) -> DimIndex {
DimIndex::RangeFrom { start: val.start.item::<i64>() }
}
}
impl From<RangeTo<Tensor>> for DimIndex {
fn from(val: RangeTo<Tensor>) -> DimIndex {
DimIndex::RangeTo { end: val.end.item::<i64>() }
}
}
impl From<RangeFull> for DimIndex {
fn from(_val: RangeFull) -> DimIndex {
DimIndex::RangeFull
}
}
impl<I: Into<DimIndex>> IntoIndex for I {
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
std::iter::once(self.into())
}
}
impl<I: Into<DimIndex>, const N: usize> IntoIndex for [I; N] {
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
self.into_iter().map(Into::into)
}
}
impl<I: Into<DimIndex> + Clone> IntoIndex for &[I] {
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
self.iter().map(|e| e.clone().into())
}
}
impl<I: Into<DimIndex>> IntoIndex for Vec<I> {
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
self.into_iter().map(Into::into)
}
}
impl<I0: Into<DimIndex>, I1: Into<DimIndex>> IntoIndex for (I0, I1) {
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
[self.0.into(), self.1.into()].into_iter()
}
}
impl<I0: Into<DimIndex>, I1: Into<DimIndex>, I2: Into<DimIndex>> IntoIndex for (I0, I1, I2) {
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
[self.0.into(), self.1.into(), self.2.into()].into_iter()
}
}
impl<I0: Into<DimIndex>, I1: Into<DimIndex>, I2: Into<DimIndex>, I3: Into<DimIndex>> IntoIndex for (I0, I1, I2, I3) {
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
[self.0.into(), self.1.into(), self.2.into(), self.3.into()].into_iter()
}
}
impl<I0: Into<DimIndex>, I1: Into<DimIndex>, I2: Into<DimIndex>, I3: Into<DimIndex>, I4: Into<DimIndex>> IntoIndex
for (I0, I1, I2, I3, I4)
{
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
[self.0.into(), self.1.into(), self.2.into(), self.3.into(), self.4.into()].into_iter()
}
}
impl<I0: Into<DimIndex>, I1: Into<DimIndex>, I2: Into<DimIndex>, I3: Into<DimIndex>, I4: Into<DimIndex>, I5: Into<DimIndex>>
IntoIndex for (I0, I1, I2, I3, I4, I5)
{
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
[
self.0.into(),
self.1.into(),
self.2.into(),
self.3.into(),
self.4.into(),
self.5.into(),
]
.into_iter()
}
}
impl<
I0: Into<DimIndex>,
I1: Into<DimIndex>,
I2: Into<DimIndex>,
I3: Into<DimIndex>,
I4: Into<DimIndex>,
I5: Into<DimIndex>,
I6: Into<DimIndex>,
> IntoIndex for (I0, I1, I2, I3, I4, I5, I6)
{
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
[
self.0.into(),
self.1.into(),
self.2.into(),
self.3.into(),
self.4.into(),
self.5.into(),
self.6.into(),
]
.into_iter()
}
}
impl<
I0: Into<DimIndex>,
I1: Into<DimIndex>,
I2: Into<DimIndex>,
I3: Into<DimIndex>,
I4: Into<DimIndex>,
I5: Into<DimIndex>,
I6: Into<DimIndex>,
I7: Into<DimIndex>,
> IntoIndex for (I0, I1, I2, I3, I4, I5, I6, I7)
{
fn into_index(self) -> impl ExactSizeIterator<Item = DimIndex> + DoubleEndedIterator {
[
self.0.into(),
self.1.into(),
self.2.into(),
self.3.into(),
self.4.into(),
self.5.into(),
self.6.into(),
self.7.into(),
]
.into_iter()
}
}