Struct EvalContext

Source
pub struct EvalContext<'a> {
    pub variables: HashMap<String, Real>,
    pub constants: HashMap<String, Real>,
    pub arrays: HashMap<String, Vec<Real>>,
    pub attributes: HashMap<String, HashMap<String, Real>>,
    pub nested_arrays: HashMap<String, HashMap<usize, Vec<Real>>>,
    pub function_registry: Rc<FunctionRegistry<'a>>,
    pub parent: Option<Rc<EvalContext<'a>>>,
    pub ast_cache: Option<RefCell<HashMap<String, Rc<AstExpr>>>>,
}
Expand description

Evaluation context for expressions.

This is the main configuration object that holds variables, constants, arrays, functions, and other settings for evaluating expressions. You typically create an EvalContext, add your variables and functions, and then pass it to the interp function.

§Examples

use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use std::rc::Rc;
 
let mut ctx = EvalContext::new();
 
// Add variables
ctx.set_parameter("x", 5.0);
ctx.set_parameter("y", 10.0);
 
// Add a constant
ctx.constants.insert("PI_SQUARED".to_string(), 9.8696);
 
// Register a custom function
ctx.register_native_function("multiply", 2, |args| args[0] * args[1]);
 
// Evaluate expressions using this context
let result = interp("x + y * PI_SQUARED", Some(Rc::new(ctx.clone()))).unwrap();
let result2 = interp("multiply(x, y)", Some(Rc::new(ctx))).unwrap();

Contexts can be nested to create scopes:

use exp_rs::context::EvalContext;
use std::rc::Rc;
 
let mut parent = EvalContext::new();
parent.set_parameter("x", 1.0);
 
let mut child = EvalContext::new();
child.set_parameter("y", 2.0); 
child.parent = Some(Rc::new(parent));
 
// The child context can access both its own variables and the parent's

Fields§

§variables: HashMap<String, Real>

Variables that can be modified during evaluation

§constants: HashMap<String, Real>

Constants that cannot be modified during evaluation

§arrays: HashMap<String, Vec<Real>>

Arrays of values that can be accessed using array[index] syntax

§attributes: HashMap<String, HashMap<String, Real>>

Object attributes that can be accessed using object.attribute syntax

§nested_arrays: HashMap<String, HashMap<usize, Vec<Real>>>

Multi-dimensional arrays (not yet fully supported)

§function_registry: Rc<FunctionRegistry<'a>>

Registry of functions available in this context

§parent: Option<Rc<EvalContext<'a>>>

Optional parent context for variable/function inheritance

§ast_cache: Option<RefCell<HashMap<String, Rc<AstExpr>>>>

Optional cache for parsed ASTs to speed up repeated evaluations

Implementations§

Source§

impl<'a> EvalContext<'a>

Source

pub fn new() -> Self

Creates a new empty evaluation context.

The context starts with no variables, constants, arrays, or functions. You can add these elements using the appropriate methods and fields.

§Examples
use exp_rs::context::EvalContext;

