use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::{One, Zero};
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use std::fmt;
use std::hash::{Hash, Hasher};
use crate::base::config::EvalConfig;
use crate::base::node::{ExprId, ExprNode, NumId, SymbolId};
use crate::base::sort_key::{SortKey, compute_sort_key};
use crate::base::symbol::SymbolTable;
pub(crate) const FN_FACTORIAL2: &str = "factorial2";
pub(crate) const FN_SUBFACTORIAL: &str = "subfactorial";
pub(crate) const FN_RISING_FACTORIAL: &str = "rising_factorial";
pub(crate) const FN_FALLING_FACTORIAL: &str = "falling_factorial";
pub(crate) const FN_FIBONACCI: &str = "fibonacci";
pub(crate) const FN_LUCAS: &str = "lucas";
pub(crate) const FN_BERNOULLI: &str = "bernoulli";
pub(crate) const FN_HARMONIC: &str = "harmonic";
pub(crate) const FN_CATALAN: &str = "catalan";
pub(crate) const FN_BELL: &str = "bell";
pub(crate) const FN_EULER_NUMBER: &str = "euler_number";
pub(crate) const FN_STIRLING1: &str = "stirling1";
pub(crate) const FN_STIRLING2: &str = "stirling2";
pub(crate) const FN_PARTITION_COUNT: &str = "partition_count";
#[allow(dead_code)]
pub(crate) const FN_LAMBERTW: &str = "lambertw";
pub(crate) const FN_BESSELJ: &str = "besselj";
pub(crate) const FN_BESSELY: &str = "bessely";
pub(crate) const FN_BESSELI: &str = "besseli";
pub(crate) const FN_BESSELK: &str = "besselk";
pub(crate) const FN_LEGENDRE: &str = "legendre";
pub(crate) const FN_CHEBYSHEV_T: &str = "chebyshev_t";
pub(crate) const FN_CHEBYSHEV_U: &str = "chebyshev_u";
pub(crate) const FN_HERMITE: &str = "hermite";
pub(crate) const FN_LAGUERRE: &str = "laguerre";
pub struct Arena {
nodes: Vec<ExprNode>,
numbers: Vec<Ratio<BigInt>>,
sort_keys: Vec<SortKey>,
dedup: FxHashMap<u64, SmallVec<[ExprId; 2]>>,
num_dedup: FxHashMap<u64, SmallVec<[NumId; 2]>>,
pub(crate) symbols: SymbolTable,
pub(crate) config: EvalConfig,
pub(crate) zero: ExprId,
pub(crate) one: ExprId,
pub(crate) neg_one: ExprId,
pub(crate) pi: ExprId,
pub(crate) e_const: ExprId,
pub(crate) i_unit: ExprId,
pub(crate) euler_gamma: ExprId,
pub(crate) catalan: ExprId,
pub(crate) golden_ratio: ExprId,
pub(crate) infinity: ExprId,
pub(crate) neg_infinity: ExprId,
pub(crate) nan: ExprId,
pub(crate) complex_infinity: ExprId,
pub(crate) empty_set: ExprId,
pub(crate) universal_set: ExprId,
pub(crate) bool_true: ExprId,
pub(crate) bool_false: ExprId,
pub(crate) zero_num: NumId,
pub(crate) one_num: NumId,
pub(crate) neg_one_num: NumId,
pub(crate) last_compact_size: usize,
}
impl Arena {
pub fn new() -> Self {
Self::with_config(EvalConfig::default())
}
pub fn with_config(config: EvalConfig) -> Self {
let mut arena = Self {
nodes: Vec::new(),
numbers: Vec::new(),
sort_keys: Vec::new(),
dedup: FxHashMap::default(),
num_dedup: FxHashMap::default(),
symbols: SymbolTable::new(),
config,
zero: ExprId(0),
one: ExprId(0),
neg_one: ExprId(0),
pi: ExprId(0),
e_const: ExprId(0),
i_unit: ExprId(0),
euler_gamma: ExprId(0),
catalan: ExprId(0),
golden_ratio: ExprId(0),
infinity: ExprId(0),
neg_infinity: ExprId(0),
nan: ExprId(0),
complex_infinity: ExprId(0),
empty_set: ExprId(0),
universal_set: ExprId(0),
bool_true: ExprId(0),
bool_false: ExprId(0),
zero_num: NumId(0),
one_num: NumId(0),
neg_one_num: NumId(0),
last_compact_size: 0,
};
arena.zero_num = arena.intern_num(Ratio::zero());
arena.one_num = arena.intern_num(Ratio::one());
arena.neg_one_num = arena.intern_num(-Ratio::<BigInt>::one());
arena.zero = arena.intern(ExprNode::Num(arena.zero_num));
arena.one = arena.intern(ExprNode::Num(arena.one_num));
arena.neg_one = arena.intern(ExprNode::Num(arena.neg_one_num));
arena.pi = arena.intern(ExprNode::Pi);
arena.e_const = arena.intern(ExprNode::E);
arena.i_unit = arena.intern(ExprNode::ImaginaryUnit);
arena.euler_gamma = arena.intern(ExprNode::EulerGamma);
arena.catalan = arena.intern(ExprNode::Catalan);
arena.golden_ratio = arena.intern(ExprNode::GoldenRatio);
arena.infinity = arena.intern(ExprNode::Infinity);
arena.neg_infinity = arena.intern(ExprNode::NegInfinity);
arena.nan = arena.intern(ExprNode::NaN);
arena.complex_infinity = arena.intern(ExprNode::ComplexInfinity);
arena.bool_true = arena.intern(ExprNode::BoolTrue);
arena.bool_false = arena.intern(ExprNode::BoolFalse);
arena.empty_set = arena.intern(ExprNode::EmptySet);
arena.universal_set = arena.intern(ExprNode::UniversalSet);
arena
}
}
impl Arena {
#[inline]
pub fn zero(&self) -> ExprId {
self.zero
}
#[inline]
pub fn one(&self) -> ExprId {
self.one
}
#[inline]
pub fn neg_one(&self) -> ExprId {
self.neg_one
}
#[inline]
pub fn pi(&self) -> ExprId {
self.pi
}
#[inline]
pub fn e_const(&self) -> ExprId {
self.e_const
}
#[inline]
pub fn i_unit(&self) -> ExprId {
self.i_unit
}
#[inline]
pub fn euler_gamma(&self) -> ExprId {
self.euler_gamma
}
#[inline]
pub fn catalan(&self) -> ExprId {
self.catalan
}
#[inline]
pub fn golden_ratio(&self) -> ExprId {
self.golden_ratio
}
#[inline]
pub fn infinity(&self) -> ExprId {
self.infinity
}
#[inline]
pub fn neg_infinity(&self) -> ExprId {
self.neg_infinity
}
#[inline]
pub fn nan(&self) -> ExprId {
self.nan
}
#[inline]
pub fn complex_infinity(&self) -> ExprId {
self.complex_infinity
}
#[inline]
pub fn bool_true(&self) -> ExprId {
self.bool_true
}
#[inline]
pub fn bool_false(&self) -> ExprId {
self.bool_false
}
#[inline]
pub fn zero_num(&self) -> NumId {
self.zero_num
}
#[inline]
pub fn one_num(&self) -> NumId {
self.one_num
}
#[inline]
pub fn neg_one_num(&self) -> NumId {
self.neg_one_num
}
#[inline]
pub fn config(&self) -> &EvalConfig {
&self.config
}
#[inline]
pub fn config_mut(&mut self) -> &mut EvalConfig {
&mut self.config
}
}
impl Arena {
fn hash_node(node: &ExprNode) -> u64 {
let mut hasher = rustc_hash::FxHasher::default();
node.hash(&mut hasher);
hasher.finish()
}
fn hash_num(value: &Ratio<BigInt>) -> u64 {
let mut hasher = rustc_hash::FxHasher::default();
value.hash(&mut hasher);
hasher.finish()
}
pub(crate) fn intern(&mut self, node: ExprNode) -> ExprId {
let hash = Self::hash_node(&node);
if let Some(candidates) = self.dedup.get(&hash) {
for &candidate in candidates {
if self.nodes[candidate.0 as usize] == node {
return candidate;
}
}
}
let id = ExprId(
u32::try_from(self.nodes.len())
.expect("arena overflow: more than 4 billion expression nodes"),
);
let sort_key = self.compute_sort_key_for(&node);
self.nodes.push(node);
self.sort_keys.push(sort_key);
self.dedup.entry(hash).or_default().push(id);
id
}
pub(crate) fn intern_num(&mut self, value: Ratio<BigInt>) -> NumId {
let hash = Self::hash_num(&value);
if let Some(candidates) = self.num_dedup.get(&hash) {
for &candidate in candidates {
if self.numbers[candidate.0 as usize] == value {
return candidate;
}
}
}
let id = NumId(
u32::try_from(self.numbers.len())
.expect("arena overflow: more than 4 billion numeric literals"),
);
self.numbers.push(value);
self.num_dedup.entry(hash).or_default().push(id);
id
}
fn compute_sort_key_for(&self, node: &ExprNode) -> SortKey {
compute_sort_key(
node,
|child_id| self.sort_keys[child_id.0 as usize].clone(),
|num_id| {
self.numbers[num_id.0 as usize].to_string().into_bytes()
},
|sym_id| self.symbols.name(sym_id).to_owned(),
)
}
}
impl Arena {
pub fn node(&self, id: ExprId) -> &ExprNode {
&self.nodes[id.0 as usize]
}
pub fn sort_key(&self, id: ExprId) -> &SortKey {
&self.sort_keys[id.0 as usize]
}
pub fn num(&self, id: NumId) -> &Ratio<BigInt> {
&self.numbers[id.0 as usize]
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn symbol_name(&self, id: SymbolId) -> &str {
self.symbols.name(id)
}
pub(crate) fn symbol_assumptions(&self, id: SymbolId) -> crate::base::assumptions::Assumptions {
self.symbols.get_assumptions(id)
}
pub(crate) fn set_symbol_assumptions(
&mut self,
id: SymbolId,
a: crate::base::assumptions::Assumptions,
) {
let mut a = a;
a.normalize_declared();
assert!(
!a.is_contradictory(),
"contradictory assumptions declared on symbol `{}`: {a} \
(properties {} are both asserted and denied)",
self.symbols.name(id),
a.known_true & a.known_false
);
self.symbols.set_assumptions(id, a);
}
pub fn children(&self, id: ExprId) -> SmallVec<[ExprId; 6]> {
self.nodes[id.0 as usize].children()
}
}
impl Arena {
pub(crate) fn as_coeff_term(&mut self, id: ExprId) -> (Ratio<BigInt>, ExprId) {
match self.node(id).clone() {
ExprNode::Num(nid) => (self.num(nid).clone(), self.one),
ExprNode::Neg(inner) => {
let neg_one = -Ratio::<BigInt>::one();
(neg_one, inner)
}
ExprNode::Mul(args) if !args.is_empty() => {
if let ExprNode::Num(nid) = self.node(args[0]) {
let coeff = self.num(*nid).clone();
let rest = &args[1..];
let term = match rest.len() {
0 => self.one,
1 => rest[0],
_ => {
let sv: SmallVec<[ExprId; 6]> = rest.iter().copied().collect();
self.intern(ExprNode::Mul(sv))
}
};
(coeff, term)
} else {
(Ratio::one(), id)
}
}
_ => (Ratio::one(), id),
}
}
pub(crate) fn as_base_exp(&self, id: ExprId) -> (ExprId, ExprId) {
match self.node(id) {
ExprNode::Pow(base, exp) => (*base, *exp),
_ => (id, self.one),
}
}
pub(crate) fn make_coeff_term(&mut self, coeff: Ratio<BigInt>, term: ExprId) -> ExprId {
if coeff.is_zero() {
return self.zero;
}
if coeff == Ratio::one() {
return term;
}
let coeff_id = {
let nid = self.intern_num(coeff);
self.intern(ExprNode::Num(nid))
};
if term == self.one {
return coeff_id;
}
crate::base::canon::canon_mul(self, &[coeff_id, term])
}
pub(crate) fn as_num(&self, id: ExprId) -> Option<&Ratio<BigInt>> {
match self.node(id) {
ExprNode::Num(nid) => Some(self.num(*nid)),
_ => None,
}
}
pub fn is_zero_structural(&self, id: ExprId) -> bool {
id == self.zero
}
pub fn is_one_structural(&self, id: ExprId) -> bool {
id == self.one
}
}
impl Arena {
pub fn int(&mut self, n: i64) -> ExprId {
let num_id = self.intern_num(Ratio::from_integer(BigInt::from(n)));
self.intern(ExprNode::Num(num_id))
}
pub fn big_int(&mut self, n: BigInt) -> ExprId {
let num_id = self.intern_num(Ratio::from_integer(n));
self.intern(ExprNode::Num(num_id))
}
pub fn rational(&mut self, p: i64, q: i64) -> ExprId {
if q == 0 {
return if p == 0 {
self.nan
} else {
self.complex_infinity
};
}
let num_id = self.intern_num(Ratio::new(BigInt::from(p), BigInt::from(q)));
self.intern(ExprNode::Num(num_id))
}
pub fn symbol(&mut self, name: &str) -> ExprId {
let sym_id = self.symbols.intern(name);
self.intern(ExprNode::Symbol(sym_id))
}
pub fn add(&mut self, args: &[ExprId]) -> ExprId {
crate::base::canon::canon_add(self, args)
}
pub fn mul(&mut self, args: &[ExprId]) -> ExprId {
crate::base::canon::canon_mul(self, args)
}
pub fn pow(&mut self, base: ExprId, exp: ExprId) -> ExprId {
crate::base::canon::canon_pow(self, base, exp)
}
pub fn neg(&mut self, expr: ExprId) -> ExprId {
crate::base::canon::canon_neg(self, expr)
}
pub fn div(&mut self, a: ExprId, b: ExprId) -> ExprId {
let neg_one = self.neg_one;
let b_inv = self.pow(b, neg_one);
self.mul(&[a, b_inv])
}
pub fn subs_structural(&mut self, expr: ExprId, old: ExprId, new: ExprId) -> ExprId {
crate::transforms::subs::subs(self, expr, old, new)
}
pub fn subs_map_structural(
&mut self,
expr: ExprId,
replacements: &[(ExprId, ExprId)],
) -> ExprId {
crate::transforms::subs::subs_map(self, expr, replacements)
}
pub fn diff_wrt(&mut self, expr: ExprId, var: ExprId) -> ExprId {
crate::transforms::diff::diff(self, expr, var)
}
pub fn formal_diff(&mut self, expr: ExprId, var: ExprId) -> ExprId {
self.intern(ExprNode::Derivative(expr, var))
}
pub fn expand_expr(&mut self, expr: ExprId) -> ExprId {
crate::transforms::expand::expand(self, expr)
}
pub fn eval_expr(&mut self, expr: ExprId) -> ExprId {
crate::transforms::eval::eval(self, expr)
}
pub fn solve_for(
&mut self,
expr: ExprId,
var: ExprId,
) -> Vec<crate::transforms::solve::Solution> {
crate::transforms::solve::solve(self, expr, var)
}
pub fn cancel_expr(&mut self, expr: ExprId, var: ExprId) -> ExprId {
crate::poly::polybridge::cancel(self, expr, var)
}
pub fn collect_expr(&mut self, expr: ExprId, var: ExprId) -> ExprId {
crate::poly::polybridge::collect(self, expr, var)
}
pub fn together_expr(&mut self, expr: ExprId) -> ExprId {
crate::poly::polybridge::together_deep(self, expr)
}
pub fn integrate_expr(&mut self, expr: ExprId, var: ExprId) -> ExprId {
crate::transforms::integrate::integrate(self, expr, var)
}
pub fn series_expr(
&mut self,
expr: ExprId,
var: ExprId,
point: ExprId,
order: u32,
) -> Result<ExprId, crate::base::errors::SymplexError> {
match crate::calculus::series::series(self, expr, var, point, order) {
Ok(result) => Ok(result),
Err(_) => {
crate::calculus::series::laurent_series(self, expr, var, point, order)
}
}
}
pub fn factor_expr(&mut self, expr: ExprId, var: ExprId) -> ExprId {
crate::simplify::factor::factor(self, expr, var)
}
pub fn degree_of(&self, expr: ExprId, var: ExprId) -> Option<usize> {
crate::poly::polybridge::poly_degree(self, expr, var)
}
pub fn coefficients_of(&mut self, expr: ExprId, var: ExprId) -> Option<Vec<ExprId>> {
crate::poly::polybridge::poly_coefficients(self, expr, var)
}
pub fn limit_expr(
&mut self,
expr: ExprId,
var: ExprId,
point: ExprId,
) -> Result<ExprId, crate::base::errors::SymplexError> {
crate::calculus::limit::limit(self, expr, var, point)
}
pub fn as_numer_denom_expr(&mut self, expr: ExprId) -> (ExprId, ExprId) {
crate::poly::polybridge::fraction_parts(self, expr)
}
pub fn apart_expr(&mut self, expr: ExprId, var: ExprId) -> ExprId {
crate::transforms::apart::apart(self, expr, var)
}
pub fn expand_trig_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::trig_expand::expand_trig(self, expr)
}
pub fn expand_log_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::log_expand::expand_log(self, expr)
}
pub fn log_combine_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::log_combine::log_combine(self, expr)
}
pub fn trig_combine_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::trig_combine::trig_combine(self, expr)
}
pub fn smart_simplify_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::simplify_engine::smart_simplify(self, expr)
}
pub fn count_ops(&self, expr: ExprId) -> usize {
crate::simplify::simplify_engine::count_ops(self, expr)
}
pub fn trigsimp_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::trigsimp::trigsimp(self, expr)
}
pub fn combsimp_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::combsimp::combsimp(self, expr)
}
pub fn nsimplify_expr(&mut self, expr: ExprId, tol: f64) -> ExprId {
crate::simplify::nsimplify::nsimplify(self, expr, tol)
}
pub fn powsimp_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::powsimp::powsimp(self, expr)
}
pub fn rewrite_as_exp_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::rewrite::rewrite_as_exp(self, expr)
}
pub fn rewrite_as_trig_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::rewrite::rewrite_as_trig(self, expr)
}
pub fn factor_terms_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::factor_terms::factor_terms(self, expr)
}
pub fn factor_terms_pair_expr(&mut self, expr: ExprId) -> (ExprId, ExprId) {
crate::simplify::factor_terms::symbolic_factor_terms_pair(self, expr)
}
pub fn rationalize_denom_expr(&mut self, expr: ExprId) -> ExprId {
crate::simplify::radsimp::rationalize_denom(self, expr)
}
pub fn separatevars_expr(
&mut self,
expr: ExprId,
vars: &[ExprId],
) -> Vec<(Vec<ExprId>, ExprId)> {
crate::domains::separatevars::separatevars(self, expr, vars)
}
pub fn as_real_imag_expr(&mut self, expr: ExprId) -> (ExprId, ExprId) {
crate::base::complex::as_real_imag(self, expr)
}
pub fn poly_gcd_expr(&mut self, a: ExprId, b: ExprId, var: ExprId) -> Option<ExprId> {
let pa = crate::poly::polybridge::expr_to_poly(self, a, var)?;
let pb = crate::poly::polybridge::expr_to_poly(self, b, var)?;
let g = crate::poly::Poly::gcd(&pa, &pb);
Some(crate::poly::polybridge::poly_to_expr(self, &g, var))
}
pub fn poly_lcm_expr(&mut self, a: ExprId, b: ExprId, var: ExprId) -> Option<ExprId> {
let pa = crate::poly::polybridge::expr_to_poly(self, a, var)?;
let pb = crate::poly::polybridge::expr_to_poly(self, b, var)?;
let g = crate::poly::Poly::gcd(&pa, &pb);
if g.is_zero() {
return None;
}
let product = &pa * &pb;
let (lcm, _rem) = product.div_rem(&g);
Some(crate::poly::polybridge::poly_to_expr(self, &lcm, var))
}
pub fn sub(&mut self, a: ExprId, b: ExprId) -> ExprId {
let neg_b = self.neg(b);
self.add(&[a, neg_b])
}
pub fn sin(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Sin(expr))
}
pub fn cos(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Cos(expr))
}
pub fn tan(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Tan(expr))
}
pub fn exp(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Exp(expr))
}
pub fn ln(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Ln(expr))
}
pub fn sqrt(&mut self, expr: ExprId) -> ExprId {
let half = self.rational(1, 2);
self.pow(expr, half)
}
pub fn cbrt(&mut self, expr: ExprId) -> ExprId {
let third = self.rational(1, 3);
self.pow(expr, third)
}
pub fn nthroot(&mut self, expr: ExprId, n: i64) -> ExprId {
let frac = self.rational(1, n);
self.pow(expr, frac)
}
pub fn abs(&mut self, expr: ExprId) -> ExprId {
match self.node(expr).clone() {
ExprNode::NaN => return self.nan,
ExprNode::Infinity | ExprNode::NegInfinity | ExprNode::ComplexInfinity => {
return self.infinity;
}
ExprNode::Abs(_) => return expr, ExprNode::Num(nid) => {
let r = self.num(nid).clone();
if r < Ratio::zero() {
let pos = -r;
let nid = self.intern_num(pos);
return self.intern(ExprNode::Num(nid));
}
return expr; }
_ => {}
}
let (coeff, term) = self.as_coeff_term(expr);
if coeff == Ratio::one() {
self.intern(ExprNode::Abs(expr))
} else {
let abs_coeff = if coeff < Ratio::zero() { -coeff } else { coeff };
let abs_term = self.abs(term); self.make_coeff_term(abs_coeff, abs_term)
}
}
pub fn sign(&mut self, expr: ExprId) -> ExprId {
match self.node(expr).clone() {
ExprNode::NaN => return self.nan,
ExprNode::Infinity => return self.one,
ExprNode::NegInfinity => return self.neg_one,
ExprNode::Num(nid) => {
let r = self.num(nid).clone();
if r > Ratio::zero() {
return self.one;
} else if r < Ratio::zero() {
return self.neg_one;
} else {
return self.zero;
}
}
_ => {}
}
let (coeff, term) = self.as_coeff_term(expr);
if coeff < Ratio::zero() {
let sign_term = self.sign(term); self.neg(sign_term)
} else if coeff == Ratio::one() {
self.intern(ExprNode::Sign(expr))
} else {
self.sign(term) }
}
pub fn floor(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Floor(expr))
}
pub fn ceiling(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Ceiling(expr))
}
pub fn asin(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Asin(expr))
}
pub fn acos(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Acos(expr))
}
pub fn atan(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Atan(expr))
}
pub fn atan2(&mut self, y: ExprId, x: ExprId) -> ExprId {
self.intern(ExprNode::Atan2(y, x))
}
pub fn sinh(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Sinh(expr))
}
pub fn cosh(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Cosh(expr))
}
pub fn tanh(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Tanh(expr))
}
pub fn asinh(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Asinh(expr))
}
pub fn acosh(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Acosh(expr))
}
pub fn atanh(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Atanh(expr))
}
pub fn gamma(&mut self, arg: ExprId) -> ExprId {
self.intern(ExprNode::Gamma(arg))
}
pub fn log_gamma(&mut self, arg: ExprId) -> ExprId {
self.intern(ExprNode::LogGamma(arg))
}
pub fn digamma(&mut self, arg: ExprId) -> ExprId {
self.intern(ExprNode::Digamma(arg))
}
pub fn erf(&mut self, arg: ExprId) -> ExprId {
self.intern(ExprNode::Erf(arg))
}
pub fn erfc(&mut self, arg: ExprId) -> ExprId {
self.intern(ExprNode::Erfc(arg))
}
pub fn beta(&mut self, a: ExprId, b: ExprId) -> ExprId {
self.intern(ExprNode::Beta(a, b))
}
pub fn re(&mut self, z: ExprId) -> ExprId {
crate::base::complex::re(self, z)
}
pub fn im(&mut self, z: ExprId) -> ExprId {
crate::base::complex::im(self, z)
}
pub fn conjugate(&mut self, z: ExprId) -> ExprId {
crate::base::complex::conjugate(self, z)
}
pub fn arg(&mut self, z: ExprId) -> ExprId {
crate::base::complex::arg(self, z)
}
pub fn si(&mut self, x: ExprId) -> ExprId {
crate::transforms::eval::eval_si(self, x).unwrap_or_else(|| self.intern(ExprNode::Si(x)))
}
pub fn ci(&mut self, x: ExprId) -> ExprId {
crate::transforms::eval::eval_ci(self, x).unwrap_or_else(|| self.intern(ExprNode::Ci(x)))
}
pub fn ei(&mut self, x: ExprId) -> ExprId {
crate::transforms::eval::eval_ei(self, x).unwrap_or_else(|| self.intern(ExprNode::Ei(x)))
}
pub fn li(&mut self, x: ExprId) -> ExprId {
crate::transforms::eval::eval_li(self, x).unwrap_or_else(|| self.intern(ExprNode::Li(x)))
}
pub fn zeta(&mut self, s: ExprId) -> ExprId {
crate::transforms::eval::eval_zeta(self, s)
.unwrap_or_else(|| self.intern(ExprNode::Zeta(s)))
}
pub fn polygamma(&mut self, n: ExprId, x: ExprId) -> ExprId {
crate::transforms::eval::eval_polygamma(self, n, x)
.unwrap_or_else(|| self.intern(ExprNode::Polygamma(n, x)))
}
pub fn kronecker_delta(&mut self, i: ExprId, j: ExprId) -> ExprId {
crate::transforms::eval::eval_kronecker_delta(self, i, j).unwrap_or_else(|| {
let (a, b) = if self.sort_key(i) <= self.sort_key(j) {
(i, j)
} else {
(j, i)
};
self.intern(ExprNode::KroneckerDelta(a, b))
})
}
pub fn factorial(&mut self, expr: ExprId) -> ExprId {
self.intern(ExprNode::Factorial(expr))
}
pub fn binomial(&mut self, n: ExprId, k: ExprId) -> ExprId {
self.intern(ExprNode::Binomial(n, k))
}
pub fn gt(&mut self, lhs: ExprId, rhs: ExprId) -> ExprId {
self.intern(ExprNode::Gt(lhs, rhs))
}
pub fn ge(&mut self, lhs: ExprId, rhs: ExprId) -> ExprId {
self.intern(ExprNode::Ge(lhs, rhs))
}
pub fn eq_(&mut self, lhs: ExprId, rhs: ExprId) -> ExprId {
self.intern(ExprNode::Eq_(lhs, rhs))
}
pub fn ne_(&mut self, lhs: ExprId, rhs: ExprId) -> ExprId {
self.intern(ExprNode::Ne(lhs, rhs))
}
pub fn and(&mut self, args: &[ExprId]) -> ExprId {
if args.is_empty() {
return self.bool_true;
}
if args.len() == 1 {
return args[0];
}
self.intern(ExprNode::And(SmallVec::from_slice(args)))
}
pub fn or(&mut self, args: &[ExprId]) -> ExprId {
if args.is_empty() {
return self.bool_false;
}
if args.len() == 1 {
return args[0];
}
self.intern(ExprNode::Or(SmallVec::from_slice(args)))
}
pub fn not(&mut self, expr: ExprId) -> ExprId {
if let ExprNode::Not(inner) = self.node(expr) {
return *inner;
}
if expr == self.bool_true {
return self.bool_false;
}
if expr == self.bool_false {
return self.bool_true;
}
self.intern(ExprNode::Not(expr))
}
pub fn piecewise(&mut self, pairs: &[(ExprId, ExprId)]) -> ExprId {
let collected: SmallVec<[(ExprId, ExprId); 3]> = pairs.iter().copied().collect();
self.intern(ExprNode::Piecewise(collected))
}
pub fn definite_integral(
&mut self,
body: ExprId,
var: ExprId,
lo: ExprId,
hi: ExprId,
) -> ExprId {
if lo == hi || self.is_zero_structural(body) {
return self.zero;
}
let infinite = |a: &Arena, id: ExprId| {
matches!(
a.node(id),
ExprNode::Infinity | ExprNode::NegInfinity | ExprNode::ComplexInfinity
)
};
if matches!(self.node(var), ExprNode::Symbol(_))
&& !infinite(self, lo)
&& !infinite(self, hi)
&& !crate::base::walk::contains(self, body, var)
{
let width = self.sub(hi, lo);
return self.mul(&[body, width]);
}
if let (Some(rl), Some(rh)) = (self.as_num(lo), self.as_num(hi))
&& rl > rh
{
let flipped = self.intern(ExprNode::DefiniteIntegral(body, var, hi, lo));
return self.neg(flipped);
}
self.intern(ExprNode::DefiniteIntegral(body, var, lo, hi))
}
pub fn factorial2(&mut self, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_FACTORIAL2);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn subfactorial(&mut self, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_SUBFACTORIAL);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn rising_factorial(&mut self, x: ExprId, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_RISING_FACTORIAL);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![x, n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn falling_factorial(&mut self, x: ExprId, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_FALLING_FACTORIAL);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![x, n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn fibonacci(&mut self, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_FIBONACCI);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn lucas(&mut self, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_LUCAS);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn bernoulli_number(&mut self, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_BERNOULLI);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn harmonic(&mut self, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_HARMONIC);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn catalan_number(&mut self, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_CATALAN);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn bell(&mut self, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_BELL);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn euler_number(&mut self, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_EULER_NUMBER);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn stirling1(&mut self, n: ExprId, k: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_STIRLING1);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n, k];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn stirling2(&mut self, n: ExprId, k: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_STIRLING2);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n, k];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn partition_count(&mut self, n: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_PARTITION_COUNT);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn heaviside(&mut self, arg: ExprId) -> ExprId {
self.intern(ExprNode::Heaviside(arg))
}
pub fn dirac_delta(&mut self, arg: ExprId) -> ExprId {
self.intern(ExprNode::DiracDelta(arg))
}
pub fn lambertw(&mut self, arg: ExprId) -> ExprId {
self.intern(ExprNode::LambertW(arg))
}
pub fn besselj(&mut self, order: ExprId, arg: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_BESSELJ);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![order, arg];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn bessely(&mut self, order: ExprId, arg: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_BESSELY);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![order, arg];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn besseli(&mut self, order: ExprId, arg: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_BESSELI);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![order, arg];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn besselk(&mut self, order: ExprId, arg: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_BESSELK);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![order, arg];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn legendre(&mut self, n: ExprId, x: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_LEGENDRE);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n, x];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn chebyshev_t(&mut self, n: ExprId, x: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_CHEBYSHEV_T);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n, x];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn chebyshev_u(&mut self, n: ExprId, x: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_CHEBYSHEV_U);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n, x];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn hermite(&mut self, n: ExprId, x: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_HERMITE);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n, x];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn laguerre(&mut self, n: ExprId, x: ExprId) -> ExprId {
let sym_id = self.symbols.intern(FN_LAGUERRE);
let args: SmallVec<[ExprId; 2]> = smallvec::smallvec![n, x];
self.intern(ExprNode::Apply(sym_id, args))
}
pub fn physical_constant(&mut self, name: &str, value: ExprId) -> ExprId {
let name_id = self.symbols.intern(name);
self.intern(ExprNode::PhysicalConstant(name_id, value))
}
pub fn residue_expr(
&mut self,
expr: ExprId,
var: ExprId,
point: ExprId,
) -> Result<ExprId, crate::base::errors::SymplexError> {
crate::calculus::residue::residue(self, expr, var, point)
}
pub fn fourier_series_expr(&mut self, expr: ExprId, var: ExprId, n_terms: u32) -> ExprId {
crate::calculus::fourier::fourier_series(self, expr, var, n_terms)
}
pub fn laplace_transform_expr(
&mut self,
expr: ExprId,
t: ExprId,
s: ExprId,
) -> Result<ExprId, crate::base::errors::SymplexError> {
crate::calculus::laplace::laplace_transform(self, expr, t, s)
}
pub fn inverse_laplace_transform_expr(
&mut self,
expr: ExprId,
s: ExprId,
t: ExprId,
) -> Result<ExprId, crate::base::errors::SymplexError> {
crate::calculus::laplace::inverse_laplace_transform(self, expr, s, t)
}
pub fn fourier_transform_expr(
&mut self,
expr: ExprId,
t: ExprId,
omega: ExprId,
) -> Result<ExprId, crate::base::errors::SymplexError> {
crate::calculus::fourier_transform::fourier_transform(self, expr, t, omega)
}
pub fn inverse_fourier_transform_expr(
&mut self,
expr: ExprId,
omega: ExprId,
t: ExprId,
) -> Result<ExprId, crate::base::errors::SymplexError> {
crate::calculus::fourier_transform::inverse_fourier_transform(self, expr, omega, t)
}
pub fn laurent_series_expr(
&mut self,
expr: ExprId,
var: ExprId,
point: ExprId,
order: u32,
) -> Result<ExprId, crate::base::errors::SymplexError> {
crate::calculus::series::laurent_series(self, expr, var, point, order)
}
pub fn evalf_expr(
&self,
expr: ExprId,
digits: u32,
) -> Result<String, crate::base::errors::SymplexError> {
crate::transforms::evalf::evalf(self, expr, digits)
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_rust_fn(
&mut self,
expr: ExprId,
name: &str,
args: &[&str],
) -> Result<String, crate::base::errors::SymplexError> {
crate::output::codegen::to_rust_fn(self, expr, name, args)
}
pub(crate) fn interval(&mut self, start: ExprId, end: ExprId, flags: u8) -> ExprId {
crate::base::canon::canon_interval(self, start, end, flags)
}
pub(crate) fn finite_set(&mut self, elems: &[ExprId]) -> ExprId {
crate::base::canon::canon_finite_set(self, elems)
}
pub(crate) fn set_union(&mut self, sets: &[ExprId]) -> ExprId {
crate::base::canon::canon_set_union(self, sets)
}
pub(crate) fn set_intersection(&mut self, sets: &[ExprId]) -> ExprId {
crate::base::canon::canon_set_intersection(self, sets)
}
pub(crate) fn set_complement(&mut self, set: ExprId, universe: ExprId) -> ExprId {
self.intern(ExprNode::SetComplement(set, universe))
}
pub(crate) fn solve_inequality_expr(
&mut self,
expr: ExprId,
var: ExprId,
rel: crate::transforms::inequalities::Relation,
) -> Result<ExprId, crate::base::errors::SymplexError> {
crate::transforms::inequalities::solve_inequality(self, expr, var, rel)
}
pub(crate) fn solveset_expr(&mut self, expr: ExprId, var: ExprId) -> ExprId {
crate::transforms::inequalities::solveset(self, expr, var)
}
pub(crate) fn eval_sum_symbolic_expr(
&mut self,
body: ExprId,
var: ExprId,
lower: ExprId,
upper: ExprId,
) -> Option<ExprId> {
crate::transforms::sum_eval::eval_sum_symbolic(self, body, var, lower, upper)
}
pub(crate) fn is_convergent_expr(&mut self, body: ExprId, var: ExprId) -> Option<bool> {
crate::calculus::convergence::is_convergent(self, body, var)
}
}
impl Default for Arena {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Arena {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Arena")
.field("node_count", &self.nodes.len())
.field("num_count", &self.numbers.len())
.field("symbol_count", &self.symbols.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use smallvec::smallvec;
#[test]
fn pre_interned_constants_are_distinct() {
let a = Arena::new();
let ids = [
a.zero,
a.one,
a.neg_one,
a.pi,
a.e_const,
a.i_unit,
a.euler_gamma,
a.catalan,
a.golden_ratio,
a.infinity,
a.neg_infinity,
a.nan,
];
for (i, &id_a) in ids.iter().enumerate() {
for (j, &id_b) in ids.iter().enumerate() {
if i != j {
assert_ne!(id_a, id_b, "constants at index {i} and {j} should differ");
}
}
}
}
#[test]
fn intern_deduplicates_atoms() {
let mut a = Arena::new();
let x1 = a.symbol("x");
let x2 = a.symbol("x");
assert_eq!(x1, x2);
}
#[test]
fn named_constants_are_pre_interned() {
let mut a = Arena::new();
assert_eq!(a.intern(ExprNode::EulerGamma), a.euler_gamma());
assert_eq!(a.intern(ExprNode::Catalan), a.catalan());
assert_eq!(a.intern(ExprNode::GoldenRatio), a.golden_ratio());
assert!(matches!(a.node(a.euler_gamma()), ExprNode::EulerGamma));
assert!(matches!(a.node(a.catalan()), ExprNode::Catalan));
assert!(matches!(a.node(a.golden_ratio()), ExprNode::GoldenRatio));
}
#[test]
fn intern_deduplicates_compound_nodes() {
let mut a = Arena::new();
let x = a.symbol("x");
let y = a.symbol("y");
let children: SmallVec<[ExprId; 6]> = smallvec![x, y];
let add1 = a.intern(ExprNode::Add(children.clone()));
let add2 = a.intern(ExprNode::Add(children));
assert_eq!(add1, add2);
}
#[test]
fn different_nodes_get_different_ids() {
let mut a = Arena::new();
let x = a.symbol("x");
let y = a.symbol("y");
assert_ne!(x, y);
}
#[test]
fn int_creates_correct_value() {
let mut a = Arena::new();
let five = a.int(5);
match a.node(five) {
ExprNode::Num(num_id) => {
assert_eq!(*a.num(*num_id), Ratio::from_integer(BigInt::from(5)));
}
other => panic!("expected Num, got {other:?}"),
}
}
#[test]
fn int_zero_returns_pre_interned() {
let mut a = Arena::new();
let z = a.int(0);
assert_eq!(z, a.zero);
}
#[test]
fn int_one_returns_pre_interned() {
let mut a = Arena::new();
let o = a.int(1);
assert_eq!(o, a.one);
}
#[test]
fn int_neg_one_returns_pre_interned() {
let mut a = Arena::new();
let m1 = a.int(-1);
assert_eq!(m1, a.neg_one);
}
#[test]
fn rational_reduces() {
let mut a = Arena::new();
let half_a = a.rational(1, 2);
let half_b = a.rational(2, 4);
assert_eq!(half_a, half_b, "2/4 should reduce to 1/2");
}
#[test]
fn big_int_works() {
let mut a = Arena::new();
let big = a.big_int(BigInt::from(999_999_999_999i64));
match a.node(big) {
ExprNode::Num(num_id) => {
let val = a.num(*num_id);
assert_eq!(*val, Ratio::from_integer(BigInt::from(999_999_999_999i64)));
}
other => panic!("expected Num, got {other:?}"),
}
}
#[test]
fn pow_creates_correct_node() {
let mut a = Arena::new();
let x = a.symbol("x");
let two = a.int(2);
let p = a.pow(x, two);
assert_eq!(*a.node(p), ExprNode::Pow(x, two));
}
#[test]
fn neg_creates_correct_node() {
let mut a = Arena::new();
let x = a.symbol("x");
let nx = a.neg(x);
if let ExprNode::Mul(args) = a.node(nx) {
assert_eq!(args.len(), 2);
assert_eq!(args[0], a.neg_one, "first factor should be -1");
assert_eq!(args[1], x, "second factor should be x");
} else {
panic!("expected Mul node, got {:?}", a.node(nx));
}
}
#[test]
fn trig_and_transcendental_functions() {
let mut a = Arena::new();
let x = a.symbol("x");
let s = a.sin(x);
assert_eq!(*a.node(s), ExprNode::Sin(x));
let c = a.cos(x);
assert_eq!(*a.node(c), ExprNode::Cos(x));
let t = a.tan(x);
assert_eq!(*a.node(t), ExprNode::Tan(x));
let e = a.exp(x);
assert_eq!(*a.node(e), ExprNode::Exp(x));
let l = a.ln(x);
assert_eq!(*a.node(l), ExprNode::Ln(x));
let sq = a.sqrt(x);
let half = a.rational(1, 2);
assert_eq!(*a.node(sq), ExprNode::Pow(x, half));
let ab = a.abs(x);
assert_eq!(*a.node(ab), ExprNode::Abs(x));
}
#[test]
fn node_count_grows() {
let mut a = Arena::new();
let before = a.node_count();
let _x = a.symbol("x");
assert_eq!(a.node_count(), before + 1);
let _x2 = a.symbol("x");
assert_eq!(a.node_count(), before + 1);
}
#[test]
fn symbol_name_roundtrip() {
let mut a = Arena::new();
let x = a.symbol("alpha");
match a.node(x) {
ExprNode::Symbol(sym_id) => {
assert_eq!(a.symbol_name(*sym_id), "alpha");
}
other => panic!("expected Symbol, got {other:?}"),
}
}
#[test]
fn children_delegates_correctly() {
let mut a = Arena::new();
let x = a.symbol("x");
let y = a.symbol("y");
let sum = a.intern(ExprNode::Add(smallvec![x, y]));
let kids = a.children(sum);
assert_eq!(kids.len(), 2);
assert_eq!(kids[0], x);
assert_eq!(kids[1], y);
}
#[test]
fn sort_keys_are_parallel_to_nodes() {
let mut a = Arena::new();
let _x = a.symbol("x");
let _y = a.symbol("y");
assert_eq!(a.nodes.len(), a.sort_keys.len());
}
#[test]
fn sort_key_ordering_numbers_before_symbols() {
let mut a = Arena::new();
let five = a.int(5);
let x = a.symbol("x");
assert!(
a.sort_key(five) < a.sort_key(x),
"numbers should sort before symbols"
);
}
#[test]
fn intern_num_deduplicates() {
let mut a = Arena::new();
let n1 = a.intern_num(Ratio::from_integer(BigInt::from(42)));
let n2 = a.intern_num(Ratio::from_integer(BigInt::from(42)));
assert_eq!(n1, n2);
}
#[test]
fn with_config_uses_custom_config() {
let cfg = EvalConfig {
max_pow_exponent: 42,
..EvalConfig::default()
};
let a = Arena::with_config(cfg);
assert_eq!(a.config.max_pow_exponent, 42);
}
#[test]
fn default_trait_works() {
let a = Arena::default();
assert!(a.node_count() >= 9);
}
#[test]
fn add_zero_args_returns_zero() {
let mut a = Arena::new();
let result = a.add(&[]);
assert_eq!(result, a.zero);
}
#[test]
fn add_one_arg_returns_arg() {
let mut a = Arena::new();
let x = a.symbol("x");
let result = a.add(&[x]);
assert_eq!(result, x);
}
#[test]
fn mul_zero_args_returns_one() {
let mut a = Arena::new();
let result = a.mul(&[]);
assert_eq!(result, a.one);
}
#[test]
fn mul_one_arg_returns_arg() {
let mut a = Arena::new();
let x = a.symbol("x");
let result = a.mul(&[x]);
assert_eq!(result, x);
}
}