use num_bigint::BigInt;
use num_rational::Ratio;
use serde::{Deserialize, Serialize};
use crate::base::arena::Arena;
use crate::base::node::{ExprId, ExprNode};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ExprTree {
Num {
numer: String,
denom: String,
},
Symbol {
name: String,
},
Pi,
E,
ImaginaryUnit,
EulerGamma,
Catalan,
GoldenRatio,
PhysicalConstant {
name: String,
value: Box<ExprTree>,
},
Infinity,
NegInfinity,
ComplexInfinity,
NaN,
Add {
terms: Vec<ExprTree>,
},
Mul {
factors: Vec<ExprTree>,
},
Pow {
base: Box<ExprTree>,
exp: Box<ExprTree>,
},
Neg {
inner: Box<ExprTree>,
},
Sin {
arg: Box<ExprTree>,
},
Cos {
arg: Box<ExprTree>,
},
Tan {
arg: Box<ExprTree>,
},
Exp {
arg: Box<ExprTree>,
},
Ln {
arg: Box<ExprTree>,
},
Sqrt {
arg: Box<ExprTree>,
},
Abs {
arg: Box<ExprTree>,
},
Asin {
arg: Box<ExprTree>,
},
Acos {
arg: Box<ExprTree>,
},
Atan {
arg: Box<ExprTree>,
},
Atan2 {
y: Box<ExprTree>,
x: Box<ExprTree>,
},
Sinh {
arg: Box<ExprTree>,
},
Cosh {
arg: Box<ExprTree>,
},
Tanh {
arg: Box<ExprTree>,
},
Asinh {
arg: Box<ExprTree>,
},
Acosh {
arg: Box<ExprTree>,
},
Atanh {
arg: Box<ExprTree>,
},
Sign {
arg: Box<ExprTree>,
},
Heaviside {
arg: Box<ExprTree>,
},
DiracDelta {
arg: Box<ExprTree>,
},
Gamma {
arg: Box<ExprTree>,
},
LogGamma {
arg: Box<ExprTree>,
},
Digamma {
arg: Box<ExprTree>,
},
Erf {
arg: Box<ExprTree>,
},
Erfc {
arg: Box<ExprTree>,
},
LambertW {
arg: Box<ExprTree>,
},
Beta {
a: Box<ExprTree>,
b: Box<ExprTree>,
},
Re {
arg: Box<ExprTree>,
},
Im {
arg: Box<ExprTree>,
},
Conjugate {
arg: Box<ExprTree>,
},
Arg {
arg: Box<ExprTree>,
},
Si {
arg: Box<ExprTree>,
},
Ci {
arg: Box<ExprTree>,
},
Ei {
arg: Box<ExprTree>,
},
Li {
arg: Box<ExprTree>,
},
Zeta {
arg: Box<ExprTree>,
},
Polygamma {
n: Box<ExprTree>,
arg: Box<ExprTree>,
},
KroneckerDelta {
i: Box<ExprTree>,
j: Box<ExprTree>,
},
Floor {
arg: Box<ExprTree>,
},
Ceiling {
arg: Box<ExprTree>,
},
Min {
args: Vec<ExprTree>,
},
Max {
args: Vec<ExprTree>,
},
BoolTrue,
BoolFalse,
Gt {
lhs: Box<ExprTree>,
rhs: Box<ExprTree>,
},
Ge {
lhs: Box<ExprTree>,
rhs: Box<ExprTree>,
},
Eq_ {
lhs: Box<ExprTree>,
rhs: Box<ExprTree>,
},
Ne {
lhs: Box<ExprTree>,
rhs: Box<ExprTree>,
},
And {
args: Vec<ExprTree>,
},
Or {
args: Vec<ExprTree>,
},
Not {
arg: Box<ExprTree>,
},
Piecewise {
pieces: Vec<(ExprTree, ExprTree)>,
},
Apply {
name: String,
args: Vec<ExprTree>,
},
Derivative {
body: Box<ExprTree>,
var: Box<ExprTree>,
},
Integral {
body: Box<ExprTree>,
var: Box<ExprTree>,
},
DefiniteIntegral {
body: Box<ExprTree>,
var: Box<ExprTree>,
lower: Box<ExprTree>,
upper: Box<ExprTree>,
},
Sum {
body: Box<ExprTree>,
var: Box<ExprTree>,
lower: Box<ExprTree>,
upper: Box<ExprTree>,
},
Product_ {
body: Box<ExprTree>,
var: Box<ExprTree>,
lower: Box<ExprTree>,
upper: Box<ExprTree>,
},
EmptySet,
UniversalSet,
Interval {
start: Box<ExprTree>,
end: Box<ExprTree>,
flags: u8,
},
FiniteSet {
elements: Vec<ExprTree>,
},
SetUnion {
sets: Vec<ExprTree>,
},
SetIntersection {
sets: Vec<ExprTree>,
},
SetComplement {
set: Box<ExprTree>,
universe: Box<ExprTree>,
},
Limit {
body: Box<ExprTree>,
var: Box<ExprTree>,
point: Box<ExprTree>,
},
Series {
body: Box<ExprTree>,
var: Box<ExprTree>,
point: Box<ExprTree>,
order: Box<ExprTree>,
},
LaplaceTransform {
body: Box<ExprTree>,
t: Box<ExprTree>,
s: Box<ExprTree>,
},
InverseLaplaceTransform {
body: Box<ExprTree>,
s: Box<ExprTree>,
t: Box<ExprTree>,
},
Residue {
body: Box<ExprTree>,
var: Box<ExprTree>,
point: Box<ExprTree>,
},
RootOf {
poly: Box<ExprTree>,
index: Box<ExprTree>,
},
DSolve {
expr: Box<ExprTree>,
func: Box<ExprTree>,
var: Box<ExprTree>,
},
RootSum {
poly: Box<ExprTree>,
body: Box<ExprTree>,
sumvar: Box<ExprTree>,
},
ConditionSet {
var: Box<ExprTree>,
condition: Box<ExprTree>,
},
}
pub(crate) fn expr_to_tree(arena: &Arena, id: ExprId) -> ExprTree {
match arena.node(id).clone() {
ExprNode::Num(nid) => {
let r = arena.num(nid);
ExprTree::Num {
numer: r.numer().to_string(),
denom: r.denom().to_string(),
}
}
ExprNode::Symbol(sid) => ExprTree::Symbol {
name: arena.symbol_name(sid).to_owned(),
},
ExprNode::Pi => ExprTree::Pi,
ExprNode::E => ExprTree::E,
ExprNode::ImaginaryUnit => ExprTree::ImaginaryUnit,
ExprNode::EulerGamma => ExprTree::EulerGamma,
ExprNode::Catalan => ExprTree::Catalan,
ExprNode::GoldenRatio => ExprTree::GoldenRatio,
ExprNode::PhysicalConstant(name_id, value_id) => ExprTree::PhysicalConstant {
name: arena.symbol_name(name_id).to_owned(),
value: Box::new(expr_to_tree(arena, value_id)),
},
ExprNode::Infinity => ExprTree::Infinity,
ExprNode::NegInfinity => ExprTree::NegInfinity,
ExprNode::ComplexInfinity => ExprTree::ComplexInfinity,
ExprNode::NaN => ExprTree::NaN,
ExprNode::Add(children) => ExprTree::Add {
terms: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
},
ExprNode::Mul(children) => ExprTree::Mul {
factors: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
},
ExprNode::Pow(base, exp) => ExprTree::Pow {
base: Box::new(expr_to_tree(arena, base)),
exp: Box::new(expr_to_tree(arena, exp)),
},
ExprNode::Neg(inner) => ExprTree::Neg {
inner: Box::new(expr_to_tree(arena, inner)),
},
ExprNode::Sin(x) => ExprTree::Sin {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Cos(x) => ExprTree::Cos {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Tan(x) => ExprTree::Tan {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Exp(x) => ExprTree::Exp {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Ln(x) => ExprTree::Ln {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Abs(x) => ExprTree::Abs {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Asin(x) => ExprTree::Asin {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Acos(x) => ExprTree::Acos {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Atan(x) => ExprTree::Atan {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Atan2(y, x) => ExprTree::Atan2 {
y: Box::new(expr_to_tree(arena, y)),
x: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Sinh(x) => ExprTree::Sinh {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Cosh(x) => ExprTree::Cosh {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Tanh(x) => ExprTree::Tanh {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Asinh(x) => ExprTree::Asinh {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Acosh(x) => ExprTree::Acosh {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Atanh(x) => ExprTree::Atanh {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Sign(x) => ExprTree::Sign {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Heaviside(x) => ExprTree::Heaviside {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::DiracDelta(x) => ExprTree::DiracDelta {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Gamma(x) => ExprTree::Gamma {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::LogGamma(x) => ExprTree::LogGamma {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Digamma(x) => ExprTree::Digamma {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Erf(x) => ExprTree::Erf {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Erfc(x) => ExprTree::Erfc {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::LambertW(x) => ExprTree::LambertW {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Beta(a, b) => ExprTree::Beta {
a: Box::new(expr_to_tree(arena, a)),
b: Box::new(expr_to_tree(arena, b)),
},
ExprNode::Re(x) => ExprTree::Re {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Im(x) => ExprTree::Im {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Conjugate(x) => ExprTree::Conjugate {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Arg(x) => ExprTree::Arg {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Si(x) => ExprTree::Si {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Ci(x) => ExprTree::Ci {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Ei(x) => ExprTree::Ei {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Li(x) => ExprTree::Li {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Zeta(x) => ExprTree::Zeta {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Polygamma(n, x) => ExprTree::Polygamma {
n: Box::new(expr_to_tree(arena, n)),
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::KroneckerDelta(i, j) => ExprTree::KroneckerDelta {
i: Box::new(expr_to_tree(arena, i)),
j: Box::new(expr_to_tree(arena, j)),
},
ExprNode::Floor(x) => ExprTree::Floor {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Ceiling(x) => ExprTree::Ceiling {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Min(children) => ExprTree::Min {
args: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
},
ExprNode::Max(children) => ExprTree::Max {
args: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
},
ExprNode::Apply(sid, args) => ExprTree::Apply {
name: arena.symbol_name(sid).to_owned(),
args: args.iter().map(|&a| expr_to_tree(arena, a)).collect(),
},
ExprNode::Derivative(body, var) => ExprTree::Derivative {
body: Box::new(expr_to_tree(arena, body)),
var: Box::new(expr_to_tree(arena, var)),
},
ExprNode::Integral(body, var) => ExprTree::Integral {
body: Box::new(expr_to_tree(arena, body)),
var: Box::new(expr_to_tree(arena, var)),
},
ExprNode::DefiniteIntegral(body, var, lo, hi) => ExprTree::DefiniteIntegral {
body: Box::new(expr_to_tree(arena, body)),
var: Box::new(expr_to_tree(arena, var)),
lower: Box::new(expr_to_tree(arena, lo)),
upper: Box::new(expr_to_tree(arena, hi)),
},
ExprNode::Sum(body, var, lo, hi) => ExprTree::Sum {
body: Box::new(expr_to_tree(arena, body)),
var: Box::new(expr_to_tree(arena, var)),
lower: Box::new(expr_to_tree(arena, lo)),
upper: Box::new(expr_to_tree(arena, hi)),
},
ExprNode::Product_(body, var, lo, hi) => ExprTree::Product_ {
body: Box::new(expr_to_tree(arena, body)),
var: Box::new(expr_to_tree(arena, var)),
lower: Box::new(expr_to_tree(arena, lo)),
upper: Box::new(expr_to_tree(arena, hi)),
},
ExprNode::Factorial(x) => ExprTree::Apply {
name: "factorial".to_owned(),
args: vec![expr_to_tree(arena, x)],
},
ExprNode::Binomial(n, k) => ExprTree::Apply {
name: "binomial".to_owned(),
args: vec![expr_to_tree(arena, n), expr_to_tree(arena, k)],
},
ExprNode::BoolTrue => ExprTree::BoolTrue,
ExprNode::BoolFalse => ExprTree::BoolFalse,
ExprNode::Gt(a, b) => ExprTree::Gt {
lhs: Box::new(expr_to_tree(arena, a)),
rhs: Box::new(expr_to_tree(arena, b)),
},
ExprNode::Ge(a, b) => ExprTree::Ge {
lhs: Box::new(expr_to_tree(arena, a)),
rhs: Box::new(expr_to_tree(arena, b)),
},
ExprNode::Eq_(a, b) => ExprTree::Eq_ {
lhs: Box::new(expr_to_tree(arena, a)),
rhs: Box::new(expr_to_tree(arena, b)),
},
ExprNode::Ne(a, b) => ExprTree::Ne {
lhs: Box::new(expr_to_tree(arena, a)),
rhs: Box::new(expr_to_tree(arena, b)),
},
ExprNode::And(children) => ExprTree::And {
args: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
},
ExprNode::Or(children) => ExprTree::Or {
args: children.iter().map(|&c| expr_to_tree(arena, c)).collect(),
},
ExprNode::Not(x) => ExprTree::Not {
arg: Box::new(expr_to_tree(arena, x)),
},
ExprNode::Piecewise(children) => ExprTree::Piecewise {
pieces: children
.iter()
.map(|&(val, cond)| (expr_to_tree(arena, val), expr_to_tree(arena, cond)))
.collect(),
},
ExprNode::EmptySet => ExprTree::EmptySet,
ExprNode::UniversalSet => ExprTree::UniversalSet,
ExprNode::Interval(start, end, flags) => ExprTree::Interval {
start: Box::new(expr_to_tree(arena, start)),
end: Box::new(expr_to_tree(arena, end)),
flags,
},
ExprNode::FiniteSet(elems) => ExprTree::FiniteSet {
elements: elems.iter().map(|&e| expr_to_tree(arena, e)).collect(),
},
ExprNode::SetUnion(sets) => ExprTree::SetUnion {
sets: sets.iter().map(|&s| expr_to_tree(arena, s)).collect(),
},
ExprNode::SetIntersection(sets) => ExprTree::SetIntersection {
sets: sets.iter().map(|&s| expr_to_tree(arena, s)).collect(),
},
ExprNode::SetComplement(a, b) => ExprTree::SetComplement {
set: Box::new(expr_to_tree(arena, a)),
universe: Box::new(expr_to_tree(arena, b)),
},
ExprNode::Limit(body, var, point) => ExprTree::Limit {
body: Box::new(expr_to_tree(arena, body)),
var: Box::new(expr_to_tree(arena, var)),
point: Box::new(expr_to_tree(arena, point)),
},
ExprNode::Series(body, var, point, order) => ExprTree::Series {
body: Box::new(expr_to_tree(arena, body)),
var: Box::new(expr_to_tree(arena, var)),
point: Box::new(expr_to_tree(arena, point)),
order: Box::new(expr_to_tree(arena, order)),
},
ExprNode::LaplaceTransform(body, t, s) => ExprTree::LaplaceTransform {
body: Box::new(expr_to_tree(arena, body)),
t: Box::new(expr_to_tree(arena, t)),
s: Box::new(expr_to_tree(arena, s)),
},
ExprNode::InverseLaplaceTransform(body, s, t) => ExprTree::InverseLaplaceTransform {
body: Box::new(expr_to_tree(arena, body)),
s: Box::new(expr_to_tree(arena, s)),
t: Box::new(expr_to_tree(arena, t)),
},
ExprNode::Residue(body, var, point) => ExprTree::Residue {
body: Box::new(expr_to_tree(arena, body)),
var: Box::new(expr_to_tree(arena, var)),
point: Box::new(expr_to_tree(arena, point)),
},
ExprNode::RootOf(poly, index) => ExprTree::RootOf {
poly: Box::new(expr_to_tree(arena, poly)),
index: Box::new(expr_to_tree(arena, index)),
},
ExprNode::DSolve(expr, func, var) => ExprTree::DSolve {
expr: Box::new(expr_to_tree(arena, expr)),
func: Box::new(expr_to_tree(arena, func)),
var: Box::new(expr_to_tree(arena, var)),
},
ExprNode::RootSum(poly, body, sumvar) => ExprTree::RootSum {
poly: Box::new(expr_to_tree(arena, poly)),
body: Box::new(expr_to_tree(arena, body)),
sumvar: Box::new(expr_to_tree(arena, sumvar)),
},
ExprNode::ConditionSet(var, condition) => ExprTree::ConditionSet {
var: Box::new(expr_to_tree(arena, var)),
condition: Box::new(expr_to_tree(arena, condition)),
},
}
}
pub(crate) fn tree_to_expr(arena: &mut Arena, tree: &ExprTree) -> ExprId {
match tree {
ExprTree::Num { numer, denom } => {
let n: BigInt = numer.parse().unwrap_or_default();
let d: BigInt = denom.parse().unwrap_or_else(|_| BigInt::from(1));
let r = Ratio::new(n, d);
let nid = arena.intern_num(r);
arena.intern(ExprNode::Num(nid))
}
ExprTree::Symbol { name } => arena.symbol(name),
ExprTree::Pi => arena.pi,
ExprTree::E => arena.e_const,
ExprTree::ImaginaryUnit => arena.i_unit,
ExprTree::EulerGamma => arena.euler_gamma,
ExprTree::Catalan => arena.catalan,
ExprTree::GoldenRatio => arena.golden_ratio,
ExprTree::PhysicalConstant { name, value } => {
let val_id = tree_to_expr(arena, value);
arena.physical_constant(name, val_id)
}
ExprTree::Infinity => arena.infinity,
ExprTree::NegInfinity => arena.neg_infinity,
ExprTree::ComplexInfinity => arena.complex_infinity,
ExprTree::NaN => arena.nan,
ExprTree::Add { terms } => {
let ids: Vec<ExprId> = terms.iter().map(|t| tree_to_expr(arena, t)).collect();
arena.add(&ids)
}
ExprTree::Mul { factors } => {
let ids: Vec<ExprId> = factors.iter().map(|f| tree_to_expr(arena, f)).collect();
arena.mul(&ids)
}
ExprTree::Pow { base, exp } => {
let b = tree_to_expr(arena, base);
let e = tree_to_expr(arena, exp);
arena.pow(b, e)
}
ExprTree::Neg { inner } => {
let x = tree_to_expr(arena, inner);
arena.neg(x)
}
ExprTree::Sin { arg } => {
let x = tree_to_expr(arena, arg);
arena.sin(x)
}
ExprTree::Cos { arg } => {
let x = tree_to_expr(arena, arg);
arena.cos(x)
}
ExprTree::Tan { arg } => {
let x = tree_to_expr(arena, arg);
arena.tan(x)
}
ExprTree::Exp { arg } => {
let x = tree_to_expr(arena, arg);
arena.exp(x)
}
ExprTree::Ln { arg } => {
let x = tree_to_expr(arena, arg);
arena.ln(x)
}
ExprTree::Sqrt { arg } => {
let x = tree_to_expr(arena, arg);
arena.sqrt(x) }
ExprTree::Abs { arg } => {
let x = tree_to_expr(arena, arg);
arena.abs(x)
}
ExprTree::Asin { arg } => {
let x = tree_to_expr(arena, arg);
arena.asin(x)
}
ExprTree::Acos { arg } => {
let x = tree_to_expr(arena, arg);
arena.acos(x)
}
ExprTree::Atan { arg } => {
let x = tree_to_expr(arena, arg);
arena.atan(x)
}
ExprTree::Atan2 { y, x } => {
let yid = tree_to_expr(arena, y);
let xid = tree_to_expr(arena, x);
arena.atan2(yid, xid)
}
ExprTree::Sinh { arg } => {
let x = tree_to_expr(arena, arg);
arena.sinh(x)
}
ExprTree::Cosh { arg } => {
let x = tree_to_expr(arena, arg);
arena.cosh(x)
}
ExprTree::Tanh { arg } => {
let x = tree_to_expr(arena, arg);
arena.tanh(x)
}
ExprTree::Asinh { arg } => {
let x = tree_to_expr(arena, arg);
arena.asinh(x)
}
ExprTree::Acosh { arg } => {
let x = tree_to_expr(arena, arg);
arena.acosh(x)
}
ExprTree::Atanh { arg } => {
let x = tree_to_expr(arena, arg);
arena.atanh(x)
}
ExprTree::Sign { arg } => {
let x = tree_to_expr(arena, arg);
arena.sign(x)
}
ExprTree::Heaviside { arg } => {
let x = tree_to_expr(arena, arg);
arena.heaviside(x)
}
ExprTree::DiracDelta { arg } => {
let x = tree_to_expr(arena, arg);
arena.dirac_delta(x)
}
ExprTree::Gamma { arg } => {
let x = tree_to_expr(arena, arg);
arena.gamma(x)
}
ExprTree::LogGamma { arg } => {
let x = tree_to_expr(arena, arg);
arena.log_gamma(x)
}
ExprTree::Digamma { arg } => {
let x = tree_to_expr(arena, arg);
arena.digamma(x)
}
ExprTree::Erf { arg } => {
let x = tree_to_expr(arena, arg);
arena.erf(x)
}
ExprTree::Erfc { arg } => {
let x = tree_to_expr(arena, arg);
arena.erfc(x)
}
ExprTree::LambertW { arg } => {
let x = tree_to_expr(arena, arg);
arena.lambertw(x)
}
ExprTree::Beta { a, b } => {
let aid = tree_to_expr(arena, a);
let bid = tree_to_expr(arena, b);
arena.beta(aid, bid)
}
ExprTree::Re { arg } => {
let x = tree_to_expr(arena, arg);
arena.re(x)
}
ExprTree::Im { arg } => {
let x = tree_to_expr(arena, arg);
arena.im(x)
}
ExprTree::Conjugate { arg } => {
let x = tree_to_expr(arena, arg);
arena.conjugate(x)
}
ExprTree::Arg { arg } => {
let x = tree_to_expr(arena, arg);
arena.arg(x)
}
ExprTree::Si { arg } => {
let x = tree_to_expr(arena, arg);
arena.si(x)
}
ExprTree::Ci { arg } => {
let x = tree_to_expr(arena, arg);
arena.ci(x)
}
ExprTree::Ei { arg } => {
let x = tree_to_expr(arena, arg);
arena.ei(x)
}
ExprTree::Li { arg } => {
let x = tree_to_expr(arena, arg);
arena.li(x)
}
ExprTree::Zeta { arg } => {
let x = tree_to_expr(arena, arg);
arena.zeta(x)
}
ExprTree::Polygamma { n, arg } => {
let nid = tree_to_expr(arena, n);
let x = tree_to_expr(arena, arg);
arena.polygamma(nid, x)
}
ExprTree::KroneckerDelta { i, j } => {
let iid = tree_to_expr(arena, i);
let jid = tree_to_expr(arena, j);
arena.kronecker_delta(iid, jid)
}
ExprTree::Floor { arg } => {
let x = tree_to_expr(arena, arg);
arena.floor(x)
}
ExprTree::Ceiling { arg } => {
let x = tree_to_expr(arena, arg);
arena.ceiling(x)
}
ExprTree::Min { args } => {
let ids: smallvec::SmallVec<[ExprId; 4]> =
args.iter().map(|a| tree_to_expr(arena, a)).collect();
arena.intern(ExprNode::Min(ids))
}
ExprTree::Max { args } => {
let ids: smallvec::SmallVec<[ExprId; 4]> =
args.iter().map(|a| tree_to_expr(arena, a)).collect();
arena.intern(ExprNode::Max(ids))
}
ExprTree::Apply { name, args } if name == "factorial" && args.len() == 1 => {
let x = tree_to_expr(arena, &args[0]);
arena.factorial(x)
}
ExprTree::Apply { name, args } if name == "binomial" && args.len() == 2 => {
let n = tree_to_expr(arena, &args[0]);
let k = tree_to_expr(arena, &args[1]);
arena.binomial(n, k)
}
ExprTree::Apply { name, args } => {
let sym_id = arena.symbols.intern(name);
let arg_ids: Vec<ExprId> = args.iter().map(|a| tree_to_expr(arena, a)).collect();
let sv: smallvec::SmallVec<[ExprId; 2]> = arg_ids.into_iter().collect();
arena.intern(ExprNode::Apply(sym_id, sv))
}
ExprTree::Derivative { body, var } => {
let b = tree_to_expr(arena, body);
let v = tree_to_expr(arena, var);
arena.intern(ExprNode::Derivative(b, v))
}
ExprTree::Integral { body, var } => {
let b = tree_to_expr(arena, body);
let v = tree_to_expr(arena, var);
arena.intern(ExprNode::Integral(b, v))
}
ExprTree::DefiniteIntegral {
body,
var,
lower,
upper,
} => {
let b = tree_to_expr(arena, body);
let v = tree_to_expr(arena, var);
let lo = tree_to_expr(arena, lower);
let hi = tree_to_expr(arena, upper);
arena.definite_integral(b, v, lo, hi)
}
ExprTree::Sum {
body,
var,
lower,
upper,
} => {
let b = tree_to_expr(arena, body);
let v = tree_to_expr(arena, var);
let lo = tree_to_expr(arena, lower);
let hi = tree_to_expr(arena, upper);
arena.intern(ExprNode::Sum(b, v, lo, hi))
}
ExprTree::Product_ {
body,
var,
lower,
upper,
} => {
let b = tree_to_expr(arena, body);
let v = tree_to_expr(arena, var);
let lo = tree_to_expr(arena, lower);
let hi = tree_to_expr(arena, upper);
arena.intern(ExprNode::Product_(b, v, lo, hi))
}
ExprTree::BoolTrue => arena.bool_true,
ExprTree::BoolFalse => arena.bool_false,
ExprTree::Gt { lhs, rhs } => {
let l = tree_to_expr(arena, lhs);
let r = tree_to_expr(arena, rhs);
arena.gt(l, r)
}
ExprTree::Ge { lhs, rhs } => {
let l = tree_to_expr(arena, lhs);
let r = tree_to_expr(arena, rhs);
arena.ge(l, r)
}
ExprTree::Eq_ { lhs, rhs } => {
let l = tree_to_expr(arena, lhs);
let r = tree_to_expr(arena, rhs);
arena.eq_(l, r)
}
ExprTree::Ne { lhs, rhs } => {
let l = tree_to_expr(arena, lhs);
let r = tree_to_expr(arena, rhs);
arena.ne_(l, r)
}
ExprTree::And { args } => {
let ids: Vec<ExprId> = args.iter().map(|a| tree_to_expr(arena, a)).collect();
arena.and(&ids)
}
ExprTree::Or { args } => {
let ids: Vec<ExprId> = args.iter().map(|a| tree_to_expr(arena, a)).collect();
arena.or(&ids)
}
ExprTree::Not { arg } => {
let x = tree_to_expr(arena, arg);
arena.not(x)
}
ExprTree::Piecewise { pieces } => {
let pairs: smallvec::SmallVec<[(ExprId, ExprId); 3]> = pieces
.iter()
.map(|(val, cond)| (tree_to_expr(arena, val), tree_to_expr(arena, cond)))
.collect();
arena.intern(ExprNode::Piecewise(pairs))
}
ExprTree::EmptySet => arena.intern(ExprNode::EmptySet),
ExprTree::UniversalSet => arena.intern(ExprNode::UniversalSet),
ExprTree::Interval { start, end, flags } => {
let s = tree_to_expr(arena, start);
let e = tree_to_expr(arena, end);
arena.intern(ExprNode::Interval(s, e, *flags))
}
ExprTree::FiniteSet { elements } => {
let ids: smallvec::SmallVec<[ExprId; 4]> =
elements.iter().map(|e| tree_to_expr(arena, e)).collect();
arena.intern(ExprNode::FiniteSet(ids))
}
ExprTree::SetUnion { sets } => {
let ids: smallvec::SmallVec<[ExprId; 4]> =
sets.iter().map(|s| tree_to_expr(arena, s)).collect();
arena.intern(ExprNode::SetUnion(ids))
}
ExprTree::SetIntersection { sets } => {
let ids: smallvec::SmallVec<[ExprId; 4]> =
sets.iter().map(|s| tree_to_expr(arena, s)).collect();
arena.intern(ExprNode::SetIntersection(ids))
}
ExprTree::SetComplement { set, universe } => {
let s = tree_to_expr(arena, set);
let u = tree_to_expr(arena, universe);
arena.intern(ExprNode::SetComplement(s, u))
}
ExprTree::Limit { body, var, point } => {
let b = tree_to_expr(arena, body);
let v = tree_to_expr(arena, var);
let p = tree_to_expr(arena, point);
arena.intern(ExprNode::Limit(b, v, p))
}
ExprTree::Series {
body,
var,
point,
order,
} => {
let b = tree_to_expr(arena, body);
let v = tree_to_expr(arena, var);
let p = tree_to_expr(arena, point);
let o = tree_to_expr(arena, order);
arena.intern(ExprNode::Series(b, v, p, o))
}
ExprTree::LaplaceTransform { body, t, s } => {
let b = tree_to_expr(arena, body);
let ti = tree_to_expr(arena, t);
let si = tree_to_expr(arena, s);
arena.intern(ExprNode::LaplaceTransform(b, ti, si))
}
ExprTree::InverseLaplaceTransform { body, s, t } => {
let b = tree_to_expr(arena, body);
let si = tree_to_expr(arena, s);
let ti = tree_to_expr(arena, t);
arena.intern(ExprNode::InverseLaplaceTransform(b, si, ti))
}
ExprTree::Residue { body, var, point } => {
let b = tree_to_expr(arena, body);
let v = tree_to_expr(arena, var);
let p = tree_to_expr(arena, point);
arena.intern(ExprNode::Residue(b, v, p))
}
ExprTree::RootOf { poly, index } => {
let p = tree_to_expr(arena, poly);
let i = tree_to_expr(arena, index);
arena.intern(ExprNode::RootOf(p, i))
}
ExprTree::DSolve { expr, func, var } => {
let e = tree_to_expr(arena, expr);
let f = tree_to_expr(arena, func);
let v = tree_to_expr(arena, var);
arena.intern(ExprNode::DSolve(e, f, v))
}
ExprTree::RootSum { poly, body, sumvar } => {
let p = tree_to_expr(arena, poly);
let b = tree_to_expr(arena, body);
let s = tree_to_expr(arena, sumvar);
arena.intern(ExprNode::RootSum(p, b, s))
}
ExprTree::ConditionSet { var, condition } => {
let v = tree_to_expr(arena, var);
let c = tree_to_expr(arena, condition);
arena.intern(ExprNode::ConditionSet(v, c))
}
}
}
impl ExprTree {
fn head_and_children(&self) -> (String, Option<Vec<&ExprTree>>) {
type Parts<'t> = (String, Option<Vec<&'t ExprTree>>);
fn one<'t>(head: &str, a: &'t ExprTree) -> Parts<'t> {
(head.to_string(), Some(vec![a]))
}
fn two<'t>(head: &str, a: &'t ExprTree, b: &'t ExprTree) -> Parts<'t> {
(head.to_string(), Some(vec![a, b]))
}
fn many<'t>(head: &str, items: &'t [ExprTree]) -> Parts<'t> {
(head.to_string(), Some(items.iter().collect()))
}
fn atom<'t>(head: &str) -> Parts<'t> {
(head.to_string(), None)
}
match self {
ExprTree::Num { numer, denom } => {
if denom == "1" {
atom(&format!("Integer({numer})"))
} else {
atom(&format!("Rational({numer}, {denom})"))
}
}
ExprTree::Symbol { name } => atom(&format!("Symbol('{name}')")),
ExprTree::Pi => atom("pi"),
ExprTree::E => atom("E"),
ExprTree::ImaginaryUnit => atom("I"),
ExprTree::EulerGamma => atom("EulerGamma"),
ExprTree::Catalan => atom("Catalan"),
ExprTree::GoldenRatio => atom("GoldenRatio"),
ExprTree::PhysicalConstant { name, value } => {
one(&format!("PhysicalConstant('{name}')"), value)
}
ExprTree::Infinity => atom("oo"),
ExprTree::NegInfinity => atom("-oo"),
ExprTree::ComplexInfinity => atom("zoo"),
ExprTree::NaN => atom("nan"),
ExprTree::Add { terms } => many("Add", terms),
ExprTree::Mul { factors } => many("Mul", factors),
ExprTree::Pow { base, exp } => two("Pow", base, exp),
ExprTree::Neg { inner } => one("Neg", inner),
ExprTree::Sin { arg } => one("sin", arg),
ExprTree::Cos { arg } => one("cos", arg),
ExprTree::Tan { arg } => one("tan", arg),
ExprTree::Exp { arg } => one("exp", arg),
ExprTree::Ln { arg } => one("log", arg),
ExprTree::Sqrt { arg } => one("sqrt", arg),
ExprTree::Abs { arg } => one("Abs", arg),
ExprTree::Asin { arg } => one("asin", arg),
ExprTree::Acos { arg } => one("acos", arg),
ExprTree::Atan { arg } => one("atan", arg),
ExprTree::Atan2 { y, x } => two("atan2", y, x),
ExprTree::Sinh { arg } => one("sinh", arg),
ExprTree::Cosh { arg } => one("cosh", arg),
ExprTree::Tanh { arg } => one("tanh", arg),
ExprTree::Asinh { arg } => one("asinh", arg),
ExprTree::Acosh { arg } => one("acosh", arg),
ExprTree::Atanh { arg } => one("atanh", arg),
ExprTree::Sign { arg } => one("sign", arg),
ExprTree::Heaviside { arg } => one("Heaviside", arg),
ExprTree::DiracDelta { arg } => one("DiracDelta", arg),
ExprTree::Gamma { arg } => one("gamma", arg),
ExprTree::LogGamma { arg } => one("loggamma", arg),
ExprTree::Digamma { arg } => one("digamma", arg),
ExprTree::Erf { arg } => one("erf", arg),
ExprTree::Erfc { arg } => one("erfc", arg),
ExprTree::LambertW { arg } => one("LambertW", arg),
ExprTree::Beta { a, b } => two("beta", a, b),
ExprTree::Re { arg } => one("re", arg),
ExprTree::Im { arg } => one("im", arg),
ExprTree::Conjugate { arg } => one("conjugate", arg),
ExprTree::Arg { arg } => one("arg", arg),
ExprTree::Si { arg } => one("Si", arg),
ExprTree::Ci { arg } => one("Ci", arg),
ExprTree::Ei { arg } => one("Ei", arg),
ExprTree::Li { arg } => one("li", arg),
ExprTree::Zeta { arg } => one("zeta", arg),
ExprTree::Polygamma { n, arg } => two("polygamma", n, arg),
ExprTree::KroneckerDelta { i, j } => two("KroneckerDelta", i, j),
ExprTree::Floor { arg } => one("floor", arg),
ExprTree::Ceiling { arg } => one("ceiling", arg),
ExprTree::Min { args } => many("Min", args),
ExprTree::Max { args } => many("Max", args),
ExprTree::BoolTrue => atom("true"),
ExprTree::BoolFalse => atom("false"),
ExprTree::Gt { lhs, rhs } => two("StrictGreaterThan", lhs, rhs),
ExprTree::Ge { lhs, rhs } => two("GreaterThan", lhs, rhs),
ExprTree::Eq_ { lhs, rhs } => two("Equality", lhs, rhs),
ExprTree::Ne { lhs, rhs } => two("Unequality", lhs, rhs),
ExprTree::And { args } => many("And", args),
ExprTree::Or { args } => many("Or", args),
ExprTree::Not { arg } => one("Not", arg),
ExprTree::Piecewise { pieces } => (
"Piecewise".to_string(),
Some(pieces.iter().flat_map(|(v, c)| [v, c]).collect()),
),
ExprTree::Apply { name, args } => many(name, args),
ExprTree::Derivative { body, var } => two("Derivative", body, var),
ExprTree::Integral { body, var } => two("Integral", body, var),
ExprTree::DefiniteIntegral {
body,
var,
lower,
upper,
} => (
"DefiniteIntegral".to_string(),
Some(vec![body, var, lower, upper]),
),
ExprTree::Sum {
body,
var,
lower,
upper,
} => ("Sum".to_string(), Some(vec![body, var, lower, upper])),
ExprTree::Product_ {
body,
var,
lower,
upper,
} => ("Product".to_string(), Some(vec![body, var, lower, upper])),
ExprTree::EmptySet => atom("EmptySet"),
ExprTree::UniversalSet => atom("UniversalSet"),
ExprTree::Interval { start, end, .. } => two("Interval", start, end),
ExprTree::FiniteSet { elements } => many("FiniteSet", elements),
ExprTree::SetUnion { sets } => many("Union", sets),
ExprTree::SetIntersection { sets } => many("Intersection", sets),
ExprTree::SetComplement { set, universe } => two("Complement", set, universe),
ExprTree::Limit { body, var, point } => {
("Limit".to_string(), Some(vec![body, var, point]))
}
ExprTree::Series {
body,
var,
point,
order,
} => ("Series".to_string(), Some(vec![body, var, point, order])),
ExprTree::LaplaceTransform { body, t, s } => {
("LaplaceTransform".to_string(), Some(vec![body, t, s]))
}
ExprTree::InverseLaplaceTransform { body, s, t } => (
"InverseLaplaceTransform".to_string(),
Some(vec![body, s, t]),
),
ExprTree::Residue { body, var, point } => {
("Residue".to_string(), Some(vec![body, var, point]))
}
ExprTree::RootOf { poly, index } => two("RootOf", poly, index),
ExprTree::DSolve { expr, func, var } => {
("DSolve".to_string(), Some(vec![expr, func, var]))
}
ExprTree::RootSum { poly, body, sumvar } => {
("RootSum".to_string(), Some(vec![poly, body, sumvar]))
}
ExprTree::ConditionSet { var, condition } => two("ConditionSet", var, condition),
}
}
fn srepr_trailing(&self) -> Option<String> {
match self {
ExprTree::Interval { flags, .. } => {
Some(format!(", {}, {}", flags & 0x01 != 0, flags & 0x02 != 0))
}
_ => None,
}
}
#[must_use]
pub fn to_srepr(&self) -> String {
enum Item<'t> {
Text(String),
Node(&'t ExprTree),
}
let mut out = String::new();
let mut stack: Vec<Item<'_>> = vec![Item::Node(self)];
while let Some(item) = stack.pop() {
match item {
Item::Text(s) => out.push_str(&s),
Item::Node(node) => {
let (head, children) = node.head_and_children();
out.push_str(&head);
let Some(children) = children else { continue };
out.push('(');
let mut close = String::new();
if let Some(trailing) = node.srepr_trailing() {
close.push_str(&trailing);
}
close.push(')');
stack.push(Item::Text(close));
let pairwise = matches!(node, ExprTree::Piecewise { .. });
for (i, child) in children.iter().enumerate().rev() {
if pairwise {
if i % 2 == 1 {
stack.push(Item::Text(")".into()));
stack.push(Item::Node(child));
stack.push(Item::Text(", ".into()));
} else {
stack.push(Item::Node(child));
stack.push(Item::Text(if i == 0 { "(" } else { ", (" }.into()));
}
} else {
stack.push(Item::Node(child));
if i > 0 {
stack.push(Item::Text(", ".into()));
}
}
}
}
}
}
out
}
#[must_use]
pub fn to_dot(&self) -> String {
let mut nodes: Vec<String> = Vec::new();
let mut edges: Vec<String> = Vec::new();
let mut stack: Vec<(&ExprTree, Option<usize>)> = vec![(self, None)];
while let Some((node, parent)) = stack.pop() {
let id = nodes.len();
let (head, children) = node.head_and_children();
let label = match node {
ExprTree::Interval { flags, .. } => format!(
"Interval('{}{}')",
if flags & 0x01 != 0 { '(' } else { '[' },
if flags & 0x02 != 0 { ')' } else { ']' }
),
_ => head,
};
nodes.push(format!(
" n{id} [label=\"{}\"];",
label.replace('\\', "\\\\").replace('"', "\\\"")
));
if let Some(p) = parent {
edges.push(format!(" n{p} -> n{id};"));
}
if let Some(children) = children {
for child in children.into_iter().rev() {
stack.push((child, Some(id)));
}
}
}
let mut out = String::from("digraph {\n ordering=out;\n rankdir=TD;\n");
for n in &nodes {
out.push_str(n);
out.push('\n');
}
for e in &edges {
out.push_str(e);
out.push('\n');
}
out.push_str("}\n");
out
}
}
impl<S: crate::api::expr::Sort> crate::api::expr::Expr<S> {
#[must_use = "returns the rendered string; does not modify in place"]
pub fn to_srepr(&self) -> String {
self.to_tree().to_srepr()
}
#[must_use = "returns the rendered string; does not modify in place"]
pub fn to_dot(&self) -> String {
self.to_tree().to_dot()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::base::arena::Arena;
fn display(a: &Arena, id: ExprId) -> String {
a.display(id).to_string()
}
#[test]
fn roundtrip_integer() {
let mut a = Arena::new();
let expr = a.int(42);
let tree = expr_to_tree(&a, expr);
let back = tree_to_expr(&mut a, &tree);
assert_eq!(display(&a, back), "42");
}
#[test]
fn roundtrip_rational() {
let mut a = Arena::new();
let expr = a.rational(3, 7);
let tree = expr_to_tree(&a, expr);
let back = tree_to_expr(&mut a, &tree);
assert_eq!(display(&a, back), "3/7");
}
#[test]
fn roundtrip_symbol() {
let mut a = Arena::new();
let expr = a.symbol("x");
let tree = expr_to_tree(&a, expr);
let back = tree_to_expr(&mut a, &tree);
assert_eq!(display(&a, back), "x");
}
#[test]
fn roundtrip_polynomial() {
let mut a = Arena::new();
let x = a.symbol("x");
let two = a.int(2);
let x2 = a.pow(x, two);
let two_id = a.int(2);
let two_x = a.mul(&[two_id, x]);
let one = a.one;
let expr = a.add(&[x2, two_x, one]);
let tree = expr_to_tree(&a, expr);
let back = tree_to_expr(&mut a, &tree);
assert_eq!(display(&a, expr), display(&a, back));
}
#[test]
fn roundtrip_sin() {
let mut a = Arena::new();
let x = a.symbol("x");
let expr = a.sin(x);
let tree = expr_to_tree(&a, expr);
let back = tree_to_expr(&mut a, &tree);
assert_eq!(display(&a, back), "sin(x)");
}
#[test]
fn roundtrip_pi() {
let a = Arena::new();
let tree = expr_to_tree(&a, a.pi);
let mut a2 = Arena::new();
let back = tree_to_expr(&mut a2, &tree);
assert_eq!(display(&a2, back), "pi");
}
#[test]
fn json_roundtrip() {
let mut a = Arena::new();
let x = a.symbol("x");
let two = a.int(2);
let expr = a.pow(x, two);
let tree = expr_to_tree(&a, expr);
let json = serde_json::to_string(&tree).unwrap();
let tree2: ExprTree = serde_json::from_str(&json).unwrap();
assert_eq!(tree, tree2);
let back = tree_to_expr(&mut a, &tree2);
assert_eq!(display(&a, back), "x^2");
}
#[test]
fn json_deserialize_from_scratch() {
let json = r#"{"type":"Add","terms":[{"type":"Num","numer":"1","denom":"1"},{"type":"Symbol","name":"x"}]}"#;
let tree: ExprTree = serde_json::from_str(json).unwrap();
let mut a = Arena::new();
let expr = tree_to_expr(&mut a, &tree);
assert_eq!(display(&a, expr), "x + 1");
}
#[test]
fn roundtrip_all_functions() {
let mut a = Arena::new();
let x = a.symbol("x");
let funcs = [
a.sin(x),
a.cos(x),
a.tan(x),
a.exp(x),
a.ln(x),
a.sqrt(x),
a.abs(x),
a.asin(x),
a.acos(x),
a.atan(x),
a.sinh(x),
a.cosh(x),
a.tanh(x),
];
for &expr in &funcs {
let tree = expr_to_tree(&a, expr);
let back = tree_to_expr(&mut a, &tree);
assert_eq!(
display(&a, expr),
display(&a, back),
"roundtrip failed for {}",
display(&a, expr)
);
}
}
#[test]
fn roundtrip_derivative() {
let mut a = Arena::new();
let x = a.symbol("x");
let body = a.sin(x);
let expr = a.intern(ExprNode::Derivative(body, x));
let tree = expr_to_tree(&a, expr);
let back = tree_to_expr(&mut a, &tree);
assert_eq!(display(&a, back), "Derivative(sin(x), x)");
}
#[test]
fn roundtrip_integral() {
let mut a = Arena::new();
let x = a.symbol("x");
let body = a.sin(x);
let expr = a.intern(ExprNode::Integral(body, x));
let tree = expr_to_tree(&a, expr);
let back = tree_to_expr(&mut a, &tree);
assert_eq!(display(&a, back), "Integral(sin(x), x)");
}
#[test]
fn roundtrip_definite_integral() {
let mut a = Arena::new();
let x = a.symbol("x");
let body = a.pow(x, x);
let one = a.one;
let expr = a.definite_integral(body, x, a.zero, one);
let tree = expr_to_tree(&a, expr);
let json = serde_json::to_string(&tree).unwrap();
let tree2: ExprTree = serde_json::from_str(&json).unwrap();
assert_eq!(tree2, tree);
assert_eq!(tree_to_expr(&mut a, &tree2), expr);
assert_eq!(display(&a, expr), "Integral(x^x, x, 0, 1)");
}
#[test]
fn roundtrip_named_constants() {
let mut a = Arena::new();
for id in [a.euler_gamma, a.catalan, a.golden_ratio] {
let tree = expr_to_tree(&a, id);
let json = serde_json::to_string(&tree).unwrap();
let tree2: ExprTree = serde_json::from_str(&json).unwrap();
assert_eq!(tree2, tree);
assert_eq!(tree_to_expr(&mut a, &tree2), id);
}
assert_eq!(expr_to_tree(&a, a.euler_gamma), ExprTree::EulerGamma);
assert_eq!(expr_to_tree(&a, a.catalan), ExprTree::Catalan);
assert_eq!(expr_to_tree(&a, a.golden_ratio), ExprTree::GoldenRatio);
}
#[test]
fn roundtrip_complex_and_special_nodes() {
let mut a = Arena::new();
let x = a.symbol("x");
let n = a.symbol("n");
let nodes = [
a.intern(ExprNode::Re(x)),
a.intern(ExprNode::Im(x)),
a.intern(ExprNode::Conjugate(x)),
a.intern(ExprNode::Arg(x)),
a.intern(ExprNode::Si(x)),
a.intern(ExprNode::Ci(x)),
a.intern(ExprNode::Ei(x)),
a.intern(ExprNode::Li(x)),
a.intern(ExprNode::Zeta(x)),
a.intern(ExprNode::Polygamma(n, x)),
a.intern(ExprNode::KroneckerDelta(n, x)),
];
for id in nodes {
let tree = expr_to_tree(&a, id);
let json = serde_json::to_string(&tree).unwrap();
let tree2: ExprTree = serde_json::from_str(&json).unwrap();
let back = tree_to_expr(&mut a, &tree2);
assert_eq!(back, id, "round trip of {}", display(&a, id));
}
}
#[test]
fn tree_to_expr_uses_canonical_constructors() {
let mut a = Arena::new();
let two = a.int(2);
let tree = ExprTree::Zeta {
arg: Box::new(expr_to_tree(&a, two)),
};
let id = tree_to_expr(&mut a, &tree);
assert_eq!(display(&a, id), "1/6*pi^2");
}
}