38 个版本
0.20.3 | 2024 年 7 月 30 日 |
---|---|
0.20.1 | 2024 年 5 月 12 日 |
0.19.0 | 2023 年 12 月 22 日 |
0.18.0 | 2023 年 10 月 23 日 |
0.8.0 | 2021 年 7 月 30 日 |
在 解析器实现 中排名 144
每月下载量 418
被 3 crates 使用
255KB
6K SLoC
Exmex
Exmex 是一个可扩展的数学表达式解析器和评估器。易用性、灵活性和高效的评估是其主要设计目标。Exmex 可以解析包含变量和运算符的数学表达式。一方面,它提供了一组用于浮点数值的默认运算符。对于可微分的默认运算符,Exmex 可以计算偏导数。另一方面,用户可以定义自己的运算符并处理不同类型的数据,如浮点数、整数、布尔值或其他实现了 Clone
、FromStr
、Debug
、Default
等类型的自定义数据。
Exmex 的部分功能可通过 Mexpress 从 Python 访问。
安装
在您的项目目录中运行
cargo add exmex
以获取 最新版本。如果您想使用 Exmex 的最新版本,请将以下内容添加到您的 Cargo.toml
文件中:
[dependencies]
# ...
exmex = { git = "https://github.com/bertiqwerty/exmex.git", branch = "main" }
基本用法
要简单地评估一个字符串,可以使用
let result = exmex::eval_str::<f64>("e^(2*π-τ)")?;
assert!((result - 1.0).abs() < 1e-12);
其中 π
/PI
、τ
/TAU
和欧拉数 E
/e
可用为常量。要创建一个表示数学函数的变量表达式,可以使用任何不定义运算符或常量的字符串,并且符合 r"[a-zA-Zα-ωΑ-Ω_]+[a-zA-Zα-ωΑ-Ω_0-9]*"
的格式,例如:
use exmex::prelude::*;
let expr = exmex::parse::<f64>("2*x^3-4/y")?;
从 prelude
的通配符导入仅使表达式特质 Express
和其实施 FlatEx
(一个扁平的表达式)可访问。要使用变量,您不需要使用上下文或明确告诉解析器变量是什么。要评估函数在 x=2.0
和 y=4.0
的位置,您可以使用
let result = expr.eval(&[2.0, 4.0])?;
assert!((result - 15.0).abs() < 1e-12);
传递给评估的变量的顺序必须与变量名的字母顺序匹配。
除了预定义的浮点运算符外,您还可以实现自定义运算符,并使用其工厂类型作为泛型参数,如下例所示。
use exmex::prelude::*;
use exmex::{BinOp, MakeOperators, Operator};
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);
更复杂的数据类型示例
- 作为操作数使用的运算符,用于 2020 年圣诞节的第 19 天 和
- 类型
Val
可以通过启用功能value
来激活,见下文。
偏导数
为了计算具有浮点数的表达式的偏导数,您可以在通过在 Cargo.toml
中激活 Exmex 功能 partial
后使用方法 partial
。
[dependencies]
exmex = { ..., features = ["partial"] }
partial
方法的结果是另一个表达式。
use exmex::prelude::*;
let expr = exmex::parse::<f64>("y*x^2")?;
// d_x
let dexpr_dx = expr.partial(0)?;
assert_eq!(format!("{}", dexpr_dx), "({x}*2.0)*{y}");
// d_xy
let ddexpr_dxy = dexpr_dx.partial(1)?;
assert_eq!(format!("{}", ddexpr_dxy), "{x}*2.0");
let result = ddexpr_dxy.eval(&[2.0, f64::MAX])?;
assert!((result - 4.0).abs() < 1e-12);
// d_xyx
let dddexpr_dxyx = ddexpr_dxy.partial(0)?;
assert_eq!(format!("{}", dddexpr_dxyx), "2.0");
let result = dddexpr_dxyx.eval(&[f64::MAX, f64::MAX])?;
assert!((result - 2.0).abs() < 1e-12);
// all in one
let dddexpr_dxyx_iter = expr.partial_iter([0, 1, 0].iter())?;
assert_eq!(format!("{}", dddexpr_dxyx_iter), "2.0");
let result = dddexpr_dxyx_iter.eval(&[f64::MAX, f64::MAX])?;
assert!((result - 2.0).abs() < 1e-12);
使用功能 value
在一个表达式中混合标量数据类型和浮点向量
激活 Exmex 功能 value
后,可以使用类型为 Val
的表达式,这是从 Evalexpr 包中的 Value
类型中受到启发的。一个 Val
实例可以包含布尔值、整数、浮点数或浮点数向量。这样,就可以在同一个表达式中使用布尔值、整数、浮点数和向量。此外,Exmex 通过 ValOpsFactory
提供了预定义的 Val
运算符集合。请参阅以下 Python 风格的 if
-else
运算符的示例。
use exmex::{Express, Val};
let expr = exmex::parse_val::<i32, f64>("0 if b < c else 1.2")?;
let res = expr.eval(&[Val::Float(34.0), Val::Int(21)])?.to_float()?;
assert!((res - 1.2).abs() < 1e-12);
请参阅 Val
-docs 以获取包含向量的示例。
如果同时激活了 partial
和 value
,则可以计算类型如 FlatExVal<i32, f64>
的表达式的偏导数。目前不支持向量。
use exmex::{Differentiate, Express, Val};
let expr = exmex::parse_val::<i32, f64>("3*x if x > 1 else x^2")?;
let deri = expr.partial(0)?;
let res = deri.eval(&[Val::Float(1.0)])?.to_float()?;
assert!((res - 2.0).abs() < 1e-12);
let res = deri.eval(&[Val::Float(7.0)])?.to_float()?;
assert!((res - 3.0).abs() < 1e-12);
序列化和反序列化
要使用 serde
,请激活功能 serde
。
文档
有关更多功能和示例(包括整数数据类型和布尔字面量),请参阅 docs.rs/exmex/ 下的文档发布或通过
cargo doc --all-features --no-deps
基准测试 v0.17.2
Exmex 是考虑到灵活性(例如,使用自己的运算符、字面量和类型)、人体工程学(例如,仅查找变量)和评估速度而创建的。另一方面,在解析期间,Exmex 比其他 crate 慢。然而,根据应用的不同,评估可能更为关键。
用于比较 Exmex 与其他 crate 的表达式是
sin: "sin(x)+sin(y)+sin(z)",
power: "x^2+y*y+z^z",
nested: "x*0.02*sin(-(3*(2*sin(x-1/(sin(y*5)+(5.0-1/z))))))",
compile: "x*0.2*5/4+x*2*4*1*1*1*1*1*1*1+7*sin(y)-z/sin(3.0/2/(1-x*4*1*1*1*1))",
以下表格显示了在 Macbook Pro M1 Max 上使用递增的 x
-值进行的 5 次评估运行的平均运行时间,单位为微秒,即越小越好。可以通过以下方式执行基于 Criterion 的基准测试
cargo bench --all-features --bench benchmark -- --noplot --sample-size 10 --nresamples 10
来计算结果。报告的是多次调用中的最佳结果。更多关于在基准测试中取最小运行时间的信息见下文。
sin | power | nested | compile | comment | |
---|---|---|---|---|---|
Evalexpr | 3.9 | 3.23 | 7.84 | 11.06 | 比数学表达式更多 |
Exmex f64 |
0.14 | 0.18 | 0.5 | 0.3 | 可以计算偏导数 |
Exmex 未编译 f64 |
0.14 | 0.18 | 0.5 | 0.66 | 可以计算偏导数 |
Exmex Val |
0.57 | 0.45 | 1.14 | 0.94 | 一个表达式中可能包含多种数据类型 |
Fasteval | 0.68 | 0.78 | 1.19 | 1.03 | 仅支持 f64 ,支持更快的、不安全模式 |
Meval | 0.42 | 0.43 | 0.79 | 0.99 | 仅支持 f64 ,无自定义运算符 |
Rsc | 4.52 | 5.0 | 10.47 | 19.8 |
请注意,我们还尝试了优化标志 --emit=asm
,这并没有在质量上改变结果。以下是在上述机器上再次以微秒为单位对所有表达式进行解析的基准测试。
所有表达式 | |
---|---|
Evalexpr | 28.95 |
Exmex f64 |
23.45 |
Exmex 未编译 f64 |
20.00 |
Exmex Val |
33.32 |
Fasteval | 13.04 |
Meval | 15.00 |
Rsc | 14.64 |
通过仅传递相关运算符,可以使 Exmex 解析更快。
crate Mexprp 和 Asciimath 在 Win10 上没有错误地运行。然而,我已经有一段时间没有尝试使用它们了。有关基准测试的更多详细信息,请参阅 源文件。
请注意,Criterion 没有提供简单地报告最小运行时间选项。Andrei Alexandrescu 的 演讲 解释了我为什么认为在许多情况下取最小值是一个好主意。还可以参见 https://github.com/bheisler/criterion.rs/issues/485。
许可证
作为库用户,您可以选择 MIT 或 Apache 2.0。
依赖项
~2.6–4MB
~69K SLoC