let ctx = EvalContext::new();
// Now add variables, constants, functions, etc.
Examples found in repository?
examples/eval_context.rs (line 26)
25fn main() {
26    let mut ctx = EvalContext::new();
27
28    #[cfg(feature = "f64")]
29    {
30        ctx.register_native_function("sin", 1, c_fn!(sin));
31        ctx.register_native_function("cos", 1, c_fn!(cos));
32        ctx.register_native_function("tan", 1, c_fn!(tan));
33        ctx.register_native_function("exp", 1, c_fn!(exp));
34        ctx.register_native_function("log", 1, c_fn!(log));
35        ctx.register_native_function("sqrt", 1, c_fn!(sqrt));
36        ctx.register_expression_function("fancy", &["x"], "sin(x) + cos(x) + 42")
37            .unwrap();
38    }
39
40    #[cfg(feature = "f32")]
41    {
42        ctx.register_native_function("sin", 1, c_fn!(sinf));
43        ctx.register_native_function("cos", 1, c_fn!(cosf));
44        ctx.register_native_function("tan", 1, c_fn!(tanf));
45        ctx.register_native_function("exp", 1, c_fn!(expf));
46        ctx.register_native_function("log", 1, c_fn!(logf));
47        ctx.register_native_function("sqrt", 1, c_fn!(sqrtf));
48        ctx.register_expression_function("fancy", &["x"], "sin(x) + cos(x) + 42")
49            .unwrap();
50    }
51
52    let exprs = [
53        "sin(1.0)",
54        "cos(1.0)",
55        "sqrt(9)",
56        "fancy(0.5)",
57        "fancy(2.0) + sqrt(16)",
58    ];
59
60    for expr in &exprs {
61        match exp_rs::engine::interp(expr, Some(std::rc::Rc::new(ctx.clone()))) {
62            Ok(val) => {
63                println!("{} = {}", expr, val);
64                // For no_std, replace with your platform's output method
65            }
66            Err(e) => {
67                println!("Error evaluating {}: {}", expr, e);
68            }
69        }
70    }
71}
More examples
Hide additional examples
examples/benchmark_compare.rs (line 60)
30fn main() {
31    let benchmarks = [
32        ("sqrt(a^1.5+a^2.5)", native_sqrt_expr as fn(Real) -> Real),
33        ("a+5", native_a_plus_5),
34        ("a+(5*2)", native_a_plus_5_times_2),
35        ("(a+5)*2", native_a_plus_5_all_times_2),
36        ("(1/(a+1)+2/(a+2)+3/(a+3))", native_sum_fractions),
37        // Additional benchmark expressions with other functions:
38        ("sin(a)", |a: Real| a.sin()),
39        ("cos(a)", |a: Real| a.cos()),
40        ("tan(a)", |a: Real| a.tan()),
41        ("log(a+10)", |a: Real| (a+10.0).log10()),
42        ("ln(a+10)", |a: Real| (a+10.0).ln()),
43        ("abs(a-50)", |a: Real| (a-50.0).abs()),
44        ("max(a,100-a)", |a: Real| a.max(100.0-a)),
45        ("min(a,100-a)", |a: Real| a.min(100.0-a)),
46        ("pow(a,1.5)", |a: Real| a.powf(1.5)),
47        ("exp(a/100.0)", |a: Real| (a/100.0).exp()),
48        ("floor(a/3.1)", |a: Real| (a/3.1).floor()),
49        ("ceil(a/3.1)", |a: Real| (a/3.1).ceil()),
50        ("fmod(a,7)", |a: Real| a % 7.0),
51        ("neg(a)", |a: Real| -a),
52    ];
53
54    for (expr, native_func) in benchmarks.iter() {
55        println!("Benchmarking: {}", expr);
56
57        let ast = parse_expression(expr).expect("parse failed");
58
59        // Create a mutable context first before wrapping in Rc
60        let mut ctx_base = EvalContext::new();
61        
62        // Only register default math functions if the feature is available
63        #[cfg(not(feature = "no-builtin-math"))]
64        {
65            ctx_base.register_default_math_functions();
66        }
67        
68        // When no-builtin-math is enabled, we need to register the functions manually
69        #[cfg(feature = "no-builtin-math")]
70        {
71            // Register the minimum functions needed for our benchmarks
72            #[cfg(feature = "f32")]
73            {
74                ctx_base.register_native_function("sqrt", 1, |args| libm::sqrtf(args[0]));
75                ctx_base.register_native_function("sin", 1, |args| libm::sinf(args[0]));
76                ctx_base.register_native_function("cos", 1, |args| libm::cosf(args[0]));
77                ctx_base.register_native_function("tan", 1, |args| libm::tanf(args[0]));
78                ctx_base.register_native_function("log", 1, |args| libm::logf(args[0]));
79                ctx_base.register_native_function("log10", 1, |args| libm::log10f(args[0]));
80                ctx_base.register_native_function("ln", 1, |args| libm::logf(args[0]));
81                ctx_base.register_native_function("abs", 1, |args| args[0].abs());
82                ctx_base.register_native_function("max", 2, |args| args[0].max(args[1]));
83                ctx_base.register_native_function("min", 2, |args| args[0].min(args[1]));
84                ctx_base.register_native_function("pow", 2, |args| libm::powf(args[0], args[1]));
85                ctx_base.register_native_function("^", 2, |args| libm::powf(args[0], args[1]));
86                ctx_base.register_native_function("exp", 1, |args| libm::expf(args[0]));
87                ctx_base.register_native_function("floor", 1, |args| libm::floorf(args[0]));
88                ctx_base.register_native_function("ceil", 1, |args| libm::ceilf(args[0]));
89                ctx_base.register_native_function("neg", 1, |args| -args[0]);
90                ctx_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
91            }
92            #[cfg(not(feature = "f32"))]
93            {
94                ctx_base.register_native_function("sqrt", 1, |args| libm::sqrt(args[0]));
95                ctx_base.register_native_function("sin", 1, |args| libm::sin(args[0]));
96                ctx_base.register_native_function("cos", 1, |args| libm::cos(args[0]));
97                ctx_base.register_native_function("tan", 1, |args| libm::tan(args[0]));
98                ctx_base.register_native_function("log", 1, |args| libm::log(args[0]));
99                ctx_base.register_native_function("log10", 1, |args| libm::log10(args[0]));
100                ctx_base.register_native_function("ln", 1, |args| libm::log(args[0]));
101                ctx_base.register_native_function("abs", 1, |args| args[0].abs());
102                ctx_base.register_native_function("max", 2, |args| args[0].max(args[1]));
103                ctx_base.register_native_function("min", 2, |args| args[0].min(args[1]));
104                ctx_base.register_native_function("pow", 2, |args| libm::pow(args[0], args[1]));
105                ctx_base.register_native_function("^", 2, |args| libm::pow(args[0], args[1]));
106                ctx_base.register_native_function("exp", 1, |args| libm::exp(args[0]));
107                ctx_base.register_native_function("floor", 1, |args| libm::floor(args[0]));
108                ctx_base.register_native_function("ceil", 1, |args| libm::ceil(args[0]));
109                ctx_base.register_native_function("neg", 1, |args| -args[0]);
110                ctx_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
111            }
112        }
113        
114        let mut evalctx_sum = 0.0;
115        let start = Instant::now();
116        for j in 0..N {
117            // Create a new context for each iteration with the parameter set
118            let mut ctx = ctx_base.clone();
119            ctx.set_parameter("a", j as Real);
120            let ctx_rc = Rc::new(ctx);
121            evalctx_sum += eval_ast(&ast, Some(ctx_rc)).unwrap();
122        }
123        let evalctx_time = start.elapsed();
124        std::hint::black_box(evalctx_sum);
125
126        // Create a mutable context first before wrapping in Rc
127        let mut ctx_interp_base = EvalContext::new();
128        
129        // Only register default math functions if the feature is available
130        #[cfg(not(feature = "no-builtin-math"))]
131        {
132            ctx_interp_base.register_default_math_functions();
133        }
134        
135        // When no-builtin-math is enabled, we need to register the functions manually
136        #[cfg(feature = "no-builtin-math")]
137        {
138            // Register the minimum functions needed for our benchmarks
139            #[cfg(feature = "f32")]
140            {
141                ctx_interp_base.register_native_function("sqrt", 1, |args| libm::sqrtf(args[0]));
142                ctx_interp_base.register_native_function("sin", 1, |args| libm::sinf(args[0]));
143                ctx_interp_base.register_native_function("cos", 1, |args| libm::cosf(args[0]));
144                ctx_interp_base.register_native_function("tan", 1, |args| libm::tanf(args[0]));
145                ctx_interp_base.register_native_function("log", 1, |args| libm::logf(args[0]));
146                ctx_interp_base.register_native_function("log10", 1, |args| libm::log10f(args[0]));
147                ctx_interp_base.register_native_function("ln", 1, |args| libm::logf(args[0]));
148                ctx_interp_base.register_native_function("abs", 1, |args| args[0].abs());
149                ctx_interp_base.register_native_function("max", 2, |args| args[0].max(args[1]));
150                ctx_interp_base.register_native_function("min", 2, |args| args[0].min(args[1]));
151                ctx_interp_base.register_native_function("pow", 2, |args| libm::powf(args[0], args[1]));
152                ctx_interp_base.register_native_function("^", 2, |args| libm::powf(args[0], args[1]));
153                ctx_interp_base.register_native_function("exp", 1, |args| libm::expf(args[0]));
154                ctx_interp_base.register_native_function("floor", 1, |args| libm::floorf(args[0]));
155                ctx_interp_base.register_native_function("ceil", 1, |args| libm::ceilf(args[0]));
156                ctx_interp_base.register_native_function("neg", 1, |args| -args[0]);
157                ctx_interp_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
158            }
159            #[cfg(not(feature = "f32"))]
160            {
161                ctx_interp_base.register_native_function("sqrt", 1, |args| libm::sqrt(args[0]));
162                ctx_interp_base.register_native_function("sin", 1, |args| libm::sin(args[0]));
163                ctx_interp_base.register_native_function("cos", 1, |args| libm::cos(args[0]));
164                ctx_interp_base.register_native_function("tan", 1, |args| libm::tan(args[0]));
165                ctx_interp_base.register_native_function("log", 1, |args| libm::log(args[0]));
166                ctx_interp_base.register_native_function("log10", 1, |args| libm::log10(args[0]));
167                ctx_interp_base.register_native_function("ln", 1, |args| libm::log(args[0]));
168                ctx_interp_base.register_native_function("abs", 1, |args| args[0].abs());
169                ctx_interp_base.register_native_function("max", 2, |args| args[0].max(args[1]));
170                ctx_interp_base.register_native_function("min", 2, |args| args[0].min(args[1]));
171                ctx_interp_base.register_native_function("pow", 2, |args| libm::pow(args[0], args[1]));
172                ctx_interp_base.register_native_function("^", 2, |args| libm::pow(args[0], args[1]));
173                ctx_interp_base.register_native_function("exp", 1, |args| libm::exp(args[0]));
174                ctx_interp_base.register_native_function("floor", 1, |args| libm::floor(args[0]));
175                ctx_interp_base.register_native_function("ceil", 1, |args| libm::ceil(args[0]));
176                ctx_interp_base.register_native_function("neg", 1, |args| -args[0]);
177                ctx_interp_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
178            }
179        }
180        
181        // Enable AST cache for the base context
182        ctx_interp_base.enable_ast_cache();
183        
184        let mut interp_sum = 0.0;
185        let start = Instant::now();
186        for j in 0..N {
187            // Create a new context for each iteration with the parameter set
188            let mut ctx_interp = ctx_interp_base.clone();
189            ctx_interp.set_parameter("a", j as Real);
190            let ctx_interp_rc = Rc::new(ctx_interp);
191            interp_sum += exp_rs::engine::interp(expr, Some(ctx_interp_rc)).unwrap();
192        }
193        let interp_eval_time = start.elapsed();
194        std::hint::black_box(interp_sum);
195
196        let mut native_sum = 0.0;
197        let start = Instant::now();
198        for j in 0..N {
199            native_sum += native_func(j as Real);
200        }
201        let native_time = start.elapsed();
202        std::hint::black_box(native_sum);
203
204        let evalctx_us = evalctx_time.as_micros();
205        let interp_us = interp_eval_time.as_micros();
206        let native_us = native_time.as_micros();
207
208        let slowdown_evalctx_vs_native = if native_us > 0 {
209            evalctx_us as f64 / native_us as f64
210        } else {
211            f64::NAN
212        };
213        let slowdown_interp_vs_native = if native_us > 0 {
214            interp_us as f64 / native_us as f64
215        } else {
216            f64::NAN
217        };
218        let slowdown_interp_vs_evalctx = if evalctx_us > 0 {
219            interp_us as f64 / evalctx_us as f64
220        } else {
221            f64::NAN
222        };
223
224        println!("evalctx - time: {} us, {:.2}x slower than native", evalctx_us, slowdown_evalctx_vs_native);
225        println!("interp - time: {} us, {:.2}x slower than native", interp_us, slowdown_interp_vs_native);
226        println!("native - time: {} us", native_us);
227        println!("evalctx vs native: {:.2}x slower", slowdown_evalctx_vs_native);
228        println!("interp vs native: {:.2}x slower", slowdown_interp_vs_native);
229        println!("interp vs evalctx: {:.2}x slower\n", slowdown_interp_vs_evalctx);
230    }
231}
Source

