Added converge builtin function

This commit is contained in:
Sebastian Schmidt 2019-03-15 12:04:01 +02:00
parent e53832ca4b
commit 1e5e9a3ae9
3 changed files with 86 additions and 2 deletions

View File

@ -16,7 +16,8 @@ Features
Supported operators: `!` `!=` `""` `''` `()` `[]` `,` `>` `<` `>=` `<=` `==` Supported operators: `!` `!=` `""` `''` `()` `[]` `,` `>` `<` `>=` `<=` `==`
`+` `-` `*` `/` `%` `&&` `||` `n..m`. `+` `-` `*` `/` `%` `&&` `||` `n..m`.
Built-in functions: `min()` `max()` `len()` `is_empty()` `array()`. Built-in functions: `min()` `max()` `len()` `is_empty()` `array()` `converge()`.
See the `builtin` module for a detailed description of each.
Where can eval be used? Where can eval be used?
----------------------- -----------------------

View File

@ -14,10 +14,35 @@ impl BuiltIn {
functions.insert("len".to_owned(), create_len_fuction()); functions.insert("len".to_owned(), create_len_fuction());
functions.insert("is_empty".to_owned(), create_is_empty_fuction()); functions.insert("is_empty".to_owned(), create_is_empty_fuction());
functions.insert("array".to_owned(), create_array_function()); functions.insert("array".to_owned(), create_array_function());
functions.insert("converge".to_owned(), create_converge_function());
functions functions
} }
} }
fn expect_number_f64(value: &Value) -> Result<f64, Error> {
if let Some(number) = value.as_f64() {
Ok(number)
} else {
Err(Error::Custom(format!("Expected number that can be represented as f64. But the given is: {:?}", value)))
}
}
fn expect_normal_f64(number: f64) -> Result<f64, Error> {
if number.is_normal() {
Ok(number)
} else {
Err(Error::Custom(format!("Expected normal number. But the given is: {:?}", number)))
}
}
fn expect_finite_f64(number: f64) -> Result<f64, Error> {
if number.is_finite() {
Ok(number)
} else {
Err(Error::Custom(format!("Expected finite number. But the given is: {:?}", number)))
}
}
#[derive(PartialEq)] #[derive(PartialEq)]
enum Compare { enum Compare {
Min, Min,
@ -116,3 +141,60 @@ fn create_len_fuction() -> Function {
fn create_array_function() -> Function { fn create_array_function() -> Function {
Function::new(|values| Ok(to_value(values))) Function::new(|values| Ok(to_value(values)))
} }
/// Converges exponentially agains `conv_y`, starting from `start_y`.
/// The first four parameters parameterize a function `f`, the last one is the `x`-value where the function should be evaluated.
/// The result of the call is `f(x)`.
///
/// The parameters `start_x` and `start_y` set the starting point of the function.
/// It holds that `f(start_x) = start_y`.
/// The parameter `conv_y` is the convergence target value.
/// It is never reached (assuming no numerical errors).
/// The parameter `step_x` is the "speed" of the convergence, that is how fast the function converges against `conv_y`.
/// In detail, the absolute difference between `start_y` and `conv_y` halves if `x` is increased by `step_x`.
///
/// All parameters are expected to be numbers.
/// The parameters `start_x`, `start_y` and `conv_y` are expected to be finite.
/// The parameter `step_x` is expected to be `normal`.
///
/// # Examples
///
/// The function `2^(-x)` is expressed as `conv(0, 1, 1, 0, x)`.
/// This is the same as `converge(1, 0.5, 1, 0, x)`.
fn create_converge_function() -> Function {
Function {
max_args: Some(5),
min_args: Some(5),
compiled: Box::new(|values| {
let start_x = expect_finite_f64(expect_number_f64(&values[0])?)?;
let start_y = expect_finite_f64(expect_number_f64(&values[1])?)?;
let step_x = expect_normal_f64(expect_number_f64(&values[2])?)?;
let conv_y = expect_finite_f64(expect_number_f64(&values[3])?)?;
let x = expect_number_f64(&values[4])?;
let units = (x - start_x) / step_x;
let interpolation_factor = 2.0_f64.powf(-units);
Ok(to_value(interpolation_factor * start_y + (1.0 - interpolation_factor) * conv_y))
}),
}
}
#[cfg(test)]
mod test {
use crate::{Expr, to_value};
#[test]
fn test_conv() {
let valid_test_cases = vec![
("converge(0, 4, 10, 0, 0)", "4.0"),
("converge(0, 4, 10, 0, 10)", "2.0"),
("converge(0, 4, 10, 0, 5)", "2.8284271247461900976033774484193961571393437507538961"),
("converge(0, 4, 10, 0, 20)", "1.0"),
("converge(0, 4, 10, 0, 0-10)", "8.0"),
];
for (term, result) in valid_test_cases {
assert_eq!(Expr::new(format!("({term} < {result} * 1.0001) && ({term} > {result} / 1.0001)", term = term, result = result)).exec(), Ok(to_value(true)));
}
}
}

View File

@ -3,7 +3,8 @@
//! Supported operators: `!` `!=` `""` `''` `()` `[]` `.` `,` `>` `<` `>=` `<=` //! Supported operators: `!` `!=` `""` `''` `()` `[]` `.` `,` `>` `<` `>=` `<=`
//! `==` `+` `-` `*` `/` `%` `&&` `||` `n..m`. //! `==` `+` `-` `*` `/` `%` `&&` `||` `n..m`.
//! //!
//! Built-in functions: `min()` `max()` `len()` `is_empty()` `array()`. //! Built-in functions: `min()` `max()` `len()` `is_empty()` `array()` `converge()`.
//! See the `builtin` module for a detailed description of each.
//! //!
//! ## Examples //! ## Examples
//! //!