#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum DimExpr {
Const(usize),
InputDim { input_idx: usize, axis: usize },
Add(Box<DimExpr>, Box<DimExpr>),
Sub(Box<DimExpr>, Box<DimExpr>),
Mul(Box<DimExpr>, Box<DimExpr>),
FloorDiv(Box<DimExpr>, Box<DimExpr>),
Min(Box<DimExpr>, Box<DimExpr>),
Max(Box<DimExpr>, Box<DimExpr>),
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum DimExprEvalError {
#[error(
"DimExpr::InputDim input index {input_idx} out of bounds for {input_count} input shapes"
)]
InputOutOfBounds {
input_idx: usize,
input_count: usize,
},
#[error("DimExpr::InputDim axis {axis} out of bounds for input {input_idx} rank {rank}")]
AxisOutOfBounds {
input_idx: usize,
axis: usize,
rank: usize,
},
#[error("DimExpr::Add overflow: {lhs} + {rhs}")]
AddOverflow { lhs: usize, rhs: usize },
#[error("DimExpr::Sub underflow: left operand {lhs} is smaller than {rhs}")]
SubUnderflow { lhs: usize, rhs: usize },
#[error("DimExpr::Mul overflow: {lhs} * {rhs}")]
MulOverflow { lhs: usize, rhs: usize },
#[error("DimExpr::FloorDiv divide by zero: left operand {lhs}, divisor {rhs}")]
FloorDivByZero { lhs: usize, rhs: usize },
}
impl DimExpr {
pub fn eval(&self, input_shapes: &[&[usize]]) -> Result<usize, DimExprEvalError> {
match self {
Self::Const(v) => Ok(*v),
Self::InputDim { input_idx, axis } => input_shapes
.get(*input_idx)
.ok_or(DimExprEvalError::InputOutOfBounds {
input_idx: *input_idx,
input_count: input_shapes.len(),
})
.and_then(|shape| {
shape
.get(*axis)
.copied()
.ok_or(DimExprEvalError::AxisOutOfBounds {
input_idx: *input_idx,
axis: *axis,
rank: shape.len(),
})
}),
Self::Add(a, b) => {
let lhs = a.eval(input_shapes)?;
let rhs = b.eval(input_shapes)?;
lhs.checked_add(rhs)
.ok_or(DimExprEvalError::AddOverflow { lhs, rhs })
}
Self::Sub(a, b) => {
let lhs = a.eval(input_shapes)?;
let rhs = b.eval(input_shapes)?;
lhs.checked_sub(rhs)
.ok_or(DimExprEvalError::SubUnderflow { lhs, rhs })
}
Self::Mul(a, b) => {
let lhs = a.eval(input_shapes)?;
let rhs = b.eval(input_shapes)?;
lhs.checked_mul(rhs)
.ok_or(DimExprEvalError::MulOverflow { lhs, rhs })
}
Self::FloorDiv(a, b) => {
let lhs = a.eval(input_shapes)?;
let rhs = b.eval(input_shapes)?;
if rhs == 0 {
return Err(DimExprEvalError::FloorDivByZero { lhs, rhs });
}
Ok(lhs / rhs)
}
Self::Min(a, b) => Ok(a.eval(input_shapes)?.min(b.eval(input_shapes)?)),
Self::Max(a, b) => Ok(a.eval(input_shapes)?.max(b.eval(input_shapes)?)),
}
}
pub fn max_input_idx(&self) -> Option<usize> {
match self {
Self::Const(_) => None,
Self::InputDim { input_idx, .. } => Some(*input_idx),
Self::Add(a, b)
| Self::Sub(a, b)
| Self::Mul(a, b)
| Self::FloorDiv(a, b)
| Self::Min(a, b)
| Self::Max(a, b) => match (a.max_input_idx(), b.max_input_idx()) {
(Some(x), Some(y)) => Some(x.max(y)),
(Some(x), None) | (None, Some(x)) => Some(x),
(None, None) => None,
},
}
}
pub fn remap(&self, from: usize, to: usize) -> Self {
match self {
Self::Const(v) => Self::Const(*v),
Self::InputDim { input_idx, axis } => Self::InputDim {
input_idx: if *input_idx == from { to } else { *input_idx },
axis: *axis,
},
Self::Add(a, b) => Self::add(a.remap(from, to), b.remap(from, to)),
Self::Sub(a, b) => Self::sub(a.remap(from, to), b.remap(from, to)),
Self::Mul(a, b) => Self::mul(a.remap(from, to), b.remap(from, to)),
Self::FloorDiv(a, b) => Self::floor_div(a.remap(from, to), b.remap(from, to)),
Self::Min(a, b) => Self::min(a.remap(from, to), b.remap(from, to)),
Self::Max(a, b) => Self::max(a.remap(from, to), b.remap(from, to)),
}
}
pub fn constant(v: usize) -> Self {
Self::Const(v)
}
#[allow(clippy::should_implement_trait)]
pub fn add(a: Self, b: Self) -> Self {
Self::Add(Box::new(a), Box::new(b))
}
#[allow(clippy::should_implement_trait)]
pub fn sub(a: Self, b: Self) -> Self {
Self::Sub(Box::new(a), Box::new(b))
}
#[allow(clippy::should_implement_trait)]
pub fn mul(a: Self, b: Self) -> Self {
Self::Mul(Box::new(a), Box::new(b))
}
pub fn floor_div(a: Self, b: Self) -> Self {
Self::FloorDiv(Box::new(a), Box::new(b))
}
pub fn min(a: Self, b: Self) -> Self {
Self::Min(Box::new(a), Box::new(b))
}
pub fn max(a: Self, b: Self) -> Self {
Self::Max(Box::new(a), Box::new(b))
}
pub fn is_const(&self) -> bool {
matches!(self, Self::Const(_))
}
pub fn from_concrete(shape: &[usize]) -> Vec<Self> {
shape.iter().map(|&v| Self::Const(v)).collect()
}
pub fn input_shape(input_idx: usize, rank: usize) -> Vec<Self> {
(0..rank)
.map(|axis| Self::InputDim { input_idx, axis })
.collect()
}
pub fn eval_all(
exprs: &[Self],
input_shapes: &[&[usize]],
) -> Result<Vec<usize>, DimExprEvalError> {
exprs.iter().map(|e| e.eval(input_shapes)).collect()
}
pub fn remap_all(exprs: &[Self], from: usize, to: usize) -> Vec<Self> {
exprs.iter().map(|e| e.remap(from, to)).collect()
}
pub fn max_input_idx_all(exprs: &[Self]) -> Option<usize> {
exprs.iter().filter_map(Self::max_input_idx).max()
}
}
impl From<usize> for DimExpr {
fn from(v: usize) -> Self {
Self::Const(v)
}
}
impl From<&DimExpr> for DimExpr {
fn from(value: &DimExpr) -> Self {
value.clone()
}
}