pub fn set_parameter(&mut self, name: &str, value: Real) -> Option<Real>

Sets a parameter (variable) in the context.

This method adds or updates a variable in the context. Variables can be used in expressions and their values can be changed between evaluations.

§Parameters
  • name: The name of the variable
  • value: The value to assign to the variable
§Returns

The previous value of the variable, if it existed

§Examples
use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use std::rc::Rc;

let mut ctx = EvalContext::new();
ctx.set_parameter("x", 42.0);

let result = interp("x * 2", Some(Rc::new(ctx))).unwrap();
assert_eq!(result, 84.0);
Examples found in repository?
examples/benchmark_compare.rs (line 119)
30fn main() {
31    let benchmarks = [
32        ("sqrt(a^1.5+a^2.5)", native_sqrt_expr as fn(Real) -> Real),
33        ("a+5", native_a_plus_5),
34        ("a+(5*2)", native_a_plus_5_times_2),
35        ("(a+5)*2", native_a_plus_5_all_times_2),
36        ("(1/(a+1)+2/(a+2)+3/(a+3))", native_sum_fractions),
37        // Additional benchmark expressions with other functions:
38        ("sin(a)", |a: Real| a.sin()),
39        ("cos(a)", |a: Real| a.cos()),
40        ("tan(a)", |a: Real| a.tan()),
41        ("log(a+10)", |a: Real| (a+10.0).log10()),
42        ("ln(a+10)", |a: Real| (a+10.0).ln()),
43        ("abs(a-50)", |a: Real| (a-50.0).abs()),
44        ("max(a,100-a)", |a: Real| a.max(100.0-a)),
45        ("min(a,100-a)", |a: Real| a.min(100.0-a)),
46        ("pow(a,1.5)", |a: Real| a.powf(1.5)),
47        ("exp(a/100.0)", |a: Real| (a/100.0).exp()),
48        ("floor(a/3.1)", |a: Real| (a/3.1).floor()),
49        ("ceil(a/3.1)", |a: Real| (a/3.1).ceil()),
50        ("fmod(a,7)", |a: Real| a % 7.0),
51        ("neg(a)", |a: Real| -a),
52    ];
53
54    for (expr, native_func) in benchmarks.iter() {
55        println!("Benchmarking: {}", expr);
56
57        let ast = parse_expression(expr).expect("parse failed");
58
59        // Create a mutable context first before wrapping in Rc
60        let mut ctx_base = EvalContext::new();
61        
62        // Only register default math functions if the feature is available
63        #[cfg(not(feature = "no-builtin-math"))]
64        {
65            ctx_base.register_default_math_functions();
66        }
67        
68        // When no-builtin-math is enabled, we need to register the functions manually
69        #[cfg(feature = "no-builtin-math")]
70        {
71            // Register the minimum functions needed for our benchmarks
72            #[cfg(feature = "f32")]
73            {
74                ctx_base.register_native_function("sqrt", 1, |args| libm::sqrtf(args[0]));
75                ctx_base.register_native_function("sin", 1, |args| libm::sinf(args[0]));
76                ctx_base.register_native_function("cos", 1, |args| libm::cosf(args[0]));
77                ctx_base.register_native_function("tan", 1, |args| libm::tanf(args[0]));
78                ctx_base.register_native_function("log", 1, |args| libm::logf(args[0]));
79                ctx_base.register_native_function("log10", 1, |args| libm::log10f(args[0]));
80                ctx_base.register_native_function("ln", 1, |args| libm::logf(args[0]));
81                ctx_base.register_native_function("abs", 1, |args| args[0].abs());
82                ctx_base.register_native_function("max", 2, |args| args[0].max(args[1]));
83                ctx_base.register_native_function("min", 2, |args| args[0].min(args[1]));
84                ctx_base.register_native_function("pow", 2, |args| libm::powf(args[0], args[1]));
85                ctx_base.register_native_function("^", 2, |args| libm::powf(args[0], args[1]));
86                ctx_base.register_native_function("exp", 1, |args| libm::expf(args[0]));
87                ctx_base.register_native_function("floor", 1, |args| libm::floorf(args[0]));
88                ctx_base.register_native_function("ceil", 1, |args| libm::ceilf(args[0]));
89                ctx_base.register_native_function("neg", 1, |args| -args[0]);
90                ctx_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
91            }
92            #[cfg(not(feature = "f32"))]
93            {
94                ctx_base.register_native_function("sqrt", 1, |args| libm::sqrt(args[0]));
95                ctx_base.register_native_function("sin", 1, |args| libm::sin(args[0]));
96                ctx_base.register_native_function("cos", 1, |args| libm::cos(args[0]));
97                ctx_base.register_native_function("tan", 1, |args| libm::tan(args[0]));
98                ctx_base.register_native_function("log", 1, |args| libm::log(args[0]));
99                ctx_base.register_native_function("log10", 1, |args| libm::log10(args[0]));
100                ctx_base.register_native_function("ln", 1, |args| libm::log(args[0]));
101                ctx_base.register_native_function("abs", 1, |args| args[0].abs());
102                ctx_base.register_native_function("max", 2, |args| args[0].max(args[1]));
103                ctx_base.register_native_function("min", 2, |args| args[0].min(args[1]));
104                ctx_base.register_native_function("pow", 2, |args| libm::pow(args[0], args[1]));
105                ctx_base.register_native_function("^", 2, |args| libm::pow(args[0], args[1]));
106                ctx_base.register_native_function("exp", 1, |args| libm::exp(args[0]));
107                ctx_base.register_native_function("floor", 1, |args| libm::floor(args[0]));
108                ctx_base.register_native_function("ceil", 1, |args| libm::ceil(args[0]));
109                ctx_base.register_native_function("neg", 1, |args| -args[0]);
110                ctx_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
111            }
112        }
113        
114        let mut evalctx_sum = 0.0;
115        let start = Instant::now();
116        for j in 0..N {
117            // Create a new context for each iteration with the parameter set
118            let mut ctx = ctx_base.clone();
119            ctx.set_parameter("a", j as Real);
120            let ctx_rc = Rc::new(ctx);
121            evalctx_sum += eval_ast(&ast, Some(ctx_rc)).unwrap();
122        }
123        let evalctx_time = start.elapsed();
124        std::hint::black_box(evalctx_sum);
125
126        // Create a mutable context first before wrapping in Rc
127        let mut ctx_interp_base = EvalContext::new();
128        
129        // Only register default math functions if the feature is available
130        #[cfg(not(feature = "no-builtin-math"))]
131        {
132            ctx_interp_base.register_default_math_functions();
133        }
134        
135        // When no-builtin-math is enabled, we need to register the functions manually
136        #[cfg(feature = "no-builtin-math")]
137        {
138            // Register the minimum functions needed for our benchmarks
139            #[cfg(feature = "f32")]
140            {
141                ctx_interp_base.register_native_function("sqrt", 1, |args| libm::sqrtf(args[0]));
142                ctx_interp_base.register_native_function("sin", 1, |args| libm::sinf(args[0]));
143                ctx_interp_base.register_native_function("cos", 1, |args| libm::cosf(args[0]));
144                ctx_interp_base.register_native_function("tan", 1, |args| libm::tanf(args[0]));
145                ctx_interp_base.register_native_function("log", 1, |args| libm::logf(args[0]));
146                ctx_interp_base.register_native_function("log10", 1, |args| libm::log10f(args[0]));
147                ctx_interp_base.register_native_function("ln", 1, |args| libm::logf(args[0]));
148                ctx_interp_base.register_native_function("abs", 1, |args| args[0].abs());
149                ctx_interp_base.register_native_function("max", 2, |args| args[0].max(args[1]));
150                ctx_interp_base.register_native_function("min", 2, |args| args[0].min(args[1]));
151                ctx_interp_base.register_native_function("pow", 2, |args| libm::powf(args[0], args[1]));
152                ctx_interp_base.register_native_function("^", 2, |args| libm::powf(args[0], args[1]));
153                ctx_interp_base.register_native_function("exp", 1, |args| libm::expf(args[0]));
154                ctx_interp_base.register_native_function("floor", 1, |args| libm::floorf(args[0]));
155                ctx_interp_base.register_native_function("ceil", 1, |args| libm::ceilf(args[0]));
156                ctx_interp_base.register_native_function("neg", 1, |args| -args[0]);
157                ctx_interp_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
158            }
159            #[cfg(not(feature = "f32"))]
160            {
161                ctx_interp_base.register_native_function("sqrt", 1, |args| libm::sqrt(args[0]));
162                ctx_interp_base.register_native_function("sin", 1, |args| libm::sin(args[0]));
163                ctx_interp_base.register_native_function("cos", 1, |args| libm::cos(args[0]));
164                ctx_interp_base.register_native_function("tan", 1, |args| libm::tan(args[0]));
165                ctx_interp_base.register_native_function("log", 1, |args| libm::log(args[0]));
166                ctx_interp_base.register_native_function("log10", 1, |args| libm::log10(args[0]));
167                ctx_interp_base.register_native_function("ln", 1, |args| libm::log(args[0]));
168                ctx_interp_base.register_native_function("abs", 1, |args| args[0].abs());
169                ctx_interp_base.register_native_function("max", 2, |args| args[0].max(args[1]));
170                ctx_interp_base.register_native_function("min", 2, |args| args[0].min(args[1]));
171                ctx_interp_base.register_native_function("pow", 2, |args| libm::pow(args[0], args[1]));
172                ctx_interp_base.register_native_function("^", 2, |args| libm::pow(args[0], args[1]));
173                ctx_interp_base.register_native_function("exp", 1, |args| libm::exp(args[0]));
174                ctx_interp_base.register_native_function("floor", 1, |args| libm::floor(args[0]));
175                ctx_interp_base.register_native_function("ceil", 1, |args| libm::ceil(args[0]));
176                ctx_interp_base.register_native_function("neg", 1, |args| -args[0]);
177                ctx_interp_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
178            }
179        }
180        
181        // Enable AST cache for the base context
182        ctx_interp_base.enable_ast_cache();
183        
184        let mut interp_sum = 0.0;
185        let start = Instant::now();
186        for j in 0..N {
187            // Create a new context for each iteration with the parameter set
188            let mut ctx_interp = ctx_interp_base.clone();
189            ctx_interp.set_parameter("a", j as Real);
190            let ctx_interp_rc = Rc::new(ctx_interp);
191            interp_sum += exp_rs::engine::interp(expr, Some(ctx_interp_rc)).unwrap();
192        }
193        let interp_eval_time = start.elapsed();
194        std::hint::black_box(interp_sum);
195
196        let mut native_sum = 0.0;
197        let start = Instant::now();
198        for j in 0..N {
199            native_sum += native_func(j as Real);
200        }
201        let native_time = start.elapsed();
202        std::hint::black_box(native_sum);
203
204        let evalctx_us = evalctx_time.as_micros();
205        let interp_us = interp_eval_time.as_micros();
206        let native_us = native_time.as_micros();
207
208        let slowdown_evalctx_vs_native = if native_us > 0 {
209            evalctx_us as f64 / native_us as f64
210        } else {
211            f64::NAN
212        };
213        let slowdown_interp_vs_native = if native_us > 0 {
214            interp_us as f64 / native_us as f64
215        } else {
216            f64::NAN
217        };
218        let slowdown_interp_vs_evalctx = if evalctx_us > 0 {
219            interp_us as f64 / evalctx_us as f64
220        } else {
221            f64::NAN
222        };
223
224        println!("evalctx - time: {} us, {:.2}x slower than native", evalctx_us, slowdown_evalctx_vs_native);
225        println!("interp - time: {} us, {:.2}x slower than native", interp_us, slowdown_interp_vs_native);
226        println!("native - time: {} us", native_us);
227        println!("evalctx vs native: {:.2}x slower", slowdown_evalctx_vs_native);
228        println!("interp vs native: {:.2}x slower", slowdown_interp_vs_native);
229        println!("interp vs evalctx: {:.2}x slower\n", slowdown_interp_vs_evalctx);
230    }
231}
Source

