use std::collections::BTreeSet;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BinOp {
Or,
And,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
Add,
Sub,
Mul,
Div,
Mod,
Pow,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum UnOp {
Neg,
Not,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Func {
Abs,
Acos,
Acosh,
Asin,
Asinh,
Atan,
Atan2,
Atanh,
Cbrt,
Ceil,
Cos,
Cosh,
Cot,
Csc,
Exp,
Exp2,
Floor,
Hypot,
If,
Int,
J0,
J1,
Jn,
Log,
Log2,
Log10,
Max,
Min,
Not,
Pow,
Sec,
Sin,
Sinh,
Sqrt,
Tan,
Tanh,
Trunc,
Y0,
Y1,
Yn,
}
impl Func {
fn lookup(name: &str) -> Option<(Func, usize)> {
use Func::*;
Some(match name {
"abs" => (Abs, 1),
"acos" => (Acos, 1),
"acosh" => (Acosh, 1),
"asin" => (Asin, 1),
"asinh" => (Asinh, 1),
"atan" => (Atan, 1),
"atan2" => (Atan2, 2),
"atanh" => (Atanh, 1),
"cbrt" => (Cbrt, 1),
"ceil" => (Ceil, 1),
"cos" => (Cos, 1),
"cosh" => (Cosh, 1),
"cot" => (Cot, 1),
"csc" => (Csc, 1),
"exp" => (Exp, 1),
"exp2" => (Exp2, 1),
"floor" => (Floor, 1),
"hypot" => (Hypot, 2),
"if" => (If, 3),
"int" => (Int, 1),
"j0" => (J0, 1),
"j1" => (J1, 1),
"jn" => (Jn, 2),
"log" => (Log, 1),
"log2" => (Log2, 1),
"log10" => (Log10, 1),
"max" => (Max, 2),
"min" => (Min, 2),
"not" => (Not, 1),
"pow" => (Pow, 2),
"sec" => (Sec, 1),
"sin" => (Sin, 1),
"sinh" => (Sinh, 1),
"sqrt" => (Sqrt, 1),
"tan" => (Tan, 1),
"tanh" => (Tanh, 1),
"trunc" => (Trunc, 1),
"y0" => (Y0, 1),
"y1" => (Y1, 1),
"yn" => (Yn, 2),
_ => return None,
})
}
fn apply(self, a: &[f64]) -> f64 {
use Func::*;
match self {
Abs => a[0].abs(),
Acos => a[0].acos(),
Acosh => a[0].acosh(),
Asin => a[0].asin(),
Asinh => a[0].asinh(),
Atan => a[0].atan(),
Atan2 => a[0].atan2(a[1]),
Atanh => a[0].atanh(),
Cbrt => a[0].cbrt(),
Ceil => a[0].ceil(),
Cos => a[0].cos(),
Cosh => a[0].cosh(),
Cot => 1.0 / a[0].tan(),
Csc => 1.0 / a[0].sin(),
Exp => a[0].exp(),
Exp2 => a[0].exp2(),
Floor => a[0].floor(),
Hypot => a[0].hypot(a[1]),
If => unreachable!("if() is evaluated lazily"),
Int => a[0].round(),
Log => a[0].ln(),
Log2 => a[0].log2(),
Log10 => a[0].log10(),
Max => a[0].max(a[1]),
Min => a[0].min(a[1]),
Not => bool_val(!truthy(a[0])),
Pow => a[0].powf(a[1]),
Sec => 1.0 / a[0].cos(),
Sin => a[0].sin(),
Sinh => a[0].sinh(),
Sqrt => a[0].sqrt(),
Tan => a[0].tan(),
Tanh => a[0].tanh(),
Trunc => a[0].trunc(),
J0 => bessel::j0(a[0]),
J1 => bessel::j1(a[0]),
Jn => bessel::jn(a[0], a[1]),
Y0 => bessel::y0(a[0]),
Y1 => bessel::y1(a[0]),
Yn => bessel::yn(a[0], a[1]),
}
}
}
#[inline]
fn truthy(v: f64) -> bool {
v != 0.0 && !v.is_nan()
}
#[inline]
fn bool_val(b: bool) -> f64 {
if b { 1.0 } else { 0.0 }
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum Op {
Num(f64),
Name(u32),
Slot(u32),
Unary(UnOp),
Bin(BinOp),
Call(Func),
AndJump(u32),
OrJump(u32),
ToBool,
JumpIfFalse(u32),
Jump(u32),
}
impl Func {
fn arity(self) -> usize {
match self {
Func::Atan2 | Func::Hypot | Func::Jn | Func::Max | Func::Min | Func::Pow | Func::Yn => {
2
}
Func::If => 3,
_ => 1,
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
struct Program {
code: Vec<Op>,
max_stack: usize,
}
const INLINE_STACK: usize = 32;
impl Program {
fn finish(code: Vec<Op>) -> Program {
let mut at: Vec<Option<usize>> = vec![None; code.len() + 1];
let (mut d, mut max) = (0usize, 0usize);
for (i, op) in code.iter().enumerate() {
if let Some(v) = at[i] {
d = v;
}
match *op {
Op::Num(_) | Op::Name(_) | Op::Slot(_) => d += 1,
Op::Unary(_) | Op::ToBool => {}
Op::Bin(_) => d -= 1,
Op::Call(f) => d = d + 1 - f.arity(),
Op::AndJump(t) | Op::OrJump(t) => {
at[t as usize] = Some(d);
d -= 1;
}
Op::JumpIfFalse(t) => {
d -= 1;
at[t as usize] = Some(d);
}
Op::Jump(t) => at[t as usize] = Some(d),
}
max = max.max(d);
}
Program {
code,
max_stack: max,
}
}
#[inline]
fn run<F>(&self, slots: &[f64], name: F) -> Option<f64>
where
F: FnMut(u32) -> Option<f64>,
{
if self.max_stack <= 8 {
let mut buf = [0.0f64; 8];
exec(&self.code, slots, &mut buf, name)
} else if self.max_stack <= INLINE_STACK {
let mut buf = [0.0f64; INLINE_STACK];
exec(&self.code, slots, &mut buf, name)
} else {
let mut buf = vec![0.0f64; self.max_stack];
exec(&self.code, slots, &mut buf, name)
}
}
}
#[inline]
fn exec<F>(code: &[Op], slots: &[f64], st: &mut [f64], mut name: F) -> Option<f64>
where
F: FnMut(u32) -> Option<f64>,
{
let mut sp = 0usize;
let mut pc = 0usize;
while let Some(op) = code.get(pc) {
match *op {
Op::Num(v) => {
st[sp] = v;
sp += 1;
}
Op::Slot(i) => {
st[sp] = slots.get(i as usize).copied().unwrap_or(0.0);
sp += 1;
}
Op::Name(i) => {
st[sp] = name(i)?;
sp += 1;
}
Op::Unary(UnOp::Neg) => st[sp - 1] = -st[sp - 1],
Op::Unary(UnOp::Not) => st[sp - 1] = bool_val(!truthy(st[sp - 1])),
Op::Bin(b) => {
sp -= 1;
st[sp - 1] = binop(b, st[sp - 1], st[sp]);
}
Op::Call(f) => {
let n = f.arity();
let r = f.apply(&st[sp - n..sp]);
sp -= n - 1;
st[sp - 1] = r;
}
Op::AndJump(t) => {
if !truthy(st[sp - 1]) {
st[sp - 1] = 0.0;
pc = t as usize;
continue;
}
sp -= 1;
}
Op::OrJump(t) => {
if truthy(st[sp - 1]) {
st[sp - 1] = 1.0;
pc = t as usize;
continue;
}
sp -= 1;
}
Op::ToBool => st[sp - 1] = bool_val(truthy(st[sp - 1])),
Op::JumpIfFalse(t) => {
sp -= 1;
if !truthy(st[sp]) {
pc = t as usize;
continue;
}
}
Op::Jump(t) => {
pc = t as usize;
continue;
}
}
pc += 1;
}
Some(if sp > 0 { st[sp - 1] } else { f64::NAN })
}
fn binop(op: BinOp, a: f64, b: f64) -> f64 {
match op {
BinOp::Or => bool_val(truthy(a) || truthy(b)),
BinOp::And => bool_val(truthy(a) && truthy(b)),
BinOp::Eq => bool_val(a == b),
BinOp::Ne => bool_val(a != b),
BinOp::Lt => bool_val(a < b),
BinOp::Le => bool_val(a <= b),
BinOp::Gt => bool_val(a > b),
BinOp::Ge => bool_val(a >= b),
BinOp::Add => a + b,
BinOp::Sub => a - b,
BinOp::Mul => a * b,
BinOp::Div => a / b,
BinOp::Mod => a % b,
BinOp::Pow => a.powf(b),
}
}
fn builtin_constant(name: &str) -> Option<f64> {
match name {
"pi" => Some(core::f64::consts::PI),
"e" => Some(core::f64::consts::E),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Expr {
src: String,
prog: Program,
names: Vec<String>,
}
impl Expr {
pub fn parse(src: &str) -> Result<Expr> {
let mut names: Vec<String> = Vec::new();
let code = Parser::new(src).parse(&mut |n: &str| {
let i = match names.iter().position(|m| m == n) {
Some(i) => i,
None => {
names.push(n.to_owned());
names.len() - 1
}
};
Ok(Op::Name(i as u32))
})?;
Ok(Expr {
src: src.to_owned(),
prog: Program::finish(code),
names,
})
}
pub fn source(&self) -> &str {
&self.src
}
pub fn names(&self) -> BTreeSet<&str> {
self.names.iter().map(String::as_str).collect()
}
pub fn eval_with(&self, mut lookup: impl FnMut(&str) -> Option<f64>) -> Result<f64> {
let mut missing = 0;
self.prog
.run(&[], |i| {
let n = &self.names[i as usize];
let v = lookup(n).or_else(|| builtin_constant(n));
if v.is_none() {
missing = i;
}
v
})
.ok_or_else(|| Error::UnknownVariable(self.names[missing as usize].clone()))
}
pub fn eval_const(&self) -> Result<f64> {
self.eval_with(|_| None)
}
pub fn compile(
&self,
vars: &[&str],
mut lookup: impl FnMut(&str) -> Option<f64>,
) -> Result<CompiledExpr> {
let code = Parser::new(&self.src).parse(&mut |n: &str| {
if let Some(i) = vars.iter().position(|v| *v == n) {
Ok(Op::Slot(i as u32))
} else if let Some(v) = lookup(n).or_else(|| builtin_constant(n)) {
Ok(Op::Num(v))
} else {
Err(Error::UnknownVariable(n.to_owned()))
}
})?;
Ok(CompiledExpr {
prog: Program::finish(code),
})
}
}
impl core::str::FromStr for Expr {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Expr::parse(s)
}
}
impl core::fmt::Display for Expr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.src)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CompiledExpr {
prog: Program,
}
impl CompiledExpr {
#[inline]
pub fn eval(&self, vars: &[f64]) -> f64 {
if let [Op::Num(v)] = self.prog.code[..] {
return v;
}
self.prog.run(vars, |_| Some(f64::NAN)).unwrap_or(f64::NAN)
}
pub fn as_constant(&self) -> Option<f64> {
match self.prog.code[..] {
[Op::Num(v)] => Some(v),
_ => None,
}
}
}
enum Pending {
Bin {
op: BinOp,
prec: u8,
jump: usize,
fold_lhs: bool,
},
Unary(UnOp),
Paren,
Call {
f: Func,
start: usize,
argc: usize,
jumps: [usize; 2],
fold_cond: Option<usize>,
},
}
const UNARY_PREC: u8 = 6;
struct Emitter {
code: Vec<Op>,
barrier: usize,
}
impl Emitter {
fn num_at(&self, i: usize) -> Option<f64> {
if i < self.barrier {
return None;
}
match self.code.get(i) {
Some(Op::Num(v)) => Some(*v),
_ => None,
}
}
fn unary(&mut self, op: UnOp) {
let n = self.code.len();
if let Some(a) = n.checked_sub(1).and_then(|i| self.num_at(i)) {
self.code[n - 1] = Op::Num(match op {
UnOp::Neg => -a,
UnOp::Not => bool_val(!truthy(a)),
});
} else {
self.code.push(Op::Unary(op));
}
}
fn bin(&mut self, op: BinOp) {
let n = self.code.len();
if n >= 2
&& let (Some(a), Some(b)) = (self.num_at(n - 2), self.num_at(n - 1))
{
self.code.truncate(n - 2);
self.code.push(Op::Num(binop(op, a, b)));
} else {
self.code.push(Op::Bin(op));
}
}
fn call(&mut self, f: Func) {
let n = self.code.len();
let k = f.arity();
if n >= k {
let mut args = [0.0; 3];
if (0..k).all(|j| match self.num_at(n - k + j) {
Some(v) => {
args[j] = v;
true
}
None => false,
}) {
self.code.truncate(n - k);
self.code.push(Op::Num(f.apply(&args[..k])));
return;
}
}
self.code.push(Op::Call(f));
}
fn patch(&mut self, at: usize) {
let here = self.code.len() as u32;
match &mut self.code[at] {
Op::AndJump(t) | Op::OrJump(t) | Op::JumpIfFalse(t) | Op::Jump(t) => *t = here,
_ => unreachable!("not a jump"),
}
self.barrier = self.barrier.max(self.code.len());
}
fn finish_logic(&mut self, op: BinOp, jump: usize, fold_lhs: bool) {
let n = self.code.len();
if fold_lhs
&& n == jump + 2
&& let (Some(Op::Num(a)), Some(b)) =
(self.code.get(jump - 1).copied(), self.num_at(n - 1))
{
self.code.truncate(jump - 1);
self.code.push(Op::Num(binop(op, a, b)));
return;
}
self.code.push(Op::ToBool);
self.patch(jump);
}
}
struct Parser<'a> {
src: &'a str,
pos: usize,
}
impl<'a> Parser<'a> {
fn new(src: &'a str) -> Self {
Parser { src, pos: 0 }
}
fn err(&self, msg: &str) -> Error {
Error::Parse {
expr: self.src.to_owned(),
pos: self.pos,
msg: msg.to_owned(),
}
}
fn skip_ws(&mut self) {
while let Some(c) = self.peek() {
if c.is_ascii_whitespace() {
self.pos += 1;
} else {
break;
}
}
}
fn peek(&self) -> Option<u8> {
self.src.as_bytes().get(self.pos).copied()
}
fn peek2(&self) -> Option<u8> {
self.src.as_bytes().get(self.pos + 1).copied()
}
fn peek_binop(&mut self) -> Option<(BinOp, u8, usize)> {
self.skip_ws();
let c = self.peek()?;
let c2 = self.peek2();
Some(match (c, c2) {
(b'|', _) => (BinOp::Or, 1, 1),
(b'&', _) => (BinOp::And, 2, 1),
(b'=', Some(b'=')) => (BinOp::Eq, 3, 2),
(b'=', _) => (BinOp::Eq, 3, 1),
(b'!', Some(b'=')) => (BinOp::Ne, 3, 2),
(b'<', Some(b'=')) => (BinOp::Le, 3, 2),
(b'<', _) => (BinOp::Lt, 3, 1),
(b'>', Some(b'=')) => (BinOp::Ge, 3, 2),
(b'>', _) => (BinOp::Gt, 3, 1),
(b'+', _) => (BinOp::Add, 4, 1),
(b'-', _) => (BinOp::Sub, 4, 1),
(b'*', _) => (BinOp::Mul, 5, 1),
(b'/', _) => (BinOp::Div, 5, 1),
(b'%', _) => (BinOp::Mod, 5, 1),
(b'^', _) => (BinOp::Pow, 7, 1),
_ => return None,
})
}
fn reduce(em: &mut Emitter, p: Pending) {
match p {
Pending::Unary(op) => em.unary(op),
Pending::Bin {
op: op @ (BinOp::And | BinOp::Or),
jump,
fold_lhs,
..
} => em.finish_logic(op, jump, fold_lhs),
Pending::Bin { op, .. } => em.bin(op),
Pending::Paren | Pending::Call { .. } => unreachable!("not an operator"),
}
}
fn reduce_for(em: &mut Emitter, ops: &mut Vec<Pending>, prec: u8, right_assoc: bool) {
while let Some(top) = ops.last() {
let top_prec = match top {
Pending::Bin { prec, .. } => *prec,
Pending::Unary(_) => UNARY_PREC,
Pending::Paren | Pending::Call { .. } => break,
};
if top_prec > prec || (top_prec == prec && !right_assoc) {
let p = ops.pop().expect("non-empty");
Self::reduce(em, p);
} else {
break;
}
}
}
fn reduce_group(em: &mut Emitter, ops: &mut Vec<Pending>) {
while matches!(ops.last(), Some(Pending::Bin { .. } | Pending::Unary(_))) {
let p = ops.pop().expect("non-empty");
Self::reduce(em, p);
}
}
fn parse(mut self, name: &mut dyn FnMut(&str) -> Result<Op>) -> Result<Vec<Op>> {
let mut em = Emitter {
code: Vec::new(),
barrier: 0,
};
let mut ops: Vec<Pending> = Vec::new();
let mut expect_operand = true;
loop {
self.skip_ws();
if expect_operand {
let start = self.pos;
match self.peek() {
None => return Err(self.err("unexpected end of expression")),
Some(b'(') => {
self.pos += 1;
ops.push(Pending::Paren);
}
Some(b'-') => {
self.pos += 1;
ops.push(Pending::Unary(UnOp::Neg));
}
Some(b'+') => self.pos += 1,
Some(b'!') if self.peek2() != Some(b'=') => {
self.pos += 1;
ops.push(Pending::Unary(UnOp::Not));
}
Some(c) if c.is_ascii_digit() || c == b'.' => {
let v = self.parse_number(start)?;
em.code.push(Op::Num(v));
expect_operand = false;
}
Some(c) if c.is_ascii_alphabetic() || c == b'_' => {
while matches!(self.peek(), Some(c) if c.is_ascii_alphanumeric() || c == b'_')
{
self.pos += 1;
}
let id = &self.src[start..self.pos];
self.skip_ws();
if self.peek() == Some(b'(') {
let Some((f, _)) = Func::lookup(id) else {
self.pos = start;
return Err(self.err(&format!("unknown function {id:?}")));
};
self.pos += 1;
ops.push(Pending::Call {
f,
start,
argc: 0,
jumps: [0; 2],
fold_cond: None,
});
self.skip_ws();
if self.peek() == Some(b')') {
self.pos = start;
let arity = f.arity();
return Err(self.err(&format!(
"function {id:?} expects {arity} argument(s), got 0"
)));
}
} else {
em.code.push(name(id)?);
expect_operand = false;
}
}
Some(_) => return Err(self.err("unexpected character")),
}
continue;
}
if let Some((op, prec, len)) = self.peek_binop() {
self.pos += len;
Self::reduce_for(&mut em, &mut ops, prec, op == BinOp::Pow);
let mut pending = Pending::Bin {
op,
prec,
jump: 0,
fold_lhs: false,
};
if let Pending::Bin { jump, fold_lhs, .. } = &mut pending
&& matches!(op, BinOp::And | BinOp::Or)
{
let n = em.code.len();
*fold_lhs = n >= 1 && em.num_at(n - 1).is_some();
*jump = n;
em.code.push(if op == BinOp::And {
Op::AndJump(0)
} else {
Op::OrJump(0)
});
}
ops.push(pending);
expect_operand = true;
continue;
}
match self.peek() {
None => break,
Some(b',') => {
Self::reduce_group(&mut em, &mut ops);
let Some(Pending::Call {
f,
argc,
jumps,
fold_cond,
..
}) = ops.last_mut()
else {
return Err(self.err("unexpected trailing input"));
};
*argc += 1;
if *argc >= f.arity() {
return Err(self.err("expected ')'"));
}
if *f == Func::If {
let n = em.code.len();
if *argc == 1 {
*fold_cond =
(n >= 1 && em.num_at(n - 1).is_some()).then_some(em.barrier);
jumps[0] = n;
em.code.push(Op::JumpIfFalse(0));
} else {
jumps[1] = n;
em.code.push(Op::Jump(0));
let j = jumps[0];
em.patch(j);
}
}
self.pos += 1;
expect_operand = true;
}
Some(b')') => {
Self::reduce_group(&mut em, &mut ops);
match ops.pop() {
Some(Pending::Paren) => {}
Some(Pending::Call {
f,
start,
argc,
jumps,
fold_cond,
}) => {
let argc = argc + 1;
if argc != f.arity() {
self.pos = start;
return Err(self.err(&format!(
"function expects {} argument(s), got {argc}",
f.arity()
)));
}
if f == Func::If {
Self::finish_if(&mut em, jumps, fold_cond);
} else {
em.call(f);
}
}
_ => return Err(self.err("unexpected ')'")),
}
self.pos += 1;
}
Some(_) => return Err(self.err("unexpected trailing input")),
}
}
Self::reduce_group(&mut em, &mut ops);
if !ops.is_empty() {
return Err(self.err("expected ')'"));
}
Ok(em.code)
}
fn finish_if(em: &mut Emitter, jumps: [usize; 2], fold_cond: Option<usize>) {
let [jz, jmp] = jumps;
let n = em.code.len();
if let Some(barrier) = fold_cond
&& jmp == jz + 2
&& n == jmp + 2
&& let (Some(Op::Num(c)), Some(Op::Num(a)), Some(Op::Num(b))) = (
em.code.get(jz - 1).copied(),
em.code.get(jz + 1).copied(),
em.code.get(jmp + 1).copied(),
)
{
em.code.truncate(jz - 1);
em.code.push(Op::Num(if truthy(c) { a } else { b }));
em.barrier = barrier;
return;
}
em.patch(jmp);
}
fn parse_number(&mut self, start: usize) -> Result<f64> {
let b = self.src.as_bytes();
let mut i = self.pos;
while i < b.len() && b[i].is_ascii_digit() {
i += 1;
}
if i < b.len() && b[i] == b'.' {
i += 1;
while i < b.len() && b[i].is_ascii_digit() {
i += 1;
}
}
if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
let mut j = i + 1;
if j < b.len() && (b[j] == b'+' || b[j] == b'-') {
j += 1;
}
if j < b.len() && b[j].is_ascii_digit() {
while j < b.len() && b[j].is_ascii_digit() {
j += 1;
}
i = j;
}
}
self.pos = i;
self.src[start..i].parse::<f64>().map_err(|_| {
self.pos = start;
self.err("invalid number")
})
}
}
pub fn parse_number(s: &str) -> Option<f64> {
let t = s.trim();
let first = *t.as_bytes().first()?;
if !(first.is_ascii_digit() || matches!(first, b'.' | b'-' | b'+')) {
return None;
}
t.parse::<f64>().ok().filter(|v| v.is_finite())
}
pub fn parse_number_lenient(s: &str) -> Option<f64> {
if let Some(v) = parse_number(s) {
return Some(v);
}
let t = s.trim().to_ascii_lowercase();
let (neg, body) = match t.strip_prefix('-') {
Some(b) => (true, b),
None => (false, t.strip_prefix('+').unwrap_or(&t)),
};
if body.starts_with("nan") || body.contains("#qnan") || body.contains("#ind") {
return Some(f64::NAN);
}
if body == "inf" || body == "infinity" || body.contains("#inf") {
return Some(if neg {
f64::NEG_INFINITY
} else {
f64::INFINITY
});
}
None
}
mod bessel {
use core::f64::consts::{FRAC_PI_4, PI};
use core::f64::consts::EULER_GAMMA;
const SERIES_LIMIT: f64 = 12.0;
fn asymptotic(nu: f64, x: f64) -> (f64, f64) {
let mu = 4.0 * nu * nu;
let (mut p, mut q) = (1.0, 0.0);
let mut term = 1.0f64;
let mut prev = f64::INFINITY;
for k in 1..60 {
let odd = (2 * k - 1) as f64;
term *= (mu - odd * odd) / (k as f64 * 8.0 * x);
if term.abs() >= prev || term.abs() < 1e-17 {
break;
}
prev = term.abs();
let sign = if (k / 2) % 2 == 0 { 1.0 } else { -1.0 };
if k % 2 == 1 {
q += sign * term;
} else {
p += sign * term;
}
}
let chi = x - (nu / 2.0) * PI - FRAC_PI_4;
let (s, c) = chi.sin_cos();
let amp = (2.0 / (PI * x)).sqrt();
(amp * (p * c - q * s), amp * (p * s + q * c))
}
fn series(x: f64) -> (f64, f64, f64, f64) {
let h = x / 2.0;
let h2 = -h * h;
let (mut j0, mut y0s) = (1.0, 0.0);
let (mut j1, mut y1s) = (h, h * (1.0 - 2.0 * EULER_GAMMA));
let mut t0 = 1.0; let mut t1 = h; let mut hk = 0.0; for k in 1..200 {
let kf = k as f64;
t0 *= h2 / (kf * kf);
t1 *= h2 / (kf * (kf + 1.0));
hk += 1.0 / kf;
j0 += t0;
y0s -= hk * t0;
j1 += t1;
y1s += t1 * (2.0 * hk + 1.0 / (kf + 1.0) - 2.0 * EULER_GAMMA);
if t0.abs() < 1e-17 * j0.abs().max(1e-300) && t1.abs() < 1e-17 {
break;
}
}
let ln = h.ln();
let y0 = 2.0 / PI * ((ln + EULER_GAMMA) * j0 + y0s);
let y1 = 2.0 / PI * ln * j1 - 2.0 / (PI * x) - y1s / PI;
(j0, j1, y0, y1)
}
fn j01(x: f64) -> (f64, f64) {
let ax = x.abs();
let (j0, j1) = if ax == 0.0 {
(1.0, 0.0)
} else if ax < SERIES_LIMIT {
let (a, b, _, _) = series(ax);
(a, b)
} else if ax.is_finite() {
(asymptotic(0.0, ax).0, asymptotic(1.0, ax).0)
} else if ax.is_infinite() {
(0.0, 0.0)
} else {
(f64::NAN, f64::NAN)
};
(j0, if x < 0.0 { -j1 } else { j1 })
}
fn y01(x: f64) -> (f64, f64) {
if x.is_nan() || x < 0.0 {
(f64::NAN, f64::NAN)
} else if x == 0.0 {
(f64::NEG_INFINITY, f64::NEG_INFINITY)
} else if x < SERIES_LIMIT {
let (_, _, a, b) = series(x);
(a, b)
} else if x.is_infinite() {
(0.0, 0.0)
} else {
(asymptotic(0.0, x).1, asymptotic(1.0, x).1)
}
}
pub(crate) fn j0(x: f64) -> f64 {
j01(x).0
}
pub(crate) fn j1(x: f64) -> f64 {
j01(x).1
}
pub(crate) fn y0(x: f64) -> f64 {
y01(x).0
}
pub(crate) fn y1(x: f64) -> f64 {
y01(x).1
}
fn order(n: f64) -> Option<i64> {
(n.is_finite() && n.abs() <= 1e6).then(|| n.round() as i64)
}
pub(crate) fn jn(n: f64, x: f64) -> f64 {
let Some(n) = order(n) else { return f64::NAN };
if x.is_nan() {
return f64::NAN;
}
let sign_n = if n < 0 && n % 2 != 0 { -1.0 } else { 1.0 };
let m = n.unsigned_abs();
let sign_x = if x < 0.0 && m % 2 == 1 { -1.0 } else { 1.0 };
let ax = x.abs();
let v = match m {
0 => j0(ax),
1 => j1(ax),
_ if ax == 0.0 || ax.is_infinite() => 0.0,
_ if ax > m as f64 => {
let (mut a, mut b) = (j0(ax), j1(ax));
for k in 1..m {
let c = 2.0 * k as f64 / ax * b - a;
a = b;
b = c;
}
b
}
_ => {
let start = 2 * ((m + 16 + (40.0 * m as f64).sqrt() as u64 + ax as u64) / 2);
let (mut next, mut cur) = (0.0f64, 1e-300f64);
let (mut sum, mut result) = (0.0f64, 0.0f64);
for k in (1..=start).rev() {
let prev = 2.0 * k as f64 / ax * cur - next;
next = cur;
cur = prev; if k - 1 == m {
result = cur;
}
if (k - 1) % 2 == 0 && k - 1 > 0 {
sum += cur;
}
if cur.abs() > 1e250 {
cur *= 1e-250;
next *= 1e-250;
sum *= 1e-250;
result *= 1e-250;
}
}
let norm = cur + 2.0 * sum; result / norm
}
};
sign_n * sign_x * v
}
pub(crate) fn yn(n: f64, x: f64) -> f64 {
let Some(n) = order(n) else { return f64::NAN };
let sign_n = if n < 0 && n % 2 != 0 { -1.0 } else { 1.0 };
let m = n.unsigned_abs();
let v = match m {
0 => y0(x),
1 => y1(x),
_ if x.is_nan() || x < 0.0 => f64::NAN,
_ if x == 0.0 => f64::NEG_INFINITY,
_ if x.is_infinite() => 0.0,
_ => {
let (mut a, mut b) = (y0(x), y1(x));
for k in 1..m {
let c = 2.0 * k as f64 / x * b - a;
a = b;
b = c;
if b.is_infinite() {
break;
}
}
b
}
};
sign_n * v
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() <= tol * b.abs().max(1.0)
}
#[test]
fn reference_values() {
let cases: [(f64, f64, f64, f64, f64); 8] = [
(
0.5,
0.938_469_807_240_813,
0.242_268_457_674_874,
-0.444_518_733_506_707,
-1.471_472_392_670_243,
),
(
1.0,
0.765_197_686_557_967,
0.440_050_585_744_934,
0.088_256_964_215_677,
-0.781_212_821_300_289,
),
(
2.5,
-0.048_383_776_468_198,
0.497_094_102_464_274,
0.498_070_359_615_232,
0.145_918_137_966_786,
),
(
5.0,
-0.177_596_771_314_338,
-0.327_579_137_591_465,
-0.308_517_625_249_034,
0.147_863_143_391_227,
),
(
10.0,
-0.245_935_764_451_348,
0.043_472_746_168_862,
0.055_671_167_283_599,
0.249_015_424_206_954,
),
(
11.9,
0.025_049_441_699_590,
-0.228_983_249_661_924,
-0.229_833_213_943_375,
-0.034_711_498_334_030,
),
(
12.1,
0.069_666_773_606_808,
-0.215_748_973_376_925,
-0.218_438_380_550_925,
-0.078_736_931_451_396,
),
(
50.0,
0.055_812_327_669_252,
-0.097_511_828_125_175,
-0.098_064_995_470_077,
-0.056_795_668_562_015,
),
];
for (x, a, b, c, d) in cases {
assert!(close(j0(x), a, 1e-9), "j0({x}) = {} want {a}", j0(x));
assert!(close(j1(x), b, 1e-9), "j1({x}) = {} want {b}", j1(x));
assert!(close(y0(x), c, 1e-9), "y0({x}) = {} want {c}", y0(x));
assert!(close(y1(x), d, 1e-9), "y1({x}) = {} want {d}", y1(x));
}
assert_eq!(j0(0.0), 1.0);
assert_eq!(j1(0.0), 0.0);
assert!(close(j1(-1.0), -0.440_050_585_744_934, 1e-12));
assert!(y0(-1.0).is_nan());
assert_eq!(y0(0.0), f64::NEG_INFINITY);
}
#[test]
fn integer_orders() {
assert!(close(jn(2.0, 1.0), 0.114_903_484_931_900, 1e-9));
assert!(close(jn(5.0, 1.0), 2.497_577_302_112_344e-4, 1e-9));
assert!(close(jn(3.0, 10.0), 0.058_379_379_305_187, 1e-9));
assert!(close(jn(20.0, 10.0), 1.151_336_924_781_34e-5, 1e-8));
assert!(close(yn(2.0, 1.0), -1.650_682_606_816_254, 1e-9));
assert!(close(yn(3.0, 10.0), -0.251_362_657_183_837, 1e-9));
assert!(close(jn(-3.0, 2.0), -jn(3.0, 2.0), 1e-14));
assert!(close(jn(3.0, -2.0), -jn(3.0, 2.0), 1e-14));
assert!(close(yn(-3.0, 2.0), -yn(3.0, 2.0), 1e-14));
assert_eq!(jn(4.0, 0.0), 0.0);
assert!(jn(f64::NAN, 1.0).is_nan());
assert!(jn(1e300, 1.0).is_nan());
for &x in &[0.3, 3.7, 17.0, 40.0] {
for n in 1..15 {
let n = n as f64;
let l = jn(n - 1.0, x) + jn(n + 1.0, x);
let r = 2.0 * n / x * jn(n, x);
assert!((l - r).abs() < 1e-9, "n={n} x={x}: {l} vs {r}");
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ev(s: &str) -> f64 {
Expr::parse(s).unwrap().eval_const().unwrap()
}
#[test]
fn arithmetic() {
assert_eq!(ev("1+2*3"), 7.0);
assert_eq!(ev("(1+2)*3"), 9.0);
assert_eq!(ev("10-4-3"), 3.0);
assert_eq!(ev("8/4/2"), 1.0);
assert_eq!(ev("2^3^2"), 512.0);
assert_eq!(ev("-2^2"), -4.0);
assert_eq!(ev("2^-1"), 0.5);
assert_eq!(ev("7%4"), 3.0);
assert_eq!(ev(" 1.5e3 + .5 "), 1500.5);
assert_eq!(ev("--3"), 3.0);
assert_eq!(ev("1*2+3*4-5/5"), 13.0);
assert_eq!(ev("2-3+4"), 3.0);
let long = vec!["1"; 5000].join("+");
assert_eq!(ev(&long), 5000.0);
assert_eq!(ev("0 & (1/0) | 2"), 1.0);
assert_eq!(ev("if(0/0, 1, 2)"), 2.0);
assert_eq!(ev("!(0/0)"), 1.0);
}
#[test]
fn logic_and_functions() {
assert_eq!(ev("1<2 & 3>=3"), 1.0);
assert_eq!(ev("1=2 | 0"), 0.0);
assert_eq!(ev("!0"), 1.0);
assert_eq!(ev("1!=2"), 1.0);
assert_eq!(ev("if(1>0, 5, 6)"), 5.0);
assert_eq!(ev("max(3, min(10, 4))"), 4.0);
assert!((ev("sin(pi/2)") - 1.0).abs() < 1e-15);
assert_eq!(ev("atan2(0, 1)"), 0.0);
assert_eq!(ev("sqrt(16)+abs(-1)"), 5.0);
}
#[test]
fn variables() {
let e = Expr::parse("a*x + b").unwrap();
assert_eq!(
e.names().into_iter().collect::<Vec<_>>(),
vec!["a", "b", "x"]
);
let c = e
.compile(&["x"], |n| match n {
"a" => Some(2.0),
"b" => Some(1.0),
_ => None,
})
.unwrap();
assert_eq!(c.eval(&[3.0]), 7.0);
assert!(e.compile(&["x"], |_| None).is_err());
assert_eq!(
Expr::parse("2*pi")
.unwrap()
.compile(&[], |_| None)
.unwrap()
.as_constant(),
Some(core::f64::consts::TAU)
);
assert_eq!(
Expr::parse("e").unwrap().eval_with(|_| Some(3.0)).unwrap(),
3.0
);
}
#[test]
fn precedence_corner_cases() {
assert_eq!(ev("2^-1*3"), 1.5);
assert_eq!(ev("-2^-2"), -0.25);
assert_eq!(ev("2^-1^2"), 0.5);
assert_eq!(ev("!1=0"), 1.0);
assert_eq!(ev("1<2<3"), 1.0);
assert_eq!(ev("3>2>1"), 0.0);
assert_eq!(ev("-3%2"), -1.0);
assert_eq!(ev("1 - -1"), 2.0);
assert_eq!(ev("+-+1"), -1.0);
assert_eq!(ev("1|0&0"), 1.0);
assert_eq!(ev("(1|0)&0"), 0.0);
assert_eq!(ev("2*if(1,3,4)"), 6.0);
assert_eq!(ev("if(0,1,2)+3"), 5.0);
assert_eq!(ev("if(1,if(0,5,6),7)"), 6.0);
assert_eq!(ev("max(if(0,1,2), 1 & 3)"), 2.0);
}
#[test]
fn folding_respects_jumps() {
let cases: &[(&str, f64, f64)] = &[
("if(x,1,2)+3", 0.0, 5.0),
("if(x,1,2)+3", 1.0, 4.0),
("3+if(x,1,2)", 0.0, 5.0),
("-if(x,1,2)", 1.0, -1.0),
("x&3", 2.0, 1.0),
("(x&3)+1", 0.0, 1.0),
("(x|0)*5", 0.0, 0.0),
("(x|0)*5", 4.0, 5.0),
("max(if(x,1,2),3)", 1.0, 3.0),
("2*if(x,3,4)", 0.0, 8.0),
("if(x,1+1,2*3)", 0.0, 6.0),
("if(1,x,2)", 7.0, 7.0),
("if(0,x,2)*x", 7.0, 14.0),
("1 & x | 0", 0.0, 0.0),
("0 & x | 2", 9.0, 1.0),
];
for &(src, x, want) in cases {
let e = Expr::parse(src).unwrap();
assert_eq!(e.eval_with(|_| Some(x)).unwrap(), want, "{src} eval_with");
let c = e.compile(&["x"], |_| None).unwrap();
assert_eq!(c.eval(&[x]), want, "{src} compiled");
let k = e.compile(&[], |_| Some(x)).unwrap();
assert_eq!(k.as_constant(), Some(want), "{src} folded");
}
}
#[test]
fn lazy_names() {
let e = Expr::parse("if(1, 2, missing)").unwrap();
assert_eq!(e.eval_const().unwrap(), 2.0);
assert!(e.compile(&[], |_| None).is_err());
assert_eq!(
Expr::parse("0 & missing").unwrap().eval_const().unwrap(),
0.0
);
assert!(Expr::parse("1 & missing").unwrap().eval_const().is_err());
}
#[test]
fn differential_random() {
let mut seed = 0x1234_5678_9abc_def0u64;
let mut rnd = |n: u64| {
seed = seed
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(seed >> 33) % n
};
let atoms = ["x", "y", "0", "1", "2.5", "pi"];
let bins = ["+", "-", "*", "/", "^", "<", ">=", "=", "!=", "&", "|", "%"];
for _ in 0..3000 {
let mut parts: Vec<String> = Vec::new();
for _ in 0..(2 + rnd(6)) {
let a = atoms[rnd(atoms.len() as u64) as usize].to_string();
let t = match rnd(6) {
0 => format!("-{a}"),
1 => format!("!{a}"),
2 => format!("sin({a})"),
_ => a,
};
parts.push(t);
while parts.len() >= 2 && rnd(3) == 0 {
let b = parts.pop().unwrap();
let a = parts.pop().unwrap();
let op = bins[rnd(bins.len() as u64) as usize];
parts.push(match rnd(4) {
0 => format!("if({a},{b},{a}{op}{b})"),
1 => format!("max({a},{b})"),
_ => format!("({a}{op}{b})"),
});
}
}
let src = parts.join("+");
let e = Expr::parse(&src).unwrap();
let (x, y) = (rnd(5) as f64 - 2.0, rnd(3) as f64 * 0.5);
let look = |n: &str| match n {
"x" => Some(x),
"y" => Some(y),
_ => None,
};
let a = e.eval_with(look).unwrap();
let b = e.compile(&["x", "y"], |_| None).unwrap().eval(&[x, y]);
let c = e.compile(&[], look).unwrap().as_constant().unwrap();
let same = |p: f64, q: f64| p == q || (p.is_nan() && q.is_nan());
assert!(same(a, b) && same(a, c), "{src}: {a} {b} {c}");
}
}
#[test]
fn errors() {
for s in ["", "1+", "(1", "foo(1)", "sin(1,2)", "1 2", "#"] {
assert!(Expr::parse(s).is_err(), "{s}");
}
}
#[test]
fn numbers() {
assert_eq!(parse_number(" -1e-3 "), Some(-1e-3));
assert_eq!(parse_number("a"), None);
assert_eq!(parse_number("inf"), None);
assert!(parse_number_lenient("-nan(ind)").unwrap().is_nan());
assert!(parse_number_lenient("nan").unwrap().is_nan());
assert_eq!(parse_number_lenient("-inf"), Some(f64::NEG_INFINITY));
assert_eq!(parse_number_lenient("x"), None);
}
}