Field - A type system for Jep

The com.singularsys.extensions.field package provides a type system for Jep, allowing use of new datatype like rational numbers and matrices.

New datatypes can be added to the Jep system by implementing the FieldI interface. This defines methods such as add(Object l, Object r) which the field must implement. Different fields can be mixed together using to produce a complex type system.

In general each method takes one or two Objects as input and returns an Object. It should test its arguments to see if they are of the correct type and if they are the method should return the result. If the argument are not of a type the field can work with then the methods should return null. In rare circumstances they can also throw an EvaluationException. A typical implementation for add would be

   public Object add(Object l, Object r) throws EvaluationException {
	   if(l instanceof Double && r instanceof Double) {
	       double ld = ((Double) l).doubleValue();
	       double rd = ((Double) r).doubleValue();
	       return Double.valueOf(ld+rd);    
	   }
	   return null;
   }

An implementation should implement:

7 basic arithmetic methods
add(l,r), sub(l,r), mul(l,r), div(l,r), pow(l,r), mod(l,r), neg(l)
6 comparison operators
eq(l,r), ne(l,r), lt(l,r), gt(l,r), le(l,r), ge(l,r)
3 logical operators
and(l,r), or(l,r), not(l).
two operators returning values
getZero() and getOne()

Often some of these methods will just return null. The arithmetic methods all return Object, and comparison and logical methods return Boolean values.

Configuration

Fields can work with Jep using the FieldOperatorTable. This links evaluation of expression containing the standard operators +, -, *, / etc. with the corresponding method in a field. A field is specified in the FieldOperatorTable's constructor and this table is then used in the Jep constructor.

For example to set up arithmetic on doubles would be

    DoubleField df = new DoubleField();
    FieldOperatorTable fot = new FieldOperatorTable(df);
    Jep jep = new Jep(fot);
    Node n = jep.parse("1.0 + 2.0 * 3.0");
    Object res = jep.evaluate(n);

Combining Fields

It is typical to mix a few different fields together. For example the DoubleField has null implementations for the logical operators. The BooleanField provides implementations for these and the two can be combined using a FieldCollection. Fields can be added to the collection using the addField(FieldI field) method or the FieldCollection(FieldI... fields) constructor. The order in which fields are added is important, During evaluation the it will test the result from the first field, if that is null it will then try the remaining fields in turn. The FieldCollection implements FieldI so can be used in the FieldOperatorTable. An implementation which combine the DoubleField and BooleanField might be

    DoubleField df = new DoubleField();
    BooleanField bf = new BooleanField();
    FieldCollection fc = new FieldCollection();
    fc.addField(df);
    fc.addField(bf);
    FieldOperatorTable fot = new FieldOperatorTable(fc);
    Jep jep = new Jep(fot);
    Node n = jep.parse("1.0 < 2.0 && 2.0 < 3.0");
    Object res = jep.evaluate(n);

For some Fields it may also be necessary to provide a implementation of a NumberFactory. The main method, createNumber(String value), is used by the parser to converter numerical strings in the input into an appropriate type. For example an implementation which uses the arbitrary-precision BigInteger type would need the BigIntegerNumberFactory.

    BigIntegerField bif = new BigIntegerField();
    BigIntegerNumberFactory binf = new BigIntegerNumberFactory();
    FieldOperatorTable fot = new FieldOperatorTable(bif);
    Jep jep = new Jep(fot,binf);
    Node n = jep.parse("1000^5");
    Object res = jep.evaluate(n);

Implementations

The com.singularsys.extensions.field.implementations provide a number of standard implementations.

DoubleField
Implemented using Doubles.
IntegerField
Implemented using Integers, requires an IntegerNumberFactory.
ExactIntegerField
Implemented using Integers and the new addExact(int,int) etc. of java.lang.Math methods of Java 1.8. These throw ArithmeticException if results overflow. The Jep implementation wrap these exceptions in EvaluationExceptions.
BigIntegerField
Unlimited precision integers, implemented using BigIntegers, requires a BigIntegerNumberFactory.
BigDecimalField
Implemented using BigDecimals. Can be set to use a specific NumberContext to set the level of accuracy required. Requires a BigDecNumberFactory.
RationalField
Rational numbers represented as a quotient of two BigIntegers, and implemented using the Rational data-type. Requires a RationalNumberFactory
ComplexField
Complex numbers implemented using the Complex data type. requires a ComplexNumberFactory.
ModulusField
Integers modulo a given base, i.e. Z(n). The modulus is specified in the constructor and can use with modulus upto 46,341.
BigModulusField
Performs integer calculations modulus a value specified in the constructor. This class can use very large values of the modulus.
BooleanField
Implements the standard logical operations. Generally used to add logical capabilities to other fields.
StringField
Allows addition and comparison of strings.

