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::budget::BudgetKind;
use crate::base::config::EvalConfig;
use crate::base::interval::IntervalKind;
use crate::base::libfn::LibFn;
use crate::base::node::{ExprId, ExprNode, NumId, SymbolId};
use crate::base::numeric::Q;
use crate::base::sort_key::{SortKey, compute_sort_key, push_bounded_key};
use crate::base::symbol::SymbolTable;
use crate::base::truth::Truth;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct PartSigns {
pub(crate) re: Option<std::cmp::Ordering>,
pub(crate) im: Option<std::cmp::Ordering>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum FactKey {
Real,
Negative,
Class,
Sign,
RePart,
ImPart,
HiddenZero,
NonzeroValue,
}
const NO_SYMBOL_NODE: u8 = u8::MAX;
pub(crate) const SHARED_SYMBOL_BIT: u64 = 1 << 63;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct NodeFacts {
pub(crate) tree_size: u32,
pub(crate) height: u16,
flags: u16,
sym_mask: u64,
}
impl NodeFacts {
const HAS_UNEVALUATED: u16 = 1 << 0;
const HAS_SYMBOL: u16 = 1 << 1;
const HAS_BINDER: u16 = 1 << 2;
const SHARED_BIT_INEXACT: u16 = 1 << 3;
const ATOM: NodeFacts = NodeFacts {
tree_size: 1,
height: 1,
flags: 0,
sym_mask: 0,
};
#[inline]
pub(crate) fn has_unevaluated(self) -> bool {
self.flags & Self::HAS_UNEVALUATED != 0
}
#[inline]
pub(crate) fn has_symbol(self) -> bool {
self.flags & Self::HAS_SYMBOL != 0
}
#[inline]
pub(crate) fn has_binder(self) -> bool {
self.flags & Self::HAS_BINDER != 0
}
#[inline]
pub(crate) fn may_contain(self, inner: NodeFacts) -> bool {
let structural = Self::HAS_UNEVALUATED | Self::HAS_SYMBOL | Self::HAS_BINDER;
self.tree_size >= inner.tree_size
&& self.height >= inner.height
&& self.flags & inner.flags & structural == inner.flags & structural
}
}
fn is_unevaluated_node(node: &ExprNode) -> bool {
matches!(
node,
ExprNode::Integral(..)
| ExprNode::DefiniteIntegral(..)
| ExprNode::Derivative(..)
| ExprNode::Limit(..)
| ExprNode::Series(..)
| ExprNode::LaplaceTransform(..)
| ExprNode::InverseLaplaceTransform(..)
| ExprNode::Residue(..)
| ExprNode::DSolve(..)
| ExprNode::ConditionSet(..)
| ExprNode::Subs(..)
| ExprNode::Sum(..)
| ExprNode::Product_(..)
)
}
pub struct Arena {
nodes: Vec<ExprNode>,
numbers: Vec<Q>,
key_bytes: Vec<u8>,
key_starts: Vec<usize>,
key_scratch: Vec<u8>,
facts: Vec<NodeFacts>,
sym_bits: Vec<u8>,
bit_symbols: Vec<ExprId>,
dedup: FxHashMap<u64, SmallVec<[ExprId; 2]>>,
num_dedup: FxHashMap<u64, SmallVec<[NumId; 2]>>,
pub(crate) symbols: SymbolTable,
pub(crate) config: EvalConfig,
constant_facts: std::panic::AssertUnwindSafe<
parking_lot::Mutex<FxHashMap<(ExprId, FactKey), crate::base::truth::SignSet>>,
>,
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,
interns: u64,
work_scopes: Vec<(BudgetKind, u64)>,
serial: u64,
pub(crate) next_wild: u32,
}
static NEXT_ARENA_SERIAL: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
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(),
key_bytes: Vec::new(),
key_starts: vec![0],
key_scratch: Vec::new(),
facts: Vec::new(),
sym_bits: Vec::new(),
bit_symbols: Vec::new(),
dedup: FxHashMap::default(),
num_dedup: FxHashMap::default(),
symbols: SymbolTable::new(),
config,
constant_facts: std::panic::AssertUnwindSafe(parking_lot::Mutex::new(
FxHashMap::default(),
)),
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,
interns: 0,
work_scopes: Vec::new(),
serial: NEXT_ARENA_SERIAL.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
next_wild: 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 {
self.constant_facts.get_mut().clear();
&mut self.config
}
pub(crate) fn constant_fact(
&self,
x: ExprId,
key: FactKey,
) -> Option<crate::base::truth::SignSet> {
self.constant_facts.lock().get(&(x, key)).copied()
}
pub(crate) fn remember_constant_fact(
&self,
x: ExprId,
key: FactKey,
signs: crate::base::truth::SignSet,
) {
self.constant_facts.lock().insert((x, key), signs);
}
}
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: &Q) -> u64 {
for (tag, p) in [(1u64, (1u64 << 61) - 1), (2, (1u64 << 62) - 57)] {
match Self::residue_of_value(value.numer(), value.denom(), p) {
Some(Some(r)) => return r.wrapping_mul(4).wrapping_add(tag),
Some(None) => continue,
None => break,
}
}
use num_integer::Integer;
let mut hasher = rustc_hash::FxHasher::default();
let (mut numer, mut denom) = (value.numer().clone(), value.denom().clone());
loop {
if denom.is_zero() {
denom.hash(&mut hasher);
break;
}
let (quot, rem) = numer.div_mod_floor(&denom);
quot.hash(&mut hasher);
numer = denom;
denom = rem;
}
hasher.finish() & !3
}
fn residue_of_value(numer: &BigInt, denom: &BigInt, p: u64) -> Option<Option<u64>> {
use num_traits::{Signed, ToPrimitive};
let res = |x: &BigInt| -> u64 {
let m = x.magnitude();
let r = match m.to_u64() {
Some(v) => v % p,
None => (m % p).to_u64().unwrap_or(0),
};
if r != 0 && x.is_negative() { p - r } else { r }
};
let mul = |a: u64, b: u64| ((u128::from(a) * u128::from(b)) % u128::from(p)) as u64;
let (mut n, mut d) = (res(numer), res(denom));
if denom.is_zero() {
return None;
}
let mut owned: Option<(BigInt, BigInt)> = None;
while d == 0 {
if n != 0 {
return Some(None);
}
let pb = BigInt::from(p);
let (a, b) = owned
.take()
.unwrap_or_else(|| (numer.clone(), denom.clone()));
let (a, b) = (a / &pb, b / &pb);
(n, d) = (res(&a), res(&b));
owned = Some((a, b));
}
if d == 1 {
return Some(Some(n));
}
let (mut r0, mut r1) = (p, d);
let (mut t0, mut t1) = (0i64, 1i64);
while r1 != 0 {
let q = r0 / r1;
(r0, r1) = (r1, r0 - q * r1);
(t0, t1) = (t1, t0.wrapping_sub((q as i64).wrapping_mul(t1)));
}
let inv = t0.rem_euclid(p as i64) as u64;
Some(Some(mul(n, inv)))
}
pub(crate) fn intern(&mut self, node: ExprNode) -> ExprId {
self.interns += 1;
if let Some(value) = crate::base::canon::canon_function(self, &node) {
return value;
}
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 facts = self.facts_of_new(id, &node);
self.push_sort_key(&node);
self.nodes.push(node);
self.facts.push(facts);
self.dedup.entry(hash).or_default().push(id);
id
}
pub(crate) fn make(&mut self, node: ExprNode) -> ExprId {
use crate::base::walk::subs_node;
match node {
ExprNode::Add(c) => self.add(&c),
ExprNode::Mul(c) => self.mul(&c),
ExprNode::Pow(b, e) => self.pow(b, e),
ExprNode::Neg(x) => self.neg(x),
ExprNode::Sin(x) => self.sin(x),
ExprNode::Cos(x) => self.cos(x),
ExprNode::Tan(x) => self.tan(x),
ExprNode::Exp(x) => self.exp(x),
ExprNode::Ln(x) => self.ln(x),
ExprNode::Abs(x) => self.abs(x),
ExprNode::Atan2(y, x) => self.atan2(y, x),
ExprNode::Sign(x) => self.sign(x),
ExprNode::Re(x) => self.re(x),
ExprNode::Im(x) => self.im(x),
ExprNode::Conjugate(x) => self.conjugate(x),
ExprNode::Arg(x) => self.arg(x),
ExprNode::Si(x) => self.si(x),
ExprNode::Ci(x) => self.ci(x),
ExprNode::Ei(x) => self.ei(x),
ExprNode::Li(x) => self.li(x),
ExprNode::Zeta(x) => self.zeta(x),
ExprNode::Polygamma(n, x) => self.polygamma(n, x),
ExprNode::KroneckerDelta(i, j) => self.kronecker_delta(i, j),
ExprNode::Beta(a, b) => self.beta(a, b),
ExprNode::Factorial(x) => self.factorial(x),
ExprNode::Binomial(n, k) => self.binomial(n, k),
ExprNode::Gt(a, b) => self.gt(a, b),
ExprNode::Ge(a, b) => self.ge(a, b),
ExprNode::Eq_(a, b) => self.eq_(a, b),
ExprNode::Ne(a, b) => self.ne_(a, b),
ExprNode::Not(x) => self.not(x),
ExprNode::And(c) => self.and(&c),
ExprNode::Or(c) => self.or(&c),
ExprNode::Piecewise(pairs) => self.piecewise(&pairs),
ExprNode::Interval(a, b, kind) => self.interval(a, b, kind),
ExprNode::FiniteSet(c) => self.finite_set(&c),
ExprNode::SetUnion(c) => self.set_union(&c),
ExprNode::SetIntersection(c) => self.set_intersection(&c),
ExprNode::SetComplement(a, b) => self.set_complement(a, b),
ExprNode::DefiniteIntegral(f, v, lo, hi) => self.definite_integral(f, v, lo, hi),
ExprNode::Subs(f, v, p) => subs_node(self, f, v, p),
ExprNode::Lib(f, args) => self.lib_apply(f, &args),
other @ (ExprNode::Num(_)
| ExprNode::Symbol(_)
| ExprNode::Pi
| ExprNode::E
| ExprNode::ImaginaryUnit
| ExprNode::EulerGamma
| ExprNode::Catalan
| ExprNode::GoldenRatio
| ExprNode::PhysicalConstant(..)
| ExprNode::Infinity
| ExprNode::NegInfinity
| ExprNode::ComplexInfinity
| ExprNode::NaN
| ExprNode::Min(_)
| ExprNode::Max(_)
| ExprNode::Floor(_)
| ExprNode::Ceiling(_)
| ExprNode::Asin(_)
| ExprNode::Acos(_)
| ExprNode::Atan(_)
| ExprNode::Sinh(_)
| ExprNode::Cosh(_)
| ExprNode::Tanh(_)
| ExprNode::Asinh(_)
| ExprNode::Acosh(_)
| ExprNode::Atanh(_)
| ExprNode::Heaviside(_)
| ExprNode::DiracDelta(_)
| ExprNode::Gamma(_)
| ExprNode::LogGamma(_)
| ExprNode::Digamma(_)
| ExprNode::Erf(_)
| ExprNode::Erfc(_)
| ExprNode::LambertW(_)
| ExprNode::BoolTrue
| ExprNode::BoolFalse
| ExprNode::Apply(..)
| ExprNode::Derivative(..)
| ExprNode::Integral(..)
| ExprNode::Sum(..)
| ExprNode::Product_(..)
| ExprNode::Limit(..)
| ExprNode::Series(..)
| ExprNode::LaplaceTransform(..)
| ExprNode::InverseLaplaceTransform(..)
| ExprNode::Residue(..)
| ExprNode::RootOf(..)
| ExprNode::RootSum(..)
| ExprNode::DSolve(..)
| ExprNode::ConditionSet(..)
| ExprNode::EmptySet
| ExprNode::UniversalSet
| ExprNode::ImageSet(..)
| ExprNode::Internal(..)) => self.intern(other),
}
}
pub(crate) fn intern_num(&mut self, value: Q) -> 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 push_sort_key(&mut self, node: &ExprNode) {
let mut scratch = std::mem::take(&mut self.key_scratch);
scratch.clear();
compute_sort_key(
node,
&mut scratch,
|child_id| self.sort_key(child_id),
|num_id, out| write_decimal(&self.numbers[num_id.0 as usize], out),
|sym_id| self.symbols.name(sym_id),
);
push_bounded_key(&scratch, &mut self.key_bytes);
self.key_starts.push(self.key_bytes.len());
self.key_scratch = scratch;
}
fn facts_of_new(&mut self, id: ExprId, node: &ExprNode) -> NodeFacts {
if let ExprNode::Symbol(s) = *node {
return NodeFacts {
tree_size: 1,
height: 1,
flags: NodeFacts::HAS_SYMBOL,
sym_mask: self.assign_symbol_bit(s, id),
};
}
if node.is_atom() {
return NodeFacts::ATOM;
}
let facts = &self.facts;
let child = |c: ExprId| facts[c.0 as usize];
let (mut tree_size, mut height, mut flags, mut sym_mask) = (1u32, 0u16, 0u16, 0u64);
node.for_each_child(|c| {
let f = child(c);
tree_size = tree_size.saturating_add(f.tree_size);
height = height.max(f.height);
flags |= f.flags;
sym_mask |= f.sym_mask;
});
if is_unevaluated_node(node) {
flags |= NodeFacts::HAS_UNEVALUATED;
}
if let Some(b) = node.binder_operands()
&& let ExprNode::Symbol(var) = self.nodes[b.var.0 as usize]
{
flags |= NodeFacts::HAS_BINDER;
let outer = b.outer.iter().fold(0, |m, &c| m | child(c).sym_mask);
let mut scoped = b.scoped.iter().fold(0, |m, &c| m | child(c).sym_mask);
let var_bit = self.symbol_bit(var).unwrap_or(SHARED_SYMBOL_BIT);
if var_bit != SHARED_SYMBOL_BIT {
scoped &= !var_bit;
} else if scoped & SHARED_SYMBOL_BIT != 0 {
flags |= NodeFacts::SHARED_BIT_INEXACT;
}
sym_mask = outer | scoped;
}
NodeFacts {
tree_size,
height: height.saturating_add(1),
flags,
sym_mask,
}
}
fn assign_symbol_bit(&mut self, s: SymbolId, id: ExprId) -> u64 {
let k = self.bit_symbols.len();
let index = if k < 63 {
self.bit_symbols.push(id);
k as u8
} else {
63
};
let i = s.0 as usize;
if self.sym_bits.len() <= i {
self.sym_bits.resize(i + 1, NO_SYMBOL_NODE);
}
self.sym_bits[i] = index;
1u64 << index
}
}
fn write_decimal(q: &Q, out: &mut Vec<u8>) {
use num_traits::ToPrimitive;
use std::io::Write as _;
let _ = match (q.numer().to_i64(), q.denom().to_i64()) {
(Some(n), Some(1)) => write!(out, "{n}"),
(Some(n), Some(d)) => write!(out, "{n}/{d}"),
_ => write!(out, "{q}"),
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct ArenaOrder(pub(crate) ExprId);
impl Ord for ArenaOrder {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.0.cmp(&other.0.0)
}
}
impl PartialOrd for ArenaOrder {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
fn head_variant_name(n: &ExprNode) -> &'static str {
match n {
ExprNode::Lib(..) | ExprNode::Internal(..) => "Apply",
other => other.variant_name(),
}
}
impl Arena {
pub(crate) fn canonical_cmp(&self, a: ExprId, b: ExprId) -> std::cmp::Ordering {
use std::cmp::Ordering;
let mut stack: SmallVec<[(ExprId, ExprId); 8]> = SmallVec::new();
stack.push((a, b));
while let Some((p, q)) = stack.pop() {
if p == q {
continue;
}
let ord = self
.sort_key(p)
.cmp(self.sort_key(q))
.then_with(|| self.node_head_cmp(p, q));
if ord != Ordering::Equal {
return ord;
}
let (cp, cq) = (self.node(p).children(), self.node(q).children());
if cp.len() != cq.len() {
return cp.len().cmp(&cq.len());
}
stack.extend(cp.iter().copied().zip(cq.iter().copied()).rev());
}
Ordering::Equal
}
fn node_head_cmp(&self, p: ExprId, q: ExprId) -> std::cmp::Ordering {
use std::cmp::Ordering;
let (np, nq) = (self.node(p), self.node(q));
match (np, nq) {
(ExprNode::Num(m), ExprNode::Num(n)) => self.num(*m).cmp(self.num(*n)),
(ExprNode::Symbol(s), ExprNode::Symbol(t))
| (ExprNode::PhysicalConstant(s, _), ExprNode::PhysicalConstant(t, _))
| (ExprNode::Apply(s, _), ExprNode::Apply(t, _)) => {
self.symbol_name(*s).cmp(self.symbol_name(*t))
}
(ExprNode::Interval(_, _, f), ExprNode::Interval(_, _, g)) => {
f.flag_bits().cmp(&g.flag_bits())
}
(ExprNode::Lib(f, _), ExprNode::Lib(g, _)) => f.name().cmp(g.name()),
(ExprNode::Lib(f, _), ExprNode::Apply(t, _)) => {
f.name().cmp(self.symbol_name(*t)).then(Ordering::Greater)
}
(ExprNode::Apply(s, _), ExprNode::Lib(g, _)) => {
self.symbol_name(*s).cmp(g.name()).then(Ordering::Less)
}
(ExprNode::Internal(f, _), ExprNode::Internal(g, _)) => f.name().cmp(g.name()),
(ExprNode::Internal(f, _), ExprNode::Apply(t, _)) => {
f.name().cmp(self.symbol_name(*t)).then(Ordering::Greater)
}
(ExprNode::Apply(s, _), ExprNode::Internal(g, _)) => {
self.symbol_name(*s).cmp(g.name()).then(Ordering::Less)
}
(ExprNode::Internal(f, _), ExprNode::Lib(g, _)) => {
f.name().cmp(g.name()).then(Ordering::Greater)
}
(ExprNode::Lib(f, _), ExprNode::Internal(g, _)) => {
f.name().cmp(g.name()).then(Ordering::Less)
}
_ if std::mem::discriminant(np) == std::mem::discriminant(nq) => Ordering::Equal,
_ => head_variant_name(np).cmp(head_variant_name(nq)),
}
}
}
impl Arena {
pub fn node(&self, id: ExprId) -> &ExprNode {
&self.nodes[id.0 as usize]
}
#[inline]
pub fn sort_key(&self, id: ExprId) -> &SortKey {
let i = id.0 as usize;
&self.key_bytes[self.key_starts[i]..self.key_starts[i + 1]]
}
#[inline]
pub(crate) fn facts(&self, id: ExprId) -> NodeFacts {
self.facts[id.0 as usize]
}
#[inline]
pub(crate) fn symbol_bit(&self, s: SymbolId) -> Option<u64> {
match self.sym_bits.get(s.0 as usize) {
Some(&k) if k != NO_SYMBOL_NODE => Some(1u64 << k),
_ => None,
}
}
#[inline]
pub(crate) fn free_symbol_fact(&self, id: ExprId, s: SymbolId) -> Truth {
let Some(bit) = self.symbol_bit(s) else {
return Truth::False;
};
if self.facts(id).sym_mask & bit == 0 {
Truth::False
} else if bit != SHARED_SYMBOL_BIT {
Truth::True
} else {
Truth::Unknown
}
}
#[inline]
pub(crate) fn free_symbol_mask(&self, id: ExprId) -> u64 {
self.facts(id).sym_mask
}
#[inline]
pub(crate) fn bit_symbol(&self, k: u32) -> Option<ExprId> {
self.bit_symbols.get(k as usize).copied()
}
pub(crate) fn is_symbol_free(&self, id: ExprId) -> bool {
let f = self.facts(id);
if f.sym_mask == 0 {
true
} else if f.sym_mask != SHARED_SYMBOL_BIT || f.flags & NodeFacts::SHARED_BIT_INEXACT == 0 {
false
} else {
crate::base::walk::free_symbols(self, id).is_empty()
}
}
pub(crate) fn tree_size_capped(&self, id: ExprId, cap: usize) -> usize {
let size = self.facts(id).tree_size;
if size < u32::MAX || cap <= u32::MAX as usize {
return (size as usize).min(cap);
}
let mut sizes: FxHashMap<ExprId, usize> = FxHashMap::default();
for n in crate::base::walk::post_order_ids(self, id) {
let mut total = 1usize;
self.node(n).for_each_child(|c| {
total = total.saturating_add(sizes.get(&c).copied().unwrap_or(1));
});
sizes.insert(n, total.min(cap));
}
sizes.get(&id).copied().unwrap_or(1)
}
pub fn num(&self, id: NumId) -> &Q {
&self.numbers[id.0 as usize]
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub(crate) fn serial(&self) -> u64 {
self.serial
}
pub(crate) fn open_work_scope(&mut self, kind: BudgetKind, cap: u64) {
let mut end = self.interns.saturating_add(cap);
if let Some(&(_, outer)) = self.work_scopes.iter().rev().find(|(k, _)| *k == kind) {
end = end.min(outer);
}
self.work_scopes.push((kind, end));
}
pub(crate) fn close_work_scope(&mut self) {
self.work_scopes.pop();
}
pub(crate) fn work_exhausted(&self, kind: BudgetKind) -> bool {
self.work_scopes
.iter()
.rev()
.find(|(k, _)| *k == kind)
.is_some_and(|&(_, end)| self.interns > end)
}
pub fn symbol_name(&self, id: SymbolId) -> &str {
self.symbols.name(id)
}
pub(crate) fn lib_apply(&mut self, f: LibFn, args: &[ExprId]) -> ExprId {
debug_assert!(
f.arity().accepts(args.len()),
"{f} called with {} argument(s)",
args.len()
);
self.intern(ExprNode::Lib(f, args.iter().copied().collect()))
}
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();
let consistent = !a.is_contradictory();
debug_assert!(
consistent,
"contradictory assumptions declared on symbol `{}`: {a}",
self.symbols.name(id)
);
if consistent {
self.constant_facts.get_mut().clear();
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) -> (Q, 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: Q, 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<&Q> {
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 num_ratio(&mut self, r: Q) -> ExprId {
let num_id = self.intern_num(r);
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(crate) fn positive_symbol(&mut self, name: &str) -> ExprId {
let sym_id = self.symbols.intern(name);
let mut a = crate::base::assumptions::Assumptions::default();
a.assert_true(crate::base::assumptions::Props::POSITIVE);
a.forward_chain();
self.set_symbol_assumptions(sym_id, a);
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)
}
#[deprecated(
since = "0.50.0",
note = "an empty list means both \"no root\" and \"not solved\"; use `Arena::solve_roots`"
)]
pub fn solve_for(
&mut self,
expr: ExprId,
var: ExprId,
) -> Vec<crate::transforms::solve::Solution> {
crate::transforms::solve::known_real_roots(self, expr, var)
.into_iter()
.map(|value| crate::transforms::solve::Solution { value })
.collect()
}
pub fn solve_roots(
&mut self,
expr: ExprId,
var: ExprId,
) -> Result<Vec<ExprId>, crate::base::errors::SymplexError> {
use crate::base::errors::SymplexError;
use crate::transforms::solve::{Refusal, roots_or_refusal, solve_classified_real};
let outcome = solve_classified_real(self, expr, var);
match roots_or_refusal(self, outcome) {
Ok(roots) if !roots.is_empty() => Ok(roots),
Ok(_) => Err(SymplexError::ComputationFailed {
operation: "solve",
reason: "no root found".into(),
}),
Err(Refusal::Identity) => Err(SymplexError::InfiniteSolutions {
operation: "solve",
reason: "the equation is an identity".into(),
}),
Err(Refusal::NoSolution(reason)) => Err(SymplexError::NoSolution {
operation: "solve",
reason,
}),
Err(Refusal::Failed(reason)) => Err(SymplexError::ComputationFailed {
operation: "solve",
reason,
}),
}
}
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(crate) fn may_vanish_identically(&self, expr: ExprId) -> bool {
crate::poly::polybridge::may_vanish_identically(self, expr)
}
pub(crate) fn may_have_vanishing_denominator(&self, expr: ExprId) -> bool {
crate::poly::polybridge::may_have_vanishing_denominator(self, expr)
}
pub(crate) fn vanishes_identically(&mut self, expr: ExprId) -> bool {
crate::poly::polybridge::vanishes_identically(self, expr)
}
pub(crate) fn vanishes_by_identity(&mut self, expr: ExprId) -> bool {
crate::simplify::simplify_engine::vanishes_by_identity(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 {
self.lib_apply(LibFn::Factorial2, &[n])
}
pub fn subfactorial(&mut self, n: ExprId) -> ExprId {
self.lib_apply(LibFn::Subfactorial, &[n])
}
pub fn rising_factorial(&mut self, x: ExprId, n: ExprId) -> ExprId {
self.lib_apply(LibFn::RisingFactorial, &[x, n])
}
pub fn falling_factorial(&mut self, x: ExprId, n: ExprId) -> ExprId {
self.lib_apply(LibFn::FallingFactorial, &[x, n])
}
pub fn fibonacci(&mut self, n: ExprId) -> ExprId {
self.lib_apply(LibFn::Fibonacci, &[n])
}
pub fn lucas(&mut self, n: ExprId) -> ExprId {
self.lib_apply(LibFn::Lucas, &[n])
}
pub fn bernoulli_number(&mut self, n: ExprId) -> ExprId {
self.lib_apply(LibFn::Bernoulli, &[n])
}
pub fn harmonic(&mut self, n: ExprId) -> ExprId {
self.lib_apply(LibFn::Harmonic, &[n])
}
pub fn catalan_number(&mut self, n: ExprId) -> ExprId {
self.lib_apply(LibFn::Catalan, &[n])
}
pub fn bell(&mut self, n: ExprId) -> ExprId {
self.lib_apply(LibFn::Bell, &[n])
}
pub fn euler_number(&mut self, n: ExprId) -> ExprId {
self.lib_apply(LibFn::EulerNumber, &[n])
}
pub fn stirling1(&mut self, n: ExprId, k: ExprId) -> ExprId {
self.lib_apply(LibFn::Stirling1, &[n, k])
}
pub fn stirling2(&mut self, n: ExprId, k: ExprId) -> ExprId {
self.lib_apply(LibFn::Stirling2, &[n, k])
}
pub fn partition_count(&mut self, n: ExprId) -> ExprId {
self.lib_apply(LibFn::PartitionCount, &[n])
}
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 lambertw_branch(&mut self, arg: ExprId, k: ExprId) -> ExprId {
if self.as_num(k).is_some_and(|q| q.is_zero()) {
return self.lambertw(arg);
}
self.lib_apply(LibFn::LambertW, &[arg, k])
}
pub fn besselj(&mut self, order: ExprId, arg: ExprId) -> ExprId {
self.lib_apply(LibFn::BesselJ, &[order, arg])
}
pub fn bessely(&mut self, order: ExprId, arg: ExprId) -> ExprId {
self.lib_apply(LibFn::BesselY, &[order, arg])
}
pub fn besseli(&mut self, order: ExprId, arg: ExprId) -> ExprId {
self.lib_apply(LibFn::BesselI, &[order, arg])
}
pub fn besselk(&mut self, order: ExprId, arg: ExprId) -> ExprId {
self.lib_apply(LibFn::BesselK, &[order, arg])
}
pub fn legendre(&mut self, n: ExprId, x: ExprId) -> ExprId {
self.lib_apply(LibFn::Legendre, &[n, x])
}
pub fn chebyshev_t(&mut self, n: ExprId, x: ExprId) -> ExprId {
self.lib_apply(LibFn::ChebyshevT, &[n, x])
}
pub fn chebyshev_u(&mut self, n: ExprId, x: ExprId) -> ExprId {
self.lib_apply(LibFn::ChebyshevU, &[n, x])
}
pub fn hermite(&mut self, n: ExprId, x: ExprId) -> ExprId {
self.lib_apply(LibFn::Hermite, &[n, x])
}
pub fn laguerre(&mut self, n: ExprId, x: ExprId) -> ExprId {
self.lib_apply(LibFn::Laguerre, &[n, x])
}
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(crate) fn sign_of_real_constant(&self, expr: ExprId) -> Option<std::cmp::Ordering> {
use crate::base::assumptions::{AssumptionCache, Props};
if !self.is_symbol_free(expr)
|| AssumptionCache::new().query(self, expr, Props::REAL) != Some(true)
{
return None;
}
crate::transforms::eval::certified_real_sign(self, expr)
}
pub(crate) fn part_signs(&self, expr: ExprId) -> Option<PartSigns> {
crate::transforms::eval::certified_part_signs(self, expr)
}
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, kind: IntervalKind) -> ExprId {
crate::base::canon::canon_interval(self, start, end, kind)
}
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::calculus::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() + 1, a.key_starts.len());
assert_eq!(a.key_starts.last().copied(), Some(a.key_bytes.len()));
assert_eq!(a.nodes.len(), a.facts.len());
}
#[test]
fn decimal_key_bytes_are_display_bytes() {
let big = |s: &str| s.parse::<BigInt>().unwrap();
let mut values = Vec::new();
for n in [
"0",
"1",
"-1",
"9223372036854775807",
"9223372036854775808",
"-9223372036854775808",
"-9223372036854775809",
"123456789012345678901234567890",
] {
for d in ["1", "2", "9223372036854775807", "9223372036854775808"] {
values.push(Ratio::new(big(n), big(d)));
}
}
values.push(Ratio::new_raw(big("3"), big("-4")));
values.push(Ratio::new_raw(big("-6"), big("-1")));
values.push(Ratio::new_raw(big("5"), big("1")));
for q in values {
let mut out = vec![7u8];
write_decimal(&q, &mut out);
assert_eq!(&out[1..], q.to_string().as_bytes(), "{q}");
}
}
#[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 hash_num_is_representation_invariant() {
let p1 = BigInt::from((1u64 << 61) - 1);
let p2 = BigInt::from((1u64 << 62) - 57);
let big: BigInt = BigInt::from(3).pow(4000u32) + 1;
let raw = |n: BigInt, d: BigInt| Ratio::new_raw(n, d);
let cases: Vec<(BigInt, BigInt)> = vec![
(BigInt::from(0), BigInt::from(1)),
(BigInt::from(-7), BigInt::from(3)),
(BigInt::from(5), p1.clone()),
(BigInt::from(-5), &p1 * &p2),
(big.clone(), BigInt::from(7).pow(3000u32)),
(-big.clone(), &p1 * BigInt::from(11)),
];
for (n, d) in cases {
let reduced = Ratio::new(n.clone(), d.clone());
let h = Arena::hash_num(&reduced);
for k in [BigInt::from(2), p1.clone(), &p1 * &p1, &p1 * &p2 * 3] {
let unreduced = raw(&n * &k, &d * &k);
assert!(Arena::hash_num(&unreduced) == h, "× {k}");
if n.bits() < 256 {
let mut a = Arena::new();
let i = a.intern_num(reduced.clone());
assert_eq!(a.intern_num(unreduced), i);
}
}
}
assert_ne!(
Arena::hash_num(&Ratio::new(BigInt::from(1), BigInt::from(3))),
Arena::hash_num(&Ratio::new(BigInt::from(2), BigInt::from(3)))
);
let start = std::time::Instant::now();
let q = Ratio::new(
BigInt::from(7).pow(7000u32) + 2,
BigInt::from(5).pow(8500u32) + 3,
);
let _ = Arena::hash_num(&q);
assert!(start.elapsed().as_secs() < 2, "took {:?}", start.elapsed());
}
#[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);
}
}