#![forbid(unsafe_code)]
#![warn(missing_docs)]
mod execute;
mod parse;
mod plan;
use std::error::Error;
use std::fmt;
use ndarray::{ArrayD, ArrayViewD, LinalgScalar};
use parse::ParsedExpression;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum Strategy {
#[default]
Auto,
Optimal,
Greedy,
Explicit(Vec<Vec<usize>>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContractionPath {
pub steps: Vec<ContractionStep>,
pub naive_flops: u128,
pub optimized_flops: u128,
pub largest_intermediate: u128,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContractionStep {
pub operands: Vec<usize>,
pub subscripts: String,
pub flops: u128,
pub gemm: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum EinsumError {
InvalidCharacter {
position: usize,
byte: u8,
},
MalformedSubscripts {
position: usize,
reason: &'static str,
},
OperandCountMismatch {
subscripts: usize,
operands: usize,
},
TooManyLabels {
operand: usize,
labels: usize,
ndim: usize,
},
TooFewLabels {
operand: usize,
labels: usize,
ndim: usize,
},
DiagonalSizeMismatch {
operand: usize,
label: char,
sizes: (usize, usize),
},
BroadcastMismatch {
label: Option<char>,
sizes: (usize, usize),
},
OutputLabelRepeated {
label: char,
},
OutputLabelUnknown {
label: char,
},
OutputEllipsisMissing {
broadcast_rank: usize,
},
SizeOverflow,
ShapeMismatch {
operand: usize,
expected: Vec<usize>,
actual: Vec<usize>,
},
InvalidPath {
reason: &'static str,
},
}
impl fmt::Display for EinsumError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidCharacter { position, byte } => {
write!(formatter, "invalid byte {byte:#04x} at position {position}")
}
Self::MalformedSubscripts { position, reason } => {
write!(formatter, "malformed subscripts at position {position}: {reason}")
}
Self::OperandCountMismatch {
subscripts,
operands,
} => write!(
formatter,
"subscripts contain {subscripts} operands but {operands} were supplied"
),
Self::TooManyLabels {
operand,
labels,
ndim,
} => write!(
formatter,
"operand {operand} has rank {ndim} but its subscript has {labels} labels"
),
Self::TooFewLabels {
operand,
labels,
ndim,
} => write!(
formatter,
"operand {operand} has rank {ndim} but its subscript has {labels} labels and no ellipsis"
),
Self::DiagonalSizeMismatch {
operand,
label,
sizes,
} => write!(
formatter,
"operand {operand} repeats label {label} on axes of lengths {} and {}",
sizes.0, sizes.1
),
Self::BroadcastMismatch { label, sizes } => match label {
Some(label) => write!(
formatter,
"label {label} cannot broadcast lengths {} and {}",
sizes.0, sizes.1
),
None => write!(
formatter,
"ellipsis axis cannot broadcast lengths {} and {}",
sizes.0, sizes.1
),
},
Self::OutputLabelRepeated { label } => {
write!(formatter, "output label {label} appears more than once")
}
Self::OutputLabelUnknown { label } => {
write!(formatter, "output label {label} does not appear in an input")
}
Self::OutputEllipsisMissing { broadcast_rank } => write!(
formatter,
"output omits an ellipsis with rank {broadcast_rank}"
),
Self::SizeOverflow => formatter.write_str("array size exceeds usize"),
Self::ShapeMismatch {
operand,
expected,
actual,
} => write!(
formatter,
"operand {operand} has shape {actual:?}, expected {expected:?}"
),
Self::InvalidPath { reason } => write!(formatter, "invalid path: {reason}"),
}
}
}
impl Error for EinsumError {}
#[derive(Clone, Debug)]
pub struct EinsumPlan {
expression: ParsedExpression,
path: ContractionPath,
execution: execute::ExecutionPlan,
output_subscripts: String,
}
impl EinsumPlan {
pub fn new(subscripts: &str, shapes: &[&[usize]]) -> Result<Self, EinsumError> {
Self::with_strategy(subscripts, shapes, Strategy::Auto)
}
pub fn with_strategy(
subscripts: &str,
shapes: &[&[usize]],
strategy: Strategy,
) -> Result<Self, EinsumError> {
let expression = parse::parse(subscripts, shapes)?;
let path = plan::build_path(&expression, strategy)?;
let execution = execute::build_execution_plan(&expression, &path)?;
let output_subscripts = parse::format_labels(&expression.output, expression.broadcast_rank);
Ok(Self {
expression,
path,
execution,
output_subscripts,
})
}
pub fn execute<A: LinalgScalar>(
&self,
operands: &[ArrayViewD<'_, A>],
) -> Result<ArrayD<A>, EinsumError> {
execute::execute(self, operands)
}
pub fn output_shape(&self) -> &[usize] {
&self.expression.output_shape
}
pub fn path(&self) -> &ContractionPath {
&self.path
}
pub fn output_subscripts(&self) -> &str {
&self.output_subscripts
}
}
pub fn einsum<A: LinalgScalar>(
subscripts: &str,
operands: &[ArrayViewD<'_, A>],
) -> Result<ArrayD<A>, EinsumError> {
let shapes: Vec<&[usize]> = operands.iter().map(ArrayViewD::shape).collect();
EinsumPlan::new(subscripts, &shapes)?.execute(operands)
}
pub fn einsum_path(subscripts: &str, shapes: &[&[usize]]) -> Result<ContractionPath, EinsumError> {
Ok(EinsumPlan::new(subscripts, shapes)?.path)
}