use std::fmt;
use smallvec::{SmallVec, smallvec};
use crate::base::arena::Arena;
use crate::base::errors::SymplexError;
use crate::base::libfn::Arity;
use crate::base::node::{ExprId, ExprNode, SymbolId};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum ArgCount {
Exactly(u8),
Between {
min: u8,
max: u8,
},
AtLeast(u8),
}
impl ArgCount {
pub(crate) const fn accepts(self, n: usize) -> bool {
match self {
ArgCount::Exactly(k) => n == k as usize,
ArgCount::Between { min, max } => min as usize <= n && n <= max as usize,
ArgCount::AtLeast(min) => min as usize <= n,
}
}
const fn lib_arity(self) -> Arity {
match self {
ArgCount::Exactly(n) => Arity::Fixed(n),
ArgCount::Between { min, max } => Arity::Range { min, max },
ArgCount::AtLeast(min) => Arity::Range { min, max: u8::MAX },
}
}
}
impl fmt::Display for ArgCount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ArgCount::Exactly(1) => f.write_str("1 argument"),
ArgCount::Exactly(n) => write!(f, "{n} arguments"),
ArgCount::Between { min, max } => write!(f, "{min} to {max} arguments"),
ArgCount::AtLeast(1) => f.write_str("at least 1 argument"),
ArgCount::AtLeast(n) => write!(f, "at least {n} arguments"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum SortSub {
Function(u8),
Rank(u8),
Named,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Tex {
Call(&'static str),
Layout(&'static [TexPiece]),
Postfix(&'static str),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum TexPiece {
Lit(&'static str),
Arg(u8),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Cut {
pub(crate) arg: u8,
pub(crate) line: CutLine,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum CutLine {
NonPositiveReals,
BelowOne,
AtLeastOne,
OutsideUnitInterval,
ImaginaryOutsideUnit,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Conj {
Entire,
OffCut(Cut),
IntegerOrderOffCut {
cut: Cut,
nonnegative: bool,
},
Polynomial {
degree: u8,
},
No,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum RealOn {
Reals,
RealLine,
PositiveReals,
NonNegativeReals,
BelowOne,
UnitInterval {
closed: bool,
},
Polynomial {
degree: u8,
},
BesselOrder,
NonNegativeIntegers,
AtLeastOne,
IntegerValued,
Everywhere,
Custom,
Unclaimed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Parity {
Odd(AutoSign),
Even(AutoSign),
Neither,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum AutoSign {
Canonical,
Coefficient,
Negation,
SymbolFree,
Never,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Constants {
Folded,
Evaluated,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Poles {
None,
NonPositiveIntegers(&'static [u8]),
Zero(u8),
One(u8),
Other,
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "`parse`, `sort`, `tex` and `parity` are read by the tests over `Func::ALL`; the \
printers, parser, sort keys and sign extraction migrate to them later"
)
)]
pub(crate) struct FuncSpec {
pub(crate) name: &'static str,
pub(crate) parse: &'static [&'static str],
pub(crate) arity: ArgCount,
pub(crate) sort: SortSub,
pub(crate) tex: Tex,
pub(crate) conj: Conj,
pub(crate) real_on: RealOn,
pub(crate) parity: Parity,
pub(crate) poles: Poles,
pub(crate) constants: Constants,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Family {
Node,
Lib,
Internal,
}
macro_rules! funcs {
(
node {
$( $(#[$ndoc:meta])* $N:ident => FuncSpec {
name: $nname:literal, $( $nfield:ident : $nvalue:expr ),+ $(,)?
}; )*
}
lib {
$( $(#[$ldoc:meta])* $L:ident => FuncSpec {
name: $lname:literal, $( $lfield:ident : $lvalue:expr ),+ $(,)?
}; )*
}
internal {
$( $(#[$idoc:meta])* $I:ident => FuncSpec {
name: $iname:literal, $( $ifield:ident : $ivalue:expr ),+ $(,)?
}; )*
}
) => {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum LibFn {
$( $(#[$ldoc])* $L, )*
}
impl LibFn {
pub const ALL: &[LibFn] = &[ $( LibFn::$L, )* ];
pub const fn name(self) -> &'static str {
match self { $( LibFn::$L => $lname, )* }
}
pub fn from_name(s: &str) -> Option<LibFn> {
match s { $( $lname => Some(LibFn::$L), )* _ => None }
}
pub const fn arity(self) -> Arity {
self.func().spec().arity.lib_arity()
}
pub(crate) const fn func(self) -> Func {
match self { $( LibFn::$L => Func::$L, )* }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Func {
$( $(#[$ndoc])* $N, )*
$( $(#[$ldoc])* $L, )*
$( $(#[$idoc])* $I, )*
}
impl Func {
#[cfg_attr(
not(test),
expect(dead_code, reason = "iterated by the tests; by the table consumers in W6")
)]
pub(crate) const ALL: &[Func] = &[
$( Func::$N, )* $( Func::$L, )* $( Func::$I, )*
];
const SPECS: &[FuncSpec] = &[
$( FuncSpec { name: $nname, $( $nfield: $nvalue ),+ }, )*
$( FuncSpec { name: $lname, $( $lfield: $lvalue ),+ }, )*
$( FuncSpec { name: $iname, $( $ifield: $ivalue ),+ }, )*
];
pub(crate) const fn spec(self) -> &'static FuncSpec {
&Self::SPECS[self as usize]
}
pub(crate) const fn family(self) -> Family {
match self {
$( Func::$N => Family::Node, )*
$( Func::$L => Family::Lib, )*
$( Func::$I => Family::Internal, )*
}
}
pub(crate) const fn as_lib(self) -> Option<LibFn> {
match self {
$( Func::$N => None, )*
$( Func::$L => Some(LibFn::$L), )*
$( Func::$I => None, )*
}
}
fn internal_named(s: &str) -> Option<Func> {
match s { $( $iname => Some(Func::$I), )* _ => None }
}
}
macro_rules! non_node_funcs {
() => { $( Func::$L )|* | $( Func::$I )|* };
}
};
}
impl From<LibFn> for Func {
fn from(f: LibFn) -> Func {
f.func()
}
}
use ArgCount::{AtLeast, Between, Exactly};
use AutoSign::{Canonical, Coefficient, Negation, SymbolFree};
use Parity::{Even, Neither, Odd};
use SortSub::{Function, Named, Rank};
use TexPiece::{Arg, Lit};
const NEG0: Cut = Cut {
arg: 0,
line: CutLine::NonPositiveReals,
};
const NEG1: Cut = Cut {
arg: 1,
line: CutLine::NonPositiveReals,
};
funcs! {
node {
Min => FuncSpec {
name: "min", parse: &[], arity: AtLeast(1), sort: Rank(130), tex: Tex::Call(r"\min"),
conj: Conj::No, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Max => FuncSpec {
name: "max", parse: &[], arity: AtLeast(1), sort: Rank(132), tex: Tex::Call(r"\max"),
conj: Conj::No, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Floor => FuncSpec {
name: "floor", parse: &[], arity: Exactly(1), sort: Function(20),
tex: Tex::Layout(&[Lit(r"\lfloor "), Arg(0), Lit(r"\rfloor")]),
conj: Conj::No, real_on: RealOn::IntegerValued, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Ceiling => FuncSpec {
name: "ceiling", parse: &["ceil"], arity: Exactly(1), sort: Function(21),
tex: Tex::Layout(&[Lit(r"\lceil "), Arg(0), Lit(r"\rceil")]),
conj: Conj::No, real_on: RealOn::IntegerValued, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Sin => FuncSpec {
name: "sin", parse: &[], arity: Exactly(1), sort: Function(0), tex: Tex::Call(r"\sin"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Odd(Negation), poles: Poles::None,
constants: Constants::Folded,
};
Cos => FuncSpec {
name: "cos", parse: &[], arity: Exactly(1), sort: Function(1), tex: Tex::Call(r"\cos"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Even(Negation), poles: Poles::None,
constants: Constants::Folded,
};
Tan => FuncSpec {
name: "tan", parse: &[], arity: Exactly(1), sort: Function(2), tex: Tex::Call(r"\tan"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Odd(Negation), poles: Poles::Other,
constants: Constants::Folded,
};
Exp => FuncSpec {
name: "exp", parse: &[], arity: Exactly(1), sort: Function(3), tex: Tex::Call(r"\exp"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Ln => FuncSpec {
name: "ln", parse: &["log"], arity: Exactly(1), sort: Function(4), tex: Tex::Call(r"\ln"),
conj: Conj::OffCut(NEG0), real_on: RealOn::PositiveReals, parity: Neither,
poles: Poles::Zero(0),
constants: Constants::Folded,
};
Abs => FuncSpec {
name: "abs", parse: &[], arity: Exactly(1), sort: Function(6),
tex: Tex::Layout(&[Lit(r"\left|"), Arg(0), Lit(r"\right|")]),
conj: Conj::Entire, real_on: RealOn::Everywhere, parity: Even(Canonical),
poles: Poles::None,
constants: Constants::Folded,
};
Asin => FuncSpec {
name: "asin", parse: &["arcsin"], arity: Exactly(1), sort: Function(8),
tex: Tex::Call(r"\arcsin"),
conj: Conj::OffCut(Cut { arg: 0, line: CutLine::OutsideUnitInterval }),
real_on: RealOn::UnitInterval { closed: true }, parity: Odd(SymbolFree),
poles: Poles::None,
constants: Constants::Folded,
};
Acos => FuncSpec {
name: "acos", parse: &["arccos"], arity: Exactly(1), sort: Function(9),
tex: Tex::Call(r"\arccos"),
conj: Conj::OffCut(Cut { arg: 0, line: CutLine::OutsideUnitInterval }),
real_on: RealOn::UnitInterval { closed: true }, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Atan => FuncSpec {
name: "atan", parse: &["arctan"], arity: Exactly(1), sort: Function(10),
tex: Tex::Call(r"\arctan"),
conj: Conj::OffCut(Cut { arg: 0, line: CutLine::ImaginaryOutsideUnit }),
real_on: RealOn::Reals, parity: Odd(SymbolFree), poles: Poles::Other,
constants: Constants::Folded,
};
Atan2 => FuncSpec {
name: "atan2", parse: &[], arity: Exactly(2), sort: Function(19),
tex: Tex::Call(r"\operatorname{atan2}"),
conj: Conj::No, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Sinh => FuncSpec {
name: "sinh", parse: &[], arity: Exactly(1), sort: Function(11), tex: Tex::Call(r"\sinh"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Odd(Negation), poles: Poles::None,
constants: Constants::Folded,
};
Cosh => FuncSpec {
name: "cosh", parse: &[], arity: Exactly(1), sort: Function(12), tex: Tex::Call(r"\cosh"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Even(Negation), poles: Poles::None,
constants: Constants::Folded,
};
Tanh => FuncSpec {
name: "tanh", parse: &[], arity: Exactly(1), sort: Function(13), tex: Tex::Call(r"\tanh"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Odd(Negation), poles: Poles::Other,
constants: Constants::Folded,
};
Asinh => FuncSpec {
name: "asinh", parse: &["arcsinh"], arity: Exactly(1), sort: Function(14),
tex: Tex::Call(r"\operatorname{asinh}"),
conj: Conj::OffCut(Cut { arg: 0, line: CutLine::ImaginaryOutsideUnit }),
real_on: RealOn::Reals, parity: Odd(SymbolFree), poles: Poles::None,
constants: Constants::Folded,
};
Acosh => FuncSpec {
name: "acosh", parse: &["arccosh"], arity: Exactly(1), sort: Function(15),
tex: Tex::Call(r"\operatorname{acosh}"),
conj: Conj::OffCut(Cut { arg: 0, line: CutLine::BelowOne }),
real_on: RealOn::AtLeastOne, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Atanh => FuncSpec {
name: "atanh", parse: &["arctanh"], arity: Exactly(1), sort: Function(16),
tex: Tex::Call(r"\operatorname{atanh}"),
conj: Conj::OffCut(Cut { arg: 0, line: CutLine::OutsideUnitInterval }),
real_on: RealOn::UnitInterval { closed: false }, parity: Odd(SymbolFree),
poles: Poles::Other,
constants: Constants::Folded,
};
Sign => FuncSpec {
name: "sign", parse: &["sgn"], arity: Exactly(1), sort: Function(18),
tex: Tex::Call(r"\operatorname{sgn}"),
conj: Conj::Entire, real_on: RealOn::IntegerValued, parity: Odd(Canonical),
poles: Poles::None,
constants: Constants::Folded,
};
Heaviside => FuncSpec {
name: "H", parse: &["heaviside"], arity: Exactly(1), sort: Function(28),
tex: Tex::Call(r"\operatorname{H}"),
conj: Conj::No, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
DiracDelta => FuncSpec {
name: "DiracDelta", parse: &["dirac_delta"], arity: Exactly(1), sort: Function(29),
tex: Tex::Call(r"\delta"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Even(AutoSign::Never),
poles: Poles::Other,
constants: Constants::Folded,
};
Re => FuncSpec {
name: "re", parse: &[], arity: Exactly(1), sort: Function(31), tex: Tex::Call(r"\Re"),
conj: Conj::Entire, real_on: RealOn::Everywhere, parity: Odd(Canonical),
poles: Poles::None,
constants: Constants::Folded,
};
Im => FuncSpec {
name: "im", parse: &[], arity: Exactly(1), sort: Function(32), tex: Tex::Call(r"\Im"),
conj: Conj::No, real_on: RealOn::Everywhere, parity: Odd(Canonical), poles: Poles::None,
constants: Constants::Folded,
};
Conjugate => FuncSpec {
name: "conjugate", parse: &["conj"], arity: Exactly(1), sort: Function(33),
tex: Tex::Layout(&[Lit(r"\overline{"), Arg(0), Lit("}")]),
conj: Conj::Entire, real_on: RealOn::Custom, parity: Odd(Canonical), poles: Poles::None,
constants: Constants::Folded,
};
Arg => FuncSpec {
name: "arg", parse: &[], arity: Exactly(1), sort: Function(34), tex: Tex::Call(r"\arg"),
conj: Conj::No, real_on: RealOn::Everywhere, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Gamma => FuncSpec {
name: "Gamma", parse: &[], arity: Exactly(1), sort: Function(22), tex: Tex::Call(r"\Gamma"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither,
poles: Poles::NonPositiveIntegers(&[0]),
constants: Constants::Folded,
};
LogGamma => FuncSpec {
name: "LogGamma", parse: &[], arity: Exactly(1), sort: Function(23),
tex: Tex::Call(r"\ln \Gamma"),
conj: Conj::OffCut(NEG0), real_on: RealOn::PositiveReals, parity: Neither,
poles: Poles::NonPositiveIntegers(&[0]),
constants: Constants::Folded,
};
Digamma => FuncSpec {
name: "Digamma", parse: &[], arity: Exactly(1), sort: Function(24), tex: Tex::Call(r"\psi"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither,
poles: Poles::NonPositiveIntegers(&[0]),
constants: Constants::Folded,
};
Erf => FuncSpec {
name: "erf", parse: &[], arity: Exactly(1), sort: Function(25),
tex: Tex::Call(r"\operatorname{erf}"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Odd(SymbolFree), poles: Poles::None,
constants: Constants::Folded,
};
Erfc => FuncSpec {
name: "erfc", parse: &[], arity: Exactly(1), sort: Function(26),
tex: Tex::Call(r"\operatorname{erfc}"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
LambertW0 => FuncSpec {
name: "W", parse: &["lambertw"], arity: Exactly(1), sort: Function(30),
tex: Tex::Call(r"\operatorname{W}"),
conj: Conj::No, real_on: RealOn::NonNegativeReals, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Beta => FuncSpec {
name: "B", parse: &["beta"], arity: Exactly(2), sort: Function(27),
tex: Tex::Call(r"\mathrm{B}"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither,
poles: Poles::NonPositiveIntegers(&[0, 1]),
constants: Constants::Evaluated,
};
Si => FuncSpec {
name: "Si", parse: &[], arity: Exactly(1), sort: Function(35),
tex: Tex::Call(r"\operatorname{Si}"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Odd(Canonical), poles: Poles::None,
constants: Constants::Folded,
};
Ci => FuncSpec {
name: "Ci", parse: &[], arity: Exactly(1), sort: Function(36),
tex: Tex::Call(r"\operatorname{Ci}"),
conj: Conj::OffCut(NEG0), real_on: RealOn::PositiveReals, parity: Neither,
poles: Poles::Zero(0),
constants: Constants::Folded,
};
Ei => FuncSpec {
name: "Ei", parse: &[], arity: Exactly(1), sort: Function(37),
tex: Tex::Call(r"\operatorname{Ei}"),
conj: Conj::OffCut(NEG0), real_on: RealOn::Reals, parity: Neither, poles: Poles::Zero(0),
constants: Constants::Folded,
};
Li => FuncSpec {
name: "li", parse: &[], arity: Exactly(1), sort: Function(38),
tex: Tex::Call(r"\operatorname{li}"),
conj: Conj::OffCut(Cut { arg: 0, line: CutLine::BelowOne }),
real_on: RealOn::PositiveReals, parity: Neither, poles: Poles::One(0),
constants: Constants::Folded,
};
Zeta => FuncSpec {
name: "zeta", parse: &[], arity: Exactly(1), sort: Function(39), tex: Tex::Call(r"\zeta"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither, poles: Poles::One(0),
constants: Constants::Folded,
};
Polygamma => FuncSpec {
name: "polygamma", parse: &[], arity: Exactly(2), sort: Function(40),
tex: Tex::Layout(&[Lit(r"\psi^{("), Arg(0), Lit(r")}\left("), Arg(1), Lit(r"\right)")]),
conj: Conj::IntegerOrderOffCut { cut: NEG1, nonnegative: true },
real_on: RealOn::PositiveReals, parity: Neither,
poles: Poles::NonPositiveIntegers(&[1]),
constants: Constants::Folded,
};
KroneckerDelta => FuncSpec {
name: "KroneckerDelta", parse: &["kronecker_delta"], arity: Exactly(2),
sort: Function(41),
tex: Tex::Layout(&[Lit(r"\delta_{"), Arg(0), Lit(" "), Arg(1), Lit("}")]),
conj: Conj::Entire, real_on: RealOn::Custom, parity: Neither, poles: Poles::None,
constants: Constants::Folded,
};
Factorial => FuncSpec {
name: "factorial", parse: &[], arity: Exactly(1), sort: Function(7),
tex: Tex::Postfix("!"),
conj: Conj::Entire, real_on: RealOn::NonNegativeReals, parity: Neither,
poles: Poles::Other,
constants: Constants::Folded,
};
Binomial => FuncSpec {
name: "C", parse: &["binomial"], arity: Exactly(2), sort: Function(8),
tex: Tex::Layout(&[Lit(r"\binom{"), Arg(0), Lit("}{"), Arg(1), Lit("}")]),
conj: Conj::Entire, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::Other,
constants: Constants::Folded,
};
}
lib {
Factorial2 => FuncSpec {
name: "factorial2", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("factorial2"),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::Other,
constants: Constants::Evaluated,
};
Subfactorial => FuncSpec {
name: "subfactorial", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("subfactorial"),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::Other,
constants: Constants::Evaluated,
};
RisingFactorial => FuncSpec {
name: "rising_factorial", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call("rising_factorial"),
conj: Conj::Entire, real_on: RealOn::Polynomial { degree: 1 }, parity: Neither,
poles: Poles::Other,
constants: Constants::Evaluated,
};
FallingFactorial => FuncSpec {
name: "falling_factorial", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call("falling_factorial"),
conj: Conj::Entire, real_on: RealOn::Polynomial { degree: 1 }, parity: Neither,
poles: Poles::Other,
constants: Constants::Evaluated,
};
Fibonacci => FuncSpec {
name: "fibonacci", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("fibonacci"),
conj: Conj::No, real_on: RealOn::RealLine, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
Lucas => FuncSpec {
name: "lucas", parse: &[], arity: Exactly(1), sort: Named, tex: Tex::Call("lucas"),
conj: Conj::No, real_on: RealOn::RealLine, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
Bernoulli => FuncSpec {
name: "bernoulli", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("bernoulli"),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::None,
constants: Constants::Evaluated,
};
Harmonic => FuncSpec {
name: "harmonic", parse: &[], arity: Exactly(1), sort: Named, tex: Tex::Call("harmonic"),
conj: Conj::No, real_on: RealOn::NonNegativeReals, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
Catalan => FuncSpec {
name: "catalan", parse: &[], arity: Exactly(1), sort: Named, tex: Tex::Call("catalan"),
conj: Conj::No, real_on: RealOn::NonNegativeReals, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
Bell => FuncSpec {
name: "bell", parse: &[], arity: Exactly(1), sort: Named, tex: Tex::Call("bell"),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::None,
constants: Constants::Evaluated,
};
EulerNumber => FuncSpec {
name: "euler_number", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("euler_number"),
conj: Conj::No, real_on: RealOn::RealLine, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
Stirling1 => FuncSpec {
name: "stirling1", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call("stirling1"),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::None,
constants: Constants::Evaluated,
};
Stirling2 => FuncSpec {
name: "stirling2", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call("stirling2"),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::None,
constants: Constants::Evaluated,
};
PartitionCount => FuncSpec {
name: "partition_count", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("partition_count"),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::None,
constants: Constants::Evaluated,
};
LambertW => FuncSpec {
name: "lambertw", parse: &["w"], arity: Between { min: 1, max: 2 }, sort: Named,
tex: Tex::Layout(&[
Lit(r"\operatorname{W}_{"), Arg(1), Lit(r"}\left("), Arg(0), Lit(r"\right)"),
]),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
BesselJ => FuncSpec {
name: "besselj", parse: &[], arity: Exactly(2), sort: Named, tex: Tex::Call("besselj"),
conj: Conj::IntegerOrderOffCut { cut: NEG1, nonnegative: false },
real_on: RealOn::BesselOrder, parity: Neither, poles: Poles::Zero(1),
constants: Constants::Evaluated,
};
BesselY => FuncSpec {
name: "bessely", parse: &[], arity: Exactly(2), sort: Named, tex: Tex::Call("bessely"),
conj: Conj::OffCut(NEG1), real_on: RealOn::PositiveReals, parity: Neither,
poles: Poles::Zero(1),
constants: Constants::Evaluated,
};
BesselI => FuncSpec {
name: "besseli", parse: &[], arity: Exactly(2), sort: Named, tex: Tex::Call("besseli"),
conj: Conj::IntegerOrderOffCut { cut: NEG1, nonnegative: false },
real_on: RealOn::BesselOrder, parity: Neither, poles: Poles::Zero(1),
constants: Constants::Evaluated,
};
BesselK => FuncSpec {
name: "besselk", parse: &[], arity: Exactly(2), sort: Named, tex: Tex::Call("besselk"),
conj: Conj::OffCut(NEG1), real_on: RealOn::PositiveReals, parity: Neither,
poles: Poles::Zero(1),
constants: Constants::Evaluated,
};
Legendre => FuncSpec {
name: "legendre", parse: &[], arity: Exactly(2), sort: Named, tex: Tex::Call("legendre"),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
ChebyshevT => FuncSpec {
name: "chebyshev_t", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call("chebyshev_t"),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
ChebyshevU => FuncSpec {
name: "chebyshev_u", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call("chebyshev_u"),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
Hermite => FuncSpec {
name: "hermite", parse: &[], arity: Exactly(2), sort: Named, tex: Tex::Call("hermite"),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
Laguerre => FuncSpec {
name: "laguerre", parse: &[], arity: Exactly(2), sort: Named, tex: Tex::Call("laguerre"),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
Erfi => FuncSpec {
name: "erfi", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{erfi}"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Odd(Coefficient),
poles: Poles::None,
constants: Constants::Evaluated,
};
ErfInv => FuncSpec {
name: "erfinv", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{erf}^{-1}"),
conj: Conj::No, real_on: RealOn::UnitInterval { closed: false },
parity: Odd(Coefficient), poles: Poles::Other,
constants: Constants::Evaluated,
};
ErfcInv => FuncSpec {
name: "erfcinv", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{erfc}^{-1}"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
ExpInt => FuncSpec {
name: "expint", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[
Lit(r"\operatorname{E}_{"), Arg(0), Lit(r"}\left("), Arg(1), Lit(r"\right)"),
]),
conj: Conj::OffCut(NEG1), real_on: RealOn::PositiveReals, parity: Neither,
poles: Poles::Zero(1),
constants: Constants::Evaluated,
};
Shi => FuncSpec {
name: "Shi", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{Shi}"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Odd(Coefficient),
poles: Poles::None,
constants: Constants::Evaluated,
};
Chi => FuncSpec {
name: "Chi", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{Chi}"),
conj: Conj::OffCut(NEG0), real_on: RealOn::PositiveReals, parity: Neither,
poles: Poles::Zero(0),
constants: Constants::Evaluated,
};
FresnelS => FuncSpec {
name: "fresnels", parse: &[], arity: Exactly(1), sort: Named, tex: Tex::Call("S"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Odd(Coefficient),
poles: Poles::None,
constants: Constants::Evaluated,
};
FresnelC => FuncSpec {
name: "fresnelc", parse: &[], arity: Exactly(1), sort: Named, tex: Tex::Call("C"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Odd(Coefficient),
poles: Poles::None,
constants: Constants::Evaluated,
};
LowerGamma => FuncSpec {
name: "lowergamma", parse: &[], arity: Exactly(2), sort: Named, tex: Tex::Call(r"\gamma"),
conj: Conj::OffCut(NEG1), real_on: RealOn::PositiveReals, parity: Neither,
poles: Poles::NonPositiveIntegers(&[0]),
constants: Constants::Evaluated,
};
UpperGamma => FuncSpec {
name: "uppergamma", parse: &[], arity: Exactly(2), sort: Named, tex: Tex::Call(r"\Gamma"),
conj: Conj::OffCut(NEG1), real_on: RealOn::PositiveReals, parity: Neither,
poles: Poles::Zero(1),
constants: Constants::Evaluated,
};
PolyLog => FuncSpec {
name: "polylog", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[
Lit(r"\operatorname{Li}_{"), Arg(0), Lit(r"}\left("), Arg(1), Lit(r"\right)"),
]),
conj: Conj::OffCut(Cut { arg: 1, line: CutLine::AtLeastOne }),
real_on: RealOn::BelowOne, parity: Neither, poles: Poles::One(1),
constants: Constants::Evaluated,
};
DirichletEta => FuncSpec {
name: "dirichlet_eta", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\eta"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
AiryAi => FuncSpec {
name: "airyai", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{Ai}"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
AiryBi => FuncSpec {
name: "airybi", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{Bi}"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
AiryAiPrime => FuncSpec {
name: "airyaiprime", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{Ai}^\prime"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
AiryBiPrime => FuncSpec {
name: "airybiprime", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{Bi}^\prime"),
conj: Conj::Entire, real_on: RealOn::Reals, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
EllipticK => FuncSpec {
name: "elliptic_k", parse: &[], arity: Exactly(1), sort: Named, tex: Tex::Call("K"),
conj: Conj::OffCut(Cut { arg: 0, line: CutLine::AtLeastOne }),
real_on: RealOn::BelowOne, parity: Neither, poles: Poles::One(0),
constants: Constants::Evaluated,
};
EllipticE => FuncSpec {
name: "elliptic_e", parse: &[], arity: Exactly(1), sort: Named, tex: Tex::Call("E"),
conj: Conj::OffCut(Cut { arg: 0, line: CutLine::AtLeastOne }),
real_on: RealOn::BelowOne, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
};
EllipticF => FuncSpec {
name: "elliptic_f", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit(r"F\left("), Arg(0), Lit(r"\middle| "), Arg(1), Lit(r"\right)")]),
conj: Conj::No, real_on: RealOn::BelowOne, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
EllipticPi => FuncSpec {
name: "elliptic_pi", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit(r"\Pi\left("), Arg(0), Lit(r"\middle| "), Arg(1), Lit(r"\right)")]),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
Gegenbauer => FuncSpec {
name: "gegenbauer", parse: &[], arity: Exactly(3), sort: Named,
tex: Tex::Layout(&[
Lit("C_{"), Arg(0), Lit(r"}^{\left("), Arg(1), Lit(r"\right)}\left("), Arg(2),
Lit(r"\right)"),
]),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
Jacobi => FuncSpec {
name: "jacobi", parse: &[], arity: Exactly(4), sort: Named,
tex: Tex::Layout(&[
Lit("P_{"), Arg(0), Lit(r"}^{\left("), Arg(1), Lit(","), Arg(2),
Lit(r"\right)}\left("), Arg(3), Lit(r"\right)"),
]),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
AssocLegendre => FuncSpec {
name: "assoc_legendre", parse: &[], arity: Exactly(3), sort: Named,
tex: Tex::Layout(&[
Lit("P_{"), Arg(0), Lit(r"}^{\left("), Arg(1), Lit(r"\right)}\left("), Arg(2),
Lit(r"\right)"),
]),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
AssocLaguerre => FuncSpec {
name: "assoc_laguerre", parse: &[], arity: Exactly(3), sort: Named,
tex: Tex::Layout(&[
Lit("L_{"), Arg(0), Lit(r"}^{\left("), Arg(1), Lit(r"\right)}\left("), Arg(2),
Lit(r"\right)"),
]),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
BetaInc => FuncSpec {
name: "betainc", parse: &[], arity: Exactly(4), sort: Named,
tex: Tex::Layout(&[
Lit(r"\operatorname{B}_{("), Arg(2), Lit(", "), Arg(3), Lit(r")}\left("), Arg(0),
Lit(", "), Arg(1), Lit(r"\right)"),
]),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
BetaIncRegularized => FuncSpec {
name: "betainc_regularized", parse: &[], arity: Exactly(4), sort: Named,
tex: Tex::Layout(&[
Lit(r"\operatorname{I}_{("), Arg(2), Lit(", "), Arg(3), Lit(r")}\left("), Arg(0),
Lit(", "), Arg(1), Lit(r"\right)"),
]),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
}
internal {
ErfcxAsym => FuncSpec {
name: "__erfcx_asym", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("__erfcx_asym"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
EiAsym => FuncSpec {
name: "__ei_asym", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("__ei_asym"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
Erfix2Asym => FuncSpec {
name: "__erfix2_asym", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("__erfix2_asym"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
Erfcx2Asym => FuncSpec {
name: "__erfcx2_asym", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("__erfcx2_asym"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
BesselIAsym => FuncSpec {
name: "__besseli_asym", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call("__besseli_asym"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
BesselKAsym => FuncSpec {
name: "__besselk_asym", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call("__besselk_asym"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
UpperGammaAsym => FuncSpec {
name: "__uppergamma_asym", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call("__uppergamma_asym"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
ZetaAsym => FuncSpec {
name: "__zeta_asym", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("__zeta_asym"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
};
}
}
impl ExprNode {
pub(crate) fn func(&self, arena: &Arena) -> Option<(Func, SmallVec<[ExprId; 4]>)> {
let one = |f: Func, x: &ExprId| Some((f, smallvec![*x]));
let two = |f: Func, a: &ExprId, b: &ExprId| Some((f, smallvec![*a, *b]));
match self {
ExprNode::Min(xs) => Some((Func::Min, xs.clone())),
ExprNode::Max(xs) => Some((Func::Max, xs.clone())),
ExprNode::Floor(x) => one(Func::Floor, x),
ExprNode::Ceiling(x) => one(Func::Ceiling, x),
ExprNode::Sin(x) => one(Func::Sin, x),
ExprNode::Cos(x) => one(Func::Cos, x),
ExprNode::Tan(x) => one(Func::Tan, x),
ExprNode::Exp(x) => one(Func::Exp, x),
ExprNode::Ln(x) => one(Func::Ln, x),
ExprNode::Abs(x) => one(Func::Abs, x),
ExprNode::Asin(x) => one(Func::Asin, x),
ExprNode::Acos(x) => one(Func::Acos, x),
ExprNode::Atan(x) => one(Func::Atan, x),
ExprNode::Atan2(y, x) => two(Func::Atan2, y, x),
ExprNode::Sinh(x) => one(Func::Sinh, x),
ExprNode::Cosh(x) => one(Func::Cosh, x),
ExprNode::Tanh(x) => one(Func::Tanh, x),
ExprNode::Asinh(x) => one(Func::Asinh, x),
ExprNode::Acosh(x) => one(Func::Acosh, x),
ExprNode::Atanh(x) => one(Func::Atanh, x),
ExprNode::Sign(x) => one(Func::Sign, x),
ExprNode::Heaviside(x) => one(Func::Heaviside, x),
ExprNode::DiracDelta(x) => one(Func::DiracDelta, x),
ExprNode::Re(x) => one(Func::Re, x),
ExprNode::Im(x) => one(Func::Im, x),
ExprNode::Conjugate(x) => one(Func::Conjugate, x),
ExprNode::Arg(x) => one(Func::Arg, x),
ExprNode::Gamma(x) => one(Func::Gamma, x),
ExprNode::LogGamma(x) => one(Func::LogGamma, x),
ExprNode::Digamma(x) => one(Func::Digamma, x),
ExprNode::Erf(x) => one(Func::Erf, x),
ExprNode::Erfc(x) => one(Func::Erfc, x),
ExprNode::LambertW(x) => one(Func::LambertW0, x),
ExprNode::Beta(a, b) => two(Func::Beta, a, b),
ExprNode::Si(x) => one(Func::Si, x),
ExprNode::Ci(x) => one(Func::Ci, x),
ExprNode::Ei(x) => one(Func::Ei, x),
ExprNode::Li(x) => one(Func::Li, x),
ExprNode::Zeta(x) => one(Func::Zeta, x),
ExprNode::Polygamma(n, x) => two(Func::Polygamma, n, x),
ExprNode::KroneckerDelta(i, j) => two(Func::KroneckerDelta, i, j),
ExprNode::Factorial(x) => one(Func::Factorial, x),
ExprNode::Binomial(n, k) => two(Func::Binomial, n, k),
ExprNode::Apply(head, args) => {
let f = Func::of_head(arena, *head)?;
f.spec()
.arity
.accepts(args.len())
.then(|| (f, args.iter().copied().collect()))
}
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::Add(_)
| ExprNode::Mul(_)
| ExprNode::Pow(..)
| ExprNode::Neg(_)
| ExprNode::BoolTrue
| ExprNode::BoolFalse
| ExprNode::Gt(..)
| ExprNode::Ge(..)
| ExprNode::Eq_(..)
| ExprNode::Ne(..)
| ExprNode::And(_)
| ExprNode::Or(_)
| ExprNode::Not(_)
| ExprNode::Piecewise(_)
| ExprNode::Derivative(..)
| ExprNode::Integral(..)
| ExprNode::DefiniteIntegral(..)
| 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::Interval(..)
| ExprNode::FiniteSet(_)
| ExprNode::SetUnion(_)
| ExprNode::SetIntersection(_)
| ExprNode::SetComplement(..)
| ExprNode::Subs(..)
| ExprNode::ImageSet(..) => None,
}
}
}
impl Func {
pub(crate) fn is_internal_name(name: &str) -> bool {
Func::internal_named(name).is_some()
}
fn of_head(arena: &Arena, head: SymbolId) -> Option<Func> {
let name = arena.symbol_name(head);
match LibFn::from_name(name) {
Some(lib) => Some(Func::from(lib)),
None => Func::internal_named(name),
}
}
}
fn arity_error(f: Func, n: usize) -> SymplexError {
let spec = f.spec();
SymplexError::invalid_argument(
"Arena::func",
format!("{} takes {}, got {n}", spec.name, spec.arity),
)
}
fn fixed<const N: usize>(f: Func, args: &[ExprId]) -> Result<[ExprId; N], SymplexError> {
<[ExprId; N]>::try_from(args).map_err(|_| arity_error(f, args.len()))
}
impl Arena {
pub(crate) fn func(&mut self, f: Func, args: &[ExprId]) -> Result<ExprId, SymplexError> {
if !f.spec().arity.accepts(args.len()) {
return Err(arity_error(f, args.len()));
}
match f.family() {
Family::Node => self.node_func(f, args),
Family::Lib => {
let Some(lib) = f.as_lib() else {
return Err(arity_error(f, args.len()));
};
Ok(match (lib, args) {
(LibFn::LambertW, [x]) => self.lambertw(*x),
(LibFn::LambertW, [x, k]) => self.lambertw_branch(*x, *k),
_ => self.lib_apply(lib, args),
})
}
Family::Internal => {
let head = self.symbols.intern(f.spec().name);
Ok(self.intern(ExprNode::Apply(head, args.iter().copied().collect())))
}
}
}
fn node_func(&mut self, f: Func, args: &[ExprId]) -> Result<ExprId, SymplexError> {
let one = |args: &[ExprId]| fixed::<1>(f, args).map(|[x]| x);
Ok(match f {
Func::Min => self.intern(ExprNode::Min(args.iter().copied().collect())),
Func::Max => self.intern(ExprNode::Max(args.iter().copied().collect())),
Func::Floor => self.floor(one(args)?),
Func::Ceiling => self.ceiling(one(args)?),
Func::Sin => self.sin(one(args)?),
Func::Cos => self.cos(one(args)?),
Func::Tan => self.tan(one(args)?),
Func::Exp => self.exp(one(args)?),
Func::Ln => self.ln(one(args)?),
Func::Abs => self.abs(one(args)?),
Func::Asin => self.asin(one(args)?),
Func::Acos => self.acos(one(args)?),
Func::Atan => self.atan(one(args)?),
Func::Atan2 => {
let [y, x] = fixed(f, args)?;
self.atan2(y, x)
}
Func::Sinh => self.sinh(one(args)?),
Func::Cosh => self.cosh(one(args)?),
Func::Tanh => self.tanh(one(args)?),
Func::Asinh => self.asinh(one(args)?),
Func::Acosh => self.acosh(one(args)?),
Func::Atanh => self.atanh(one(args)?),
Func::Sign => self.sign(one(args)?),
Func::Heaviside => self.heaviside(one(args)?),
Func::DiracDelta => self.dirac_delta(one(args)?),
Func::Re => self.re(one(args)?),
Func::Im => self.im(one(args)?),
Func::Conjugate => self.conjugate(one(args)?),
Func::Arg => self.arg(one(args)?),
Func::Gamma => self.gamma(one(args)?),
Func::LogGamma => self.log_gamma(one(args)?),
Func::Digamma => self.digamma(one(args)?),
Func::Erf => self.erf(one(args)?),
Func::Erfc => self.erfc(one(args)?),
Func::LambertW0 => self.lambertw(one(args)?),
Func::Beta => {
let [a, b] = fixed(f, args)?;
self.beta(a, b)
}
Func::Si => self.si(one(args)?),
Func::Ci => self.ci(one(args)?),
Func::Ei => self.ei(one(args)?),
Func::Li => self.li(one(args)?),
Func::Zeta => self.zeta(one(args)?),
Func::Polygamma => {
let [n, x] = fixed(f, args)?;
self.polygamma(n, x)
}
Func::KroneckerDelta => {
let [i, j] = fixed(f, args)?;
self.kronecker_delta(i, j)
}
Func::Factorial => self.factorial(one(args)?),
Func::Binomial => {
let [n, k] = fixed(f, args)?;
self.binomial(n, k)
}
non_node_funcs!() => {
return Err(SymplexError::computation_failed(
"Arena::func",
format!("{} is not a node-backed function", f.spec().name),
));
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::context::Context;
use crate::api::expr::Ex;
use crate::base::assumptions::{Assumption, Props};
const SLOTS: [&str; 4] = ["t", "x", "y", "z"];
fn sample_count(f: Func) -> usize {
match f.spec().arity {
ArgCount::Exactly(n) => usize::from(n),
ArgCount::Between { max, .. } => usize::from(max),
ArgCount::AtLeast(n) => usize::from(n).max(2),
}
}
fn built(f: Func, n: usize) -> (Context, Vec<Ex>, Ex) {
let ctx = Context::new();
let syms: Vec<Ex> = SLOTS[..n].iter().map(|s| ctx.symbol(s)).collect();
let ids: Vec<ExprId> = syms.iter().map(Ex::raw_id).collect();
let id = ctx
.with_arena_mut(|a| a.func(f, &ids))
.unwrap_or_else(|e| panic!("{f:?}: {e}"));
let e = syms[0].wrap(id);
(ctx, syms, e)
}
fn render_tex(tex: Tex, args: &[String], atom: bool) -> String {
match tex {
Tex::Call(head) => format!(r"{head}\left({}\right)", args.join(", ")),
Tex::Layout(pieces) => pieces
.iter()
.map(|p| match *p {
TexPiece::Lit(s) => s.to_owned(),
TexPiece::Arg(i) => args[usize::from(i)].clone(),
})
.collect(),
Tex::Postfix(op) if atom => format!("{}{op}", args[0]),
Tex::Postfix(op) => format!(r"\left({}\right){op}", args[0]),
}
}
#[test]
fn the_list_has_every_function_once_in_discriminant_order() {
let count = |fam: Family| Func::ALL.iter().filter(|f| f.family() == fam).count();
assert_eq!(
(
count(Family::Node),
count(Family::Lib),
count(Family::Internal)
),
(43, 50, 8)
);
assert_eq!(Func::ALL.len(), Func::SPECS.len());
for (i, f) in Func::ALL.iter().enumerate() {
assert_eq!(*f as usize, i, "{f:?}");
}
let mut names: Vec<&str> = Func::ALL.iter().map(|f| f.spec().name).collect();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), Func::ALL.len(), "two entries with one name");
}
#[test]
fn lib_fn_is_the_library_part_of_func() {
let libs: Vec<Func> = Func::ALL
.iter()
.copied()
.filter(|f| f.family() == Family::Lib)
.collect();
assert_eq!(libs.len(), LibFn::ALL.len());
for (lib, f) in LibFn::ALL.iter().zip(&libs) {
assert_eq!(Func::from(*lib), *f);
assert_eq!(f.as_lib(), Some(*lib));
assert_eq!(lib.name(), f.spec().name);
assert_eq!(LibFn::from_name(lib.name()), Some(*lib));
assert!(!matches!(f.spec().arity, ArgCount::AtLeast(_)), "{lib:?}");
for n in 0..6 {
assert_eq!(
lib.arity().accepts(n),
f.spec().arity.accepts(n),
"{lib:?} {n}"
);
}
}
for f in Func::ALL.iter().filter(|f| f.family() != Family::Lib) {
assert_eq!(f.as_lib(), None);
assert_eq!(LibFn::from_name(f.spec().name), None, "{f:?}");
}
}
#[test]
fn sort_keys_follow_the_spec() {
for &f in Func::ALL {
let (ctx, syms, e) = built(f, sample_count(f));
let mut expected: Vec<u8> = match f.spec().sort {
SortSub::Function(b) => vec![100, b],
SortSub::Rank(r) => vec![r],
SortSub::Named => {
let mut k = vec![100, 17];
k.extend_from_slice(f.spec().name.as_bytes());
k.push(0);
k
}
};
ctx.with_arena_mut(|a| {
for s in &syms {
expected.extend_from_slice(a.sort_key(s.raw_id()).as_bytes());
}
assert_eq!(a.sort_key(e.raw_id()).as_bytes(), &expected[..], "{f:?}");
});
}
}
#[test]
fn sort_bytes_are_unique_but_for_binomial_and_asin() {
let mut seen: Vec<(u8, Func)> = Vec::new();
for &f in Func::ALL {
if let SortSub::Function(b) = f.spec().sort {
for &(c, g) in &seen {
if c == b {
assert_eq!((g, f), (Func::Asin, Func::Binomial), "byte {b}");
}
}
seen.push((b, f));
}
}
assert_ne!(seen.len(), 0);
}
#[test]
fn display_uses_the_spec_name() {
for &f in Func::ALL {
let n = sample_count(f);
let (_ctx, _syms, e) = built(f, n);
let expected = match f {
Func::Factorial => "t!".to_owned(),
Func::LambertW => "W(t, x)".to_owned(),
_ => format!("{}({})", f.spec().name, SLOTS[..n].join(", ")),
};
assert_eq!(e.to_string(), expected, "{f:?}");
}
}
#[test]
fn parse_of_display_and_of_every_name_builds_the_same_node() {
for &f in Func::ALL {
let n = sample_count(f);
let (ctx, _syms, e) = built(f, n);
if f.family() == Family::Internal {
assert!(Context::new().parse(&e.to_string()).is_err(), "{f:?}");
continue;
}
let back = ctx
.parse(&e.to_string())
.unwrap_or_else(|err| panic!("{f:?}: {err}"));
assert_eq!(back.raw_id(), e.raw_id(), "{f:?}: {e}");
let spec = f.spec();
let mut names: Vec<String> = spec.parse.iter().map(|s| (*s).to_owned()).collect();
names.push(spec.name.to_owned());
if spec.name != "H" {
names.push(spec.name.to_ascii_lowercase());
}
for name in names {
let src = format!("{name}({})", SLOTS[..n].join(", "));
let back = ctx.parse(&src).unwrap_or_else(|err| panic!("{src}: {err}"));
assert_eq!(back.raw_id(), e.raw_id(), "{src}");
}
}
}
#[test]
fn latex_is_the_spec_layout() {
for &f in Func::ALL {
let n = sample_count(f);
let (_ctx, syms, e) = built(f, n);
let args: Vec<String> = syms.iter().map(Ex::to_latex).collect();
assert_eq!(e.to_latex(), render_tex(f.spec().tex, &args, true), "{f:?}");
}
let ctx = Context::new();
let x = ctx.symbol("x");
let x1 = &x + 1;
let id = ctx.with_arena_mut(|a| a.func(Func::Factorial, &[x1.raw_id()]));
let fact = x.wrap(id.unwrap_or_else(|e| panic!("{e}")));
assert_eq!(
fact.to_latex(),
render_tex(Func::Factorial.spec().tex, &[x1.to_latex()], false)
);
}
#[test]
fn arity_errors_for_wrong_argument_counts() {
for &f in Func::ALL {
let ctx = Context::new();
let syms: Vec<ExprId> = ["t", "x", "y", "z", "u", "v"]
.iter()
.map(|s| ctx.symbol(s).raw_id())
.collect();
for n in 0..=syms.len() {
let r = ctx.with_arena_mut(|a| a.func(f, &syms[..n]));
match r {
Ok(_) => assert!(f.spec().arity.accepts(n), "{f:?} accepted {n}"),
Err(SymplexError::InvalidArgument { reason, .. }) => {
assert!(!f.spec().arity.accepts(n), "{f:?} refused {n}: {reason}");
assert!(reason.starts_with(f.spec().name), "{reason}");
}
Err(e) => panic!("{f:?} {n}: {e}"),
}
}
}
}
#[test]
fn view_inverts_the_constructor() {
for &f in Func::ALL {
let n = sample_count(f);
let (ctx, syms, e) = built(f, n);
let ids: SmallVec<[ExprId; 4]> = syms.iter().map(Ex::raw_id).collect();
ctx.with_arena_mut(|a| {
let node = a.node(e.raw_id()).clone();
assert_eq!(node.func(a), Some((f, ids.clone())), "{f:?}");
});
}
}
fn build_f(ctx: &Context, f: Func, args: &[Ex]) -> Option<Ex> {
let ids: Vec<ExprId> = args.iter().map(Ex::raw_id).collect();
let id = ctx
.with_arena_mut(|a| a.func(f, &ids))
.unwrap_or_else(|e| panic!("{f:?}: {e}"));
let e = ctx.zero().wrap(id);
let same = ctx.with_arena_mut(|a| {
let node = a.node(id).clone();
node.func(a).is_some_and(|(g, _)| g == f)
});
same.then_some(e)
}
fn declared(ctx: &Context, name: &str, facts: &[Assumption]) -> Ex {
ctx.symbol_with(name, facts)
.unwrap_or_else(|e| panic!("{name}: {e}"))
}
#[test]
fn parity_is_todays_sign_extraction() {
for &f in Func::ALL {
if f.spec().arity != ArgCount::Exactly(1) {
assert_eq!(f.spec().parity, Parity::Neither, "{f:?}");
continue;
}
let ctx = Context::new();
let z = ctx.symbol("z");
let two = ctx.int(2);
let two_z = &two * &z;
let one = |arg: &Ex| {
let id = ctx
.with_arena_mut(|a| a.func(f, &[arg.raw_id()]))
.unwrap_or_else(|e| panic!("{f:?}: {e}"));
z.wrap(id)
};
let (f_z, f_neg_z) = (one(&z), one(&-&z));
let (f_2z, f_neg_2z) = (one(&two_z), one(&-&two_z));
let (f_2, f_neg_2) = (one(&two), one(&-&two));
let (odd, sign) = match f.spec().parity {
Parity::Odd(s) => (true, s),
Parity::Even(s) => (false, s),
Parity::Neither => {
assert_eq!(f_neg_z.eval(), f_neg_z, "{f:?}");
continue;
}
};
let signed = |e: &Ex| if odd { -e } else { e.clone() };
match sign {
AutoSign::Canonical => assert_eq!(f_neg_z, signed(&f_z), "{f:?}"),
AutoSign::Coefficient => {
assert_ne!(f_neg_z, signed(&f_z), "{f:?} at construction");
assert_eq!(f_neg_2z.eval(), signed(&f_2z), "{f:?}");
}
AutoSign::Negation => {
assert_eq!(f_neg_z.eval(), signed(&f_z), "{f:?}");
assert_eq!(f_neg_2z.eval(), f_neg_2z, "{f:?}");
assert_eq!(f_neg_2.eval(), signed(&f_2), "{f:?}");
}
AutoSign::SymbolFree => {
assert_eq!(f_neg_z.eval(), f_neg_z, "{f:?}");
assert_eq!(f_neg_2.eval(), signed(&f_2.eval()), "{f:?}");
}
AutoSign::Never => assert_eq!(f_neg_z.eval(), f_neg_z, "{f:?}"),
}
}
}
#[test]
fn real_on_is_what_the_assumption_system_claims() {
use Assumption::{Integer, Negative, NonNegative, Positive, Real};
for &f in Func::ALL {
let real_on = f.spec().real_on;
if real_on == RealOn::Custom {
continue;
}
let n = sample_count(f);
let ctx = Context::new();
let mut args: Vec<Ex> = (0..n)
.map(|i| declared(&ctx, &format!("r{i}"), &[Real]))
.collect();
let last = n - 1;
let x = declared(&ctx, "x", &[Real]);
match real_on {
RealOn::PositiveReals => args[last] = declared(&ctx, "p", &[Positive]),
RealOn::NonNegativeReals => args[last] = declared(&ctx, "u", &[Real, NonNegative]),
RealOn::BelowOne => args[last] = declared(&ctx, "q", &[Negative]),
RealOn::UnitInterval { closed: true } => args[last] = x.sin(),
RealOn::UnitInterval { closed: false } => args[last] = x.tanh(),
RealOn::AtLeastOne => args[last] = x.cosh(),
RealOn::Polynomial { degree } => {
args[usize::from(degree)] = declared(&ctx, "k", &[Integer, NonNegative]);
}
RealOn::BesselOrder => args[0] = declared(&ctx, "m", &[Integer]),
RealOn::NonNegativeIntegers => {
for (i, a) in args.iter_mut().enumerate() {
*a = declared(&ctx, &format!("k{i}"), &[Integer, NonNegative]);
}
}
_ => {}
}
if let Some(e) = build_f(&ctx, f, &args) {
let real = ctx.query(&e, Props::REAL);
if real_on == RealOn::Unclaimed {
assert_ne!(real, Some(true), "{f:?}: {e}");
} else {
assert_eq!(real, Some(true), "{f:?}: {e}");
}
if real_on == RealOn::IntegerValued {
assert_eq!(ctx.query(&e, Props::INTEGER), Some(true), "{f:?}: {e}");
}
}
let plain: Vec<Ex> = (0..n).map(|i| ctx.symbol(&format!("c{i}"))).collect();
if real_on != RealOn::Everywhere
&& let Some(e) = build_f(&ctx, f, &plain)
{
assert_ne!(ctx.query(&e, Props::REAL), Some(true), "{f:?}: {e}");
}
}
}
#[test]
fn constants_facet_decides_claims_at_constant_arguments() {
use crate::base::assumptions::AssumptionCache;
let ctx = Context::new();
ctx.symbol_with("x", &[Assumption::Real])
.unwrap_or_else(|e| panic!("{e}"));
for (src, want) in [
("beta(1/2, -1/2)", None),
("subfactorial(1)", None),
("chebyshev_t(3, 0)", None),
("erfi(2)", None),
("beta(x, -1/2)", Some(true)),
("erfi(x)", Some(true)),
("Gamma(1/3)", Some(true)),
("lambertw(2)", Some(true)),
("factorial(5/2)", Some(true)),
] {
let e = ctx.parse(src).unwrap_or_else(|err| panic!("{src}: {err}"));
let got =
ctx.with_arena_mut(|a| AssumptionCache::new().query(a, e.raw_id(), Props::REAL));
assert_eq!(got, want, "{src}: {e}");
}
}
#[test]
fn no_claim_at_a_known_pole() {
use Assumption::{Integer, NonPositive, Real, Zero};
for &f in Func::ALL {
let n = sample_count(f);
let ctx = Context::new();
let mut args: Vec<Ex> = (0..n)
.map(|i| declared(&ctx, &format!("r{i}"), &[Real]))
.collect();
match f.spec().poles {
Poles::NonPositiveIntegers(which) => {
for &i in which {
args[usize::from(i)] = declared(&ctx, "np", &[Integer, NonPositive]);
}
}
Poles::Zero(i) => args[usize::from(i)] = declared(&ctx, "o", &[Zero]),
Poles::One(i) => args[usize::from(i)] = ctx.int(1),
Poles::None | Poles::Other => continue,
}
if let Some(e) = build_f(&ctx, f, &args) {
assert_ne!(ctx.query(&e, Props::REAL), Some(true), "{f:?}: {e}");
assert_ne!(ctx.query(&e, Props::COMPLEX), Some(true), "{f:?}: {e}");
}
}
}
#[test]
fn conjugate_follows_the_reflection_facet() {
use Assumption::{Imaginary, Integer, NonNegative, Real};
for &f in Func::ALL {
let n = sample_count(f);
let ctx = Context::new();
let plain: Vec<Ex> = (0..n).map(|i| ctx.symbol(&format!("c{i}"))).collect();
let with = |i: u8, e: Ex| {
let mut args = plain.clone();
args[usize::from(i)] = e;
args
};
let off_cut = |cut: Cut| {
if cut.line == CutLine::ImaginaryOutsideUnit {
declared(&ctx, "r", &[Real])
} else {
declared(&ctx, "w", &[Imaginary])
}
};
let integer = |nonnegative: bool| {
if nonnegative {
declared(&ctx, "k", &[Integer, NonNegative])
} else {
declared(&ctx, "m", &[Integer])
}
};
let cases: Vec<(Vec<Ex>, bool)> = match f.spec().conj {
Conj::Entire => vec![(plain.clone(), true)],
Conj::OffCut(cut) => {
vec![(plain.clone(), false), (with(cut.arg, off_cut(cut)), true)]
}
Conj::IntegerOrderOffCut { cut, nonnegative } => vec![
(plain.clone(), false),
(with(cut.arg, off_cut(cut)), true),
(with(0, integer(nonnegative)), true),
],
Conj::Polynomial { degree } => {
vec![(plain.clone(), false), (with(degree, integer(true)), true)]
}
Conj::No => vec![(plain.clone(), false)],
};
for (args, holds) in cases {
let Some(e) = build_f(&ctx, f, &args) else {
continue;
};
if ctx.query(&e, Props::REAL) == Some(true) {
continue;
}
let ids: Vec<ExprId> = if holds {
args.iter().map(|a| a.conjugate().raw_id()).collect()
} else {
Vec::new()
};
let want = ctx.with_arena_mut(|a| {
if holds {
a.func(f, &ids).unwrap_or_else(|err| panic!("{f:?}: {err}"))
} else {
a.intern(ExprNode::Conjugate(e.raw_id()))
}
});
assert_eq!(e.conjugate(), e.wrap(want), "{f:?}: conjugate({e})");
}
}
}
#[test]
fn view_of_non_functions_is_none() {
let ctx = Context::new();
let x = ctx.symbol("x");
let three = ctx.int(3);
let user = ctx.apply("f", std::slice::from_ref(&x)).unwrap();
let shi2 = ctx.apply("Shi", &[x.clone(), three]).unwrap();
let sum = &x + 1;
ctx.with_arena_mut(|a| {
for e in [&user, &shi2, &sum, &x] {
let node = a.node(e.raw_id()).clone();
assert_eq!(node.func(a), None, "{node:?}");
}
});
}
}