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.
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();
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(); }
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.
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.
A few cautionary notes:
RpEval.duplicate()
method can be used to allow the
same command strings to be used in multiple threads.A lot of things have been done to make it as fast as possible:
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.
The table below indicates the evaluation speeds. Times are in milliseconds for a million evaluations.
Expression | Speed using Jep | Speed using rpe | Speed using Java |
---|---|---|---|
5 | 125 | 47 | |
x | 281 | 94 | |
1+x | 515 | 110 | |
x^2 | 594 | 109 | |
x*x | 468 | 109 | |
5*x | 484 | 110 | |
1+x+x^2 | 1375 | 172 | |
1+x+x^2+x^3 | 1671 | 250 | |
1+x+x^2+x^3+x^4 | 2234 | 328 | |
1+x+x^2+x^3+x^4+x^5 | 2860 | 406 | |
1+x(1+x(1+x(1+x(1+x)))) | 2375 | 359 | 62 |
cos(x) | 485 | 203 | 79 |
cos(x)^2+sin(x)^2 | 1750 | 406 | |
if(x>0.5,1,0) | 734 | 218 | |
y=x*x; z=y*y; w=z*z | 1829 | 313 |
This indicates a speed up of between 3 and 13 times as fast.
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.