1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3
4mod execute;
10mod parse;
11mod plan;
12
13use std::error::Error;
14use std::fmt;
15
16use ndarray::{ArrayD, ArrayViewD, LinalgScalar};
17
18use parse::ParsedExpression;
19
20#[derive(Clone, Debug, Default, Eq, PartialEq)]
22#[non_exhaustive]
23pub enum Strategy {
24 #[default]
27 Auto,
28 Optimal,
33 Greedy,
35 Explicit(Vec<Vec<usize>>),
39}
40
41#[derive(Clone, Debug, Eq, PartialEq)]
43pub struct ContractionPath {
44 pub steps: Vec<ContractionStep>,
46 pub naive_flops: u128,
48 pub optimized_flops: u128,
50 pub largest_intermediate: u128,
52}
53
54#[derive(Clone, Debug, Eq, PartialEq)]
56pub struct ContractionStep {
57 pub operands: Vec<usize>,
59 pub subscripts: String,
61 pub flops: u128,
63 pub gemm: bool,
65}
66
67#[derive(Clone, Debug, Eq, PartialEq)]
69#[non_exhaustive]
70pub enum EinsumError {
71 InvalidCharacter {
73 position: usize,
75 byte: u8,
77 },
78 MalformedSubscripts {
80 position: usize,
82 reason: &'static str,
84 },
85 OperandCountMismatch {
87 subscripts: usize,
89 operands: usize,
91 },
92 TooManyLabels {
94 operand: usize,
96 labels: usize,
98 ndim: usize,
100 },
101 TooFewLabels {
103 operand: usize,
105 labels: usize,
107 ndim: usize,
109 },
110 DiagonalSizeMismatch {
112 operand: usize,
114 label: char,
116 sizes: (usize, usize),
118 },
119 BroadcastMismatch {
121 label: Option<char>,
123 sizes: (usize, usize),
125 },
126 OutputLabelRepeated {
128 label: char,
130 },
131 OutputLabelUnknown {
133 label: char,
135 },
136 OutputEllipsisMissing {
138 broadcast_rank: usize,
140 },
141 SizeOverflow,
143 ShapeMismatch {
145 operand: usize,
147 expected: Vec<usize>,
149 actual: Vec<usize>,
151 },
152 InvalidPath {
154 reason: &'static str,
156 },
157}
158
159impl fmt::Display for EinsumError {
160 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
161 match self {
162 Self::InvalidCharacter { position, byte } => {
163 write!(formatter, "invalid byte {byte:#04x} at position {position}")
164 }
165 Self::MalformedSubscripts { position, reason } => {
166 write!(formatter, "malformed subscripts at position {position}: {reason}")
167 }
168 Self::OperandCountMismatch {
169 subscripts,
170 operands,
171 } => write!(
172 formatter,
173 "subscripts contain {subscripts} operands but {operands} were supplied"
174 ),
175 Self::TooManyLabels {
176 operand,
177 labels,
178 ndim,
179 } => write!(
180 formatter,
181 "operand {operand} has rank {ndim} but its subscript has {labels} labels"
182 ),
183 Self::TooFewLabels {
184 operand,
185 labels,
186 ndim,
187 } => write!(
188 formatter,
189 "operand {operand} has rank {ndim} but its subscript has {labels} labels and no ellipsis"
190 ),
191 Self::DiagonalSizeMismatch {
192 operand,
193 label,
194 sizes,
195 } => write!(
196 formatter,
197 "operand {operand} repeats label {label} on axes of lengths {} and {}",
198 sizes.0, sizes.1
199 ),
200 Self::BroadcastMismatch { label, sizes } => match label {
201 Some(label) => write!(
202 formatter,
203 "label {label} cannot broadcast lengths {} and {}",
204 sizes.0, sizes.1
205 ),
206 None => write!(
207 formatter,
208 "ellipsis axis cannot broadcast lengths {} and {}",
209 sizes.0, sizes.1
210 ),
211 },
212 Self::OutputLabelRepeated { label } => {
213 write!(formatter, "output label {label} appears more than once")
214 }
215 Self::OutputLabelUnknown { label } => {
216 write!(formatter, "output label {label} does not appear in an input")
217 }
218 Self::OutputEllipsisMissing { broadcast_rank } => write!(
219 formatter,
220 "output omits an ellipsis with rank {broadcast_rank}"
221 ),
222 Self::SizeOverflow => formatter.write_str("array size exceeds usize"),
223 Self::ShapeMismatch {
224 operand,
225 expected,
226 actual,
227 } => write!(
228 formatter,
229 "operand {operand} has shape {actual:?}, expected {expected:?}"
230 ),
231 Self::InvalidPath { reason } => write!(formatter, "invalid path: {reason}"),
232 }
233 }
234}
235
236impl Error for EinsumError {}
237
238#[derive(Clone, Debug)]
240pub struct EinsumPlan {
241 expression: ParsedExpression,
242 path: ContractionPath,
243 execution: execute::ExecutionPlan,
244 output_subscripts: String,
245}
246
247impl EinsumPlan {
248 pub fn new(subscripts: &str, shapes: &[&[usize]]) -> Result<Self, EinsumError> {
250 Self::with_strategy(subscripts, shapes, Strategy::Auto)
251 }
252
253 pub fn with_strategy(
255 subscripts: &str,
256 shapes: &[&[usize]],
257 strategy: Strategy,
258 ) -> Result<Self, EinsumError> {
259 let expression = parse::parse(subscripts, shapes)?;
260 let path = plan::build_path(&expression, strategy)?;
261 let execution = execute::build_execution_plan(&expression, &path)?;
262 let output_subscripts = parse::format_labels(&expression.output, expression.broadcast_rank);
263 Ok(Self {
264 expression,
265 path,
266 execution,
267 output_subscripts,
268 })
269 }
270
271 pub fn execute<A: LinalgScalar>(
273 &self,
274 operands: &[ArrayViewD<'_, A>],
275 ) -> Result<ArrayD<A>, EinsumError> {
276 execute::execute(self, operands)
277 }
278
279 pub fn output_shape(&self) -> &[usize] {
281 &self.expression.output_shape
282 }
283
284 pub fn path(&self) -> &ContractionPath {
286 &self.path
287 }
288
289 pub fn output_subscripts(&self) -> &str {
293 &self.output_subscripts
294 }
295}
296
297pub fn einsum<A: LinalgScalar>(
299 subscripts: &str,
300 operands: &[ArrayViewD<'_, A>],
301) -> Result<ArrayD<A>, EinsumError> {
302 let shapes: Vec<&[usize]> = operands.iter().map(ArrayViewD::shape).collect();
303 EinsumPlan::new(subscripts, &shapes)?.execute(operands)
304}
305
306pub fn einsum_path(subscripts: &str, shapes: &[&[usize]]) -> Result<ContractionPath, EinsumError> {
308 Ok(EinsumPlan::new(subscripts, shapes)?.path)
309}