use rustc_hash::FxHashMap;
use crate::base::arena::Arena;
use crate::base::node::{ExprId, ExprNode};
pub(crate) fn transfer_subtree(
src: &Arena,
dst: &mut Arena,
root: ExprId,
map: &mut FxHashMap<ExprId, ExprId>,
) -> ExprId {
let post_order = crate::base::walk::post_order_ids(src, root);
tracing::trace!(
root = ?root,
traversal_len = post_order.len(),
"transfer_subtree: starting post-order transfer",
);
for &old_id in &post_order {
if map.contains_key(&old_id) {
continue;
}
let new_id = transfer_node(src, dst, old_id, map);
map.insert(old_id, new_id);
}
dst.last_compact_size = dst.node_count();
map[&root]
}
pub(crate) fn liveness_ratio(arena: &Arena, roots: &[ExprId]) -> f64 {
let total = arena.node_count();
if total == 0 {
return 1.0;
}
let mut alive = vec![false; total]; let mut stack: Vec<ExprId> = roots.to_vec();
while let Some(id) = stack.pop() {
let idx = id.0 as usize;
if idx < total && !alive[idx] {
alive[idx] = true;
arena.node(id).for_each_child(|child| {
if (child.0 as usize) < total && !alive[child.0 as usize] {
stack.push(child);
}
});
}
}
let live = alive.iter().filter(|&&b| b).count();
tracing::trace!(
total_nodes = total,
live_nodes = live,
ratio = live as f64 / total as f64,
"liveness_ratio computed",
);
live as f64 / total as f64
}
pub(crate) fn should_compact(arena: &Arena, roots: &[ExprId]) -> bool {
let total = arena.node_count();
if total < 100_000 {
tracing::trace!(total_nodes = total, "should_compact: arena too small");
return false;
}
if total < arena.last_compact_size.saturating_mul(2) {
tracing::trace!(
total_nodes = total,
last_compact_size = arena.last_compact_size,
"should_compact: not yet doubled since last compact",
);
return false;
}
let ratio = liveness_ratio(arena, roots);
let verdict = ratio < 0.5;
tracing::trace!(ratio, verdict, "should_compact: liveness check",);
verdict
}
fn transfer_node(
src: &Arena,
dst: &mut Arena,
old_id: ExprId,
map: &FxHashMap<ExprId, ExprId>,
) -> ExprId {
let node = src.node(old_id).clone();
let new_node = remap_node(src, dst, &node, map);
dst.intern(new_node)
}
fn remap_node(
src: &Arena,
dst: &mut Arena,
node: &ExprNode,
map: &FxHashMap<ExprId, ExprId>,
) -> ExprNode {
let m = |old: &ExprId| -> ExprId {
*map.get(old)
.expect("compact bug: child not yet transferred — post-order invariant violated")
};
match node {
ExprNode::Num(old_nid) => {
let value = src.num(*old_nid).clone();
let new_nid = dst.intern_num(value);
ExprNode::Num(new_nid)
}
ExprNode::Symbol(old_sid) => {
let name = src.symbol_name(*old_sid).to_owned();
let new_sid = dst.symbols.intern(&name);
let assumptions = src.symbol_assumptions(*old_sid);
dst.set_symbol_assumptions(new_sid, assumptions);
ExprNode::Symbol(new_sid)
}
ExprNode::Pi => ExprNode::Pi,
ExprNode::E => ExprNode::E,
ExprNode::ImaginaryUnit => ExprNode::ImaginaryUnit,
ExprNode::EulerGamma => ExprNode::EulerGamma,
ExprNode::Catalan => ExprNode::Catalan,
ExprNode::GoldenRatio => ExprNode::GoldenRatio,
ExprNode::PhysicalConstant(old_sid, old_value_id) => {
let name = src.symbol_name(*old_sid).to_owned();
let new_sid = dst.symbols.intern(&name);
let new_value_id = if let Some(&mapped) = map.get(old_value_id) {
mapped
} else {
transfer_node(src, dst, *old_value_id, map)
};
ExprNode::PhysicalConstant(new_sid, new_value_id)
}
ExprNode::Infinity => ExprNode::Infinity,
ExprNode::NegInfinity => ExprNode::NegInfinity,
ExprNode::ComplexInfinity => ExprNode::ComplexInfinity,
ExprNode::NaN => ExprNode::NaN,
ExprNode::BoolTrue => ExprNode::BoolTrue,
ExprNode::BoolFalse => ExprNode::BoolFalse,
ExprNode::EmptySet => ExprNode::EmptySet,
ExprNode::UniversalSet => ExprNode::UniversalSet,
ExprNode::Add(children) => ExprNode::Add(children.iter().map(&m).collect()),
ExprNode::Mul(children) => ExprNode::Mul(children.iter().map(&m).collect()),
ExprNode::And(children) => ExprNode::And(children.iter().map(&m).collect()),
ExprNode::Or(children) => ExprNode::Or(children.iter().map(&m).collect()),
ExprNode::Min(children) => ExprNode::Min(children.iter().map(&m).collect()),
ExprNode::Max(children) => ExprNode::Max(children.iter().map(&m).collect()),
ExprNode::FiniteSet(children) => ExprNode::FiniteSet(children.iter().map(&m).collect()),
ExprNode::SetUnion(children) => ExprNode::SetUnion(children.iter().map(&m).collect()),
ExprNode::SetIntersection(children) => {
ExprNode::SetIntersection(children.iter().map(&m).collect())
}
ExprNode::Pow(a, b) => ExprNode::Pow(m(a), m(b)),
ExprNode::Binomial(a, b) => ExprNode::Binomial(m(a), m(b)),
ExprNode::Gt(a, b) => ExprNode::Gt(m(a), m(b)),
ExprNode::Ge(a, b) => ExprNode::Ge(m(a), m(b)),
ExprNode::Eq_(a, b) => ExprNode::Eq_(m(a), m(b)),
ExprNode::Ne(a, b) => ExprNode::Ne(m(a), m(b)),
ExprNode::Derivative(a, b) => ExprNode::Derivative(m(a), m(b)),
ExprNode::Integral(a, b) => ExprNode::Integral(m(a), m(b)),
ExprNode::Atan2(a, b) => ExprNode::Atan2(m(a), m(b)),
ExprNode::Beta(a, b) => ExprNode::Beta(m(a), m(b)),
ExprNode::Polygamma(a, b) => ExprNode::Polygamma(m(a), m(b)),
ExprNode::KroneckerDelta(a, b) => ExprNode::KroneckerDelta(m(a), m(b)),
ExprNode::SetComplement(a, b) => ExprNode::SetComplement(m(a), m(b)),
ExprNode::Interval(a, b, flags) => ExprNode::Interval(m(a), m(b), *flags),
ExprNode::Sum(body, var, lo, hi) => ExprNode::Sum(m(body), m(var), m(lo), m(hi)),
ExprNode::Product_(body, var, lo, hi) => ExprNode::Product_(m(body), m(var), m(lo), m(hi)),
ExprNode::DefiniteIntegral(body, var, lo, hi) => {
ExprNode::DefiniteIntegral(m(body), m(var), m(lo), m(hi))
}
ExprNode::Limit(a, b, c) => ExprNode::Limit(m(a), m(b), m(c)),
ExprNode::LaplaceTransform(a, b, c) => ExprNode::LaplaceTransform(m(a), m(b), m(c)),
ExprNode::InverseLaplaceTransform(a, b, c) => {
ExprNode::InverseLaplaceTransform(m(a), m(b), m(c))
}
ExprNode::Residue(a, b, c) => ExprNode::Residue(m(a), m(b), m(c)),
ExprNode::DSolve(a, b, c) => ExprNode::DSolve(m(a), m(b), m(c)),
ExprNode::RootSum(a, b, c) => ExprNode::RootSum(m(a), m(b), m(c)),
ExprNode::Series(a, b, c, d) => ExprNode::Series(m(a), m(b), m(c), m(d)),
ExprNode::RootOf(a, b) => ExprNode::RootOf(m(a), m(b)),
ExprNode::ConditionSet(a, b) => ExprNode::ConditionSet(m(a), m(b)),
ExprNode::Neg(x) => ExprNode::Neg(m(x)),
ExprNode::Sin(x) => ExprNode::Sin(m(x)),
ExprNode::Cos(x) => ExprNode::Cos(m(x)),
ExprNode::Tan(x) => ExprNode::Tan(m(x)),
ExprNode::Exp(x) => ExprNode::Exp(m(x)),
ExprNode::Ln(x) => ExprNode::Ln(m(x)),
ExprNode::Abs(x) => ExprNode::Abs(m(x)),
ExprNode::Asin(x) => ExprNode::Asin(m(x)),
ExprNode::Acos(x) => ExprNode::Acos(m(x)),
ExprNode::Atan(x) => ExprNode::Atan(m(x)),
ExprNode::Sinh(x) => ExprNode::Sinh(m(x)),
ExprNode::Cosh(x) => ExprNode::Cosh(m(x)),
ExprNode::Tanh(x) => ExprNode::Tanh(m(x)),
ExprNode::Asinh(x) => ExprNode::Asinh(m(x)),
ExprNode::Acosh(x) => ExprNode::Acosh(m(x)),
ExprNode::Atanh(x) => ExprNode::Atanh(m(x)),
ExprNode::Sign(x) => ExprNode::Sign(m(x)),
ExprNode::Factorial(x) => ExprNode::Factorial(m(x)),
ExprNode::Not(x) => ExprNode::Not(m(x)),
ExprNode::Floor(x) => ExprNode::Floor(m(x)),
ExprNode::Ceiling(x) => ExprNode::Ceiling(m(x)),
ExprNode::Gamma(x) => ExprNode::Gamma(m(x)),
ExprNode::LogGamma(x) => ExprNode::LogGamma(m(x)),
ExprNode::Digamma(x) => ExprNode::Digamma(m(x)),
ExprNode::Erf(x) => ExprNode::Erf(m(x)),
ExprNode::Erfc(x) => ExprNode::Erfc(m(x)),
ExprNode::LambertW(x) => ExprNode::LambertW(m(x)),
ExprNode::Heaviside(x) => ExprNode::Heaviside(m(x)),
ExprNode::DiracDelta(x) => ExprNode::DiracDelta(m(x)),
ExprNode::Re(x) => ExprNode::Re(m(x)),
ExprNode::Im(x) => ExprNode::Im(m(x)),
ExprNode::Conjugate(x) => ExprNode::Conjugate(m(x)),
ExprNode::Arg(x) => ExprNode::Arg(m(x)),
ExprNode::Si(x) => ExprNode::Si(m(x)),
ExprNode::Ci(x) => ExprNode::Ci(m(x)),
ExprNode::Ei(x) => ExprNode::Ei(m(x)),
ExprNode::Li(x) => ExprNode::Li(m(x)),
ExprNode::Zeta(x) => ExprNode::Zeta(m(x)),
ExprNode::Piecewise(pairs) => {
ExprNode::Piecewise(pairs.iter().map(|(v, c)| (m(v), m(c))).collect())
}
ExprNode::Apply(old_sid, args) => {
let name = src.symbol_name(*old_sid).to_owned();
let new_sid = dst.symbols.intern(&name);
ExprNode::Apply(new_sid, args.iter().map(m).collect())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::base::arena::Arena;
use rustc_hash::FxHashMap;
#[test]
fn transfer_atom_symbol() {
let mut src = Arena::new();
let x = src.symbol("x");
let mut dst = Arena::new();
let mut map = FxHashMap::default();
let new_x = transfer_subtree(&src, &mut dst, x, &mut map);
assert_eq!(
dst.symbol_name(match dst.node(new_x) {
ExprNode::Symbol(sid) => *sid,
_ => panic!("expected Symbol"),
}),
"x"
);
}
#[test]
fn transfer_atom_num() {
let mut src = Arena::new();
let two = src.int(2);
let mut dst = Arena::new();
let mut map = FxHashMap::default();
let new_two = transfer_subtree(&src, &mut dst, two, &mut map);
let nid = match dst.node(new_two) {
ExprNode::Num(nid) => *nid,
_ => panic!("expected Num"),
};
assert_eq!(*dst.num(nid), num_rational::Ratio::from_integer(2.into()));
}
#[test]
fn transfer_compound_expression() {
let mut src = Arena::new();
let x = src.symbol("x");
let two = src.int(2);
let x2 = src.pow(x, two);
let one = src.one();
let sum = src.add(&[x2, one]);
let mut dst = Arena::new();
let mut map = FxHashMap::default();
let new_sum = transfer_subtree(&src, &mut dst, sum, &mut map);
match dst.node(new_sum) {
ExprNode::Add(_) => {}
other => panic!("expected Add, got {:?}", other),
}
}
#[test]
fn transfer_preserves_sharing() {
let mut src = Arena::new();
let x = src.symbol("x");
let sx = src.sin(x);
let one = src.one();
let sum1 = src.add(&[sx, one]);
let two = src.int(2);
let pow = src.pow(sx, two);
let mut dst = Arena::new();
let mut map = FxHashMap::default();
let _new1 = transfer_subtree(&src, &mut dst, sum1, &mut map);
let _new2 = transfer_subtree(&src, &mut dst, pow, &mut map);
let dst_count = dst.node_count();
assert!(dst_count > 0);
}
#[test]
fn transfer_preserves_assumptions() {
let mut src = Arena::new();
let x_id = src.symbol("x");
let sid = match src.node(x_id) {
ExprNode::Symbol(s) => *s,
_ => panic!("expected Symbol"),
};
let mut assumptions = src.symbol_assumptions(sid);
assumptions.known_true |= crate::base::assumptions::Props::POSITIVE;
assumptions.known_true |= crate::base::assumptions::Props::REAL;
src.set_symbol_assumptions(sid, assumptions);
let mut dst = Arena::new();
let mut map = FxHashMap::default();
let new_x = transfer_subtree(&src, &mut dst, x_id, &mut map);
let new_sid = match dst.node(new_x) {
ExprNode::Symbol(s) => *s,
_ => panic!("expected Symbol"),
};
let new_assumptions = dst.symbol_assumptions(new_sid);
assert!(
new_assumptions
.known_true
.contains(crate::base::assumptions::Props::POSITIVE)
);
assert!(
new_assumptions
.known_true
.contains(crate::base::assumptions::Props::REAL)
);
}
#[test]
fn transfer_constants() {
let src = Arena::new();
let pi = src.pi();
let mut dst = Arena::new();
let mut map = FxHashMap::default();
let new_pi = transfer_subtree(&src, &mut dst, pi, &mut map);
assert!(matches!(dst.node(new_pi), ExprNode::Pi));
}
#[test]
fn transfer_reduces_arena_size() {
let mut src = Arena::new();
let x = src.symbol("x");
let _junk1 = src.sin(x);
let _junk2 = src.cos(x);
let _junk3 = src.exp(x);
let _junk4 = src.ln(x);
let two = src.int(2);
let _junk5 = src.pow(x, two);
let three = src.int(3);
let _junk6 = src.pow(x, three);
let src_count = src.node_count();
let mut dst = Arena::new();
let dst_baseline = dst.node_count(); let mut map = FxHashMap::default();
let _new_x = transfer_subtree(&src, &mut dst, x, &mut map);
let dst_count = dst.node_count();
assert_eq!(dst_count, dst_baseline + 1);
assert!(
dst_count < src_count,
"dst ({}) should be smaller than src ({})",
dst_count,
src_count
);
}
#[test]
fn transfer_new_constants_and_function_nodes() {
let mut src = Arena::new();
let z = src.symbol("z");
let n = src.symbol("n");
let re_z = src.intern(ExprNode::Re(z));
let conj_z = src.intern(ExprNode::Conjugate(z));
let pg = src.intern(ExprNode::Polygamma(n, z));
let kd = src.intern(ExprNode::KroneckerDelta(n, z));
let si = src.intern(ExprNode::Si(z));
let consts = src.add(&[src.euler_gamma, src.catalan, src.golden_ratio]);
let root = src.add(&[re_z, conj_z, pg, kd, si, consts]);
let mut dst = Arena::new();
let mut map = FxHashMap::default();
let new_root = transfer_subtree(&src, &mut dst, root, &mut map);
assert_eq!(
dst.display(new_root).to_string(),
src.display(root).to_string()
);
assert_eq!(map[&src.euler_gamma], dst.euler_gamma);
assert_eq!(map[&src.catalan], dst.catalan);
assert_eq!(map[&src.golden_ratio], dst.golden_ratio);
}
}