Vectors and Matrices in Jep

The com.singularsys.extensions.matrix package provides support for working with vectors and matrices allowing various different implementations. It provides simple interfaces which implementations must support as well as a number of different implementations. The matrix package builds on the field package which provides a type system for Jep.

Basic interfaces

Two interfaces VectorI and MatrixI define the basic types for vectors and matrices. These are deliberately kept simple to allow easy implementations. The MatrixI interface defines methods getNRows(), getNCols() which return the number of rows and columns of the matrix respectively. Individual elements can be get and set using getEle(row,col) and setEle(row,col,val). The whole matrix can be converted into an 2 dimensions array using toArray() with return an 2D Object[][] array. The VectorI interface defines getNEles(), getEle(i), setEle(i,val) and toArray() with returns an Object[].

The MatrixFactoryI interface defines methods for creating vectors and matrices of different sizes.

The MatrixFieldI defines the various operations performed on a vectors and matrices. This extends the FieldI interface with methods like add(l,r) which attempts to add two objects and adds specific vectors and matrix operations: the dot product of two vectors dot(u,v), the cross product of two 3D vectors cross(u,v), determinant det(m) trace trace(m) and transpose of a matrix trans(m).

Standard Implementations

A number of standard implementations are provided by sub-packages

com.singularsys.extensions.matrix.doublemat
double matrices
com.singularsys.extensions.matrix.objectmat
matrices defined for objects with the operations on individual elements defined by a Field.
com.singularsys.extensions.matrix.complexmat
Complex matrices
com.singularsys.extensions.matrix.sequencemat
an efficient implementation for vectors and matrices defined over doubles.
The objectmat package make it easy to define matrices for any given type.

Usage

The simplest method for setting up Jep to use Vector and Matrices involves constructing a MatrixFactoryI and a MatrixFieldI. From these a MatrixOperatorTable and a MatrixFunctionTable are constructed and these are used in building the Jep implementation.

MatrixFactoryI mfac = new DoubleMatrixFactory();
MatrixFieldI mf = new DoubleMatrixField(mfac);
MatrixOperatorTable opTab = new MatrixOperatorTable(mfac, mf);
MatrixFunctionTable mftab = new MatrixFunctionTable(mfac, mf);
Jep jep = new Jep(opTab,mftab,new StandardConfigurableParser());

Lists are parsed as vector and lists of list parsed as matrices. Multiplication can be carried out between matrices and vectors.

Node n1 = jep.parse("v = [2.0,1.0]");
VectorI vres = (VectorI) jep.evaluate(n1);
Node n2 = jep.parse("m = [[3., 0.],[0., 2.]]");
MatrixI mres = (MatrixI) jep.evaluate(n2);
Node n3 = jep.parse("m * v");
VectorI vres2 = (VectorI) jep.evaluate(n3);
System.out.println(vres2); // Prints [6.0, 2.0]
System.out.println("First element "+vres2.getEle(0)
    +" second element "+vres2.getEle(1));

Note all matrices must be rectangular with the same number of elements in each row. 3 dimensional arrays are not allowed.

The matrix factory class can be used to construct vectors and matrices.

VectorI u = mfac.newVector(3.0, 4.0);
MatrixI m = mfac.newMatrix(new Object[][]{{0.0, -1.0},{1.0,0.0}} );
jep.addVariable("u", u);
jep.addVariable("m", m);
Node n4 = jep.parse("m*u");
VectorI vres3 = (VectorI) jep.evaluate(n4);
System.out.println(vres3); // Prints [-4.0, 3.0]

A number of operators and functions are available.

jep.parse("u . u"); // dot-product of vectors
jep.parse("i = [1,0,0]"); // setting vectors
jep.parse("j = [0,1,0]");
jep.parse("i ^^ j"); // cross product of two 3D vectors
jep.parse("m1 = [[1,2,3],[4,5,6],[7,8,0]]");
jep.parse("det(m1)");  // determinant of a matrix
jep.parse("trace(m1)");  // trace of a matrix
jep.parse("trans(m1)");  // transpose of a matrix
jep.parse("id(3)");      // generate an identity matrix
jep.parse("size(m1)");   // the size of a matrix
jep.parse("v = solve(m1,u)");   // solve 

Individual elements of vectors can be read using the v[2] syntax, which get the second element of an array. They can also be set using v[2] = 5. For matrices the m[1][2] syntax can be used to get the second element in the first row. Note, this requires the configurable parser as the standard parser does not recognize the syntax.

