XJep - extensions to JEP


The org.lsmp.djep.xjep package offers a number of extensions to the standard JEP package:

An interactive console applet interactive console applet illustrates the functionality of XJep.

Basic Usage


To use all the features mention on this page the org.lsmp.djep.xjep.XJep class should be used instead of the standard JEP class. All the standard JEP methods are available. For example
import org.nfunk.jep.*;
import org.lsmp.djep.xjep.*;

public class XJepExample {
public static void main(String[] args) {

XJep j = new XJep();

j.addStandardConstants();
j.addStandardFunctions();
j.addComplex();
j.setAllowUndeclared(true);
j.setImplicitMul(true);
j.setAllowAssignment(true);

try {
Node node = j.parse("x = 3");
Node processed = j.preprocess(node);
Node simp = j.simplify(processed);
Object value = j.evaluate(simp);
System.out.println(value.toString());
j.println(simp);
} catch (ParseException e) {} catch (Exception e) {}
}

The parse, preprocess, simplify, evaluate sequence is the standard idiom for using this package.

Printing and conversion to strings

A number of different routines are available to print equations:
public void print(Node node);  // prints the expression tree
// specified by node on standard output
public void print(Node node,PrintStream out); // prints on given stream
public void println(Node node); // newline at end
public void println(Node node,PrintStream out); // newline at end
public String toString(Node node); // returns a string

These methods convert the expression specified by node to a one line string representation. The routines attempt to produce as simple a representation of the string as possible. However brackets are used to resolve ambiguity. Hence the equation "a+(b*c)" will be printed as "a+b*c" whilst "a*(b+c)" will be printed as "a*(b+c)".

XJep j = new XJep();
....
try {
// parse expression
Node node = j.parse("a*b+c*(d+sin(x))");
// print it
j.println(node);
// convert to string
String str = j.toString(node);
System.out.println("String is '"+str+"'");
} catch(ParseException e) { System.out.println("Parse error"); }
By default unnecessary brackets are removed. If you wish to print with lots of brackets (for example to examine exactly how an expression has been interpreted) then you can use:
    j.getPrintVisitor().setMode(PrintVisitor.FULL_BRACKET,true);
j.println(node);

At some stage in the future print facilities to produce MathML and other output formats will be included.

The way that numbers are displayed can be set by supplying a java.text.NumberFormat object.

NumberFormat format = NumberFormat.getInstance();
j.getPrintVisitor().setNumberFormat(format);
format.setMaximumFractionDigits(3);
format.setMinimumFractionDigits(0);
		
String s1 = "[10,0,0.1,0.11,0.111,0.1111,0.999,0.9999]";
Node n1 = j.parse(s1);
System.out.println(j.toString(n1));

Prints [10,0,0.1,0.11,0.111,0.111,0.999,1] i.e. numbers are displayed rounded to three decimal places.

Simplification

The XJep class also offers routines to simplify expressions:

XJep j = new XJep();
....
Node node=j.parse("1*x^1+0");
j.println(node);
Node simp=j.simplify(node);
j.println(simp);
which produces the output
1.0*x^1.0+0.0
x
Note how redundant parts of the equation like addition by zero, multiplication by 1 and raising to the power of one are removed from the equation. Any constant expressions like 1+2*3*cos(0) will also be simplified (in this case giving 7). The simplification algorithm is not perfect and there may well be some expressions which will not be simplified completely. We hope to improve the algorithm more at a later date.

Experimental A more advanced simplification routine is offered by the and org.lsmp.djep.sjep.* package. This converts nodes to an internal polynomial representation, ensuring that monomials of the same type are added together. Also allows full expansion of polynomials. It should also work with functions which contain other functions.

Experimental The and org.lsmp.djep.rewrite.* package. allows arbitaty rewrite of equations. Very early in development.

Re-entrant Parsers

A new syntactical feature is the use of a semi-colon ; to separate expressions. This allows string like "x=1; y=2; z=x+y;" to be parsed. To enable this the re-entrant methods of the XJep class should be used to access the parser.

public void restartParser(String string);
public void restartParser(Reader reader);
public Node continueParsing() throws ParseException;

The first two methods re-initialise the parse instructing it to read equations either from the string or a  given Reader (allowing a sequence of equations to be specified in a file). The last method reads the next equation and finishes whenever a semi-colon is encountered. null is returned when there is no more to read. For example

XJep j = new XJep();
j.setAllowAssignment(true);
...
j.restartParser("x=1; y=2; z=x+y;");
try {
Node node;
while((node = j.continueParsing()) != null) {
Node simp = j.simplify(j.preprocess(node));
Object value = j.evaluate(simp);
j.println(simp);
System.out.println(value.toString);
}
} catch(Exception e) {}

Note: null will also be returned if an empty equation is encountered i.e. for string "x=1; ;y=2; z=x+y;" the above loop would terminate before "y=2" is parsed.

Variables and the preprocessing stage

Internally variables in the org.lsmp.djep.xjep package have both a value and an equation. The equation for a variable is set using the assignment syntax "x=3" or "y=x^2". However the equations are not set by the parser. Instead a new preprocess method is called after parsing and before evaluation.

