use std::fmt;
use smallvec::SmallVec;
#[cfg(test)]
use smallvec::smallvec;
use crate::base::arena::Arena;
use crate::base::errors::SymplexError;
use crate::base::libfn::Arity;
use crate::base::node::{ExprId, ExprNode};
#[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),
Group(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, PartialEq, Eq, Hash)]
pub(crate) enum Backend {
Vm,
Rust,
C,
Python,
}
impl Backend {
const fn bit(self) -> u8 {
match self {
Backend::Vm => 1,
Backend::Rust => 2,
Backend::C => 4,
Backend::Python => 8,
}
}
pub(crate) const fn entry_point(self) -> &'static str {
match self {
Backend::Vm => "compile",
Backend::Rust => "to_rust_fn",
Backend::C => "to_c_fn",
Backend::Python => "to_python",
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct Backends(u8);
impl Backends {
pub(crate) const NONE: Backends = Backends(0);
pub(crate) const VM_RUST_C: Backends = Backends(1 | 2 | 4);
pub(crate) const ALL: Backends = Backends(1 | 2 | 4 | 8);
pub(crate) const fn contains(self, b: Backend) -> bool {
self.0 & b.bit() != 0
}
}
impl fmt::Debug for Backends {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let all = [Backend::Vm, Backend::Rust, Backend::C, Backend::Python];
f.debug_set()
.entries(all.iter().filter(|b| self.contains(**b)))
.finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Kernel {
Own,
Unary(&'static str),
Binary(&'static str),
Ordered(&'static str),
Branches(&'static [&'static str]),
None,
}
impl Kernel {
#[cfg(test)]
pub(crate) fn helpers(self) -> SmallVec<[&'static str; 2]> {
match self {
Kernel::Unary(h) | Kernel::Binary(h) | Kernel::Ordered(h) => smallvec![h],
Kernel::Branches(hs) => hs.iter().copied().collect(),
Kernel::Own | Kernel::None => SmallVec::new(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Runtime {
pub(crate) kernel: Kernel,
pub(crate) backends: Backends,
pub(crate) scipy: Option<&'static str>,
}
impl Runtime {
const fn own(on: Backends) -> Runtime {
Runtime {
kernel: Kernel::Own,
backends: on,
scipy: None,
}
}
const fn helper(kernel: Kernel, on: Backends) -> Runtime {
Runtime {
kernel,
backends: on,
scipy: None,
}
}
const NONE: Runtime = Runtime {
kernel: Kernel::None,
backends: Backends::NONE,
scipy: None,
};
const fn scipy(self, call: &'static str) -> Runtime {
Runtime {
scipy: Some(call),
..self
}
}
pub(crate) const fn on(self, b: Backend) -> bool {
self.backends.contains(b)
}
pub(crate) fn elsewhere(self, b: Backend) -> String {
let others: Vec<&str> = [Backend::Vm, Backend::Rust, Backend::C, Backend::Python]
.into_iter()
.filter(|&o| o != b && self.on(o))
.map(Backend::entry_point)
.collect();
if others.is_empty() {
String::new()
} else {
format!(" (available in {})", others.join(", "))
}
}
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "`parse`, `sort` and `parity` are read by the tests over `Func::ALL`; the \
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,
pub(crate) rt: Runtime,
}
#[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, )* }
}
}
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum InternalFn {
$( $(#[$idoc])* $I, )*
}
impl InternalFn {
pub(crate) const fn name(self) -> &'static str {
match self { $( InternalFn::$I => $iname, )* }
}
pub(crate) const fn func(self) -> Func {
match self { $( InternalFn::$I => Func::$I, )* }
}
}
#[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, )*
}
}
pub(crate) const fn as_internal(self) -> Option<InternalFn> {
match self {
$( Func::$N => None, )*
$( Func::$L => None, )*
$( Func::$I => Some(InternalFn::$I), )*
}
}
fn is_internal_head(s: &str) -> bool {
matches!(s, $( $iname )|*)
}
}
macro_rules! non_node_funcs {
() => { $( Func::$L )|* | $( Func::$I )|* };
}
};
}
impl From<LibFn> for Func {
fn from(f: LibFn) -> Func {
f.func()
}
}
impl From<InternalFn> for Func {
fn from(f: InternalFn) -> 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, Group, 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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::NONE,
};
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,
rt: Runtime::NONE,
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::VM_RUST_C).scipy("scipy.special.psi(x)"),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::VM_RUST_C),
};
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,
rt: Runtime::own(Backends::VM_RUST_C).scipy("scipy.special.beta(a, b)"),
};
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,
rt: Runtime::helper(Kernel::Unary("sin_integral"), Backends::VM_RUST_C)
.scipy("scipy.special.sici(x)[0]"),
};
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,
rt: Runtime::helper(Kernel::Unary("cos_integral"), Backends::VM_RUST_C)
.scipy("scipy.special.sici(x)[1]"),
};
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,
rt: Runtime::helper(Kernel::Unary("exp_integral_ei"), Backends::VM_RUST_C)
.scipy("scipy.special.expi(x)"),
};
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,
rt: Runtime::helper(Kernel::Unary("log_integral"), Backends::VM_RUST_C),
};
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,
rt: Runtime::NONE.scipy("scipy.special.zeta(s)"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.polygamma(n, x)"),
};
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,
rt: Runtime::NONE,
};
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,
rt: Runtime::own(Backends::ALL),
};
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,
rt: Runtime::own(Backends::VM_RUST_C).scipy("scipy.special.binom(n, k)"),
};
}
lib {
Factorial2 => FuncSpec {
name: "factorial2", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Postfix("!!"),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Unary("factorial2"), Backends::VM_RUST_C)
.scipy("scipy.special.factorial2(n)"),
};
Subfactorial => FuncSpec {
name: "subfactorial", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Layout(&[Lit("!"), Group(0)]),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
RisingFactorial => FuncSpec {
name: "rising_factorial", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit("{"), Group(0), Lit(r"}^{\left("), Arg(1), Lit(r"\right)}")]),
conj: Conj::Entire, real_on: RealOn::Polynomial { degree: 1 }, parity: Neither,
poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Binary("rising_factorial"), Backends::VM_RUST_C)
.scipy("scipy.special.poch(x, n)"),
};
FallingFactorial => FuncSpec {
name: "falling_factorial", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit(r"{\left("), Arg(0), Lit(r"\right)}_{"), Arg(1), Lit("}")]),
conj: Conj::Entire, real_on: RealOn::Polynomial { degree: 1 }, parity: Neither,
poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Binary("falling_factorial"), Backends::VM_RUST_C)
.scipy("scipy.special.poch(x - n + 1, n)"),
};
Fibonacci => FuncSpec {
name: "fibonacci", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Layout(&[Lit("F_{"), Arg(0), Lit("}")]),
conj: Conj::No, real_on: RealOn::RealLine, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Unary("fibonacci"), Backends::VM_RUST_C),
};
Lucas => FuncSpec {
name: "lucas", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Layout(&[Lit("L_{"), Arg(0), Lit("}")]),
conj: Conj::No, real_on: RealOn::RealLine, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Unary("lucas"), Backends::VM_RUST_C),
};
Bernoulli => FuncSpec {
name: "bernoulli", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Layout(&[Lit("B_{"), Arg(0), Lit("}")]),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::None,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
Harmonic => FuncSpec {
name: "harmonic", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Layout(&[Lit("H_{"), Arg(0), Lit("}")]),
conj: Conj::No, real_on: RealOn::NonNegativeReals, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Unary("harmonic"), Backends::VM_RUST_C),
};
Catalan => FuncSpec {
name: "catalan", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Layout(&[Lit("C_{"), Arg(0), Lit("}")]),
conj: Conj::No, real_on: RealOn::NonNegativeReals, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
Bell => FuncSpec {
name: "bell", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Layout(&[Lit("B_{"), Arg(0), Lit("}")]),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::None,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
EulerNumber => FuncSpec {
name: "euler_number", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Layout(&[Lit("E_{"), Arg(0), Lit("}")]),
conj: Conj::No, real_on: RealOn::RealLine, parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
Stirling1 => FuncSpec {
name: "stirling1", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit(r"\left[{"), Arg(0), Lit(r" \atop "), Arg(1), Lit(r"}\right]")]),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::None,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
Stirling2 => FuncSpec {
name: "stirling2", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit(r"\left\{{"), Arg(0), Lit(r" \atop "), Arg(1), Lit(r"}\right\}")]),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::None,
constants: Constants::Evaluated,
rt: Runtime::NONE.scipy("scipy.special.stirling2(n, k)"),
};
PartitionCount => FuncSpec {
name: "partition_count", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call("p"),
conj: Conj::No, real_on: RealOn::NonNegativeIntegers, parity: Neither,
poles: Poles::None,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
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,
rt: Runtime::helper(Kernel::Branches(&["lambert_w0", "lambert_wm1"]), Backends::VM_RUST_C),
};
BesselJ => FuncSpec {
name: "besselj", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit("J_{"), Arg(0), Lit(r"}\left("), Arg(1), Lit(r"\right)")]),
conj: Conj::IntegerOrderOffCut { cut: NEG1, nonnegative: false },
real_on: RealOn::BesselOrder, parity: Neither, poles: Poles::Zero(1),
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Ordered("bessel_j"), Backends::VM_RUST_C)
.scipy("scipy.special.jv(order, x)"),
};
BesselY => FuncSpec {
name: "bessely", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit("Y_{"), 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,
rt: Runtime::helper(Kernel::Ordered("bessel_y"), Backends::VM_RUST_C)
.scipy("scipy.special.yv(order, x)"),
};
BesselI => FuncSpec {
name: "besseli", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit("I_{"), Arg(0), Lit(r"}\left("), Arg(1), Lit(r"\right)")]),
conj: Conj::IntegerOrderOffCut { cut: NEG1, nonnegative: false },
real_on: RealOn::BesselOrder, parity: Neither, poles: Poles::Zero(1),
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Ordered("bessel_i"), Backends::VM_RUST_C)
.scipy("scipy.special.iv(order, x)"),
};
BesselK => FuncSpec {
name: "besselk", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit("K_{"), 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,
rt: Runtime::helper(Kernel::Ordered("bessel_k"), Backends::VM_RUST_C)
.scipy("scipy.special.kv(order, x)"),
};
Legendre => FuncSpec {
name: "legendre", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit("P_{"), Arg(0), Lit(r"}\left("), Arg(1), Lit(r"\right)")]),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Ordered("legendre_p"), Backends::VM_RUST_C)
.scipy("scipy.special.eval_legendre(n, x)"),
};
ChebyshevT => FuncSpec {
name: "chebyshev_t", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit("T_{"), Arg(0), Lit(r"}\left("), Arg(1), Lit(r"\right)")]),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Ordered("chebyshev_t"), Backends::VM_RUST_C)
.scipy("scipy.special.eval_chebyt(n, x)"),
};
ChebyshevU => FuncSpec {
name: "chebyshev_u", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit("U_{"), Arg(0), Lit(r"}\left("), Arg(1), Lit(r"\right)")]),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Ordered("chebyshev_u"), Backends::VM_RUST_C)
.scipy("scipy.special.eval_chebyu(n, x)"),
};
Hermite => FuncSpec {
name: "hermite", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit("H_{"), Arg(0), Lit(r"}\left("), Arg(1), Lit(r"\right)")]),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Ordered("hermite_h"), Backends::VM_RUST_C)
.scipy("scipy.special.eval_hermite(n, x)"),
};
Laguerre => FuncSpec {
name: "laguerre", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Layout(&[Lit("L_{"), Arg(0), Lit(r"}\left("), Arg(1), Lit(r"\right)")]),
conj: Conj::Polynomial { degree: 0 }, real_on: RealOn::Polynomial { degree: 0 },
parity: Neither, poles: Poles::None,
constants: Constants::Evaluated,
rt: Runtime::helper(Kernel::Ordered("laguerre_l"), Backends::VM_RUST_C)
.scipy("scipy.special.eval_laguerre(n, x)"),
};
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,
rt: Runtime::helper(Kernel::Unary("erfi"), Backends::VM_RUST_C)
.scipy("scipy.special.erfi(x)"),
};
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,
rt: Runtime::helper(Kernel::Unary("erfinv"), Backends::VM_RUST_C)
.scipy("scipy.special.erfinv(y)"),
};
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,
rt: Runtime::helper(Kernel::Unary("erfcinv"), Backends::VM_RUST_C)
.scipy("scipy.special.erfcinv(y)"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.expn(n, x)"),
};
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,
rt: Runtime::helper(Kernel::Unary("sinh_integral"), Backends::VM_RUST_C)
.scipy("scipy.special.shichi(x)[0]"),
};
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,
rt: Runtime::helper(Kernel::Unary("cosh_integral"), Backends::VM_RUST_C)
.scipy("scipy.special.shichi(x)[1]"),
};
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,
rt: Runtime::helper(Kernel::Unary("fresnel_s"), Backends::VM_RUST_C)
.scipy("scipy.special.fresnel(x)[0]"),
};
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,
rt: Runtime::helper(Kernel::Unary("fresnel_c"), Backends::VM_RUST_C)
.scipy("scipy.special.fresnel(x)[1]"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.gamma(s) * scipy.special.gammainc(s, x)"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.gamma(s) * scipy.special.gammaincc(s, x)"),
};
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,
rt: Runtime::NONE,
};
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,
rt: Runtime::NONE,
};
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,
rt: Runtime::NONE.scipy("scipy.special.airy(x)[0]"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.airy(x)[2]"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.airy(x)[1]"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.airy(x)[3]"),
};
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,
rt: Runtime::helper(Kernel::Unary("elliptic_k"), Backends::VM_RUST_C)
.scipy("scipy.special.ellipk(m)"),
};
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,
rt: Runtime::helper(Kernel::Unary("elliptic_e"), Backends::VM_RUST_C)
.scipy("scipy.special.ellipe(m)"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.ellipkinc(phi, m)"),
};
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,
rt: Runtime::NONE,
};
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,
rt: Runtime::NONE.scipy("scipy.special.eval_gegenbauer(n, alpha, x)"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.eval_jacobi(n, alpha, beta, x)"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.lpmv(m, n, x)"),
};
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,
rt: Runtime::NONE.scipy("scipy.special.eval_genlaguerre(n, alpha, x)"),
};
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,
rt: Runtime::NONE
.scipy("scipy.special.beta(a, b) * (scipy.special.betainc(a, b, x2) - scipy.special.betainc(a, b, x1))"),
};
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,
rt: Runtime::NONE
.scipy("scipy.special.betainc(a, b, x2) - scipy.special.betainc(a, b, x1)"),
};
}
internal {
ErfcxAsym => FuncSpec {
name: "__erfcx_asym", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{\_\_erfcx\_asym}"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
EiAsym => FuncSpec {
name: "__ei_asym", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{\_\_ei\_asym}"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
Erfix2Asym => FuncSpec {
name: "__erfix2_asym", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{\_\_erfix2\_asym}"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
Erfcx2Asym => FuncSpec {
name: "__erfcx2_asym", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{\_\_erfcx2\_asym}"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
BesselIAsym => FuncSpec {
name: "__besseli_asym", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call(r"\operatorname{\_\_besseli\_asym}"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
BesselKAsym => FuncSpec {
name: "__besselk_asym", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call(r"\operatorname{\_\_besselk\_asym}"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
UpperGammaAsym => FuncSpec {
name: "__uppergamma_asym", parse: &[], arity: Exactly(2), sort: Named,
tex: Tex::Call(r"\operatorname{\_\_uppergamma\_asym}"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
ZetaAsym => FuncSpec {
name: "__zeta_asym", parse: &[], arity: Exactly(1), sort: Named,
tex: Tex::Call(r"\operatorname{\_\_zeta\_asym}"),
conj: Conj::No, real_on: RealOn::Unclaimed, parity: Neither, poles: Poles::Other,
constants: Constants::Evaluated,
rt: Runtime::NONE,
};
}
}
impl ExprNode {
pub(crate) const fn func_head(&self) -> Option<Func> {
match self {
ExprNode::Min(_) => Some(Func::Min),
ExprNode::Max(_) => Some(Func::Max),
ExprNode::Floor(_) => Some(Func::Floor),
ExprNode::Ceiling(_) => Some(Func::Ceiling),
ExprNode::Sin(_) => Some(Func::Sin),
ExprNode::Cos(_) => Some(Func::Cos),
ExprNode::Tan(_) => Some(Func::Tan),
ExprNode::Exp(_) => Some(Func::Exp),
ExprNode::Ln(_) => Some(Func::Ln),
ExprNode::Abs(_) => Some(Func::Abs),
ExprNode::Asin(_) => Some(Func::Asin),
ExprNode::Acos(_) => Some(Func::Acos),
ExprNode::Atan(_) => Some(Func::Atan),
ExprNode::Atan2(..) => Some(Func::Atan2),
ExprNode::Sinh(_) => Some(Func::Sinh),
ExprNode::Cosh(_) => Some(Func::Cosh),
ExprNode::Tanh(_) => Some(Func::Tanh),
ExprNode::Asinh(_) => Some(Func::Asinh),
ExprNode::Acosh(_) => Some(Func::Acosh),
ExprNode::Atanh(_) => Some(Func::Atanh),
ExprNode::Sign(_) => Some(Func::Sign),
ExprNode::Heaviside(_) => Some(Func::Heaviside),
ExprNode::DiracDelta(_) => Some(Func::DiracDelta),
ExprNode::Re(_) => Some(Func::Re),
ExprNode::Im(_) => Some(Func::Im),
ExprNode::Conjugate(_) => Some(Func::Conjugate),
ExprNode::Arg(_) => Some(Func::Arg),
ExprNode::Gamma(_) => Some(Func::Gamma),
ExprNode::LogGamma(_) => Some(Func::LogGamma),
ExprNode::Digamma(_) => Some(Func::Digamma),
ExprNode::Erf(_) => Some(Func::Erf),
ExprNode::Erfc(_) => Some(Func::Erfc),
ExprNode::LambertW(_) => Some(Func::LambertW0),
ExprNode::Beta(..) => Some(Func::Beta),
ExprNode::Si(_) => Some(Func::Si),
ExprNode::Ci(_) => Some(Func::Ci),
ExprNode::Ei(_) => Some(Func::Ei),
ExprNode::Li(_) => Some(Func::Li),
ExprNode::Zeta(_) => Some(Func::Zeta),
ExprNode::Polygamma(..) => Some(Func::Polygamma),
ExprNode::KroneckerDelta(..) => Some(Func::KroneckerDelta),
ExprNode::Factorial(_) => Some(Func::Factorial),
ExprNode::Binomial(..) => Some(Func::Binomial),
ExprNode::Lib(lib, _) => Some(lib.func()),
ExprNode::Internal(g, _) => Some(g.func()),
ExprNode::Apply(..)
| 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,
}
}
pub(crate) fn unary_arg_if(&self, pred: impl Fn(Func) -> bool) -> Option<ExprId> {
let f = self.func_head()?;
if !pred(f) {
return None;
}
match self.children().as_slice() {
&[a] => Some(a),
_ => None,
}
}
pub(crate) fn func(&self, _arena: &Arena) -> Option<(Func, SmallVec<[ExprId; 4]>)> {
let f = self.func_head()?;
let args: SmallVec<[ExprId; 4]> = self.children().into_iter().collect();
(f.family() == Family::Node || f.spec().arity.accepts(args.len())).then_some((f, args))
}
}
impl Func {
pub(crate) fn is_internal_name(name: &str) -> bool {
Func::is_internal_head(name)
}
pub(crate) const fn elementary(self) -> Option<Elementary> {
match self {
Func::Sin | Func::Cos | Func::Tan => Some(Elementary::Trig),
Func::Asin | Func::Acos | Func::Atan => Some(Elementary::InverseTrig),
Func::Sinh | Func::Cosh | Func::Tanh => Some(Elementary::Hyperbolic),
Func::Asinh | Func::Acosh | Func::Atanh => Some(Elementary::InverseHyperbolic),
Func::Exp => Some(Elementary::Exp),
Func::Ln => Some(Elementary::Ln),
Func::Min
| Func::Max
| Func::Floor
| Func::Ceiling
| Func::Abs
| Func::Atan2
| Func::Sign
| Func::Heaviside
| Func::DiracDelta
| Func::Re
| Func::Im
| Func::Conjugate
| Func::Arg
| Func::Gamma
| Func::LogGamma
| Func::Digamma
| Func::Erf
| Func::Erfc
| Func::LambertW0
| Func::Beta
| Func::Si
| Func::Ci
| Func::Ei
| Func::Li
| Func::Zeta
| Func::Polygamma
| Func::KroneckerDelta
| Func::Factorial
| Func::Binomial
| non_node_funcs!() => None,
}
}
pub(crate) const fn is_special(self) -> bool {
match self {
Func::Gamma
| Func::LogGamma
| Func::Digamma
| Func::Polygamma
| Func::Zeta
| Func::Beta
| Func::Factorial
| Func::Binomial
| Func::Erf
| Func::Erfc
| Func::Ei
| Func::Li
| Func::Si
| Func::Ci
| Func::LambertW0 => true,
Func::Min
| Func::Max
| Func::Floor
| Func::Ceiling
| Func::Sin
| Func::Cos
| Func::Tan
| Func::Exp
| Func::Ln
| Func::Abs
| Func::Asin
| Func::Acos
| Func::Atan
| Func::Atan2
| Func::Sinh
| Func::Cosh
| Func::Tanh
| Func::Asinh
| Func::Acosh
| Func::Atanh
| Func::Sign
| Func::Heaviside
| Func::DiracDelta
| Func::Re
| Func::Im
| Func::Conjugate
| Func::Arg
| Func::KroneckerDelta
| non_node_funcs!() => false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Elementary {
Trig,
InverseTrig,
Hyperbolic,
InverseHyperbolic,
Exp,
Ln,
}
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 Some(g) = f.as_internal() else {
return Err(arity_error(f, args.len()));
};
Ok(self.intern(ExprNode::Internal(g, 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)]
pub(crate) fn selected(pred: impl Fn(Func) -> bool) -> std::collections::HashSet<Func> {
Func::ALL.iter().copied().filter(|&f| pred(f)).collect()
}
#[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(),
TexPiece::Group(i) if atom => args[usize::from(i)].clone(),
TexPiece::Group(i) => format!(r"\left({}\right)", args[usize::from(i)]),
})
.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 internal_functions_are_internal_nodes_that_no_name_builds() {
for &f in Func::ALL {
let Some(g) = f.as_internal() else {
assert_ne!(f.family(), Family::Internal, "{f:?}");
assert!(!Func::is_internal_name(f.spec().name), "{f:?}");
continue;
};
assert_eq!(f.family(), Family::Internal);
assert_eq!(Func::from(g), f);
assert_eq!(g.name(), f.spec().name);
assert!(Func::is_internal_name(g.name()));
let n = sample_count(f);
let (ctx, syms, e) = built(f, n);
ctx.with_arena_mut(|a| {
assert!(matches!(a.node(e.raw_id()), ExprNode::Internal(h, _) if *h == g));
});
let json = e.to_json().unwrap_or_else(|err| panic!("{f:?}: {err}"));
assert!(
json.contains(&format!("\"name\":\"{}\"", g.name())),
"{json}"
);
let back = ctx.from_tree(&e.to_tree());
assert_ne!(back.raw_id(), e.raw_id(), "{f:?}");
ctx.with_arena_mut(|a| {
assert!(
matches!(a.node(back.raw_id()), ExprNode::Apply(..)),
"{f:?}"
);
assert_eq!(a.node(back.raw_id()).func(a), None);
});
assert!(ctx.from_json(&json).is_err(), "{f:?}");
assert!(ctx.parse(&e.to_string()).is_err(), "{f:?}");
assert!(ctx.apply(g.name(), &syms).is_err(), "{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()));
}
assert_eq!(a.sort_key(e.raw_id()), &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 library_functions_are_lib_nodes_and_apply_is_never_one() {
for &f in Func::ALL.iter().filter(|f| f.family() == Family::Lib) {
let n = sample_count(f);
let (ctx, _syms, e) = built(f, n);
ctx.with_arena_mut(|a| {
let node = a.node(e.raw_id()).clone();
assert!(
matches!(node, ExprNode::Lib(lib, ref args)
if Some(lib) == f.as_lib() && args.len() == n),
"{f:?}: {node:?}"
);
assert_eq!(node.func(a).map(|(g, _)| g), Some(f), "{f:?}");
let head = a.symbols.intern(f.spec().name);
let apply = ExprNode::Apply(head, node.children().into_iter().collect());
assert_eq!(apply.func(a), None, "{f:?}");
let old = a.intern(apply);
assert_ne!(old, e.raw_id());
assert_eq!(a.sort_key(old), a.sort_key(e.raw_id()), "{f:?}");
});
}
}
#[test]
fn elementary_is_the_old_list_of_fourteen() {
use Func::*;
let old = [
Ln, Exp, Sin, Cos, Tan, Sinh, Cosh, Tanh, Asin, Acos, Atan, Asinh, Acosh, Atanh,
];
assert_eq!(
selected(|f| f.elementary().is_some()),
old.into_iter().collect()
);
assert_eq!(Sin.elementary(), Some(Elementary::Trig));
assert_eq!(Acosh.elementary(), Some(Elementary::InverseHyperbolic));
}
#[test]
fn elementary_and_abs_are_the_old_component_lists() {
use Func::*;
let old = [
Sin, Cos, Tan, Exp, Ln, Asin, Acos, Atan, Sinh, Cosh, Tanh, Asinh, Acosh, Atanh, Abs,
];
assert_eq!(
selected(|f| f.elementary().is_some() || f == Abs),
old.into_iter().collect()
);
}
#[test]
fn is_special_is_the_old_costly_list() {
use Func::*;
let old = [
Gamma, LogGamma, Digamma, Polygamma, Zeta, Beta, Factorial, Binomial, Erf, Erfc, Ei,
Li, Si, Ci, LambertW0,
];
assert_eq!(selected(Func::is_special), old.into_iter().collect());
}
#[test]
fn apply_unary_covers_its_old_arms() {
use Func::*;
let old = [
Sin, Cos, Tan, Asin, Acos, Atan, Sinh, Cosh, Tanh, Asinh, Acosh, Atanh, Erf, Erfc,
Gamma, LogGamma, Digamma, LambertW0, Factorial, Abs, Sign, Heaviside, Exp, Ln,
];
for f in old {
assert_eq!(f.spec().arity, Exactly(1), "{f:?}");
let (ctx, syms, e) = built(f, 1);
ctx.with_arena_mut(|a| {
let node = a.node(e.raw_id()).clone();
assert_eq!(
node.func(a),
Some((f, smallvec![syms[0].raw_id()])),
"{f:?}"
);
});
}
}
#[test]
fn internal_heads_print_as_valid_tex() {
for &f in Func::ALL.iter().filter(|f| f.family() == Family::Internal) {
let (_ctx, _syms, e) = built(f, sample_count(f));
let tex = e.to_latex();
assert!(tex.starts_with(r"\operatorname{\_\_"), "{f:?}: {tex}");
assert!(!tex.replace(r"\_", "").contains('_'), "{f:?}: {tex}");
}
}
#[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();
assert!(ctx.apply("Shi", &[x.clone(), three.clone()]).is_err());
let sum = &x + 1;
ctx.with_arena_mut(|a| {
assert!(a.func(Func::Shi, &[x.raw_id(), three.raw_id()]).is_err());
let head = a.symbols.intern(Func::EiAsym.spec().name);
let internal2 = a.intern(ExprNode::Apply(head, smallvec![x.raw_id(), three.raw_id()]));
for id in [user.raw_id(), internal2, sum.raw_id(), x.raw_id()] {
let node = a.node(id).clone();
assert_eq!(node.func(a), None, "{node:?}");
}
});
}
}