1use crate::base::arena::Arena;
22use crate::base::errors::SymplexError;
23use crate::base::node::{ExprId, ExprNode, SymbolId};
24use crate::output::codegen::numeric_rt as rt;
25use num_traits::{One, ToPrimitive, Zero};
26use rustc_hash::FxHashMap;
27use smallvec::SmallVec;
28use std::fmt;
29use std::ops::Deref;
30use std::sync::Arc;
31
32type SharedFn = Arc<dyn Fn(&[f64]) -> f64 + Send + Sync>;
34
35#[derive(Clone)]
63pub struct CompiledFn {
64 program: Arc<Program>,
65 func: SharedFn,
66}
67
68impl CompiledFn {
69 fn from_program(program: Program) -> Self {
70 let program = Arc::new(program);
71 let p = Arc::clone(&program);
72 let func: SharedFn = Arc::new(move |args: &[f64]| p.run_scalar(args));
73 Self { program, func }
74 }
75
76 #[must_use]
78 pub fn arity(&self) -> usize {
79 self.program.arity
80 }
81
82 #[must_use]
84 pub fn call(&self, args: &[f64]) -> f64 {
85 self.program.run_scalar(args)
86 }
87
88 pub fn try_call(&self, args: &[f64]) -> Result<f64, SymplexError> {
90 self.program.check_arity("CompiledFn::try_call", args)?;
91 Ok(self.program.run_scalar(args))
92 }
93
94 #[must_use]
96 pub fn instruction_count(&self) -> usize {
97 self.program.code.len()
98 }
99}
100
101impl Deref for CompiledFn {
102 type Target = dyn Fn(&[f64]) -> f64 + Send + Sync;
103 fn deref(&self) -> &Self::Target {
104 &*self.func
105 }
106}
107
108impl fmt::Debug for CompiledFn {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 f.debug_struct("CompiledFn")
111 .field("arity", &self.program.arity)
112 .field("instructions", &self.program.code.len())
113 .field("locals", &self.program.n_locals)
114 .finish()
115 }
116}
117
118#[derive(Clone)]
138pub struct CompiledFnVec {
139 program: Arc<Program>,
140}
141
142impl CompiledFnVec {
143 #[must_use]
145 pub fn arity(&self) -> usize {
146 self.program.arity
147 }
148
149 #[must_use]
151 pub fn len(&self) -> usize {
152 self.program.n_outputs
153 }
154
155 #[must_use]
157 pub fn is_empty(&self) -> bool {
158 self.program.n_outputs == 0
159 }
160
161 pub fn call(&self, args: &[f64], out: &mut [f64]) {
166 if args.len() != self.program.arity || out.len() != self.program.n_outputs {
167 out.fill(f64::NAN);
168 return;
169 }
170 self.program.run(args, out);
171 }
172
173 pub fn try_call(&self, args: &[f64], out: &mut [f64]) -> Result<(), SymplexError> {
175 self.program.check_arity("CompiledFnVec::try_call", args)?;
176 if out.len() != self.program.n_outputs {
177 return Err(SymplexError::InvalidArgument {
178 operation: "CompiledFnVec::try_call",
179 reason: format!(
180 "output slice has length {}, expected {}",
181 out.len(),
182 self.program.n_outputs
183 ),
184 });
185 }
186 self.program.run(args, out);
187 Ok(())
188 }
189
190 #[must_use]
193 pub fn call_vec(&self, args: &[f64]) -> Vec<f64> {
194 let mut out = vec![f64::NAN; self.program.n_outputs];
195 self.call(args, &mut out);
196 out
197 }
198
199 #[must_use]
201 pub fn instruction_count(&self) -> usize {
202 self.program.code.len()
203 }
204}
205
206impl fmt::Debug for CompiledFnVec {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 f.debug_struct("CompiledFnVec")
209 .field("arity", &self.program.arity)
210 .field("outputs", &self.program.n_outputs)
211 .field("instructions", &self.program.code.len())
212 .field("locals", &self.program.n_locals)
213 .finish()
214 }
215}
216
217pub(crate) fn compile(
225 arena: &mut Arena,
226 expr: ExprId,
227 var_names: &[&str],
228) -> Result<CompiledFn, SymplexError> {
229 let program = compile_program(arena, &[expr], var_names)?;
230 Ok(CompiledFn::from_program(program))
231}
232
233pub(crate) fn compile_raw(
240 arena: &Arena,
241 expr: ExprId,
242 var_names: &[&str],
243) -> Result<CompiledFn, SymplexError> {
244 check_params(var_names)?;
245 let mut em = Emitter::new(arena, var_names, FxHashMap::default());
246 em.lower(expr)?;
247 em.emit(Instruction::StoreOut(0));
248 let code = em.finish()?;
249 Ok(CompiledFn::from_program(Program {
250 code,
251 arity: var_names.len(),
252 n_locals: 0,
253 n_outputs: 1,
254 }))
255}
256
257pub(crate) fn compile_many(
259 arena: &mut Arena,
260 exprs: &[ExprId],
261 var_names: &[&str],
262) -> Result<CompiledFnVec, SymplexError> {
263 let program = compile_program(arena, exprs, var_names)?;
264 Ok(CompiledFnVec {
265 program: Arc::new(program),
266 })
267}
268
269pub(crate) fn compile_many_empty(var_names: &[&str]) -> Result<CompiledFnVec, SymplexError> {
271 check_params(var_names)?;
272 Ok(CompiledFnVec {
273 program: Arc::new(Program {
274 code: Vec::new(),
275 arity: var_names.len(),
276 n_locals: 0,
277 n_outputs: 0,
278 }),
279 })
280}
281
282fn check_params(var_names: &[&str]) -> Result<(), SymplexError> {
283 for (i, a) in var_names.iter().enumerate() {
284 if var_names[..i].contains(a) {
285 return Err(SymplexError::InvalidArgument {
286 operation: "compile",
287 reason: format!("duplicate parameter name '{a}'"),
288 });
289 }
290 }
291 Ok(())
292}
293
294fn compile_program(
296 arena: &mut Arena,
297 exprs: &[ExprId],
298 var_names: &[&str],
299) -> Result<Program, SymplexError> {
300 check_params(var_names)?;
301
302 let evaled: Vec<ExprId> = exprs
312 .iter()
313 .map(|&e| {
314 if needs_raw_lowering(arena, e) {
315 e
316 } else {
317 crate::transforms::eval::eval(arena, e)
318 }
319 })
320 .collect();
321
322 let cse = crate::output::cse::cse_multi(arena, &evaled);
324
325 let mut locals: FxHashMap<SymbolId, usize> = FxHashMap::default();
326 for (i, (name_id, _)) in cse.bindings.iter().enumerate() {
327 if let ExprNode::Symbol(sid) = arena.node(*name_id) {
328 locals.insert(*sid, i);
329 }
330 }
331
332 let mut em = Emitter::new(arena, var_names, locals);
333 for (i, (_, value)) in cse.bindings.iter().enumerate() {
334 em.lower(*value)?;
335 em.emit(Instruction::StoreLocal(i));
336 }
337 for (i, &e) in cse.exprs.iter().enumerate() {
338 em.lower(e)?;
339 em.emit(Instruction::StoreOut(i));
340 }
341 let code = em.finish()?;
342
343 Ok(Program {
344 code,
345 arity: var_names.len(),
346 n_locals: cse.bindings.len(),
347 n_outputs: exprs.len(),
348 })
349}
350
351struct Program {
357 code: Vec<Instruction>,
358 arity: usize,
359 n_locals: usize,
360 n_outputs: usize,
361}
362
363impl Program {
364 fn check_arity(&self, operation: &'static str, args: &[f64]) -> Result<(), SymplexError> {
365 if args.len() != self.arity {
366 return Err(SymplexError::InvalidArgument {
367 operation,
368 reason: format!("expected {} argument(s), got {}", self.arity, args.len()),
369 });
370 }
371 Ok(())
372 }
373
374 fn run_scalar(&self, args: &[f64]) -> f64 {
375 if args.len() != self.arity {
376 return f64::NAN;
377 }
378 let mut out = [f64::NAN];
379 self.run(args, &mut out);
380 out[0]
381 }
382
383 fn run(&self, args: &[f64], out: &mut [f64]) {
385 let mut stack: SmallVec<[f64; 32]> = SmallVec::new();
386 let mut locals: SmallVec<[f64; 16]> = SmallVec::new();
387 locals.resize(self.n_locals, 0.0);
388 let code = &self.code;
389 let mut pc = 0usize;
390
391 macro_rules! pop {
392 () => {
393 stack.pop().unwrap_or(f64::NAN)
394 };
395 }
396 macro_rules! un {
397 ($f:expr) => {{
398 let a = pop!();
399 stack.push($f(a));
400 }};
401 }
402 macro_rules! bin {
403 ($f:expr) => {{
404 let b = pop!();
405 let a = pop!();
406 stack.push($f(a, b));
407 }};
408 }
409
410 while pc < code.len() {
411 match code[pc] {
412 Instruction::PushConst(v) => stack.push(v),
413 Instruction::PushVar(i) => stack.push(args.get(i).copied().unwrap_or(f64::NAN)),
414 Instruction::LoadLocal(i) => stack.push(locals.get(i).copied().unwrap_or(f64::NAN)),
415 Instruction::StoreLocal(i) => {
416 let v = pop!();
417 if let Some(slot) = locals.get_mut(i) {
418 *slot = v;
419 }
420 }
421 Instruction::StoreOut(i) => {
422 let v = pop!();
423 if let Some(slot) = out.get_mut(i) {
424 *slot = v;
425 }
426 }
427 Instruction::Add => bin!(|a: f64, b: f64| a + b),
428 Instruction::Mul => bin!(|a: f64, b: f64| a * b),
429 Instruction::Div => bin!(|a: f64, b: f64| a / b),
430 Instruction::Neg => un!(|a: f64| -a),
431 Instruction::Pow => bin!(|a: f64, b: f64| a.powf(b)),
432 Instruction::Powi(n) => un!(|a: f64| a.powi(n)),
433 Instruction::Sqrt => un!(|a: f64| a.sqrt()),
434 Instruction::Cbrt => un!(|a: f64| a.cbrt()),
435 Instruction::ExpM1 => un!(|a: f64| a.exp_m1()),
436 Instruction::Ln1p => un!(|a: f64| a.ln_1p()),
437 Instruction::Sin => un!(|a: f64| a.sin()),
438 Instruction::Cos => un!(|a: f64| a.cos()),
439 Instruction::Tan => un!(|a: f64| a.tan()),
440 Instruction::Exp => un!(|a: f64| a.exp()),
441 Instruction::Ln => un!(|a: f64| a.ln()),
442 Instruction::Abs => un!(|a: f64| a.abs()),
443 Instruction::Asin => un!(|a: f64| a.asin()),
444 Instruction::Acos => un!(|a: f64| a.acos()),
445 Instruction::Atan => un!(|a: f64| a.atan()),
446 Instruction::Sinh => un!(|a: f64| a.sinh()),
447 Instruction::Cosh => un!(|a: f64| a.cosh()),
448 Instruction::Tanh => un!(|a: f64| a.tanh()),
449 Instruction::Asinh => un!(|a: f64| a.asinh()),
450 Instruction::Acosh => un!(|a: f64| a.acosh()),
451 Instruction::Atanh => un!(|a: f64| a.atanh()),
452 Instruction::Sign => un!(|a: f64| if a > 0.0 {
453 1.0
454 } else if a < 0.0 {
455 -1.0
456 } else {
457 0.0
458 }),
459 Instruction::Heaviside => un!(|a: f64| if a > 0.0 {
460 1.0
461 } else if a < 0.0 {
462 0.0
463 } else {
464 0.5
465 }),
466 Instruction::DiracDelta => un!(|a: f64| if a.is_nan() { f64::NAN } else { 0.0 }),
468 Instruction::Atan2 => bin!(|y: f64, x: f64| y.atan2(x)),
469 Instruction::Floor => un!(|a: f64| a.floor()),
470 Instruction::Ceiling => un!(|a: f64| a.ceil()),
471 Instruction::Min2 => bin!(|a: f64, b: f64| a.min(b)),
472 Instruction::Max2 => bin!(|a: f64, b: f64| a.max(b)),
473 Instruction::Gamma => un!(rt::gamma),
474 Instruction::LogGamma => un!(rt::lgamma),
475 Instruction::Digamma => un!(rt::digamma),
476 Instruction::Erf => un!(rt::erf),
477 Instruction::Erfc => un!(rt::erfc),
478 Instruction::LambertW => un!(rt::lambert_w0),
479 Instruction::Factorial => un!(rt::factorial),
480 Instruction::Binomial => bin!(rt::binomial),
481 Instruction::Beta => bin!(rt::beta),
482 Instruction::BesselJ(n) => un!(|a: f64| rt::bessel_j(n, a)),
483 Instruction::BesselY(n) => un!(|a: f64| rt::bessel_y(n, a)),
484 Instruction::BesselI(n) => un!(|a: f64| rt::bessel_i(n, a)),
485 Instruction::BesselK(n) => un!(|a: f64| rt::bessel_k(n, a)),
486 Instruction::LegendreP(n) => un!(|a: f64| rt::legendre_p(n, a)),
487 Instruction::ChebyshevT(n) => un!(|a: f64| rt::chebyshev_t(n, a)),
488 Instruction::ChebyshevU(n) => un!(|a: f64| rt::chebyshev_u(n, a)),
489 Instruction::HermiteH(n) => un!(|a: f64| rt::hermite_h(n, a)),
490 Instruction::LaguerreL(n) => un!(|a: f64| rt::laguerre_l(n, a)),
491 Instruction::Fibonacci => un!(rt::fibonacci),
492 Instruction::Lucas => un!(rt::lucas),
493 Instruction::Harmonic => un!(rt::harmonic),
494 Instruction::Factorial2 => un!(rt::factorial2),
495 Instruction::RisingFactorial => bin!(rt::rising_factorial),
496 Instruction::FallingFactorial => bin!(rt::falling_factorial),
497 Instruction::Gt => bin!(|a: f64, b: f64| bool_f64(a > b)),
498 Instruction::Ge => bin!(|a: f64, b: f64| bool_f64(a >= b)),
499 Instruction::Eq => bin!(|a: f64, b: f64| bool_f64(a == b)),
500 Instruction::Ne => bin!(|a: f64, b: f64| bool_f64(a != b)),
501 Instruction::And => bin!(|a: f64, b: f64| bool_f64(a != 0.0 && b != 0.0)),
502 Instruction::Or => bin!(|a: f64, b: f64| bool_f64(a != 0.0 || b != 0.0)),
503 Instruction::Not => un!(|a: f64| bool_f64(a == 0.0)),
504 Instruction::JumpIfZero(target) => {
505 let c = pop!();
506 if c == 0.0 || c.is_nan() {
507 pc = target;
508 continue;
509 }
510 }
511 Instruction::Jump(target) => {
512 pc = target;
513 continue;
514 }
515 }
516 pc += 1;
517 }
518 }
519}
520
521#[inline(always)]
522fn bool_f64(b: bool) -> f64 {
523 if b { 1.0 } else { 0.0 }
524}
525
526#[derive(Clone, Copy, Debug, PartialEq)]
528enum Instruction {
529 PushConst(f64),
530 PushVar(usize),
532 LoadLocal(usize),
534 StoreLocal(usize),
536 StoreOut(usize),
538 Add,
539 Mul,
540 Div,
542 Neg,
543 Pow,
545 Powi(i32),
546 Sqrt,
547 Cbrt,
548 ExpM1,
549 Ln1p,
550 Sin,
551 Cos,
552 Tan,
553 Exp,
554 Ln,
555 Abs,
556 Asin,
557 Acos,
558 Atan,
559 Sinh,
560 Cosh,
561 Tanh,
562 Asinh,
563 Acosh,
564 Atanh,
565 Sign,
566 Heaviside,
567 DiracDelta,
568 Atan2,
570 Floor,
571 Ceiling,
572 Min2,
573 Max2,
574 Gamma,
575 LogGamma,
576 Digamma,
577 Erf,
578 Erfc,
579 LambertW,
580 Factorial,
581 Binomial,
583 Beta,
585 BesselJ(i32),
586 BesselY(i32),
587 BesselI(i32),
588 BesselK(i32),
589 LegendreP(i32),
590 ChebyshevT(i32),
591 ChebyshevU(i32),
592 HermiteH(i32),
593 LaguerreL(i32),
594 Fibonacci,
595 Lucas,
596 Harmonic,
597 Factorial2,
598 RisingFactorial,
599 FallingFactorial,
600 Gt,
601 Ge,
602 Eq,
603 Ne,
604 And,
605 Or,
606 Not,
607 JumpIfZero(usize),
609 Jump(usize),
610}
611
612#[derive(Clone, Copy)]
618enum Task {
619 Node(ExprId),
621 Emit(Instruction),
623 Label(usize),
625 JumpIfZero(usize),
627 Jump(usize),
629 Bool(ExprId),
631}
632
633struct Emitter<'a> {
634 arena: &'a Arena,
635 vars: FxHashMap<&'a str, usize>,
636 locals: FxHashMap<SymbolId, usize>,
637 code: Vec<Instruction>,
638 labels: Vec<Option<usize>>,
639 fixups: Vec<(usize, usize)>,
640 barrier: usize,
643 work: Vec<Task>,
644}
645
646impl<'a> Emitter<'a> {
647 fn new(arena: &'a Arena, var_names: &[&'a str], locals: FxHashMap<SymbolId, usize>) -> Self {
648 let vars = var_names.iter().enumerate().map(|(i, &n)| (n, i)).collect();
649 Self {
650 arena,
651 vars,
652 locals,
653 code: Vec::new(),
654 labels: Vec::new(),
655 fixups: Vec::new(),
656 barrier: 0,
657 work: Vec::new(),
658 }
659 }
660
661 fn new_label(&mut self) -> usize {
662 self.labels.push(None);
663 self.labels.len() - 1
664 }
665
666 fn emit(&mut self, inst: Instruction) {
667 if inst == Instruction::Mul
669 && self.code.len() > self.barrier
670 && self.code.last() == Some(&Instruction::Powi(-1))
671 {
672 self.code.pop();
673 self.code.push(Instruction::Div);
674 return;
675 }
676 self.code.push(inst);
677 }
678
679 fn push_seq(&mut self, tasks: &[Task]) {
681 for t in tasks.iter().rev() {
682 self.work.push(*t);
683 }
684 }
685
686 fn lower(&mut self, root: ExprId) -> Result<(), SymplexError> {
688 self.work.push(Task::Node(root));
689 while let Some(task) = self.work.pop() {
690 match task {
691 Task::Emit(inst) => self.emit(inst),
692 Task::Label(l) => {
693 self.labels[l] = Some(self.code.len());
694 self.barrier = self.code.len();
695 }
696 Task::JumpIfZero(l) => {
697 self.fixups.push((self.code.len(), l));
698 self.code.push(Instruction::JumpIfZero(usize::MAX));
699 }
700 Task::Jump(l) => {
701 self.fixups.push((self.code.len(), l));
702 self.code.push(Instruction::Jump(usize::MAX));
703 }
704 Task::Node(id) => self.lower_node(id)?,
705 Task::Bool(id) => self.lower_bool(id)?,
706 }
707 }
708 Ok(())
709 }
710
711 fn finish(mut self) -> Result<Vec<Instruction>, SymplexError> {
712 for (pos, label) in self.fixups.drain(..) {
713 let target = self.labels[label].ok_or_else(|| SymplexError::ComputationFailed {
714 operation: "compile",
715 reason: "internal error: unbound jump label".to_string(),
716 })?;
717 match &mut self.code[pos] {
718 Instruction::JumpIfZero(t) | Instruction::Jump(t) => *t = target,
719 _ => {}
720 }
721 }
722 Ok(self.code)
723 }
724
725 fn unsupported(&self, what: &str) -> SymplexError {
726 SymplexError::NotImplemented(format!("cannot compile `{what}` to a numerical function"))
727 }
728
729 fn const_i32(&self, id: ExprId, what: &str) -> Result<i32, SymplexError> {
732 if let Some(r) = self.arena.as_num(id)
733 && r.is_integer()
734 && let Some(n) = r.to_integer().to_i32()
735 {
736 return Ok(n);
737 }
738 if let ExprNode::Neg(inner) = self.arena.node(id)
739 && let Some(r) = self.arena.as_num(*inner)
740 && r.is_integer()
741 && let Some(n) = r.to_integer().to_i32()
742 {
743 return Ok(-n);
744 }
745 Err(SymplexError::NotImplemented(format!(
746 "{what} requires a constant integer order/degree, got `{}`",
747 self.arena.display(id)
748 )))
749 }
750
751 fn unary(&mut self, child: ExprId, inst: Instruction) {
752 self.push_seq(&[Task::Node(child), Task::Emit(inst)]);
753 }
754
755 fn binary(&mut self, a: ExprId, b: ExprId, inst: Instruction) {
756 self.push_seq(&[Task::Node(a), Task::Node(b), Task::Emit(inst)]);
757 }
758
759 fn nary(&mut self, children: &[ExprId], inst: Instruction, empty: f64) {
761 if children.is_empty() {
762 self.emit(Instruction::PushConst(empty));
763 return;
764 }
765 let mut tasks: Vec<Task> = Vec::with_capacity(children.len() * 2);
766 tasks.push(Task::Node(children[0]));
767 for &c in &children[1..] {
768 tasks.push(Task::Node(c));
769 tasks.push(Task::Emit(inst));
770 }
771 self.push_seq(&tasks);
772 }
773
774 fn lower_node(&mut self, id: ExprId) -> Result<(), SymplexError> {
775 let arena = self.arena;
776 match arena.node(id).clone() {
777 ExprNode::Num(nid) => {
778 let r = arena.num(nid);
779 let n = r.numer().to_f64().unwrap_or(f64::NAN);
780 let d = r.denom().to_f64().unwrap_or(f64::NAN);
781 self.emit(Instruction::PushConst(n / d));
782 }
783 ExprNode::Symbol(sid) => {
784 if let Some(&slot) = self.locals.get(&sid) {
785 self.emit(Instruction::LoadLocal(slot));
786 } else {
787 let name = arena.symbol_name(sid);
788 match self.vars.get(name) {
789 Some(&idx) => self.emit(Instruction::PushVar(idx)),
790 None => {
791 return Err(SymplexError::FreeSymbol {
792 name: name.to_string(),
793 });
794 }
795 }
796 }
797 }
798 ExprNode::Pi => self.emit(Instruction::PushConst(std::f64::consts::PI)),
799 ExprNode::E => self.emit(Instruction::PushConst(std::f64::consts::E)),
800 ExprNode::EulerGamma => self.emit(Instruction::PushConst(
801 crate::output::codegen::numeric_rt::EULER_GAMMA_F64,
802 )),
803 ExprNode::Catalan => self.emit(Instruction::PushConst(
804 crate::output::codegen::numeric_rt::CATALAN_F64,
805 )),
806 ExprNode::GoldenRatio => self.emit(Instruction::PushConst(
807 crate::output::codegen::numeric_rt::GOLDEN_RATIO_F64,
808 )),
809 ExprNode::PhysicalConstant(_, value_id) => self.work.push(Task::Node(value_id)),
810 ExprNode::Infinity => self.emit(Instruction::PushConst(f64::INFINITY)),
811 ExprNode::NegInfinity => self.emit(Instruction::PushConst(f64::NEG_INFINITY)),
812 ExprNode::NaN | ExprNode::ComplexInfinity => {
813 self.emit(Instruction::PushConst(f64::NAN))
814 }
815 ExprNode::ImaginaryUnit => return Err(self.unsupported("ImaginaryUnit")),
818 ExprNode::Re(_) => return Err(self.unsupported("re")),
819 ExprNode::Im(_) => return Err(self.unsupported("im")),
820 ExprNode::Conjugate(_) => return Err(self.unsupported("conjugate")),
821 ExprNode::Arg(_) => return Err(self.unsupported("arg")),
822 ExprNode::Si(_) => return Err(self.unsupported("Si")),
823 ExprNode::Ci(_) => return Err(self.unsupported("Ci")),
824 ExprNode::Ei(_) => return Err(self.unsupported("Ei")),
825 ExprNode::Li(_) => return Err(self.unsupported("li")),
826 ExprNode::Zeta(_) => return Err(self.unsupported("zeta")),
827 ExprNode::Polygamma(_, _) => return Err(self.unsupported("polygamma")),
828 ExprNode::KroneckerDelta(_, _) => return Err(self.unsupported("KroneckerDelta")),
829
830 ExprNode::Add(ref children) => {
831 if children.len() >= 2 {
833 let exp_idx = children
834 .iter()
835 .position(|&c| matches!(arena.node(c), ExprNode::Exp(_)));
836 let neg_one_idx = children.iter().position(|&c| is_neg_one(arena, c));
837 if let (Some(ei), Some(ni)) = (exp_idx, neg_one_idx)
838 && ei != ni
839 && let ExprNode::Exp(inner) = arena.node(children[ei]).clone()
840 {
841 let mut tasks = vec![Task::Node(inner), Task::Emit(Instruction::ExpM1)];
842 for (i, &c) in children.iter().enumerate() {
843 if i != ei && i != ni {
844 tasks.push(Task::Node(c));
845 tasks.push(Task::Emit(Instruction::Add));
846 }
847 }
848 self.push_seq(&tasks);
849 return Ok(());
850 }
851 }
852 self.nary(children, Instruction::Add, 0.0);
853 }
854 ExprNode::Mul(ref children) => self.nary(children, Instruction::Mul, 1.0),
855 ExprNode::Min(ref children) => self.nary(children, Instruction::Min2, f64::INFINITY),
856 ExprNode::Max(ref children) => {
857 self.nary(children, Instruction::Max2, f64::NEG_INFINITY)
858 }
859 ExprNode::Pow(base, exp) => {
860 if let Some(r) = arena.as_num(exp) {
861 if r.is_integer()
862 && let Some(n) = r.to_integer().to_i32()
863 {
864 self.unary(base, Instruction::Powi(n));
865 return Ok(());
866 }
867 let one: num_bigint::BigInt = One::one();
868 if *r.numer() == one {
869 if *r.denom() == num_bigint::BigInt::from(2) {
870 self.unary(base, Instruction::Sqrt);
871 return Ok(());
872 }
873 if *r.denom() == num_bigint::BigInt::from(3) {
874 self.unary(base, Instruction::Cbrt);
875 return Ok(());
876 }
877 }
878 let two = num_bigint::BigInt::from(2);
881 if (r.denom() % &two) != Zero::zero() {
882 let odd_numer = (r.numer() % &two) != Zero::zero();
883 if odd_numer {
884 self.push_seq(&[
885 Task::Node(base),
886 Task::Emit(Instruction::Sign),
887 Task::Node(base),
888 Task::Emit(Instruction::Abs),
889 Task::Node(exp),
890 Task::Emit(Instruction::Pow),
891 Task::Emit(Instruction::Mul),
892 ]);
893 } else {
894 self.push_seq(&[
895 Task::Node(base),
896 Task::Emit(Instruction::Abs),
897 Task::Node(exp),
898 Task::Emit(Instruction::Pow),
899 ]);
900 }
901 return Ok(());
902 }
903 }
904 self.binary(base, exp, Instruction::Pow);
905 }
906 ExprNode::Neg(x) => self.unary(x, Instruction::Neg),
907 ExprNode::Floor(x) => self.unary(x, Instruction::Floor),
908 ExprNode::Ceiling(x) => self.unary(x, Instruction::Ceiling),
909 ExprNode::Sin(x) => self.unary(x, Instruction::Sin),
910 ExprNode::Cos(x) => self.unary(x, Instruction::Cos),
911 ExprNode::Tan(x) => self.unary(x, Instruction::Tan),
912 ExprNode::Exp(x) => self.unary(x, Instruction::Exp),
913 ExprNode::Ln(x) => {
914 if let ExprNode::Add(ref ch) = arena.node(x).clone()
916 && ch.len() == 2
917 {
918 if is_one(arena, ch[0]) {
919 self.unary(ch[1], Instruction::Ln1p);
920 return Ok(());
921 }
922 if is_one(arena, ch[1]) {
923 self.unary(ch[0], Instruction::Ln1p);
924 return Ok(());
925 }
926 }
927 self.unary(x, Instruction::Ln);
928 }
929 ExprNode::Abs(x) => self.unary(x, Instruction::Abs),
930 ExprNode::Asin(x) => self.unary(x, Instruction::Asin),
931 ExprNode::Acos(x) => self.unary(x, Instruction::Acos),
932 ExprNode::Atan(x) => self.unary(x, Instruction::Atan),
933 ExprNode::Atan2(y, x) => self.binary(y, x, Instruction::Atan2),
934 ExprNode::Sinh(x) => self.unary(x, Instruction::Sinh),
935 ExprNode::Cosh(x) => self.unary(x, Instruction::Cosh),
936 ExprNode::Tanh(x) => self.unary(x, Instruction::Tanh),
937 ExprNode::Asinh(x) => self.unary(x, Instruction::Asinh),
938 ExprNode::Acosh(x) => self.unary(x, Instruction::Acosh),
939 ExprNode::Atanh(x) => self.unary(x, Instruction::Atanh),
940 ExprNode::Sign(x) => self.unary(x, Instruction::Sign),
941 ExprNode::Heaviside(x) => self.unary(x, Instruction::Heaviside),
942 ExprNode::DiracDelta(x) => self.unary(x, Instruction::DiracDelta),
943
944 ExprNode::Gamma(x) => self.unary(x, Instruction::Gamma),
945 ExprNode::LogGamma(x) => self.unary(x, Instruction::LogGamma),
946 ExprNode::Digamma(x) => self.unary(x, Instruction::Digamma),
947 ExprNode::Erf(x) => self.unary(x, Instruction::Erf),
948 ExprNode::Erfc(x) => self.unary(x, Instruction::Erfc),
949 ExprNode::LambertW(x) => self.unary(x, Instruction::LambertW),
950 ExprNode::Beta(a, b) => self.binary(a, b, Instruction::Beta),
951 ExprNode::Factorial(x) => self.unary(x, Instruction::Factorial),
952 ExprNode::Binomial(n, k) => self.binary(n, k, Instruction::Binomial),
953
954 ExprNode::Piecewise(ref branches) => {
955 let end = self.new_label();
956 let mut tasks: Vec<Task> = Vec::with_capacity(branches.len() * 5 + 2);
957 for &(value, cond) in branches.iter() {
958 let next = self.new_label();
959 tasks.push(Task::Bool(cond));
960 tasks.push(Task::JumpIfZero(next));
961 tasks.push(Task::Node(value));
962 tasks.push(Task::Jump(end));
963 tasks.push(Task::Label(next));
964 }
965 tasks.push(Task::Emit(Instruction::PushConst(f64::NAN)));
966 tasks.push(Task::Label(end));
967 self.push_seq(&tasks);
968 }
969
970 ExprNode::BoolTrue
971 | ExprNode::BoolFalse
972 | ExprNode::Gt(_, _)
973 | ExprNode::Ge(_, _)
974 | ExprNode::Eq_(_, _)
975 | ExprNode::Ne(_, _)
976 | ExprNode::And(_)
977 | ExprNode::Or(_)
978 | ExprNode::Not(_) => self.work.push(Task::Bool(id)),
979
980 ExprNode::Apply(sid, ref args) => {
981 let name = arena.symbol_name(sid).to_string();
982 self.lower_apply(&name, args)?;
983 }
984
985 ExprNode::Derivative(_, _) => return Err(self.unsupported("Derivative")),
986 ExprNode::Integral(_, _) => return Err(self.unsupported("Integral")),
987 ExprNode::DefiniteIntegral(_, _, _, _) => {
988 return Err(self.unsupported("DefiniteIntegral"));
989 }
990 ExprNode::Sum(_, _, _, _) => return Err(self.unsupported("Sum")),
991 ExprNode::Product_(_, _, _, _) => return Err(self.unsupported("Product")),
992 ExprNode::Limit(_, _, _) => return Err(self.unsupported("Limit")),
993 ExprNode::Series(_, _, _, _) => return Err(self.unsupported("Series")),
994 ExprNode::LaplaceTransform(_, _, _) => {
995 return Err(self.unsupported("LaplaceTransform"));
996 }
997 ExprNode::InverseLaplaceTransform(_, _, _) => {
998 return Err(self.unsupported("InverseLaplaceTransform"));
999 }
1000 ExprNode::Residue(_, _, _) => return Err(self.unsupported("Residue")),
1001 ExprNode::RootOf(_, _) => return Err(self.unsupported("RootOf")),
1002 ExprNode::RootSum(_, _, _) => return Err(self.unsupported("RootSum")),
1003 ExprNode::DSolve(_, _, _) => return Err(self.unsupported("DSolve")),
1004 ExprNode::ConditionSet(_, _) => return Err(self.unsupported("ConditionSet")),
1005 ExprNode::EmptySet => return Err(self.unsupported("EmptySet")),
1006 ExprNode::UniversalSet => return Err(self.unsupported("UniversalSet")),
1007 ExprNode::Interval(_, _, _) => return Err(self.unsupported("Interval")),
1008 ExprNode::FiniteSet(_) => return Err(self.unsupported("FiniteSet")),
1009 ExprNode::SetUnion(_) => return Err(self.unsupported("SetUnion")),
1010 ExprNode::SetIntersection(_) => return Err(self.unsupported("SetIntersection")),
1011 ExprNode::SetComplement(_, _) => return Err(self.unsupported("SetComplement")),
1012 }
1013 Ok(())
1014 }
1015
1016 fn lower_bool(&mut self, id: ExprId) -> Result<(), SymplexError> {
1018 let arena = self.arena;
1019 match arena.node(id).clone() {
1020 ExprNode::BoolTrue => self.emit(Instruction::PushConst(1.0)),
1021 ExprNode::BoolFalse => self.emit(Instruction::PushConst(0.0)),
1022 ExprNode::Gt(a, b) => self.binary(a, b, Instruction::Gt),
1023 ExprNode::Ge(a, b) => self.binary(a, b, Instruction::Ge),
1024 ExprNode::Eq_(a, b) => self.binary(a, b, Instruction::Eq),
1025 ExprNode::Ne(a, b) => self.binary(a, b, Instruction::Ne),
1026 ExprNode::And(ref ch) => self.nary_bool(ch, Instruction::And, 1.0),
1027 ExprNode::Or(ref ch) => self.nary_bool(ch, Instruction::Or, 0.0),
1028 ExprNode::Not(x) => self.push_seq(&[Task::Bool(x), Task::Emit(Instruction::Not)]),
1029 _ => self.work.push(Task::Node(id)),
1031 }
1032 Ok(())
1033 }
1034
1035 fn nary_bool(&mut self, children: &[ExprId], inst: Instruction, empty: f64) {
1036 if children.is_empty() {
1037 self.emit(Instruction::PushConst(empty));
1038 return;
1039 }
1040 let mut tasks: Vec<Task> = Vec::with_capacity(children.len() * 2);
1041 tasks.push(Task::Bool(children[0]));
1042 for &c in &children[1..] {
1043 tasks.push(Task::Bool(c));
1044 tasks.push(Task::Emit(inst));
1045 }
1046 self.push_seq(&tasks);
1047 }
1048
1049 fn lower_apply(&mut self, name: &str, args: &[ExprId]) -> Result<(), SymplexError> {
1052 use crate::base::arena as names;
1053 let arity_err = |n: usize| {
1054 SymplexError::NotImplemented(format!(
1055 "cannot compile `{name}` with {} argument(s) (expected {n})",
1056 args.len()
1057 ))
1058 };
1059 let ordered: Option<fn(i32) -> Instruction> = match name {
1061 n if n == names::FN_BESSELJ => Some(Instruction::BesselJ),
1062 n if n == names::FN_BESSELY => Some(Instruction::BesselY),
1063 n if n == names::FN_BESSELI => Some(Instruction::BesselI),
1064 n if n == names::FN_BESSELK => Some(Instruction::BesselK),
1065 n if n == names::FN_LEGENDRE => Some(Instruction::LegendreP),
1066 n if n == names::FN_CHEBYSHEV_T => Some(Instruction::ChebyshevT),
1067 n if n == names::FN_CHEBYSHEV_U => Some(Instruction::ChebyshevU),
1068 n if n == names::FN_HERMITE => Some(Instruction::HermiteH),
1069 n if n == names::FN_LAGUERRE => Some(Instruction::LaguerreL),
1070 _ => None,
1071 };
1072 if let Some(make) = ordered {
1073 if args.len() != 2 {
1074 return Err(arity_err(2));
1075 }
1076 let order = self.const_i32(args[0], name)?;
1077 self.unary(args[1], make(order));
1078 return Ok(());
1079 }
1080 let unary: Option<Instruction> = match name {
1081 n if n == names::FN_FIBONACCI => Some(Instruction::Fibonacci),
1082 n if n == names::FN_LUCAS => Some(Instruction::Lucas),
1083 n if n == names::FN_HARMONIC => Some(Instruction::Harmonic),
1084 n if n == names::FN_FACTORIAL2 => Some(Instruction::Factorial2),
1085 _ => None,
1086 };
1087 if let Some(inst) = unary {
1088 if args.len() != 1 {
1089 return Err(arity_err(1));
1090 }
1091 self.unary(args[0], inst);
1092 return Ok(());
1093 }
1094 let binary: Option<Instruction> = match name {
1095 n if n == names::FN_RISING_FACTORIAL => Some(Instruction::RisingFactorial),
1096 n if n == names::FN_FALLING_FACTORIAL => Some(Instruction::FallingFactorial),
1097 _ => None,
1098 };
1099 if let Some(inst) = binary {
1100 if args.len() != 2 {
1101 return Err(arity_err(2));
1102 }
1103 self.binary(args[0], args[1], inst);
1104 return Ok(());
1105 }
1106 Err(SymplexError::NotImplemented(format!(
1107 "cannot compile `Apply({name}, …)` to a numerical function"
1108 )))
1109 }
1110}
1111
1112fn needs_raw_lowering(arena: &Arena, root: ExprId) -> bool {
1115 use crate::base::arena as names;
1116 let mut stack = vec![root];
1117 let mut seen = rustc_hash::FxHashSet::default();
1118 while let Some(id) = stack.pop() {
1119 if !seen.insert(id) {
1120 continue;
1121 }
1122 let node = arena.node(id);
1123 match node {
1124 ExprNode::Piecewise(_) => return true,
1125 ExprNode::Apply(sid, _) => {
1126 let name = arena.symbol_name(*sid);
1127 if name == names::FN_LEGENDRE
1128 || name == names::FN_CHEBYSHEV_T
1129 || name == names::FN_CHEBYSHEV_U
1130 || name == names::FN_HERMITE
1131 || name == names::FN_LAGUERRE
1132 {
1133 return true;
1134 }
1135 }
1136 _ => {}
1137 }
1138 node.for_each_child(|c| stack.push(c));
1139 }
1140 false
1141}
1142
1143fn is_neg_one(arena: &Arena, id: ExprId) -> bool {
1145 if let Some(r) = arena.as_num(id) {
1146 return *r == num_rational::Ratio::from_integer((-1).into());
1147 }
1148 if let ExprNode::Neg(inner) = arena.node(id)
1149 && let Some(r) = arena.as_num(*inner)
1150 {
1151 return r.is_one();
1152 }
1153 false
1154}
1155
1156fn is_one(arena: &Arena, id: ExprId) -> bool {
1158 arena.as_num(id).is_some_and(|r| r.is_one())
1159}
1160
1161#[cfg(test)]
1162#[allow(clippy::excessive_precision)]
1163mod tests {
1164 use super::*;
1165
1166 fn close(a: f64, b: f64, tol: f64) -> bool {
1167 (a - b).abs() <= tol * (1.0 + b.abs())
1168 }
1169
1170 #[test]
1171 fn lambdify_polynomial() {
1172 let mut a = Arena::new();
1173 let x = a.symbol("x");
1174 let two = a.int(2);
1175 let three = a.int(3);
1176 let x2 = a.pow(x, two);
1177 let three_x = a.mul(&[three, x]);
1178 let one = a.int(1);
1179 let expr = a.add(&[x2, three_x, one]);
1180 let f = compile(&mut a, expr, &["x"]).unwrap();
1182 assert!((f(&[2.0]) - 11.0).abs() < 1e-10); assert!((f(&[0.0]) - 1.0).abs() < 1e-10);
1184 assert!((f(&[-1.0]) - (-1.0)).abs() < 1e-10); assert_eq!(f.arity(), 1);
1186 }
1187
1188 #[test]
1189 fn lambdify_trig() {
1190 let mut a = Arena::new();
1191 let x = a.symbol("x");
1192 let expr = a.sin(x);
1193 let f = compile(&mut a, expr, &["x"]).unwrap();
1194 assert!((f(&[0.0]) - 0.0).abs() < 1e-10);
1195 assert!((f(&[std::f64::consts::FRAC_PI_2]) - 1.0).abs() < 1e-10);
1196 }
1197
1198 #[test]
1199 fn lambdify_two_vars() {
1200 let mut a = Arena::new();
1201 let x = a.symbol("x");
1202 let y = a.symbol("y");
1203 let xy = a.mul(&[x, y]);
1204 let one = a.int(1);
1205 let expr = a.add(&[xy, one]);
1206 let f = compile(&mut a, expr, &["x", "y"]).unwrap();
1207 assert!((f(&[3.0, 4.0]) - 13.0).abs() < 1e-10);
1208 }
1209
1210 #[test]
1211 fn lambdify_exp_sin() {
1212 let mut a = Arena::new();
1213 let x = a.symbol("x");
1214 let sin_x = a.sin(x);
1215 let expr = a.exp(sin_x);
1216 let f = compile(&mut a, expr, &["x"]).unwrap();
1217 let expected = 0.5f64.sin().exp();
1218 assert!((f(&[0.5]) - expected).abs() < 1e-14);
1219 }
1220
1221 #[test]
1222 fn lambdify_pi_e() {
1223 let mut a = Arena::new();
1224 let pi = a.pi;
1225 let f = compile(&mut a, pi, &[]).unwrap();
1226 assert!((f(&[]) - std::f64::consts::PI).abs() < 1e-10);
1227 let e = a.e_const;
1228 let f2 = compile(&mut a, e, &[]).unwrap();
1229 assert!((f2(&[]) - std::f64::consts::E).abs() < 1e-10);
1230 }
1231
1232 #[test]
1233 fn lambdify_complex_fails() {
1234 let mut a = Arena::new();
1235 let i = a.i_unit;
1236 let result = compile(&mut a, i, &[]);
1237 assert!(matches!(result, Err(SymplexError::NotImplemented(_))));
1238 }
1239
1240 #[test]
1241 fn lambdify_free_symbol_is_error() {
1242 let mut a = Arena::new();
1243 let x = a.symbol("x");
1244 let y = a.symbol("y");
1245 let expr = a.add(&[x, y]);
1246 match compile(&mut a, expr, &["x"]) {
1247 Err(SymplexError::FreeSymbol { name }) => assert_eq!(name, "y"),
1248 other => panic!("expected FreeSymbol, got {other:?}"),
1249 }
1250 }
1251
1252 #[test]
1253 fn arity_mismatch_is_nan_or_error() {
1254 let mut a = Arena::new();
1255 let x = a.symbol("x");
1256 let f = compile(&mut a, x, &["x"]).unwrap();
1257 assert!(f.call(&[]).is_nan());
1258 assert!(f(&[1.0, 2.0]).is_nan());
1259 assert!(f.try_call(&[]).is_err());
1260 assert_eq!(f.try_call(&[7.0]).unwrap(), 7.0);
1261 }
1262
1263 #[test]
1264 fn duplicate_param_is_error() {
1265 let mut a = Arena::new();
1266 let x = a.symbol("x");
1267 assert!(matches!(
1268 compile(&mut a, x, &["x", "x"]),
1269 Err(SymplexError::InvalidArgument { .. })
1270 ));
1271 }
1272
1273 #[test]
1274 fn division_peephole() {
1275 let mut a = Arena::new();
1276 let x = a.symbol("x");
1277 let y = a.symbol("y");
1278 let m1 = a.int(-1);
1279 let inv_y = a.pow(y, m1);
1280 let expr = a.mul(&[x, inv_y]);
1281 let f = compile(&mut a, expr, &["x", "y"]).unwrap();
1282 assert_eq!(f(&[1.0, 3.0]), 1.0 / 3.0);
1283 }
1284
1285 #[test]
1286 fn piecewise_and_booleans() {
1287 let mut a = Arena::new();
1288 let x = a.symbol("x");
1289 let zero = a.int(0);
1290 let one = a.int(1);
1291 let two = a.int(2);
1292 let gt = a.gt(x, zero);
1293 let neg_x = a.neg(x);
1294 let eq1 = a.eq_(x, one);
1295 let t = a.bool_true;
1296 let pw = a.piecewise(&[(two, eq1), (x, gt), (neg_x, t)]);
1298 let f = compile(&mut a, pw, &["x"]).unwrap();
1299 assert_eq!(f(&[1.0]), 2.0);
1300 assert_eq!(f(&[3.0]), 3.0);
1301 assert_eq!(f(&[-4.0]), 4.0);
1302 let pw2 = a.piecewise(&[(x, gt)]);
1304 let g = compile(&mut a, pw2, &["x"]).unwrap();
1305 assert_eq!(g(&[2.0]), 2.0);
1306 assert!(g(&[-2.0]).is_nan());
1307 let five = a.int(5);
1309 let lt5 = a.gt(five, x);
1310 let both = a.and(&[gt, lt5]);
1311 let not_both = a.not(both);
1312 let pw3 = a.piecewise(&[(one, not_both), (zero, t)]);
1313 let h = compile(&mut a, pw3, &["x"]).unwrap();
1314 assert_eq!(h(&[3.0]), 0.0);
1315 assert_eq!(h(&[7.0]), 1.0);
1316 assert_eq!(h(&[-1.0]), 1.0);
1317 }
1318
1319 #[test]
1320 fn special_functions_compile() {
1321 let mut a = Arena::new();
1322 let x = a.symbol("x");
1323 let g = a.gamma(x);
1324 let f = compile(&mut a, g, &["x"]).unwrap();
1325 assert!(close(f(&[0.5]), std::f64::consts::PI.sqrt(), 1e-15));
1326 let e = a.erf(x);
1327 let f = compile(&mut a, e, &["x"]).unwrap();
1328 assert!(close(f(&[1.0]), 0.84270079294971486934, 1e-15));
1329 let w = a.lambertw(x);
1330 let f = compile(&mut a, w, &["x"]).unwrap();
1331 assert!(close(f(&[1.0]), 0.56714329040978387300, 1e-15));
1332 let two = a.int(2);
1333 let j2 = a.besselj(two, x);
1334 let f = compile(&mut a, j2, &["x"]).unwrap();
1335 assert!(close(f(&[10.0]), 0.25463031368512062253, 1e-14));
1336 let sym_order = a.symbol("n");
1337 let jn = a.besselj(sym_order, x);
1338 assert!(matches!(
1339 compile(&mut a, jn, &["x", "n"]),
1340 Err(SymplexError::NotImplemented(_))
1341 ));
1342 let fac = a.factorial(x);
1343 let f = compile(&mut a, fac, &["x"]).unwrap();
1344 assert_eq!(f(&[5.0]), 120.0);
1345 let fib = a.fibonacci(x);
1346 let f = compile(&mut a, fib, &["x"]).unwrap();
1347 assert_eq!(f(&[20.0]), 6765.0);
1348 }
1349
1350 #[test]
1351 fn compile_many_shares_cse() {
1352 let mut a = Arena::new();
1353 let x = a.symbol("x");
1354 let s = a.sin(x);
1355 let c = a.cos(x);
1356 let two = a.int(2);
1357 let s2 = a.pow(s, two);
1358 let e1 = a.add(&[s2, c]);
1359 let e2 = a.mul(&[s, c]);
1360 let f = compile_many(&mut a, &[e1, e2], &["x"]).unwrap();
1361 assert_eq!(f.len(), 2);
1362 assert_eq!(f.arity(), 1);
1363 let out = f.call_vec(&[0.3]);
1364 let (s, c) = (0.3f64.sin(), 0.3f64.cos());
1365 assert!(close(out[0], s * s + c, 1e-15));
1366 assert!(close(out[1], s * c, 1e-15));
1367 let mut buf = [0.0; 1];
1368 f.call(&[0.3], &mut buf);
1369 assert!(buf[0].is_nan());
1370 assert!(f.try_call(&[0.3], &mut buf).is_err());
1371 let empty = compile_many(&mut a, &[], &["x"]).unwrap();
1372 assert!(empty.is_empty());
1373 assert_eq!(empty.call_vec(&[1.0]).len(), 0);
1374 let empty2 = compile_many_empty(&["x", "y"]).unwrap();
1375 assert_eq!(empty2.arity(), 2);
1376 assert!(empty2.is_empty());
1377 }
1378
1379 #[test]
1380 fn deep_expression_does_not_overflow_stack() {
1381 let mut a = Arena::new();
1382 let x = a.symbol("x");
1383 let mut e = x;
1384 for _ in 0..20_000 {
1385 e = a.sin(e);
1386 }
1387 let f = compile(&mut a, e, &["x"]).unwrap();
1388 assert!(f(&[1.0]).is_finite());
1389 }
1390}