use std::collections::HashMap;
use crate::{Atom, AtomArena, AtomNode, Symbol};
use super::spec::TensorRegistry;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DummyError {
OverContracted(Symbol),
BadContraction(Symbol),
}
pub fn refresh_dummies<'a>(
ctx: &'a AtomArena<'a>,
expr: Atom<'a>,
registry: &TensorRegistry,
) -> Result<Atom<'a>, DummyError> {
let mut index_counts: HashMap<Atom<'a>, usize> = HashMap::new();
collect_counts(expr, &mut index_counts);
let dummies: Vec<Atom<'a>> = index_counts
.iter()
.filter(|(_, c)| **c == 2)
.map(|(l, _)| *l)
.collect();
if dummies.is_empty() {
return Ok(expr);
}
let mut group_counters: HashMap<u64, usize> = HashMap::new();
let mut renames: HashMap<Atom<'a>, Atom<'a>> = HashMap::new();
for d in &dummies {
let sym = Symbol::new(&d.to_string());
let group = registry.index_group(sym);
let cnt = group_counters.entry(group).or_insert(0);
let new_label = if group == 0 {
ctx.var(&format!("d{}", cnt))
} else {
ctx.var(&format!("d{}_{}", group, cnt))
};
*cnt += 1;
renames.insert(*d, new_label);
}
Ok(rename_in_expr(ctx, expr, &renames))
}
fn collect_counts<'a>(atom: Atom<'a>, counts: &mut HashMap<Atom<'a>, usize>) {
match atom.node() {
AtomNode::Num(_) | AtomNode::Var(_) => {}
AtomNode::Fun(_, args) => {
for a in args.iter() {
*counts.entry(*a).or_insert(0) += 1;
}
}
AtomNode::Add(args) | AtomNode::Mul(args) => {
for a in args.iter() {
collect_counts(*a, counts);
}
}
AtomNode::Pow(base, exp) => {
collect_counts(*base, counts);
collect_counts(*exp, counts);
}
}
}
fn rename_in_expr<'a>(
ctx: &'a AtomArena<'a>,
atom: Atom<'a>,
renames: &HashMap<Atom<'a>, Atom<'a>>,
) -> Atom<'a> {
match atom.node() {
AtomNode::Num(_) => atom,
AtomNode::Var(_) => renames.get(&atom).copied().unwrap_or(atom),
AtomNode::Fun(name, args) => {
let new_args: Vec<Atom<'a>> = args
.iter()
.map(|a| rename_in_expr(ctx, *a, renames))
.collect();
ctx.fun(name.as_str(), &new_args)
}
AtomNode::Add(args) => {
let new_args: Vec<Atom<'a>> = args
.iter()
.map(|a| rename_in_expr(ctx, *a, renames))
.collect();
ctx.add(&new_args)
}
AtomNode::Mul(args) => {
let new_args: Vec<Atom<'a>> = args
.iter()
.map(|a| rename_in_expr(ctx, *a, renames))
.collect();
ctx.mul(&new_args)
}
AtomNode::Pow(base, exp) => {
let new_base = rename_in_expr(ctx, *base, renames);
let new_exp = rename_in_expr(ctx, *exp, renames);
ctx.pow(new_base, new_exp)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::AtomArena;
use crate::Symbol;
use crate::tensor::spec::SymmetrySpec;
use ocas_core::arena::Arena;
#[test]
fn refresh_renames_dummy() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let mut reg = TensorRegistry::new();
reg.register(Symbol::new("T"), SymmetrySpec::none());
reg.register(Symbol::new("U"), SymmetrySpec::none());
let i = ctx.var("i");
let j = ctx.var("j");
let t = ctx.fun("T", &[i, j]);
let u = ctx.fun("U", &[j, i]);
let prod = ctx.mul(&[t, u]);
let result = refresh_dummies(&ctx, prod, ®).unwrap();
let s = result.to_string();
assert!(s.contains("d0"), "expected dummy d0 in: {s}");
}
}