/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
package org.geogebra.common.kernel.arithmetic;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.TreeSet;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.VarString;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.arithmetic.Inequality.IneqType;
import org.geogebra.common.kernel.arithmetic.Traversing.CopyReplacer;
import org.geogebra.common.kernel.arithmetic.Traversing.VariablePolyReplacer;
import org.geogebra.common.kernel.arithmetic3D.MyVec3DNode;
import org.geogebra.common.kernel.commands.EvalInfo;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoFunction;
import org.geogebra.common.kernel.geos.GeoLine;
import org.geogebra.common.main.MyError;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.util.MaxSizeHashMap;
import org.geogebra.common.util.MyMath;
import org.geogebra.common.util.StringUtil;
import org.geogebra.common.util.debug.Log;
/**
* Function of N variables that returns either a number or a boolean. This
* depends on the expression this function is based on.
*
* @author Markus Hohenwarter + mathieu
*/
public class FunctionNVar extends ValidExpression
implements FunctionalNVar, VarString {
/** function expression */
protected ExpressionNode expression;
/** function variables */
protected FunctionVariable[] fVars;
private IneqTree ineqs;
/** standard case: number function, see initFunction() */
protected boolean isBooleanFunction = false;
/** if the function is of type f(x) = c */
protected boolean isConstantFunction = false;
/** kernel */
protected Kernel kernel;
/**
* Creates new Function from expression. Note: call initFunction() after
* this constructor.
*
* @param expression
* function expression
*/
public FunctionNVar(ExpressionNode expression) {
kernel = expression.getKernel();
this.expression = expression;
}
/**
* Creates new Function from expression where the function variables in
* expression is already known.
*
* @param exp
* function expression
* @param fVars
* variables
*/
public FunctionNVar(ExpressionNode exp, FunctionVariable[] fVars) {
kernel = exp.getKernel();
expression = exp;
this.fVars = fVars;
}
/**
* Creates a Function that has no expression yet. Use setExpression() to do
* this later.
*
* @param kernel
* kernel
*/
public FunctionNVar(Kernel kernel) {
this.kernel = kernel;
}
/**
* Copy expression and variables from template (shallow)
*
* @param fn
* template function
*/
public void set(FunctionNVar fn) {
this.expression = fn.getExpression();
this.fVars = fn.fVars;
this.isBooleanFunction = fn.isBooleanFunction;
this.isConstantFunction = fn.isConstantFunction;
this.ineqs = fn.ineqs;
}
/**
* copy constructor
*
* @param f
* source function
* @param kernel
* kernel
*/
public FunctionNVar(FunctionNVar f, Kernel kernel) {
expression = f.expression.getCopy(kernel);
fVars = f.fVars; // no deep copy of function variable
isBooleanFunction = f.isBooleanFunction;
isConstantFunction = f.isConstantFunction;
this.kernel = kernel;
}
/**
* Determine whether var is function variable of this function
*
* @param var
* variable name
* @return true if var is function variable of this function
*/
public boolean isFunctionVariable(String var) {
if (fVars == null) {
return false;
}
for (int i = 0; i < fVars.length; i++) {
if (fVars[i].toString(StringTemplate.defaultTemplate).equals(var)) {
return true;
}
}
return false; // if none of function vars equals var
}
/**
* @return kernel
*/
@Override
public Kernel getKernel() {
return kernel;
}
@Override
public FunctionNVar deepCopy(Kernel kernel1) {
return new FunctionNVar(this, kernel1);
}
/**
* @return function expression
*/
final public ExpressionNode getExpression() {
return expression;
}
@Override
public void resolveVariables(EvalInfo info) {
expression.resolveVariables(info);
}
/**
* Replaces geo and all its dependent geos in this function's expression by
* copies of their values.
*
* @param geo
* geo to be replaced
*/
public void replaceChildrenByValues(GeoElement geo) {
if (expression != null) {
expression.replaceChildrenByValues(geo);
}
}
/**
* Use this method only if you really know what you are doing.
*
* @param exp
* function expression
*/
public void setExpression(ExpressionNode exp) {
expression = exp;
}
/**
* Use this method only if you really know what you are doing.
*
* @param exp
* function expression
* @param vars
* variables
*/
public void setExpression(ExpressionNode exp, FunctionVariable[] vars) {
expression = exp;
fVars = vars;
}
@Override
public FunctionNVar getFunction() {
return this;
}
/**
* Returns array of all variables
*
* @return array of variables
*/
@Override
public FunctionVariable[] getFunctionVariables() {
return fVars;
}
/**
* Returns name of i-th variable
*
* @param i
* index
* @param tpl
* string template
* @return name of i-th variable
*/
final public String getVarString(int i, StringTemplate tpl) {
return fVars[i].toString(tpl);
}
/**
* Number of arguments of this function, e.g. 2 for f(x,y)
*
* @return number of variables
*/
final public int getVarNumber() {
return fVars.length;
}
@Override
public String getVarString(final StringTemplate tpl) {
StringBuilder sb = new StringBuilder();
return appendVarString(sb, tpl).toString();
}
/**
* Appends varstring to the builder
*
* @param sb
* string builder
* @param tpl
* string template
* @return sb
*/
public StringBuilder appendVarString(StringBuilder sb,
final StringTemplate tpl) {
for (int i = 0; i < fVars.length - 1; i++) {
sb.append(fVars[i].toString(tpl));
sb.append(", ");
}
sb.append(fVars[fVars.length - 1].toString(tpl));
return sb;
}
/**
* @return init function and simplify int nodes
*/
public final boolean initFunction() {
return initFunction(true);
}
/**
* Call this function to resolve variables and init the function. May throw
* MyError (InvalidFunction).
*
* @param simplifyInt
* whether int nodes should be simplified
*
* @return whether this is a valid (numeric or boolean) function
*/
public boolean initFunction(boolean simplifyInt) {
// replace function variables in tree
for (int i = 0; i < fVars.length; i++) {
FunctionVariable fVar = fVars[i];
// look for Variable objects with name of function variable and
// replace them
// x, y got polynomials while parsing
VariablePolyReplacer s = VariablePolyReplacer.getReplacer(fVar);
expression.traverse(s);
int replacements = s.getReplacements();
isConstantFunction = isConstantFunction && replacements == 0;
}
// replace variable names by objects
expression.resolveVariables(new EvalInfo(false));
// the idea here was to allow something like: Derivative[f] + 3x
// but wrapping the GeoFunction objects as ExpressionNodes of type
// FUNCTION
// leads to Derivative[f](x) + 3x
// expression.wrapGeoFunctionsAsExpressionNode();
// replace all polynomials in expression (they are all equal to "1x" if
// we got this far)
// by an instance of MyDouble
// simplify constant parts in expression
if (simplifyInt) {
expression.simplifyConstantIntegers();
}
// evaluate expression to find out about the type of function
ExpressionValue ev;
try {
ev = expression.evaluate(StringTemplate.defaultTemplate);
} catch (MyError err) {
// Evaluation failed: DESPERATE MODE
try {
// try to fix structure of expression and then try evaluation
// again
fixStructure();
ev = expression.evaluate(StringTemplate.defaultTemplate);
} catch (Throwable th) {
// throw original error when desperate mode failed
throw err;
}
}
// initialize type as boolean or numeric function
return initType(ev);
}
/**
* Tries to fix a structural problem leading to an evaluation error, e.g.
* x(x+1) is interpreted as xcoord(x+1). This can be fixed by changing the
* structure to x*(x+1) for example.
*/
private void fixStructure() {
// get function variables for x, y, z
FunctionVariable xVar = null, yVar = null, zVar = null;
for (FunctionVariable fVar : fVars) {
if ("x".equals(fVar.toString(StringTemplate.defaultTemplate))) {
xVar = fVar;
} else if ("y".equals(fVar.toString(StringTemplate.defaultTemplate))) {
yVar = fVar;
} else if ("z".equals(fVar.toString(StringTemplate.defaultTemplate))) {
zVar = fVar;
}
}
// try to replace x(x+1) by x*(x+1)
undecided.clear();
expression.replaceXYZnodes(xVar, yVar, zVar, undecided);
for (ExpressionNode en : undecided) {
en.setOperation(Operation.MULTIPLY);
}
undecided.clear();
}
private static ArrayList<ExpressionNode> undecided = new ArrayList<ExpressionNode>();
/**
* Receives result of evaluate as input, hence may use instanceof
*
* @return whether this is a valid (numeric or boolean) function
*/
private boolean initType(ExpressionValue ev) {
if (ev instanceof BooleanValue) {
isBooleanFunction = true;
} else if (ev instanceof NumberValue) {
isBooleanFunction = false;
} else if (ev instanceof FunctionNVar) {
expression = ((FunctionNVar) ev).getExpression();
fVars = ((FunctionNVar) ev).getFunctionVariables();
} else if (ev instanceof GeoFunction) {
expression = ((GeoFunction) ev).getFunctionExpression();
fVars = ((GeoFunction) ev).getFunction().getFunctionVariables();
} else {
return false;
}
return true;
}
/**
* Returns whether this function always evaluates to BooleanValue.
*/
@Override
final public boolean isBooleanFunction() {
return isBooleanFunction;
}
/**
* Returns whether this function always evaluates to the same numerical
* value, i.e. it is of the form f(x1,...,xn) = c.
*
* @return true iff constant
*/
final public boolean isConstantFunction() {
if (isConstantFunction) {
return true;
}
for (int i = 0; i < fVars.length; i++) {
if (expression.contains(fVars[i])) {
return false;
}
}
return true; // none of the vars appears in the expression
}
@Override
public boolean isConstant() {
return false;
}
@Override
public boolean isLeaf() {
return true;
}
/**
* Returns this function's value at position.
*
* @param vals
* values of variables
* @return f(vals)
*/
@Override
final public double evaluate(double[] vals) {
if (isBooleanFunction) {
// BooleanValue
return evaluateBoolean(vals) ? 1 : 0;
}
// NumberValue
for (int i = 0; i < fVars.length; i++) {
fVars[i].set(vals[i]);
}
return expression.evaluateDouble();
}
@Override
final public double evaluate(double x, double y) {
if (isBooleanFunction) {
// BooleanValue
return evaluateBoolean(new double[] { x, y }) ? 1 : 0;
}
// NumberValue
fVars[0].set(x);
fVars[1].set(y);
return expression.evaluateDouble();
}
/**
* Returns this function's value at position vals. (Note: use this method if
* isBooleanFunction() returns true.
*
* @param vals
* values of variables
* @return f(vals)
*/
final public boolean evaluateBoolean(double[] vals) {
for (int i = 0; i < fVars.length; i++) {
fVars[i].set(vals[i]);
}
return expression.evaluateBoolean();
}
@Override
public HashSet<GeoElement> getVariables() {
return expression.getVariables();
}
/**
*
* @return GeoElements acting as variables
*/
public GeoElement[] getGeoElementVariables() {
return expression.getGeoElementVariables();
}
@Override
public String toString(StringTemplate tpl) {
return expression.toString(tpl);
}
@Override
final public String toValueString(StringTemplate tpl) {
return expression.toValueString(tpl);
}
@Override
final public String toOutputValueString(StringTemplate tpl) {
return expression.toOutputValueString(tpl);
}
@Override
final public String toLaTeXString(boolean symbolic, StringTemplate tpl) {
return expression.toLaTeXString(symbolic, tpl);
}
/*
* *************** CAS Stuff **************
*/
/**
* Evaluates this function using the given CAS command. Caching is used for
* symbolic CAS evaluations.
*
* @param ggbCasCmd
* the GeoGebraCAS command needs to include % in all places where
* the function f should be substituted, e.g. "Derivative(%,x)"
* @param symb
* true for symbolic evaluation, false to use values of
* GeoElement variables
* @param arbconst
* arbitrary constant handler
* @return resulting function
*/
final public FunctionNVar evalCasCommand(String ggbCasCmd, boolean symb,
MyArbitraryConstant arbconst) {
StringBuilder sb = new StringBuilder(80);
// remember expression and its CAS string
boolean useCaching = true;
boolean symbolic = symb;
// for multi-variate functions we need to ensure value form,
// i.e. f(x,m)=x^2+m, g(x)=f(x,2), Derivative[g] gets sent as
// Derivative[x^2+2] instead of Derivative[f(x,2)]
// see http://www.geogebra.org/trac/ticket/1466
symbolic = symbolic && !expression.containsGeoFunctionNVar();
// make sure to use temporary variable names
// e.g. a in Derivative[a*x^2,x] needs to be renamed temporarily when a
// exists in GeoGebra
// see http://www.geogebra.org/trac/ticket/929
StringTemplate tpl = StringTemplate.prefixedDefault;
// did expression change since last time?
// or did symbolic falg change?
if (casEvalExpression != expression
|| (symbolic && casEvalStringSymbolic == null)) {
casEvalExpression = expression;
if (symbolic) {
casEvalStringSymbolic = expression.getCASstring(tpl, true);
}
// caching should only be done if the expression doesn't contain
// other functions
// e.g. this is important for f(x) = x^2, g(x,y) = f(x) + y,
// Derivative(g(x,y), x)
// where we cannot cache the derivative of g because g may have
// changed
useCaching = !expression.containsCasEvaluableFunction();
}
// build command string for CAS
String expString = symbolic ? casEvalStringSymbolic
: expression.getCASstring(tpl, false);
// substitute % by expString in ggbCasCmd
String casString = ggbCasCmd.replaceAll("%", expString);
FunctionNVar resultFun = null;
// eval with CAS
try {
if (useCaching) {
// check if result is in cache
resultFun = lookupCasEvalMap(casString);
if (resultFun != null) {
// System.out.println("caching worked: " + casString +
// " -> " + resultFun);
return resultFun;
}
}
// evaluate expression by CAS
String result = symbolic
? kernel.evaluateGeoGebraCAS(casString, arbconst) : // symbolic
kernel.evaluateCachedGeoGebraCAS(casString, arbconst); // value
// string
// System.out.println("evaluateGeoGebraCAS: " + casString + " -> " +
// result);
// parse CAS result back into GeoGebra
sb.setLength(0);
sb.append("f("); // this name is never used, just needed for parsing
sb.append(getVarString(StringTemplate.defaultTemplate));
sb.append(") = ");
sb.append(result);
// parse result
if (getVarNumber() == 1) {
resultFun = (kernel.getParser().parseFunction(sb.toString()));
} else {
resultFun = (kernel.getParser()
.parseFunctionNVar(sb.toString()));
}
resultFun.initFunction();
} catch (Error err) {
err.printStackTrace();
resultFun = null;
} catch (Exception e) {
e.printStackTrace();
resultFun = null;
} catch (Throwable e) {
resultFun = null;
}
// cache result
if (useCaching && resultFun != null) {
getCasEvalMap().put(casString, resultFun);
}
// System.out.println("NO caching: " + casString + " -> " + resultFun);
return resultFun;
}
private ExpressionNode casEvalExpression;
private String casEvalStringSymbolic;
/**
*
* @return CAS map command -> result
*/
protected MaxSizeHashMap<String, FunctionNVar> getCasEvalMap() {
if (casEvalMap == null) {
casEvalMap = new MaxSizeHashMap<String, FunctionNVar>(
MAX_CAS_EVAL_MAP_SIZE);
}
return casEvalMap;
}
/**
*
* @param sb
* XML builder
*/
public void printCASevalMapXML(StringBuilder sb) {
if (casEvalMap != null) {
sb.append("<casMap>\n");
for (Entry<String, FunctionNVar> entry : casEvalMap.entrySet()) {
sb.append("\t<entry key=\"");
StringUtil.encodeXML(sb, entry.getKey());
sb.append("\" val=\"");
StringUtil.encodeXML(sb,
entry.getValue().toString(StringTemplate.xmlTemplate));
sb.append("\"/>\n");
}
sb.append("</casMap>\n");
}
}
private FunctionNVar lookupCasEvalMap(String casString) {
if (casEvalMap == null) {
return null;
}
return casEvalMap.get(casString);
}
/**
* Clears the cache (needed in Web when the CAS loads)
*
* @param label
* not used
*/
public void clearCasEvalMap(String label) {
if (casEvalMap == null) {
return;
}
casEvalMap.clear();
}
private final static int MAX_CAS_EVAL_MAP_SIZE = 100;
private MaxSizeHashMap<String, FunctionNVar> casEvalMap;
@Override
public boolean isNumberValue() {
return false;
}
@Override
final public boolean contains(ExpressionValue ev) {
return ev == this;
}
@Override
public String getLabelForAssignment() {
StringBuilder sb = new StringBuilder();
// function, e.g. f(x) := 2*x
sb.append(getLabel());
sb.append("(");
sb.append(getVarString(StringTemplate.defaultTemplate));
sb.append(")");
return sb.toString();
}
@Override
public IneqTree getIneqs() {
return ineqs;
}
/**
* initializes inequalities
*
* @param fe
* expression node
* @param functional
* function to which ineqs are associated
* @return true if the functions consists of inequalities
*/
public boolean initIneqs(ExpressionNode fe, FunctionalNVar functional) {
if (ineqs == null || fe == getExpression()) {
ineqs = new IneqTree();
}
boolean b = initIneqs(fe, functional, ineqs, false);
ineqs.recomputeSize();
return b;
}
private boolean initIneqs(ExpressionNode fe, FunctionalNVar functional,
IneqTree tree, boolean negate) {
Operation op = fe.getOperation();
ExpressionNode leftTree = fe.getLeftTree();
ExpressionNode rightTree = fe.getRightTree();
if (op.equals(Operation.GREATER) || op.equals(Operation.GREATER_EQUAL)
|| op.equals(Operation.LESS)
|| op.equals(Operation.LESS_EQUAL)) {
Inequality newIneq = new Inequality(kernel, leftTree, rightTree,
adjustOp(op, negate), getFunction().getFunctionVariables());
if (newIneq.getType() != IneqType.INEQUALITY_INVALID) {
if (newIneq.getType() != IneqType.INEQUALITY_1VAR_X
&& newIneq.getType() != IneqType.INEQUALITY_1VAR_Y) {
newIneq.getBorder().setInverseFill(newIneq.isAboveBorder());
}
tree.setIneq(newIneq);
}
return newIneq.getType() != IneqType.INEQUALITY_INVALID;
} else if (op.equals(Operation.AND) || op.equals(Operation.AND_INTERVAL)
|| op.equals(Operation.OR) || op.equals(Operation.EQUAL_BOOLEAN)
|| op.equals(Operation.NOT_EQUAL)) {
tree.setOperation(adjustOp(op, negate));
tree.setLeft(new IneqTree());
tree.setRight(new IneqTree());
return initIneqs(leftTree, functional, tree.getLeft(), negate)
&& initIneqs(rightTree, functional, tree.getRight(),
negate);
} else if (op.equals(Operation.NOT)) {
return initIneqs(leftTree, functional, tree, !negate);
} else if (op.equals(Operation.IMPLICATION)) {
tree.setOperation(Operation.OR);
tree.setLeft(new IneqTree());
tree.setRight(new IneqTree());
return initIneqs(leftTree, functional, tree.getLeft(), !negate)
&& initIneqs(rightTree, functional, tree.getRight(),
negate);
} else if (op.equals(Operation.FUNCTION_NVAR)) {
FunctionalNVar nv = (FunctionalNVar) leftTree.getLeft();
ExpressionNode subExpr = nv.getFunction().getExpression()
.getCopy(kernel);
FunctionVariable[] subVars = nv.getFunction()
.getFunctionVariables();
for (int i = 0; i < subVars.length; i++) {
subExpr.replace(subVars[i],
((MyList) rightTree.getLeft()).getListElement(i));
}
return initIneqs(subExpr, functional, tree, negate);
} else {
return false;
}
}
private static Operation adjustOp(Operation op, boolean negate) {
if (!negate) {
return op;
}
switch (op) {
case AND:
return Operation.OR;
case OR:
return Operation.AND;
case GREATER_EQUAL:
return Operation.LESS;
case GREATER:
return Operation.LESS_EQUAL;
case LESS_EQUAL:
return Operation.GREATER;
case LESS:
return Operation.GREATER_EQUAL;
case EQUAL_BOOLEAN:
return Operation.NOT_EQUAL;
case NOT_EQUAL:
return Operation.EQUAL_BOOLEAN;
default:
break;
}
return Operation.NO_OPERATION;
}
/**
* updates list of inequalities
*
* @return true iff all inequalities are drawable
*/
public boolean updateIneqs() {
if (ineqs == null) {
return false;
}
return ineqs.updateCoef();
}
/**
* Evaluates function at given point
*
* @param pt
* point for evaluation
* @return function value
*/
public double evaluate(VectorNDValue pt) {
if (fVars.length == 1 && "y"
.equals(fVars[0].toString(StringTemplate.defaultTemplate))) {
return evaluate(new double[] { pt.getPointAsDouble()[1] });
}
return evaluate(pt.getPointAsDouble());
}
/**
* Translates the function by (vx,vy)
*
* @param vx
* x-coord of translation vector
* @param vy
* y-coord of translation vector
*/
public void translate(double vx, double vy) {
// translate x
if (!Kernel.isZero(vx)) {
translateX(expression, vx, 0);
}
// translate y
if (!Kernel.isZero(vy)) {
translateX(expression, vy, 1);
}
// make sure that expression object is changed!
// this is needed to know that the expression has changed
if (expression.isLeaf() && expression.getLeft().isExpressionNode()) {
expression = new ExpressionNode(
(ExpressionNode) expression.getLeft());
} else {
expression = new ExpressionNode(expression);
}
}
/**
* translate the expression
*
* @param vx
* x-translation
* @param vy
* y-translation
* @param vz
* z-translation
*/
public void translate(double vx, double vy, double vz) {
int zIndex;
if (!Kernel.isZero(vz) && (zIndex = getVarIndex("z")) != -1) {
translateX(expression, vz, zIndex);
}
translate(vx, vy);
}
private int getVarIndex(String s) {
for (int i = 0; i < fVars.length; i++) {
if (s.equals(fVars[i].toString(StringTemplate.defaultTemplate))) {
return i;
}
}
return -1;
}
/**
* replace every x in tree by (x - vx) // i.e. replace fVar with (fvar - vx)
*
* @param en
* node
* @param vx
* shift
* @param varNo
* variable index
*/
protected void translateX(ExpressionNode en, double vx, int varNo) {
ExpressionValue left = en.getLeft();
ExpressionValue right = en.getRight();
// left tree
if (left == fVars[varNo]) {
if (right instanceof MyDouble && right.isConstant()) { // is there a
// constant
// number to
// the
// right?
MyDouble num = (MyDouble) right;
double temp;
switch (en.getOperation()) {
case PLUS:
temp = num.getDouble() - vx;
if (Kernel.isZero(temp)) {
expression = expression.replace(en, fVars[varNo])
.wrap();
} else if (temp < 0) {
en.setOperation(Operation.MINUS);
num.set(-temp);
} else {
num.set(temp);
}
return;
case MINUS:
temp = num.getDouble() + vx;
if (Kernel.isZero(temp)) {
expression = expression.replace(en, fVars[varNo])
.wrap();
} else if (temp < 0) {
en.setOperation(Operation.PLUS);
num.set(-temp);
} else {
num.set(temp);
}
return;
default:
en.setLeft(shiftXnode(vx, varNo));
}
} else {
en.setLeft(shiftXnode(vx, varNo));
}
} else {
translateExpressionX(left, vx, varNo);
}
// right tree
if (right == fVars[varNo]) {
en.setRight(shiftXnode(vx, varNo));
} else {
translateExpressionX(right, vx, varNo);
}
}
private void translateExpressionX(ExpressionValue right, double vx,
int varNo) {
if (right instanceof ExpressionNode) {
translateX((ExpressionNode) right, vx, varNo);
} else if (right instanceof MyList) {
for (int i = 0; i < ((MyList) right).size(); i++) {
translateX(((MyList) right).getListElement(i).wrap(), vx,
varNo);
}
} else if (right instanceof MyVecNode) {
translateX(((MyVecNode) right).getX().wrap(), vx, varNo);
translateX(((MyVecNode) right).getY().wrap(), vx, varNo);
} else if (right instanceof MyVec3DNode) {
translateX(((MyVec3DNode) right).getX().wrap(), vx, varNo);
translateX(((MyVec3DNode) right).getY().wrap(), vx, varNo);
translateX(((MyVec3DNode) right).getZ().wrap(), vx, varNo);
}
}
/**
* Dilates node in single direction
*
* @param en
* node to dilate
* @param vx
* coefficient
* @param varNo
* variable in which to dilate
*/
protected void dilateX(ExpressionNode en, double vx, int varNo) {
Log.debug(vx);
ExpressionValue left = en.getLeft();
ExpressionValue right = en.getRight();
// left tree
if (left == fVars[varNo]) {
if (right instanceof MyDouble && right.isConstant()) { // is there a
// constant
// number to
// the
// right?
MyDouble num = (MyDouble) right;
double temp;
switch (en.getOperation()) {
case MULTIPLY:
temp = num.getDouble() / vx;
if (Kernel.isEqual(1, temp)) {
expression = expression.replace(en, fVars[varNo])
.wrap();
} else {
num.set(temp);
}
return;
case DIVIDE:
temp = num.getDouble() * vx;
if (Kernel.isEqual(1, temp)) {
expression = expression.replace(en, fVars[varNo])
.wrap();
} else {
num.set(temp);
}
return;
default:
en.setLeft(multXnode(vx, varNo));
}
} else {
en.setLeft(multXnode(vx, varNo));
}
} else {
dilateExpressionX(left, vx, varNo);
}
// right tree
if (right == fVars[varNo]) {
if (left instanceof MyDouble && left.isConstant()) { // is there a
// constant
// number to
// the
// right?
MyDouble num = (MyDouble) left;
double temp;
switch (en.getOperation()) {
case MULTIPLY:
temp = num.getDouble() / vx;
if (Kernel.isEqual(1, temp)) {
expression = expression.replace(en, fVars[varNo])
.wrap();
} else {
num.set(temp);
}
return;
default:
en.setRight(multXnode(vx, varNo));
}
} else {
en.setRight(multXnode(vx, varNo));
}
} else {
dilateExpressionX(right, vx, varNo);
}
}
private void dilateExpressionX(ExpressionValue right, double vx,
int varNo) {
if (right instanceof ExpressionNode) {
dilateX((ExpressionNode) right, vx, varNo);
} else if (right instanceof MyList) {
for (int i = 0; i < ((MyList) right).size(); i++) {
dilateX(((MyList) right).getListElement(i).wrap(), vx, varNo);
}
} else if (right instanceof MyVecNode) {
dilateX(((MyVecNode) right).getX().wrap(), vx, varNo);
dilateX(((MyVecNode) right).getY().wrap(), vx, varNo);
} else if (right instanceof MyVec3DNode) {
dilateX(((MyVec3DNode) right).getX().wrap(), vx, varNo);
dilateX(((MyVec3DNode) right).getY().wrap(), vx, varNo);
dilateX(((MyVec3DNode) right).getZ().wrap(), vx, varNo);
}
}
// node for (x - vx)
/**
* @param vx0
* shift
* @param varNo
* variable index
* @return variable with given index shifted by given amount
*/
protected ExpressionNode shiftXnode(double vx0, int varNo) {
ExpressionNode node;
double vx = Kernel.checkDecimalFraction(vx0);
if (vx > 0) {
node = new ExpressionNode(kernel, fVars[varNo], Operation.MINUS,
new MyDouble(kernel, vx));
} else {
node = new ExpressionNode(kernel, fVars[varNo], Operation.PLUS,
new MyDouble(kernel, -vx));
}
return node;
}
/**
* @param vx0
* coefficient
* @param varNo
* variable index
* @return node coefficient * variable
*/
protected ExpressionNode multXnode(double vx0, int varNo) {
double vx = Kernel.checkDecimalFraction(1 / vx0);
return new ExpressionNode(kernel, new MyDouble(kernel, vx),
Operation.MULTIPLY, fVars[varNo]);
}
/**
* @param d
* dilate factor
* @param s
* coordinates
*/
public void dilate3D(NumberValue d, Coords s) {
double r = 1.0 / d.getDouble();
int zIndex = this.getVarIndex("z");
if (zIndex != -1) {
this.translate(-s.getX(), -s.getY(), -s.getZ());
ExpressionNode newX = new ExpressionNode(kernel, d,
Operation.MULTIPLY, fVars[0]);
ExpressionNode newY = new ExpressionNode(kernel, d,
Operation.MULTIPLY, fVars[1]);
ExpressionNode newZ = new ExpressionNode(kernel, d,
Operation.MULTIPLY, fVars[zIndex]);
expression = expression.replace(fVars[0], newX).wrap();
expression = expression.replace(fVars[1], newY).wrap();
expression = expression.replace(fVars[zIndex], newZ).wrap();
this.initIneqs(expression, this);
this.translate(s.getX(), s.getY(), s.getZ());
} else {
this.translate(-s.getX(), -s.getY());
this.matrixTransform(r, 0, 0, r);
this.translate(s.getX(), s.getY());
double dv = d.getDouble();
expression = expression.multiply(dv).plus(s.getZ() * (1 - dv));
}
}
/**
* Transforms this function using matrix {{a00,a01},{a01,a11}}
*
* @param a00
* a00
* @param a01
* a01
* @param a10
* a10
* @param a11
* a11
*/
public void matrixTransform(double a00, double a01, double a10,
double a11) {
ExpressionNode dummy = new ExpressionNode();
expression.replace(fVars[0], dummy).wrap();
MyDouble ma00 = new MyDouble(kernel, a00);
MyDouble ma01 = new MyDouble(kernel, a01);
MyDouble ma10 = new MyDouble(kernel, a10);
MyDouble ma11 = new MyDouble(kernel, a11);
ExpressionNode newX = fVars[0].wrap().multiply(ma00)
.plus(fVars[1].wrap().multiply(ma01));
ExpressionNode newY = fVars[0].wrap().multiply(ma10)
.plus(fVars[1].wrap().multiply(ma11));
expression = expression
.traverse(CopyReplacer.getReplacer(fVars[1], newY, kernel))
.wrap();
expression = expression
.traverse(CopyReplacer.getReplacer(dummy, newX, kernel)).wrap();
this.initIneqs(expression, this);
}
/**
*
* @param a00
* a00
* @param a01
* a11
* @param a02
* a02
* @param a10
* a10
* @param a11
* a11
* @param a12
* a12
* @param a20
* a20
* @param a21
* a21
* @param a22
* a22
*/
public void matrixTransform(double a00, double a01, double a02, double a10,
double a11, double a12, double a20, double a21, double a22) {
ExpressionNode dummy = new ExpressionNode();
expression = expression.replace(fVars[0], dummy).wrap();
double[][] b = MyMath.adjoint(a00, a01, a02, a10, a11, a12, a20, a21,
a22);
MyDouble[][] mbTrans = new MyDouble[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
mbTrans[i][j] = new MyDouble(kernel, b[j][i]);
}
}
ExpressionNode newZ = new ExpressionNode(kernel, mbTrans[2][0],
Operation.MULTIPLY, fVars[0])
.plus(new ExpressionNode(kernel, mbTrans[2][1],
Operation.MULTIPLY, fVars[1]))
.plus(mbTrans[2][2]);
ExpressionNode newX = new ExpressionNode(kernel, mbTrans[0][0],
Operation.MULTIPLY, fVars[0])
.plus(new ExpressionNode(kernel, mbTrans[0][1],
Operation.MULTIPLY, fVars[1]))
.plus(mbTrans[0][2]);
ExpressionNode newY = new ExpressionNode(kernel, mbTrans[1][0],
Operation.MULTIPLY, fVars[0])
.plus(new ExpressionNode(kernel, mbTrans[1][1],
Operation.MULTIPLY, fVars[1]))
.plus(mbTrans[1][2]);
expression = expression.traverse(
CopyReplacer.getReplacer(fVars[1], newY.divide(newZ), kernel))
.wrap();
expression = expression.traverse(
CopyReplacer.getReplacer(dummy, newX.divide(newZ), kernel))
.wrap();
this.initIneqs(expression, this);
}
@Override
public ExpressionValue traverse(Traversing t) {
ExpressionValue ev = t.process(this);
if (ev != this) {
return ev;
}
expression = expression.traverse(t).wrap();
return this;
}
@Override
public boolean inspect(Inspecting t) {
return t.check(this) || expression.inspect(t);
}
@Override
public ExpressionNode getFunctionExpression() {
return expression;
}
@Override
public boolean isDefined() {
return true;
}
@Override
public void setDefined(boolean b) {
// nothing to do
}
/**
* @param fv
* variable with respect to which the derivative is computed
* @param n
* order of derivative
* @return derivative calculated without the CAS
*/
public FunctionNVar getDerivativeNoCAS(FunctionVariable fv, int n) {
ExpressionNode expDeriv = expression;
for (int i = 0; i < n; i++) {
expDeriv = expDeriv.derivative(fv, kernel);
}
expDeriv.simplifyConstantIntegers();
return new FunctionNVar(expDeriv, fVars);
}
/**
* @param fv
* variable with respect to which the derivative is computed
* @return integral calculated without the CAS (will work only for very
* simple functions eg sin(3x))
*/
public FunctionNVar getIntegralNoCAS(FunctionVariable fv) {
return new FunctionNVar(expression.integral(fv, kernel), fVars);
}
/**
* Make sure the variable list is at least as long as oldvars
*
* @param oldVars
* list of variables to be used for filling in the empty slots
*/
public void fillVariables(FunctionVariable[] oldVars) {
if (oldVars == null) {
return;
}
int length = oldVars.length;
if (fVars.length >= length) {
return;
}
FunctionVariable[] newVars = new FunctionVariable[length];
TreeSet<String> usedNames = new TreeSet<String>();
for (int i = 0; i < fVars.length; i++) {
newVars[i] = fVars[i];
usedNames.add(fVars[i].toString(StringTemplate.defaultTemplate));
}
int pos = fVars.length;
for (int i = 0; i < oldVars.length && pos < length; i++) {
if (!usedNames.contains(
oldVars[i].toString(StringTemplate.defaultTemplate))) {
newVars[pos] = oldVars[i];
pos++;
}
}
fVars = newVars;
}
@Override
public ExpressionNode wrap() {
return new ExpressionNode(kernel, this);
}
@Override
public ExpressionValue derivative(FunctionVariable fv, Kernel kernel0) {
return expression.derivative(fv, kernel0);
}
@Override
public final ExpressionValue integral(FunctionVariable fv, Kernel kernel0) {
return expression.integral(fv, kernel0);
}
@Override
public final boolean hasCoords() {
return expression.hasCoords();
}
@Override
public ValueType getValueType() {
return ValueType.FUNCTION;
}
/**
* @param map
* map from XML in the string -> string form
*/
public void updateCASEvalMap(TreeMap<String, String> map) {
if (map == null) {
return;
}
for (Entry<String, String> entry : map.entrySet()) {
FunctionalNVar gfun = kernel.getAlgebraProcessor()
.evaluateToFunctionNVar(entry.getValue(), true);
if (gfun != null) {
getCasEvalMap().put(entry.getKey(), gfun.getFunction());
}
}
}
/**
* @param r
* dilation factor
* @param S
* dilation center
*/
public void dilate(NumberValue r, Coords S) {
translate(-S.getX(), -S.getY());
matrixTransform(1 / r.getDouble(), 0, 0, 1 / r.getDouble());
translate(S.getX(), S.getY());
}
/**
* @param phi
* angle (in rad)
*/
public void rotate(NumberValue phi) {
double cosPhi = Math.cos(phi.getDouble());
double sinPhi = Math.sin(phi.getDouble());
matrixTransform(cosPhi, sinPhi, -sinPhi, cosPhi);
}
/**
* @param v
* translation coordinates
*/
public void translate(Coords v) {
translate(v.getX(), v.getY());
}
private void mirror(double phi) {
double cosPhi = Math.cos(phi);
double sinPhi = Math.sin(phi);
matrixTransform(cosPhi, sinPhi, sinPhi, -cosPhi);
}
/**
* @param g
* line
*/
public void mirror(GeoLine g) {
double qx, qy;
if (Math.abs(g.getX()) > Math.abs(g.getY())) {
qx = g.getZ() / g.getX();
qy = 0.0d;
} else {
qx = 0.0d;
qy = g.getZ() / g.getY();
}
// translate -Q
translate(qx, qy);
// S(phi)
mirror(2.0 * Math.atan2(-g.getX(), g.getY()));
// translate back +Q
translate(-qx, -qy);
}
/**
* @param phi
* angle
* @param p
* center
*/
public void rotate(NumberValue phi, Coords p) {
translate(-p.getX(), -p.getY());
rotate(phi);
translate(p.getX(), p.getY());
}
}