use rustc_hash::FxHashMap;
use crate::base::arena::Arena;
use crate::base::node::{ExprId, ExprNode};
pub(crate) fn subs(arena: &mut Arena, expr: ExprId, old: ExprId, new: ExprId) -> ExprId {
if old == new {
return expr;
}
if expr == old {
return new;
}
if arena.node(expr).is_atom() {
return expr;
}
let map: FxHashMap<ExprId, ExprId> = std::iter::once((old, new)).collect();
subs_scoped(arena, expr, &map)
}
pub(crate) fn subs_map(
arena: &mut Arena,
expr: ExprId,
replacements: &[(ExprId, ExprId)],
) -> ExprId {
if replacements.is_empty() {
return expr;
}
let map: FxHashMap<ExprId, ExprId> = replacements.iter().copied().collect();
if let Some(&new) = map.get(&expr) {
return new;
}
if arena.node(expr).is_atom() {
return expr;
}
subs_scoped(arena, expr, &map)
}
fn subs_scoped(arena: &mut Arena, expr: ExprId, map: &FxHashMap<ExprId, ExprId>) -> ExprId {
let post_order = crate::base::walk::post_order_ids(arena, expr);
let mut cache: FxHashMap<ExprId, ExprId> = FxHashMap::default();
for &id in &post_order {
if let Some(&new) = map.get(&id) {
cache.insert(id, new);
continue;
}
let binder = match arena.node(id) {
ExprNode::DefiniteIntegral(body, var, lo, hi) if map.contains_key(var) => {
Some((*body, *var, *lo, *hi))
}
_ => None,
};
let rebuilt = if let Some((body, var, lo, hi)) = binder {
let nlo = cache.get(&lo).copied().unwrap_or(lo);
let nhi = cache.get(&hi).copied().unwrap_or(hi);
let nbody = if map.len() == 1 {
body
} else {
let inner: FxHashMap<ExprId, ExprId> = map
.iter()
.filter(|(k, _)| **k != var)
.map(|(k, v)| (*k, *v))
.collect();
subs_scoped(arena, body, &inner)
};
arena.definite_integral(nbody, var, nlo, nhi)
} else if arena.node(id).is_atom() {
id
} else {
crate::base::walk::rebuild_with_cache(arena, id, &cache)
};
cache.insert(id, rebuilt);
}
cache.get(&expr).copied().unwrap_or(expr)
}
pub(crate) fn eval_derivatives(arena: &mut Arena, expr: ExprId) -> ExprId {
let post_order = crate::base::walk::post_order_ids(arena, expr);
let mut cache: FxHashMap<ExprId, ExprId> = FxHashMap::default();
for &id in &post_order {
let node = arena.node(id).clone();
match node {
ExprNode::Derivative(inner, var) => {
let new_inner = cache.get(&inner).copied().unwrap_or(inner);
let new_var = cache.get(&var).copied().unwrap_or(var);
let result = crate::transforms::diff::diff(arena, new_inner, new_var);
cache.insert(id, result);
}
_ => {
let new_id = crate::base::walk::rebuild_with_cache(arena, id, &cache);
cache.insert(id, new_id);
}
}
}
cache.get(&expr).copied().unwrap_or(expr)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::base::arena::Arena;
fn sym(a: &mut Arena, name: &str) -> ExprId {
a.symbol(name)
}
fn display(a: &Arena, id: ExprId) -> String {
a.display(id).to_string()
}
#[test]
fn subs_symbol_for_number() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let three = a.int(3);
let expr = a.add(&[x, a.one]);
let result = subs(&mut a, expr, x, three);
assert_eq!(display(&a, result), "4");
}
#[test]
fn subs_symbol_in_product() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let two = a.int(2);
let expr = a.mul(&[two, x]);
let result = subs(&mut a, expr, x, y);
assert_eq!(display(&a, result), "2*y");
}
#[test]
fn subs_in_pow() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let two = a.int(2);
let expr = a.pow(x, two);
let three = a.int(3);
let result = subs(&mut a, expr, x, three);
assert_eq!(display(&a, result), "9");
}
#[test]
fn subs_in_sin() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let expr = a.sin(x);
let result = subs(&mut a, expr, x, y);
assert_eq!(display(&a, result), "sin(y)");
}
#[test]
fn subs_no_match_returns_same_id() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let expr = a.add(&[x, a.one]);
let n99 = a.int(99);
let result = subs(&mut a, expr, y, n99);
assert_eq!(result, expr, "no match should return same ExprId");
}
#[test]
fn subs_entire_expression() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let result = subs(&mut a, x, x, y);
assert_eq!(result, y);
}
#[test]
fn subs_old_equals_new_is_noop() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let expr = a.add(&[x, a.one]);
let result = subs(&mut a, expr, x, x);
assert_eq!(result, expr);
}
#[test]
fn subs_in_nested_add_mul() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let two = a.int(2);
let three = a.int(3);
let x_sq = a.pow(x, two);
let two_x = a.mul(&[two, x]);
let expr = a.add(&[x_sq, two_x, a.one]);
let result = subs(&mut a, expr, x, three);
assert_eq!(display(&a, result), "16");
}
#[test]
fn subs_in_function_of_pow() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let two = a.int(2);
let xp = a.pow(x, two);
let expr = a.sin(xp);
let result = subs(&mut a, expr, x, y);
assert_eq!(display(&a, result), "sin(y^2)");
}
#[test]
fn subs_replaces_all_occurrences() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let expr = a.add(&[x, x]);
let result = subs(&mut a, expr, x, y);
assert_eq!(display(&a, result), "2*y");
}
#[test]
fn subs_does_not_match_algebraic_subexpressions() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let two = a.int(2);
let one = a.one;
let x_inv = a.pow(x, a.neg_one);
let x_sq = a.pow(x, two);
let result = subs(&mut a, x_inv, x_sq, one);
assert_eq!(
result, x_inv,
"structural subs should not match x^2 in x^(-1)"
);
}
#[test]
fn subs_with_zero_evaluates() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let zero = a.zero;
let expr = a.mul(&[x, y]);
let result = subs(&mut a, expr, y, zero);
assert_eq!(result, a.zero);
}
#[test]
fn subs_map_simultaneous() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let expr = a.add(&[x, y]);
let result = subs_map(&mut a, expr, &[(x, y), (y, x)]);
assert_eq!(result, expr, "swapping x↔y in x+y should give x+y");
}
#[test]
fn subs_map_empty_is_noop() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let expr = a.add(&[x, a.one]);
let result = subs_map(&mut a, expr, &[]);
assert_eq!(result, expr);
}
#[test]
fn subs_map_multiple() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let two = a.int(2);
let three = a.int(3);
let expr = a.add(&[x, y]);
let result = subs_map(&mut a, expr, &[(x, two), (y, three)]);
assert_eq!(display(&a, result), "5");
}
#[test]
fn subs_deep_expression_no_stack_overflow() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let mut expr = x;
for _ in 0..10_000 {
expr = a.sin(expr);
}
let result = subs(&mut a, expr, x, y);
assert_ne!(
result, expr,
"substitution should have changed the deep expression"
);
}
#[test]
fn subs_moderate_depth_display_works() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let y = sym(&mut a, "y");
let mut expr = x;
for _ in 0..50 {
expr = a.sin(expr);
}
let result = subs(&mut a, expr, x, y);
let s = format!("{}", a.display(result));
assert!(s.starts_with("sin("), "should still start with sin(");
assert!(s.contains('y'), "should contain y after substitution");
assert!(!s.contains('x'), "should not contain x after substitution");
}
#[test]
fn eval_derivatives_sin_x() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let sin_x = a.sin(x);
let formal = a.intern(crate::base::node::ExprNode::Derivative(sin_x, x));
let result = super::eval_derivatives(&mut a, formal);
assert_eq!(display(&a, result), "cos(x)");
}
#[test]
fn eval_derivatives_nested() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let two = a.int(2);
let x2 = a.pow(x, two);
let d1 = a.intern(crate::base::node::ExprNode::Derivative(x2, x));
let d2 = a.intern(crate::base::node::ExprNode::Derivative(d1, x));
let result = super::eval_derivatives(&mut a, d2);
assert_eq!(display(&a, result), "2");
}
#[test]
fn eval_derivatives_no_derivative_unchanged() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let two = a.int(2);
let x2 = a.pow(x, two);
let expr = a.add(&[x2, a.one]);
let result = super::eval_derivatives(&mut a, expr);
assert_eq!(result, expr);
}
#[test]
fn eval_derivatives_inside_add() {
let mut a = Arena::new();
let x = sym(&mut a, "x");
let two = a.int(2);
let x2 = a.pow(x, two);
let d = a.intern(crate::base::node::ExprNode::Derivative(x2, x));
let expr = a.add(&[x, d]);
let result = super::eval_derivatives(&mut a, expr);
assert_eq!(display(&a, result), "3*x");
}
}