The org.lsmp.djep.xjep package offers a number of extensions to the standard JEP package:
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.
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();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:
....
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"); }
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 XJep class also offers routines to simplify expressions:
XJep j = new XJep();which produces the output
....
Node node=j.parse("1*x^1+0");
j.println(node);
Node simp=j.simplify(node);
j.println(simp);
1.0*x^1.0+0.0Note 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.
x
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.
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.
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 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
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 argumentSee the MacroFunction for precise details of the syntax. Currently only works in 1D, i.e. no vectors or matrices.
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