bertiqwerty / exmex Goto Github PK
View Code? Open in Web Editor NEWMath parser and evaluator in Rust, capable of partial differentiation, allows the use of custom operators.
License: Apache License 2.0
Math parser and evaluator in Rust, capable of partial differentiation, allows the use of custom operators.
License: Apache License 2.0
Hi! Thank you for the crate!
I was wondering if you could add serde support, maybe through a feature flag.
I am using your crate and I am missing serde support for FlatEx
.
I noticed that you use smallvec
. It has serde support through a feature flag, so it should be easy.
Thanks beforehand!
I fuzzed this crate using honggfuzz and found 3 crashes.
You can fuzz the code yourself and further investigate the crashes with this repo.
In the main function there are also three test functions which can be used to easily reproduce the crashes. If you go into the hfuzz_workspace/Fuzzing Folder you can also see the inputs that crash.
This could help improve the check_preconditions function and i hope this is useful.
Create a generic value type to show-case that it is possible to mix different types, e.g., integers, floats, and booleans, in one expression.
Currently, the partial
method only takes the 1st derivative, could a method be added that takes the nth derivative? such as maybe partial_nth
or something?
exmex (partial) derivation feature is awesome but the lack of some common math/trigonometry binary operators reduces its general usefulness. It'd be awesome if operators like atan2
were implemented, including their derivatives.
Fuzzing showed that exmex crashes when unicode input is provided. As long as we do not support unicode, we should at least give a sane error message.
The regex crate is currently used for checking if a string starts with a valid identifier or is a valid identifier. From what I know this is a bit overkill and it's faster to write simple matches explicitly. I suggest doing something like this:
pub trait VarName {
fn is_start_character(c: char) -> bool;
fn is_continue_character(c: char) -> bool {
Self::is_start_character(c) || matches!(c, '0'..='9')
}
fn try_parse(s: &str) -> Option<(&str, &str)> {
let mut first = true;
let (name, rest) = s.split_once(|c|
if first {
first = false;
!Self::is_start_character(c)
} else {
!Self::is_continue_character(c)
}
).unwrap_or((s, ""));
if name.is_empty() {
None
} else {
Some((name, rest))
}
}
fn is_exact_variable_name(s: &str) -> bool {
s.starts_with(Self::is_start_character) && s.chars().skip(1).all(Self::is_continue_character)
}
}
pub struct ASCII;
impl VarName for ASCII {
fn is_start_character(c: char) -> bool {
matches!(c, 'a'..='z' | 'A'..='Z' | '_')
}
}
pub struct LatinGreek;
impl VarName for LatinGreek {
fn is_start_character(c: char) -> bool {
ASCII::is_start_character(c) || matches!(c, 'α'..='ω' || 'Α'..='Ω')
}
}
#[cfg(feature = "unicode")]
pub struct Unicode;
#[cfg(feature = "unicode")]
impl VarName for LatinGreek {
fn is_start_character(c: char) -> bool {
unicode_ident::is_xid_start(c)
}
fn is_start_character(c: char) -> bool {
unicode_ident::is_xid_continue(c)
}
}
with unicode-ident = { version = "1", optional = true }
and a feature unicode = ["unicode-ident"]
.
If you think this is a good idea I can make a pull request.
Currently, variables are named x0, x1, ... after unparsing.
Since we keep track of the original names anyway to handle overloaded operators, we can also use the original names.
Serialization currently assumes that the default number regex-pattern is used to identify numbers or operands. In case one tries to serialize and de-serialize an expression that was created, e.g., by FlatEx::from_regex
de-serialization can lead to parse errors or strange behavior. This includes the type Val
that can be activated with the feature value
.
Hi again!
Thanks for the work for #1! I have no complaints, but wanted to ask for a convenience struct for ease of use of the crate.
Add a struct that wraps over FlatEx allowing for:
The idea is simple: an online plotter (see website). In my application, the following steps are performed:
In the last step, I serialize all the input and deserialize upon a new visit.
I am serializing and deserializing the input and parsing when reconstructing the struct. Hope this code is helpful.
struct FnInput {
raw_input: String,
exp: FlatEx<f64>, // Exmex version 0.7.1
}
impl FnInput {
pub fn eval(&self, vars: &[f64]) -> Result<f64, ExParseError> {
self.exp(vars)
}
}
impl Serialize for FnInput {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_struct("FnInput", 1)?;
s.serialize_field("raw_input", &self.raw_input)?;
s.end() // We do not serialize FlatEx
}
}
impl<'de> Deserialize<'de> for FnInput {
fn deserialize<D>(deserializer: D) -> Result<FnInput, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Debug, Clone, Deserialize)]
struct __FnInput {
raw_input: String,
}
let __fn_input = __FnInput::deserialize(deserializer)?;
let exp = exmex::parse_with_default_ops::<f64>(&__fn_input.raw_input).unwrap(); // Reparse here
Ok(FnInput {
string: __fn_input.string,
kind,
})
}
}
Note: Another option is to parse for every evaluation, but that is too wasteful...
I presume this would entail a version of FlatEx
with no life parameter, but you know better. Feel free to deny this request if it feels out of the scope of the crate for you.
Either way, I do not how to reconstruct how to easily reconstruct my workaround with exmex version 0.9.
Hi there!
exmex = { version: 0.7.1 }
Description
Parsing the expression 2sin({x})
generates a panic, while an error is expected.
Details
Running the following code generates a panic, while it should be a parse error.
fn main() {
let e = exmex::parse::<f64>("2sin({x})", &exmex::make_default_operators::<f64>());
println!("{:?}", e);
}
This is the panic error I get This is probably a bug. Expected binary operator but there was none.
Currently only E
can be used to represent Euler's number, could e
do the same thing as well? I haven't found a good way of accomplishing this.
Hi,
I love the performance and footprint! I guess basic vector math cannot be added via custom types right ?
Thanks
Faster parsing from Issue #21 should support compilation and serialization. FlatEx
needs to keep track of names.
Currently functions that don't contain a variable don't output a value, so for instance the function π
doesn't just simply output 3.14.... no matter the output.
It would be nice to be able to substitute variables with arbitrary expressions.
My current workaround is to unparse, use regex
replacement functionality and re-parse the expression.
Use derivative operators that transform expressions into their partial derivatives. These derivative operators depend on the defined binary and unary operators.
In case a custom number pattern is used, a un-parsable number might occur. unwrap
leads to panic.
Problem is here
Line 216 in 32e9fc9
Could the reverse of partial
be added so a function could be integrated instead? this would be tremendously useful! Thanks.
Hello @bertiqwerty
I trying wrap FlaxEx in my struct:
#[derive(Debug, Clone, PartialOrd, PartialEq, Validate)]
pub struct QuickExpression {
#[validate(length(min = 1), custom = "validate_expression")]
raw_text: String,
//expr: exmex::FlatEx<exmex::Val, exmex::ValOpsFactory, exmex::ValMatcher>,
//expr: exmex::FlatEx<Val<i64>, exmex::ValOpsFactory<i64>, exmex::ValMatcher>,
}
impl QuickExpression
{
pub fn do_something(&self) {
let big_num = 0xF1F2F3F4F5F6F7u64 as i64;
let res = self.expr.eval(&[bin_num]);
//...
}
}
I need to work with f64 and i64:
let text = "2 * {random_value}";
let expr = exmex::parse_val::<i64, f64>(text.as_str());
let my_expr = QuickExpression{raw_text: text, expr: expr};
For expressions like "-y*(x*(-(1-y))) + 1.7"
the computation FlatEx::to_deepex
is wrong. Hence, for these expressions also partial
is wrong.
Create a github action that uses cargo fuzz
with nightly.
Since eval_str
calls parse_with_default_ops
we only need eval_str
as target and can remove the target parse_with_default_ops
. @Nereuxofficial, do you disagree?
I was wondering if there was an idiomatic may to have variables with user defined values that prissiest between calculations. Similar to how evalexpr handles things.
Theoretical usage:
use exmex::prelude::*;
exmex::parse::<f64>("x=14")?;
let expr = exmex::parse::<f64>("x^2+4*x-3")?;
assert_eq!(expr , 249.0);
Is it possible to add function call syntax to binary operators? Possibly extended to n-ary operations.
My motivating example is that I want to support max
and min
functions.
A possible workaround for me is to add a tuple variant to my Value
type which is built through a ,
operator and use unary operators for this. Unfortunately this blows up all other code because then there are a lots of non-sensical states representable in the type system as not all values are allowed as operator inputs.
As I see there are the following alternatives:
Value
type if requiredThis is a feature I require and currently implement by formatting the expression(s) into a String with the required operator and reparsing it.
The compile string in the benchmarks section of the Readme needs to be corrected.
Taking a relative tolerance into account might be helpful.
When using the feature value
and the value type Val
, boolean literals true
and false
are interpreted as variables instead of literals when using from_str
instead of parse_val
.
If variable names have spaces, an error is returned. Variable names with spaces are valid.
Using the macro literal_matcher_from_pattern!
requires adding lazy_static
to Cargo.toml
in the consuming crate.
If I understand this stack overflow answer correctly this can be fixed by exporting lazy_static
(maybe with #[doc(hidden)]?) and refer to it via $crate::lazy_static::lazy_static
.
When calling eval_str
, directly parse into FlatEx
without creating a DeepEx
and evaluate without compilation.
I love this expression evaluator, it's very fast and the api is well designed!
I'm just wondering if it's possible/planned to add the feature of evaluating absolute values, like |-1.5|
as 1.5
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.