Decorators

FieldDecorators allow a form of type casting with Field elements, allowing different types to be mixed. The above fields only work on their corresponding types, so a DoubleField will only accept Double arguments and only produce Double results. A FieldDecerator can be wrapped around a field converting the input and output of each method. For example the NumberToDoubleDecorator converts Integers and other Number types to Doubles. This can be with a DoubleField and allows Integers and Doubles to be added together. A typical setup might be

    // Can add int+int
    IntegerField intf = new IntegerField();                        
    // Can add double + double
    DoubleField df = new DoubleField();                            
    
    // A decorator applied to the DoubleField
    // Can add double+double and int+double
    NumberToDoubleDecorator ndd = new NumberToDoubleDecorator(df); 
    
    // Can add int+int, double+double and int+double
    FieldCollection fc = new FieldCollection(intf,ndd);            
    FieldOperatorTable fot = new FieldOperatorTable(fc);
    IntDoubleNumberFactory idnf = new IntDoubleNumberFactory();
    Jep jep = new Jep(fot,idnf);
    
    // Adding integer + integer result in an integer
    Object res = jep.evaluate(jep.parse("2+3"));
    assertTrue(res instanceof Integer);
    // Adding double + double result in a double
    res = jep.evaluate(jep.parse("2.5+3.5"));
    assertTrue(res instanceof Double);
    // Adding an integer + double result in a double
    res = jep.evaluate(jep.parse("2+3.5"));
    assertTrue(res instanceof Integer);

The above example uses an IntDoubleNumberFactory which can parse numbers as either Integers or Doubles. During evaluation the IntergerField will first be used to try and evaluate the result, if this fails then the decorator will convert both arguments to doubles and use the DoubleField to do the evaluation.

The abstract base class FieldDecorators define two abstract methods convertInput(Object in) and convertOutput(Object out) which must be implemented by sub-classes to convert the inputs and outputs to the correct type of a field. A field will be specified in its constructor.

Four decorators are provided

NumberToDoubleDecorator
Converts all number types to Doubles. A flag can be set so that on output Doubles can be converted to Integers when there is no loss of precision.
NumberToComplexDecorator
Converts all number types to Complex. A flag can be set so that if the complex number represents a real number the output is converted to a Double.
IntegerToBigIntegerDecorator
Converts Integer, Short and Long to to BigInteger. A flag can be set so that on output BigIntegers can be converted to Integers when there is no loss of precision.
BigIntegerToRationalDecorator
Converts Integer, Short, Long and BigInteger to Rationals. A flag can be set so that if the Rational number can be converted to BigIntegers when there is no loss of precision.

Base Classes

Three base classes are provided to make it simpler to construct fields.

The AbstractComparativeField replaces the six comparative operations eq, ne, lt, le, gt, ge by a single Integer cmp(Object l,Object r) which returns -1, 0, +1 depending on the comparison.

The GenericField<E> reduces the need for type casts. Rather than writing methods like Object add(Object l,Object r) with type casting code a single method E cast(Object l) takes care of type casting and methods with a more specific type E addG(E l, E r) method are used for evaluation.

The GenericPowerField<E> extends the GenericField and adds a method for calculating integer powers of a field element.

Example Field

A field for String could be implemented in a number of ways. The basic method requires all methods to be implemented

// just need to implement additive and equality operations
public class StringField  implements FieldI {
    private static final long serialVersionUID = 1L;

    // If both arguments are string then concatenate the results
    // otherwise return null
    @Override
    public Object add(Object l, Object r) throws EvaluationException {
        if(l instanceof String && r instanceof String)
            return ((String) l)+((String)r);
        return null;
    }
    