pub fn register_native_function<F>( &mut self, name: &str, arity: usize, implementation: F, )
where F: Fn(&[Real]) -> Real + 'static,

Registers a native function in the context.

Native functions are implemented in Rust and can be called from expressions. They take a slice of Real values as arguments and return a Real value.

§Parameters
  • name: The name of the function as it will be used in expressions
  • arity: The number of arguments the function expects
  • implementation: A closure or function that implements the function logic
§Examples
use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use std::rc::Rc;

let mut ctx = EvalContext::new();

// Register a function that adds all its arguments
ctx.register_native_function("sum", 3, |args| {
    args.iter().sum()
});

let result = interp("sum(10, 20, 30)", Some(Rc::new(ctx))).unwrap();
assert_eq!(result, 60.0);

Functions with variable argument counts:

use exp_rs::context::EvalContext; 
use exp_rs::engine::interp;
use std::rc::Rc;

let mut ctx = EvalContext::new();

// Register a function that calculates the mean of its arguments
ctx.register_native_function("mean", 5, |args| {
    args.iter().sum::<f64>() / args.len() as f64
});

let result = interp("mean(1, 2, 3, 4, 5)", Some(Rc::new(ctx))).unwrap();
assert_eq!(result, 3.0);
Examples found in repository?
examples/eval_context.rs (line 30)
25fn main() {
26    let mut ctx = EvalContext::new();
27
28    #[cfg(feature = "f64")]
29    {
30        ctx.register_native_function("sin", 1, c_fn!(sin));
31        ctx.register_native_function("cos", 1, c_fn!(cos));
32        ctx.register_native_function("tan", 1, c_fn!(tan));
33        ctx.register_native_function("exp", 1, c_fn!(exp));
34        ctx.register_native_function("log", 1, c_fn!(log));
35        ctx.register_native_function("sqrt", 1, c_fn!(sqrt));
36        ctx.register_expression_function("fancy", &["x"], "sin(x) + cos(x) + 42")
37            .unwrap();
38    }
39
40    #[cfg(feature = "f32")]
41    {
42        ctx.register_native_function("sin", 1, c_fn!(sinf));
43        ctx.register_native_function("cos", 1, c_fn!(cosf));
44        ctx.register_native_function("tan", 1, c_fn!(tanf));
45        ctx.register_native_function("exp", 1, c_fn!(expf));
46        ctx.register_native_function("log", 1, c_fn!(logf));
47        ctx.register_native_function("sqrt", 1, c_fn!(sqrtf));
48        ctx.register_expression_function("fancy", &["x"], "sin(x) + cos(x) + 42")
49            .unwrap();
50    }
51
52    let exprs = [
53        "sin(1.0)",
54        "cos(1.0)",
55        "sqrt(9)",
56        "fancy(0.5)",
57        "fancy(2.0) + sqrt(16)",
58    ];
59
60    for expr in &exprs {
61        match exp_rs::engine::interp(expr, Some(std::rc::Rc::new(ctx.clone()))) {
62            Ok(val) => {
63                println!("{} = {}", expr, val);
64                // For no_std, replace with your platform's output method
65            }
66            Err(e) => {
67                println!("Error evaluating {}: {}", expr, e);
68            }
69        }
70    }
71}
Source

