#[cfg(test)] mod utils; use exmex::{exerr, DeepEx}; #[cfg(test)] use exmex::{ literal_matcher_from_pattern, ops_factory, parse, prelude::*, Calculate, ExError, ExResult, MatchLiteral, {BinOp, FloatOpsFactory, MakeOperators, Operator}, }; use regex::Regex; use std::fs::{self, File}; use std::io::{self, BufRead}; use std::iter::repeat; use utils::assert_float_eq_f64; use std::fmt::Debug; #[cfg(test)] use std::{ iter::once, ops::{BitAnd, BitOr}, str::FromStr, }; #[test] fn test_version() { // make sure the version strings in the Cargo.toml and lib.rs coincide let file = File::open("src/lib.rs").unwrap(); let version_line_lib = io::BufReader::new(file) .lines() .find(|line| line.as_ref().unwrap().contains("html_root_url")) .unwrap() .unwrap(); let re_version = Regex::new(r#"[0-9]{1,4}\.[0-9]{1,4}\.[0-9]{1,4}"#).unwrap(); let match_lib = re_version.find(&version_line_lib).unwrap().as_str(); let toml_string = fs::read_to_string("Cargo.toml").unwrap(); let cargo_toml: toml::Value = toml::from_str(&toml_string).unwrap(); let package = cargo_toml.get("package").unwrap().as_table().unwrap(); let version = package.get("version").unwrap().as_str().unwrap(); assert_eq!(match_lib, version); } #[test] fn test_display() -> ExResult<()> { let flatex = FlatEx::<f64>::parse("sin(var)/5")?; println!("{}", flatex); assert_eq!(format!("{}", flatex), "sin(var)/5"); Ok(()) } #[test] fn test_expr() -> ExResult<()> { fn test(sut: &str, vars: &[f64], reference: f64) -> ExResult<()> { fn inner_test<'a, E: Express<'a, f64>>( expr: &E, vars: &[f64], reference: f64, more_vars: bool, ) -> ExResult<()> { utils::assert_float_eq_f64(expr.eval(vars)?, reference); utils::assert_float_eq_f64(expr.eval_relaxed(vars)?, reference); let n_vars = vars.len(); if n_vars > 0 { assert!(expr.eval(&vars[0..n_vars - 1]).is_err()); } if more_vars { for n_additional_vars in 1..11 { let more_vars = vars .iter() .copied() .chain( repeat(10) .map(|exp| { (rand::random::<f64>() * n_additional_vars as f64).powi(exp) }) .take(n_additional_vars), ) .collect::<Vec<_>>(); assert!(expr.eval(&more_vars).is_err()); utils::assert_float_eq_f64(expr.eval_relaxed(&more_vars)?, reference); } } println!("...ok."); Ok(()) } println!("testing {}...", sut); let flatex = FlatEx::<f64>::parse(sut)?; inner_test(&flatex, vars, reference, true)?; inner_test(&flatex.to_deepex()?, vars, reference, false)?; let deepex = DeepEx::<f64>::parse(sut)?; inner_test(&deepex, vars, reference, false)?; for (vidx, vn) in deepex.var_names().iter().enumerate() { let sub_num = 3.7; fn sub<'a>(v: &str, vn_: &str, x: f64) -> Option<DeepEx<'a, f64>> { if v == vn_ { Some(DeepEx::from_num(x)) } else { None } } let mut sub_closure = |v: &str| sub(v, vn, sub_num); let subs = deepex.clone().subs(&mut sub_closure)?; let vars_for_subs = vars .iter() .enumerate() .filter(|(i, _)| *i != vidx) .map(|(_, x)| *x) .collect::<Vec<_>>(); let mut vars_substituted = vars.iter().map(|x| *x).collect::<Vec<_>>(); vars_substituted[vidx] = sub_num; assert!((subs.eval(&vars_for_subs)? - deepex.eval(&vars_substituted)?).abs() < 1e-12); } inner_test(&FlatEx::from_deepex(deepex)?, vars, reference, true)?; Ok(()) } test("sin(1)", &[], 1.0f64.sin())?; test("2*3^2", &[], 2.0 * 3.0f64.powi(2))?; test("(3.7)-2.0*1.0/{x}", &[1.5], 3.7 - 2.0 / 1.5)?; test("sin(-(sin(2)))*2", &[], (-(2f64.sin())).sin() * 2.0)?; test("sin(-(0.7))", &[], (-0.7f64).sin())?; test("sin(-0.7)", &[], (-0.7f64).sin())?; test("sin(-x)", &[0.7], (-0.7f64).sin())?; test("1.3+(-0.7)", &[], 0.6)?; test("2-1/2", &[], 2.0 - 1.0 / 2.0)?; test("ln(log2(2))*tan(2)+exp(1.5)", &[], 4.4816890703380645)?; test("sin(0)", &[], 0f64.sin())?; test("1-(1-2)", &[], 2.0)?; test("1-(1-x)", &[2.0], 2.0)?; test("1*sin(2-0.1) + x", &[1.0], 1.0 + 1.9f64.sin())?; test("sin(6)", &[], -0.27941549819892586)?; test("sin(x+2)", &[5.0], 0.6569865987187891)?; test("sin((x+1))", &[5.0], -0.27941549819892586)?; test("sin(y^(x+1))", &[5.0, 2.0], 0.9200260381967907)?; test("sin(((a*y^(x+1))))", &[0.5, 5.0, 2.0], 0.5514266812416906)?; test( "sin(((cos((a*y^(x+1))))))", &[0.5, 5.0, 2.0], 0.7407750251209115, )?; test("sin(cos(x+1))", &[5.0], 0.819289219220601)?; test( "5*{χ} + 4*log2(ln(1.5+γ))*({χ}*-(tan(cos(sin(652.2-{γ}))))) + 3*{χ}", &[1.2, 1.0], 8.040556934857268, )?; test("y-2*1/x", &[1.5, 0.2532], -1.0801333333333334)?; test( "5*sin(x * (4-y^(2-x) * 3 * cos(x-2*(3.7-1/(y-2*1/cos(sin(x*y))))))*x)", &[1.5, 0.2532], 0.3102594604194633, )?; test( "5*sin(x * (4-y^(2-x) * 3 * cos(x-2*(y-1/(y-2*1/cos(sin(x*y))))))*x)", &[1.5, 0.2532], -3.1164569260604176, )?; test("sin(x)+sin(y)+sin(z)", &[1.0, 2.0, 3.0], 1.8918884196934453)?; test("x*0.2*5.0/4.0+x*2.0*4.0*1.0*1.0*1.0*1.0*1.0*1.0*1.0+7.0*sin(y)-z/sin(3.0/2.0/(1.0-x*4.0*1.0*1.0*1.0*1.0))", &[1.0, 2.0, 3.0], 20.872570916580237)?; test("sin(-(1.0))", &[], -0.8414709848078965)?; test("x*0.02*(3-(2*y))", &[1.0, 2.0], -0.02)?; test("x*((x*1)-0.98)*(0.5*-y)", &[1.0, 2.0], -0.02)?; test("x*0.02*sin(3*(2*y))", &[1.0, 2.0], 0.02 * 12.0f64.sin())?; test( "x*0.02*sin(-(3.0*(2.0*sin(x-1.0/(sin(y*5.0)+(5.0-1.0/z))))))", &[1.0, 2.0, 3.0], 0.01661860154948708, )?; let n_vars = 65; let s = (0..n_vars) .map(|i| format!("{{{}}}", i.to_string())) .collect::<Vec<_>>() .join("+"); test(s.as_str(), &vec![1.0; n_vars], n_vars as f64)?; Ok(()) } #[test] fn test_readme() -> ExResult<()> { fn readme() -> ExResult<()> { let result = exmex::eval_str::<f64>("E^(2*Ï€-Ï„)")?; assert!((result - 1.0).abs() < 1e-12); let expr = parse::<f64>("2*x^3-4/y")?; let result = expr.eval(&[2.0, 4.0])?; assert!((result - 15.0).abs() < 1e-12); Ok(()) } fn readme_int() -> ExResult<()> { ops_factory!( BitwiseOpsFactory, u32, Operator::make_bin( "|", BinOp { apply: |a, b| a | b, prio: 0, is_commutative: true } ), Operator::make_unary("!", |a| !a) ); let expr = FlatEx::<_, BitwiseOpsFactory>::parse("!(a|b)")?; let result = expr.eval(&[0, 1])?; assert_eq!(result, u32::MAX - 1); Ok(()) } readme()?; readme_int()?; Ok(()) } #[test] fn test_variables_curly_space_names() -> ExResult<()> { let sut = "{x } + { y }"; let expr = FlatEx::<f32>::parse(sut)?; utils::assert_float_eq::<f32>(expr.eval(&[1.0, 1.0])?, 2.0, 1e-6, 0.0, ""); assert_eq!(expr.unparse(), sut); let sut = "2*(4*{ xasd sa } + { y z}^2)"; let expr = FlatEx::<f32>::parse(sut)?; utils::assert_float_eq::<f32>(expr.eval(&[2.0, 3.0])?, 34.0, 1e-6, 0.0, ""); assert_eq!(expr.unparse(), sut); Ok(()) } #[test] fn test_variables_curly() -> ExResult<()> { let sut = "5*{x} + 4*log2(ln(1.5+{gamma}))*({x}*-(tan(cos(sin(652.2-{gamma}))))) + 3*{x}"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[1.2, 1.0]).unwrap(), 8.040556934857268); let sut = "sin({myvwmlf4i58eo;w/-sin(a)r_25})"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[std::f64::consts::FRAC_PI_2]).unwrap(), 1.0); let sut = "((sin({myvar_25})))"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[std::f64::consts::FRAC_PI_2]).unwrap(), 1.0); Ok(()) } #[test] fn test_variables_non_ascii() -> ExResult<()> { let sut = "5*Ï‚"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[1.2]).unwrap(), 6.0); let sut = "5*{χ} + 4*log2(ln(1.5+γ))*({χ}*-(tan(cos(sin(652.2-{γ}))))) + 3*{χ}"; let expr = FlatEx::<f64>::parse(sut)?; println!("{}", expr); utils::assert_float_eq_f64(expr.eval(&[1.2, 1.0]).unwrap(), 8.040556934857268); let sut = "sin({myvwmlf4i😎8eo;w/-sin(a)r_25})"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[std::f64::consts::FRAC_PI_2]).unwrap(), 1.0); let sut = "((sin({myvar_25✔})))"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[std::f64::consts::FRAC_PI_2]).unwrap(), 1.0); #[derive(Clone, Debug, Default, PartialEq, Eq)] struct Thumbs { val: bool, } impl BitOr for Thumbs { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { Self { val: self.val || rhs.val, } } } impl BitAnd for Thumbs { type Output = Self; fn bitand(self, rhs: Self) -> Self::Output { Self { val: self.val && rhs.val, } } } impl FromStr for Thumbs { type Err = ExError; fn from_str(s: &str) -> Result<Self, Self::Err> { if s == "ðŸ‘" { Ok(Self { val: true }) } else if s == "👎" { Ok(Self { val: false }) } else { Err(exerr!("cannot parse {} to `Thumbs`", s)) } } } ops_factory!( UnicodeOpsFactory, Thumbs, Operator::make_bin( "οÏ", BinOp { apply: |a, b| a | b, prio: 0, is_commutative: true, } ), Operator::make_bin( "ανδ", BinOp { apply: |a, b| a & b, prio: 0, is_commutative: true, } ), Operator::make_constant("γ", Thumbs { val: false }) ); literal_matcher_from_pattern!(ThumbsMatcher, r"^(ðŸ‘|👎)"); let sut = "γ Î¿Ï ðŸ‘οÏ👎"; let expr = FlatEx::<_, UnicodeOpsFactory, ThumbsMatcher>::parse(sut)?; assert_eq!(expr.eval(&[]).unwrap(), Thumbs { val: true }); let sut = "(👠ανδ👎)Î¿Ï ðŸ‘"; let expr = FlatEx::<_, UnicodeOpsFactory, ThumbsMatcher>::parse(sut)?; assert_eq!(expr.eval(&[]).unwrap(), Thumbs { val: true }); let sut = "(ðŸ‘ανδ 👎)οÏγαβ23"; let expr = FlatEx::<_, UnicodeOpsFactory, ThumbsMatcher>::parse(sut)?; assert_eq!(expr.eval(&[Thumbs { val: true }])?, Thumbs { val: true }); assert_eq!(expr.eval(&[Thumbs { val: false }])?, Thumbs { val: false }); Ok(()) } #[test] fn test_variables() -> ExResult<()> { let sut = "sin ({x})+(((cos({y}) ^ (sin({z})))*ln(cos({y})))*cos({z}))"; let expr = FlatEx::<f64>::parse(sut)?; assert_eq!(expr.var_names().len(), 3usize); let reference = |x: f64, y: f64, z: f64| x.sin() + y.cos().powf(z.sin()) * y.cos().ln() * z.cos(); utils::assert_float_eq_f64( expr.eval(&[-0.18961918881278095, -6.383306547710852, 3.1742139703464503]) .unwrap(), reference(-0.18961918881278095, -6.383306547710852, 3.1742139703464503), ); let sut = "sin(sin(x - 1 / sin(y * 5)) + (5.0 - 1/z))"; let expr = FlatEx::<f64>::parse(sut)?; let reference = |x: f64, y: f64, z: f64| ((x - 1.0 / (y * 5.0).sin()).sin() + (5.0 - 1.0 / z)).sin(); utils::assert_float_eq_f64( expr.eval(&[1.0, 2.0, 4.0]).unwrap(), reference(1.0, 2.0, 4.0), ); let sut = "0.02*sin( - (3*(2*(5.0 - 1/z))))"; let expr = FlatEx::<f64>::parse(sut)?; let reference = |z: f64| 0.02 * (-(3.0 * (2.0 * (5.0 - 1.0 / z)))).sin(); utils::assert_float_eq_f64(expr.eval(&[4.0]).unwrap(), reference(4.0)); let sut = "y + 1 + 0.5 * x"; let expr = FlatEx::<f64>::parse(sut)?; assert_eq!(expr.var_names().len(), 2usize); utils::assert_float_eq_f64(expr.eval(&[3.0, 1.0]).unwrap(), 3.5); let sut = " -(-(1+x))"; let expr = FlatEx::<f64>::parse(sut)?; assert_eq!(expr.var_names().len(), 1usize); utils::assert_float_eq_f64(expr.eval(&[1.0]).unwrap(), 2.0); let sut = " sin(cos(-3.14159265358979*x))"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[1.0]).unwrap(), -0.841470984807896); let sut = "5*sin(x * (4-y^(2-x) * 3 * cos(x-2*(y-1/(y-2*1/cos(sin(x*y))))))*x)"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[1.5, 0.2532]).unwrap(), -3.1164569260604176); let sut = "5*x + 4*y + 3*x"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[1.0, 0.0]).unwrap(), 8.0); let sut = "5*x + 4*y"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[0.0, 1.0]).unwrap(), 4.0); let sut = "5*x + 4*y + x^2"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[2.5, 3.7]).unwrap(), 33.55); utils::assert_float_eq_f64(expr.eval(&[12.0, 9.3]).unwrap(), 241.2); let sut = "2*(4*x + y^2)"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[2.0, 3.0]).unwrap(), 34.0); let sut = "sin(myvar_25)"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[std::f64::consts::FRAC_PI_2]).unwrap(), 1.0); let sut = "((sin(myvar_25)))"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[std::f64::consts::FRAC_PI_2]).unwrap(), 1.0); let sut = "(0 * myvar_25 + cos(x))"; let expr = FlatEx::<f64>::parse(sut)?; let expr = expr.to_deepex()?; utils::assert_float_eq_f64( expr.eval(&[std::f64::consts::FRAC_PI_2, std::f64::consts::PI]) .unwrap(), -1.0, ); let sut = "(-x^2)"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[1.0]).unwrap(), 1.0); let sut = "ln(x) + 2* (-x^2 + sin(4*y))"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[2.5, 3.7]).unwrap(), 14.992794866624788); let sut = "-sqrt(x)/(tanh(5-x)*2) + floor(2.4)* 1/asin(sin(4*sinh(y)))"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64( expr.eval(&[2.5, 3.7]).unwrap(), -(2.5f64.sqrt()) / (2.5f64.tanh() * 2.0) + 2.0 / ((3.7f64.sinh() * 4.0).sin()).asin(), ); let sut = "asin(sin(x)) + acos(cos(x)) + atan(tan(x))"; let expr = DeepEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[0.5]).unwrap(), 1.5); let sut = "sqrt(alpha^ceil(centauri))"; let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[2.0, 3.1]).unwrap(), 4.0); let sut = "trunc(x) + fract(x)"; let expr = DeepEx::<f64>::parse(sut)?; let expr = FlatEx::from_deepex(expr)?; utils::assert_float_eq_f64(expr.eval(&[23422.52345]).unwrap(), 23422.52345); Ok(()) } #[test] fn test_custom_ops_invert() -> ExResult<()> { #[derive(Clone, Debug)] struct SomeF32Operators; impl MakeOperators<f32> for SomeF32Operators { fn make<'a>() -> Vec<Operator<'a, f32>> { vec![ Operator::make_unary("invert", |a| 1.0 / a), Operator::make_unary("sqrt", |a| a.sqrt()), ] } } let expr = FlatEx::<f32, SomeF32Operators>::parse("sqrt(invert(a))")?; utils::assert_float_eq::<f32>(expr.eval(&[0.25]).unwrap(), 2.0, 1e-6, 0.0, ""); Ok(()) } #[test] fn test_custom_ops() -> ExResult<()> { #[derive(Clone, Debug)] struct SomeF32Operators; impl MakeOperators<f32> for SomeF32Operators { fn make<'a>() -> Vec<Operator<'a, f32>> { vec![ Operator::make_bin( "**", BinOp { apply: |a: f32, b| a.powf(b), prio: 2, is_commutative: false, }, ), Operator::make_bin( "*", BinOp { apply: |a, b| a * b, prio: 1, is_commutative: true, }, ), Operator::make_unary("invert", |a: f32| 1.0 / a), ] } } let expr = FlatEx::<f32, SomeF32Operators>::parse("2**2*invert(3)")?; let val = expr.eval(&[])?; utils::assert_float_eq::<f32>(val, 4.0 / 3.0, 1e-6, 0.0, ""); #[derive(Clone, Debug)] struct ExtendedF32Operators; impl MakeOperators<f32> for ExtendedF32Operators { fn make<'a>() -> Vec<Operator<'a, f32>> { let zero_mapper = Operator::make_bin_unary( "zer0", BinOp { apply: |_: f32, _| 0.0, prio: 2, is_commutative: true, }, |_| 0.0, ); FloatOpsFactory::<f32>::make() .iter() .cloned() .chain(once(zero_mapper)) .collect::<Vec<_>>() } } let expr = FlatEx::<f32, ExtendedF32Operators>::parse("2^2*1/(berti) + zer0(4)")?; let val = expr.eval(&[4.0])?; utils::assert_float_eq::<f32>(val, 1.0, 1e-6, 0.0, ""); Ok(()) } #[test] fn test_eval_str() -> ExResult<()> { fn test(sut: &str, reference: f64) -> ExResult<()> { println!(" === testing {}", sut); utils::assert_float_eq_f64(exmex::eval_str(sut)?, reference); let expr = FlatEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[])?, reference); let expr = DeepEx::<f64>::parse(sut)?; utils::assert_float_eq_f64(expr.eval(&[])?, reference); let expr = FlatEx::from_deepex(expr)?; utils::assert_float_eq_f64(expr.eval(&[])?, reference); let expr = expr.to_deepex()?; utils::assert_float_eq_f64(expr.eval(&[])?, reference); Ok(()) } assert!(exmex::eval_str::<f64>("0/0")?.is_nan()); test("abs( -22/2)", 11.0)?; test("signum(-22/2)", -1.0)?; test("cbrt(8)", 2.0)?; test("2*3^2", 18.0)?; test("PI", std::f64::consts::PI)?; test("Ï€", std::f64::consts::PI)?; test("TAU", std::f64::consts::TAU)?; test("Ï„", std::f64::consts::TAU)?; test("Ï„", std::f64::consts::PI * 2.0)?; test("E", std::f64::consts::E)?; test("e", std::f64::consts::E)?; test("cos(TAU)", 1.0)?; test("sin(Ï„)", 0.0)?; test("cos(PI/2)", 0.0)?; test("cos(Ï€/2)", 0.0)?; test("-3^2", 9.0)?; test("11.3", 11.3)?; test("round(11.3)", 11.0)?; test("+11.3", 11.3)?; test("-11.3", -11.3)?; test("(-11.3)", -11.3)?; test("11.3+0.7", 12.0)?; test("31.3+0.7*2", 32.7)?; test("1.3+0.7*2-1", 1.7)?; test("1.3+0.7*2-1/10", 2.6)?; test("(1.3+0.7)*2-1/10", 3.9)?; test("1.3+(0.7*2)-1/10", 2.6)?; test("1.3+0.7*(2-1)/10", 1.37)?; test("1.3+0.7*(2-1/10)", 2.63)?; test("-1*(1.3+0.7*(2-1/10))", -2.63)?; test("-1*(1.3+(-0.7)*(2-1/10))", 0.03)?; test("-1*((1.3+0.7)*(2-1/10))", -3.8)?; test("sin 3.14159265358979", 0.0)?; test("0-sin(3.14159265358979 / 2)", -1.0)?; test("-sin(Ï€ / 2)", -1.0)?; test("3-(-1+sin(PI/2)*2)", 2.0)?; test("3-(-1+sin(cos(-3.14159265358979))*2)", 5.6829419696157935)?; test("-(-1+((-PI)/5)*2)", 2.256637061435916)?; test("((2-4)/5)*2", -0.8)?; test("-(-1+(sin(-PI)/5)*2)", 1.0)?; test("-(-1+sin(cos(-PI)/5)*2)", 1.3973386615901224)?; test("-cos(PI)", 1.0)?; test("1+sin(-cos(-PI))", 1.8414709848078965)?; test("-1+sin(-cos(-PI))", -0.1585290151921035)?; test("-(-1+sin(-cos(-PI)/5)*2)", 0.6026613384098776)?; test("sin(-(2))*2", -1.8185948536513634)?; test("sin(sin(2))*2", 1.5781446871457767)?; test("sin(-(sin(2)))*2", -1.5781446871457767)?; test("-sin(2)*2", -1.8185948536513634)?; test("sin(-sin(2))*2", -1.5781446871457767)?; test("sin(-sin(2)^2)*2", 1.4715655294841483)?; test("sin(-sin(2)*-sin(2))*2", 1.4715655294841483)?; test("--(1)", 1.0)?; test("--1", 1.0)?; test("----1", 1.0)?; test("---1", -1.0)?; test("3-(4-2/3+(1-2*2))", 2.666666666666666)?; test("ln(ln(2))*tan(2)+exp(1.5)", 5.2825344122094045)?; test("log(log(2))*tan(2)+exp(1.5)", 5.2825344122094045)?; test("ln(log2(2))*tan(2)+exp(1.5)", 4.4816890703380645)?; test("log2(2)", 1.0)?; test("2^log2(2)", 2.0)?; test("2^(cos(0)+2)", 8.0)?; test("2^cos(0)+2", 4.0)?; Ok(()) } #[test] fn test_error_handling() { assert!(exmex::parse::<f64>("z+/Q").is_err()); assert!(exmex::parse::<f64>("6-^6").is_err()); assert!(exmex::eval_str::<f64>("").is_err()); assert!(exmex::eval_str::<f64>("5+5-(").is_err()); assert!(exmex::eval_str::<f64>(")2*(5+5)*3-2)*2").is_err()); assert!(exmex::eval_str::<f64>("2*(5+5))").is_err()); } #[cfg(feature = "serde")] #[test] fn test_serde_public_interface() -> ExResult<()> { let s = "{x}^(3.0-{y})"; let flatex = FlatEx::<f64>::parse(s)?; let serialized = serde_json::to_string(&flatex).unwrap(); let deserialized = serde_json::from_str::<FlatEx<f64>>(serialized.as_str()).unwrap(); assert_eq!(s, format!("{}", deserialized)); Ok(()) } #[test] fn test_constants() -> ExResult<()> { utils::assert_float_eq_f64(exmex::eval_str::<f64>("PI")?, std::f64::consts::PI); utils::assert_float_eq_f64(exmex::eval_str::<f64>("E")?, std::f64::consts::E); let expr = parse::<f64>("x / PI * 180")?; utils::assert_float_eq_f64(expr.eval(&[std::f64::consts::FRAC_PI_2])?, 90.0); let expr = parse::<f32>("E ^ x")?; utils::assert_float_eq::<f32>(expr.eval(&[5.0])?, 1f32.exp().powf(5.0), 1e-6, 0.0, ""); let expr = parse::<f32>("E ^ Erwin"); assert_eq!(expr?.unparse(), "E ^ Erwin"); Ok(()) } #[test] fn test_fuzz() { assert!(exmex::eval_str::<f64>("an").is_err()); assert!(FlatEx::<f64>::parse("\n").is_err()); } #[cfg(feature = "serde")] #[test] fn test_to_deepex_non_default() -> ExResult<()> { ops_factory!( SomeOps, i64, Operator::make_bin( "/", BinOp { apply: |a, b| a / b, prio: 2, is_commutative: true } ), Operator::make_bin( "*", BinOp { apply: |a, b| a * b, prio: 2, is_commutative: true } ), Operator::make_bin( "+", BinOp { apply: |a, b| a + b, prio: 1, is_commutative: true } ), Operator::make_bin_unary( "-", BinOp { apply: |a, b| a - b, prio: 1, is_commutative: false }, |a| -a ), Operator::make_bin( "%", BinOp { apply: |a, b| a % b, prio: 2, is_commutative: false } ), Operator::make_bin( "|", BinOp { apply: |a, b| a | b, prio: 2, is_commutative: true } ), Operator::make_bin( "&", BinOp { apply: |a, b| a & b, prio: 2, is_commutative: true } ) ); let flatex = FlatEx::<i64, SomeOps>::parse("alpha*(-beta-(11-gamma/(omikron*3+zeta)))")?; let deepex = flatex.clone().to_deepex()?; let input = [7, 5, 4, 3, 2]; assert_eq!(flatex.eval(&input), deepex.eval(&input)); let flatex2 = FlatEx::from_deepex(deepex)?; assert_eq!(flatex.eval(&input), flatex2.eval(&input)); let serialized = serde_json::to_string(&flatex).unwrap(); let deserialized = serde_json::from_str::<FlatEx<i64, SomeOps>>(serialized.as_str()).unwrap(); let deepex = deserialized.to_deepex()?; assert_eq!(flatex.eval(&input), deepex.eval(&input)); let flatex2 = FlatEx::from_deepex(deepex)?; assert_eq!(flatex.eval(&input), flatex2.eval(&input)); let xpy_f = FlatEx::<i64, SomeOps>::parse("x + y")?; let y_f = FlatEx::<i64, SomeOps>::parse("y")?; let xpy = xpy_f.to_deepex()?; let y = y_f.to_deepex()?; let xpy_mod_y = (xpy.clone() % y.clone())?; assert_eq!(xpy_mod_y.eval(&[1, 2])?, 1); let xpy_or_y = (xpy.clone() | y.clone())?; assert_eq!( xpy_or_y.eval(&[1, 2])?, DeepEx::<i64, SomeOps>::parse("(x+y) | y")?.eval(&[1, 2])? ); let xpy_and_y = (xpy.clone() & y.clone())?; assert_eq!( xpy_and_y.eval(&[7, 2])?, DeepEx::<i64, SomeOps>::parse("(x+y) & y")?.eval(&[7, 2])? ); Ok(()) } #[test] fn test_ops() -> ExResult<()> { let d1 = DeepEx::<f64>::one(); let d2 = DeepEx::<f64>::one(); let sum = (d1 + d2)?; utils::assert_float_eq_f64(sum.eval(&[])?, 2.0); let mul = (sum.clone() * sum)?; utils::assert_float_eq_f64(mul.eval(&[])?, 4.0); let div = (mul.clone() / mul)?; utils::assert_float_eq_f64(div.eval(&[])?, 1.0); let sub = (div.clone() - div)?; utils::assert_float_eq_f64(sub.eval(&[])?, 0.0); let d = DeepEx::<f64>::parse("x^2")?; let d = (d ^ DeepEx::from_num(2.0))?; assert_eq!( d.eval(&[7.3]), DeepEx::<f64>::parse("{x}^4.0")?.eval(&[7.3]) ); let d = (-d)?; let ref_val = DeepEx::<f64>::parse("-({x}^4.0)")?.eval(&[7.3])?; let ref_val_2 = DeepEx::<f64>::parse("-({x}^4.0)")?.eval(&[0.95])?; assert_eq!(d.eval(&[7.3])?, ref_val); assert!((d.clone() & d.clone()).is_err()); assert!((d.clone() | d.clone()).is_err()); assert!((d.clone() % d.clone()).is_err()); utils::assert_float_eq_f64(d.clone().abs()?.eval(&[7.3])?, ref_val.abs()); utils::assert_float_eq_f64(d.clone().sin()?.eval(&[7.3])?, ref_val.sin()); utils::assert_float_eq_f64(d.clone().cos()?.eval(&[7.3])?, ref_val.cos()); utils::assert_float_eq_f64(d.clone().tan()?.eval(&[7.3])?, ref_val.tan()); utils::assert_float_eq_f64(d.clone().asin()?.eval(&[0.95])?, ref_val_2.asin()); utils::assert_float_eq_f64(d.clone().acos()?.eval(&[0.95])?, ref_val_2.acos()); utils::assert_float_eq_f64(d.clone().atan()?.eval(&[0.95])?, ref_val_2.atan()); utils::assert_float_eq_f64(d.clone().ceil()?.eval(&[7.3])?, ref_val.ceil()); utils::assert_float_eq_f64(d.clone().floor()?.eval(&[7.3])?, ref_val.floor()); utils::assert_float_eq_f64(d.clone().round()?.eval(&[7.3])?, ref_val.round()); utils::assert_float_eq_f64(d.clone().exp()?.eval(&[7.3])?, ref_val.exp()); utils::assert_float_eq_f64((-d.clone())?.clone().log()?.eval(&[7.3])?, (-ref_val).ln()); utils::assert_float_eq_f64( (-d.clone())?.clone().log2()?.eval(&[7.3])?, (-ref_val).log2(), ); utils::assert_float_eq_f64( (-d.clone())?.clone().log10()?.eval(&[7.3])?, (-ref_val).log10(), ); utils::assert_float_eq_f64((-d.clone())?.clone().ln()?.eval(&[7.3])?, (-ref_val).ln()); utils::assert_float_eq_f64(d.clone().signum()?.eval(&[7.3])?, ref_val.signum()); utils::assert_float_eq_f64( (-d.clone())?.clone().sqrt()?.eval(&[7.3])?, (-ref_val).sqrt(), ); utils::assert_float_eq_f64( (-d.clone())?.clone().cbrt()?.eval(&[7.3])?, (-ref_val).cbrt(), ); utils::assert_float_eq_f64(d.clone().trunc()?.eval(&[7.3])?, ref_val.trunc()); utils::assert_float_eq_f64(d.clone().fract()?.eval(&[7.3])?, ref_val.fract()); utils::assert_float_eq_f64(DeepEx::<f64>::pi().eval(&[])?, std::f64::consts::PI); utils::assert_float_eq_f64(DeepEx::<f64>::e().eval(&[])?, std::f64::consts::E); utils::assert_float_eq_f64(DeepEx::<f64>::tau().eval(&[])?, std::f64::consts::TAU); Ok(()) } #[test] fn test_calculate() -> ExResult<()> { let one = DeepEx::<f64>::one(); let another_one = one.clone(); let two = one.operate_binary(another_one, "+")?; utils::assert_float_eq_f64(two.eval(&[])?, 2.0); let expr = DeepEx::<f64>::parse("exp(x)+2*y")?; let expr_sub = DeepEx::parse("2*z")?; let mut subs = |var: &str| match var { "y" => Some(expr_sub.clone()), _ => None, }; let subsed = expr.subs(&mut subs)?; utils::assert_float_eq_f64(subsed.eval(&[0.0, 2.0])?, 9.0); Ok(()) } #[test] fn test_var_indices_ordered() { fn test<'a>(s: &str, reference: impl Iterator<Item = &'a usize>) { let expr = FlatEx::<f64>::parse(s).unwrap(); let vars_ordered = expr.var_indices_ordered(); let reference = reference.collect::<Vec<_>>(); assert_eq!(reference.len(), vars_ordered.len()); for (a, b) in reference.iter().zip(vars_ordered) { assert_eq!(**a, b); } } test("x + x", [0, 0].iter()); test("x + y + x", [0, 1, 0].iter()); test("x + y * z", [1, 2, 0].iter()); test("(x + y) * z", [0, 1, 2].iter()); test("(x + y) * cos(a - sin(b + z))", [1, 4, 0, 2, 3].iter()); test("(x + y) * (a - (b * z)) + a", [1, 4, 0, 2, 3, 0].iter()); } #[test] fn test_eval_vec_iter() { fn test<'a>(s: &str, vars: Vec<f64>, reference: f64) { let expr = FlatEx::<f64>::parse(s).unwrap(); assert!((expr.eval_vec(vars.clone()).unwrap() - reference).abs() < 1e-12); assert!((expr.eval_iter(vars.into_iter()).unwrap() - reference).abs() < 1e-12); } test("x + y * z", vec![1.0, 2.0, 3.0], 7.0); test("(x + y) * z", vec![1.0, 2.0, 3.0], 9.0); test("x * z * (x + y) - a * a", vec![0.5, 1.0, 2.0, 3.0], 8.75); #[derive(Debug, PartialEq, Eq)] struct StringContainer { data: Vec<String>, has_been_cloned: bool, } impl StringContainer { fn new(s: &str) -> Self { Self { data: vec![s.to_string()], has_been_cloned: false, } } fn from_slice(v: &[&str]) -> Self { Self { data: v.iter().map(|s| s.to_string()).collect(), has_been_cloned: false, } } } impl Default for StringContainer { fn default() -> Self { Self::new("default") } } impl Clone for StringContainer { fn clone(&self) -> Self { Self { data: self.data.clone(), has_been_cloned: true, } } } impl FromStr for StringContainer { type Err = ExError; fn from_str(s: &str) -> Result<Self, Self::Err> { Ok(StringContainer::new(s)) } } ops_factory!( StringOps, StringContainer, Operator::make_bin( "+", BinOp { apply: |mut s1, mut s2| { s1.data.append(&mut s2.data); s1 }, prio: 2, is_commutative: false } ), Operator::make_bin( "-", BinOp { apply: |mut s1, mut s2| { s1.data.append(&mut s2.data); s1 }, prio: 3, is_commutative: false } ), Operator::make_bin( "|", BinOp { apply: |mut s1, mut s2| { s1.data.append(&mut s2.data); s1 }, prio: 0, is_commutative: false } ), Operator::make_bin( "==", BinOp { apply: |mut s1, mut s2| { s1.data.append(&mut s2.data); s1 }, prio: 1, is_commutative: false } ) ); let expr = FlatEx::<StringContainer, StringOps>::parse("x+y").unwrap(); let x = StringContainer::new("x"); let y = StringContainer::new("y"); let res = expr.eval_vec(vec![x, y]).unwrap(); assert_eq!(res, StringContainer::from_slice(&["x", "y"])); assert!(!res.has_been_cloned); let expr = FlatEx::<StringContainer, StringOps>::parse("x+y+x").unwrap(); let x = StringContainer::new("x"); let y = StringContainer::new("y"); let res = expr.eval_vec(vec![x, y]).unwrap(); assert!(res.has_been_cloned); assert_eq!(res, StringContainer::from_slice(&["x", "y", "x"]).clone()); let expr = FlatEx::<StringContainer, StringOps>::parse("(x|y-2)-(x|y==2)").unwrap(); let x = StringContainer::new("alpha"); let y = StringContainer::new("beta"); let res = expr.eval_vec(vec![x, y]).unwrap(); assert_eq!( res, StringContainer::from_slice(&["alpha", "beta", "2", "alpha", "beta", "2"]).clone() ); } #[test] fn test_string_ops() { literal_matcher_from_pattern!(StringMatcher, r"^[a-zA-z0-9]+"); ops_factory!( StringOpsFactory, String, Operator::make_bin( "+", BinOp { apply: |mut s1, s2| { s1.push_str(&s2); s1 }, prio: 2, is_commutative: false } ), Operator::make_constant("-", "MINUS".to_string()) ); let expr = FlatEx::<String, StringOpsFactory, StringMatcher>::parse("x+y+{_}+-").unwrap(); assert_eq!( expr.eval(&["abc".to_string()]).unwrap(), "xyabcMINUS".to_string() ); } #[test] fn test_binary_function_style() { fn test(s: &str, vars: &[f64], reference: f64) { println!("testing {s}"); fn test_<'a, EX: Express<'a, f64> + Debug>(s: &'a str, vars: &[f64], reference: f64) { let expr = EX::parse(s).unwrap(); assert_float_eq_f64(expr.eval(vars).unwrap(), reference); } println!("flatex..."); test_::<FlatEx<f64>>(s, vars, reference); println!("deepex..."); test_::<DeepEx<f64>>(s, vars, reference); } test( "atan2(0.2/y, x)", &[1.2, 2.1], (0.2 / 2.1_f64).atan2(1.2_f64), ); test("+ (1, -2) / 2", &[], -0.5); test("/ 1 2 * 3", &[], 1.5); test("atan2(1, 2) * 3", &[], 1.0f64.atan2(2.0) * 3.0); test( "2 + atan2(1, x / 2) * 3", &[1.0], 2.0 + 1.0f64.atan2(0.5) * 3.0, ); test( "sin(atan2(1, x / 2)) * 3", &[1.0], (1.0f64.atan2(0.5)).sin() * 3.0, ); test( "max(sin(atan2(1, x / 2)) * 3, x)", &[1.0], ((1.0f64.atan2(0.5)).sin() * 3.0).max(1.0), ); assert!(FlatEx::<f64>::parse("atan3(z, y, x").is_err()); } #[test] fn test_op_reprs() { fn test(s: &str, uo_reference: &[&str], bo_reference: &[&str], ao_reference: &[&str]) { println!("testing {s}"); fn test_<'a, EX: Express<'a, f64> + Debug>( s: &'a str, uo_reference: &[&str], bo_reference: &[&str], ao_reference: &[&str], ) { let expr = EX::parse(s).unwrap(); let uops = expr.unary_reprs().to_vec(); let bops = expr.binary_reprs().to_vec(); let aops = expr.operator_reprs().to_vec(); let mut uo_reference = uo_reference .iter() .map(|s| s.to_string()) .collect::<Vec<_>>(); let mut bo_reference = bo_reference .iter() .map(|s| s.to_string()) .collect::<Vec<_>>(); let mut ao_reference = ao_reference .iter() .map(|s| s.to_string()) .collect::<Vec<_>>(); uo_reference.sort(); bo_reference.sort(); ao_reference.sort(); assert_eq!(uops, uo_reference); assert_eq!(bops, bo_reference); assert_eq!(aops, ao_reference); } println!("flatex..."); test_::<FlatEx<f64>>(s, uo_reference, bo_reference, ao_reference); println!("deepex..."); test_::<DeepEx<f64>>(s, uo_reference, bo_reference, ao_reference); } test("ln(x)", &["ln"], &[], &["ln"]); test( "ln(x) + log(y)", &["ln", "log"], &["+"], &["+", "ln", "log"], ); test("x*y", &[], &["*"], &["*"]); test("x+y", &[], &["+"], &["+"]); test("atan2(0.2/y, x)", &[], &["atan2", "/"], &["atan2", "/"]); test("-x", &["-"], &[], &["-"]); test("sin(-x)", &["-", "sin"], &[], &["-", "sin"]); test( "sin(tan(cos(x)))", &["cos", "sin", "tan"], &[], &["cos", "sin", "tan"], ); test( "sin(1+tan(cos(x)))", &["cos", "sin", "tan"], &["+"], &["+", "cos", "sin", "tan"], ); test( "sin(-tan(cos(x)))", &["-", "cos", "sin", "tan"], &[], &["-", "cos", "sin", "tan"], ); test( "sin(-tan(y+cos(x-z)))", &["-", "cos", "sin", "tan"], &["+", "-"], &["+", "-", "cos", "sin", "tan"], ); test( "sin(-tan(y+sin(cos(x-z))))", &["-", "cos", "sin", "tan"], &["+", "-"], &["+", "-", "cos", "sin", "tan"], ); test( "4/3 * a / b * (1.3 / 65.2 / (log(18.93+c+d+e) / ln(111 + d)))", &["ln", "log"], &["*", "+", "/"], &["*", "+", "/", "ln", "log"], ); test( "4/3 * a / b * (1.3 / 65.2 / ((18.93+c+d+e) / 111))", &[], &["*", "+", "/"], &["*", "+", "/"], ); }