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.
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 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.
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
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.
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
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:
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.
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.