    Node node = j.parse("x=3");
    Node processed = j.preprocess(node);  // sets the equation for variable x
    Node simp = j.simplify(processed);
    Object value = j.evaluate(simp);
    Node node2 = j.parse("y=x^2");
    Node processed2 = j.preprocess(node2); // sets the equation for variable y
    Node simp2 = j.simplify(processed2);
    Object value2 = j.evaluate(simp2);

The equation for a variable can be recovered by using

j.getVar("y").getEquation();

the preprocess method has additional features when DJep or MatrixJep are used.

Reusing sets of equations

The same set of equations can be used with different values for the variables. However, a little care is needed to keep all the variables with up to date values. There are several different strategies which could be used.

Re-evaluation: By calling j.evaluate on each node this will bring the values of variables on the left had side of an assignment ("x=3" or "y=x^2") will be brought up-to-date.  It is important that this is carried out in the correct order, so that the equation setting the value of a variable is evaluated before equations which rely on this equation.

XJep j = new XJep();
...
// Setting up equations x=3; y=x^2; z=y+x;
Node node1 = j.preprocess(j.parse("x=3"));
System.out.println(j.evaluate(node1)); // prints 3
Node node2 = j.preprocess(j.parse("y=x^2"));
System.out.println(j.evaluate(node2)); // prints 9
Node node3 = j.simplify(j.preprocess(j.parse("z=y+x")));
System.out.println(j.evaluate(node3)); // prints 12

// Change value of x, evaluate equations in turn
j.setVarValue("x",new Double(4));
System.out.println(j.evaluate(node2)); // prints 16
System.out.println(j.evaluate(node3)); // prints 20
System.out.println("z: "+j.getVarValue("z").toString()); // prints 20

Calculating variable values from their equations: The calcVarValue method re-calculate the value of variables using it's equation. Note that is important that the preprocess call is used to set the equations for the variables. The values of the variables should be calculated in order.

j.setVarValue("x",new Double(5));
System.out.println(j.calcVarValue("y").toString()); // prints 25
System.out.println(j.calcVarValue("z").toString()); // prints 30

Lazy evaluation: A lazy evaluation strategy is used by the evaluator to calculate the values of variables. Each variable has a flag to specify whether its value is up-to date or valid. If if is valid then the current value will be used during evaluation, if not then the variable's equation is used to calculate its value. This evaluation happens in a recursive fashion, so that if z depends on y and y depends on x then the equation for x will be evaluated first.

It should be noted that the values of variables are marked as valid whenever their equations are evaluated. This can cause curious behaviour in long chains of equations. The j.getSymbolTable().clearValues() method can be called to mark all variables as being invalid (except constants) and hence ensures that all intermediate equations will be executed as needed. This method should be called before the values of equations are set using setVarValue. 

The upshot of the above is that if clearValues is called then there is no need to evaluate intermediate equations, just the final variable or equation needs to be calculated.

j.getSymbolTable().clearValues();
j.setVarValue("x",new Double(6));
System.out.println(j.findVarValue("z").toString()); // prints 42

j.getSymbolTable().clearValues();
j.setVarValue("x",new Double(7));
System.out.println(j.evaluate(node3));

The motivation behind this scheme comes into play if differentiation when partial derivatives of variables are automatically calculated.

Summary of Variable Use

Summary of use of variables in the XJep package:

Class
Method
Action
JEP
public void addConstant(String name,Object value)
Adds a constant variable whose value can not be changed.
JEP
public void addVariable(String name,Object value) Adds a mutable variable.
JEP
public boolean setVarValue(String name,Object value) Sets the value of a mutable variable. False on error.
JEP
public Variable getVar(String name)
Returns the object representing the variable.
JEP
public Object getVarValue(String name)
Gets the value of the variable. Does not re-calculate.
JEP
public SymbolTable getSymbolTable()
Returns the symbol table containing all the variables.
XJep
public Object calcVarValue(String name) Calculates the value of a variable from its equation.
XJep
public preprocess(Node node)
Causes the equations of variable on the lhs of an assignment equation to be set.
XVariable
public Node getEquation()
Returns the equation of a variable.
XVariable
public Object calcValue()
Calculates the value of a variable from its equation.
SymbolTable
public void clearValues()
Marks all non constant variables as invalid.


There are further methods for working with variable in the Variable, XVariable, SymbolTable and XSymbolTable classes. Standard Hashtable methods can also be used

Macro Functions

XJep also make it easier to define you own simple functions in your code, without having to create a new sub-class of PostFixMathCommand. Such functions can be defined using an String containing its defining equation.

// creates a function with 1 argument
j.addFunction("zap",new MacroFunction("zap",1,"x*(x-1)/2",j));
Node node = j.parse("zap(10)");
System.out.println(j.evaluate(node)); // print 45
See the MacroFunction for precise details of the syntax. Currently only works in 1D, i.e. no vectors or matrices.

Sum type function

An equivilent of the sigma notation for summation are provided by SumType functions. For example Sum(x^2,x,1,10) finds the sum of x^2 with x running from 1 to 10, i.e. 1^2+2^2+...+10^10. Other sum-type functions include:

Note all these function begin with a capital letter to distinguish from sum(1,2,3,4) which justs finds the sum of its arguments.

Other Functions

The toHex() function converts to hexidecimal string toHex(255) -> 0xff. If a second argument is given this specifies number of displayed fraction digits toHex(-15/16,1) -> -0x0.1.

toBase(val,base) and toBase(val,base,digits) convert to a given base with digits fractional digits.

Finding Variables in equations

getVarsInEquation find all the variables in an equation. getVarsInEquation Finds variables, in an equation. If any of those variables are defined by equations, returns those variables as well. An ordered sequence is returned, so that evaluation each variable in turn will give a correct equation.

Other utilities

A number of other features are included in the xjep package. These all work on the trees which are used internally to represent an equation.