beltoforion.de
 Impressum |  Datenschutzerklärung |  CSS |  XHTML
muParserX - math expression parser
muParserX Forum Download

Introduction


The evaluation of a mathematical expression is a standard task required in many applications. It can be solved by either using a standard math expression parser such as muParser or by embedding a scripting language such as Lua. There are however some limitations: Although muParser is pretty fast it will only work with scalar values and although Lua is very flexible it does neither support binary operators for arrays nor complex numbers. So if you need a math expression parser with support for arrays, matrices and strings muParserX may be able to help you. It is based on the original muParser engine but has evolved into a standalone project ever since.

The following table gives an overview over the fundamental differences between the parsers of the muParser family:

Parser Data types Precision User defined operators User defined functions Locale
support
Licence Performance
(Expr. per second)*
complex scalar  string  vector Binary Postfix Infix Strings as
parameters
Arbitrary number
of parameters
muParser partially(1) ok partially(2) fail double ok ok ok ok ok ok MIT ~ 10.000.000
muParserSSE fail ok fail fail float ok ok ok fail max. 10 ok MIT ~ 20.000.000
muParserX ok ok ok ok double ok ok ok ok ok fail LGPL V3 ~ 1.600.000
Table 1: Feature comparison with other derivatives of muParser. (* Average performance calculated using this set of expressions; (1) muParser comes with an implementation for complex numbers but this is rather limited and more of a hack; (2) muParser can define strings but only as constants.)

So much for the good news, the bad news is that: there ain't no such thing as a free lunch. The obvious consequence of this redesign is a loss in performance. You can't expect mathematical expressions to be evaluated with the same speed when they are evaluated using complex numbers. You can't expect callbacks to be equally performant when they have to do runtime type checking on their arguments. So in order not to let the new features compromise muParsers performance i created an experimental version named muParserX and this article will show you how to use it.

Features


Predefined Constants

By default the parser supports the following mathematical constants:

Binary and ternary operators:

Postfix operators:

Infix operators:

Predefined Functions

Sample expressions

The next table shows samples of expressions that can be evaluated using muParserX:

Expression Result Explanation
"hello"=="world" false Comparing strings
"hello "//"world" "hello world" String concatanation operator
sin(a+8i) ... Support for a variety of predefined functions
working with complex numbers.
va[3]+vb[5] ... Support for array variables
va[3]=9 ... Assignment operator in combination with
indexed access to a vector
toupper("hello"//"world") "HELLOWORLD" Transforming a concatenated
string to uppercase.
#010010 18 Interpreting binary values
0x1eff 7935 Interpreting hex values
#10>0x1eff false Comparing binary and hex values
1+2-3*4/5^6 2.99923 The standard operators
a = ((a<b) ? 10 : -10) 10 or -10
depending on a and b
Ternary operator for if-then-else conditionals
(((-9))-e/(((((((pi-(((-7)+(-3)/4/e))))/(((-5))-2)
-((pi+(-0))*(sqrt((e+e))*(-8))*(((-pi)+(-pi)-(-9)*
(6*5))/(-e)-e))/2)/((((sqrt(2/(-e)+6)-(4-2))+((5/(
-2))/(1*(-pi)+3))/8)*pi*((pi/((-2)/(-6)*1*(-1))*(-
6)+(-e)))))/((e+(-2)+(-e)*((((-3)*9+(-e)))+(-9))))
)))-((((e-7+(((5/pi-(3/1+pi)))))/e)/(-5))/(sqrt(((
(((1+(-7))))+((((-e)*(-e)))-8))*(-5)/((-e)))*(-6)-
((((((-2)-(-9)-(-e)-1)/3))))/(sqrt((8+(e-((-6))+(9
*(-9))))*(((3+2-8))*(7+6+(-5))+((0/(-e)*(-pi))+7))
)+(((((-e)/e/e)+((-6)*5)*e+(3+(-5)/pi))))+pi))/
sqrt((((9))+((((pi))-8+2))+pi))/e*4)*((-5)/(((-pi)
)*(sqrt(e)))))-(((((((-e)*(e)-pi))/4+(pi)*(-9)))))
))+(-pi)
-12.23016549 No limit on equation complexity

Using the library


Getting the latest source code

A stable version of muParserX is available for download at the muParser sourceforge site. The latest source code can be checked out from the muParser SVN repository using the following command:

svn co https://muparser.svn.sourceforge.net/svnroot/muparser/branches/muParserX muparserx
Although i never deliberately check in broken versions this is a development snapshots which may not always compile on all systems and may not pass its UNIT test without errors. Most time this does'nt mean it contains more bugs than the official release it just means i added new tests for previousely unknown bugs to the unit test.

Adding the library to your projects

This library depends on the Standard template library (STL). The problem with the STL is that every C++ compiler has its own implementation of the STL and different STL implementations are not compatible in terms of their memory layout. Since STL template classes are used in the API of muParserX I do not see a maintainable way to provide a prebuild library. Your only choice of using the library is adding its source code directly to your project. So In order to use the library you should add all files located in the "muparserx/parser" subdiretory into your own project. The library currently comes with project files for VisualStudio 2008 and a Makefile for the GNU C++ compiler but It should compile on every standard compliant C++ compiler.

Parser Tokens

Before starting I should give you an overview over the classes related to muParserX. Parsing works by splitting the mathematical expression into so called tokens. A token can be either a value, a variable, a function call, an operator or a special character such as brackets or commas. The tokens will be stored internally for processing during the evaluation process. When using the parser you will deal with tokens directly only when setting variables, constants functions or operators.

parser token overview

The parser defines an abstract base class named mup::IToken from which all of its tokens are derived. There are three major kinds of token: Value tokens, Callback tokens and generic tokens. Values represent either constants or variables, callbacks represent functions and operators, generic tokens are used for brackets, commas and special characters.
It's important to know that muParserX handles its tokens via reference counted smartpointers using the mup::TokenPtr<...> template class. There is no need to release their pointers explicitely with the delete operator. The following typedefs represent smartpointer names:

namespace mup
{
  // Type of a managed pointer storing parser tokens via their base type.
  typedef TokenPtr<IToken>   ptr_tok_type;

  // Type of a managed pointer storing value tokens via their base type. 
  typedef TokenPtr<IValue>   ptr_val_type;

  // Type of a managed pointer storing binary operator tokens. 
  typedef TokenPtr<IOprtBin> ptr_binop_type;
}

Parser initialization

In order to use muParserX you have to include the file mpParser.h into your projects. The parser resides in the namespace mup.
#include "mpParser.h"

using namespace mup;

The parser functionality is organized in so called packages. A package consits of a set of predefined mathematical functions, operators and constants as well as value detection callbacks. Creating a parser instance is simple: The constructor takes an optional variable made up of several flags for activating certain parser packages. For convenience the parser already defines the enumeration values pckALL_COMPLEX and pckALL_NON_COMPLEX which can be used to configure the parser for either complex valued calculation or noncomplex calculations. If this parameter is omitted the parser will be run in its default mode which means it is using complex numbers and all available operators and functions will be installed (pckALL_COMPLEX).

ParserX  p(pckALL_NON_COMPLEX);

The following bits and bit combinations can be used for activating or deactivating certain parser packages at construction time:

Representing parser values

muParserX defines a common interface for classes representing Values. This interface is implemented by the classes mup::Value and mup::Variable.

parser value overview

mup::Value is a variant type class able to store different data types in a single object. If you want to set up a value for use with muParserX you first have to wrap it into an object of this type. Then you need to bind the value to a mup::Variable object which essentially serves as a proxy for value objects.

Important member functions of parser value classes:

As you may have already guessed by now, the parser supports the following data types:

Type Shortcut Description Additional
qualification
Integer 'i' signed Integer values scalar
float 'f' double precision floating point value
complex 'c' Complex values with double precision for real an imaginary part.
array 'a' An array of values; May contain elements of mixed type. array
boolean 'b' Boolean values. other
string 's' Literal string values.
void 'v' For internal use only; signals a variable is not bound to a value.

Defining variables and constants

Variables can be defined either explicitely in your C++ code or implicitely at parser runtime. Implicit creation of variables is usefull when writing console applications that require dynamic creation of variables (i.e. by using the assignment operator).

Explicit declaration of variables

In order to create a value for use with muParserX you have to create a value object first. The value class provides overloaded constructors for all relevant types:

  using namespace mup;

  // ...

  // Create a complex variable
  Value cVal(cmplx_type(1, 1));
  
  // Create a string variable
  Value sVal(_T("Hello World"));

  // Creating a floating point variable
  Value fVal(1.1);

  // Creating an integer variable
  Value fVal(1);

  // Creating an boolean variable
  Value bVal(true);

  // Create a 3x3 identity matrix
  Value m1(3, 3, 0);
  m1[0][0] = 1;
  m1[1][1] = 1;
  m1[2][2] = 1;

  // Create an array
  Value arr(2, 0);      // Arguments: number of elements, default value
  arr[0] = 2.0;
  arr[1] = _T("hallo"); // note that arrays can consits of mixed type elements

Once you have a parser value you can bind it to a variable object. A variable serves as a proxy class for value objects. It merely holds a pointer to the original value and refers all queries to it thus allowing the parser to change it. After creating the variable object use the DefineVar function in order to add it to the parser. In a similar procedure constants can be defined by using the DefineConst member function but they can be submitted directly.

  ParserX   p;
  Value  val( 1.1);
  Variable var(&val);
  
  p.DefineVar("a", var);

  // Now lets define some constants
  p.DefineConst("b", val);
  // Alternatively you could simply use:
  p.DefineConst("b", 1.1);
warning Once a variable is defined there is no need to call DefineVar again just to change it's value! If you want to change the value change the submitted value object directly. Remember: The parser has a pointer to this value object and it gets the value directly from there! Consequently you have to make sure the value object exists throughout the lifetime of the parser instance that is using it!

Implicit declaration of variables

Implicit creation of variables refers to the creation of parser variables at parser runtime. With this feature you can create variables on the fly without any additional client code. Since this is usefull only for applications requiring direct user interaction it is turned off by default. In order to use it you have to activate it first by calling the EnableAutoCreateVar member function:

  ParserX p;
  p.EnableAutoCreateVar(true);

  // Lets set up an expression for defining a new variable named "a" with the value 123
  p.SetExpr("a=123");

  // The call to Eval() will create the variable internally
  p.Eval();

Once you have activated the automatic creation of variables the parser will add a variable every time it finds unknown input that could represent a variable name without actually having a definition for such a variable. For instance an expression like a=10 would automatically create a new variable named a provided the variable isn't defined already. It's recommended to always use this feature together with the assignment operator in order to initialize the variable with a proper value. If no assignment operator is found the variable is initialized to zero. The downside is that a statement like abc would also create a variable if there is not already one with that name defined.

Evaluating an expression

After setting up your variables properly you can use the SetExpr member function of muParser in order to define the expression. The next thing you have to do is call Eval to evaluate the expression. The return value is again stored in an object of type mup::Value. The complete code for creating variables, setting up the expression and evaluating the result is shown here:
  // Create the parser instance
  ParserX  p;

  // Create an array of mixed type
  Value arr(3, 0);
  arr[0] = 2.0;
  arr[1] = "this is a string";

  // Create some basic values
  Value cVal(cmplx_type(1, 1));
  Value sVal("Hello World");
  Value fVal(1.1);

  // Now add the variable to muParser
  p.DefineVar("va", Variable(&arr));
  p.DefineVar("a",  Variable(&cVal));
  p.DefineVar("b",  Variable(&sVal));
  p.DefineVar("c",  Variable(&fVal));

  p.SetExpr("va[0]+a*strlen(b)-c");
  for (int i=0; i<10; ++i)
  {
    // evaluate the expression and change the value of
    // the variable c in each turn
    cVal = 1.1 * i;
    Value result = p.Eval();

    // print the result
    console() << result << "\n";
  }

Querying Variables or constants

Sometimes its necessary to get a list of all variables or constants currently defined be muParserX. You may either want to query all variables or just the variables used in an expression. The latter may be usefull when you are dealing with a large number of variables and it's not possible to define all of them before evaluating an expression. In order to get the list of all variables currently defined by an instance of muParserX simply use the ParserX::GetVar() member function. It returns a map containing the variable names as the keys and pointers to the variable tokens as the values. In order to further process the variable you should cast the token into its proper type (mup::Variable) as shown in the following sample:
  // Get a map of all variables used by muParserX
  var_maptype vmap = parser.GetVar();
  for (var_maptype::iterator item = vmap.begin(); item!=vmap.end(); ++item)
    cout << item->first << "=" << (Variable&)(*(item->second)) << "\n";

Getting the expressions used in an expression does work the same way except that you have to use the ParserX::GetExprVar() member function instead of ParserX::GeVar(). Querying the expression variables does only make sense after having set up an expression using ParserX::SetExpr(...).

  // Set the expression
  parser.SetExpr("a*sin(b)");

  // Query the expression variables 
  var_maptype vmap = parser.GetExprVar();
  for (var_maptype::iterator item = vmap.begin(); item!=vmap.end(); ++item)
    cout << "  " << item->first << " =  " << (Variable&)(*(item->second)) << "\n";
Querying all parser constants works the same way by using ParserX::GetConst() member function:
  // Get a map containing all constants
  val_maptype cmap = parser.GetConst();
  for (val_maptype::iterator item = cmap.begin(); item!=cmap.end(); ++item)
    cout << "  " << item->first << " =  " << (Value&)(*(item->second)) << "\n";

Extending the library


Like the original muParser this library is designed to allow users to extend it with custom functions or and operators. There is however one fundamental difference: muParserX is using objects as callbacks and not static functions. The following sections will show you how to extend the library with custom callbacks.

Defining custom functions

All custom functions must implement the ICallback interface. This interface provides the ICallback::Clone(), ICallback::Eval(...) and ICallback::GetDesc() member functions. An implementation of a user defined class may look like this:

  class MySine : public mup::ICallback
  {
    MySine()
      :ICallback(cmFUNC, "mysin", 1)
    {}

    virtual void Eval(ptr_val_type &ret, const ptr_val_type *a_pArg, int a_iArgc)
    {
      // Get the argument from the argument input vector
      float_type a = argv[0]->GetFloat();

      // The return value is passed by writing it to the reference ret
      *ret = sin(a);
    }

    const char_type* GetDesc() const
    {
      return "mysin(x) - A custom sine function";
    }

    IToken* Clone() const
    {
      return new MySine(*this);
    }
  };

The mup::ICallback constructor takes three argument. The first determines the type of the callback. If you want to add a parser function use cmFUNC. The second is the function name and the last parameter is the number of arguments. Please note that the parser leaves the type checking of the function arguments entirely to your implementation of ICallback::Eval.

Binary operators are treated like functions taking two arguments, unary operators like functions with a single argument. Once you have a class which you would like to add as a function to muParserX you have to bind it to your parser instance using the DefineFun member function:

ParserX p;
p.DefineFun(new MySine);
Once submitted to DefineFun the parser takes ownership of the newly created pointer. You may not release it on your own!

Defining custom operators

Defining your own operators is very similar to defining new functions. You have to create a callback object and register it at the parser instance. The parser lets you add unary operators (both infix and prefix) as well as binary operators using the following member functions:

License


LGPL V3I have decided to put muParserX under the LGPLv3 License. Please note that this license is a bit more restrictive than the MIT-Licence used by the original muParser. It allows for commercial use of the unmodified library. However If you modify its source code you are required to publish it under the terms of the LGPL. If you have questions regarding the licence or are in desperate need of another licence contact me and I'll see what i can do on a case by case base.

  muParserX - A C++ math parser library with array and string support
  Copyright (C) 2010 Ingo Berg

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
  as published by the Free Software Foundation, either version 3 of 
  the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU LESSER GENERAL PUBLIC LICENSE for more details.

  You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
  along with this program.  If not, see http://www.gnu.org/licenses.

Benchmarks


Due to it's multiple data types and using complex numbers as it's base type muParserX has to make compromises with regard to absolute parsing performance. However i have been quite successfull in minimizing this effect and consequently the latest Version is now approximately 2000 times faster than the initial release. In other words on a modern computer you should be able to compute well above 1.000.000 expressions per second with muParserX. This should be sufficient for most applications and its not too bad either when compared to other expression parsers. First lets have a look at how the performance of muParserX has increased since its initial release:


Image 1: By comparing benchmark results of different muParserX versions a significant improvement of parsing performance from one version to the next can be shown. This is due to introducing parsing from cached tokens instead of the expression string (V1.05), parsing from the reverse polish notation of the expression (V1.07) and from introducing references for returning results from callback objects rather than using dynamically created value objects (V1.08) and from reducing the number of new/delete calls by introducing a simple memory pool for value items (V1.09).

The next image shows a comparison with other free math parsers. The top performer in this test are muParserSSE and MathPresso which are both using asmjit for just in time compilation of the expression.


Image 2:Even when compared to other parsers with fewer functionality muParserX performs suprisingly well although it does not reach their performance fully due to the overhead imposed by it's additional features.

History


V1.10 (20110703)

Warning: The API of this version is not backwards compatible and requires minor changes to client code!

Changes: Bugfixes:

V1.09 (20101117)

New Feature: Changes: Bugfixes:

V1.08 (20100907)

New Feature: Changes:

V1.07 (20100819)

Enhancements: Bugfixes:

V1.06 (20100711)

V1.05 (20100523)

V1.04 (20100516)

V1.03

V1.02

V1.0 Initial Release (20090327)


Creative Commons License
Autor: Ingo Berg; Dieser Text und die dazugehörigen Mediendateien stehen, sofern nicht anderweitig angegeben, unter der Creative Commons Namensnennung 3.0 Deutschland Lizenz.