Node n5 = jep.parse("m = [[0,-1],[1,0]]");
System.out.println(jep.evaluate(n5));
Node n6 = jep.parse("m[1][2]");
Double ele = (Double) jep.evaluate(n6);
System.out.println(ele);
Node n7 = jep.parse("m[1][2]=1.0");
ele = (Double) jep.evaluate(n7);
System.out.println(ele);
Node n8 = jep.parse("m");
MatrixI mres2 = (MatrixI) jep.evaluate(n8);
System.out.println(mres2);

By default the matrix fields will also perform operation on item in the base field. So in a DoubleMatrixField it will perform

Node n9 =jep.parse("u = [3 , 3+1]");
VectorI vres4 = (VectorI) jep.evaluate(n9);
System.out.println(vres4);
Node n10 = jep.parse("u . u - 25");
Double dres = (Double) jep.evaluate(n10);
System.out.println(dres);

For some field this behaviour can be switched off. See Combining fields section below for how a seperate field can be added to perform operations on the base field.

Matrix implementations

A number of different implementations are provided.

Simple matrices

The ObjectMatrixFactory and SimpleObjectMatrixField provide a general implementation, where elements of vectors and matrices can be any type accepted by the standard Jep system. Calculations on the individual elements are performed using the standard Jep functions and operators

MatrixFactoryI mfac = new ObjectMatrixFactory();
MatrixFieldI mf = new SimpleMatrixField(mfac);
MatrixOperatorTable opTab = new MatrixOperatorTable(mfac, mf);
MatrixFunctionTable mftab = new MatrixFunctionTable(mfac, mf);
Jep jep = new Jep(opTab,mftab,new StandardConfigurableParser());

Field Operations

The FieldMatrixField have operations defined by a Field. For instance an implementation which uses BigDecimals might be

NumberFactory nf = new BigDecNumberFactory(MathContext.DECIMAL128);
FieldI f = new BigDecimalField(MathContext.DECIMAL128); 
MatrixFactoryI mfac = new ObjectMatrixFactory(nf.getZero(),nf.getOne());
MatrixFieldI mf = new FieldMatrixField(mfac,f);
MatrixOperatorTable opTab = new MatrixOperatorTable(mfac, mf);
MatrixFunctionTable mftab = new MatrixFunctionTable(mfac, mf);
Jep jep = new Jep(opTab,mftab,nf,
	new StandardConfigurableParser() ); 

Double matrices

There are two version optimised for vectors and matrices where every element is a double. In the first DoubleMatrixFactory DoubleMatrixField define define the elements of matrix as an array double[nrows][ncols] this is about 50% faster than the standard Simple matrix implementation.

NumberFactory numf = new DoubleNumberFactory();
MatrixFactoryI mfac = new DoubleMatrixFactory();
MatrixFieldI mf = new DoubleMatrixField(mfac);
MatrixOperatorTable opTab = new MatrixOperatorTable(mfac, mf);
MatrixFunctionTable mftab = new MatrixFunctionTable(mfac, mf);
Jep jep = new Jep(opTab,mftab,numf,new StandardConfigurableParser());
A further improvement the standard implementation and the SequenceMatrixFactory SequenceMatrixField define a matrix as a linear array rather than a 2D array. This give an additional 20% speed improvement.
NumberFactory numf = new DoubleNumberFactory();
SequenceMatrixFactory mfac = new SequenceMatrixFactory();
SequenceMatrixField mf = new SequenceMatrixField(mfac);
OperatorTable2 opTab = new MatrixOperatorTable(mfac, mf);
MatrixFunctionTable mftab = new MatrixFunctionTable(mfac, mf);
Jep jep = new Jep(opTab,mftab,numf,new StandardConfigurableParser());

Complex matrices

The ComplexMatrixFactory ComplexMatrixField give matrices where all elements are complex numbers.

NumberFactory numf = new ComplexNumberFactory();
ComplexMatrixFactory mfac = new ComplexMatrixFactory();
ComplexMatrixField mf = new ComplexMatrixField(mfac);
OperatorTable2 opTab = new MatrixOperatorTable(mfac, mf);
MatrixFunctionTable mftab = new MatrixFunctionTable(mfac, mf);
Jep jep = new Jep(opTab,mftab,numf,new StandardConfigurableParser());

Combining fields

A matrix field can be combined with other fields using the MatrixFieldCollection. Typically one matrix field can be combined with BooleanField to allow logical operators to be used.

// Create a field and factory
ComplexMatrixFactory cmfac = new ComplexMatrixFactory();
MatrixFieldI cmf = new ComplexMatrixField(cmfac);
// Create a field collection and add fields to it
MatrixFieldCollection mfc = new MatrixFieldCollection();
mfc.addField(cmf);
BooleanField bf = new BooleanField();
mfc.addField(bf);
// Use the collection in construction of the Operator and Function Tables
OperatorTable2 opTab = new MatrixOperatorTable(cmfac, mfc);
MatrixFunctionTable mftab = new MatrixFunctionTable(cmfac, mfc);
Jep jep = new Jep(opTab,mftab);

jep.parse("u=[1,2,3]");
jep.evaluate();
jep.parse("v=[2,4,6]");
jep.evaluate();
jep.parse("2 * u == v && u == v - u");
Boolean res = (Boolean) jep.evaluate();
System.out.println(res);

One point to note is the a MatrixField also allows operations on the base field. So the DoubleMatrixField also provides operations on double.

Custom types

Custom implementations must provide implementations of the two basic types VectorI and MatrixI as well as providing implementations of MatrixFactoryI and MatrixFieldI. various base classes are used to make implementing these types easier.

The most general is the AbstractMatrixField this takes care of distinguishing between operations on vectors and matrices so rather than implementing add(Object l,Object r) the three methods add(VectorI l,VectorI r), add(MatrixI l,MatrixI r), addEle(Object l,Object r) must be implemented. The latter just assumes elements in the base field and there is it safe to assume the arguments will not be a matrix or vector.

An easier set of base class to implement are the com.singularsys.extensions.matrix.genericmat package which provides a generic base type when matrices are defined over some particular Java type. The Complex implementation above is implemented using generic matrices. This defines types

public class ComplexMatrix2 extends GenericMatrix<Complex> { ... }
public class ComplexVector2 extends GenericVector<Complex> { ... }
public class ComplexMatrixFactory2 extends GenericMatrixFactory<Complex> { ... }
public class ComplexMatrixField2 extends GenericMatrixField<Complex> { ... }

Here just the protected Complex addEle(Complex l, Complex r) needs to be implemented for the field. The actual data is stored in an Object[][] array and methods are provided for to add matrices by looping over the array.

Still easier is the GenericFieldMatrixFactory GenericFieldMatrixField here it uses the GenericFieldMatrixField there is no need to provide implementations of any field methods, as these are carried out using corresponding methods of the GenericPowerField.

NumberFactory numf = new RationalNumberFactory();
RationalField brf = new RationalField(); // Implements
                         // GenericPowerField<Rational>
MatrixFactoryI mfact = new GenericMatrixFactory<>(brf);
MatrixFieldI mfield = new GenericFieldMatrixField<>(mfact, brf);
OperatorTable2 opTab = new MatrixOperatorTable(mfact, mfield);
MatrixFunctionTable mftab = new MatrixFunctionTable(mfact, mfield);
Jep jep = new Jep(opTab, mftab, numf, new StandardConfigurableParser());

The above stores elements in a Object[][] array it is possible to force the data array to be of type Rational[][] by subclassing the GenericFieldMatrixFactory. The RationalMatrixFactory is an example implementation. The benefits of using this type rather than FieldMatrixField above are minimal.

Functions

The MatrixFunctionTable adds a number of functions which can be used in jep expressions.

Further functions which perform statistical operations on matrices can be found in the statistical package.

MatrixComponents and MatrixComponentSet

The construction process can be made a bit simpler using a MatrixComponents, and a MatrixComponentSet. MatrixComponents combine a MatrixFactory and MatrixField in a single class which implements the JepComponent. DoubleMatrixComponents is an implementation which uses vectors and matricies with double elements. MatrixComponentSet is a complete set of Jep components needed for matrix operation.

A typical implementation using double matrices would be

MatrixComponentSet compSet = new MatrixComponentSet(new DoubleMatrixComponents());
Jep jep = new Jep(compSet);
or using a other matrix types with boolean logic support
NumberFactory numf = new ComplexNumberFactory();
ComplexMatrixFactory cmfac = new ComplexMatrixFactory();
MatrixFieldI cmf = new ComplexMatrixField(cmfac);
// Here the flags allow for boolean/string support
MatrixComponents comp = new MatrixComponents(cmfac,cmf,true,false);
Jep jep = new Jep(new MatrixComponentSet(numf,comp));

Other classes

The Dimensions class is used to hold information about the size of vectors and matrices, accessed by the getDimensions() methods of the VectorI and matrixI interfaces. The methods is0D(), is1D(), is2D() to tell is the dimension represents a scaler, vector or matrix and the methods getFirstDim() returns the size of vector or the number of rows in a matrix, getLastDim() return the number columns of a matrix.

The DimensionsVisitor class is a visitor which can calculate the dimensions of a node tree before it has been evaluated. The MatrixFunctionI defines a method to allow the dimensions of a particular matrix aware PostfixMathCommandI to be calculated. to be calculated.

Examples and tests

top