pub fn register_expression_function( &mut self, name: &str, params: &[&str], expression: &str, ) -> Result<(), ExprError>

Registers a function defined by an expression.

Expression functions are defined by a string expression and a list of parameter names. They can use other functions and variables available in the context.

§Parameters
  • name: The name of the function as it will be used in expressions
  • params: The names of the parameters the function accepts
  • expression: The expression that defines the function’s body
§Returns

Ok(()) if the function was registered successfully, or an error if the expression could not be parsed.

§Examples
use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use std::rc::Rc;

let mut ctx = EvalContext::new();

// Register a function to calculate the hypotenuse
ctx.register_expression_function(
    "hypotenuse",
    &["a", "b"],
    "sqrt(a^2 + b^2)"
).unwrap();

let result = interp("hypotenuse(3, 4)", Some(Rc::new(ctx))).unwrap();
assert_eq!(result, 5.0);

Expression functions can call other functions:

use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use std::rc::Rc;

let mut ctx = EvalContext::new();

// Register a polynomial function
ctx.register_expression_function(
    "polynomial",
    &["x"],
    "x^3 + 2*x^2 + 3*x + 4"
).unwrap();

let result = interp("polynomial(2)", Some(Rc::new(ctx))).unwrap();
assert_eq!(result, 26.0); // 2^3 + 2*2^2 + 3*2 + 4 = 8 + 8 + 6 + 4 = 26
Examples found in repository?
examples/eval_context.rs (line 36)
25fn main() {
26    let mut ctx = EvalContext::new();
27
28    #[cfg(feature = "f64")]
29    {
30        ctx.register_native_function("sin", 1, c_fn!(sin));
31        ctx.register_native_function("cos", 1, c_fn!(cos));
32        ctx.register_native_function("tan", 1, c_fn!(tan));
33        ctx.register_native_function("exp", 1, c_fn!(exp));
34        ctx.register_native_function("log", 1, c_fn!(log));
35        ctx.register_native_function("sqrt", 1, c_fn!(sqrt));
36        ctx.register_expression_function("fancy", &["x"], "sin(x) + cos(x) + 42")
37            .unwrap();
38    }
39
40    #[cfg(feature = "f32")]
41    {
42        ctx.register_native_function("sin", 1, c_fn!(sinf));
43        ctx.register_native_function("cos", 1, c_fn!(cosf));
44        ctx.register_native_function("tan", 1, c_fn!(tanf));
45        ctx.register_native_function("exp", 1, c_fn!(expf));
46        ctx.register_native_function("log", 1, c_fn!(logf));
47        ctx.register_native_function("sqrt", 1, c_fn!(sqrtf));
48        ctx.register_expression_function("fancy", &["x"], "sin(x) + cos(x) + 42")
49            .unwrap();
50    }
51
52    let exprs = [
53        "sin(1.0)",
54        "cos(1.0)",
55        "sqrt(9)",
56        "fancy(0.5)",
57        "fancy(2.0) + sqrt(16)",
58    ];
59
60    for expr in &exprs {
61        match exp_rs::engine::interp(expr, Some(std::rc::Rc::new(ctx.clone()))) {
62            Ok(val) => {
63                println!("{} = {}", expr, val);
64                // For no_std, replace with your platform's output method
65            }
66            Err(e) => {
67                println!("Error evaluating {}: {}", expr, e);
68            }
69        }
70    }
71}
Source

pub fn enable_ast_cache(&self)

Enables AST caching for this context to improve performance.

When enabled, repeated calls to interp with the same expression string will reuse the parsed AST, greatly improving performance for repeated evaluations with different variable values.

This is particularly useful in loops or when evaluating the same expression multiple times with different parameter values.

§Examples
use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use std::rc::Rc;

let mut ctx = EvalContext::new();
ctx.enable_ast_cache();

// First evaluation will parse and cache the AST
ctx.set_parameter("x", 1.0);
let result1 = interp("x^2 + 2*x + 1", Some(Rc::new(ctx.clone()))).unwrap();
assert_eq!(result1, 4.0); // 1^2 + 2*1 + 1 = 4

