1use crate::expr::Expression;
11use crate::profile::UserConstant;
12use crate::symbol::{NumType, Seft, Symbol};
13use crate::udf::{UdfOp, UserFunction};
14
15#[derive(Debug, Clone, Copy)]
17pub struct EvalResult {
18 pub value: f64,
20 pub derivative: f64,
22 pub num_type: NumType,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
31pub enum EvalError {
32 #[error("Stack underflow: not enough operands on stack")]
34 StackUnderflow,
35 #[error("Missing user constant: slot u{0} is not configured")]
37 MissingUserConstant(usize),
38 #[error("Division by zero: divisor was zero or near-zero")]
40 DivisionByZero,
41 #[error("Logarithm domain error: argument was non-positive")]
43 LogDomain,
44 #[error("Square root domain error: argument was negative")]
46 SqrtDomain,
47 #[error("Overflow: result is infinite or NaN")]
49 Overflow,
50 #[error("Invalid expression: malformed or incomplete")]
52 Invalid,
53 #[error("{err} at position {pos}")]
55 WithPosition {
56 #[source]
57 err: Box<EvalError>,
58 pos: usize,
59 },
60 #[error("{err} (value: {val})")]
62 WithValue {
63 #[source]
64 err: Box<EvalError>,
65 val: ordered_float::OrderedFloat<f64>,
66 },
67 #[error("{err} in expression '{expr}'")]
69 WithExpression {
70 #[source]
71 err: Box<EvalError>,
72 expr: String,
73 },
74}
75
76impl EvalError {
77 pub fn with_context(self, position: Option<usize>, value: Option<f64>) -> Self {
79 let mut err = self;
80 if let Some(pos) = position {
81 err = EvalError::WithPosition {
82 err: Box::new(err),
83 pos,
84 };
85 }
86 if let Some(val) = value {
87 err = EvalError::WithValue {
88 err: Box::new(err),
89 val: ordered_float::OrderedFloat(val),
90 };
91 }
92 err
93 }
94
95 pub fn with_expression(self, expr: String) -> Self {
97 EvalError::WithExpression {
98 err: Box::new(self),
99 expr,
100 }
101 }
102}
103
104pub mod constants {
106 pub const PI: f64 = std::f64::consts::PI;
107 pub const E: f64 = std::f64::consts::E;
108 pub const PHI: f64 = 1.618_033_988_749_895; pub const GAMMA: f64 = 0.577_215_664_901_532_9;
111 pub const PLASTIC: f64 = 1.324_717_957_244_746;
113 pub const APERY: f64 = 1.202_056_903_159_594_2;
115 pub const CATALAN: f64 = 0.915_965_594_177_219;
117}
118
119pub const DEFAULT_TRIG_ARGUMENT_SCALE: f64 = std::f64::consts::PI;
123
124const TRIG_EXACT_ARGUMENT_TOLERANCE: f64 = 1e-12;
127
128#[derive(Clone, Copy, Debug)]
133pub struct EvalContext<'a> {
134 pub trig_argument_scale: f64,
136 pub user_constants: &'a [UserConstant],
138 pub user_functions: &'a [UserFunction],
140}
141
142impl Default for EvalContext<'static> {
143 fn default() -> Self {
144 Self {
145 trig_argument_scale: DEFAULT_TRIG_ARGUMENT_SCALE,
146 user_constants: &[],
147 user_functions: &[],
148 }
149 }
150}
151
152impl EvalContext<'static> {
153 pub fn new() -> Self {
155 Self::default()
156 }
157}
158
159impl<'a> EvalContext<'a> {
160 pub fn from_slices(
162 user_constants: &'a [UserConstant],
163 user_functions: &'a [UserFunction],
164 ) -> Self {
165 Self {
166 trig_argument_scale: DEFAULT_TRIG_ARGUMENT_SCALE,
167 user_constants,
168 user_functions,
169 }
170 }
171
172 pub fn with_trig_argument_scale(mut self, scale: f64) -> Self {
174 if scale.is_finite() && scale != 0.0 {
175 self.trig_argument_scale = scale;
176 }
177 self
178 }
179}
180
181#[derive(Debug, Clone, Copy)]
183struct StackEntry {
184 val: f64,
185 deriv: f64,
186 num_type: NumType,
187}
188
189impl StackEntry {
190 fn new(val: f64, deriv: f64, num_type: NumType) -> Self {
191 Self {
192 val,
193 deriv,
194 num_type,
195 }
196 }
197
198 fn constant(val: f64, num_type: NumType) -> Self {
199 Self {
200 val,
201 deriv: 0.0,
202 num_type,
203 }
204 }
205}
206
207pub struct EvalWorkspace {
227 stack: Vec<StackEntry>,
228}
229
230impl EvalWorkspace {
231 pub fn new() -> Self {
235 Self {
236 stack: Vec::with_capacity(32),
237 }
238 }
239
240 #[inline]
242 fn clear(&mut self) {
243 self.stack.clear();
244 }
245}
246
247impl Default for EvalWorkspace {
248 fn default() -> Self {
249 Self::new()
250 }
251}
252
253#[inline]
261pub fn evaluate_with_workspace(
262 expr: &Expression,
263 x: f64,
264 workspace: &mut EvalWorkspace,
265) -> Result<EvalResult, EvalError> {
266 evaluate_with_workspace_and_context(expr, x, workspace, &EvalContext::new())
267}
268
269#[inline]
277pub fn evaluate_with_workspace_and_constants(
278 expr: &Expression,
279 x: f64,
280 workspace: &mut EvalWorkspace,
281 user_constants: &[UserConstant],
282) -> Result<EvalResult, EvalError> {
283 let context = EvalContext::from_slices(user_constants, &[]);
284 evaluate_with_workspace_and_context(expr, x, workspace, &context)
285}
286
287#[inline]
293pub fn evaluate_with_workspace_and_constants_and_functions(
294 expr: &Expression,
295 x: f64,
296 workspace: &mut EvalWorkspace,
297 user_constants: &[UserConstant],
298 user_functions: &[UserFunction],
299) -> Result<EvalResult, EvalError> {
300 let context = EvalContext::from_slices(user_constants, user_functions);
301 evaluate_with_workspace_and_context(expr, x, workspace, &context)
302}
303
304#[inline]
309pub fn evaluate_with_workspace_and_context(
310 expr: &Expression,
311 x: f64,
312 workspace: &mut EvalWorkspace,
313 context: &EvalContext<'_>,
314) -> Result<EvalResult, EvalError> {
315 workspace.clear();
316 let stack = &mut workspace.stack;
317
318 for &sym in expr.symbols() {
319 match sym.seft() {
320 Seft::A => {
321 let entry = eval_constant_with_user(sym, x, context.user_constants)?;
322 stack.push(entry);
323 }
324 Seft::B => {
325 if matches!(
327 sym,
328 Symbol::UserFunction0
329 | Symbol::UserFunction1
330 | Symbol::UserFunction2
331 | Symbol::UserFunction3
332 | Symbol::UserFunction4
333 | Symbol::UserFunction5
334 | Symbol::UserFunction6
335 | Symbol::UserFunction7
336 | Symbol::UserFunction8
337 | Symbol::UserFunction9
338 | Symbol::UserFunction10
339 | Symbol::UserFunction11
340 | Symbol::UserFunction12
341 | Symbol::UserFunction13
342 | Symbol::UserFunction14
343 | Symbol::UserFunction15
344 ) {
345 let a = stack.pop().ok_or(EvalError::StackUnderflow)?;
347 let result = eval_user_function(sym, a, context, x)?;
348 stack.push(result);
349 } else {
350 let a = stack.pop().ok_or(EvalError::StackUnderflow)?;
351 let result = eval_unary(sym, a, context.trig_argument_scale)?;
352 stack.push(result);
353 }
354 }
355 Seft::C => {
356 let b = stack.pop().ok_or(EvalError::StackUnderflow)?;
357 let a = stack.pop().ok_or(EvalError::StackUnderflow)?;
358 let result = eval_binary(sym, a, b)?;
359 stack.push(result);
360 }
361 }
362 }
363
364 if stack.len() != 1 {
365 return Err(EvalError::Invalid);
366 }
367
368 let result = stack.pop().unwrap();
370
371 if result.val.is_nan() || result.val.is_infinite() {
373 return Err(EvalError::Overflow);
374 }
375
376 Ok(EvalResult {
377 value: result.val,
378 derivative: result.deriv,
379 num_type: result.num_type,
380 })
381}
382
383pub fn evaluate(expr: &Expression, x: f64) -> Result<EvalResult, EvalError> {
391 evaluate_with_context(expr, x, &EvalContext::new())
392}
393
394pub fn evaluate_with_constants(
398 expr: &Expression,
399 x: f64,
400 user_constants: &[UserConstant],
401) -> Result<EvalResult, EvalError> {
402 let context = EvalContext::from_slices(user_constants, &[]);
403 evaluate_with_context(expr, x, &context)
404}
405
406pub fn evaluate_with_constants_and_functions(
410 expr: &Expression,
411 x: f64,
412 user_constants: &[UserConstant],
413 user_functions: &[UserFunction],
414) -> Result<EvalResult, EvalError> {
415 let context = EvalContext::from_slices(user_constants, user_functions);
416 evaluate_with_context(expr, x, &context)
417}
418
419pub fn evaluate_with_context(
423 expr: &Expression,
424 x: f64,
425 context: &EvalContext<'_>,
426) -> Result<EvalResult, EvalError> {
427 let mut workspace = EvalWorkspace::new();
428 evaluate_with_workspace_and_context(expr, x, &mut workspace, context)
429}
430
431#[inline]
440pub fn evaluate_fast(expr: &Expression, x: f64) -> Result<EvalResult, EvalError> {
441 evaluate_fast_with_context(expr, x, &EvalContext::new())
442}
443
444#[inline]
452pub fn evaluate_fast_with_constants(
453 expr: &Expression,
454 x: f64,
455 user_constants: &[UserConstant],
456) -> Result<EvalResult, EvalError> {
457 let context = EvalContext::from_slices(user_constants, &[]);
458 evaluate_fast_with_context(expr, x, &context)
459}
460
461#[inline]
496pub fn evaluate_fast_with_constants_and_functions(
497 expr: &Expression,
498 x: f64,
499 user_constants: &[UserConstant],
500 user_functions: &[UserFunction],
501) -> Result<EvalResult, EvalError> {
502 let context = EvalContext::from_slices(user_constants, user_functions);
503 evaluate_fast_with_context(expr, x, &context)
504}
505
506#[inline]
508pub fn evaluate_fast_with_context(
509 expr: &Expression,
510 x: f64,
511 context: &EvalContext<'_>,
512) -> Result<EvalResult, EvalError> {
513 thread_local! {
514 static WORKSPACE: std::cell::RefCell<EvalWorkspace> = std::cell::RefCell::new(EvalWorkspace::new());
520 }
521
522 WORKSPACE.with(|ws| {
523 let mut workspace = ws.borrow_mut();
524 evaluate_with_workspace_and_context(expr, x, &mut workspace, context)
525 })
526}
527
528fn eval_constant_with_user(
530 sym: Symbol,
531 x: f64,
532 user_constants: &[UserConstant],
533) -> Result<StackEntry, EvalError> {
534 use Symbol::*;
535 match sym {
536 One => Ok(StackEntry::constant(1.0, NumType::Integer)),
537 Two => Ok(StackEntry::constant(2.0, NumType::Integer)),
538 Three => Ok(StackEntry::constant(3.0, NumType::Integer)),
539 Four => Ok(StackEntry::constant(4.0, NumType::Integer)),
540 Five => Ok(StackEntry::constant(5.0, NumType::Integer)),
541 Six => Ok(StackEntry::constant(6.0, NumType::Integer)),
542 Seven => Ok(StackEntry::constant(7.0, NumType::Integer)),
543 Eight => Ok(StackEntry::constant(8.0, NumType::Integer)),
544 Nine => Ok(StackEntry::constant(9.0, NumType::Integer)),
545 Pi => Ok(StackEntry::constant(constants::PI, NumType::Transcendental)),
546 E => Ok(StackEntry::constant(constants::E, NumType::Transcendental)),
547 Phi => Ok(StackEntry::constant(constants::PHI, NumType::Algebraic)),
548 Gamma => Ok(StackEntry::constant(
550 constants::GAMMA,
551 NumType::Transcendental,
552 )),
553 Plastic => Ok(StackEntry::constant(constants::PLASTIC, NumType::Algebraic)),
554 Apery => Ok(StackEntry::constant(
555 constants::APERY,
556 NumType::Transcendental,
557 )),
558 Catalan => Ok(StackEntry::constant(
559 constants::CATALAN,
560 NumType::Transcendental,
561 )),
562 X => Ok(StackEntry::new(x, 1.0, NumType::Integer)), UserConstant0 | UserConstant1 | UserConstant2 | UserConstant3 | UserConstant4
565 | UserConstant5 | UserConstant6 | UserConstant7 | UserConstant8 | UserConstant9
566 | UserConstant10 | UserConstant11 | UserConstant12 | UserConstant13 | UserConstant14
567 | UserConstant15 => {
568 let idx = sym.user_constant_index().unwrap() as usize;
570 user_constants
571 .get(idx)
572 .map(|uc| StackEntry::constant(uc.value, uc.num_type))
573 .ok_or(EvalError::MissingUserConstant(idx))
574 }
575 _ => Err(EvalError::Invalid),
576 }
577}
578
579fn eval_user_function(
584 sym: Symbol,
585 input: StackEntry,
586 context: &EvalContext<'_>,
587 x: f64,
588) -> Result<StackEntry, EvalError> {
589 let idx = sym.user_function_index().ok_or(EvalError::Invalid)? as usize;
591
592 let udf = context.user_functions.get(idx).ok_or(EvalError::Invalid)?;
594
595 thread_local! {
600 static UDF_STACK: std::cell::RefCell<Vec<StackEntry>> =
601 std::cell::RefCell::new(Vec::with_capacity(16));
602 }
603
604 UDF_STACK.with(|cell| -> Result<StackEntry, EvalError> {
605 let mut stack = cell.borrow_mut();
606 stack.clear();
607 stack.push(input);
608
609 for op in &udf.body {
611 match op {
612 UdfOp::Symbol(sym) => {
613 match sym.seft() {
614 Seft::A => {
615 let entry = eval_constant_with_user(*sym, x, context.user_constants)?;
617 stack.push(entry);
618 }
619 Seft::B => {
620 let a = stack.pop().ok_or(EvalError::StackUnderflow)?;
622 let result = eval_unary(*sym, a, context.trig_argument_scale)?;
623 stack.push(result);
624 }
625 Seft::C => {
626 let b = stack.pop().ok_or(EvalError::StackUnderflow)?;
628 let a = stack.pop().ok_or(EvalError::StackUnderflow)?;
629 let result = eval_binary(*sym, a, b)?;
630 stack.push(result);
631 }
632 }
633 }
634 UdfOp::Dup => {
635 let top = *stack.last().ok_or(EvalError::StackUnderflow)?;
638 stack.push(top);
639 }
640 UdfOp::Swap => {
641 let len = stack.len();
643 if len < 2 {
644 return Err(EvalError::StackUnderflow);
645 }
646 stack.swap(len - 1, len - 2);
647 }
648 }
649 }
650
651 if stack.len() != 1 {
653 return Err(EvalError::Invalid);
654 }
655
656 let result = stack.pop().unwrap();
658
659 if result.val.is_nan() || result.val.is_infinite() {
661 return Err(EvalError::Overflow);
662 }
663
664 Ok(result)
665 })
666}
667
668fn eval_unary(
670 sym: Symbol,
671 a: StackEntry,
672 trig_argument_scale: f64,
673) -> Result<StackEntry, EvalError> {
674 use Symbol::*;
675
676 #[inline]
677 fn is_default_trig_scale(scale: f64) -> bool {
678 (scale - DEFAULT_TRIG_ARGUMENT_SCALE).abs() <= f64::EPSILON
679 }
680
681 #[inline]
682 fn rounded_f64_to_i64(rounded: f64) -> Option<i64> {
683 if !rounded.is_finite() {
684 return None;
685 }
686 if rounded < i64::MIN as f64 || rounded > i64::MAX as f64 {
687 return None;
688 }
689 Some(rounded as i64)
690 }
691
692 #[inline]
693 fn snap_integer(value: f64) -> Option<i64> {
694 let rounded = value.round();
695 if (value - rounded).abs() <= TRIG_EXACT_ARGUMENT_TOLERANCE {
696 rounded_f64_to_i64(rounded)
697 } else {
698 None
699 }
700 }
701
702 #[inline]
703 fn snap_half_integer_index(value: f64) -> Option<i64> {
704 let doubled = value * 2.0;
705 let rounded = doubled.round();
706 if (doubled - rounded).abs() > TRIG_EXACT_ARGUMENT_TOLERANCE {
707 return None;
708 }
709 let rounded_i = rounded_f64_to_i64(rounded)?;
710 if rounded_i.rem_euclid(2) == 0 {
711 return None;
712 }
713 Some((rounded_i - 1) / 2)
714 }
715
716 #[inline]
717 fn alternating_sign(n: i64) -> f64 {
718 if n.rem_euclid(2) == 0 {
719 1.0
720 } else {
721 -1.0
722 }
723 }
724
725 let (val, deriv, num_type) = match sym {
726 Neg => (-a.val, -a.deriv, a.num_type),
728
729 Recip => {
731 if a.val.abs() < f64::MIN_POSITIVE {
732 return Err(EvalError::DivisionByZero);
733 }
734 let val = 1.0 / a.val;
735 let deriv = -a.deriv / (a.val * a.val);
736 let num_type = if a.num_type == NumType::Integer {
737 NumType::Rational
738 } else {
739 a.num_type
740 };
741 (val, deriv, num_type)
742 }
743
744 Sqrt => {
746 if a.val < 0.0 {
747 return Err(EvalError::SqrtDomain);
748 }
749 let val = a.val.sqrt();
750 let deriv = if val.abs() > f64::MIN_POSITIVE {
751 a.deriv / (2.0 * val)
752 } else {
753 0.0
754 };
755 let num_type = if a.num_type >= NumType::Constructible {
756 NumType::Constructible
757 } else {
758 a.num_type
759 };
760 (val, deriv, num_type)
761 }
762
763 Square => {
765 let val = a.val * a.val;
766 let deriv = 2.0 * a.val * a.deriv;
767 (val, deriv, a.num_type)
768 }
769
770 Ln => {
772 if a.val <= 0.0 {
773 return Err(EvalError::LogDomain);
774 }
775 let val = a.val.ln();
776 let deriv = a.deriv / a.val;
777 (val, deriv, NumType::Transcendental)
778 }
779
780 Exp => {
782 let val = a.val.exp();
783 if val.is_infinite() {
784 return Err(EvalError::Overflow);
785 }
786 let deriv = val * a.deriv;
787 (val, deriv, NumType::Transcendental)
788 }
789
790 SinPi => {
792 if is_default_trig_scale(trig_argument_scale) {
793 if let Some(n) = snap_integer(a.val) {
794 (
795 0.0,
796 trig_argument_scale * alternating_sign(n) * a.deriv,
797 NumType::Transcendental,
798 )
799 } else if let Some(n) = snap_half_integer_index(a.val) {
800 (alternating_sign(n), 0.0, NumType::Transcendental)
801 } else {
802 let val = (trig_argument_scale * a.val).sin();
803 let deriv = trig_argument_scale * (trig_argument_scale * a.val).cos() * a.deriv;
804 (val, deriv, NumType::Transcendental)
805 }
806 } else {
807 let val = (trig_argument_scale * a.val).sin();
808 let deriv = trig_argument_scale * (trig_argument_scale * a.val).cos() * a.deriv;
809 (val, deriv, NumType::Transcendental)
810 }
811 }
812
813 CosPi => {
815 if is_default_trig_scale(trig_argument_scale) {
816 if let Some(n) = snap_integer(a.val) {
817 (alternating_sign(n), 0.0, NumType::Transcendental)
818 } else if let Some(n) = snap_half_integer_index(a.val) {
819 (
820 0.0,
821 -trig_argument_scale * alternating_sign(n) * a.deriv,
822 NumType::Transcendental,
823 )
824 } else {
825 let val = (trig_argument_scale * a.val).cos();
826 let deriv =
827 -trig_argument_scale * (trig_argument_scale * a.val).sin() * a.deriv;
828 (val, deriv, NumType::Transcendental)
829 }
830 } else {
831 let val = (trig_argument_scale * a.val).cos();
832 let deriv = -trig_argument_scale * (trig_argument_scale * a.val).sin() * a.deriv;
833 (val, deriv, NumType::Transcendental)
834 }
835 }
836
837 TanPi => {
839 if is_default_trig_scale(trig_argument_scale) {
840 if snap_half_integer_index(a.val).is_some() {
841 return Err(EvalError::Overflow);
842 }
843 if snap_integer(a.val).is_some() {
844 (0.0, trig_argument_scale * a.deriv, NumType::Transcendental)
845 } else {
846 let cos_val = (trig_argument_scale * a.val).cos();
847 if cos_val.abs() < 1e-10 {
848 return Err(EvalError::Overflow);
849 }
850 let val = (trig_argument_scale * a.val).tan();
851 let deriv = trig_argument_scale * a.deriv / (cos_val * cos_val);
852 (val, deriv, NumType::Transcendental)
853 }
854 } else {
855 let cos_val = (trig_argument_scale * a.val).cos();
856 if cos_val.abs() < 1e-10 {
857 return Err(EvalError::Overflow);
858 }
859 let val = (trig_argument_scale * a.val).tan();
860 let deriv = trig_argument_scale * a.deriv / (cos_val * cos_val);
861 (val, deriv, NumType::Transcendental)
862 }
863 }
864
865 LambertW => {
867 let val = lambert_w(a.val)?;
868 let deriv = if a.val.abs() < 1e-10 {
871 a.deriv } else {
873 let denom = a.val * (1.0 + val);
874 if denom.abs() > f64::MIN_POSITIVE {
875 val / denom * a.deriv
876 } else {
877 0.0
878 }
879 };
880 (val, deriv, NumType::Transcendental)
881 }
882
883 UserFunction0 | UserFunction1 | UserFunction2 | UserFunction3 | UserFunction4
886 | UserFunction5 | UserFunction6 | UserFunction7 | UserFunction8 | UserFunction9
887 | UserFunction10 | UserFunction11 | UserFunction12 | UserFunction13 | UserFunction14
888 | UserFunction15 => {
889 return Err(EvalError::Invalid);
892 }
893
894 _ => return Err(EvalError::Invalid),
896 };
897
898 Ok(StackEntry::new(val, deriv, num_type))
899}
900
901fn eval_binary(sym: Symbol, a: StackEntry, b: StackEntry) -> Result<StackEntry, EvalError> {
903 use Symbol::*;
904
905 let (val, deriv, num_type) = match sym {
906 Add => {
908 let val = a.val + b.val;
909 let deriv = a.deriv + b.deriv;
910 let num_type = a.num_type.combine(b.num_type);
911 (val, deriv, num_type)
912 }
913
914 Sub => {
916 let val = a.val - b.val;
917 let deriv = a.deriv - b.deriv;
918 let num_type = a.num_type.combine(b.num_type);
919 (val, deriv, num_type)
920 }
921
922 Mul => {
924 let val = a.val * b.val;
925 let deriv = a.val * b.deriv + b.val * a.deriv;
926 let num_type = a.num_type.combine(b.num_type);
927 (val, deriv, num_type)
928 }
929
930 Div => {
932 if b.val.abs() < f64::MIN_POSITIVE {
933 return Err(EvalError::DivisionByZero);
934 }
935 let val = a.val / b.val;
936 let deriv = (b.val * a.deriv - a.val * b.deriv) / (b.val * b.val);
937 let mut num_type = a.num_type.combine(b.num_type);
938 if num_type == NumType::Integer {
939 num_type = NumType::Rational;
940 }
941 (val, deriv, num_type)
942 }
943
944 Pow => {
946 if a.val <= 0.0 && b.val.fract() != 0.0 {
947 return Err(EvalError::SqrtDomain);
948 }
949 let val = a.val.powf(b.val);
950 if val.is_infinite() || val.is_nan() {
951 return Err(EvalError::Overflow);
952 }
953 let deriv = if a.val > f64::MIN_POSITIVE {
955 val * (b.val * a.deriv / a.val + a.val.ln() * b.deriv)
956 } else if a.val.abs() < f64::MIN_POSITIVE && b.val > 0.0 {
957 0.0
958 } else {
959 if a.val.abs() < f64::MIN_POSITIVE {
967 0.0
968 } else {
969 val * b.val * a.deriv / a.val
970 }
971 };
972 let num_type = if b.num_type == NumType::Integer {
973 a.num_type
974 } else {
975 NumType::Transcendental
976 };
977 (val, deriv, num_type)
978 }
979
980 Root => {
982 if a.val.abs() < f64::MIN_POSITIVE {
983 return Err(EvalError::DivisionByZero);
984 }
985 let exp = 1.0 / a.val;
986
987 if b.val < 0.0 {
990 let rounded = a.val.round();
992 let is_integer = (a.val - rounded).abs() < 1e-10;
993
994 if !is_integer {
995 return Err(EvalError::SqrtDomain);
997 }
998
999 let int_val = rounded as i64;
1001 if int_val % 2 == 0 {
1002 return Err(EvalError::SqrtDomain);
1004 }
1005 }
1007
1008 let val = if b.val < 0.0 {
1009 -((-b.val).powf(exp))
1011 } else {
1012 b.val.powf(exp)
1013 };
1014 if val.is_infinite() || val.is_nan() {
1015 return Err(EvalError::Overflow);
1016 }
1017 let deriv = if b.val.abs() > f64::MIN_POSITIVE {
1019 val * (b.deriv / (a.val * b.val) - b.val.abs().ln() * a.deriv / (a.val * a.val))
1020 } else {
1021 0.0
1022 };
1023 (val, deriv, NumType::Algebraic)
1024 }
1025
1026 Log => {
1028 if a.val <= 0.0 || a.val == 1.0 || b.val <= 0.0 {
1029 return Err(EvalError::LogDomain);
1030 }
1031 let ln_a = a.val.ln();
1032 let ln_b = b.val.ln();
1033 let val = ln_b / ln_a;
1034 let deriv = b.deriv / (b.val * ln_a) - ln_b * a.deriv / (a.val * ln_a * ln_a);
1036 (val, deriv, NumType::Transcendental)
1037 }
1038
1039 Atan2 => {
1041 let val = a.val.atan2(b.val);
1042 let denom = a.val * a.val + b.val * b.val;
1044 let deriv = if denom.abs() > f64::MIN_POSITIVE {
1045 (b.val * a.deriv - a.val * b.deriv) / denom
1046 } else {
1047 0.0
1048 };
1049 (val, deriv, NumType::Transcendental)
1050 }
1051
1052 _ => return Err(EvalError::Invalid),
1054 };
1055
1056 Ok(StackEntry::new(val, deriv, num_type))
1057}
1058
1059fn lambert_w(x: f64) -> Result<f64, EvalError> {
1064 const INV_E: f64 = 1.0 / std::f64::consts::E;
1066 const NEG_INV_E: f64 = -INV_E; if x < NEG_INV_E {
1070 return Err(EvalError::LogDomain);
1071 }
1072
1073 if x == 0.0 {
1075 return Ok(0.0); }
1077 if (x - NEG_INV_E).abs() < 1e-15 {
1078 return Ok(-1.0); }
1080 if x == constants::E {
1081 return Ok(1.0); }
1083
1084 let mut w = if x < -0.3 {
1086 let p = (2.0 * (constants::E * x + 1.0)).sqrt();
1089 -1.0 + p * (1.0 - p / 3.0 * (1.0 - 11.0 * p / 72.0))
1090 } else if x < 0.25 {
1091 let x2 = x * x;
1095 x * (1.0 - x + x2 * (1.5 - 2.6667 * x))
1096 } else if x < 4.0 {
1097 let lnx = x.ln();
1100 if lnx > 0.0 {
1101 let lnlnx = lnx.ln().max(0.0);
1102 lnx - lnlnx + lnlnx / lnx.max(1.0)
1103 } else {
1104 x }
1106 } else {
1107 let l1 = x.ln();
1109 let l2 = l1.ln();
1110 l1 - l2 + l2 / l1
1111 };
1112
1113 for _ in 0..25 {
1116 let ew = w.exp();
1117
1118 if !ew.is_finite() {
1120 w = x.ln() - w.ln().max(1e-10);
1122 continue;
1123 }
1124
1125 let wew = w * ew;
1126 let diff = wew - x;
1127
1128 let tol = 1e-15 * (1.0 + w.abs().max(x.abs()));
1130 if diff.abs() < tol {
1131 break;
1132 }
1133
1134 let w1 = w + 1.0;
1135 let denom = ew * w1 - 0.5 * (w + 2.0) * diff / w1;
1137 if denom.abs() < f64::MIN_POSITIVE {
1138 break;
1139 }
1140
1141 let delta = diff / denom;
1142
1143 let correction = if w < -0.5 && delta.abs() > 0.5 {
1145 delta * 0.5 } else {
1147 delta
1148 };
1149
1150 w -= correction;
1151 }
1152
1153 if !w.is_finite() {
1155 return Err(EvalError::Overflow);
1156 }
1157
1158 Ok(w)
1159}
1160
1161#[cfg(test)]
1162mod tests {
1163 use super::*;
1164
1165 fn approx_eq(a: f64, b: f64) -> bool {
1166 (a - b).abs() < 1e-10
1167 }
1168
1169 #[test]
1170 fn test_basic_eval() {
1171 let expr = Expression::parse("32+").unwrap();
1172 let result = evaluate(&expr, 0.0).unwrap();
1173 assert!(approx_eq(result.value, 5.0));
1174 assert!(approx_eq(result.derivative, 0.0));
1175 }
1176
1177 #[test]
1178 fn test_variable() {
1179 let expr = Expression::parse("x").unwrap();
1180 let result = evaluate(&expr, 3.5).unwrap();
1181 assert!(approx_eq(result.value, 3.5));
1182 assert!(approx_eq(result.derivative, 1.0));
1183 }
1184
1185 #[test]
1186 fn test_x_squared() {
1187 let expr = Expression::parse("xs").unwrap(); let result = evaluate(&expr, 3.0).unwrap();
1189 assert!(approx_eq(result.value, 9.0));
1190 assert!(approx_eq(result.derivative, 6.0)); }
1192
1193 #[test]
1194 fn test_sqrt_pi() {
1195 let expr = Expression::parse("pq").unwrap(); let result = evaluate(&expr, 0.0).unwrap();
1197 assert!(approx_eq(result.value, constants::PI.sqrt()));
1198 }
1199
1200 #[test]
1201 fn test_e_to_x() {
1202 let expr = Expression::parse("xE").unwrap(); let result = evaluate(&expr, 1.0).unwrap();
1204 assert!(approx_eq(result.value, constants::E));
1205 assert!(approx_eq(result.derivative, constants::E)); }
1207
1208 #[test]
1209 fn test_complex_expr() {
1210 let expr = Expression::parse("xs2x*+1+").unwrap();
1212 let result = evaluate(&expr, 3.0).unwrap();
1213 assert!(approx_eq(result.value, 16.0)); assert!(approx_eq(result.derivative, 8.0)); }
1216
1217 #[test]
1218 fn test_lambert_w() {
1219 let w = lambert_w(1.0).unwrap();
1221 assert!((w - 0.5671432904).abs() < 1e-9);
1222
1223 let w = lambert_w(constants::E).unwrap();
1225 assert!((w - 1.0).abs() < 1e-10);
1226 }
1227
1228 #[test]
1229 fn test_sinpi_snaps_exact_integer_and_half_integer_arguments() {
1230 let integer_expr = Expression::parse("xS").unwrap();
1231 let integer_result = evaluate(&integer_expr, 1.0).unwrap();
1232 assert_eq!(integer_result.value, 0.0);
1233 assert!(approx_eq(integer_result.derivative, -constants::PI));
1234
1235 let half_integer_expr = Expression::parse("12/S").unwrap();
1236 let half_integer_result = evaluate(&half_integer_expr, 0.0).unwrap();
1237 assert_eq!(half_integer_result.value, 1.0);
1238 assert_eq!(half_integer_result.derivative, 0.0);
1239 }
1240
1241 #[test]
1242 fn test_cospi_snaps_exact_integer_and_half_integer_arguments() {
1243 let integer_expr = Expression::parse("xC").unwrap();
1244 let integer_result = evaluate(&integer_expr, 1.0).unwrap();
1245 assert_eq!(integer_result.value, -1.0);
1246 assert_eq!(integer_result.derivative, 0.0);
1247
1248 let half_integer_expr = Expression::parse("xC").unwrap();
1249 let half_integer_result = evaluate(&half_integer_expr, 0.5).unwrap();
1250 assert_eq!(half_integer_result.value, 0.0);
1251 assert!(approx_eq(half_integer_result.derivative, -constants::PI));
1252 }
1253
1254 #[test]
1255 fn test_tanpi_snaps_exact_integer_and_half_integer_arguments() {
1256 let integer_expr = Expression::parse("xT").unwrap();
1257 let integer_result = evaluate(&integer_expr, 1.0).unwrap();
1258 assert_eq!(integer_result.value, 0.0);
1259 assert!(approx_eq(integer_result.derivative, constants::PI));
1260
1261 let half_integer_expr = Expression::parse("12/T").unwrap();
1262 assert!(matches!(
1263 evaluate(&half_integer_expr, 0.0),
1264 Err(EvalError::Overflow)
1265 ));
1266 }
1267
1268 #[test]
1269 fn test_custom_trig_argument_scale_preserves_raw_trig_behavior() {
1270 let expr = Expression::parse("xS").unwrap();
1271 let context = EvalContext::new().with_trig_argument_scale(1.0);
1272 let result = evaluate_with_context(&expr, 1.0, &context).unwrap();
1273
1274 assert!(approx_eq(result.value, 1.0_f64.sin()));
1275 assert!(approx_eq(result.derivative, 1.0_f64.cos()));
1276 }
1277
1278 #[test]
1279 fn test_exact_trig_zero_rejects_pathological_negative_power_branch() {
1280 let expr = Expression::parse("1Sxn^S").unwrap();
1281 assert!(matches!(
1282 evaluate(&expr, 2.506314),
1283 Err(EvalError::SqrtDomain)
1284 ));
1285 }
1286
1287 #[test]
1288 fn test_user_constant_evaluation() {
1289 use crate::profile::UserConstant;
1290
1291 let user_constants = vec![UserConstant {
1293 weight: 8,
1294 name: "g".to_string(),
1295 description: "gamma".to_string(),
1296 value: 0.5772156649,
1297 num_type: NumType::Transcendental,
1298 }];
1299
1300 let expr = Expression::from_symbols(&[Symbol::UserConstant0]);
1302
1303 let result = evaluate_with_constants(&expr, 0.0, &user_constants).unwrap();
1305
1306 assert!(approx_eq(result.value, 0.5772156649));
1308 assert!(approx_eq(result.derivative, 0.0));
1310 }
1311
1312 #[test]
1313 fn test_user_constant_in_expression() {
1314 use crate::profile::UserConstant;
1315
1316 let user_constants = vec![
1318 UserConstant {
1319 weight: 8,
1320 name: "a".to_string(),
1321 description: "constant a".to_string(),
1322 value: 2.0,
1323 num_type: NumType::Integer,
1324 },
1325 UserConstant {
1326 weight: 8,
1327 name: "b".to_string(),
1328 description: "constant b".to_string(),
1329 value: 3.0,
1330 num_type: NumType::Integer,
1331 },
1332 ];
1333
1334 let expr = Expression::from_symbols(&[
1336 Symbol::UserConstant0,
1337 Symbol::X,
1338 Symbol::Mul,
1339 Symbol::UserConstant1,
1340 Symbol::Add,
1341 ]);
1342
1343 let result = evaluate_with_constants(&expr, 4.0, &user_constants).unwrap();
1345 assert!(approx_eq(result.value, 11.0));
1346 assert!(approx_eq(result.derivative, 2.0));
1348 }
1349
1350 #[test]
1351 fn test_user_constant_missing_returns_error() {
1352 let expr = Expression::from_symbols(&[Symbol::UserConstant0]);
1355
1356 let result = evaluate_with_constants(&expr, 0.0, &[]);
1357 assert!(matches!(result, Err(EvalError::MissingUserConstant(0))));
1358 }
1359
1360 #[test]
1361 fn test_user_function_sinh() {
1362 use crate::udf::UserFunction;
1363
1364 let user_functions = vec![UserFunction::parse("4:sinh:hyperbolic sine:E|r-2/").unwrap()];
1367
1368 let expr = Expression::from_symbols(&[Symbol::X, Symbol::UserFunction0]);
1370
1371 let result =
1373 evaluate_with_constants_and_functions(&expr, 1.0, &[], &user_functions).unwrap();
1374 let expected = (constants::E - 1.0 / constants::E) / 2.0;
1375 assert!(approx_eq(result.value, expected));
1376
1377 let expected_deriv = (constants::E + 1.0 / constants::E) / 2.0;
1379 assert!((result.derivative - expected_deriv).abs() < 1e-10);
1380 }
1381
1382 #[test]
1383 fn test_user_function_xex() {
1384 use crate::udf::UserFunction;
1385
1386 let user_functions = vec![UserFunction::parse("4:XeX:x*exp(x):|E*").unwrap()];
1389
1390 let expr = Expression::from_symbols(&[Symbol::X, Symbol::UserFunction0]);
1392
1393 let result =
1395 evaluate_with_constants_and_functions(&expr, 1.0, &[], &user_functions).unwrap();
1396 assert!(approx_eq(result.value, constants::E));
1397
1398 let expected_deriv = constants::E * 2.0;
1400 assert!((result.derivative - expected_deriv).abs() < 1e-10);
1401 }
1402
1403 #[test]
1404 fn test_user_function_missing_returns_error() {
1405 let expr = Expression::from_symbols(&[Symbol::X, Symbol::UserFunction0]);
1407
1408 let result = evaluate_with_constants_and_functions(&expr, 1.0, &[], &[]);
1409 assert!(result.is_err());
1410 }
1411}