Pss­Math­Parser

by Ginko Balboa

Posted: July 27, 2018



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:

  1. Load an expression as a string:
MathParser *mp = MathParser::makeMathParser();
mp->setMath("2 * sin( 2*pi*f*t + phi )");
  1. 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: