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.
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);
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);
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);
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);
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));
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.
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.