use std::collections::HashSet;
use crate::syntax::notation::{char_to_label, split_and_validate_notation};
use crate::syntax::subscripts::Subscripts;
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub enum NestedEinsum {
Leaf(usize),
Node {
subscripts: Subscripts,
children: Vec<NestedEinsum>,
},
}
impl NestedEinsum {
pub fn count_leaves(&self) -> usize {
match self {
Self::Leaf(_) => 1,
Self::Node { children, .. } => children.iter().map(|c| c.count_leaves()).sum(),
}
}
pub fn parse(notation: &str) -> Result<Self> {
let (lhs, output_str) = split_and_validate_notation(notation)?;
let output: Vec<u32> = output_str
.chars()
.map(char_to_label)
.collect::<Result<_>>()?;
let mut leaf_counter: usize = 0;
let outer_needed: HashSet<u32> = output.iter().copied().collect();
Self::parse_group(lhs, &outer_needed, &output, &mut leaf_counter)
}
fn parse_group(
group_str: &str,
outer_needed: &HashSet<u32>,
final_output: &[u32],
leaf_counter: &mut usize,
) -> Result<Self> {
let items = Self::split_top_level(group_str)?;
let mut children = Vec::with_capacity(items.len());
let mut child_subscript_inputs: Vec<Vec<u32>> = Vec::with_capacity(items.len());
for (idx, item) in items.iter().enumerate() {
if item.starts_with('(') && item.ends_with(')') {
let inner = &item[1..item.len() - 1];
let group_labels = Self::collect_labels_in_order(inner)?;
let sibling_labels = Self::collect_sibling_labels(&items, idx)?;
let mut needed: HashSet<u32> = HashSet::new();
let mut sub_output = Vec::new();
for label in group_labels {
if outer_needed.contains(&label) || sibling_labels.contains(&label) {
needed.insert(label);
sub_output.push(label);
}
}
let child = Self::parse_group(inner, &needed, &sub_output, leaf_counter)?;
child_subscript_inputs.push(sub_output);
children.push(child);
} else {
let labels: Vec<u32> = item.chars().map(char_to_label).collect::<Result<_>>()?;
child_subscript_inputs.push(labels);
children.push(NestedEinsum::Leaf(*leaf_counter));
*leaf_counter += 1;
}
}
let node_output: Vec<u32> = final_output.to_vec();
let subscripts = Subscripts {
inputs: child_subscript_inputs,
output: node_output,
};
Ok(NestedEinsum::Node {
subscripts,
children,
})
}
fn split_top_level(s: &str) -> Result<Vec<&str>> {
let mut items = Vec::new();
let mut depth: usize = 0;
let mut start = 0;
for (pos, c) in s.char_indices() {
match c {
'(' => depth += 1,
')' => {
if depth == 0 {
return Err(Error::invalid_subscripts(format!(
"unmatched ')' in einsum group: {s}"
)));
}
depth -= 1;
}
',' if depth == 0 => {
items.push(&s[start..pos]);
start = pos + 1; }
_ => {}
}
}
items.push(&s[start..]);
Ok(items)
}
fn collect_labels(s: &str) -> Result<HashSet<u32>> {
let mut labels = HashSet::new();
for c in s.chars() {
match c {
'(' | ')' | ',' => continue,
_ => {
labels.insert(char_to_label(c)?);
}
}
}
Ok(labels)
}
fn collect_labels_in_order(s: &str) -> Result<Vec<u32>> {
let mut seen = HashSet::new();
let mut labels = Vec::new();
for c in s.chars() {
match c {
'(' | ')' | ',' => continue,
_ => {
let label = char_to_label(c)?;
if seen.insert(label) {
labels.push(label);
}
}
}
}
Ok(labels)
}
fn collect_sibling_labels(items: &[&str], current_idx: usize) -> Result<HashSet<u32>> {
let mut labels = HashSet::new();
for (idx, item) in items.iter().enumerate() {
if idx == current_idx {
continue;
}
for label in Self::collect_labels(item)? {
labels.insert(label);
}
}
Ok(labels)
}
}
#[cfg(test)]
mod tests;