// Subsequent evaluations will reuse the cached AST
ctx.set_parameter("x", 2.0);
let result2 = interp("x^2 + 2*x + 1", Some(Rc::new(ctx))).unwrap();
assert_eq!(result2, 9.0); // 2^2 + 2*2 + 1 = 9
Examples found in repository?
examples/benchmark_compare.rs (line 182)
30fn main() {
31    let benchmarks = [
32        ("sqrt(a^1.5+a^2.5)", native_sqrt_expr as fn(Real) -> Real),
33        ("a+5", native_a_plus_5),
34        ("a+(5*2)", native_a_plus_5_times_2),
35        ("(a+5)*2", native_a_plus_5_all_times_2),
36        ("(1/(a+1)+2/(a+2)+3/(a+3))", native_sum_fractions),
37        // Additional benchmark expressions with other functions:
38        ("sin(a)", |a: Real| a.sin()),
39        ("cos(a)", |a: Real| a.cos()),
40        ("tan(a)", |a: Real| a.tan()),
41        ("log(a+10)", |a: Real| (a+10.0).log10()),
42        ("ln(a+10)", |a: Real| (a+10.0).ln()),
43        ("abs(a-50)", |a: Real| (a-50.0).abs()),
44        ("max(a,100-a)", |a: Real| a.max(100.0-a)),
45        ("min(a,100-a)", |a: Real| a.min(100.0-a)),
46        ("pow(a,1.5)", |a: Real| a.powf(1.5)),
47        ("exp(a/100.0)", |a: Real| (a/100.0).exp()),
48        ("floor(a/3.1)", |a: Real| (a/3.1).floor()),
49        ("ceil(a/3.1)", |a: Real| (a/3.1).ceil()),
50        ("fmod(a,7)", |a: Real| a % 7.0),
51        ("neg(a)", |a: Real| -a),
52    ];
53
54    for (expr, native_func) in benchmarks.iter() {
55        println!("Benchmarking: {}", expr);
56
57        let ast = parse_expression(expr).expect("parse failed");
58
59        // Create a mutable context first before wrapping in Rc
60        let mut ctx_base = EvalContext::new();
61        
62        // Only register default math functions if the feature is available
63        #[cfg(not(feature = "no-builtin-math"))]
64        {
65            ctx_base.register_default_math_functions();
66        }
67        
68        // When no-builtin-math is enabled, we need to register the functions manually
69        #[cfg(feature = "no-builtin-math")]
70        {
71            // Register the minimum functions needed for our benchmarks
72            #[cfg(feature = "f32")]
73            {
74                ctx_base.register_native_function("sqrt", 1, |args| libm::sqrtf(args[0]));
75                ctx_base.register_native_function("sin", 1, |args| libm::sinf(args[0]));
76                ctx_base.register_native_function("cos", 1, |args| libm::cosf(args[0]));
77                ctx_base.register_native_function("tan", 1, |args| libm::tanf(args[0]));
78                ctx_base.register_native_function("log", 1, |args| libm::logf(args[0]));
79                ctx_base.register_native_function("log10", 1, |args| libm::log10f(args[0]));
80                ctx_base.register_native_function("ln", 1, |args| libm::logf(args[0]));
81                ctx_base.register_native_function("abs", 1, |args| args[0].abs());
82                ctx_base.register_native_function("max", 2, |args| args[0].max(args[1]));
83                ctx_base.register_native_function("min", 2, |args| args[0].min(args[1]));
84                ctx_base.register_native_function("pow", 2, |args| libm::powf(args[0], args[1]));
85                ctx_base.register_native_function("^", 2, |args| libm::powf(args[0], args[1]));
86                ctx_base.register_native_function("exp", 1, |args| libm::expf(args[0]));
87                ctx_base.register_native_function("floor", 1, |args| libm::floorf(args[0]));
88                ctx_base.register_native_function("ceil", 1, |args| libm::ceilf(args[0]));
89                ctx_base.register_native_function("neg", 1, |args| -args[0]);
90                ctx_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
91            }
92            #[cfg(not(feature = "f32"))]
93            {
94                ctx_base.register_native_function("sqrt", 1, |args| libm::sqrt(args[0]));
95                ctx_base.register_native_function("sin", 1, |args| libm::sin(args[0]));
96                ctx_base.register_native_function("cos", 1, |args| libm::cos(args[0]));
97                ctx_base.register_native_function("tan", 1, |args| libm::tan(args[0]));
98                ctx_base.register_native_function("log", 1, |args| libm::log(args[0]));
99                ctx_base.register_native_function("log10", 1, |args| libm::log10(args[0]));
100                ctx_base.register_native_function("ln", 1, |args| libm::log(args[0]));
101                ctx_base.register_native_function("abs", 1, |args| args[0].abs());
102                ctx_base.register_native_function("max", 2, |args| args[0].max(args[1]));
103                ctx_base.register_native_function("min", 2, |args| args[0].min(args[1]));
104                ctx_base.register_native_function("pow", 2, |args| libm::pow(args[0], args[1]));
105                ctx_base.register_native_function("^", 2, |args| libm::pow(args[0], args[1]));
106                ctx_base.register_native_function("exp", 1, |args| libm::exp(args[0]));
107                ctx_base.register_native_function("floor", 1, |args| libm::floor(args[0]));
108                ctx_base.register_native_function("ceil", 1, |args| libm::ceil(args[0]));
109                ctx_base.register_native_function("neg", 1, |args| -args[0]);
110                ctx_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
111            }
112        }
113        
114        let mut evalctx_sum = 0.0;
115        let start = Instant::now();
116        for j in 0..N {
117            // Create a new context for each iteration with the parameter set
118            let mut ctx = ctx_base.clone();
119            ctx.set_parameter("a", j as Real);
120            let ctx_rc = Rc::new(ctx);
121            evalctx_sum += eval_ast(&ast, Some(ctx_rc)).unwrap();
122        }
123        let evalctx_time = start.elapsed();
124        std::hint::black_box(evalctx_sum);
125
126        // Create a mutable context first before wrapping in Rc
127        let mut ctx_interp_base = EvalContext::new();
128        
129        // Only register default math functions if the feature is available
130        #[cfg(not(feature = "no-builtin-math"))]
131        {
132            ctx_interp_base.register_default_math_functions();
133        }
134        
135        // When no-builtin-math is enabled, we need to register the functions manually
136        #[cfg(feature = "no-builtin-math")]
137        {
138            // Register the minimum functions needed for our benchmarks
139            #[cfg(feature = "f32")]
140            {
141                ctx_interp_base.register_native_function("sqrt", 1, |args| libm::sqrtf(args[0]));
142                ctx_interp_base.register_native_function("sin", 1, |args| libm::sinf(args[0]));
143                ctx_interp_base.register_native_function("cos", 1, |args| libm::cosf(args[0]));
144                ctx_interp_base.register_native_function("tan", 1, |args| libm::tanf(args[0]));
145                ctx_interp_base.register_native_function("log", 1, |args| libm::logf(args[0]));
146                ctx_interp_base.register_native_function("log10", 1, |args| libm::log10f(args[0]));
147                ctx_interp_base.register_native_function("ln", 1, |args| libm::logf(args[0]));
148                ctx_interp_base.register_native_function("abs", 1, |args| args[0].abs());
149                ctx_interp_base.register_native_function("max", 2, |args| args[0].max(args[1]));
150                ctx_interp_base.register_native_function("min", 2, |args| args[0].min(args[1]));
151                ctx_interp_base.register_native_function("pow", 2, |args| libm::powf(args[0], args[1]));
152                ctx_interp_base.register_native_function("^", 2, |args| libm::powf(args[0], args[1]));
153                ctx_interp_base.register_native_function("exp", 1, |args| libm::expf(args[0]));
154                ctx_interp_base.register_native_function("floor", 1, |args| libm::floorf(args[0]));
155                ctx_interp_base.register_native_function("ceil", 1, |args| libm::ceilf(args[0]));
156                ctx_interp_base.register_native_function("neg", 1, |args| -args[0]);
157                ctx_interp_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
158            }
159            #[cfg(not(feature = "f32"))]
160            {
161                ctx_interp_base.register_native_function("sqrt", 1, |args| libm::sqrt(args[0]));
162                ctx_interp_base.register_native_function("sin", 1, |args| libm::sin(args[0]));
163                ctx_interp_base.register_native_function("cos", 1, |args| libm::cos(args[0]));
164                ctx_interp_base.register_native_function("tan", 1, |args| libm::tan(args[0]));
165                ctx_interp_base.register_native_function("log", 1, |args| libm::log(args[0]));
166                ctx_interp_base.register_native_function("log10", 1, |args| libm::log10(args[0]));
167                ctx_interp_base.register_native_function("ln", 1, |args| libm::log(args[0]));
168                ctx_interp_base.register_native_function("abs", 1, |args| args[0].abs());
169                ctx_interp_base.register_native_function("max", 2, |args| args[0].max(args[1]));
170                ctx_interp_base.register_native_function("min", 2, |args| args[0].min(args[1]));
171                ctx_interp_base.register_native_function("pow", 2, |args| libm::pow(args[0], args[1]));
172                ctx_interp_base.register_native_function("^", 2, |args| libm::pow(args[0], args[1]));
173                ctx_interp_base.register_native_function("exp", 1, |args| libm::exp(args[0]));
174                ctx_interp_base.register_native_function("floor", 1, |args| libm::floor(args[0]));
175                ctx_interp_base.register_native_function("ceil", 1, |args| libm::ceil(args[0]));
176                ctx_interp_base.register_native_function("neg", 1, |args| -args[0]);
177                ctx_interp_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
178            }
179        }
180        
181        // Enable AST cache for the base context
182        ctx_interp_base.enable_ast_cache();
183        
184        let mut interp_sum = 0.0;
185        let start = Instant::now();
186        for j in 0..N {
187            // Create a new context for each iteration with the parameter set
188            let mut ctx_interp = ctx_interp_base.clone();
189            ctx_interp.set_parameter("a", j as Real);
190            let ctx_interp_rc = Rc::new(ctx_interp);
191            interp_sum += exp_rs::engine::interp(expr, Some(ctx_interp_rc)).unwrap();
192        }
193        let interp_eval_time = start.elapsed();
194        std::hint::black_box(interp_sum);
195
196        let mut native_sum = 0.0;
197        let start = Instant::now();
198        for j in 0..N {
199            native_sum += native_func(j as Real);
200        }
201        let native_time = start.elapsed();
202        std::hint::black_box(native_sum);
203
204        let evalctx_us = evalctx_time.as_micros();
205        let interp_us = interp_eval_time.as_micros();
206        let native_us = native_time.as_micros();
207
208        let slowdown_evalctx_vs_native = if native_us > 0 {
209            evalctx_us as f64 / native_us as f64
210        } else {
211            f64::NAN
212        };
213        let slowdown_interp_vs_native = if native_us > 0 {
214            interp_us as f64 / native_us as f64
215        } else {
216            f64::NAN
217        };
218        let slowdown_interp_vs_evalctx = if evalctx_us > 0 {
219            interp_us as f64 / evalctx_us as f64
220        } else {
221            f64::NAN
222        };
223
224        println!("evalctx - time: {} us, {:.2}x slower than native", evalctx_us, slowdown_evalctx_vs_native);
225        println!("interp - time: {} us, {:.2}x slower than native", interp_us, slowdown_interp_vs_native);
226        println!("native - time: {} us", native_us);
227        println!("evalctx vs native: {:.2}x slower", slowdown_evalctx_vs_native);
228        println!("interp vs native: {:.2}x slower", slowdown_interp_vs_native);
229        println!("interp vs evalctx: {:.2}x slower\n", slowdown_interp_vs_evalctx);
230    }
231}
Source

