Fast evaluation of mathematical equations in Java - extensions to Jep


The com.singularsys.extensions.fastreal packages offers fast evaluation routines for expressions involving double scalars. These offer a 5-10 times speed improvement over the standard Jep evaluation and approaches the speed obtainable by native java code. The classes are intended to be used in situations where the same expressions are repeatedly evaluated using different values of variables. For expressions involving vectors and matrices see the fastmatrix package instead.

top

Scaler expressions

The com.singularsys.extensions.fastreal.RpEval class is used to perform evaluation for scaler expressions using doubles. A two step process is used first the expression is compiled into an RpCommandList and this is later evaluated. The name "RpEval" stems from Reverse Polish notation which is how the compiled expression is encoded.

Jep j = new Jep();
RpEval rpe = new RpEval(j);

Node node = j.parse("cos(pi/3)^2"); 
RpCommandList list = rpe.compile(node);
double val = rpe.evaluate(list);
System.out.println(val);
rpe.cleanUp();

Variable

The evaluator maintains its only private list of variables values which are stored in an array. The array index of a variable can be found using

int ref = rpe.getVarRef("x");

or

Variable v = j.getVar("x");
int ref = rpe.getVarRef(v);
and the value of the variable can be set and read using
rpe.setVarValue(ref,0.1234);
double val = rpe.getVarValue(ref);

This mechanism allows very fast access to the variable value.

Variable values can also be set using the standard Variable.setValue() or Jep.setVarVal(name,value) methods but this is a little slower. Setting the value of a Jep variable will automatically update the corresponding rpe value but there will be a performance hit if done repeatedly. Setting the value of the rpe variable does not change the value of the corresponding Jep value, however calling rpe.updateJepVariables() will set the values of corresponding Jep variables.

Typical usage

The system works best when the same expression is repeatedly evaluated with different values for the variables. The following code calculates the hight of a surface which depends on two variables, x and y.

// Parse and compile an expression
RpEval rpe = new RpEval(jep);
String s = "a x^2 + b y^2";
Node n = jep.parse(s);
RpCommandList coms = rpe.compile(n);

// Get the references to the variables
int xRef = rpe.getVarRef("x");
int yRef = rpe.getVarRef("y");
int aRef = rpe.getVarRef("a");
int bRef = rpe.getVarRef("b");

// sets the two parameters a, b
rpe.setVarValue(aRef, 2.5);
rpe.setVarValue(bRef, -3.1);

// loop over different values of x and y
for(double x=-1.0;x<=1.0;x+=0.1) {
    rpe.setVarValue(xRef, x);
    for(double y=-1.0;y<1.0;y+=0.1) {
        rpe.setVarValue(yRef, y);
      
        // evaluate the expression with these variable values
        double res = rpe.evaluate(coms);
        System.out.printf("%4.1f ",res);
    }
    System.out.println();
}

How it works

The compile methods converts the expression represented by node into a string of commands. For example the expression "4+5*6" will be converted into the sequence of commands

  Constant no 1 (4) (pushes constant onto stack)
  Constant no 2 (5)
  Constant no 3 (6)
  Multiply scalers (multiplies last two entries on stack)
  -  (6*5), the result (30) is pushed onto the top of the stack
  Add scalers (adds last two entries on stack)
  -  (30+4) the result (34) is pushed onto top of the stack

The evaluate method executes these methods sequentially using a stack and returns the last object on the stack.

Uses as an Evaluator

The com.singularsys.extensions.fastreal.RpEvaluator allows the evaluator to be used in normal jep code. To set up use

  jep = new Jep(new RpEvaluator(true));

And then continue to use the jep code as normal. This is somewhat slower than using rpe.evaluate() directly but is still faster than using the normal Jep evaluator. Some speedup can be obtained by using new RpEvaluator(false) which does not update the jep variables following evaluation.

Implementation notes

A few cautionary notes:

A lot of things have been done to make it as fast as possible:

Supported functions

Functions which take double arguments and return double results are supported. Other functions which return strings or complex numbers will raise exceptions when used.

Some functions have been optimised for speed these are: sin, cos, tan, sec, cosec, cot, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh, abs, exp, log, ln, sqrt, atan2, if these are hand coded in the routines for high performance.

Other jep functions which take double arguments and return double results are also supported. These can be used directly and no additional calls are required.

Performance

The table below indicates the evaluation speeds. Times are in milliseconds for a million evaluations.

ExpressionSpeed using JepSpeed using rpeSpeed using Java
512547
x28194
1+x515110
x^2594109
x*x468109
5*x484110
1+x+x^21375172
1+x+x^2+x^31671250
1+x+x^2+x^3+x^42234328
1+x+x^2+x^3+x^4+x^52860406
1+x(1+x(1+x(1+x(1+x))))237535962
cos(x)48520379
cos(x)^2+sin(x)^21750406
if(x>0.5,1,0)734218
y=x*x; z=y*y; w=z*z1829313

This indicates a speed up of between 3 and 13 times as fast.

Example applications

top