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.getVariable("x");
int ref2 = 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);
// 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 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.

Serialization

Both RpEval and RpCommandList implements Serializable so serialized versions of expressions can be stored or transmitted.

// set up and compile
Jep jep = new Jep();
RpEval rpe = new RpEval(jep);
String s = "1+2*x/4";
Node n = jep.parse(s);
RpCommandList coms = rpe.compile(n);
int xref = rpe.getVarRef("x");

// Serialize
ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(rpe);
oos.writeObject(coms);
oos.writeInt(xref);
oos.close();
byte bytes[] = baos.toByteArray();

// Deserialize
ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 
ObjectInputStream ois = new ObjectInputStream(bais);
RpEval rpe2 = (RpEval) ois.readObject();
RpCommandList coms2 = (RpCommandList) ois.readObject();
int xref2 = ois.readInt();
ois.close();

// Evaluate
rpe2.setVarValue(xref2, 5.0);
double res2 = rpe2.evaluate(coms2);        

A Jep instance can also be serialised along with the rpe instance. On deserialization the ReEval.init(Jep jep) methods should be called.

Example applications

top