use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Shape(Vec<usize>);
impl Shape {
pub fn new(dims: Vec<usize>) -> Self {
Shape(dims)
}
pub fn dims(&self) -> &[usize] {
&self.0
}
pub fn rank(&self) -> usize {
self.0.len()
}
pub fn elem_count(&self) -> usize {
self.0.iter().product::<usize>().max(1)
}
pub fn stride_contiguous(&self) -> Vec<usize> {
let mut strides = vec![0usize; self.rank()];
if self.rank() > 0 {
strides[self.rank() - 1] = 1;
for i in (0..self.rank() - 1).rev() {
strides[i] = strides[i + 1] * self.0[i + 1];
}
}
strides
}
pub fn dim(&self, d: usize) -> crate::Result<usize> {
self.0.get(d).copied().ok_or(crate::Error::DimOutOfRange {
dim: d,
rank: self.rank(),
})
}
pub fn broadcast_shape(lhs: &Shape, rhs: &Shape) -> crate::Result<Shape> {
let l = lhs.dims();
let r = rhs.dims();
let max_rank = l.len().max(r.len());
let mut result = Vec::with_capacity(max_rank);
for i in 0..max_rank {
let ld = if i < l.len() { l[l.len() - 1 - i] } else { 1 };
let rd = if i < r.len() { r[r.len() - 1 - i] } else { 1 };
if ld == rd {
result.push(ld);
} else if ld == 1 {
result.push(rd);
} else if rd == 1 {
result.push(ld);
} else {
return Err(crate::Error::msg(format!(
"shapes {:?} and {:?} are not broadcast-compatible (dim {} from right: {} vs {})",
l, r, i, ld, rd
)));
}
}
result.reverse(); Ok(Shape::new(result))
}
pub fn broadcast_strides(&self, target: &Shape) -> Vec<usize> {
let self_dims = self.dims();
let target_dims = target.dims();
let self_strides = self.stride_contiguous();
let mut result = vec![0usize; target_dims.len()];
let offset = target_dims.len() - self_dims.len();
for i in 0..self_dims.len() {
if self_dims[i] == target_dims[i + offset] {
result[i + offset] = self_strides[i];
} else {
result[i + offset] = 0;
}
}
result
}
}
impl fmt::Display for Shape {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[")?;
for (i, d) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", d)?;
}
write!(f, "]")
}
}
impl From<()> for Shape {
fn from(_: ()) -> Self {
Shape(vec![])
}
}
impl From<usize> for Shape {
fn from(d: usize) -> Self {
Shape(vec![d])
}
}
impl From<(usize,)> for Shape {
fn from((d0,): (usize,)) -> Self {
Shape(vec![d0])
}
}
impl From<(usize, usize)> for Shape {
fn from((d0, d1): (usize, usize)) -> Self {
Shape(vec![d0, d1])
}
}
impl From<(usize, usize, usize)> for Shape {
fn from((d0, d1, d2): (usize, usize, usize)) -> Self {
Shape(vec![d0, d1, d2])
}
}
impl From<(usize, usize, usize, usize)> for Shape {
fn from((d0, d1, d2, d3): (usize, usize, usize, usize)) -> Self {
Shape(vec![d0, d1, d2, d3])
}
}
impl From<Vec<usize>> for Shape {
fn from(v: Vec<usize>) -> Self {
Shape(v)
}
}
impl From<&[usize]> for Shape {
fn from(s: &[usize]) -> Self {
Shape(s.to_vec())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scalar_shape() {
let s = Shape::from(());
assert_eq!(s.rank(), 0);
assert_eq!(s.elem_count(), 1);
assert_eq!(s.stride_contiguous(), vec![]);
}
#[test]
fn test_vector_shape() {
let s = Shape::from(5);
assert_eq!(s.rank(), 1);
assert_eq!(s.elem_count(), 5);
assert_eq!(s.stride_contiguous(), vec![1]);
}
#[test]
fn test_matrix_shape() {
let s = Shape::from((3, 4));
assert_eq!(s.rank(), 2);
assert_eq!(s.elem_count(), 12);
assert_eq!(s.stride_contiguous(), vec![4, 1]);
}
#[test]
fn test_3d_strides() {
let s = Shape::from((2, 3, 4));
assert_eq!(s.stride_contiguous(), vec![12, 4, 1]);
assert_eq!(s.elem_count(), 24);
}
#[test]
fn test_display() {
let s = Shape::from((3, 4));
assert_eq!(format!("{}", s), "[3, 4]");
}
}