/* 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. */ /* * ExpressionNode.java * * binary tree node for ExpressionValues (NumberValues, VectorValues) * * Created on 03. Oktober 2001, 09:37 */ package org.geogebra.common.kernel.arithmetic; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import org.geogebra.common.cas.giac.CASgiac.CustomFunctions; import org.geogebra.common.export.MathmlTemplate; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.arithmetic.Traversing.Replacer; import org.geogebra.common.kernel.arithmetic3D.MyVec3DNode; import org.geogebra.common.kernel.arithmetic3D.Vector3DValue; import org.geogebra.common.kernel.commands.EvalInfo; import org.geogebra.common.kernel.geos.CasEvaluableFunction; import org.geogebra.common.kernel.geos.GeoCasCell; import org.geogebra.common.kernel.geos.GeoDummyVariable; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoFunctionNVar; import org.geogebra.common.kernel.geos.GeoLine; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPolygon; import org.geogebra.common.kernel.geos.GeoSegment; import org.geogebra.common.kernel.geos.GeoVec2D; import org.geogebra.common.kernel.kernelND.GeoSurfaceCartesianND; import org.geogebra.common.kernel.parser.FunctionParser; import org.geogebra.common.main.App; import org.geogebra.common.main.Localization; import org.geogebra.common.main.MyError; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Tree node for expressions like "3*a - b/5" * * @author Markus */ public class ExpressionNode extends ValidExpression implements ExpressionNodeConstants, ReplaceChildrenByValues { private static final Inspecting TRICKY_DIVISION_CHECKER = new Inspecting() { @Override public boolean check(ExpressionValue v) { return v.isExpressionNode() && ((ExpressionNode) v).getOperation() == Operation.DIVIDE && Kernel.isZero(v.evaluateDouble()) && ((ExpressionNode) v).getLeft().evaluateDouble() != 0; } }; private Localization loc; private Kernel kernel; private ExpressionValue left, right; private Operation operation = Operation.NO_OPERATION; private boolean forceVector = false, forcePoint = false, forceFunction = false; /** true if this holds text and the text is in LaTeX format */ public boolean holdsLaTeXtext = false; /** for leaf mode */ public boolean leaf = false; private boolean brackets; private ExpressionValue resolve; // used by NDerivative command // (answer not displayed in Algebra View) private AlgoElement isSecret; /** * Creates dummy expression node */ public ExpressionNode() { } /** * Creates new ExpressionNode * * @param kernel * kernel * @param left * left subexpression * @param operation * operation * @param right * right subexpression */ public ExpressionNode(Kernel kernel, ExpressionValue left, Operation operation, ExpressionValue right) { this.kernel = kernel; loc = kernel.getLocalization(); this.operation = operation; setLeft(left); if (right != null) { setRight(right); } else { // set dummy value setRight(new MyDouble(kernel, Double.NaN)); } } /** * for only one leaf (for wrapping ExpressionValues as ValidExpression) * * @param kernel * Kernel * @param leaf * value to be wrapped */ public ExpressionNode(Kernel kernel, ExpressionValue leaf) { this.kernel = kernel; loc = kernel.getLocalization(); setLeft(leaf); this.leaf = true; } /** * copy constructor: NO deep copy of subtrees is done here! this is needed * for translation of functions * * @param node * Node to copy */ public ExpressionNode(ExpressionNode node) { kernel = node.kernel; loc = node.loc; leaf = node.leaf; operation = node.operation; isSecret = node.isSecret; setLeft(node.left); setRight(node.right); } /** * * wraps a double in an ExpressionNode * * @param kernel2 * kernel * @param d * double */ public ExpressionNode(Kernel kernel2, double d) { this(kernel2, new MyDouble(kernel2, d)); } /** * @return kernel */ public Kernel getKernel() { return kernel; } /** * @return current operation */ final public Operation getOperation() { return operation; } /** * @param op * new operation */ public void setOperation(Operation op) { operation = op; } /** * @param flag * true if holds LaTeX text */ public void setHoldsLaTeXtext(boolean flag) { holdsLaTeXtext = flag; } /** * @return left subexpression */ final public ExpressionValue getLeft() { return left; } /** * @param l * left subexpression */ final public void setLeft(ExpressionValue l) { left = l; left.setInTree(true); // needed fot list operations eg k=2 then k {1,2} } /** * Result is never null; for leaves, left is packed in ExpressionNode in * result. * * @return left subtree */ public ExpressionNode getLeftTree() { if (left.isExpressionNode()) { return (ExpressionNode) left; } return new ExpressionNode(kernel, left); } /** * @return right subexpression */ final public ExpressionValue getRight() { return right; } /** * @param r * new right subexpression */ final public void setRight(ExpressionValue r) { right = r; if (right != null) { right.setInTree(true); // needed for list operations eg k=2 then k // {1,2} } leaf = operation == Operation.NO_OPERATION; // right is a dummy MyDouble // by // default } /** * @return right subtree */ public ExpressionNode getRightTree() { if (right == null) { return null; } if (right.isExpressionNode()) { return (ExpressionNode) right; } return new ExpressionNode(kernel, right); } @Override public ExpressionNode deepCopy(Kernel kernel1) { return getCopy(kernel1); } /** * copy the whole tree structure except leafs * * @param kernel1 * kernel * @return copy of this node */ public ExpressionNode getCopy(Kernel kernel1) { // Application.debug("getCopy() input: " + this); ExpressionNode newNode = null; ExpressionValue lev = null, rev = null; if (left != null) { lev = copy(left, kernel1); } if (right != null) { rev = copy(right, kernel1); } if (lev != null) { newNode = new ExpressionNode(kernel1, lev, operation, rev); newNode.leaf = leaf; } else { // something went wrong return null; } // set member vars that are not set by constructors newNode.forceVector = forceVector; newNode.forcePoint = forcePoint; newNode.forceFunction = forceFunction; newNode.brackets = brackets; newNode.isSecret = isSecret; // Application.debug("getCopy() output: " + newNode); return newNode; } /** * deep copy except for GeoElements * * @param ev * value to copy * @param kernel * kernel * @return copy of value */ public static ExpressionValue copy(ExpressionValue ev, Kernel kernel) { if (ev == null) { return null; } ExpressionValue ret = null; // Application.debug("copy ExpressionValue input: " + ev); if (ev.isExpressionNode()) { ExpressionNode en = (ExpressionNode) ev; ret = en.getCopy(kernel); } else if (ev instanceof MyList) { MyList en = (MyList) ev; ret = en.getCopy(kernel); } // deep copy // important to check for commands before we call isConstant as that // might // result in evaluation, FunctionNVar important because of // FunctionExpander else if ((ev.inspect(Inspecting.CommandFinder.INSTANCE)) || ev.isConstant() || ev instanceof FunctionNVar || ev instanceof Equation || ev instanceof MyVecNode || ev instanceof MyVec3DNode) { ret = ev.deepCopy(kernel); } else { ret = ev; } // Application.debug("copy ExpressionValue output: " + ev); return ret; } /** * Replaces all ExpressionNodes in tree that are leafs (=wrappers) by their * leaf objects (of type ExpressionValue). */ final public void simplifyLeafs() { if (left.isExpressionNode()) { ExpressionNode node = (ExpressionNode) left; if (node.leaf) { left = node.left; simplifyLeafs(); } } if (right != null) { if (right.isExpressionNode()) { ExpressionNode node = (ExpressionNode) right; if (node.leaf) { right = node.left; simplifyLeafs(); } } } } /** * Replaces all Command objects in tree by their evaluated GeoElement * objects. */ final private void simplifyAndEvalCommands(EvalInfo info) { // don't evaluate any commands for the CAS here if (kernel.isResolveUnkownVarsAsDummyGeos()) { return; } if (left.isExpressionNode()) { ((ExpressionNode) left).simplifyAndEvalCommands(info); } else if (left instanceof Command) { left = ((Command) left).simplify(info); } if (right != null) { if (right.isExpressionNode()) { ((ExpressionNode) right).simplifyAndEvalCommands(info); } else if (right instanceof Command) { right = ((Command) right).simplify(info); } } } /** * Replaces all constant parts in tree by their values */ final public void simplifyConstantIntegers() { if (left.isExpressionNode()) { left = doSimplifyConstantIntegers(left); } if ((right != null) && right.isExpressionNode()) { right = doSimplifyConstantIntegers(right); } } private static ExpressionValue doSimplifyConstantIntegers( ExpressionValue left2) { ExpressionNode node = (ExpressionNode) left2; if (left2.isConstant()) { ExpressionValue eval = node .evaluate(StringTemplate.defaultTemplate); if (eval instanceof NumberValue) { // we only simplify numbers that have integer values if (Kernel.isInteger(((NumberValue) eval).getDouble())) { if (node.inspect(TRICKY_DIVISION_CHECKER)) { node.simplifyConstantIntegers(); return left2; } return eval; } } else { return eval; } } else { node.simplifyConstantIntegers(); } return left2; } /** * Evaluates this expression * * @param tpl * template (needed for possible string concatenation) * @return value */ @Override public ExpressionValue evaluate(StringTemplate tpl) { if (resolve instanceof ExpressionNode) { resolve = null; } return kernel.getExpressionNodeEvaluator().evaluate(this, tpl); } /* * public ExpressionValue evaluate(boolean cache) { return * kernel.getExpressionNodeEvaluator().evaluate(this); } */ /** * look for Variable objects in the tree and replace them by their resolved * GeoElement */ @Override public final void resolveVariables(EvalInfo info) { doResolveVariables(info); simplifyAndEvalCommands(info); simplifyLeafs(); // left instanceof NumberValue needed rather than left.isNumberValue() // as left can be an // ExpressionNode, eg Normal[0,1,x] switch (operation) { case POWER: // eg e^x if ((left instanceof NumberValue) && MyDouble .exactEqual(((NumberValue) left).getDouble(), Math.E)) { GeoElement geo = kernel.lookupLabel("e"); if ((geo != null) && geo.needsReplacingInExpressionNode()) { // replace e^x with exp(x) // if e was autocreated operation = Operation.EXP; left = right; kernel.getConstruction().removeLabel(geo); } } break; case MULTIPLY: // eg 1 * e or e * 1 case DIVIDE: // eg 1 / e or e / 1 case PLUS: // eg 1 + e or e + 1 case MINUS: // eg 1 - e or e - 1 if ((left instanceof NumberValue) && MyDouble .exactEqual(((NumberValue) left).getDouble(), Math.E)) { GeoElement geo = kernel.lookupLabel("e"); if ((geo != null) && geo.needsReplacingInExpressionNode()) { // replace 'e' with exp(1) // if e was autocreated left = new ExpressionNode(kernel, new MyDouble(kernel, 1.0), Operation.EXP, null); kernel.getConstruction().removeLabel(geo); } } else if ((right instanceof NumberValue) && MyDouble .exactEqual(((NumberValue) right).getDouble(), Math.E)) { GeoElement geo = kernel.lookupLabel("e"); if ((geo != null) && geo.needsReplacingInExpressionNode()) { // replace 'e' with exp(1) // if e was autocreated right = new ExpressionNode(kernel, new MyDouble(kernel, 1.0), Operation.EXP, null); kernel.getConstruction().removeLabel(geo); } } break; default: break; } } private void doResolveVariables(EvalInfo info) { // resolve left wing if (left.isVariable()) { left = ((Variable) left).resolveAsExpressionValue(); if (operation == Operation.POWER || operation == Operation.FACTORIAL) { fixPowerFactorial(Operation.MULTIPLY); } if (operation == Operation.SQRT_SHORT) { fixSqrtShort(Operation.MULTIPLY); } } else { left.resolveVariables(info); } // resolve right wing if (right != null) { if (right.isVariable()) { right = ((Variable) right).resolveAsExpressionValue(); } else { right.resolveVariables(info); } } } /** * look for GeoFunction objects in the tree and replace them by FUNCTION * ExpressionNodes. This makes operations like f + g possible by changing * this to f(x) + g(x) * * public void wrapGeoFunctionsAsExpressionNode() { Polynomial polyX = new * Polynomial(kernel, "x"); * * // left wing if (left.isExpressionNode()) { * ((ExpressionNode)left).wrapGeoFunctionsAsExpressionNode(); } else if * (left instanceof GeoFunction) { left = new ExpressionNode(kernel, left, * ExpressionNode.FUNCTION, polyX); } * * // resolve right wing if (right != null) { if (right.isExpressionNode()) * { ((ExpressionNode)right).wrapGeoFunctionsAsExpressionNode(); } else if * (right instanceof GeoFunction) { right = new ExpressionNode(kernel, * right, ExpressionNode.FUNCTION, polyX); } } } */ /** * Returns whether this ExpressionNode should evaluate to a GeoVector. This * method returns true when all GeoElements in this tree are GeoVectors and * there are no other constanct VectorValues (i.e. constant points) * * @return true if this should evaluate to GeoVector */ public boolean shouldEvaluateToGeoVector() { if (forcePoint) { return false; } if (forceVector) { return true; } boolean evalToVector = false; if (left.isExpressionNode()) { evalToVector = (((ExpressionNode) left) .shouldEvaluateToGeoVector()); } else if (left.isGeoElement()) { GeoElement geo = (GeoElement) left; evalToVector = geo.isGeoVector() || geo.isNumberValue(); } else if (left.isNumberValue()) { evalToVector = true; } else if (left instanceof MyVecNDNode) { evalToVector = ((MyVecNDNode) left).isCASVector(); } if ((right != null) && evalToVector) { if (right.isExpressionNode()) { evalToVector = ((ExpressionNode) right) .shouldEvaluateToGeoVector(); } else if (right.isGeoElement()) { GeoElement geo = (GeoElement) right; evalToVector = geo.isGeoVector() || geo.isNumberValue(); } else if (right.isNumberValue()) { evalToVector = true; } } return evalToVector; } /** * Returns true if this tree includes a division by val * * @param val * possible divisor * @return true iff contains division by val */ final public boolean includesDivisionBy(ExpressionValue val) { if (operation == Operation.POWER) { // x^-4 if (left.contains(val) && right.evaluateDouble() < 0) { return true; } } if (operation == Operation.DIVIDE) { if (right.contains(val)) { return true; } if (left.isExpressionNode() && ((ExpressionNode) left).includesDivisionBy(val)) { return true; } } else { if (left.isExpressionNode() && ((ExpressionNode) left).includesDivisionBy(val)) { return true; } if ((right != null) && right.isExpressionNode() && ((ExpressionNode) right).includesDivisionBy(val)) { return true; } } return false; } /** * Returns true if this tree includes eg abs(), If[] function * * * @return true iff contains abs(), If[] etc */ final public boolean includesNonContinuousIntegral() { if (Operation.integralIsNonContinuous(operation)) { return true; } if (left.isExpressionNode() && ((ExpressionNode) left).includesNonContinuousIntegral()) { return true; } if ((right != null) && right.isExpressionNode() && ((ExpressionNode) right).includesNonContinuousIntegral()) { return true; } return false; } /** * Returns true if this tree includes Freehand or DataFunction * * * @return true iff contains abs(), If[] etc */ final public boolean includesFreehandOrData() { if (Operation.includesFreehandOrData(operation)) { return true; } if (left.isExpressionNode() && ((ExpressionNode) left).includesFreehandOrData()) { return true; } if ((right != null) && right.isExpressionNode() && ((ExpressionNode) right).includesFreehandOrData()) { return true; } return false; } /** * Replaces all Variable objects with the given varName in tree by the given * FunctionVariable object. * * Only works if the varName is inserted without CAS prefix * * @param varName * variable name * @param fVar * replacement variable * * @return number of replacements done */ public final int replaceVariables(String varName, FunctionVariable fVar) { int replacements = 0; // left tree if (left.isExpressionNode()) { replacements += ((ExpressionNode) left).replaceVariables(varName, fVar); } else if (left instanceof MyList) { replacements += ((MyList) left).replaceVariables(varName, fVar); } else if (left instanceof Variable) { if (varName.equals(((Variable) left) .getName(StringTemplate.defaultTemplate))) { left = fVar; replacements++; } } else if (left instanceof Command) { replacements += ((Command) left).replaceVariables(varName, fVar); } if (left instanceof GeoDummyVariable) { if (varName.equals(((GeoDummyVariable) left) .toString(StringTemplate.defaultTemplate))) { left = fVar; replacements++; } } else if (left instanceof FunctionVariable) { if (varName.equals(((FunctionVariable) left) .toString(StringTemplate.defaultTemplate))) { left = fVar; replacements++; } } // right tree if (right != null) { if (right.isExpressionNode()) { replacements += ((ExpressionNode) right) .replaceVariables(varName, fVar); } else if (right instanceof MyList) { replacements += ((MyList) right).replaceVariables(varName, fVar); } else if (right instanceof Variable) { if (varName.equals(((Variable) right) .getName(StringTemplate.defaultTemplate))) { right = fVar; replacements++; } } else if (right instanceof GeoDummyVariable) { if (varName.equals(((GeoDummyVariable) right) .toString(StringTemplate.defaultTemplate))) { right = fVar; replacements++; } } else if (right instanceof FunctionVariable) { if (varName.equals(((FunctionVariable) right) .toString(StringTemplate.defaultTemplate))) { right = fVar; replacements++; } } } return replacements; } /** * Replaces all XCOORD, YCOORD, ZCOORD nodes by mutliplication nodes, e.g. * x(x+1) becomes x*(x+1). The given function variables for "x", "y", "z" * are used in this process. * * @param xVar * variable x * @param yVar * variable y * @param zVar * variable z * @param undecided * list for subexpressions where it's not clear whether they can * be used for multiplication directly or not * * @return number of replacements done */ protected int replaceXYZnodes(FunctionVariable xVar, FunctionVariable yVar, FunctionVariable zVar, ArrayList<ExpressionNode> undecided) { if ((xVar == null) && ((yVar == null) & (zVar == null))) { return 0; } // left tree if (left.isExpressionNode()) { ((ExpressionNode) left).replaceXYZnodes(xVar, yVar, zVar, undecided); } // right tree if ((right != null) && right.isExpressionNode()) { ((ExpressionNode) right).replaceXYZnodes(xVar, yVar, zVar, undecided); } switch (operation) { case XCOORD: if (xVar != null && !leftHasCoord()) { undecided.add(this); operation = Operation.MULTIPLY_OR_FUNCTION; right = left; left = xVar; } break; case YCOORD: if (yVar != null && !leftHasCoord()) { undecided.add(this); operation = Operation.MULTIPLY_OR_FUNCTION; right = left; left = yVar; } break; case ZCOORD: if (zVar != null && !leftHasCoord()) { undecided.add(this); operation = Operation.MULTIPLY_OR_FUNCTION; right = left; left = zVar; } break; case POWER: case FACTORIAL: fixPowerFactorial(Operation.MULTIPLY_OR_FUNCTION); break; case SQRT_SHORT: fixSqrtShort(Operation.MULTIPLY_OR_FUNCTION); break; default: break; } return undecided.size(); } private boolean leftHasCoord() { return left.evaluatesToNDVector() || left.getValueType() == ValueType.COMPLEX || (left.unwrap() instanceof GeoLine); } private void fixSqrtShort(Operation multiplicativeOperation) { if (left.isExpressionNode() && ((ExpressionNode) left).operation == multiplicativeOperation && !((ExpressionNode) left).hasBrackets()) { right = ((ExpressionNode) left).getRight(); left = new ExpressionNode(kernel, ((ExpressionNode) left).getLeft(), Operation.SQRT, null); operation = Operation.MULTIPLY; } } private void fixPowerFactorial(Operation multiplicativeOperation) { if (left.isExpressionNode() && ((ExpressionNode) left).operation == multiplicativeOperation && !((ExpressionNode) left).hasBrackets()) { right = new ExpressionNode(kernel, ((ExpressionNode) left).getRight(), operation, right); left = ((ExpressionNode) left).getLeft(); operation = Operation.MULTIPLY; } } @Override public ExpressionValue traverse(Traversing t) { ExpressionValue ev = t.process(this); if (ev != this) { return ev; } if (left != null) { left = left.traverse(t); } if (right != null) { right = right.traverse(t); } // if we did some replacement in a leaf, // we might need to update the leaf flag (#3512) return ev.unwrap().wrap(); } @Override public boolean inspect(Inspecting t) { return t.check(this) || left.inspect(t) || (right != null && right.inspect(t)); } @Override public void replaceChildrenByValues(GeoElement geo) { // left tree if (left.isGeoElement()) { GeoElement treeGeo = (GeoElement) left; if ((left == geo) || treeGeo.isChildOf(geo)) { left = treeGeo.copyInternal(treeGeo.getConstruction()); } } else if (left instanceof ReplaceChildrenByValues) { ((ReplaceChildrenByValues) left).replaceChildrenByValues(geo); } // right tree if (right != null) { if (right.isGeoElement()) { GeoElement treeGeo = (GeoElement) right; if ((right == geo) || treeGeo.isChildOf(geo)) { right = treeGeo.copyInternal(treeGeo.getConstruction()); } } else if (right instanceof ReplaceChildrenByValues) { ((ReplaceChildrenByValues) right).replaceChildrenByValues(geo); } } } /** * Returns true when the given object is found in this expression tree. */ @Override final public boolean contains(ExpressionValue ev) { if (leaf) { return left.contains(ev); } return left.contains(ev) || right.contains(ev); } /** * @return true if contains CAS evaluable function */ final public boolean containsCasEvaluableFunction() { if ((left instanceof CasEvaluableFunction) || (right instanceof CasEvaluableFunction)) { return true; } if ((left instanceof ExpressionNode) && ((ExpressionNode) left).containsCasEvaluableFunction()) { return true; } if ((right instanceof ExpressionNode) && ((ExpressionNode) right).containsCasEvaluableFunction()) { return true; } return false; } /** * @return true when the FunctionNVar is found in this expression tree. */ final public boolean containsGeoFunctionNVar() { if ((left instanceof GeoFunctionNVar) || (right instanceof GeoFunctionNVar)) { return true; } if ((left instanceof ExpressionNode) && ((ExpressionNode) left).containsGeoFunctionNVar()) { return true; } if ((right instanceof ExpressionNode) && ((ExpressionNode) right).containsGeoFunctionNVar()) { return true; } return false; } /** * transfers every non-polynomial in this tree to a polynomial. This is * needed to enable polynomial simplification by evaluate() * * @param equ * equation * @return expanded polynomial */ protected final Polynomial makePolynomialTree(Equation equ) { Polynomial lt; Polynomial rt = null; if (operation == Operation.FUNCTION_NVAR) { if ((left instanceof FunctionalNVar) && (right instanceof MyList)) { return makePolynomialTreeFromFunctionNVar( ((FunctionalNVar) left).getFunction(), equ); } } else if (operation == Operation.FUNCTION) { if (left instanceof GeoFunction) { Function func = ((Functional) left).getFunction(); return makePolyTreeFromFunction(func, equ); } else if (left instanceof ExpressionNode && ((ExpressionNode) left) .getOperation() == Operation.DERIVATIVE) { Function base = ((Functional) ((ExpressionNode) left).getLeft()) .getFunction(); int deg = (int) Math.ceil( ((ExpressionNode) left).getRight().evaluateDouble()); for (int i = 0; i < deg; i++) { base = new Function( ((Functional) ((ExpressionNode) left).getLeft()) .getFunction() .derivative(base.getFunctionVariable(), kernel) .wrap(), base.getFunctionVariable()); } return makePolyTreeFromFunction(base, equ); } } if (!polynomialOperation(operation)) { if (left instanceof ValidExpression && ((ValidExpression) left).containsFunctionVariable()) { equ.setIsPolynomial(false); } if (right instanceof ValidExpression && ((ValidExpression) right).containsFunctionVariable()) { equ.setIsPolynomial(false); } return new Polynomial(kernel, new Term( new ExpressionNode(kernel, left, operation, right), "")); } ExpressionNode scalarExpanded = VectorArithmetic .expandScalarProduct(kernel, left, right, operation); if (scalarExpanded != null) { return scalarExpanded.makePolynomialTree(equ); } // transfer left subtree if (left.isExpressionNode()) { lt = ((ExpressionNode) left).makePolynomialTree(equ); } else if (left instanceof FunctionVariable) { lt = new Polynomial(kernel, ((FunctionVariable) left).getSetVarString()); } else { lt = new Polynomial(kernel, new Term(left, "")); } // transfer right subtree if (right != null) { if (right.isExpressionNode()) { rt = ((ExpressionNode) right).makePolynomialTree(equ); } else if (right instanceof FunctionVariable) { rt = new Polynomial(kernel, ((FunctionVariable) right).getSetVarString()); } else { if (right instanceof MyList) { MyList list = (MyList) right; for (int i = 0; i < list.size(); i++) { ExpressionValue ev = list.getListElement(i); if (ev instanceof ExpressionNode) { ((ExpressionNode) ev).makePolynomialTree(equ); } } } // both for f(x,x) and x+3 we don't need the second argument // wrapped return lt.apply(operation, right, equ); } } return lt.apply(operation, rt, equ); } private Polynomial makePolynomialTreeFromFunctionNVar(FunctionNVar func, Equation equ) { MyList list = ((MyList) right); ExpressionNode expr = func.getExpression().getCopy(kernel); if (func.getFunctionVariables().length == list.size()) { for (int i = 0; i < list.size(); i++) { ExpressionValue ev = list.getListElement(i); if (ev instanceof ExpressionNode) { ExpressionNode en = ((ExpressionNode) ev).getCopy(kernel); if (!equ.isFunctionDependent()) { equ.setFunctionDependent(en.containsFunctionVariable()); } // we may only make polynomial trees after // replacement // en.makePolynomialTree(equ); ev = en; } else if (list.getListElement(i) instanceof FunctionVariable) { equ.setFunctionDependent(true); } expr = expr.replace(func.getFunctionVariables()[i], ev).wrap(); } } else { throw new MyError(loc, new String[] { "IllegalArgumentNumber" }); } if (equ.isFunctionDependent()) { return expr.makePolynomialTree(equ); } return new Polynomial(kernel, new Term( new ExpressionNode(kernel, left, operation, right), "")); } private Polynomial makePolyTreeFromFunction(Function func, Equation equ) { if (right instanceof ExpressionNode) { if (!equ.isFunctionDependent()) { equ.setFunctionDependent( ((ExpressionNode) right).containsFunctionVariable()); } // we may only make polynomial trees after replacement // ((ExpressionNode) right).makePolynomialTree(equ); } else if (right instanceof FunctionVariable) { equ.setFunctionDependent(true); } if (equ.isFunctionDependent()) { ExpressionNode expr = func.getExpression().getCopy(kernel); expr = expr.replace(func.getFunctionVariable(), right).wrap(); return expr.makePolynomialTree(equ); } return new Polynomial(kernel, new Term( new ExpressionNode(kernel, left, operation, right), "")); } private static boolean polynomialOperation(Operation operation2) { switch (operation2) { case NO_OPERATION: case PLUS: case MINUS: case MULTIPLY: case MULTIPLY_OR_FUNCTION: case DIVIDE: case POWER: case FUNCTION: case FUNCTION_NVAR: return true; default: return false; } } /** * returns true, if there are no variable objects in the subtree */ @Override final public boolean isConstant() { if (isLeaf()) { return left.isConstant(); } return left.isConstant() && right.isConstant(); } private ExpressionValue computeResolve() { Resolution res = new Resolution(); res.setType(ValueType.resolve(operation, left, right, res)); return res; } @Override final public boolean evaluatesToVectorNotPoint() { if (operation == Operation.RANDOM || operation == Operation.XCOORD || operation == Operation.YCOORD || operation == Operation.ZCOORD || operation == Operation.ABS || operation == Operation.ARG || operation == Operation.ALT) { return false; } if (isLeaf()) { return left.evaluatesToVectorNotPoint(); } // sin(vector), conjugate(vector), ... are complex numbers if (Operation.isSimpleFunction(operation) || operation == Operation.CONJUGATE) { return false; } boolean leftVector = left.evaluatesToVectorNotPoint(); boolean rightVector = right.evaluatesToVectorNotPoint(); if (operation == Operation.PLUS && left.evaluatesToNDVector()) { return leftVector && rightVector; } boolean ret = leftVector || rightVector; if (leftVector && rightVector && (operation == Operation.MULTIPLY || operation == Operation.VECTORPRODUCT)) { ret = false; } return ret; } /** * Force this to evaluate to vector */ public void setForceVector() { // this expression should be considered as a vector, not a point forceVector = true; } /** * @return true if forced to evaluate to vector */ final public boolean isForcedVector() { return forceVector; } /** * */ public void setForcePoint() { // this expression should be considered as a point, not a vector forcePoint = true; } /** * @return true iff forced to evaluate to point */ final public boolean isForcedPoint() { return forcePoint; } /** * Force to evaluate to function */ public void setForceFunction() { // this expression should be considered as a point, not a vector forceFunction = true; } /** * @return true iff forced to be a function */ final public boolean isForcedFunction() { return forceFunction; } /** * Returns whether this tree has any operations * * @return true iff this tree has any operations */ final public boolean hasOperations() { if (leaf) { if (left.isExpressionNode()) { return ((ExpressionNode) left).hasOperations(); } else if (left instanceof MyVecNDNode) { return true; } else { return false; } } return (right != null); } /** * Returns all GeoElement objects in the subtree */ @Override final public HashSet<GeoElement> getVariables() { if (leaf) { return left.getVariables(); } HashSet<GeoElement> leftVars = left.getVariables(); HashSet<GeoElement> rightVars = right.getVariables(); if (leftVars == null) { return rightVars; } else if (rightVars == null) { return leftVars; } else { leftVars.addAll(rightVars); return leftVars; } } /** * @return GeoElement variables */ final public GeoElement[] getGeoElementVariables() { HashSet<GeoElement> varset = getVariables(); if (varset == null) { return null; } Iterator<GeoElement> i = varset.iterator(); GeoElement[] ret = new GeoElement[varset.size()]; int j = 0; while (i.hasNext()) { ret[j++] = i.next(); } return ret; } @Override final public boolean isLeaf() { return leaf; // || operation == NO_OPERATION; } /** * @return true if this is leaf containing only GeoElement */ final public boolean isSingleGeoElement() { return leaf && left.isGeoElement(); } /** * @return left subexpression as GeoElement */ final public GeoElement getSingleGeoElement() { return (GeoElement) left; } /** * @return true if this is leaf containing only Variable */ public boolean isSingleVariable() { return (isLeaf() && (left instanceof Variable)); } /** * @return true if this is leaf containing only imaginary unit */ public boolean isImaginaryUnit() { return (isLeaf() && (left instanceof GeoVec2D) && ((GeoVec2D) left).isImaginaryUnit()); } /** * @return true iff this node contains MyStringBuffer as subnode */ public boolean containsMyStringBuffer() { if ((left instanceof MyStringBuffer) || (right instanceof MyStringBuffer)) { return true; } boolean ret = false; if (left.isExpressionNode()) { ret = ret || ((ExpressionNode) left).containsMyStringBuffer(); } if ((right != null) && right.isExpressionNode()) { ret = ret || ((ExpressionNode) right).containsMyStringBuffer(); } return ret; } /** * Returns a string representation of this node that can be used with the * given CAS, e.g. "*" and "^" are always printed. * * @param symbolic * true for variable names, false for values of variables * @param tpl * String template * @return string representation of this node that can be used with given * CAS */ public String getCASstring(StringTemplate tpl, boolean symbolic) { String ret = null; if (leaf) { // leaf is GeoElement or not /* * if (symbolic) { if (left.isGeoElement()) ret = ((GeoElement) * left).getLabel(); else if (left.isExpressionNode()) ret = * ((ExpressionNode)left).printJSCLString(symbolic); else ret = * left.toString(); } else { ret = left.toValueString(); } */ if (symbolic && left.isGeoElement()) { ret = ((GeoElement) left).getLabel(tpl); } else if (left.isExpressionNode()) { ret = ((ExpressionNode) left).getCASstring(tpl, symbolic); } else { ret = symbolic ? left.toString(tpl) : left.toValueString(tpl); } } // STANDARD case: no leaf else { // expression node // we could group the factors of all possible numerators here. // we won't do that. // http://www.geogebra.org/forum/viewtopic.php?f=22&t=29017 // numerGroup(); String leftStr = null, rightStr = null; if (symbolic && left.isGeoElement()) { leftStr = ((GeoElement) left).getLabel(tpl); } else if (left.isExpressionNode()) { leftStr = ((ExpressionNode) left).getCASstring(tpl, symbolic); } else { leftStr = symbolic ? left.toString(tpl) : left.toValueString(tpl); } if (right != null) { if (symbolic && right.isGeoElement()) { rightStr = ((GeoElement) right).getLabel(tpl); } else if (right.isExpressionNode()) { rightStr = ((ExpressionNode) right).getCASstring(tpl, symbolic); } else if (shaveBrackets()) { rightStr = ((MyList) right).toString(tpl, !symbolic, false); } else { rightStr = symbolic ? right.toString(tpl) : right.toValueString(tpl); } } // do not send random() to CAS // #4072 if (operation == Operation.RANDOM) { double d = left.evaluateDouble(); leftStr = kernel.format(d, StringTemplate.defaultTemplate); ret = ExpressionNode.operationToString(left, right, operation, leftStr, rightStr, true, tpl, kernel); } else { ret = ExpressionNode.operationToString(left, right, operation, leftStr, rightStr, !symbolic, tpl, kernel); } } return ret; } /** * @return a representation of all classes present in the tree */ final public String getTreeClass() { return getTreeClass(""); } /** * Decide whether the current expression value must be expanded for * OpenGeoProver. This code helps both the Wu and the Area method. * * @param ev * left or right side of the expression * @return if expansion is required */ final private boolean expandForOGP(ExpressionValue ev) { // The following types of operations and GeoElements are supported. // See also the OGP code for the available (parsable) expressions. if (operation.equals(Operation.EQUAL_BOOLEAN) && ev instanceof GeoSegment) { return false; // don't expand "AreEqual[Segment[X,Y],Segment[Z,W]]" } // format expressions return ((operation.equals(Operation.EQUAL_BOOLEAN) || operation.equals(Operation.DIVIDE) || operation.equals(Operation.MULTIPLY) || operation.equals(Operation.MINUS) || operation.equals(Operation.PLUS) || operation.equals(Operation.POWER)) && (ev instanceof GeoSegment || ev instanceof GeoPolygon || ev instanceof GeoNumeric)); } /** * @param prefix * @return a representation of all classes present in the tree */ final private String getTreeClass(String prefix) { String ret = ""; ret += "-\n"; if (left != null) { ret += prefix + " \\l:"; if (left instanceof ExpressionNode) { ret += ((ExpressionNode) left).getTreeClass(prefix + " "); } else { ret += left.getClass(); } ret += "\n"; } if (right != null) { ret += prefix + " \\r:"; if (right instanceof ExpressionNode) { ret += ((ExpressionNode) right).getTreeClass(prefix + " "); } else { ret += right.getClass(); } ret += "\n"; } return ret; } /** * Returns a string representation of this node. */ @Override final public String toString(StringTemplate tpl) { if (isSecret()) { return isSecret.getDefinition(tpl); } if (leaf) { // leaf is GeoElement or not if (left.isGeoElement()) { return ((GeoElement) left).getLabel(tpl); } return left.toString(tpl); } // expression node String leftStr = null, rightStr = null; if (left.isGeoElement()) { if (tpl.getStringType().equals(StringType.OGP) && expandForOGP(left)) { leftStr = ((GeoElement) left).getDefinition(tpl); } else { leftStr = ((GeoElement) left).getLabel(tpl); } } else { leftStr = left.toString(tpl); } if (right != null) { if (right.isGeoElement()) { if (tpl.getStringType().equals(StringType.OGP) && expandForOGP(right)) { rightStr = ((GeoElement) right).getDefinition(tpl); } else { rightStr = ((GeoElement) right).getLabel(tpl); } } else { if (shaveBrackets()) { rightStr = ((MyList) right).toString(tpl, false, false); } else { rightStr = right.toString(tpl); } } } return ExpressionNode.operationToString(left, right, operation, leftStr, rightStr, false, tpl, kernel); } private boolean shaveBrackets() { // TODO Auto-generated method stub return (operation == Operation.FUNCTION_NVAR || operation == Operation.ELEMENT_OF || (operation == Operation.VEC_FUNCTION && left instanceof GeoSurfaceCartesianND)) && (right instanceof MyList); } /** like toString() but with current values of variables */ @Override final public String toValueString(StringTemplate tpl) { if (isSecret()) { return isSecret.getDefinition(tpl); } if (isLeaf()) { // leaf is GeoElement or not if (left != null) { return left.toValueString(tpl); } } // needed for TRAC-4217 if (tpl.hasCASType() && left instanceof GeoNumeric && !(left instanceof GeoDummyVariable) && ((GeoElement) left).getLabelSimple() != null && ((GeoElement) left).getLabelSimple().startsWith("c_")) { ((GeoNumeric) left).setSendValueToCas(false); } // expression node String leftStr = left.toValueString(tpl); String rightStr = null; if (right != null) { if (shaveBrackets()) { rightStr = ((MyList) right).toString(tpl, true, false); } else { rightStr = right.toValueString(tpl); } } return ExpressionNode.operationToString(left, right, operation, leftStr, rightStr, true, tpl, kernel); } @Override final public String toOutputValueString(StringTemplate tpl) { if (isSecret()) { return isSecret.getDefinition(tpl); } if (isLeaf()) { // leaf is GeoElement or not if (left != null) { return left.toOutputValueString(tpl); } } // expression node String leftStr = left.toOutputValueString(tpl); String rightStr = null; if (right != null) { if (shaveBrackets()) { rightStr = ((MyList) right).toString(tpl, true, false); } else { rightStr = right.toOutputValueString(tpl); } } return ExpressionNode.operationToString(left, right, operation, leftStr, rightStr, true, tpl, kernel); } /** * Returns a string representation of this node in LaTeX syntax. Note: the * resulting string may contain special unicode characters like greek * characters or special signs for integer exponents. These sould be handled * afterwards! * * @param symbolic * true for variable names, false for values of variables */ @Override final public String toLaTeXString(boolean symbolic, StringTemplate tpl) { String ret; if (isSecret != null) { return isSecret.getDefinition(tpl); } if (isLeaf()) { // leaf is GeoElement or not if (left != null) { ret = left.toLaTeXString(symbolic, tpl); return checkMathml(ret, tpl); } } // expression node String leftStr = left.toLaTeXString(symbolic, tpl); String rightStr = null; if (right != null) { if (((operation == Operation.FUNCTION_NVAR) || (operation == Operation.ELEMENT_OF)) && (right instanceof MyList)) { // 1 character will be taken from the left and right // of rightStr in operationToString, but more // is necessary in case of LaTeX, we do that here // " \\{ " is put by MyList 5 - 1(escape) -1(operationToString) rightStr = ((MyList) right).toLaTeXStringNoBrackets(symbolic, tpl); } else { rightStr = right.toLaTeXString(symbolic, tpl); } } // build latex string ret = ExpressionNode.operationToString(left, right, operation, leftStr, rightStr, !symbolic, tpl, kernel); return checkMathml(ret, tpl); } /** * make sure string wrapped in MathML if necessary eg <ci>x</ci> */ private static String checkMathml(String str, StringTemplate tpl) { if (tpl.hasType(StringType.CONTENT_MATHML) && str.charAt(0) != '<') { return "<ci>" + str + "</ci>"; } return str; } /** * Returns a string representation of a node. * * @param left * left subtree * @param right * right subtree * @param operation * operation * @param leftStr * serialized left subtree * @param rightStr * serialized right subtree * @param valueForm * whether to show value or symbols * @param tpl * string template * @param kernel * kernel * @return string representation of a node. */ @SuppressFBWarnings({ "SF_SWITCH_FALLTHROUGH", "missing break is deliberate" }) final public static String operationToString(ExpressionValue left, ExpressionValue right, Operation operation, String leftStr, String rightStr, boolean valueForm, StringTemplate tpl, Kernel kernel) { ExpressionValue leftEval; StringBuilder sb = new StringBuilder(); StringType stringType = tpl.getStringType(); Localization loc = kernel.getLocalization(); switch (operation) { case NO_OPERATION: return leftStr; case NOT: return tpl.notString(left, leftStr); case OR: return tpl.orString(left, right, leftStr, rightStr); case AND_INTERVAL: return tpl.andIntervalString(left, right, leftStr, rightStr, valueForm); case AND: return tpl.andString(left, right, leftStr, rightStr); case IMPLICATION: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<implies/>", leftStr, rightStr); } else { tpl.append(sb, leftStr, left, operation); sb.append(' '); switch (stringType) { case LATEX: if (tpl.isInsertLineBreaks()) { sb.append("\\-"); } sb.append("\\to"); break; case LIBRE_OFFICE: sb.append("toward"); // don't know if it is correct TAM // 5/28/2012 break; default: sb.append(strIMPLIES); } sb.append(' '); tpl.append(sb, rightStr, right, operation); } break; case EQUAL_BOOLEAN: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<eq/>", leftStr, rightStr); } else if (stringType.equals(StringType.OGP)) { sb.append("AreEqual[" + leftStr + "," + rightStr + "]"); } else { if (tpl.getStringType().isGiac()) { // brackets needed round FIRST argument for eg // $1==$2 where $1=y=9 and $2=y=9 sb.append( "when(" + CustomFunctions.IS_ZERO + "(simplify(("); tpl.append(sb, leftStr, left, operation); sb.append(")-("); tpl.append(sb, rightStr, right, operation); sb.append("))),true,false)"); } else { tpl.infixBinary(sb, left, right, operation, leftStr, rightStr, tpl, tpl.equalSign()); } } break; case NOT_EQUAL: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<neq/>", leftStr, rightStr); } else { tpl.infixBinary(sb, left, right, operation, leftStr, rightStr, tpl, tpl.notEqualSign()); } break; case IS_ELEMENT_OF: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<in/>", leftStr, rightStr); } else if (stringType.isGiac()) { sb.append("when(count\\_eq("); sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(")==0,false,true)"); } else { tpl.append(sb, leftStr, left, operation); // sb.append(leftStr); sb.append(' '); switch (stringType) { case LATEX: if (tpl.isInsertLineBreaks()) { sb.append("\\-"); } sb.append("\\in"); break; case LIBRE_OFFICE: sb.append(" in "); break; default: sb.append(strIS_ELEMENT_OF); } sb.append(' '); tpl.append(sb, rightStr, right, operation); // sb.append(rightStr); } break; case IS_SUBSET_OF: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<subset/>", leftStr, rightStr); } else if (stringType.isGiac()) { sb.append("when(("); sb.append(leftStr); sb.append(") union ("); sb.append(rightStr); sb.append(")==("); sb.append(rightStr); // {1,2,3,3} union {} = {1,2,3} sb.append(") union {},true,false)"); } else { tpl.infixBinary(sb, left, right, operation, leftStr, rightStr, tpl, tpl.subsetSign()); } break; case IS_SUBSET_OF_STRICT: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<prsubset/>", leftStr, rightStr); } else if (stringType.isGiac()) { sb.append("when(("); sb.append(leftStr); sb.append(") union ("); sb.append(rightStr); sb.append(")==("); sb.append(rightStr); // {1,2,3,3} union {} = {1,2,3} sb.append(") union {} && dim("); sb.append(leftStr); sb.append("union {})<dim("); sb.append(rightStr); sb.append("union {}),true,false)"); } else { tpl.infixBinary(sb, left, right, operation, leftStr, rightStr, tpl, tpl.strictSubsetSign()); } break; case SET_DIFFERENCE: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<setdiff/>", leftStr, rightStr); } else if (stringType.isGiac()) { sb.append('('); sb.append(leftStr); sb.append(" minus "); sb.append(rightStr); sb.append(')'); } else { tpl.append(sb, leftStr, left, operation); // sb.append(leftStr); sb.append(' '); switch (stringType) { case LATEX: if (tpl.isInsertLineBreaks()) { sb.append("\\-"); } sb.append("\\setminus"); break; case LIBRE_OFFICE: sb.append(" setminus "); break; default: sb.append(strSET_DIFFERENCE); } sb.append(' '); if (right.isExpressionNode() && right.wrap() .getOperation() == Operation.SET_DIFFERENCE) { sb.append(tpl.leftBracket()); sb.append(rightStr); sb.append(tpl.rightBracket()); } else { tpl.append(sb, rightStr, right, operation); } // sb.append(rightStr); } break; case LESS: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<lt/>", leftStr, rightStr); } else { tpl.infixBinary(sb, left, right, operation, leftStr, rightStr, tpl, tpl.lessSign()); } break; case GREATER: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<gt/>", leftStr, rightStr); } else { tpl.infixBinary(sb, left, right, operation, leftStr, rightStr, tpl, tpl.greaterSign()); } break; case LESS_EQUAL: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<leq/>", leftStr, rightStr); } else { tpl.infixBinary(sb, left, right, operation, leftStr, rightStr, tpl, tpl.leqSign()); } break; case GREATER_EQUAL: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<qeq/>", leftStr, rightStr); } else { tpl.infixBinary(sb, left, right, operation, leftStr, rightStr, tpl, tpl.geqSign()); } break; case PARALLEL: if (stringType.equals(StringType.OGP)) { sb.append("AreParallel[" + leftStr + "," + rightStr + "]"); break; } tpl.infixBinary(sb, left, right, operation, leftStr, rightStr, tpl, tpl.parallelSign()); break; case PERPENDICULAR: if (stringType.equals(StringType.OGP)) { sb.append("ArePerpendicular[" + leftStr + "," + rightStr + "]"); break; } tpl.infixBinary(sb, left, right, operation, leftStr, rightStr, tpl, tpl.perpSign()); break; case VECTORPRODUCT: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<vectorproduct/>", leftStr, rightStr); } else if (stringType.isGiac()) { ArrayList<ExpressionNode> crossArg = new ArrayList<ExpressionNode>(); crossArg.add(left.wrap()); crossArg.add(right.wrap()); sb.append(kernel.getGeoGebraCAS() .translateCommandSignature("Cross.2") .replace("%0", leftStr).replace("%1", rightStr)); // from Ggb2Giac Cross.2 } else { tpl.append(sb, leftStr, left, operation); // sb.append(leftStr); sb.append(' '); switch (stringType) { case LATEX: if (tpl.isInsertLineBreaks()) { sb.append("\\-"); } sb.append("\\otimes"); break; case LIBRE_OFFICE: sb.append(" cdot "); break; default: sb.append(strVECTORPRODUCT); } sb.append(' '); boolean rightVectorProduct = right.isExpressionNode() && ((ExpressionNode) right) .getOperation() == Operation.VECTORPRODUCT; if (rightVectorProduct) { sb.append(tpl.leftBracket()); sb.append(rightStr); sb.append(tpl.rightBracket()); } else { tpl.append(sb, rightStr, right, operation); } // sb.append(rightStr); } break; case PLUS: return tpl.plusString(left, right, leftStr, rightStr, valueForm); case MINUS: return tpl.minusString(left, right, leftStr, rightStr, valueForm, loc); case MULTIPLY: return tpl.multiplyString(left, right, leftStr, rightStr, valueForm, loc); case DIVIDE: return tpl.divideString(left, right, leftStr, rightStr, valueForm); case POWER: return tpl.powerString(left, right, leftStr, rightStr, valueForm); case FACTORIAL: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<factorial/>", leftStr, null); break; case LIBRE_OFFICE: sb.append("fact {"); if ((leftStr.charAt(0) != '-') && // no unary left.isLeaf()) { sb.append(leftStr); } else { sb.append('('); sb.append(leftStr); sb.append(')'); } sb.append(" }"); break; default: if (((leftStr.charAt(0) != '-') && // no unary left.isLeaf()) || (opID(left) > Operation.POWER.ordinal())) { // not +, // -, *, // /, ^ sb.append(leftStr); } else { sb.append(tpl.leftBracket()); sb.append(leftStr); sb.append(tpl.rightBracket()); } sb.append('!'); break; } break; case COS: trig(leftStr, sb, "<cos/>", "\\cos", "COS(", "cos", "cos", tpl, loc, true); break; case SIN: trig(leftStr, sb, "<sin/>", "\\sin", "SIN(", "sin", "sin", tpl, loc, true); break; case TAN: trig(leftStr, sb, "<tan/>", "\\tan", "TAN(", "tan", "tan", tpl, loc, true); break; case CSC: trig(leftStr, sb, "<csc/>", "\\csc", "CSC(", "csc", "csc", tpl, loc, true); break; case SEC: trig(leftStr, sb, "<sec/>", "\\sec", "SEC(", "sec", "sec", tpl, loc, true); break; case COT: trig(leftStr, sb, "<cot/>", "\\cot", "COT(", "cot", "cot", tpl, loc, true); break; case CSCH: trig(leftStr, sb, "<csch/>", "\\csch", "CSCH(", "csch", "func csch", tpl, loc, false); break; case SECH: trig(leftStr, sb, "<sech/>", "\\sech", "SECH(", "sech", "func sech", tpl, loc, false); break; case COTH: trig(leftStr, sb, "<coth/>", "\\coth", "COTH(", "coth", "coth", tpl, loc, false); break; case ARCCOS: trig(leftStr, sb, "<arccos/>", "\\arccos", "ACOS(", "acos", "arccos", degFix("acos", kernel), tpl, loc, false, true); break; case ARCCOSD: trig(leftStr, sb, "<arccos/>", "\\arccos", "ACOS(", "acosd", "arccos", "acosd", tpl, loc, false, true); break; case ARCSIN: trig(leftStr, sb, "<arcsin/>", "\\arcsin", "ASIN(", "asin", "arcsin", degFix("asin", kernel), tpl, loc, false, true); break; case ARCSIND: trig(leftStr, sb, "<arcsin/>", "\\arcsin", "ASIN(", "asind", "arcsin", "asind", tpl, loc, false, true); break; case ARCTAN: trig(leftStr, sb, "<arctan/>", "\\arctan", "ATAN(", "atan", "arctan", degFix("atan", kernel), tpl, loc, false, true); break; case ARCTAND: trig(leftStr, sb, "<arctan/>", "\\arctan", "ATAN(", "atand", "arctan", "atand", tpl, loc, false, true); break; case ARCTAN2: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<atan/>", leftStr, rightStr); } else { switch (stringType) { case LATEX: wrapInBackslashOperatorname(sb, "atan2"); sb.append(" \\left( "); break; case LIBRE_OFFICE: sb.append("func atan2 left( "); break; case PSTRICKS: sb.append("ATAN2("); break; case GIAC: sb.append(degFix("atan2", kernel)); sb.append("("); break; default: sb.append("atan2("); } sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(tpl.rightBracket()); } break; case ARCTAN2D: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<atan/>", leftStr, rightStr); } else { switch (stringType) { case LATEX: wrapInBackslashOperatorname(sb, "atan2"); sb.append(" \\left( "); break; case LIBRE_OFFICE: sb.append("func atan2 left( "); break; case PSTRICKS: sb.append("ATAN2("); break; case GIAC: default: sb.append("atan2d("); } sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(tpl.rightBracket()); } break; case COSH: trig(leftStr, sb, "<cosh/>", "\\cosh", "COSH(", "cosh", "cosh", tpl, loc, false); break; case SINH: trig(leftStr, sb, "<sinh/>", "\\sinh", "SINH(", "sinh", "sinh", tpl, loc, false); break; case TANH: trig(leftStr, sb, "<tanh/>", "\\tanh", "TANH(", "tanh", "tanh", tpl, loc, false); break; case ACOSH: trig(leftStr, sb, "<arccosh/>", "\\operatorname{acosh}", "ACOSH(", "acosh", "arcosh", tpl, loc, false); break; case ASINH: trig(leftStr, sb, "<arcsinh/>", "\\operatorname{asinh}", "ASINH(", "asinh", "arsinh", tpl, loc, false); break; case ATANH: trig(leftStr, sb, "<arctanh/>", "\\operatorname{atanh}", "ATANH(", "atanh", "artanh", tpl, loc, false); break; case REAL: trig(leftStr, sb, "<real/>", "\\operatorname{real}", "", "real", "real", "re", tpl, loc, false); break; case IMAGINARY: trig(leftStr, sb, "<imaginary/>", "\\operatorname{imaginary}", "", "imaginary", "imaginary", "im", tpl, loc, false); break; case FRACTIONAL_PART: trig(leftStr, sb, "<todo/>", "\\operatorname{fractionalPart}", "", "fractionalPart", "fractionalPart", "fPart", tpl, loc, false); break; case ZETA: switch (stringType) { case LATEX: sb.append("\\zeta\\left( "); break; case LIBRE_OFFICE: sb.append("func zeta left ("); break; case GIAC: sb.append("Zeta("); break; default: sb.append("zeta("); } sb.append(leftStr); sb.append(tpl.rightBracket()); break; case CI: switch (stringType) { case LATEX: wrapInBackslashOperatorname(sb, "Ci"); sb.append(" \\left( "); break; case LIBRE_OFFICE: sb.append("func Ci left ("); break; case GIAC: appendFunction(sb, "Ci"); break; default: sb.append("cosIntegral("); } sb.append(leftStr); sb.append(tpl.rightBracket()); break; case SI: switch (stringType) { case LATEX: wrapInBackslashOperatorname(sb, "Si"); sb.append(" \\left( "); break; case LIBRE_OFFICE: sb.append("func Si left ("); break; case GIAC: appendFunction(sb, "Si"); break; default: sb.append("sinIntegral("); } sb.append(leftStr); sb.append(tpl.rightBracket()); break; case EI: switch (stringType) { case LATEX: wrapInBackslashOperatorname(sb, "Ei"); sb.append(" \\left( "); break; case LIBRE_OFFICE: sb.append("func Ei left ("); break; case GIAC: appendFunction(sb, "Ei"); break; default: sb.append("expIntegral("); } sb.append(leftStr); sb.append(tpl.rightBracket()); break; case ARBCONST: sb.append("arbconst("); sb.append(leftStr); sb.append(")"); break; case ARBINT: sb.append("arbint("); sb.append(leftStr); sb.append(")"); break; case ARBCOMPLEX: sb.append("arbcomplex("); sb.append(leftStr); sb.append(")"); break; case EXP: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<exp/>", leftStr, null); break; case LIBRE_OFFICE: sb.append("func "); case LATEX: // add brackets for eg e^b^c -> e^(b^c) boolean addParentheses = (left.isExpressionNode() && ((ExpressionNode) left).getOperation() .equals(Operation.POWER)); sb.append("\\mathit{e}^{"); if (addParentheses) { sb.append(tpl.leftBracket()); } sb.append(leftStr); if (addParentheses) { sb.append(tpl.rightBracket()); } sb.append('}'); break; case GEOGEBRA_XML: case GIAC: sb.append("exp("); sb.append(leftStr); sb.append(')'); break; case PSTRICKS: sb.append("EXP("); sb.append(leftStr); sb.append(')'); break; default: sb.append(Unicode.EULER_STRING); if (left.isLeaf()) { sb.append("^"); sb.append(leftStr); } else { sb.append("^("); sb.append(leftStr); sb.append(')'); } break; } break; case LOG: if (stringType.equals(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, "<ln/>", leftStr, null); } else { switch (stringType) { case LATEX: sb.append("\\ln \\left( "); break; case LIBRE_OFFICE: sb.append("ln left ( "); break; case GIAC: case GEOGEBRA_XML: sb.append("log("); break; case PSTRICKS: case PGF: default: sb.append("ln("); break; } sb.append(leftStr); sb.append(tpl.rightBracket()); } break; case LOGB: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<log/>", "<logbase>", leftStr, "</logbase>", "", rightStr, ""); break; case LATEX: sb.append("\\log_{"); sb.append(leftStr); sb.append('}'); sb.append(tpl.leftBracket()); sb.append(rightStr); sb.append(tpl.rightBracket()); break; case LIBRE_OFFICE: sb.append("log_{"); sb.append(leftStr); sb.append('}'); sb.append(tpl.leftBracket()); sb.append(rightStr); sb.append(tpl.rightBracket()); break; case GIAC: case PSTRICKS: case PGF: // ln(x)/ln(b) sb.append("ln("); sb.append(rightStr); sb.append(")/ln("); sb.append(leftStr); sb.append(')'); break; default: sb.append("log("); sb.append(leftStr); sb.append(", "); sb.append(rightStr); sb.append(')'); break; } break; case POLYGAMMA: switch (stringType) { case LATEX: sb.append("\\psi_{"); sb.append(leftStr); sb.append('}'); sb.append(tpl.leftBracket()); sb.append(rightStr); sb.append(tpl.rightBracket()); break; case GIAC: // ******************* // arguments swapped // swapped back in CommandDispatcherGiac // ******************* appendFunction(sb, "Psi"); sb.append(rightStr); sb.append(','); sb.append(leftStr); sb.append(')'); break; default: sb.append("polygamma("); sb.append(leftStr); if (stringType.equals(StringType.LIBRE_OFFICE)) { sb.append("\",\""); } else { sb.append(", "); } sb.append(rightStr); sb.append(')'); break; } break; case ERF: switch (stringType) { case LATEX: wrapInBackslashOperatorname(sb, "erf"); sb.append(tpl.leftBracket()); sb.append(leftStr); sb.append(tpl.rightBracket()); break; case LIBRE_OFFICE: sb.append("func "); case GIAC: default: sb.append("erf("); sb.append(leftStr); sb.append(')'); break; } break; case PSI: switch (stringType) { case LATEX: sb.append("\\psi"); sb.append(tpl.leftBracket()); sb.append(leftStr); sb.append(tpl.rightBracket()); break; case GIAC: appendFunction(sb, "Psi"); sb.append(leftStr); sb.append(')'); break; case LIBRE_OFFICE: sb.append("func "); default: sb.append("psi("); sb.append(leftStr); sb.append(')'); break; } break; case LOG10: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<log/>", leftStr, null); break; case LATEX: sb.append("\\log_{10} \\left("); sb.append(leftStr); sb.append("\\right)"); break; case LIBRE_OFFICE: sb.append("log_10 ("); sb.append(leftStr); sb.append(")"); break; case PSTRICKS: sb.append("log("); sb.append(leftStr); sb.append(')'); break; case GIAC: case PGF: sb.append("log10("); // user-defined function in Maxima sb.append(leftStr); sb.append(')'); break; default: sb.append("lg("); sb.append(leftStr); sb.append(')'); break; } break; case LOG2: switch (stringType) { case LATEX: sb.append("\\log_{2} \\left("); sb.append(leftStr); sb.append("\\right)"); break; case LIBRE_OFFICE: sb.append("log_2 ("); sb.append(leftStr); sb.append(")"); break; case GIAC: sb.append("log("); sb.append(leftStr); sb.append(")/log(2)"); break; default: sb.append("ld("); sb.append(leftStr); sb.append(')'); break; } break; case NROOT: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<root/>", leftStr, null); break; case LATEX: sb.append("\\sqrt["); sb.append(rightStr); sb.append("]{"); sb.append(leftStr); sb.append('}'); break; case LIBRE_OFFICE: sb.append("nroot{"); sb.append(rightStr); sb.append("},{"); sb.append(leftStr); sb.append('}'); break; case GEOGEBRA_XML: case GEOGEBRA: if (tpl.isPrintLocalizedCommandNames() && tpl != StringTemplate.editorTemplate) { sb.append(loc.getFunction("nroot")); } else { sb.append("nroot"); } sb.append("("); sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(')'); break; case GIAC: if (leftStr.equals(Unicode.EULER_STRING)) { sb.append("exp(1/("); sb.append(rightStr); sb.append("))"); } else { // was simplify(surd(, causes problems with output from // cubic formula, eg x^3 - 6x^2 - 7x + 9 sb.append("surd("); sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(")"); } break; default: // MAXIMA, MPREDUCE, PSTRICKS, ... sb.append("("); sb.append(leftStr); sb.append(")^(1/("); sb.append(rightStr); sb.append("))"); break; } break; case SQRT_SHORT: case SQRT: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<root/>", leftStr, null); break; case LATEX: sb.append("\\sqrt{"); sb.append(leftStr); sb.append('}'); break; case LIBRE_OFFICE: sb.append("sqrt{"); sb.append(leftStr); sb.append('}'); break; default: sb.append("sqrt("); sb.append(leftStr); sb.append(')'); } break; case CBRT: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<root/>", "<degree>", "3", "</degree>", "", leftStr, ""); break; case LATEX: sb.append("\\sqrt[3]{"); sb.append(leftStr); sb.append('}'); break; case LIBRE_OFFICE: sb.append("nroot{3}{"); sb.append(leftStr); sb.append('}'); break; case GIAC: // was simplify(surd(, causes problems with output from cubic // formula, eg x^3 - 6x^2 - 7x + 9 sb.append("surd("); sb.append(leftStr); sb.append(",3)"); break; default: sb.append("cbrt("); sb.append(leftStr); sb.append(')'); } break; case ABS: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<abs/>", leftStr, null); break; case LATEX: sb.append("\\left|"); sb.append(leftStr); sb.append("\\right|"); break; case LIBRE_OFFICE: sb.append("abs{"); sb.append(leftStr); sb.append('}'); break; case GIAC: // Giac's abs() now works for Vectors // so this is OK // (used to be custom ggbabs() function) sb.append("abs("); sb.append(leftStr); sb.append(")"); break; default: sb.append("abs("); sb.append(leftStr); sb.append(')'); } break; case SGN: switch (stringType) { case LATEX: wrapInBackslashOperatorname(sb, "sgn"); break; case GIAC: sb.append("sign"); break; case LIBRE_OFFICE: sb.append("func "); // fall through default: sb.append("sgn"); } sb.append(tpl.leftBracket()); sb.append(leftStr); sb.append(tpl.rightBracket()); break; case CONJUGATE: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<conjugate/>", leftStr, null); break; case LATEX: sb.append("\\overline{"); sb.append(leftStr); sb.append("}"); break; case LIBRE_OFFICE: sb.append("overline{"); sb.append(leftStr); sb.append("}"); break; case GIAC: sb.append("conj("); sb.append(leftStr); sb.append(')'); break; default: if (tpl.isPrintLocalizedCommandNames()) { sb.append(loc.getFunction("conjugate")); } else { sb.append("conjugate"); } sb.append("("); sb.append(leftStr); sb.append(')'); } break; case ARG: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<arg/>", leftStr, null); break; case LATEX: sb.append("\\arg \\left( "); sb.append(leftStr); sb.append("\\right)"); break; case GIAC: sb.append("arg("); sb.append(leftStr); sb.append(')'); break; case LIBRE_OFFICE: sb.append("func "); default: sb.append("arg("); sb.append(leftStr); sb.append(')'); } break; case ALT: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<alt/>", leftStr, null); break; case LATEX: sb.append("\\operatorname{alt} \\left( "); sb.append(leftStr); sb.append("\\right)"); break; case GIAC: sb.append("ggbalt("); sb.append(leftStr); sb.append(')'); break; case LIBRE_OFFICE: sb.append("func "); default: sb.append("alt("); sb.append(leftStr); sb.append(')'); } break; case FLOOR: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<floor/>", leftStr, null); break; case LATEX: sb.append("\\left"); sb.append("\\lfloor "); sb.append(leftStr); sb.append("\\right"); sb.append("\\rfloor "); break; case LIBRE_OFFICE: sb.append(" left lfloor "); sb.append(leftStr); sb.append(" right rfloor"); break; default: sb.append("floor("); sb.append(leftStr); sb.append(')'); } break; case CEIL: switch (stringType) { case CONTENT_MATHML: MathmlTemplate.mathml(sb, "<ceiling/>", leftStr, null); break; case LATEX: sb.append("\\left"); sb.append("\\lceil "); sb.append(leftStr); sb.append("\\right"); sb.append("\\rceil "); break; case LIBRE_OFFICE: sb.append("left lceil "); sb.append(leftStr); sb.append(" right rceil"); break; case GIAC: case PSTRICKS: sb.append("ceiling("); sb.append(leftStr); sb.append(')'); break; default: sb.append("ceil("); sb.append(leftStr); sb.append(')'); } break; case ROUND2: case ROUND: switch (stringType) { case LATEX: wrapInBackslashOperatorname(sb, "round"); sb.append(" \\left( "); break; case LIBRE_OFFICE: sb.append("func round left ("); default: sb.append("round("); } sb.append(leftStr); if (operation == Operation.ROUND2) { sb.append(", "); sb.append(rightStr); } sb.append(tpl.rightBracket()); break; case GAMMA: switch (stringType) { case LATEX: sb.append(" \\Gamma \\left( "); break; case LIBRE_OFFICE: sb.append("%GAMMA left ("); break; case GIAC: sb.append("Gamma("); break; default: sb.append("gamma("); } sb.append(leftStr); sb.append(tpl.rightBracket()); break; case GAMMA_INCOMPLETE: switch (stringType) { case LATEX: sb.append(" \\gamma \\left( "); break; case LIBRE_OFFICE: sb.append("%GAMMA left ("); break; case GIAC: sb.append("igamma("); break; default: sb.append("gamma("); } sb.append(leftStr); if (stringType.equals(StringType.LIBRE_OFFICE)) { sb.append("\",\""); } else { sb.append(", "); } sb.append(rightStr); sb.append(tpl.rightBracket()); break; case GAMMA_INCOMPLETE_REGULARIZED: switch (stringType) { case LATEX: wrapInBackslashOperatorname(sb, "P"); sb.append(" \\left( "); break; case LIBRE_OFFICE: sb.append("func gammaRegularized left ("); case GIAC: sb.append("igamma("); break; default: sb.append("gammaRegularized("); } sb.append(leftStr); if (stringType.equals(StringType.LIBRE_OFFICE)) { sb.append("\",\""); } else { sb.append(", "); } sb.append(rightStr); if (stringType.isGiac()) { sb.append(",1"); } sb.append(tpl.rightBracket()); break; case BETA: switch (stringType) { case LATEX: sb.append("\\Beta \\left( "); break; case LIBRE_OFFICE: sb.append("%BETA left("); break; case GIAC: sb.append("Beta("); break; default: sb.append("beta("); } sb.append(leftStr); if (stringType.equals(StringType.LIBRE_OFFICE)) { sb.append("\",\""); } else { sb.append(", "); } sb.append(rightStr); sb.append(tpl.rightBracket()); break; case BETA_INCOMPLETE: switch (stringType) { case LATEX: sb.append("\\Beta \\left( "); break; case LIBRE_OFFICE: sb.append("%BETA left("); break; case GIAC: sb.append("Beta("); break; default: sb.append("beta("); } sb.append(leftStr); if (stringType.equals(StringType.LIBRE_OFFICE)) { sb.append("\",\""); } else { sb.append(", "); } sb.append(rightStr); sb.append(tpl.rightBracket()); break; case BETA_INCOMPLETE_REGULARIZED: switch (stringType) { case LATEX: wrapInBackslashOperatorname(sb, "I"); sb.append(" \\left( "); break; case LIBRE_OFFICE: sb.append("func betaRegularized left ("); case GIAC: sb.append("Beta("); break; default: sb.append("betaRegularized("); } sb.append(leftStr); if (stringType.equals(StringType.LIBRE_OFFICE)) { sb.append("\",\""); } else { sb.append(", "); } sb.append(rightStr); if (stringType.isGiac()) { sb.append(",1"); } sb.append(tpl.rightBracket()); break; case RANDOM: if (valueForm) { sb.append(leftStr); } else { switch (stringType) { case GIAC: sb.append("rand(0,1)"); break; case LIBRE_OFFICE: sb.append("func "); default: sb.append("random()"); } } break; case XCOORD: if (!stringType.isGiac() && valueForm && !left.wrap().containsFunctionVariable() && (leftEval = left .evaluate(tpl)) instanceof VectorNDValue) { sb.append(kernel.format( ((VectorNDValue) leftEval).getVector().getX(), tpl)); } else if (valueForm && ((leftEval = left.evaluate(tpl)) instanceof GeoLine)) { sb.append(kernel.format(((GeoLine) leftEval).getX(), tpl)); } else { switch (stringType) { case LATEX: sb.append(" x \\left( "); sb.append(leftStr); sb.append(tpl.rightBracket()); break; case LIBRE_OFFICE: sb.append("func x left ("); sb.append(leftStr); sb.append(tpl.rightBracket()); case GIAC: sb.append("xcoord("); sb.append(leftStr); sb.append(")"); break; default: sb.append("x("); sb.append(leftStr); sb.append(')'); } } break; case YCOORD: if (!stringType.isGiac() && valueForm && !left.wrap().containsFunctionVariable() && (leftEval = left .evaluate(tpl)) instanceof VectorNDValue) { sb.append(kernel.format( ((VectorNDValue) leftEval).getVector().getY(), tpl)); } else if (valueForm && ((leftEval = left.evaluate(tpl)) instanceof GeoLine)) { sb.append(kernel.format(((GeoLine) leftEval).getY(), tpl)); } else { switch (stringType) { case LATEX: sb.append(" y \\left( "); sb.append(leftStr); sb.append("\\right)"); break; case LIBRE_OFFICE: sb.append("func y left ("); sb.append(leftStr); sb.append(tpl.rightBracket()); case GIAC: sb.append("ycoord("); sb.append(leftStr); sb.append(")"); break; default: sb.append("y("); sb.append(leftStr); sb.append(')'); } } break; case ZCOORD: if (!stringType.isGiac() && valueForm && !left.wrap().containsFunctionVariable() && (leftEval = left .evaluate(tpl)) instanceof Vector3DValue) { sb.append(kernel.format( ((Vector3DValue) leftEval).getPointAsDouble()[2], tpl)); } else if (valueForm && ((leftEval = left.evaluate(tpl)) instanceof GeoLine)) { sb.append(kernel.format(((GeoLine) leftEval).getZ(), tpl)); } else { switch (stringType) { case LATEX: sb.append(" z \\left( "); sb.append(leftStr); sb.append("\\right)"); break; case LIBRE_OFFICE: sb.append("func z left ("); sb.append(leftStr); sb.append(tpl.rightBracket()); case GIAC: sb.append("zcoord("); sb.append(leftStr); sb.append(")"); break; default: sb.append("z("); sb.append(leftStr); sb.append(')'); } } break; case MULTIPLY_OR_FUNCTION: Log.debug("Operation not resolved"); //$FALL-THROUGH$ case FUNCTION: if (stringType.isGiac() && right instanceof ListValue) { // TODO: does this ever get called? ListValue list = (ListValue) right; // eg seq(sin({4,5,6}[j]),j,0,2) // DON'T USE i (sqrt(-1) in Giac) sb.append("seq("); sb.append(leftStr); sb.append('('); sb.append(rightStr); sb.append("[j]),j,0,"); sb.append(list.size() - 1); sb.append(')'); break; } // GeoFunction and GeoFunctionConditional should not be expanded if (left instanceof GeoFunction) { GeoFunction geo = (GeoFunction) left; if (geo.isLabelSet() || geo.isLocalVariable()) { if (stringType.equals(StringType.LIBRE_OFFICE)) { sb.append("func "); } sb.append(geo.getLabel(tpl)); sb.append(tpl.leftBracket()); sb.append(rightStr); sb.append(tpl.rightBracket()); } else { // inline function: replace function var by right side FunctionVariable var = geo.getFunction() .getFunctionVariable(); String oldVarStr = var.toString(tpl); var.setVarString(rightStr); if (stringType.equals(StringType.LIBRE_OFFICE)) { sb.append("func "); } // do not recompute the expression string if we are plugging // in the same variable; #3481 String rhString = oldVarStr.equals(rightStr) ? leftStr : geo.getLabel(tpl); sb.append(rhString); var.setVarString(oldVarStr); } } else if (valueForm && left.isExpressionNode()) { ExpressionNode en = (ExpressionNode) left; // left could contain $ nodes to wrap a GeoElement // e.g. A1(x) = x^2 and B1(x) = $A$1(x) // value form of B1 is x^2 and NOT x^2(x) switch (en.operation) { case $VAR_ROW: case $VAR_COL: case $VAR_ROW_COL: sb.append(tpl.leftBracket()); sb.append(leftStr); sb.append(tpl.rightBracket()); break; case DERIVATIVE: if(stringType.isGiac()){ sb.append("diff("); sb.append(en.getLeft().toValueString(tpl)); sb.append("("); sb.append(rightStr); sb.append("))"); break; } appendUserFunction(sb,leftStr, rightStr, tpl); break; default: appendUserFunction(sb,leftStr, rightStr, tpl); break; } } else { // standard case if we get here appendUserFunction(sb,leftStr, rightStr, tpl); } break; // TODO: put back into case FUNCTION_NVAR:, see #1115 case ELEMENT_OF: if (tpl.hasCASType() && right instanceof MyList) { sb.append(leftStr); sb.append("["); if (((MyList) right).size() > 1) { ListValue list = (ListValue) right; for (int i = 0; i < list.size(); i++) { if (i != 0) { sb.append(','); } sb.append("("); sb.append(list.getListElement(i).toString(tpl)); sb.append(")-1"); } } else { sb.append("("); sb.append(rightStr); sb.append(")-1"); } sb.append("]"); break; } appendFunctionNVar(sb, left, leftStr, rightStr, tpl); break; case FUNCTION_NVAR: if (valueForm) { // TODO: avoid replacing of expressions in operationToString if ((left instanceof FunctionalNVar) && (right instanceof MyList)) { FunctionNVar func = ((FunctionalNVar) left).getFunction(); ExpressionNode en = func.getExpression().getCopy(kernel); for (int i = 0; (i < func.getVarNumber()) && (i < ((MyList) right).size()); i++) { en.replace(func.getFunctionVariables()[i], ((MyList) right).getListElement(i)); } // add brackets, see // http://www.geogebra.org/trac/ticket/1446 if (!stringType.equals(StringType.LATEX)) { sb.append(tpl.leftBracket()); } sb.append(en.toValueString(tpl)); if (!stringType.equals(StringType.LATEX)) { sb.append(tpl.rightBracket()); } } else if (left instanceof GeoDummyVariable) { sb.append(tpl.leftBracket()); sb.append(leftStr); sb.append(tpl.leftBracket()); sb.append(rightStr); sb.append(tpl.rightBracket()); sb.append(tpl.rightBracket()); } else { sb.append(tpl.leftBracket()); sb.append(leftStr); sb.append(tpl.rightBracket()); } } else { appendFunctionNVar(sb, left, leftStr, rightStr, tpl); } break; case VEC_FUNCTION: // GeoCurveCartesian should not be expanded if (left.isGeoElement() && ((GeoElement) left).isGeoCurveCartesian()) { sb.append(((GeoElement) left).getLabel(tpl)); } else { sb.append(leftStr); } sb.append(tpl.leftBracket()); sb.append(rightStr); sb.append(tpl.rightBracket()); break; case DIFF: // we only serialize this temporarily during GIAC parsing, so only // default template needed // GIAC template added for safety if (tpl.hasCASType()) { sb.append("diff("); } else { sb.append("ggbdiff("); } sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(")"); case DERIVATIVE: // e.g. f'' if (tpl.hasCASType()) { Log.error("Serialization not handled in Operation.Function"); } // labeled GeoElements should not be expanded if (left.isGeoElement() && ((GeoElement) left).isLabelSet()) { sb.append(((GeoElement) left).getLabel(tpl)); } else { sb.append(leftStr); } if (right.unwrap() instanceof NumberValue) { int order = (int) Math.round(right.evaluateDouble()); for (; order > 0; order--) { sb.append('\''); } } else { sb.append(right); } break; case $VAR_ROW: // e.g. A$1 if (valueForm || tpl.hasCASType()) { // GeoElement value sb.append(leftStr); } else { // $ for row GeoElement geo = (GeoElement) left; if (geo.getSpreadsheetCoords() != null) { sb.append(geo.getSpreadsheetLabelWithDollars(false, true)); } else { sb.append(leftStr); } } break; case $VAR_COL: // e.g. $A1 if (valueForm || tpl.hasCASType()) { // GeoElement value sb.append(leftStr); } else { // maybe wrongly parsed dynamic reference in CAS -- TODO decide // whether we need this if (!left.isGeoElement()) { sb.append('$'); sb.append(leftStr); break; } // $ for row GeoElement geo = (GeoElement) left; if (geo.getSpreadsheetCoords() != null) { sb.append(geo.getSpreadsheetLabelWithDollars(true, false)); } else { sb.append(leftStr); } } break; case $VAR_ROW_COL: // e.g. $A$1 if (valueForm || tpl.hasCASType()) { // GeoElement value sb.append(leftStr); } else { // $ for row GeoElement geo = (GeoElement) left; if (geo.getSpreadsheetCoords() != null) { sb.append(geo.getSpreadsheetLabelWithDollars(true, true)); } else { sb.append(leftStr); } } break; case FREEHAND: // need to output eg freehand(ggbtmpvarx) so that Derivative fails // rather than giving zero sb.append(loc.getPlain("Function.freehand")); sb.append('('); sb.append(leftStr); sb.append(')'); break; case DATA: // need to output eg freehand(ggbtmpvarx) so that Derivative fails // rather than giving zero if (tpl.isPrintLocalizedCommandNames()) { sb.append(loc.getCommand("DataFunction")); } else { sb.append("DataFunction"); } sb.append(tpl.leftSquareBracket()); if (tpl.hasType(StringType.GEOGEBRA_XML)) { sb.append(rightStr); sb.append(','); } sb.append(leftStr); sb.append(tpl.rightSquareBracket()); break; case INTEGRAL: if (stringType == StringType.LATEX) { sb.append("\\int "); sb.append(leftStr); sb.append("d"); sb.append(rightStr); } else if (stringType == StringType.LIBRE_OFFICE) { sb.append("int "); sb.append(leftStr); sb.append(" d"); sb.append(rightStr); } else { if (stringType.isGiac()) { sb.append("int("); } else { sb.append("gGbInTeGrAl("); } sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(")"); // AbstractApplication.debug(sb); } break; case SUM: if (stringType == StringType.LATEX) { sb.append("\\sum_{"); sb.append(((MyNumberPair) left).y.toString(tpl)); sb.append("="); sb.append(((MyNumberPair) right).x.toString(tpl)); sb.append("}^{"); sb.append(((MyNumberPair) right).y.toString(tpl)); sb.append("}"); sb.append(((MyNumberPair) left).x.toString(tpl)); } else if (stringType == StringType.LIBRE_OFFICE) { sb.append("sum from{"); sb.append(((MyNumberPair) left).y.toString(tpl)); sb.append("="); sb.append(((MyNumberPair) right).x.toString(tpl)); sb.append("} to{"); sb.append(((MyNumberPair) right).y.toString(tpl)); sb.append("}"); sb.append(((MyNumberPair) left).x.toString(tpl)); } else { if (stringType.isGiac()) { sb.append("sum("); } else { sb.append("gGbSuM("); } sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(")"); // AbstractApplication.debug(sb); } break; case SUBSTITUTION: if (stringType == StringType.LATEX) { sb.append("\\left."); sb.append(rightStr); sb.append("\\right\\mid_{"); sb.append(leftStr); sb.append("}"); } else if (stringType == StringType.LIBRE_OFFICE) { sb.append("left none"); sb.append(rightStr); sb.append("right rline_{"); sb.append(leftStr); sb.append("}"); } else { if (stringType.isGiac()) { sb.append("subst("); } else { sb.append("gGbSuBsTiTuTiOn("); } sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(")"); } break; case IF: if (stringType.isGiac()) { sb.append("when("); sb.append(leftStr); sb.append(','); sb.append(rightStr); sb.append(",undef)"); } else { if (tpl.isPrintLocalizedCommandNames()) { sb.append(loc.getCommand("If")); } else { sb.append("If"); } sb.append(tpl.leftSquareBracket()); sb.append(leftStr); sb.append(", "); sb.append(rightStr); sb.append(tpl.rightSquareBracket()); } break; case IF_ELSE: if (stringType.isGiac()) { sb.append("when("); sb.append(leftStr); sb.append(","); sb.append(rightStr); sb.append(")"); } else { if (tpl.isPrintLocalizedCommandNames()) { sb.append(loc.getCommand("If")); } else { sb.append("If"); } sb.append(tpl.leftSquareBracket()); sb.append(leftStr); sb.append(", "); sb.append(rightStr); sb.append(tpl.rightSquareBracket()); } break; case IF_LIST: if (stringType.isGiac()) { sb.append(loc.getCommand("piecewise(")); } else if (tpl.isPrintLocalizedCommandNames()) { sb.append(loc.getCommand("If")); sb.append(tpl.leftSquareBracket()); } else { sb.append("If"); sb.append(tpl.leftSquareBracket()); } MyList cond = (MyList) left; MyList fn = (MyList) right; for (int i = 0; i < cond.size(); i++) { if (i > 0) { sb.append(", "); } sb.append(valueForm ? cond.getListElement(i).toValueString(tpl) : cond.getListElement(i).toString(tpl)); sb.append(", "); sb.append(valueForm ? fn.getListElement(i).toValueString(tpl) : fn.getListElement(i).toString(tpl)); } if (fn.size() > cond.size()) { sb.append(", "); sb.append(valueForm ? fn.getListElement(fn.size() - 1).toValueString(tpl) : fn.getListElement(fn.size() - 1).toString(tpl)); } sb.append(stringType.isGiac() ? ")" : tpl.rightSquareBracket()); break; case SEQUENCE: if (tpl.hasCASType()) { sb.append("seq(round("); sb.append(leftStr); sb.append("),round("); sb.append(rightStr); sb.append("),1)"); } else { if (!left.isLeaf()) { sb.append(tpl.leftBracket()); } sb.append(leftStr); if (!left.isLeaf()) { sb.append(tpl.rightBracket()); } sb.append(Unicode.ellipsis); if (!right.isLeaf()) { sb.append(tpl.leftBracket()); } sb.append(rightStr); if (!right.isLeaf()) { sb.append(tpl.rightBracket()); } } break; default: sb.append("unhandled operation "); sb.append(operation); } return sb.toString(); } private static void appendUserFunction(StringBuilder sb, String leftStr, String rightStr, StringTemplate tpl) { sb.append(leftStr); sb.append(tpl.leftBracket()); sb.append(rightStr); sb.append(tpl.rightBracket()); } private static void wrapInBackslashOperatorname(StringBuilder sb, String cmd) { sb.append("\\operatorname{"); sb.append(cmd); sb.append("}"); } private static void appendFunctionNVar(StringBuilder sb, ExpressionValue left, String leftStr, String rightStr, StringTemplate tpl) { // multivariate functions if (left.isGeoElement()) { sb.append(((GeoElement) left).getLabel(tpl)); } else { sb.append(leftStr); } // no parameters for LeftSide[a], Derivative[sin(x+y),y], etc // parameters for unknown cas functions if (!left.isGeoElement() || ((GeoElement) left).isLabelSet() || ((GeoElement) left).isLocalVariable() || left instanceof GeoDummyVariable || left instanceof GeoCasCell) { sb.append(tpl.leftBracket()); // rightStr is a list of arguments, e.g. {2, 3} // drop the curly braces { and } // or list( and ) in case of mpreduce sb.append(rightStr); sb.append(tpl.rightBracket()); } } private static String degFix(String string, Kernel kernel) { if (kernel.getInverseTrigReturnsAngle()) { return "deg" + string; } return string; } private static void trig( String leftStr, StringBuilder sb, String mathml, String latex, String psTricks, String key, String libreOffice, StringTemplate tpl, Localization loc, boolean needDegrees) { // send "key" for Giac trig(leftStr, sb, mathml, latex, psTricks, key, libreOffice, key, tpl, loc, needDegrees); } private static void trig( String leftStr, StringBuilder sb, String mathml, String latex, String psTricks2, String key, String libreOffice, String giac, StringTemplate tpl, Localization loc, boolean needDegrees) { trig(leftStr, sb, mathml, latex, psTricks2, key, libreOffice, giac, tpl, loc, needDegrees, false); } /** * @param left * left expression (might need context-aware serialization for * GIAC) */ private static void trig( String leftStr, StringBuilder sb, String mathml, String latex, String psTricks, String key, String libreOffice, String giac, StringTemplate tpl, Localization loc, boolean needDegrees, boolean inverseNeedsDegrees) { if (tpl.hasType(StringType.CONTENT_MATHML)) { MathmlTemplate.mathml(sb, mathml, leftStr, null); } else { switch (tpl.getStringType()) { case LATEX: if (tpl.isPrintLocalizedCommandNames()) { // eg \\operatorname{sen} when sin translated sb.append("\\operatorname{"); sb.append(loc.getFunction(key)); sb.append("}"); } else { sb.append(latex); } sb.append(" \\left( "); break; case LIBRE_OFFICE: if (!libreOffice.equals(loc.getFunction(key))) { sb.append("func "); } sb.append(loc.getFunction(key)); sb.append(" left( "); break; case GIAC: sb.append(giac); sb.append('('); break; case PGF: // http://tex.stackexchange.com/questions/12951/incorrect-plot-using-pgfplots if (inverseNeedsDegrees) { sb.append("rad("); } sb.append(key); sb.append('('); break; case PSTRICKS: sb.append(psTricks); break; default: if (tpl.isPrintLocalizedCommandNames()) { sb.append(loc.getFunction(key)); } else { sb.append(key); } sb.append("("); } if (needDegrees && tpl.hasType(StringType.PGF)) { sb.append("(" + leftStr + ") 180/pi"); } else { sb.append(leftStr); } sb.append(tpl.rightBracket()); // extra closing bracket for rad(atan(...)) if (inverseNeedsDegrees && tpl.hasType(StringType.PGF)) { sb.append(")"); } } } private static void appendFunction(StringBuilder sb, String string) { sb.append(string); sb.append('('); } /** * @param exp * expression * @return whether top level operation is * or / */ public static boolean isMultiplyOrDivide(ExpressionNode exp) { return exp.getOperation().equals(Operation.MULTIPLY) || exp.getOperation().equals(Operation.DIVIDE); } /** * return operation number for expression nodes and -1 for other expression * values * * @param ev * expression value * @return operation number */ static public int opID(ExpressionValue ev) { if (ev.isExpressionNode()) { Operation op = ((ExpressionNode) ev).operation; // input (x>y)==(x+y>3) must be kept if (op.equals(Operation.GREATER) || op.equals(Operation.LESS) || op.equals(Operation.LESS_EQUAL) || op.equals(Operation.GREATER_EQUAL)) { return Operation.NOT_EQUAL.ordinal() - 1; } return op.ordinal(); } return -1; } @Override public boolean isNumberValue() { return this.evaluatesToNumber(false); } @Override final public boolean isExpressionNode() { return true; } /** * Returns true iff ev1 and ev2 are equal. * * http://tube.geogebra.org/m/1199093?cb=jenkins4310 * * @param ev1 * first value to compare * @param ev2 * second value to compare * @return true iff ev1 and ev2 are equal */ public static boolean isEqual(ExpressionValue ev1, ExpressionValue ev2) { if (ev1 instanceof NumberValue && ev2 instanceof NumberValue) { return Kernel.isEqual(((NumberValue) ev1).getDouble(), ((NumberValue) ev2).getDouble(), Kernel.STANDARD_PRECISION); } else if (ev1 instanceof TextValue && ev2 instanceof TextValue) { return ((TextValue) ev1) .toValueString(StringTemplate.defaultTemplate) .equals(((TextValue) ev2) .toValueString(StringTemplate.defaultTemplate)); } else if (ev1 instanceof VectorValue && ev2 instanceof VectorValue) { return ((VectorValue) ev1).getVector() .isEqual(((VectorValue) ev2).getVector()); } else if (ev1 instanceof BooleanValue && ev2 instanceof BooleanValue) { return ((BooleanValue) ev1).getMyBoolean() .getBoolean() == ((BooleanValue) ev2).getMyBoolean() .getBoolean(); } else if (ev1.isGeoElement() && ev2.isGeoElement()) { return ((GeoElement) ev1).isEqual(((GeoElement) ev2)); } else if (ev1 instanceof Functional && ev2 instanceof Functional) { return ((Functional) ev1).getGeoFunction() .isEqual(((Functional) ev2).getGeoFunction()); } return false; } /** * Returns whether the given expression will give the same String output as * val. * * @param symbolic * whether we should use the value (true) or the label (false) of * ev when it is a GeoElement * @param val * numeric value * @param ev * expression value to compare with val * @return true iff output of ev and val are the same */ final public static boolean isEqualString(ExpressionValue ev, double val, boolean symbolic) { if (ev.isLeaf() && (ev instanceof NumberValue)) { // function variables need to be kept if (ev instanceof FunctionVariable || ev instanceof GeoDummyVariable) { return false; } else if (ev instanceof MySpecialDouble) { // special doubles like pi, degree, rad need to be kept return false; } // check if ev is a labeled GeoElement if (symbolic) { if (ev.isGeoElement()) { // labeled GeoElement GeoElement geo = (GeoElement) ev; if (geo.isLabelSet() || geo.isLocalVariable() || !geo.isIndependent()) { return false; } } } NumberValue nv = (NumberValue) ev; return nv.getDouble() == val; } return false; } @Override public boolean isTopLevelCommand() { return isLeaf() && (left instanceof Command); } @Override public boolean isTopLevelCommand(String checkName) { return isLeaf() && (left instanceof ValidExpression) && (((ValidExpression) left).isTopLevelCommand(checkName)); } @Override public Command getTopLevelCommand() { if (isTopLevelCommand()) { return (Command) left; } return null; } /** * If the expression is linear in fv, returns the corresponding coefficient. * Otherwise returns null. * * @param fv * variable whose coefficient we want * @return coefficient or null */ public Double getCoefficient(FunctionVariable fv) { if (this.isLeaf()) { if (this.toString(StringTemplate.defaultTemplate) .equals(fv.toString(StringTemplate.defaultTemplate))) { return 1.0; } return 0.0; } Double lc = getLeftTree().getCoefficient(fv); Double rc = getRightTree() == null ? null : getRightTree().getCoefficient(fv); if ((lc == null) || (rc == null)) { return null; } switch (this.operation) { case PLUS: return lc + rc; case MINUS: return lc - rc; case MULTIPLY: if (!getRightTree().containsFunctionVariable()) { return lc * getRightTree().evaluateDouble(); } else if (!getLeftTree().containsFunctionVariable()) { return rc * getLeftTree().evaluateDouble(); } break; case DIVIDE: if (!getRightTree().containsFunctionVariable()) { return lc / getRightTree().evaluateDouble(); } break; default: break; } if ((left.contains(fv) || right.contains(fv))) { return null; } return 0.0; } /* * appends a string to sb, brackets are put around it if the order of * operation dictates */ /** * @param op * operation * @return whether this operation returns boolean and can be used in chain * eg. x < y <=z */ public static boolean chainedBooleanOp(Operation op) { switch (op) { case EQUAL_BOOLEAN: case NOT_EQUAL: case IS_SUBSET_OF: case IS_SUBSET_OF_STRICT: case LESS: case LESS_EQUAL: case GREATER: case GREATER_EQUAL: case PERPENDICULAR: case PARALLEL: return true; default: return false; } } /** * @param ev * expression * @param v * value * @return whether expression is a constant with given value, possibly * wrapped */ public static boolean isConstantDouble(ExpressionValue ev, double v) { ExpressionValue base = ev.unwrap(); return base instanceof MyDouble && base.isConstant() // don't use Kernel.isEqual() to check == 0 // as can lose leading coefficient of polynomial && v == ((MyDouble) base).getDouble(); } /** * @param v2 * value to add * @return result of addition */ public ExpressionNode plus(ExpressionValue v2) { if (isConstantDouble(v2, 0)) { return this; } if (this.isLeaf() && isConstantDouble(left, 0)) { return v2.wrap(); } return new ExpressionNode(kernel, this, Operation.PLUS, v2); } /** * @param v2 * value to compare * @return result this < v2 */ public ExpressionNode lessThan(ExpressionValue v2) { return new ExpressionNode(kernel, this, Operation.LESS, v2); } /** * @param d * value to compare * @return result this < d */ public ExpressionNode lessThan(double d) { return new ExpressionNode(kernel, this, Operation.LESS, new MyDouble(kernel, d)); } /** * @param d * value to compare * @return result this <= d */ public ExpressionNode lessThanEqual(double d) { return new ExpressionNode(kernel, this, Operation.LESS_EQUAL, new MyDouble(kernel, d)); } /** * @return result of erf(this) */ public ExpressionNode erf() { return new ExpressionNode(kernel, this, Operation.ERF, null); } /** * @return result of abs(this) */ public ExpressionNode abs() { return new ExpressionNode(kernel, this, Operation.ABS, null); } /** * @return result of sec(this) */ public ExpressionNode sec() { return new ExpressionNode(kernel, this, Operation.SEC, null); } /** * @return result of tan(this) */ public ExpressionNode tan() { return new ExpressionNode(kernel, this, Operation.TAN, null); } /** * @return result of sech(this) */ public ExpressionNode sech() { return new ExpressionNode(kernel, this, Operation.SECH, null); } /** * @return result of tanh(this) */ public ExpressionNode tanh() { return new ExpressionNode(kernel, this, Operation.TANH, null); } /** * @return result of cosech(this) */ public ExpressionNode cosech() { return new ExpressionNode(kernel, this, Operation.CSCH, null); } /** * @return result of coth(this) */ public ExpressionNode coth() { return new ExpressionNode(kernel, this, Operation.COTH, null); } /** * @return result of cosec(this) */ public ExpressionNode cosec() { return new ExpressionNode(kernel, this, Operation.CSC, null); } /** * @return result of cot(this) */ public ExpressionNode cot() { return new ExpressionNode(kernel, this, Operation.COT, null); } /** * @return result of this! */ public ExpressionNode factorial() { return new ExpressionNode(kernel, this, Operation.FACTORIAL, null); } /** * @return result of ln(this) */ public ExpressionNode ln() { return new ExpressionNode(kernel, this, Operation.LOG, null); } /** * @return result of gamma(this) */ public ExpressionNode gamma() { return new ExpressionNode(kernel, this, Operation.GAMMA, null); } /** * @param v2 * input * @return result of gamma(v2, this) */ public ExpressionNode gammaIncompleteReverseArgs(ExpressionValue v2) { return new ExpressionNode(kernel, v2, Operation.GAMMA_INCOMPLETE, this); } /** * @param v2 * input * @return result of gamma(this, v2) */ public ExpressionNode gammaIncomplete(ExpressionValue v2) { return new ExpressionNode(kernel, this, Operation.GAMMA_INCOMPLETE, v2); } /** * @param v2 * input * @return result of beta(this, v2) */ public ExpressionNode beta(ExpressionValue v2) { return new ExpressionNode(kernel, this, Operation.BETA, v2); } /** * @return result of exp(this) */ public ExpressionNode exp() { return new ExpressionNode(kernel, this, Operation.EXP, null); } /** * @param n * order of polygamma * @return result of polyganma(n, this) */ public ExpressionNode polygamma(double n) { return new ExpressionNode(kernel, wrap(n), Operation.POLYGAMMA, this); } /** * @return result of sin(this) */ public ExpressionNode sin() { return new ExpressionNode(kernel, this, Operation.SIN, null); } /** * @return result of sinh(this) */ public ExpressionNode sinh() { return new ExpressionNode(kernel, this, Operation.SINH, null); } /** * @return result of cosh(this) */ public ExpressionNode cosh() { return new ExpressionNode(kernel, this, Operation.COSH, null); } /** * @return result of cos(this) */ public ExpressionNode cos() { return new ExpressionNode(kernel, this, Operation.COS, null); } /** * @return result of 1/this */ public ExpressionNode reciprocate() { return new ExpressionNode(kernel, this, Operation.POWER, new MyDouble(kernel, -1)); } /** * @return result of sqrt(this) */ public ExpressionNode sqrt() { return new ExpressionNode(kernel, this, Operation.SQRT, null); } /** * @return result of cbrt(this) */ public ExpressionNode cbrt() { return new ExpressionNode(kernel, this, Operation.CBRT, null); } /** * @return result of sqrt(this) */ public ExpressionNode sgn() { return new ExpressionNode(kernel, this, Operation.SGN, null); } /** * @return result of atan(this) */ public ExpressionNode atan() { return new ExpressionNode(kernel, this, Operation.ARCTAN, null); } /** * @return result of this * -1 */ public ExpressionNode reverseSign() { return new ExpressionNode(kernel, new MyDouble(kernel, -1.0), Operation.MULTIPLY, this); } /** * @return result of 0 - this */ public ExpressionNode reverseSign2() { return new ExpressionNode(kernel, new MyDouble(kernel, 0.0), Operation.MINUS, this); } /** * @param v1 * input * @param v2 * input * @return result of betaRegularized(this, v1, v2) */ public ExpressionNode betaRegularized(ExpressionValue v1, ExpressionValue v2) { return new ExpressionNode(kernel, new MyNumberPair(kernel, this, v1), Operation.BETA_INCOMPLETE_REGULARIZED, v2); } /** * @return result of this^2 */ public ExpressionNode square() { return new ExpressionNode(kernel, this, Operation.POWER, new MyDouble(kernel, 2.0)); } /** * @param v2 * value to subtract * @return result of subtract */ public ExpressionNode subtract(ExpressionValue v2) { if (isConstantDouble(v2, 0)) { return this; } if (this.isLeaf() && isConstantDouble(left, 0)) { return v2.wrap().reverseSign(); } return new ExpressionNode(kernel, this, Operation.MINUS, v2); } /** * @param d * value to add * @return result of add */ public ExpressionNode plus(double d) { if (d == 0) { return this; } return new ExpressionNode(kernel, this, Operation.PLUS, new MyDouble(kernel, d)); } /** * @param d * value to add * @return result of add */ public ExpressionNode plusR(double d) { if (d == 0) { return this; } return new ExpressionNode(kernel, new MyDouble(kernel, d), Operation.PLUS, this); } /** * @param d * value to multiply * @return result of multiply */ public ExpressionNode multiply(double d) { if (d == 0 || isConstantDouble(this, 0)) { // don't use Kernel.isZero() to check == 0 // as can lose leading coefficient of polynomial return new ExpressionNode(kernel, 0); } else if (Kernel.isEqual(1, d)) { return this; } return new ExpressionNode(kernel, this, Operation.MULTIPLY, new MyDouble(kernel, d)); } /** * @param d * value to multiply * @return result of multiply */ public ExpressionNode multiplyR(double d) { if (d == 0) { // don't use Kernel.isZero() to check == 0 // as can lose leading coefficient of polynomial return new ExpressionNode(kernel, 0); } else if (Kernel.isEqual(1, d)) { return this; } return new ExpressionNode(kernel, new MyDouble(kernel, d), Operation.MULTIPLY, this); } /** * @param d * value to multiply * @return result of multiply */ public ExpressionNode power(double d) { if (Kernel.isZero(d)) { return new ExpressionNode(kernel, 1); } else if (Kernel.isEqual(1, d) || isConstantDouble(this, 1) || isConstantDouble(this, 0)) { return this; } return new ExpressionNode(kernel, this, Operation.POWER, new MyDouble(kernel, d)); } /** * @param d * value to subtract * @return this - d */ public ExpressionNode subtract(double d) { if (d == 0) { return this; } return new ExpressionNode(kernel, this, Operation.MINUS, new MyDouble(kernel, d)); } /** * @param d * value to subtract * @return d - this */ public ExpressionNode subtractR(double d) { if (d == 0) { return new ExpressionNode(kernel, new MyDouble(kernel, -1), Operation.MULTIPLY, this); } return new ExpressionNode(kernel, new MyDouble(kernel, d), Operation.MINUS, this); } /** * @param v2 * coefficient * @return result of multiplication */ public ExpressionNode multiply(ExpressionValue v2) { if (isConstantDouble(v2, 0) || isConstantDouble(this, 1)) { return v2.wrap(); } if (isConstantDouble(v2, 1) || isConstantDouble(this, 0)) { return this; } return new ExpressionNode(kernel, v2, Operation.MULTIPLY, this); } /** * @param v2 * coefficient * @return result of multiplication */ public ExpressionNode multiplyR(ExpressionValue v2) { if (isConstantDouble(v2, 0) || isConstantDouble(this, 1)) { return v2.wrap(); } if (isConstantDouble(v2, 1)) { return this; } return new ExpressionNode(kernel, this, Operation.MULTIPLY, v2); } /** * @param v2 * exponent * @return resulting power */ public ExpressionNode power(ExpressionValue v2) { if (isConstantDouble(v2, 0)) { return new ExpressionNode(kernel, 1); } if (isConstantDouble(v2, 1)) { return this; } return new ExpressionNode(kernel, this, Operation.POWER, v2); } /** * @param d * exponent * @return d ^ this */ public ExpressionNode powerR(double d) { return new ExpressionNode(kernel, new MyDouble(kernel, d), Operation.POWER, this); } /** * @param v2 * divisor * @return result of division */ public ExpressionNode divide(ExpressionValue v2) { return new ExpressionNode(kernel, this, Operation.DIVIDE, v2); } public ExpressionNode divideSimplify(ExpressionValue v2) { // 0 / y = 0 if (isConstantDouble(this, 0)) { return this; } // x / 1 = x if (isConstantDouble(v2, 1)) { return this; } return new ExpressionNode(kernel, this, Operation.DIVIDE, v2); } /** * @param d * divisor * @return result of division */ public ExpressionNode divide(double d) { if (d == 1) { // don't use Kernel.isEqual() to check == 1 // as can lose leading coefficient of polynomial return this; } if (d == -1) { // don't use Kernel.isEqual() to check == -1 // as can lose leading coefficient of polynomial return this.multiplyR(-1); } return new ExpressionNode(kernel, this, Operation.DIVIDE, new MyDouble(kernel, d)); } /** * @param v2 * clause * @return result of conjuction */ public ExpressionNode and(ExpressionValue v2) { return new ExpressionNode(kernel, this, Operation.AND, v2); } /** * @return negation of this expression (optimizes negation of >,<,=>,<=) */ public ExpressionNode negation() { Operation opNegated = this.operation.negate(); if (Operation.NOT.equals(opNegated)) { // unary, not binary return new ExpressionNode(kernel, this, Operation.NOT, null); } return new ExpressionNode(kernel, left, opNegated, right); } /** * @param toRoot * true to replace powers by roots * @param maxRoot * do not use roots higher than that * @return this node with replaced powers / roots */ public boolean replacePowersRoots(boolean toRoot, int maxRoot) { boolean didReplacement = false; if (toRoot && getOperation() == Operation.POWER && getRight().isExpressionNode()) { boolean hit = false; ExpressionNode rightLeaf = (ExpressionNode) getRight(); // replaces 1 DIVIDE 2 by SQRT 2, and same for CBRT if ((rightLeaf.getOperation() == Operation.DIVIDE) && rightLeaf.isConstant()) { if (rightLeaf.getRight() .toString(StringTemplate.defaultTemplate).equals("2")) { setOperation(Operation.SQRT); hit = true; } else if (rightLeaf.getRight() .toString(StringTemplate.defaultTemplate).equals("3")) { setOperation(Operation.CBRT); hit = true; } else if (!rightLeaf.getRight().unwrap().isExpressionNode() && rightLeaf.getRight() instanceof NumberValue && Kernel.isInteger(((NumberValue) rightLeaf.getRight()) .getDouble()) && ((NumberValue) rightLeaf.getRight()) .getDouble() <= maxRoot) { setOperation(Operation.NROOT); setRight(new MyDouble(kernel, ((NumberValue) rightLeaf.getRight()).getDouble())); hit = true; } if (hit) { didReplacement = true; if (rightLeaf.getLeft() .toString(StringTemplate.defaultTemplate) .equals("1")) { if (operation != Operation.NROOT) { setRight(new MyDouble(kernel, Double.NaN)); } } else { // to parse x^(c/2) to sqrt(x^c) double c = 1; if (rightLeaf.getLeft().isConstant()) { c = rightLeaf.getLeft().evaluateDouble(); } if (c < 0) { setRight(new ExpressionNode(kernel, getLeft().wrap().power(-c), getOperation(), getRight())); setOperation(Operation.DIVIDE); setLeft(new MyDouble(kernel, 1.0)); } else { setLeft(new ExpressionNode(kernel, getLeft(), Operation.POWER, rightLeaf.getLeft())); } } } } } else if (!toRoot) { boolean hit = false; // replaces SQRT 2 by 1 DIVIDE 2, and same for CBRT ExpressionNode power = null; if (getOperation() == Operation.SQRT) { power = new ExpressionNode(kernel, new MyDouble(kernel, 1), Operation.DIVIDE, new MyDouble(kernel, 2)); hit = true; } else if (getOperation() == Operation.CBRT) { power = new ExpressionNode(kernel, new MyDouble(kernel, 1), Operation.DIVIDE, new MyDouble(kernel, 3)); hit = true; } else if (getOperation() == Operation.NROOT) { power = new ExpressionNode(kernel, new MyDouble(kernel, 1), Operation.DIVIDE, right); hit = true; } if (hit) { didReplacement = true; setOperation(Operation.POWER); setRight(power); } } return didReplacement; } /** * Replaces one object with another * * @param oldObj * object to be replaced * @param newObj * replacement * @return this node with replaced objects */ public ExpressionValue replace(ExpressionValue oldObj, ExpressionValue newObj) { return traverse(Replacer.getReplacer(oldObj, newObj)); } /** * @param fv * parameter of function, eg 'x' in f(x)=x^2 * @return new GeoFunction */ public GeoFunction buildFunction(FunctionVariable fv) { Function tempFun = new Function(this, fv); tempFun.initFunction(); return new GeoFunction(kernel.getConstruction(), tempFun); } @Override public ExpressionValue unwrap() { if (isLeaf()) { return getLeft(); } return this; } @Override public ExpressionNode wrap() { return this; } @Override public boolean hasCoords() { if (isLeaf()) { return left != null && left.hasCoords(); } return getLeft().hasCoords() || getRight().hasCoords() || operation == Operation.VEC_FUNCTION; } @Override public ExpressionNode derivative(FunctionVariable fv, Kernel kernel0) { return Derivative.get(left, right, operation, fv, kernel0); } private static ExpressionNode wrap(ExpressionValue exp) { return exp.wrap(); } @Override public ExpressionNode integral(FunctionVariable fv, Kernel kernel0) { // symbolic integrals disabled in exam mode switch (operation) { case XCOORD: case YCOORD: case ZCOORD: return new ExpressionNode(kernel0, this, Operation.MULTIPLY, fv); case POWER: // eg x^2 if (left == fv && !right.contains(fv)) { double index = right.evaluateDouble(); if (!Double.isNaN(index) && !Double.isInfinite(index)) { if (Kernel.isZero(index + 1)) { return new ExpressionNode(kernel0, left, Operation.LOG, null); } return wrap(left).power(index + 1).divide(index + 1); } } else if (!left.contains(fv)) { // eg 2^x if (right == fv) { double base = left.evaluateDouble(); if (!Double.isNaN(base) && !Double.isInfinite(base)) { // 1^x if (Kernel.isEqual(base, 1)) { return wrap(fv); } if (Kernel.isGreater(base, 0)) { return this.divide(wrap(left).ln()); } } } } else if (right.isNumberValue() && !right.contains(fv)) { double index = right.evaluateDouble(); if (!Double.isNaN(index) && !Double.isInfinite(index)) { double coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { // (exp)^-1 -> ln(abs(exp)) if (Kernel.isEqual(index, -1)) { return wrap(left).abs().ln().divide(coeff); } return wrap(left).power(index + 1) .divide(coeff * ((index + 1))); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { if (Kernel.isEqual(index, -1)) { // (exp)^-1 -> ln(abs(exp)) return wrap(left).abs().ln().multiply(coeff); } return wrap(left).power(index + 1) .multiply(coeff / ((index + 1))); } } } break; case NO_OPERATION: return wrap(left.integral(fv, kernel0)); case DIVIDE: if (right.isNumberValue() && !right.contains(fv)) { return wrap(left.integral(fv, kernel0)).divide(right); } if (left.isNumberValue() && !left.contains(fv) && right == fv) { // eg 4/x return new ExpressionNode(kernel0, fv, Operation.LOG, null) .multiply(left); } break; case MULTIPLY: if (right.isNumberValue() && !right.contains(fv)) { return wrap(left.integral(fv, kernel0)).multiplyR(right); } else if (left.isNumberValue() && !left.contains(fv)) { return wrap(right.integral(fv, kernel0)).multiplyR(left); } // can't do by parts without simplification (use Polynomial?) break; case PLUS: return wrap(left.integral(fv, kernel0)) .plus(right.integral(fv, kernel0)); case MINUS: return wrap(left.integral(fv, kernel0)) .subtract(right.integral(fv, kernel0)); case SIN: return linearIntegral(-1, Operation.COS, fv); case COS: return linearIntegral(1, Operation.SIN, fv); case TAN: double coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).sec().abs().ln().divide(coeff); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).sec().abs().ln().multiply(coeff); } break; case SEC: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).sec().plus(wrap(left).tan()).abs().ln() .divide(coeff); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).sec().plus(wrap(left).tan()).abs().ln() .multiply(coeff); } break; case CSC: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).cosec().plus(wrap(left).cot()).abs().ln() .divide(-coeff); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).cosec().plus(wrap(left).cot()).abs().ln() .multiply(-coeff); } break; case COT: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).sin().abs().ln().divide(coeff); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).sin().abs().ln().multiply(coeff); } break; case SINH: return linearIntegral(1, Operation.COSH, fv); case COSH: return linearIntegral(1, Operation.SINH, fv); case TANH: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).cosh().abs().ln().divide(coeff); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).cosh().abs().ln().multiply(coeff); } break; case SECH: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).exp().atan().divide(coeff / 2); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).exp().atan().multiply(2 * coeff); } break; case CSCH: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).cosech().plus(wrap(left).coth()).abs().ln() .divide(-coeff); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).cosech().plus(wrap(left).coth()).abs().ln() .multiply(-coeff); } break; case COTH: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).sinh().abs().ln().divide(coeff); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).sinh().abs().ln().multiply(coeff); } break; case EXP: return linearIntegral(1, Operation.EXP, fv); case ARCSIN: case ARCSIND: case ARCCOS: case ARCTAN: case ASINH: case ACOSH: case ATANH: case ABS: case SGN: case SI: case CI: case EI: case ERF: case PSI: case POLYGAMMA: case LOGB: break; case IF_ELSE: MyNumberPair np = (MyNumberPair) left; np = new MyNumberPair(kernel0, np.x, np.y.derivative(fv, kernel0)); return new ExpressionNode(kernel0, np, Operation.IF_ELSE, right.integral(fv, kernel0)); case IF: return new ExpressionNode(kernel0, left, Operation.IF, right.integral(fv, kernel0)); case LOG: // base e (ln) coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).ln().multiply(left).subtract(left) .divide(coeff); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).ln().multiply(left).subtract(left) .multiply(coeff); } break; case LOG10: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).ln().multiply(left).subtract(left) .divide(wrap(10).ln().multiply(coeff)); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).ln().multiply(left).subtract(left) .multiply(coeff).divide(wrap(10).ln()); } break; case LOG2: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).ln().multiply(left).subtract(left) .divide(wrap(2).ln().multiply(coeff)); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).ln().multiply(left).subtract(left) .multiply(coeff).divide(wrap(2).ln()); } break; case NROOT: if (right.isNumberValue() && !right.contains(fv)) { coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).nroot(right).multiply(left) .multiply(right) .divide((right.evaluateDouble() + 1) * coeff); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).nroot(right).multiply(left) .multiply(right) .divide((right.evaluateDouble() + 1) / coeff); } } break; case SQRT: case SQRT_SHORT: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).sqrt().multiply(left).divide(coeff * 3d / 2d); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).sqrt().multiply(left) .multiply(coeff * 2d / 3d); } break; case CBRT: coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).cbrt().multiply(left).divide(coeff * 4d / 3d); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return wrap(left).cbrt().multiply(left) .multiply(coeff * 3d / 4d); } break; default: break; } Log.warn("unhandled operation in integral() (no CAS version): " + operation.toString()); // undefined return wrap(Double.NaN); } /** * @param n * n * @return nth root of this */ public ExpressionNode nroot(ExpressionValue n) { return new ExpressionNode(kernel, this, Operation.NROOT, n); } private ExpressionNode linearIntegral(int i, Operation op, FunctionVariable fv) { if (left == fv) { return new ExpressionNode(kernel, left, op, null).multiplyR(i); } double coeff = getLinearCoefficient(fv, left); if (!Double.isNaN(coeff)) { return new ExpressionNode(kernel, left, op, null).multiplyR(i) .divide(coeff); } coeff = getLinearCoefficientDiv(fv, left); if (!Double.isNaN(coeff)) { return new ExpressionNode(kernel, left, op, null).multiply(coeff) .multiplyR(i); } Log.warn("not linear integral"); return wrap(Double.NaN); } /** * get coefficient from simple linear expression with coefficient as divide * eg 3 * x + 1 * * returns Double.NaN if it's not in the correct form */ private static double getLinearCoefficient(FunctionVariable fv, ExpressionValue ev2) { // just x if (ev2 == fv) { return 1; } ExpressionValue ev = ev2; double factor = 1; Operation op; // 3x+1 or 1+3x or 3x-1 or 1-3x if (ev.isExpressionNode() && (op = ((ExpressionNode) ev).getOperation()) .isPlusorMinus()) { ExpressionNode en = (ExpressionNode) ev; if (en.left.isNumberValue() && !en.left.contains(fv)) { // strip off the "+1" etc ev = en.right; factor = op.equals(Operation.PLUS) ? 1 : -1; } else if (en.right.isNumberValue() && !en.right.contains(fv)) { // strip off the "+1" etc ev = en.left; factor = 1; } } // x+2 or 2-x if (ev == fv) { return factor; } // 3*x or x*3 if (ev.isExpressionNode() && ((ExpressionNode) ev).getOperation() .equals(Operation.MULTIPLY)) { ExpressionNode en = (ExpressionNode) ev; if (en.left == fv && en.right.isNumberValue() && !en.right.contains(fv)) { return ((NumberValue) en.right).getDouble() * factor; // return wrap(en.right).multiply(factor); } else if (en.right == fv && en.left.isNumberValue() && !en.left.contains(fv)) { return ((NumberValue) en.left).getDouble() * factor; // return wrap(en.left).multiply(factor); } } // not (simple) linear return Double.NaN; } /** * get coefficient from simple linear expression with coefficient as divide * eg x / 3 + 1 * * returns Double.NaN if it's not in the correct form */ private static double getLinearCoefficientDiv(FunctionVariable fv, ExpressionValue ev2) { ExpressionValue ev = ev2; double factor = 1; Operation op; // x/3+1 or 1+x/3 or x/3-1 or 1-x/3 if (ev.isExpressionNode() && (op = ((ExpressionNode) ev).getOperation()) .isPlusorMinus()) { ExpressionNode en = (ExpressionNode) ev; if (en.left.isNumberValue() && !en.left.contains(fv)) { // strip off the "+1" etc ev = en.right; factor = op.equals(Operation.PLUS) ? 1 : -1; } else if (en.right.isNumberValue() && !en.right.contains(fv)) { // strip off the "+1" etc ev = en.left; factor = 1; } } // x/3 if (ev.isExpressionNode() && ((ExpressionNode) ev).getOperation() .equals(Operation.DIVIDE)) { ExpressionNode en = (ExpressionNode) ev; if (en.left == fv && en.right.isNumberValue() && !en.right.contains(fv)) { return ((NumberValue) en.right).getDouble() * factor; } } // not (simple) linear return Double.NaN; } private ExpressionNode wrap(double n) { return new MyDouble(kernel, n).wrap(); } /** * @return whether the top-level operation is IF / IF_ELSE / IF_LIST */ public boolean isConditional() { return operation == Operation.IF || operation == Operation.IF_ELSE || operation == Operation.IF_LIST; } /** * @return whether this contains any conditional operations */ public boolean isConditionalDeep() { return isConditional() || (left instanceof ExpressionNode && ((ExpressionNode) left).isConditionalDeep()) || (right instanceof ExpressionNode && ((ExpressionNode) right).isConditionalDeep()); } /** * Builds an if-else expression based on this condition * * @param ifBranch * if branch * @param elseBranch * else branch * @return if-else expression */ public ExpressionNode ifElse(ExpressionValue ifBranch, ExpressionValue elseBranch) { return new ExpressionNode(kernel, new MyNumberPair(kernel, this, ifBranch), Operation.IF_ELSE, elseBranch); } @Override public double evaluateDouble() { if (isLeaf()) { return left.evaluateDouble(); } switch (operation) { case PLUS: return left.evaluateDouble() + right.evaluateDouble(); case MINUS: return left.evaluateDouble() - right.evaluateDouble(); case MULTIPLY: return evaluateMultiplyDouble(); case DIVIDE: return left.evaluateDouble() / right.evaluateDouble(); case POWER: return evaluatePowerDouble(); case SIN: return Math.sin(left.evaluateDouble()); case COS: return Math.cos(left.evaluateDouble()); case SQRT: return Math.sqrt(left.evaluateDouble()); default: break; } // TODO: evaluate basic operations here, but make sure errors are thrown // when necessary return super.evaluateDouble(); } private double evaluateMultiplyDouble() { double lt = left.evaluateDouble(); if (!Double.isNaN(lt)) { lt *= right.evaluateDouble(); } return !Double.isNaN(lt) ? lt : super.evaluateDouble(); } private double evaluatePowerDouble() { if (!left.evaluatesToNumber(false)) { return super.evaluateDouble(); } double lt = left.evaluateDouble(); if (lt < 0 && right.isExpressionNode() && ((ExpressionNode) right) .getOperation() == Operation.DIVIDE) { return ExpressionNodeEvaluator.negPower(lt, right); } return Math.pow(left.evaluateDouble(), right.evaluateDouble()); } /** * Replaces some CAS commands unknown to GeoGebra This may change the * structure of the ExpressionNode * * @return the expression node with replaced commands */ public ExpressionNode replaceCasCommands() { return this.traverse(Traversing.CASCommandReplacer.replacer).wrap(); } /** * @return whether this expression was wrapped in brackets when parsed */ public boolean hasBrackets() { return brackets; } /** * Set the brackets flag, should be called from parser * * @param brackets * whether this expression was wrapped in brackets when parsed */ public void setBrackets(boolean brackets) { this.brackets = brackets; } /** * Apply given unary operation on this node * * @param operation2 * operation * @return expression node operation2(this) */ public ExpressionNode apply(Operation operation2) { return new ExpressionNode(kernel, this, operation2, null); } /** * Apply given binary operation on this node and additional argument * * @param operation2 * operation * @param arg * second argument * @return expression node opertion2this,arg) */ public ExpressionNode apply(Operation operation2, ExpressionValue arg) { return new ExpressionNode(kernel, this, operation2, arg); } /** * @param name * variable name * @return whether this contains FVar that is not part of equation or list */ public boolean containsFreeFunctionVariable(String name) { return checkForFreeVars(left, name) || (right != null && checkForFreeVars(right, name)) || ((operation == Operation.FUNCTION_NVAR || operation == Operation.ELEMENT_OF) && right instanceof MyList && ((ValidExpression) right) .containsFunctionVariable(name)); } /** * @param vars * forbidden vars * @return whether one of the forbidden vars appers in expression node */ public boolean containsFreeFunctionVariableOtherThan( FunctionVariable[] vars) { return checkForFreeVars(left, vars) || (right != null && checkForFreeVars(right, vars)) || ((operation == Operation.FUNCTION_NVAR || operation == Operation.ELEMENT_OF) && right instanceof MyList && ((ValidExpression) right) .containsFunctionVariableOtherThan(vars)); } private boolean checkForFreeVars(ExpressionValue ev, FunctionVariable[] vars) { if (ev instanceof FunctionVariable) { return doesNotInclude(vars, ev); } if (ev instanceof ExpressionNode) { return ((ExpressionNode) ev) .containsFreeFunctionVariableOtherThan(vars); } if (ev instanceof MyVecNode) { return checkForFreeVars(((MyVecNode) ev).getX(), vars) || checkForFreeVars(((MyVecNode) ev).getY(), vars); } return false; } /** * @param vars * haystack * @param ev * needle (probably function variable, but not necessary to * check) * @return whther the array does NOT contain the variable */ public static boolean doesNotInclude(FunctionVariable[] vars, ExpressionValue ev) { for (int i = 0; i < vars.length; i++) { if (vars[i] == ev) { return false; } } return true; } private boolean checkForFreeVars(ExpressionValue ev, String name) { if (ev instanceof FunctionVariable) { return name == null || name.equals(((FunctionVariable) ev).getSetVarString()); } if (ev instanceof ExpressionNode) { return ((ExpressionNode) ev).containsFreeFunctionVariable(name); } if (ev instanceof MyVecNode) { return checkForFreeVars(((MyVecNode) ev).getX(), name) || checkForFreeVars(((MyVecNode) ev).getY(), name); } if (ev instanceof MyVec3DNode) { return checkForFreeVars(((MyVec3DNode) ev).getX(), name) || checkForFreeVars(((MyVec3DNode) ev).getY(), name); } return false; } /** * @return variables that must be defined in order for the result to be * defined eg. d+If[a>0,b,c] has unconditional variable d * * */ public HashSet<GeoElement> getUnconditionalVars() { // TODO Auto-generated method stub if (!this.isConditionalDeep()) { return null; } if (leaf) { return left.getVariables(); } if (isConditional()) { return new HashSet<GeoElement>(); } HashSet<GeoElement> leftVars = left.getVariables(); HashSet<GeoElement> rightVars = right.getVariables(); if (leftVars == null) { return rightVars; } else if (rightVars == null) { return leftVars; } else { leftVars.addAll(rightVars); return leftVars; } } private static boolean checkFraction(ExpressionValue[] parts, ExpressionValue lt, boolean expandPlus) { ExpressionValue left1 = lt == null ? null : lt.unwrap(); if (left1 instanceof ExpressionNode) { ((ExpressionNode) left1).getFraction(parts, expandPlus); return true; } else if (left1 instanceof GeoNumeric && ((GeoNumeric) left1).getDefinition() != null) { ((GeoElement) left1).getDefinition().getFraction(parts, expandPlus); return true; } return false; } /** * @param parts * output parameter * @param expandPlus * whether to expand a/b+c/d to (ad+bc)/bd */ public void getFraction(ExpressionValue[] parts, boolean expandPlus) { if (this.resolve != null && this.resolve.isExpressionNode()) { this.resolve.wrap().getFraction(parts, expandPlus); return; } ExpressionValue numL, numR, denL = null, denR = null; if (checkFraction(parts, left, expandPlus)) { numL = parts[0]; denL = parts[1]; } else { numL = left; } if (checkFraction(parts, right, expandPlus)) { numR = parts[0]; denR = parts[1]; } else { numR = right; } switch (operation) { case MULTIPLY: parts[0] = numL.wrap().multiply(numR); parts[1] = multiplyCheck(denR, denL); return; case DIVIDE: parts[0] = multiplyCheck(denR, numL); parts[1] = multiplyCheck(denL, numR); return; case POWER: parts[0] = powerCheck(numL, right); parts[1] = powerCheck(denL, right); return; case PLUS: if (expandPlus) { parts[0] = multiplyCheck(denR, numL).wrap() .plus(multiplyCheck(denL, numR)); parts[1] = multiplyCheck(denR, denL); return; } case MINUS: if (expandPlus) { parts[0] = multiplyCheck(denR, numL).wrap() .subtract(multiplyCheck(denL, numR)); parts[1] = multiplyCheck(denR, denL); return; } case FUNCTION: if (expandPlus && left instanceof Functional) { Function fn = ((Functional) left).getFunction(); if (fn != null) { ExpressionNode expCopy = fn.getExpression() .deepCopy(kernel); expCopy.replace(fn.getFunctionVariables()[0], denR == null ? numR : numR.wrap().divide(denR)); expCopy.getFraction(parts, expandPlus); return; } } default: parts[0] = this; parts[1] = null; return; } } private static ExpressionValue multiplyCheck(ExpressionValue denR, ExpressionValue denL) { return denL == null ? denR : (denR == null ? denL : denL.wrap().multiply(denR)); } private static ExpressionValue powerCheck(ExpressionValue base, ExpressionValue exp) { return exp == null ? base : (base == null ? null : base.wrap().power(exp)); } /** * Builds product of two expressions * * @param left * left factor * @param right * right factor * @param kernel * kernel * @param giacParsing * whether this is from GIAC * @return product of factors */ public static ExpressionValue multiplySpecial(ExpressionValue left, ExpressionValue right, Kernel kernel, boolean giacParsing) { String leftImg; App app = kernel.getApplication(); // sin x in GGB is function application if "sin" is not a variable if (left instanceof Variable) { leftImg = left.toString(StringTemplate.defaultTemplate); Operation op = app.getParserFunctions().get(leftImg, 1); if (op != null && kernel.lookupLabel(leftImg) == null && !"x".equals(leftImg) && !"y".equals(leftImg) && !"z".equals(leftImg)) { return new ExpressionNode(kernel, right, op, null); } if (leftImg.startsWith("log_") && kernel.lookupLabel(leftImg) == null) { ExpressionValue index = FunctionParser.getLogIndex(leftImg, kernel); if (index != null) { return new ExpressionNode(kernel, index, Operation.LOGB, right); } } // x * sin x in GGB is function applied on the right if "sin" is not // a variable } else if (left instanceof ExpressionNode && ((ExpressionNode) left).getOperation() == Operation.POWER && ((ExpressionNode) left).getLeft() instanceof Variable) { leftImg = ((ExpressionNode) left).getLeft() .toString(StringTemplate.defaultTemplate); Operation op = app.getParserFunctions().get(leftImg, 1); if (op != null && kernel.lookupLabel(leftImg) == null && !"x".equals(leftImg) && !"y".equals(leftImg) && !"z".equals(leftImg)) { ExpressionValue exponent = ((ExpressionNode) left).getRight() .unwrap(); if (exponent.isConstant() && Kernel.isEqual(-1, exponent.evaluateDouble())) { return kernel.inverseTrig(op, right); } return new ExpressionNode(kernel, right, op, null) .power(exponent); } // a * b * f -- check if b*f needs special handling } else if (left instanceof ExpressionNode && ((ExpressionNode) left) .getOperation() == Operation.MULTIPLY) { ExpressionValue bf = multiplySpecial( ((ExpressionNode) left).getRight(), right, kernel, giacParsing); return bf == null ? null : new ExpressionNode(kernel, ((ExpressionNode) left).getLeft(), Operation.MULTIPLY, bf); } if (giacParsing) { // (a)(b) in Giac is function application if (left instanceof Variable) { Command ret = new Command(kernel, left.toString(StringTemplate.defaultTemplate), true, true); ret.addArgument(right.wrap()); return ret; // c*(a)(b) in Giac: function applied on right subtree } } return null; } /** * @return true if the ExpressionNode is a GeoSegment on even power */ public boolean isSegmentSquare() { if (this.getLeft() instanceof GeoSegment && this.getOperation() == Operation.POWER && this.getRight() instanceof NumberValue) { double d = this.getRight().evaluateDouble(); if (Kernel.isInteger(d) && d % 2 == 0) { return true; } } return false; } /** * if expressionNode is GeoSegment * * @return geoSegment leaf */ public GeoSegment getGeoSegment() { if (this.isGeoElement() && this.left instanceof GeoSegment) { return (GeoSegment) this.left; } return null; } @Override public ValueType getValueType() { if (resolve == null) { resolve = computeResolve(); } return resolve.getValueType(); } @Override public int getListDepth() { if (resolve == null) { resolve = computeResolve(); } return resolve.getListDepth(); } /** * @return whether this expression has trigonometric operations */ public boolean has2piPeriodicOperations() { return this.inspect(new Inspecting() { @Override public boolean check(ExpressionValue v) { return v.isExpressionNode() && is2piPeriodicOperation( ((ExpressionNode) v).getOperation()); } private boolean is2piPeriodicOperation(Operation op) { return op == Operation.SIN || op == Operation.COS || op == Operation.TAN || op == Operation.COT || op == Operation.SEC || op == Operation.CSC; } }); } /** * Wraps in unary minus node, for constant MyDouble return just MyDouble * * @param kernel2 * kernel for result * @param f * value * @return -f */ public static ExpressionValue unaryMinus(Kernel kernel2, ExpressionValue f) { if (f instanceof MyDouble && f.isConstant() && !(f instanceof MySpecialDouble)) { return new MyDouble(kernel2, -f.evaluateDouble()); } return new ExpressionNode(kernel2, new MyDouble(kernel2, -1), Operation.MULTIPLY, f); } /** * @return faster but less accurate version of evaluatesToString: detects * only a+b+c as TextValue if one of a,b,c is TextValue */ public boolean isStringAddition() { if (getOperation() != Operation.PLUS) { return false; } if (left instanceof TextValue || right instanceof TextValue) { return true; } if (left instanceof ExpressionNode && ((ExpressionNode) left).isStringAddition()) { return true; } if (right instanceof ExpressionNode && ((ExpressionNode) right).isStringAddition()) { return true; } return false; } /** * GGB-605 set when expression shouldn't be displayed to the user eg * NDerivative * * @param algo * secret algo * * @return this */ public ExpressionNode setSecret(AlgoElement algo) { this.isSecret = algo; return this; } /** * GGB-605 * * @return true if expression shouldn't be displayed to the user */ public boolean isSecret() { return isSecret != null; } // collect factors of expression recursively private void collectFactors(ArrayList<ExpressionNode> factors) { if (!getOperation().equals(Operation.MULTIPLY)) { factors.add(deepCopy(kernel)); return; } if (left instanceof ExpressionNode) { ((ExpressionNode) left).collectFactors(factors); } else if (left != null) { factors.add(left.deepCopy(kernel).wrap()); } if (right instanceof ExpressionNode) { ((ExpressionNode) right).collectFactors(factors); } else if (right != null) { factors.add(right.deepCopy(kernel).wrap()); } } /** * @return list of factors without power */ public ArrayList<ExpressionNode> getFactorsWithoutPow() { ArrayList<ExpressionNode> factors = new ArrayList<ExpressionNode>(); collectFactors(factors); ArrayList<ExpressionNode> factorsWithoutPow = new ArrayList<ExpressionNode>( factors.size()); if (!factors.isEmpty()) { for (int i = 0; i < factors.size(); i++) { if (factors.get(i).getOperation().equals(Operation.POWER) && (factors.get(i).getRight() instanceof MyDouble || factors.get(i) .getRight() instanceof MySpecialDouble)) { factorsWithoutPow.add(factors.get(i).getLeftTree()); } else { factorsWithoutPow.add(factors.get(i)); } } } return factorsWithoutPow; } /** * @param tpl * template * @return this as fraction */ public String toFractionString(StringTemplate tpl) { initFraction(); return ((ExpressionNode) resolve).toFractionStringFlat(tpl); } /** * @return Whether this is a fraction (also true for 1/2+1/3) */ public boolean isFraction() { initFraction(); return ((ExpressionNode) resolve).getOperation() == Operation.DIVIDE; } private void initFraction() { if (resolve == null || !resolve.isExpressionNode()) { ExpressionValue[] fraction = new ExpressionValue[2]; getFraction(fraction, true); if (fraction[0] != null) { ExpressionValue ltVal = fraction[0] .evaluate(StringTemplate.defaultTemplate); double lt = ltVal.evaluateDouble(); boolean pi = false; double piDiv = lt / Math.PI; if (Kernel.isInteger(piDiv) && !Kernel.isZero(piDiv)) { lt = piDiv; pi = true; } double rt = 1; if (fraction[1] != null) { rt = fraction[1].evaluateDouble(); } else if (!pi) { // keep angle dimension resolve = ltVal.deepCopy(kernel).wrap(); return; } if (Kernel.isInteger(rt) && Kernel.isInteger(lt) && !Kernel.isZero(rt) && Math.abs(lt) < 1E15 && Math.abs(rt) < 1E15) { double g = Math .abs(Kernel.gcd(Math.round(lt), Math.round(rt))) * Math.signum(rt); lt = lt / g; rt = rt / g; resolve = (pi ? new ExpressionNode(kernel, new MyDouble(kernel, lt), Operation.MULTIPLY, new MyDouble(kernel, Math.PI)) : new ExpressionNode(kernel, lt)).divide(rt); } else { resolve = new ExpressionNode(kernel, pi ? Math.PI * lt / rt : lt / rt); } } else { resolve = evaluate(StringTemplate.defaultTemplate).wrap(); } } } private String toFractionStringFlat(StringTemplate tpl) { if (operation == Operation.MULTIPLY && right instanceof MyDouble && MyDouble.exactEqual(right.evaluateDouble(), Math.PI)) { return tpl.multiplyString(left, right, left.toValueString(tpl), right.toValueString(tpl), true, loc); } if (operation == Operation.DIVIDE) { return tpl.divideString(left, right, left.toValueString(tpl), right.toValueString(tpl), true); } return toValueString(tpl); } /** * @return true if it evals to true, false if it's false or invalid */ public boolean evaluateBoolean() { ExpressionValue ev = evaluate(StringTemplate.defaultTemplate); return ev instanceof BooleanValue && ((BooleanValue) ev).getBoolean(); } /** * Check whether denominator and numerator are both independent integers * * @return whether is a simple fraction like 7/2 or -1/2 */ public boolean isSimpleFraction() { if (operation == Operation.DIVIDE) { ExpressionValue leftUnsigned = left.unwrap(); if (left.isExpressionNode() && getLeftTree().getOperation() == Operation.MULTIPLY && ExpressionNode.isConstantDouble(getLeftTree().getLeft(), -1)) { leftUnsigned = getLeftTree().getRight(); } if (leftUnsigned instanceof MyDouble && right.unwrap() instanceof MyDouble) { double lt = left.evaluateDouble(); double rt = right.evaluateDouble(); if (Kernel.isInteger(lt) && Kernel.isInteger(rt)) { return true; } } } return false; } }