1. General info
Library written in C++ for parsing mathematical expressions and creating expression maps that can be used as an arbitrary functions in runtime. The syntax of the expression is in C and the parsing is done by transforming the given infix expression to Reverse Polish (RP) notation. The RP is then further parsed to yield a map of arguments and operators that can be reused. The RP map can be used as an arbitrary function by setting the user defined variables in the expression and calling for the calculation of the result.
2. Class architecture and working
The architecture of the class is build around the single export class MathParser. This class is virtual class that returns a pointer to the created object inside the library space. This way we keep all the allocated memory inside the library heap and the functionality is provided by a reference to the created object.
2.1. Objective
The main objective of this library is to have the following functionality:
- Load an expression as a string:
MathParser *mp = MathParser::makeMathParser();
mp->setMath("2 * sin( 2*pi*f*t + phi )");
- Use the generated object as a function with arguments in runtime:
mp->setVariableDouble("phi", 0.785398);
double result = mp->calculateExpression();
2.2. Parsing
We parse the expression and resolve the elements to different types of entities. Then we combine the entities in a RP notation. We run through the RP notation and generate intermediary steps of the calculation as separate arguments (generated variables).
For example, if we parse the infix expression :
a*pow(5*b, pi/(2 + 1)^4)
The following RP notation is generated:
a 5 b * pi 2 1 + 4 ^ / pow *
Which in the code is written as:
#AA #AB #AC * pi #AD #AE + #AF ^ / pow *
After expanding the RP expression to intermediate steps we get the following expression:
#AA #AB #AC * pi #AD #AE + #AF ^ / pow * #AA #AG pi #AH #AF ^ / pow * #AA #AG pi #AI / pow * #AA #AG #AJ pow * #AA #AK * #AL
Arguments:
user variable | user constant | generated variable |
---|---|---|
#AA = a | ||
#AB = 5 | ||
#AC = b | ||
#AD = 2 | ||
#AE = 1 | ||
#AF = 4 | ||
#AG = 5*b | ||
#AH = 2+1 | ||
#AI = (2+1)^4 | ||
#AJ = pi/(2+1)^4 | ||
#AK = pow(5*b, pi/(2+1)^4) | ||
#AL = a*pow(5*b, pi/(2+1)^4) |
The expanded expression is not meant to be calculated, instead calculations are performed only on the generated variables. In order to get fast calling we store the generated variables in separate map. Upon request for the result we calculate generated variables successively from #AG to #AL. In the end #AL is the result of our calculation. By setting the user variables prior to calling the result this train of calculations gives the result of the function operating on those user variables.
2.3. Entities
We recognize three types of entities:
-
Argument - anything that can have a value. This can be a predefined constant in the system, a number from the expression (user constant), a variable or a generated variable (a temporal variable generated as a stack for calculation steps).
-
Operator - functions defined in the system at compile time. Names of operators are reserved words. These are functions defined for example in the math.h library like sin, pow or static functions that are defined in the MathParser.
-
Generator - combination of operators and arguments used in mid-steps of calculating the result. Names of generator coincide with some names of arguments from the argument map, this connects the argument and generator. Arguments connected to generators are generated variables.
3. Examples of use
First you must include the header in the .c file where you want to use the MathParser.
You can create parsed expression by directly writing the expression as a string.
#include "pssmathparser.h"
// Some code
using namespace PssMathParser;
// Some code
int main()
{
MathParser *mp = MathParser::makeMathParser();
mp->setMath("2 * sin( 2*pi*f*t + phi )");
mp->setVariableDouble("phi", 0.785398);
double result = mp->calculateExpression();
}
Or if you have some code that parses user input (or files) you can load it in a variable and use the result.
string exStr;
string arg1Str;
double arg1Val;
// Load the expression (function) in the variable (exStr)
// Load the function arguments (arg1Str)
// Get the argument values (arg1Val)
// Calculate the function in runtime
mp->setMath(line);
mp->setVariableDouble(arg1Str, arg1Val);
double result = mp->calculateExpression();
You can get the number of variables (arguments):
uint16_t numVariables = mp->getVariableSize();
4. Using binaries
If you only want to use the library without modifying it, download the binaries (.dll for Windows or .so for Linux) and the header files. Include the header in your project and point the linker where the library is. After this you can use it as described.
To see how you can link to the pssmathparser library check the tests. There is a Makefile that includes the library:
LDFLAGS = -Wl,-R $(PWD)/../lib/ -L$(PWD)/../lib/
LIBS = -lpssmathparser
INCLUDES = -I$(PWD)/../src/
# Some code ...
all:
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $^ $(INCLUDES) $(LIBS)
Here we pass the Library Flags (LDFLAGS) to point where the library is located. In our case the library binaries are under folder above in the lib folder. We pass the name of the library to the linker (LIBS). We also must tell the compiler where the header "pssmathparser.h" file is located (INCLUDES).
Then we include the header in the .c file where we use the MathParser.
#include "pssmathparser.h"
// Some code
using namespace PssMathParser;
// Some code
int main()
{
MathParser *mp = MathParser::makeMathParser();
mp->setMath("2 * sin( 2*pi*f*t + phi )");
mp->setVariableDouble("phi", 0.785398);
double result = mp->calculateExpression();
}
5. Tests and metrics
There are couple of tests testing each functionality. The test No. 4 is for testing the speed of the calculating the parsed functions vs calling the hardcoded function.
I've measured good speed (between 1 to 3 times slower than the hardcoded
function). This is from library compiled with -03
optimization.
func1 = Io*(exp(qe*V/(kBJ*(ToK+TC)))-1);
func2 = Io*(exp(qe*V/(kBJ*(ToK+TC)))-1)+Io*(exp(qe*V^(2.5)/(kBJ*(ToK+TC)))-2);
func3 = cos(2*pi*3*t)*exp(-pi*t^(2));
Function | System (compiler) | Parsed execution time [s] | Hardcoded execution time [s] | Parsed/Hardcoded execution time |
---|---|---|---|---|
func1 | Win10 x64 (MSVC2013) | 4.8e-08 | 1.6e-08 | 3 |
func1 | Ubuntu16.04 x64 (GCC 5.4.0) | 1.704e-07 | 9.566e-08 | 1.781 |
func2 | Win10 x64 (MSVC2013) | 1.24e-7 | 9.3e-8 | 1.333 |
func2 | Ubuntu16.04 x64 (GCC 5.4.0) | 3.442e-07 | 2.238e-07 | 1.538 |
func3 | Win10 x64 (MSVC2013) | 1.24e-7 | 4.6e-8 | 2.696 |
func3 | Ubuntu16.04 x64 (GCC 5.4.0) | 1.759e-07 | 1.639e-07 | 1.073 |
6. License, version and download
The PssMathParser library is released under the GNU General Public License
Note:
Version number should be set at the following places:
1. Makefile
from the root folder.
2. pssmathparser.pro
from the root folder.
3. In Doxyfile
from the docs
folder, set the variable PROJECT_NUMBER
- 1.0.0 : Minimum set of functions and constants.
To download the compiled binary of the latest version go here: