use tenferro_tensor::{DType, ErrorKind, ValidationKind};
use crate::SymDim;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ShapeRelation {
Equal,
}
#[doc(hidden)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExtensionShapeConstraint {
relation: ShapeRelation,
lhs: SymDim,
rhs: SymDim,
}
impl ExtensionShapeConstraint {
fn equal(lhs: SymDim, rhs: SymDim) -> Self {
Self {
relation: ShapeRelation::Equal,
lhs,
rhs,
}
}
#[doc(hidden)]
pub fn relation(&self) -> ShapeRelation {
self.relation
}
#[doc(hidden)]
pub fn lhs(&self) -> &SymDim {
&self.lhs
}
#[doc(hidden)]
pub fn rhs(&self) -> &SymDim {
&self.rhs
}
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum ExtensionShapeError {
#[error(
"extension family {family_id:?} input index {input} out of bounds for {input_count} inputs"
)]
InputOutOfBounds {
family_id: &'static str,
input: usize,
input_count: usize,
},
#[error(
"extension family {family_id:?} axis {axis} out of bounds for input {input} rank {rank}"
)]
AxisOutOfBounds {
family_id: &'static str,
input: usize,
axis: usize,
rank: usize,
},
#[error(
"extension family {family_id:?} requires inputs {lhs_input} and {rhs_input} to have the same shape, but their ranks are {lhs_rank} and {rhs_rank}"
)]
RankMismatch {
family_id: &'static str,
lhs_input: usize,
lhs_rank: usize,
rhs_input: usize,
rhs_rank: usize,
},
}
impl From<ExtensionShapeError> for tenferro_tensor::Error {
fn from(error: ExtensionShapeError) -> Self {
let (family_id, kind) = match &error {
ExtensionShapeError::InputOutOfBounds { family_id, .. } => (
*family_id,
ErrorKind::Validation(ValidationKind::InvalidArgument),
),
ExtensionShapeError::AxisOutOfBounds { family_id, .. } => (
*family_id,
ErrorKind::Validation(ValidationKind::AxisOutOfBounds),
),
ExtensionShapeError::RankMismatch { family_id, .. } => (
*family_id,
ErrorKind::Validation(ValidationKind::RankMismatch),
),
};
tenferro_tensor::Error::extension("extension", family_id, kind, error)
}
}
#[derive(Debug)]
pub struct ExtensionShapeContext<'a> {
family_id: &'static str,
input_dtypes: &'a [DType],
input_shapes: &'a [&'a [SymDim]],
constraints: Vec<ExtensionShapeConstraint>,
}
impl<'a> ExtensionShapeContext<'a> {
#[doc(hidden)]
pub fn new_for_inference(
family_id: &'static str,
input_dtypes: &'a [DType],
input_shapes: &'a [&'a [SymDim]],
) -> Self {
Self {
family_id,
input_dtypes,
input_shapes,
constraints: Vec::new(),
}
}
pub fn input_dtype(&self, input: usize) -> Result<DType, ExtensionShapeError> {
self.input_dtypes
.get(input)
.copied()
.ok_or(ExtensionShapeError::InputOutOfBounds {
family_id: self.family_id,
input,
input_count: self.input_dtypes.len(),
})
}
pub fn input_shape(&self, input: usize) -> Result<&[SymDim], ExtensionShapeError> {
self.input_shapes
.get(input)
.copied()
.ok_or(ExtensionShapeError::InputOutOfBounds {
family_id: self.family_id,
input,
input_count: self.input_shapes.len(),
})
}
pub fn input_axis(&self, input: usize, axis: usize) -> Result<SymDim, ExtensionShapeError> {
let shape = self.input_shape(input)?;
shape
.get(axis)
.cloned()
.ok_or(ExtensionShapeError::AxisOutOfBounds {
family_id: self.family_id,
input,
axis,
rank: shape.len(),
})
}
pub fn require_equal(&mut self, lhs: SymDim, rhs: SymDim) -> Result<(), ExtensionShapeError> {
self.constraints
.push(ExtensionShapeConstraint::equal(lhs, rhs));
Ok(())
}
pub fn require_axes_equal(
&mut self,
lhs: (usize, usize),
rhs: (usize, usize),
) -> Result<(), ExtensionShapeError> {
let lhs = self.input_axis(lhs.0, lhs.1)?;
let rhs = self.input_axis(rhs.0, rhs.1)?;
self.require_equal(lhs, rhs)
}
pub fn require_same_shape(
&mut self,
lhs_input: usize,
rhs_input: usize,
) -> Result<(), ExtensionShapeError> {
let lhs_shape = self.input_shape(lhs_input)?;
let rhs_shape = self.input_shape(rhs_input)?;
if lhs_shape.len() != rhs_shape.len() {
return Err(ExtensionShapeError::RankMismatch {
family_id: self.family_id,
lhs_input,
lhs_rank: lhs_shape.len(),
rhs_input,
rhs_rank: rhs_shape.len(),
});
}
let equalities: Vec<_> = lhs_shape
.iter()
.cloned()
.zip(rhs_shape.iter().cloned())
.map(|(lhs, rhs)| ExtensionShapeConstraint::equal(lhs, rhs))
.collect();
self.constraints.extend(equalities);
Ok(())
}
#[doc(hidden)]
pub fn constraints(&self) -> &[ExtensionShapeConstraint] {
&self.constraints
}
#[doc(hidden)]
pub fn into_constraints(self) -> Vec<ExtensionShapeConstraint> {
self.constraints
}
}
#[cfg(test)]
mod tests;