pub fn disable_ast_cache(&self)

Disables AST caching and clears the cache.

This is useful if you want to free up memory or if you want to force re-parsing of expressions.

§Examples
use exp_rs::context::EvalContext;

let ctx = EvalContext::new();
ctx.enable_ast_cache();
// ... use the context with AST caching ...
ctx.disable_ast_cache();
Source

pub fn clear_ast_cache(&self)

Clear the AST cache if enabled.

Source

pub fn register_default_math_functions(&mut self)

Registers all built-in math functions as native functions in the context.

This is only available if the no-builtin-math feature is not enabled.

§Usage
let mut ctx = EvalContext::new();
ctx.register_default_math_functions();

After calling this, you can override any built-in by registering your own native function with the same name using register_native_function.

§Feature: no-builtin-math

If the no-builtin-math feature is enabled, this method is not available and you must register all required math functions yourself.

Examples found in repository?
examples/benchmark_compare.rs (line 65)
30fn main() {
31    let benchmarks = [
32        ("sqrt(a^1.5+a^2.5)", native_sqrt_expr as fn(Real) -> Real),
33        ("a+5", native_a_plus_5),
34        ("a+(5*2)", native_a_plus_5_times_2),
35        ("(a+5)*2", native_a_plus_5_all_times_2),
36        ("(1/(a+1)+2/(a+2)+3/(a+3))", native_sum_fractions),
37        // Additional benchmark expressions with other functions:
38        ("sin(a)", |a: Real| a.sin()),
39        ("cos(a)", |a: Real| a.cos()),
40        ("tan(a)", |a: Real| a.tan()),
41        ("log(a+10)", |a: Real| (a+10.0).log10()),
42        ("ln(a+10)", |a: Real| (a+10.0).ln()),
43        ("abs(a-50)", |a: Real| (a-50.0).abs()),
44        ("max(a,100-a)", |a: Real| a.max(100.0-a)),
45        ("min(a,100-a)", |a: Real| a.min(100.0-a)),
46        ("pow(a,1.5)", |a: Real| a.powf(1.5)),
47        ("exp(a/100.0)", |a: Real| (a/100.0).exp()),
48        ("floor(a/3.1)", |a: Real| (a/3.1).floor()),
49        ("ceil(a/3.1)", |a: Real| (a/3.1).ceil()),
50        ("fmod(a,7)", |a: Real| a % 7.0),
51        ("neg(a)", |a: Real| -a),
52    ];
53
54    for (expr, native_func) in benchmarks.iter() {
55        println!("Benchmarking: {}", expr);
56
57        let ast = parse_expression(expr).expect("parse failed");
58
59        // Create a mutable context first before wrapping in Rc
60        let mut ctx_base = EvalContext::new();
61        
62        // Only register default math functions if the feature is available
63        #[cfg(not(feature = "no-builtin-math"))]
64        {
65            ctx_base.register_default_math_functions();
66        }
67        
68        // When no-builtin-math is enabled, we need to register the functions manually
69        #[cfg(feature = "no-builtin-math")]
70        {
71            // Register the minimum functions needed for our benchmarks
72            #[cfg(feature = "f32")]
73            {
74                ctx_base.register_native_function("sqrt", 1, |args| libm::sqrtf(args[0]));
75                ctx_base.register_native_function("sin", 1, |args| libm::sinf(args[0]));
76                ctx_base.register_native_function("cos", 1, |args| libm::cosf(args[0]));
77                ctx_base.register_native_function("tan", 1, |args| libm::tanf(args[0]));
78                ctx_base.register_native_function("log", 1, |args| libm::logf(args[0]));
79                ctx_base.register_native_function("log10", 1, |args| libm::log10f(args[0]));
80                ctx_base.register_native_function("ln", 1, |args| libm::logf(args[0]));
81                ctx_base.register_native_function("abs", 1, |args| args[0].abs());
82                ctx_base.register_native_function("max", 2, |args| args[0].max(args[1]));
83                ctx_base.register_native_function("min", 2, |args| args[0].min(args[1]));
84                ctx_base.register_native_function("pow", 2, |args| libm::powf(args[0], args[1]));
85                ctx_base.register_native_function("^", 2, |args| libm::powf(args[0], args[1]));
86                ctx_base.register_native_function("exp", 1, |args| libm::expf(args[0]));
87                ctx_base.register_native_function("floor", 1, |args| libm::floorf(args[0]));
88                ctx_base.register_native_function("ceil", 1, |args| libm::ceilf(args[0]));
89                ctx_base.register_native_function("neg", 1, |args| -args[0]);
90                ctx_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
91            }
92            #[cfg(not(feature = "f32"))]
93            {
94                ctx_base.register_native_function("sqrt", 1, |args| libm::sqrt(args[0]));
95                ctx_base.register_native_function("sin", 1, |args| libm::sin(args[0]));
96                ctx_base.register_native_function("cos", 1, |args| libm::cos(args[0]));
97                ctx_base.register_native_function("tan", 1, |args| libm::tan(args[0]));
98                ctx_base.register_native_function("log", 1, |args| libm::log(args[0]));
99                ctx_base.register_native_function("log10", 1, |args| libm::log10(args[0]));
100                ctx_base.register_native_function("ln", 1, |args| libm::log(args[0]));
101                ctx_base.register_native_function("abs", 1, |args| args[0].abs());
102                ctx_base.register_native_function("max", 2, |args| args[0].max(args[1]));
103                ctx_base.register_native_function("min", 2, |args| args[0].min(args[1]));
104                ctx_base.register_native_function("pow", 2, |args| libm::pow(args[0], args[1]));
105                ctx_base.register_native_function("^", 2, |args| libm::pow(args[0], args[1]));
106                ctx_base.register_native_function("exp", 1, |args| libm::exp(args[0]));
107                ctx_base.register_native_function("floor", 1, |args| libm::floor(args[0]));
108                ctx_base.register_native_function("ceil", 1, |args| libm::ceil(args[0]));
109                ctx_base.register_native_function("neg", 1, |args| -args[0]);
110                ctx_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
111            }
112        }
113        
114        let mut evalctx_sum = 0.0;
115        let start = Instant::now();
116        for j in 0..N {
117            // Create a new context for each iteration with the parameter set
118            let mut ctx = ctx_base.clone();
119            ctx.set_parameter("a", j as Real);
120            let ctx_rc = Rc::new(ctx);
121            evalctx_sum += eval_ast(&ast, Some(ctx_rc)).unwrap();
122        }
123        let evalctx_time = start.elapsed();
124        std::hint::black_box(evalctx_sum);
125
126        // Create a mutable context first before wrapping in Rc
127        let mut ctx_interp_base = EvalContext::new();
128        
129        // Only register default math functions if the feature is available
130        #[cfg(not(feature = "no-builtin-math"))]
131        {
132            ctx_interp_base.register_default_math_functions();
133        }
134        
135        // When no-builtin-math is enabled, we need to register the functions manually
136        #[cfg(feature = "no-builtin-math")]
137        {
138            // Register the minimum functions needed for our benchmarks
139            #[cfg(feature = "f32")]
140            {
141                ctx_interp_base.register_native_function("sqrt", 1, |args| libm::sqrtf(args[0]));
142                ctx_interp_base.register_native_function("sin", 1, |args| libm::sinf(args[0]));
143                ctx_interp_base.register_native_function("cos", 1, |args| libm::cosf(args[0]));
144                ctx_interp_base.register_native_function("tan", 1, |args| libm::tanf(args[0]));
145                ctx_interp_base.register_native_function("log", 1, |args| libm::logf(args[0]));
146                ctx_interp_base.register_native_function("log10", 1, |args| libm::log10f(args[0]));
147                ctx_interp_base.register_native_function("ln", 1, |args| libm::logf(args[0]));
148                ctx_interp_base.register_native_function("abs", 1, |args| args[0].abs());
149                ctx_interp_base.register_native_function("max", 2, |args| args[0].max(args[1]));
150                ctx_interp_base.register_native_function("min", 2, |args| args[0].min(args[1]));
151                ctx_interp_base.register_native_function("pow", 2, |args| libm::powf(args[0], args[1]));
152                ctx_interp_base.register_native_function("^", 2, |args| libm::powf(args[0], args[1]));
153                ctx_interp_base.register_native_function("exp", 1, |args| libm::expf(args[0]));
154                ctx_interp_base.register_native_function("floor", 1, |args| libm::floorf(args[0]));
155                ctx_interp_base.register_native_function("ceil", 1, |args| libm::ceilf(args[0]));
156                ctx_interp_base.register_native_function("neg", 1, |args| -args[0]);
157                ctx_interp_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
158            }
159            #[cfg(not(feature = "f32"))]
160            {
161                ctx_interp_base.register_native_function("sqrt", 1, |args| libm::sqrt(args[0]));
162                ctx_interp_base.register_native_function("sin", 1, |args| libm::sin(args[0]));
163                ctx_interp_base.register_native_function("cos", 1, |args| libm::cos(args[0]));
164                ctx_interp_base.register_native_function("tan", 1, |args| libm::tan(args[0]));
165                ctx_interp_base.register_native_function("log", 1, |args| libm::log(args[0]));
166                ctx_interp_base.register_native_function("log10", 1, |args| libm::log10(args[0]));
167                ctx_interp_base.register_native_function("ln", 1, |args| libm::log(args[0]));
168                ctx_interp_base.register_native_function("abs", 1, |args| args[0].abs());
169                ctx_interp_base.register_native_function("max", 2, |args| args[0].max(args[1]));
170                ctx_interp_base.register_native_function("min", 2, |args| args[0].min(args[1]));
171                ctx_interp_base.register_native_function("pow", 2, |args| libm::pow(args[0], args[1]));
172                ctx_interp_base.register_native_function("^", 2, |args| libm::pow(args[0], args[1]));
173                ctx_interp_base.register_native_function("exp", 1, |args| libm::exp(args[0]));
174                ctx_interp_base.register_native_function("floor", 1, |args| libm::floor(args[0]));
175                ctx_interp_base.register_native_function("ceil", 1, |args| libm::ceil(args[0]));
176                ctx_interp_base.register_native_function("neg", 1, |args| -args[0]);
177                ctx_interp_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
178            }
179        }
180        
181        // Enable AST cache for the base context
182        ctx_interp_base.enable_ast_cache();
183        
184        let mut interp_sum = 0.0;
185        let start = Instant::now();
186        for j in 0..N {
187            // Create a new context for each iteration with the parameter set
188            let mut ctx_interp = ctx_interp_base.clone();
189            ctx_interp.set_parameter("a", j as Real);
190            let ctx_interp_rc = Rc::new(ctx_interp);
191            interp_sum += exp_rs::engine::interp(expr, Some(ctx_interp_rc)).unwrap();
192        }
193        let interp_eval_time = start.elapsed();
194        std::hint::black_box(interp_sum);
195
196        let mut native_sum = 0.0;
197        let start = Instant::now();
198        for j in 0..N {
199            native_sum += native_func(j as Real);
200        }
201        let native_time = start.elapsed();
202        std::hint::black_box(native_sum);
203
204        let evalctx_us = evalctx_time.as_micros();
205        let interp_us = interp_eval_time.as_micros();
206        let native_us = native_time.as_micros();
207
208        let slowdown_evalctx_vs_native = if native_us > 0 {
209            evalctx_us as f64 / native_us as f64
210        } else {
211            f64::NAN
212        };
213        let slowdown_interp_vs_native = if native_us > 0 {
214            interp_us as f64 / native_us as f64
215        } else {
216            f64::NAN
217        };
218        let slowdown_interp_vs_evalctx = if evalctx_us > 0 {
219            interp_us as f64 / evalctx_us as f64
220        } else {
221            f64::NAN
222        };
223
224        println!("evalctx - time: {} us, {:.2}x slower than native", evalctx_us, slowdown_evalctx_vs_native);
225        println!("interp - time: {} us, {:.2}x slower than native", interp_us, slowdown_interp_vs_native);
226        println!("native - time: {} us", native_us);
227        println!("evalctx vs native: {:.2}x slower", slowdown_evalctx_vs_native);
228        println!("interp vs native: {:.2}x slower", slowdown_interp_vs_native);
229        println!("interp vs evalctx: {:.2}x slower\n", slowdown_interp_vs_evalctx);
230    }
231}
Source