    // Test equality of two strings, 
    // returns true is equal, false if not equal or null if not strings
    @Override
    public Boolean eq(Object l, Object r) throws EvaluationException {
        if(l instanceof String && r instanceof String) {
            String ls = (String) l;
            String rs = (String) r;
            boolean res = ls.equals(rs);
            return res;
        }
        return null;
    }
    // Other comparison operations need to be implemented
    public Boolean ne(Object l, Object r)  { .... }
    public Boolean gt(Object l, Object r)  { .... }
    public Boolean ge(Object l, Object r)  { .... }
    public Boolean lt(Object l, Object r)  { .... }
    public Boolean le(Object l, Object r)  { .... }
    
    // Null results for other operators
    public Object sub(Object l, Object r)  { return null; }
    public Object neg(Object l)            { return null; }
    public Object mul(Object l, Object r)  { return null; }
    public Object div(Object l, Object r)  { return null; }
    public Object mod(Object l, Object r)  { return null; }
    public Object pow(Object l, Object r)  { return null; }
    public Boolean and(Object l, Object r) { return null; }
    public Boolean or(Object l, Object r)  { return null; }
    public Boolean not(Object l)           { return null; }
}

An alternative is to extend the abstract base class AbstractComparativeField fields. Here the single cmp method needs to be implemented rather than the six comparisons.

public class StringField2  extends AbstractComparativeField {
    private static final long serialVersionUID = 1L;

    // If both arguments are string then concatenate the results
    // otherwise return null
    @Override
    public Object add(Object l, Object r) throws EvaluationException {
        if(l instanceof String && r instanceof String)
            return ((String) l)+((String)r);
        return null;
    }
    
    // Compare two string, 
    // returns -1, 0 or 1 depending on the comparisons
    @Override
    public Integer cmp(Object l, Object r) throws EvaluationException {
        if(l instanceof String && r instanceof String) {
            String ls = (String) l;
            String rs = (String) r;
            int res = ls.compareTo(rs);
            return res;
        }
        return null;
    }
    
    // Null results for other fields
    public Object sub(Object l, Object r)  { return null; }
    public Object neg(Object l)            { return null; }
    public Object mul(Object l, Object r)  { return null; }
    public Object div(Object l, Object r)  { return null; }
    public Object mod(Object l, Object r)  { return null; }
    public Object pow(Object l, Object r)  { return null; }
    public Boolean and(Object l, Object r) { return null; }
    public Boolean or(Object l, Object r)  { return null; }
    public Boolean not(Object l)           { return null; }
}

This base class is not always appropriate. For a double's there are problems with comparing NaN's and the individual lt, gt, le, lt methods need to be implemented.

Another option would be to use the generic base class GenericField<String>.

public class StringField3 extends GenericField<String> {
    private static final long serialVersionUID = 1L;

	// Cast the argument to a String or return null
    @Override
    public String cast(Object l) throws EvaluationException {
        if(l instanceof String) 
            return ((String) l);
        return null;
    }

    // No need for casting in individual operations
    @Override
    public String addG(String l, String r) throws EvaluationException {
        return l + r;
    }

    // A single method for all comparison operations
    @Override
    public Integer cmpG(String l, String r) throws EvaluationException {
        return l.compareTo(r);
    }

    // other methods just return null
    public String subG(String l, String r) { return null; }
    public String negG(String l) { return null; }
    public String mulG(String l, String r) { return null; }
    public String divG(String l, String r) { return null; }
    public String modG(String l, String r) { return null; }
    public String powG(String l, String r) { return null; }
    
    // no need to implement comparison operations le(l,r) etc.
    // or logical operations and(l,r), or(l,r), not(l) 
}

Other features

The optional com.singularsys.extensions.field.functions provides binomial and factorial functions which can work with any field.

Examples

The FieldConsole is a console application which allow different fields to be used.

The ExtSpeedTest compare the speed of evaluation using different fields. Using fields is slightly slower than a Standard Jep implementation.

The com.singularsys.exttests.field package has a number of test cases for checking various implementations of Fields.

The com.singularsys.extensions.matrix package offers a framework for working with vectors and matrices (Documentation, Javadoc).

top