Fast evaluation of expression with vector and matrices - Jep extensions

The com.singularsys.extensions.fastmatrix package provides a fast evaluation algorithm for evaluating equations using vectors and matrix defined over doubles. It is a more advanced version of the fastreal package which only works for scaler expressions.

The main class is the MrpEval class which performs compilation into a sequence stored in a MrpCommandList. The same class also evalutes the sequence returning results which are a sub-type of MrpRes these can be converted to a double, or a VectorI or MatrixI objects as appropriate.

The name stands for Matrix Reverse Polish Evaluator and the comand sequence uses an efficient reverse-Polish notion. This allows a very simple step by step evaluation. Evaluation using the package is about 10 times faster than a standard Jep implemenation and three times faster the the next most efficient implementation. The package depends on the matrix package which in turn depends on the field package.

Setting up

To set up the Jep needs to initialised with support for matrices typically either using the DoubleMatrixFactory and DoubleMatrixField. and using the MatrixFunctionTable and MatrixOperatorTable classes. Additionally a DimensionVisitor class is needed to calculate the sizes of results at each step.

NumberFactory numf = new DoubleNumberFactory();
MatrixFactoryI mfact = new DoubleMatrixFactory();
DoubleMatrixField mfield = new DoubleMatrixField(mfact);
MatrixFunctionTable ft = new MatrixFunctionTable(mfact,mfield);
MatrixOperatorTable ot = new MatrixOperatorTable(mfact,mfield);
StandardConfigurableParser cp = new StandardConfigurableParser();
Jep jep = new Jep(numf,ft,ot,cp);
DimensionVisitor dimV = new DimensionVisitor(jep);

The StandardConfigurableParser is needed to allow some syntactical elements like the two dimensional element-of operation mat[1][2].

A simpler set up is to use

MatrixComponentSet compSet = new MatrixComponentSet(new DoubleMatrixComponents());
Jep jep = new Jep(compSet);
MatrixFactoryI mfact = compSet.getMatrixFactory();
DimensionVisitor dimV = compSet.getDimensionVisitor();

Once those are created the main MrpEval can be constructed:

MrpEval mrpe = new MrpEval(jep,mfact);

Compilation and evaluation

To compile first the dimensions of each node must be calculated using the DimensionVisitor, then the actual compilation takes place, which returns a list of commands MrpCommandList.

Node node = jep.parse("[[1,2],[3,4]] *[1,2]");
dimV.visit(node);
MrpCommandList com = mrpe.compile(node);

To evaluate the evaluate method is called with the list of commands.

MrpRes res = mrpe.evaluate(com);

Handling the results

The type returned, MrpRes, is that used internally by the package and should be converted to another, more useful type. This value will be corrupted if the evaluate method is called again. If its know the result will be a double then the res.doubleValue() can be called.

// Dot product of vectors
Node node2 = jep.parse("[1,2,3].[1,1,1]");
dimV.visit(node2);
MrpCommandList com2 = mrpe.compile(node2);
MrpRes res2 = mrpe.evaluate(com2);
double dres = res2.doubleValue();
System.out.println(dres);

If the result is known to be a vector the result can be converted to an array of doubles using the res.toArrayVec() method

Node node3 = jep.parse("[1,2,3]+[1,1,1]");
dimV.visit(node3);
MrpCommandList com3 = mrpe.compile(node3);
MrpRes res3 = mrpe.evaluate(com3);
double[] vres = res3.toArrayVec();
System.out.println(Arrays.toString(vres));
or it can be converted to a Double[] array
Double[] doubleVec = res3.toDoubleVec();
System.out.println(Arrays.toString(doubleVec));

If the result is known to be a matrix the result can be converted to a two dimensional array of doubles using the res.toArrayMat() method

// Matrix multiplication
Node node4 = jep.parse("[[1,0],[0,-1]]*[[1,2],[3,4]]");
dimV.visit(node4);
MrpCommandList com4 = mrpe.compile(node4);
MrpRes res4 = mrpe.evaluate(com4);
double[][] mres = res4.toArrayMat();
System.out.println(Arrays.deepToString(mres));
or to a Double[][] array
Double[][] doubleMat = res4.toDoubleMat();
System.out.println(Arrays.toString(doubleMat));

The result type can be determined using the res.getDimensions() which returns a Dimensions type. In particular res.getDimensions().order() methods will return 0 for scaler (Double) results, 1 for vectors and 2 for matrices. The size of vectors can be found using res.getDimensions().getFirstDim() and the number of rows and columns of a matrix by using res.getDimensions().getFirstDim() and res.getDimensions().getLastDim() respectively. The dimensions of the result is always fixed and can be found using MrpCommandList.getDimsOfResult() of the list of commands.

Results can also copied into a VectorI or MatrixI types. The The MrpEval class provides a methods provides two convenience methods to do this. For vectors

VectorI vec1 = mrpe.convertToVector(res3);
System.out.println(vec1);

For matrices

MatrixI mat4 = mrpe.convertToMatrix(res4);
System.out.println(mat4);
a third method MrpEval.convertResult(MrpRes) which converts the result into an appropriate type, either Double, VectorI or MatrixI and requires a cast. For example
MatrixI mat4a = (MatrixI) mrpe.convertResult(res4);

The results can also be copied VectorI or MatrixI objects created by a matrix factory.

// For vectors
VectorI vec3 = mfact.zeroVec(res3.getDimensions().getFirstDim());
res3.copyToVec(vec3);

// For matrices
MatrixI mat4b = mfact.zeroMat(res4.getDimensions().getFirstDim(),
                            res4.getDimensions().getLastDim());
res4.copyToMat(mat4b);

Using Variables

The evaluator uses its own set of variables and the MrpVarRef type is used to identify each variable. A reference to the variable can be found with the getVarRef(String) and getVarRef(Variable) methods which look up the reference either by name or variable. Once found the reference can be used to get and set the variables.

// Create a vector valued variable
Variable uVar = jep.addVariable("u", mfact.newVector(1.0,2.0,2.0));
Node node5 = jep.parse("len = sqrt(u.u)");
dimV.visit(node5);
MrpCommandList com5 = mrpe.compile(node5);
MrpVarRef uRef = mrpe.getVarRef(uVar);     // Look up by variable
MrpVarRef lenRef = mrpe.getVarRef("len");  // look up by name
mrpe.evaluate(com5);
MrpRes lenVal = mrpe.getVarValue(lenRef);  // get the value of the variable
System.out.println(lenVal.doubleValue());

The values of variables returned by getVarValue(MrpVarRef) is the same as returned by main evaluate method and can be converted in the same way. The value can be set by setVarValue(MrpVarRef,double) setVarValue(MrpVarRef,double...) setVarValue(MrpVarRef,VectorI) setVarValue(MrpVarRef,MatrixI)

// Set value using an array of doubles
mrpe.setVarValue(uRef, new double[]{2,3,6});  
mrpe.evaluate(com5);
lenVal = mrpe.getVarValue(lenRef);

// Set the value of a vector using varargs list of arguments
mrpe.setVarValue(uRef, 1.,4.,8.);
mrpe.evaluate(com5);
lenVal = mrpe.getVarValue(lenRef);

// Set value using a VectorI
VectorI vec2 = mfact.newVector(new Object[]{4.0,4.0,7.0});
mrpe.setVarValue(uRef, vec2);
mrpe.evaluate(com5);
lenVal = mrpe.getVarValue(lenRef);

The variables values used by the evaluator are not synchronized with the values of the normal Jep values. The methods updateFromJepVariables() and updateToJepVariables() can be used to copy the variables values from Jep to the evaluator and vica-versa.

// Create two jep variables
Variable xvar = jep.addVariable("x", 1.0);
Variable yvar = jep.addVariable("y", 0.0);

// calculates a variable based on another others

// On compilation the values of Jep variables are copied to mrpe values
String s = "y=x+10";
Node n = jep.parse(s);
dimV.visit(n);
MrpCommandList coms = mrpe.compile(n);
MrpVarRef xref = mrpe.getVarRef("x");
MrpRes xval = mrpe.getVarValue(xref);
assertEquals("Rpe value of x",1d, xval.doubleValue(),1e-9);
MrpVarRef yref = mrpe.getVarRef("y");
MrpRes yval = mrpe.getVarValue(yref);
assertEquals("Rpe value of y",0d, yval.doubleValue(),1e-9);

// evaluation will alter the y value
res = mrpe.evaluate(coms);
assertEquals(s,11.0, res.doubleValue(),1e-9);

// Set the rpe x variable and re-evaluate
mrpe.setVarValue(xref, 2.0);
res = mrpe.evaluate(coms);
assertEquals(s, 12.0, res.doubleValue(),1e-9);

// check the value of the rpe y variable
MrpRes val = mrpe.getVarValue(yref);
assertEquals("Rpe value of y", 12.0, val.doubleValue(),1e-9);

// Altering an rpe variable will not change the corresponding jep variable
assertEquals("Jep value of y", 0.0, yvar.getValue());

// Unless this method is called
mrpe.updateToJepVariables();
assertEquals("Jep value of y", 12.0, (Double) yvar.getValue(),1e-9);

// set a variable value using Jep
// will not alter the rpe variable
jep.setVariable("x",3.0);
res = mrpe.evaluate(coms);
assertEquals(s, 12.0, res.doubleValue(),1e-9);

// unless this method is called
mrpe.updateFromJepVariables();
res = mrpe.evaluate(coms);
assertEquals(s, 13.0, res.doubleValue(),1e-9);

Repeated evaluation

The evaluator really comes into its own when the same expression is re-evaluated multiple times with different values for the variables. The general pattern is compile,

String s2 = "3 x^2 + 4 x - 5";
// Parse, and compile 
Node n2 = jep.parse(s2);
dimV.visit(n2);
MrpCommandList coms2 = mrpe.compile(n2);
// Get the variable reference
MrpVarRef xRef = mrpe.getVarRef("x");
for(double x=1.0;x<10;++x) {
// Set the value
	mrpe.setVarValue(xRef, x);
    // evaluate
    MrpRes res2b = mrpe.evaluate(coms2);
    System.out.println(res2b);         
}

A more extensive example, calculating points on a sphere.

// Vector valued variable need to be specified so dimensions can be calculated
jep.addVariable("p0", mfact.newVector( 1.0, 2.0, 0.0));

// Parse, compile and evaluate an expression
String s3 = "p0+r*[cos(phi) sin(theta), sin(phi) sin(theta), cos(theta)]";
Node n3 = jep.parse(s3);
dimV.visit(n3);
MrpCommandList coms3 = mrpe.compile(n3);

// references for variables
MrpVarRef thetaRef = mrpe.getVarRef("theta");
MrpVarRef phiRef = mrpe.getVarRef("phi");
MrpVarRef rRef = mrpe.getVarRef("r");
MrpVarRef p0Ref = mrpe.getVarRef("p0");

// Set the value for r
mrpe.setVarValue(rRef, 2.5);

// 3D array to store results
double results[][][] = new double[21][21][3];

for(int i=0;i<21;++i) {
    double theta = 0.0 + Math.PI * i / 20;
    mrpe.setVarValue(thetaRef, theta);
    for(int j=0;j<21;++j) {
        double phi = -Math.PI + 2 * Math.PI *j / 20;
        mrpe.setVarValue(phiRef, phi);
        MrpRes res5 = mrpe.evaluate(coms3);
        System.arraycopy(res5.toArrayVec(), 0,results[i][j], 0, 3);
    }
}
System.out.println(Arrays.deepToString(results));

Implementation notes

The evaluator can be used with more than one expression. They can be compiled and evaluated in any order. There is one caveat if the dimensions of variables are change, for instance if the variable x changes from being a 2D vector to a 3D vector then the reset() method should be called. This clear all internal data are remove the variables.

Supported functions

All functions which take double arguments and return double results are supported, some functions have been optimized 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

Functions which implement com.singularsys.extensions.matrix.functions.MatrixFunctionI are supported. These can take vector of matrix arguments and return vector or matrix results. Example include MatrixDet, MatrixTrace and MatrixTrans which calculate the determinant, trace and transpose of a matrix.

Other functions which return strings or complex numbers will raise exceptions when used.

Example applications

top