pub fn get_variable(&self, name: &str) -> Option<Real>

Register a native function with the context.

§Overriding Built-ins

If a function with the same name as a built-in is registered, the user-defined function will take precedence over the built-in. This allows users to override any built-in math function at runtime.

§Disabling Built-ins

If the no-builtin-math feature is enabled, built-in math functions are not available, and users must register their own implementations for all required functions.

§Example
let mut ctx = EvalContext::new();
// Override the "sin" function
ctx.register_native_function("sin", 1, |args| 42.0);
Source

pub fn get_constant(&self, name: &str) -> Option<Real>

Source

pub fn get_array(&self, name: &str) -> Option<&Vec<Real>>

Source

pub fn get_attribute_map(&self, base: &str) -> Option<&HashMap<String, Real>>

Source

pub fn get_native_function(&self, name: &str) -> Option<&NativeFunction<'_>>

Source

pub fn get_user_function(&self, name: &str) -> Option<&UserFunction>

Source

pub fn get_expression_function(&self, name: &str) -> Option<&ExpressionFunction>

Trait Implementations§

Source§

impl<'a> Clone for EvalContext<'a>

Source§

fn clone(&self) -> Self

Returns a copy of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<'a> Default for EvalContext<'a>

Source§

fn default() -> EvalContext<'a>

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<'a> !Freeze for EvalContext<'a>

§

impl<'a> !RefUnwindSafe for EvalContext<'a>

§

impl<'a> !Send for EvalContext<'a>

§

impl<'a> !Sync for EvalContext<'a>

§

impl<'a> Unpin for EvalContext<'a>

§

impl<'a> !UnwindSafe for EvalContext<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.