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>
impl<'a> EvalContext<'a>
Sourcepub fn new() -> Self
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?
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
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}
Sourcepub fn set_parameter(&mut self, name: &str, value: Real) -> Option<Real>
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 variablevalue
: 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?
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}
Sourcepub fn register_native_function<F>(
&mut self,
name: &str,
arity: usize,
implementation: F,
)
pub fn register_native_function<F>( &mut self, name: &str, arity: usize, implementation: F, )
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 expressionsarity
: The number of arguments the function expectsimplementation
: 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?
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}
Sourcepub fn register_expression_function(
&mut self,
name: &str,
params: &[&str],
expression: &str,
) -> Result<(), ExprError>
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 expressionsparams
: The names of the parameters the function acceptsexpression
: 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?
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}
Sourcepub fn enable_ast_cache(&self)
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?
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}
Sourcepub fn disable_ast_cache(&self)
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();
Sourcepub fn clear_ast_cache(&self)
pub fn clear_ast_cache(&self)
Clear the AST cache if enabled.
Sourcepub fn register_default_math_functions(&mut self)
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?
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}
Sourcepub fn get_variable(&self, name: &str) -> Option<Real>
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);