use smallvec::SmallVec;
use std::fmt;
use symplex_macros::NodeShape;
use crate::base::interval::IntervalKind;
use crate::base::walk::Binder;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExprId(pub(in crate::base) u32);
impl ExprId {
pub(crate) const fn index(self) -> u32 {
self.0
}
pub(crate) const fn from_index(i: u32) -> ExprId {
ExprId(i)
}
}
impl fmt::Debug for ExprId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "e{}", self.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct NumId(pub(in crate::base) u32);
impl fmt::Debug for NumId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "n{}", self.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SymbolId(pub(in crate::base) u32);
impl fmt::Debug for SymbolId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "s{}", self.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CtxId(pub u32);
impl fmt::Debug for CtxId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ctx{}", self.0)
}
}
#[derive(Clone, PartialEq, Eq, Hash, NodeShape)]
pub enum ExprNode {
Num(NumId),
Symbol(SymbolId),
Pi,
E,
ImaginaryUnit,
EulerGamma,
Catalan,
GoldenRatio,
PhysicalConstant(SymbolId, #[payload] ExprId),
Infinity,
NegInfinity,
ComplexInfinity,
NaN,
Add(SmallVec<[ExprId; 6]>),
Mul(SmallVec<[ExprId; 6]>),
Min(SmallVec<[ExprId; 4]>),
Max(SmallVec<[ExprId; 4]>),
Pow(ExprId, ExprId),
Neg(ExprId),
Floor(ExprId),
Ceiling(ExprId),
Sin(ExprId),
Cos(ExprId),
Tan(ExprId),
Exp(ExprId),
Ln(ExprId),
Abs(ExprId),
Asin(ExprId),
Acos(ExprId),
Atan(ExprId),
Atan2(ExprId, ExprId),
Sinh(ExprId),
Cosh(ExprId),
Tanh(ExprId),
Asinh(ExprId),
Acosh(ExprId),
Atanh(ExprId),
Sign(ExprId),
Heaviside(ExprId),
DiracDelta(ExprId),
Re(ExprId),
Im(ExprId),
Conjugate(ExprId),
Arg(ExprId),
Gamma(ExprId),
LogGamma(ExprId),
Digamma(ExprId),
Erf(ExprId),
Erfc(ExprId),
LambertW(ExprId),
Beta(ExprId, ExprId),
Si(ExprId),
Ci(ExprId),
Ei(ExprId),
Li(ExprId),
Zeta(ExprId),
Polygamma(ExprId, ExprId),
KroneckerDelta(ExprId, ExprId),
Factorial(ExprId),
Binomial(ExprId, ExprId),
BoolTrue,
BoolFalse,
Gt(ExprId, ExprId),
Ge(ExprId, ExprId),
Eq_(ExprId, ExprId),
Ne(ExprId, ExprId),
And(SmallVec<[ExprId; 6]>),
Or(SmallVec<[ExprId; 6]>),
Not(ExprId),
Piecewise(SmallVec<[(ExprId, ExprId); 3]>),
Apply(SymbolId, SmallVec<[ExprId; 2]>),
#[var_slot(var = 1, scoped(0))]
Derivative(ExprId, ExprId),
#[var_slot(var = 1, scoped(0))]
Integral(ExprId, ExprId),
#[binder(var = 1, scoped(0), outer(2, 3))]
DefiniteIntegral(ExprId, ExprId, ExprId, ExprId),
#[binder(var = 1, scoped(0), outer(2, 3))]
Sum(ExprId, ExprId, ExprId, ExprId),
#[binder(var = 1, scoped(0), outer(2, 3))]
Product_(ExprId, ExprId, ExprId, ExprId),
#[binder(var = 1, scoped(0), outer(2))]
Limit(ExprId, ExprId, ExprId),
#[var_slot(var = 1, scoped(0), outer(2, 3))]
Series(ExprId, ExprId, ExprId, ExprId),
#[binder(var = 1, scoped(0), outer(2))]
LaplaceTransform(ExprId, ExprId, ExprId),
#[binder(var = 1, scoped(0), outer(2))]
InverseLaplaceTransform(ExprId, ExprId, ExprId),
#[binder(var = 1, scoped(0), outer(2))]
Residue(ExprId, ExprId, ExprId),
#[binder(var = 1, scoped(0), outer(2))]
RootOf(ExprId, ExprId, ExprId),
#[binder(var = 2, scoped(0, 1))]
RootSum(ExprId, ExprId, ExprId),
#[var_slot(var = 2, scoped(0, 1))]
DSolve(ExprId, ExprId, ExprId),
#[binder(var = 0, scoped(1))]
ConditionSet(ExprId, ExprId),
EmptySet,
UniversalSet,
Interval(ExprId, ExprId, IntervalKind),
FiniteSet(SmallVec<[ExprId; 4]>),
SetUnion(SmallVec<[ExprId; 4]>),
SetIntersection(SmallVec<[ExprId; 4]>),
SetComplement(ExprId, ExprId),
#[binder(var = 1, scoped(0), outer(2))]
Subs(ExprId, ExprId, ExprId),
#[binder(var = 0, scoped(1))]
ImageSet(ExprId, ExprId),
}
impl IntervalKind {
pub(crate) const fn flag_bits(self) -> u8 {
match self {
IntervalKind::Closed => 0,
IntervalKind::LeftOpen => 1,
IntervalKind::RightOpen => 2,
IntervalKind::Open => 3,
}
}
pub(crate) const fn from_flag_bits(flags: u8) -> Self {
IntervalKind::from_open_ends(flags & 1 != 0, flags & 2 != 0)
}
}
pub(crate) trait IdRemap {
fn expr(&mut self, id: ExprId) -> ExprId;
fn num(&mut self, id: NumId) -> NumId;
fn symbol(&mut self, id: SymbolId) -> SymbolId;
}
impl ExprNode {
pub fn is_set_node(&self) -> bool {
matches!(
self,
ExprNode::EmptySet
| ExprNode::UniversalSet
| ExprNode::Interval(..)
| ExprNode::FiniteSet(_)
| ExprNode::SetUnion(_)
| ExprNode::SetIntersection(_)
| ExprNode::SetComplement(..)
| ExprNode::ImageSet(..)
)
}
}
impl fmt::Debug for ExprNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExprNode::Num(id) => write!(f, "Num({id:?})"),
ExprNode::Symbol(id) => write!(f, "Symbol({id:?})"),
ExprNode::Pi => write!(f, "Pi"),
ExprNode::E => write!(f, "E"),
ExprNode::ImaginaryUnit => write!(f, "ImaginaryUnit"),
ExprNode::EulerGamma => write!(f, "EulerGamma"),
ExprNode::Catalan => write!(f, "Catalan"),
ExprNode::GoldenRatio => write!(f, "GoldenRatio"),
ExprNode::PhysicalConstant(name, val) => {
write!(f, "PhysicalConstant({name:?}, {val:?})")
}
ExprNode::Infinity => write!(f, "Infinity"),
ExprNode::NegInfinity => write!(f, "NegInfinity"),
ExprNode::ComplexInfinity => write!(f, "ComplexInfinity"),
ExprNode::NaN => write!(f, "NaN"),
ExprNode::Add(ids) => f.debug_tuple("Add").field(ids).finish(),
ExprNode::Mul(ids) => f.debug_tuple("Mul").field(ids).finish(),
ExprNode::Pow(base, exp) => f.debug_tuple("Pow").field(base).field(exp).finish(),
ExprNode::Neg(x) => f.debug_tuple("Neg").field(x).finish(),
ExprNode::Sin(x) => f.debug_tuple("Sin").field(x).finish(),
ExprNode::Cos(x) => f.debug_tuple("Cos").field(x).finish(),
ExprNode::Tan(x) => f.debug_tuple("Tan").field(x).finish(),
ExprNode::Exp(x) => f.debug_tuple("Exp").field(x).finish(),
ExprNode::Ln(x) => f.debug_tuple("Ln").field(x).finish(),
ExprNode::Abs(x) => f.debug_tuple("Abs").field(x).finish(),
ExprNode::Asin(x) => f.debug_tuple("Asin").field(x).finish(),
ExprNode::Acos(x) => f.debug_tuple("Acos").field(x).finish(),
ExprNode::Atan(x) => f.debug_tuple("Atan").field(x).finish(),
ExprNode::Atan2(y, x) => f.debug_tuple("Atan2").field(y).field(x).finish(),
ExprNode::Sinh(x) => f.debug_tuple("Sinh").field(x).finish(),
ExprNode::Cosh(x) => f.debug_tuple("Cosh").field(x).finish(),
ExprNode::Tanh(x) => f.debug_tuple("Tanh").field(x).finish(),
ExprNode::Asinh(x) => f.debug_tuple("Asinh").field(x).finish(),
ExprNode::Acosh(x) => f.debug_tuple("Acosh").field(x).finish(),
ExprNode::Atanh(x) => f.debug_tuple("Atanh").field(x).finish(),
ExprNode::Floor(x) => f.debug_tuple("Floor").field(x).finish(),
ExprNode::Ceiling(x) => f.debug_tuple("Ceiling").field(x).finish(),
ExprNode::Min(ids) => f.debug_tuple("Min").field(ids).finish(),
ExprNode::Max(ids) => f.debug_tuple("Max").field(ids).finish(),
ExprNode::Sign(id) => write!(f, "Sign({id:?})"),
ExprNode::Heaviside(x) => f.debug_tuple("Heaviside").field(x).finish(),
ExprNode::DiracDelta(x) => f.debug_tuple("DiracDelta").field(x).finish(),
ExprNode::Re(x) => f.debug_tuple("Re").field(x).finish(),
ExprNode::Im(x) => f.debug_tuple("Im").field(x).finish(),
ExprNode::Conjugate(x) => f.debug_tuple("Conjugate").field(x).finish(),
ExprNode::Arg(x) => f.debug_tuple("Arg").field(x).finish(),
ExprNode::Gamma(x) => f.debug_tuple("Gamma").field(x).finish(),
ExprNode::LogGamma(x) => f.debug_tuple("LogGamma").field(x).finish(),
ExprNode::Digamma(x) => f.debug_tuple("Digamma").field(x).finish(),
ExprNode::Erf(x) => f.debug_tuple("Erf").field(x).finish(),
ExprNode::Erfc(x) => f.debug_tuple("Erfc").field(x).finish(),
ExprNode::LambertW(x) => f.debug_tuple("LambertW").field(x).finish(),
ExprNode::Beta(a, b) => f.debug_tuple("Beta").field(a).field(b).finish(),
ExprNode::Si(x) => f.debug_tuple("Si").field(x).finish(),
ExprNode::Ci(x) => f.debug_tuple("Ci").field(x).finish(),
ExprNode::Ei(x) => f.debug_tuple("Ei").field(x).finish(),
ExprNode::Li(x) => f.debug_tuple("Li").field(x).finish(),
ExprNode::Zeta(x) => f.debug_tuple("Zeta").field(x).finish(),
ExprNode::Polygamma(n, x) => f.debug_tuple("Polygamma").field(n).field(x).finish(),
ExprNode::KroneckerDelta(i, j) => {
f.debug_tuple("KroneckerDelta").field(i).field(j).finish()
}
ExprNode::Factorial(id) => write!(f, "Factorial({id:?})"),
ExprNode::Binomial(n, k) => write!(f, "Binomial({n:?}, {k:?})"),
ExprNode::BoolTrue => write!(f, "BoolTrue"),
ExprNode::BoolFalse => write!(f, "BoolFalse"),
ExprNode::Gt(a, b) => f.debug_tuple("Gt").field(a).field(b).finish(),
ExprNode::Ge(a, b) => f.debug_tuple("Ge").field(a).field(b).finish(),
ExprNode::Eq_(a, b) => f.debug_tuple("Eq_").field(a).field(b).finish(),
ExprNode::Ne(a, b) => f.debug_tuple("Ne").field(a).field(b).finish(),
ExprNode::And(ids) => f.debug_tuple("And").field(ids).finish(),
ExprNode::Or(ids) => f.debug_tuple("Or").field(ids).finish(),
ExprNode::Not(x) => f.debug_tuple("Not").field(x).finish(),
ExprNode::Piecewise(pairs) => {
let mut d = f.debug_tuple("Piecewise");
for pair in pairs {
d.field(pair);
}
d.finish()
}
ExprNode::Apply(sym, args) => f.debug_tuple("Apply").field(sym).field(args).finish(),
ExprNode::Derivative(body, var) => {
f.debug_tuple("Derivative").field(body).field(var).finish()
}
ExprNode::Integral(body, var) => {
f.debug_tuple("Integral").field(body).field(var).finish()
}
ExprNode::DefiniteIntegral(body, var, lo, hi) => f
.debug_tuple("DefiniteIntegral")
.field(body)
.field(var)
.field(lo)
.field(hi)
.finish(),
ExprNode::Sum(body, var, lo, hi) => f
.debug_tuple("Sum")
.field(body)
.field(var)
.field(lo)
.field(hi)
.finish(),
ExprNode::Product_(body, var, lo, hi) => f
.debug_tuple("Product_")
.field(body)
.field(var)
.field(lo)
.field(hi)
.finish(),
ExprNode::EmptySet => write!(f, "EmptySet"),
ExprNode::UniversalSet => write!(f, "UniversalSet"),
ExprNode::Interval(a, b, kind) => f
.debug_tuple("Interval")
.field(a)
.field(b)
.field(&kind.flag_bits())
.finish(),
ExprNode::FiniteSet(ids) => f.debug_tuple("FiniteSet").field(ids).finish(),
ExprNode::SetUnion(ids) => f.debug_tuple("SetUnion").field(ids).finish(),
ExprNode::SetIntersection(ids) => f.debug_tuple("SetIntersection").field(ids).finish(),
ExprNode::SetComplement(a, b) => {
f.debug_tuple("SetComplement").field(a).field(b).finish()
}
ExprNode::Limit(body, var, point) => f
.debug_tuple("Limit")
.field(body)
.field(var)
.field(point)
.finish(),
ExprNode::Series(body, var, point, order) => f
.debug_tuple("Series")
.field(body)
.field(var)
.field(point)
.field(order)
.finish(),
ExprNode::LaplaceTransform(body, t, s) => f
.debug_tuple("LaplaceTransform")
.field(body)
.field(t)
.field(s)
.finish(),
ExprNode::InverseLaplaceTransform(body, s, t) => f
.debug_tuple("InverseLaplaceTransform")
.field(body)
.field(s)
.field(t)
.finish(),
ExprNode::Residue(body, var, point) => f
.debug_tuple("Residue")
.field(body)
.field(var)
.field(point)
.finish(),
ExprNode::RootOf(poly, var, idx) => f
.debug_tuple("RootOf")
.field(poly)
.field(var)
.field(idx)
.finish(),
ExprNode::DSolve(expr, func, var) => f
.debug_tuple("DSolve")
.field(expr)
.field(func)
.field(var)
.finish(),
ExprNode::RootSum(poly, body, sumvar) => f
.debug_tuple("RootSum")
.field(poly)
.field(body)
.field(sumvar)
.finish(),
ExprNode::ConditionSet(var, cond) => f
.debug_tuple("ConditionSet")
.field(var)
.field(cond)
.finish(),
ExprNode::Subs(body, var, point) => f
.debug_tuple("Subs")
.field(body)
.field(var)
.field(point)
.finish(),
ExprNode::ImageSet(var, body) => {
f.debug_tuple("ImageSet").field(var).field(body).finish()
}
}
}
}
#[cfg(test)]
pub(crate) fn shape_samples(
c: [ExprId; 8],
num: NumId,
sym: SymbolId,
value: ExprId,
) -> Vec<ExprNode> {
use ExprNode as N;
use smallvec::smallvec;
let [a, b, d, e, g, h, k, m] = c;
vec![
N::Num(num),
N::Symbol(sym),
N::Pi,
N::E,
N::ImaginaryUnit,
N::EulerGamma,
N::Catalan,
N::GoldenRatio,
N::PhysicalConstant(sym, value),
N::Infinity,
N::NegInfinity,
N::ComplexInfinity,
N::NaN,
N::Add(smallvec![a, b]),
N::Add(smallvec![a, b, d, e, g, h, k]),
N::Mul(smallvec![a, b, d]),
N::Mul(smallvec![a, b, d, e, g, h, k]),
N::Min(smallvec![a, b]),
N::Max(smallvec![a, b, d, e, g]),
N::Pow(a, b),
N::Neg(a),
N::Floor(a),
N::Ceiling(a),
N::Sin(a),
N::Cos(a),
N::Tan(a),
N::Exp(a),
N::Ln(a),
N::Abs(a),
N::Asin(a),
N::Acos(a),
N::Atan(a),
N::Atan2(a, b),
N::Sinh(a),
N::Cosh(a),
N::Tanh(a),
N::Asinh(a),
N::Acosh(a),
N::Atanh(a),
N::Sign(a),
N::Heaviside(a),
N::DiracDelta(a),
N::Re(a),
N::Im(a),
N::Conjugate(a),
N::Arg(a),
N::Gamma(a),
N::LogGamma(a),
N::Digamma(a),
N::Erf(a),
N::Erfc(a),
N::LambertW(a),
N::Beta(a, b),
N::Si(a),
N::Ci(a),
N::Ei(a),
N::Li(a),
N::Zeta(a),
N::Polygamma(a, b),
N::KroneckerDelta(a, b),
N::Factorial(a),
N::Binomial(a, b),
N::BoolTrue,
N::BoolFalse,
N::Gt(a, b),
N::Ge(a, b),
N::Eq_(a, b),
N::Ne(a, b),
N::And(smallvec![a, b]),
N::Or(smallvec![a, b, d]),
N::Not(a),
N::Piecewise(smallvec![(a, b)]),
N::Piecewise(smallvec![(a, b), (d, e), (g, h), (k, m)]),
N::Apply(sym, smallvec![]),
N::Apply(sym, smallvec![a]),
N::Apply(sym, smallvec![a, b, d]),
N::Derivative(a, b),
N::Integral(a, b),
N::DefiniteIntegral(a, b, d, e),
N::Sum(a, b, d, e),
N::Product_(a, b, d, e),
N::Limit(a, b, d),
N::Series(a, b, d, e),
N::LaplaceTransform(a, b, d),
N::InverseLaplaceTransform(a, b, d),
N::Residue(a, b, d),
N::RootOf(a, b, d),
N::RootSum(a, b, d),
N::DSolve(a, b, d),
N::ConditionSet(a, b),
N::EmptySet,
N::UniversalSet,
N::Interval(a, b, IntervalKind::Closed),
N::Interval(a, b, IntervalKind::LeftOpen),
N::Interval(a, b, IntervalKind::RightOpen),
N::Interval(a, b, IntervalKind::Open),
N::FiniteSet(smallvec![a]),
N::FiniteSet(smallvec![a, b, d, e, g]),
N::SetUnion(smallvec![a, b]),
N::SetIntersection(smallvec![a, b, d]),
N::SetComplement(a, b),
N::Subs(a, b, d),
N::ImageSet(a, b),
]
}
#[cfg(test)]
mod tests {
use super::*;
use smallvec::smallvec;
fn ids8(base: u32) -> [ExprId; 8] {
std::array::from_fn(|i| ExprId(base + i as u32))
}
fn samples() -> Vec<ExprNode> {
shape_samples(ids8(10), NumId(3), SymbolId(4), ExprId(99))
}
const ATOMS: &[&str] = &[
"Num",
"Symbol",
"Pi",
"E",
"ImaginaryUnit",
"EulerGamma",
"Catalan",
"GoldenRatio",
"PhysicalConstant",
"Infinity",
"NegInfinity",
"ComplexInfinity",
"NaN",
"BoolTrue",
"BoolFalse",
"EmptySet",
"UniversalSet",
];
const BINDERS: &[(&str, usize, &[usize], &[usize])] = &[
("Sum", 1, &[0], &[2, 3]),
("Product_", 1, &[0], &[2, 3]),
("DefiniteIntegral", 1, &[0], &[2, 3]),
("RootSum", 2, &[0, 1], &[]),
("ConditionSet", 0, &[1], &[]),
("ImageSet", 0, &[1], &[]),
("RootOf", 1, &[0], &[2]),
("Limit", 1, &[0], &[2]),
("Residue", 1, &[0], &[2]),
("Subs", 1, &[0], &[2]),
("LaplaceTransform", 1, &[0], &[2]),
("InverseLaplaceTransform", 1, &[0], &[2]),
];
const VAR_SLOTS: &[(&str, usize, &[usize], &[usize])] = &[
("Derivative", 1, &[0], &[]),
("Integral", 1, &[0], &[]),
("Series", 1, &[0], &[2, 3]),
("DSolve", 2, &[0, 1], &[]),
];
#[test]
fn shape_samples_cover_every_variant() {
let names: std::collections::BTreeSet<&str> =
samples().iter().map(ExprNode::variant_name).collect();
let all: std::collections::BTreeSet<&str> =
ExprNode::VARIANT_NAMES.iter().copied().collect();
assert_eq!(names, all);
assert_eq!(all.len(), ExprNode::VARIANT_NAMES.len());
for n in samples() {
let debug = format!("{n:?}");
let head = debug.split('(').next().unwrap_or_default();
assert_eq!(n.variant_name(), head, "{debug}");
}
}
#[test]
fn generated_children_count_and_atoms_agree() {
for n in samples() {
let mut seen: SmallVec<[ExprId; 6]> = SmallVec::new();
n.for_each_child(|c| seen.push(c));
assert_eq!(seen, n.children(), "{n:?}");
assert_eq!(n.child_count(), seen.len(), "{n:?}");
assert_eq!(n.is_atom(), ATOMS.contains(&n.variant_name()), "{n:?}");
if n.is_atom() {
assert!(seen.is_empty(), "{n:?}");
}
}
let pc = ExprNode::PhysicalConstant(SymbolId(1), ExprId(5));
assert!(pc.children().is_empty() && pc.is_atom());
let pw = ExprNode::Piecewise(smallvec![(ExprId(1), ExprId(2)), (ExprId(3), ExprId(4))]);
assert_eq!(
pw.children().as_slice(),
&[ExprId(1), ExprId(2), ExprId(3), ExprId(4)]
);
}
#[test]
fn map_children_maps_each_child_in_order_and_keeps_payload() {
let shift = |c: ExprId| ExprId(c.0 + 100);
for n in samples() {
assert_eq!(n.map_children(|c| c), None, "{n:?}");
let mut order = Vec::new();
let mapped = n.map_children(|c| {
order.push(c);
shift(c)
});
assert_eq!(order.as_slice(), n.children().as_slice(), "{n:?}");
match mapped {
None => assert!(n.child_count() == 0, "{n:?}"),
Some(m) => {
assert_eq!(m.variant_name(), n.variant_name());
let want: SmallVec<[ExprId; 6]> = n.children().into_iter().map(shift).collect();
assert_eq!(m.children(), want, "{n:?}");
assert_eq!(m.map_children(|c| ExprId(c.0 - 100)), Some(n.clone()));
}
}
}
}
struct Offset(i64);
impl IdRemap for Offset {
fn expr(&mut self, id: ExprId) -> ExprId {
ExprId((i64::from(id.0) + self.0) as u32)
}
fn num(&mut self, id: NumId) -> NumId {
NumId((i64::from(id.0) + self.0) as u32)
}
fn symbol(&mut self, id: SymbolId) -> SymbolId {
SymbolId((i64::from(id.0) + self.0) as u32)
}
}
#[test]
fn remap_ids_maps_every_id_and_nothing_else() {
for n in samples() {
let m = n.remap_ids(&mut Offset(1000));
assert_eq!(m.variant_name(), n.variant_name());
let want: SmallVec<[ExprId; 6]> = n
.children()
.into_iter()
.map(|c| ExprId(c.0 + 1000))
.collect();
assert_eq!(m.children(), want, "{n:?}");
assert_eq!(m.remap_ids(&mut Offset(-1000)), n, "{n:?}");
}
let pc = ExprNode::PhysicalConstant(SymbolId(1), ExprId(5));
assert_eq!(
pc.remap_ids(&mut Offset(7)),
ExprNode::PhysicalConstant(SymbolId(8), ExprId(12))
);
assert_eq!(
ExprNode::Num(NumId(2)).remap_ids(&mut Offset(7)),
ExprNode::Num(NumId(9))
);
let iv = ExprNode::Interval(ExprId(1), ExprId(2), IntervalKind::LeftOpen);
assert_eq!(
iv.remap_ids(&mut Offset(1)),
ExprNode::Interval(ExprId(2), ExprId(3), IntervalKind::LeftOpen)
);
}
fn check_layouts(
table: &[(&str, usize, &[usize], &[usize])],
view: impl Fn(&ExprNode) -> Option<Binder>,
rebuild: impl Fn(&ExprNode, ExprId, &[ExprId], &[ExprId]) -> Option<ExprNode>,
) {
for n in samples() {
let kids = n.children();
let row = table.iter().find(|r| r.0 == n.variant_name());
match (view(&n), row) {
(None, None) => {
assert_eq!(
rebuild(&n, kids.first().copied().unwrap_or(ExprId(0)), &[], &[]),
None
);
}
(Some(b), Some(&(_, var, scoped, outer))) => {
assert_eq!(b.var, kids[var], "{n:?}");
let s: Vec<ExprId> = scoped.iter().map(|&i| kids[i]).collect();
let o: Vec<ExprId> = outer.iter().map(|&i| kids[i]).collect();
assert_eq!(b.scoped.as_slice(), s.as_slice(), "{n:?}");
assert_eq!(b.outer.as_slice(), o.as_slice(), "{n:?}");
assert_eq!(n.var_operand(), Some(b.var), "{n:?}");
assert_eq!(rebuild(&n, b.var, &b.scoped, &b.outer), Some(n.clone()));
let renamed = rebuild(&n, ExprId(77), &b.scoped, &b.outer);
assert_eq!(renamed.and_then(|r| r.var_operand()), Some(ExprId(77)));
assert_eq!(
rebuild(&n, b.var, &b.outer, &b.scoped).is_some(),
scoped.len() == outer.len()
);
}
(got, want) => panic!("{n:?}: layout {got:?}, table {want:?}"),
}
}
}
#[test]
fn binder_and_var_slot_layouts_are_the_048_tables() {
check_layouts(
BINDERS,
ExprNode::binder_operands,
ExprNode::with_binder_operands,
);
check_layouts(
VAR_SLOTS,
ExprNode::var_slot_operands,
ExprNode::with_var_slot_operands,
);
for n in samples() {
let both = n.binder_operands().is_some() && n.var_slot_operands().is_some();
assert!(!both, "{n:?}");
if n.binder_operands().is_none() && n.var_slot_operands().is_none() {
assert_eq!(n.var_operand(), None, "{n:?}");
}
}
}
#[test]
fn interval_kind_flag_bits_are_the_048_flags() {
use IntervalKind::*;
for (kind, bits) in [(Closed, 0u8), (LeftOpen, 1), (RightOpen, 2), (Open, 3)] {
assert_eq!(kind.flag_bits(), bits);
assert_eq!(IntervalKind::from_flag_bits(bits), kind);
assert_eq!(kind.lower_open(), bits & 1 != 0);
assert_eq!(kind.upper_open(), bits & 2 != 0);
}
assert_eq!(IntervalKind::from_flag_bits(4), Closed);
assert_eq!(IntervalKind::from_flag_bits(0xff), Open);
assert_eq!(
format!("{:?}", ExprNode::Interval(ExprId(1), ExprId(2), RightOpen)),
"Interval(e1, e2, 2)"
);
}
#[test]
fn debug_expr_id() {
let id = ExprId(42);
assert_eq!(format!("{id:?}"), "e42");
}
#[test]
fn debug_num_id() {
let id = NumId(7);
assert_eq!(format!("{id:?}"), "n7");
}
#[test]
fn debug_symbol_id() {
let id = SymbolId(3);
assert_eq!(format!("{id:?}"), "s3");
}
#[test]
fn debug_ctx_id() {
let id = CtxId(0);
assert_eq!(format!("{id:?}"), "ctx0");
}
#[test]
fn atom_has_no_children() {
let node = ExprNode::Pi;
assert!(node.is_atom());
assert!(node.children().is_empty());
}
#[test]
fn num_is_atom() {
let node = ExprNode::Num(NumId(0));
assert!(node.is_atom());
assert!(node.children().is_empty());
}
#[test]
fn symbol_is_atom() {
let node = ExprNode::Symbol(SymbolId(1));
assert!(node.is_atom());
}
#[test]
fn special_values_are_atoms() {
for node in [
ExprNode::Infinity,
ExprNode::NegInfinity,
ExprNode::ComplexInfinity,
ExprNode::NaN,
ExprNode::ImaginaryUnit,
ExprNode::E,
] {
assert!(node.is_atom(), "{node:?} should be an atom");
assert!(node.children().is_empty());
}
}
#[test]
fn add_children() {
let ids: SmallVec<[ExprId; 6]> = smallvec![ExprId(1), ExprId(2), ExprId(3)];
let node = ExprNode::Add(ids.clone());
assert!(!node.is_atom());
assert_eq!(node.children(), ids);
}
#[test]
fn mul_children() {
let ids: SmallVec<[ExprId; 6]> = smallvec![ExprId(4), ExprId(5)];
let node = ExprNode::Mul(ids.clone());
assert!(!node.is_atom());
assert_eq!(node.children(), ids);
}
#[test]
fn pow_children() {
let node = ExprNode::Pow(ExprId(10), ExprId(20));
assert!(!node.is_atom());
let kids = node.children();
assert_eq!(kids.len(), 2);
assert_eq!(kids[0], ExprId(10));
assert_eq!(kids[1], ExprId(20));
}
#[test]
fn unary_children() {
for node in [
ExprNode::Neg(ExprId(1)),
ExprNode::Sin(ExprId(1)),
ExprNode::Cos(ExprId(1)),
ExprNode::Tan(ExprId(1)),
ExprNode::Exp(ExprId(1)),
ExprNode::Ln(ExprId(1)),
ExprNode::Abs(ExprId(1)),
] {
assert!(!node.is_atom());
let kids = node.children();
assert_eq!(kids.len(), 1, "{node:?} should have exactly 1 child");
assert_eq!(kids[0], ExprId(1));
}
}
#[test]
fn apply_children() {
let args: SmallVec<[ExprId; 2]> = smallvec![ExprId(5), ExprId(6)];
let node = ExprNode::Apply(SymbolId(0), args);
assert!(!node.is_atom());
let kids = node.children();
assert_eq!(kids.len(), 2);
assert_eq!(kids[0], ExprId(5));
assert_eq!(kids[1], ExprId(6));
}
#[test]
fn derivative_children() {
let node = ExprNode::Derivative(ExprId(3), ExprId(7));
assert!(!node.is_atom());
let kids = node.children();
assert_eq!(kids.len(), 2);
assert_eq!(kids[0], ExprId(3));
assert_eq!(kids[1], ExprId(7));
}
#[test]
fn integral_children() {
let node = ExprNode::Integral(ExprId(8), ExprId(9));
assert!(!node.is_atom());
let kids = node.children();
assert_eq!(kids.len(), 2);
assert_eq!(kids[0], ExprId(8));
assert_eq!(kids[1], ExprId(9));
}
#[test]
fn named_constants_are_atoms() {
for node in [
ExprNode::EulerGamma,
ExprNode::Catalan,
ExprNode::GoldenRatio,
] {
assert!(node.is_atom(), "{node:?} should be an atom");
assert!(node.children().is_empty());
assert_eq!(node.child_count(), 0);
let mut count = 0;
node.for_each_child(|_| count += 1);
assert_eq!(count, 0);
}
assert_eq!(format!("{:?}", ExprNode::EulerGamma), "EulerGamma");
assert_eq!(format!("{:?}", ExprNode::Catalan), "Catalan");
assert_eq!(format!("{:?}", ExprNode::GoldenRatio), "GoldenRatio");
}
#[test]
fn complex_and_special_unary_children() {
for node in [
ExprNode::Re(ExprId(3)),
ExprNode::Im(ExprId(3)),
ExprNode::Conjugate(ExprId(3)),
ExprNode::Arg(ExprId(3)),
ExprNode::Si(ExprId(3)),
ExprNode::Ci(ExprId(3)),
ExprNode::Ei(ExprId(3)),
ExprNode::Li(ExprId(3)),
ExprNode::Zeta(ExprId(3)),
] {
assert!(!node.is_atom());
assert_eq!(node.child_count(), 1);
let kids = node.children();
assert_eq!(kids.len(), 1, "{node:?} should have exactly 1 child");
assert_eq!(kids[0], ExprId(3));
let mut seen = Vec::new();
node.for_each_child(|c| seen.push(c));
assert_eq!(seen, vec![ExprId(3)]);
}
}
#[test]
fn polygamma_and_kronecker_children() {
for node in [
ExprNode::Polygamma(ExprId(1), ExprId(2)),
ExprNode::KroneckerDelta(ExprId(1), ExprId(2)),
] {
assert!(!node.is_atom());
assert_eq!(node.child_count(), 2);
let kids = node.children();
assert_eq!(kids.len(), 2);
assert_eq!(kids[0], ExprId(1));
assert_eq!(kids[1], ExprId(2));
let mut seen = Vec::new();
node.for_each_child(|c| seen.push(c));
assert_eq!(seen, vec![ExprId(1), ExprId(2)]);
}
assert_eq!(
format!("{:?}", ExprNode::Polygamma(ExprId(1), ExprId(2))),
"Polygamma(e1, e2)"
);
assert_eq!(
format!("{:?}", ExprNode::KroneckerDelta(ExprId(1), ExprId(2))),
"KroneckerDelta(e1, e2)"
);
}
}