/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ package org.geogebra.common.kernel.geos; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.TreeMap; import java.util.TreeSet; import org.apache.commons.math3.analysis.UnivariateFunction; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.MyPoint; import org.geogebra.common.kernel.PathMover; import org.geogebra.common.kernel.PathMoverGeneric; import org.geogebra.common.kernel.PathParameter; import org.geogebra.common.kernel.Region; import org.geogebra.common.kernel.RegionParameters; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.VarString; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.Matrix.Coords3; import org.geogebra.common.kernel.algos.AlgoDistancePointObject; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.AlgoFunctionFreehand; import org.geogebra.common.kernel.algos.AlgoMacroInterface; import org.geogebra.common.kernel.arithmetic.BooleanValue; import org.geogebra.common.kernel.arithmetic.Evaluate2Var; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants.StringType; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.Function; import org.geogebra.common.kernel.arithmetic.FunctionNVar; import org.geogebra.common.kernel.arithmetic.FunctionVariable; import org.geogebra.common.kernel.arithmetic.Functional; import org.geogebra.common.kernel.arithmetic.FunctionalNVar; import org.geogebra.common.kernel.arithmetic.IneqTree; import org.geogebra.common.kernel.arithmetic.MyArbitraryConstant; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.MyList; import org.geogebra.common.kernel.arithmetic.MyNumberPair; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.arithmetic.PolyFunction; import org.geogebra.common.kernel.arithmetic.ValueType; import org.geogebra.common.kernel.cas.AlgoDerivative; import org.geogebra.common.kernel.commands.EvalInfo; import org.geogebra.common.kernel.implicit.GeoImplicit; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.kernelND.SurfaceEvaluable; import org.geogebra.common.kernel.roots.RealRootUtil; import org.geogebra.common.main.error.ErrorHelper; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Explicit function in one variable ("x"). This is actually a wrapper class for * Function in geogebra.kernel.arithmetic. In arithmetic trees (ExpressionNode) * it evaluates to a Function. * * @author Markus Hohenwarter */ public class GeoFunction extends GeoElement implements VarString, Translateable, Functional, FunctionalNVar, GeoFunctionable, Region, CasEvaluableFunction, ParametricCurve, UnivariateFunction, Dilateable, Transformable, InequalityProperties, SurfaceEvaluable, GeoLocusable { /** inner function representation */ protected Function fun; /** true if this function should be considered defined */ protected boolean isDefined = true; private boolean trace; // if the function includes a division by var, e.g. 1/x, 1/(2+x) private boolean includesDivisionByVar = false; private boolean includesNonContinuousIntegral = false; private boolean includesFreehandOrDataFunction = false; /** function may be limited to interval [a, b] */ protected boolean interval = false; /** lower interval bound */ protected double intervalMin; /** upper interval bound */ protected double intervalMax; private boolean evalSwapped; // parent conditional function // private GeoFunctionConditional parentCondFun = null; private Boolean isInequality = null; /** * Creates new function * * @param c * construction */ public GeoFunction(Construction c) { this(c, true); } /** * @param c * construction * @param setDefaults * true to set defaults */ public GeoFunction(Construction c, boolean setDefaults) { super(c); if (setDefaults) { setConstructionDefaults(); // init visual settings } surfaceEvaluables = new TreeSet<SurfaceEvaluable>(); } /** * @param autoLabel * whether label was set by * @return whether function contains only valid variables */ public boolean validate(boolean autoLabel) { return validate(autoLabel, cons.isSuppressLabelsActive()); } /** * @param autoLabel * whether label was set by * @param suppressLabel * whether labels are suppressed (parsing command argument) * @return whether function contains only valid variables */ public boolean validate(boolean autoLabel, boolean suppressLabel) { if (!cons.isFileLoading() && fun != null) { if (getFunctionExpression().containsFreeFunctionVariableOtherThan( getFunctionVariables())) { return false; } } // If labels are suppressed (processing command arguments) accept y and // z as // functions if (suppressLabel || isBooleanFunction()) { return true; } if ((this.isFunctionOfY() // needed for GGB-1028 && this.getCorrespondingCasCell() == null) || (autoLabel && this.isFunctionOfZ())) { return false; } return true; } /** * Creates new function * * @param c * construction * @param f * function */ public GeoFunction(Construction c, Function f) { this(c, f, true); } /** * @param c * construction * @param f * function * @param simplifyInt * whether integer subexperessions should be simplified */ public GeoFunction(Construction c, Function f, boolean simplifyInt) { super(c); surfaceEvaluables = new TreeSet<SurfaceEvaluable>(); fun = f; // setConstructionDefaults is called from initFunction initFunction(simplifyInt); } /** implicit poly for composite function */ GeoImplicit iPoly; /** substitute functions for composite function */ GeoFunction[] substituteFunctions; // Currently, the composite function is only for internal use // The expression is not correct but it is not to be shown anyway. /** * Creates composite function iPoly(f(x), g(x)) * * @param c * construction * @param iPoly * polynomial * @param f * function for x * @param g * function for y */ public GeoFunction(Construction c, GeoImplicit iPoly, GeoFunction f, GeoFunction g) { this(c); this.iPoly = iPoly; substituteFunctions = new GeoFunction[2]; substituteFunctions[0] = f; substituteFunctions[1] = g; if (f == null && g != null) { setInterval(g.intervalMin, g.intervalMax); fun = new Function(c.getKernel()) { @Override public double value(double x) { return GeoFunction.this.iPoly.evaluateImplicitCurve(x, substituteFunctions[1].getFunction().value(x)); } }; /* * Iterator it = iPoly.poly.getVariables().iterator(); * ExpressionNode iPolyEN = * (ExpressionNode)iPoly.poly.deepCopy(kernel); ExpressionNode gEN = * (ExpressionNode)g.getFunctionExpression().deepCopy(kernel); * ExpressionValue varX = null; ExpressionValue varY = null; * ExpressionValue vargX = null; vargX = * g.getFunction().getFunctionVariable(); if (it.hasNext()) varX = * (ExpressionValue)it.next(); if (it.hasNext()) varY = * (ExpressionValue)it.next(); * * if (vargX!= null && varX !=null && varY !=null) { * * ExpressionNode dummyX = new ExpressionNode(); gEN = * gEN.replaceAndWrap(g.getFunction().getFunctionVariable(), * dummyX); iPolyEN = iPolyEN.replaceAndWrap(varY, gEN); gEN = * gEN.replaceAndWrap(dummyX, vargX); * * } */ // } // g.getFunction().getFunctionVariable(); // TODO: set the correct expression fun.setExpression(new ExpressionNode(kernel, new GeoNumeric(c, 0))); } else if (f != null && g == null) { setInterval(f.intervalMin, f.intervalMax); fun = new Function(c.getKernel()) { @Override public double value(double x) { return GeoFunction.this.iPoly.evaluateImplicitCurve( substituteFunctions[0].getFunction().value(x), x); } }; // TODO: set the correct expression fun.setExpression(new ExpressionNode(kernel, new GeoNumeric(c, 0))); } else if (f != null && g != null) { setInterval(Math.max(f.intervalMin, g.intervalMin), Math.min(f.intervalMax, g.intervalMax)); fun = new Function(c.getKernel()) { @Override public double value(double x) { return GeoFunction.this.iPoly.evaluateImplicitCurve( substituteFunctions[0].getFunction().value(x), substituteFunctions[1].getFunction().value(x)); } }; // TODO: set the correct expression fun.setExpression(new ExpressionNode(kernel, new GeoNumeric(c, 0))); } // else: error } @Override public void setVisualStyle(GeoElement g) { super.setVisualStyle(g); if (g instanceof GeoFunction) { setShowOnAxis(((GeoFunction) g).showOnAxis); } } @Override public String getTypeString() { return (isInequality != null && isInequality) ? "Inequality" : "Function"; } @Override public GeoClass getGeoClassType() { return GeoClass.FUNCTION; } /** * copy constructor * * @param f * Function to be copied */ public GeoFunction(GeoFunction f) { this(f.cons); set(f); } /** * @param en * expression * @param fv * variable */ public GeoFunction(ExpressionNode en, FunctionVariable fv) { this(en.getKernel().getConstruction(), new Function(en, fv)); } @Override public GeoElement copy() { return new GeoFunction(this); } @Override public void set(GeoElementND geo) { Function geoFun = geo == null ? null : ((GeoFunctionable) geo).getGeoFunction().getFunction(); if (geoFun == null) { fun = null; isDefined = false; return; } isDefined = geo.isDefined(); setFunction(new Function(geoFun, kernel)); // macro OUTPUT if (geo.getConstruction() != cons && isAlgoMacroOutput()) { // this object is an output object of AlgoMacro // we need to check the references to all geos in its function's // expression if (!geo.isIndependent()) { AlgoMacroInterface algoMacro = (AlgoMacroInterface) getParentAlgorithm(); algoMacro.initFunction(this.fun); } } isInequality = null; } /** * Sets the inner function * * @param f * function */ public void setFunction(Function f) { fun = f; // reset this for garbage collection, also the flag needs update for // #5054 this.includesNonContinuousIntegralFun = null; this.includesFreehandOrDataFun = null; for (SurfaceEvaluable geo : surfaceEvaluables) { geo.resetDerivatives(); } } private TreeSet<SurfaceEvaluable> surfaceEvaluables; @Override public boolean addToUpdateSets(final AlgoElement algorithm) { final boolean added = super.addToUpdateSets(algorithm); if (added) { // store surfaces to reset derivatives if needed for (int i = 0; i < algorithm.getOutputLength(); i++) { GeoElement geo = algorithm.getOutput(i); if (geo instanceof SurfaceEvaluable) { surfaceEvaluables.add((SurfaceEvaluable) geo); } } } return added; } @Override public boolean removeFromUpdateSets(final AlgoElement algorithm) { final boolean removed = super.removeFromUpdateSets(algorithm); if (removed) { // store surfaces to reset derivatives if needed for (int i = 0; i < algorithm.getOutputLength(); i++) { GeoElement geo = algorithm.getOutput(i); if (geo instanceof SurfaceEvaluable) { surfaceEvaluables.remove(geo); } } } return removed; } /** * initializes function type; if boolean, uses default styl for inequalities * * @param simplifyInt * whether integer subexperessions should be simplified */ public void initFunction(boolean simplifyInt) { fun.initFunction(simplifyInt); // apply inequality style when suitable setConstructionDefaults(); } @Override public Function getFunction() { return fun; } /** * Sets interval for the function * * @param a * lower bound * @param b * upper bound * @return true if the resulting interval is non-empty */ final public boolean setInterval(double a, double b) { if (a <= b) { interval = true; this.intervalMin = a; this.intervalMax = b; } else { interval = false; } return interval; } /** * Returns function expression * * @return function expression */ @Override final public ExpressionNode getFunctionExpression() { if (getFunction() == null) { return null; } return getFunction().getExpression(); } /** * Replaces geo and all its dependent geos in this function's expression by * copies of their values. * * @param geo * geo to be replaced */ @Override public void replaceChildrenByValues(GeoElement geo) { if (fun != null) { fun.replaceChildrenByValues(geo); } } /** * Returns the corresponding Function for the given x-value. This is * important for conditional functions where we have two differen Function * objects. * * @param x * x-value * @return coresponding function */ public Function getFunction(double x) { return fun; } /** * Set this function to the n-th derivative of f * * @param fd * function to be differenced * @param n * order of derivative * @param fast * true -> non-CAS derivative */ public void setDerivative(CasEvaluableFunction fd, int n, boolean fast) { GeoFunction f = (GeoFunction) fd; if (f.isDefined()) { fun = f.fun.getDerivative(n, fast); checkDefined(); } else { isDefined = false; } } /** * #4076 make sure if CAS returns "?" function is undefined so eg * Integral[f,a,b] uses numerical method */ private void checkDefined() { isDefined = fun != null; if (fun != null && "?" .equals(fun.toValueString(StringTemplate.defaultTemplate))) { isDefined = false; } } /** * Sets this function by applying a GeoGebraCAS command to a function. * * @param ggbCasCmd * the GeoGebraCAS command needs to include % in all places where * the function f should be substituted, e.g. "Derivative(%,x)" * @param f * the function that the CAS command is applied to */ @Override public void setUsingCasCommand(String ggbCasCmd, CasEvaluableFunction f, boolean symbolic, MyArbitraryConstant arbconst) { GeoFunction ff = (GeoFunction) f; if (ff.isDefined()) { fun = (Function) ff.fun.evalCasCommand(ggbCasCmd, symbolic, arbconst); checkDefined(); } else { isDefined = false; } } /** * Returns this function's value at position x. * * @param x * point for evaluation * @return f(x) */ @Override public double value(double x) { if (fun == null || !isDefined) { return Double.NaN; } /* * if (geoFunctionType == FUNCTION_COMPOSITE_IPOLY_FUNCS) { double evalX * = substituteFunctions[0].evaluate(x); double evalY = * substituteFunctions[1].evaluate(x); return iPoly.evalPolyAt(evalX, * evalY); } else */ return fun.value(x); } /** * Returns this function's value at position x. * * @param vals * array of length 1 containing x * @return f(val[0]) or f(val[1]) */ @Override public double evaluate(double[] vals) { return value(vals[0]); } /** * If restricted to interval, returns its minimum * * @return interval minimum */ public final double getIntervalMin() { return intervalMin; } /** * If restricted to interval, returns its maximum * * @return interval maximum */ public final double getIntervalMax() { return intervalMax; } /** * Iff restricted to interval, returns true * * @return true iff restricted to interval */ public final boolean hasInterval() { return interval; } /** * Returns this boolean function's value at position x. * * @param x * point for evaluation * @return f(x) */ final public boolean evaluateBoolean(double x) { if (fun == null || !isDefined) { return false; } return fun.evaluateBoolean(x); } @Override public GeoFunction getGeoDerivative(int order, boolean fast) { if (derivGeoFun == null) { derivGeoFun = new GeoFunction(cons); } // if (getParentAlgorithm() instanceof AlgoFunctionFreehand) // derivGeoFun.setUndefined(); // else derivGeoFun.setDerivative(this, order, fast); if (!kernel.getApplication().getSettings().getCasSettings() .isEnabled()) { derivGeoFun.setSecret( new AlgoDerivative(cons, this, true, new EvalInfo(false))); } return derivGeoFun; } private GeoFunction derivGeoFun; /** * translate function by vector v */ @Override final public void translate(Coords v) { translate(v.getX(), v.getY()); } @Override final public boolean isTranslateable() { return fun != null && !isBooleanFunction(); } /** * Shifts the function by vx to right and by vy up * * @param vx * horizontal shift * @param vy * vertical shift */ public void translate(double vx, double vy) { if (getParentAlgorithm() instanceof AlgoFunctionFreehand) { AlgoFunctionFreehand algo = (AlgoFunctionFreehand) getParentAlgorithm(); GeoList list = algo.getList(); // left/right boundaries ((GeoNumeric) list.get(0)) .setValue(((GeoNumeric) list.get(0)).getDouble() + vx); ((GeoNumeric) list.get(1)) .setValue(((GeoNumeric) list.get(1)).getDouble() + vx); // heights for (int i = 2; i < list.size(); i++) { ((GeoNumeric) list.get(i)) .setValue(((GeoNumeric) list.get(i)).getDouble() + vy); } algo.compute(); } else { fun.translate(vx, vy); } } /** * Returns true if this function is a polynomial. * * @return true if this function is a polynomial. * @param forRootFinding * set to true if you want to allow functions that can be * factored into polynomial factors for root finding (e.g. * sqrt(x) could be replaced by x) * @param symbolic * function's symbolic expression must be a polynomial, e.g. x^2 * is ok but not x^a */ public boolean isPolynomialFunction(boolean forRootFinding, boolean symbolic) { // don't do root finding simplification here // i.e. don't replace a factor "sqrt(x)" by "x" if (!isDefined()) { return false; } return fun.isPolynomialFunction(forRootFinding, symbolic); } /** * Returns true if this function is a polynomial. * * @return true if this function is a polynomial. * @param forRootFinding * set to true if you want to allow functions that can be * factored into polynomial factors for root finding (e.g. * sqrt(x) could be replaced by x) */ public boolean isPolynomialFunction(boolean forRootFinding) { return isPolynomialFunction(forRootFinding, false); } /** * Returns whether this function includes a division by variable, e.g. f(x) * = 1/x, 1/(2+x), sin(3/x), ... * * @return true iff this function includes a division by variable */ final public boolean includesDivisionByVar() { if (includesDivisionByVarFun != fun) { includesDivisionByVarFun = fun; includesDivisionByVar = fun != null && fun.includesDivisionByVariable(); } return includesDivisionByVar; } /** * Returns whether this function includes eg abs(), If[] function * * @return true iff this function includes abs(), If[] etc */ final public boolean includesFreehandOrData() { if (includesFreehandOrDataFun != fun) { includesFreehandOrDataFun = fun; includesFreehandOrDataFunction = fun != null && fun.includesFreehandOrDataFunction(); } return includesFreehandOrDataFunction; } /** * Returns whether this function includes eg Freehand, DataFunction * functions * * @return true iff this function includes abs(), If[] etc */ final public boolean includesNonContinuousIntegral() { if (includesNonContinuousIntegralFun != fun) { includesNonContinuousIntegralFun = fun; includesNonContinuousIntegral = fun != null && fun.includesNonContinuousIntegral(); } return includesNonContinuousIntegral; } private Function includesFreehandOrDataFun = null; private Function includesNonContinuousIntegralFun = null; private Function includesDivisionByVarFun = null; @Override public boolean isDefined() { return isDefined && fun != null; } @Override public boolean isFillable() { if (fun != null && isInequality == null && isBooleanFunction()) { getIneqs(); } return isInequality != null && isInequality; } @Override public boolean isInverseFillable() { return isFillable(); } /** * Changes the defined state * * @param defined * true iff the function should be considered defined */ @Override public void setDefined(boolean defined) { isDefined = defined; } @Override public void setUndefined() { isDefined = false; } @Override public boolean showInAlgebraView() { return true; } @Override protected boolean showInEuclidianView() { if (fun != null && isInequality == null && isBooleanFunction()) { getIneqs(); } return isDefined() && (!isBooleanFunction() || isInequality); } /** * @return function description as f(x)=... for real and e.g. f:x>4 for bool */ @Override public String toString(StringTemplate tpl) { sbToString.setLength(0); initStringBuilder(sbToString, tpl, label, getVarString(tpl), isLabelSet(), isBooleanFunction()); sbToString.append(toValueString(tpl)); return sbToString.toString(); } /** * @param stringBuilder * string builder * @param tpl * string template * @param label * geo label * @param var * geo var * @param isLabelSet * says if label is set * @param isBooleanFunction * says if it's a boolean function */ public final static void initStringBuilder(StringBuilder stringBuilder, StringTemplate tpl, String label, String var, boolean isLabelSet, boolean isBooleanFunction) { stringBuilder.setLength(0); if (isLabelSet) { stringBuilder.append(label); if (isBooleanFunction && !tpl.hasType(StringType.GEOGEBRA_XML)) { stringBuilder.append(": "); } else { stringBuilder.append(tpl.leftBracket()); stringBuilder.append(var); stringBuilder.append(tpl.rightBracket()); stringBuilder.append(" = "); } } } /** StringBuilder for temporary string manipulation */ protected StringBuilder sbToString = new StringBuilder(80); private boolean showOnAxis; @Override public String toValueString(StringTemplate tpl) { // make sure Freehand Functions have different entries in drop-down // lists if (this.isFreehandFunction()) { return this.getLabel(tpl) + tpl.leftBracket() + fun.getFunctionVariable() + tpl.rightBracket(); } if (fun != null && isDefined()) { return fun.toValueString(tpl); } return "?"; } /* * (non-Javadoc) * * @see geogebra.kernel.GeoElement#toOutputValueString() needed for eg * KeepIf[x!="h",{"h","k","o"}] */ @Override public String toOutputValueString(StringTemplate tpl) { if (isLocalVariable()) { return label; } if (fun != null && isDefined()) { return fun.toOutputValueString(tpl); } return "?"; } @Override public String toSymbolicString(StringTemplate tpl) { if (fun != null && isDefined()) { return fun.toString(tpl); } return "?"; } @Override public String toLaTeXString(boolean symbolic, StringTemplate tpl) { if (fun != null && isDefined()) { return fun.toLaTeXString(symbolic, tpl); } return "?"; } /** * save object in xml format */ @Override public final void getXML(boolean getListenersToo, StringBuilder sbxml) { // an independent function needs to add // its expression itself // e.g. f(x) = x^2 - 3x if (isIndependent() && getDefaultGeoType() < 0) { sbxml.append("<expression"); sbxml.append(" label =\""); sbxml.append(label); sbxml.append("\" exp=\""); StringUtil.encodeXML(sbxml, toString(StringTemplate.xmlTemplate)); // expression sbxml.append("\"/>\n"); } sbxml.append("<element"); sbxml.append(" type=\"function\""); sbxml.append(" label=\""); sbxml.append(label); if (getDefaultGeoType() >= 0) { sbxml.append("\" default=\""); sbxml.append(getDefaultGeoType()); } sbxml.append("\">\n"); getXMLtags(sbxml); getCaptionXML(sbxml); printCASEvalMapXML(sbxml); if (getListenersToo) { getListenerTagsXML(sbxml); } sbxml.append("</element>\n"); } /** * returns all class-specific xml tags for getXML */ @Override protected void getXMLtags(StringBuilder sbxml) { super.getXMLtags(sbxml); // line thickness and type getLineStyleXML(sbxml); if (showOnAxis()) { sbxml.append("<showOnAxis val=\"true\" />"); } } /** * we don't care about values of these */ final private static String[] dummy1 = { "", "" }; /** * we don't care about values of these */ final private static char[] dummy2 = { ' ', ' ' }; private double[] bounds; /** * * assumes function in form If[ interval, simple function] * * @param bounds0 * contains {min, max} on exit */ public void getInterval(double[] bounds0) { bounds0[0] = Double.NEGATIVE_INFINITY; bounds0[1] = Double.POSITIVE_INFINITY; double bound; ExpressionNode inequalityEn = (ExpressionNode) getFunctionExpression() .getLeft(); Operation op = inequalityEn.getOperation(); switch (op) { default: Log.error("problem in GeoFunction.getInterval()"); return; case AND_INTERVAL: GeoInterval.updateBoundaries(inequalityEn, bounds0, GeoFunction.dummy1, GeoFunction.dummy2); break; case LESS: case LESS_EQUAL: case GREATER: case GREATER_EQUAL: // make sure 2<x and x>2 both work if (inequalityEn.getLeft() instanceof FunctionVariable) { bound = inequalityEn.getRight().evaluateDouble(); } else if (inequalityEn.getRight() instanceof FunctionVariable) { bound = inequalityEn.getLeft().evaluateDouble(); op = op.reverseLeftToRight(); } else { // shouldn't happen bound = Double.NaN; } switch (op) { case LESS: case LESS_EQUAL: bounds0[1] = bound; break; case GREATER: case GREATER_EQUAL: bounds0[0] = bound; break; default: break; } } } /* * Path interface */ @SuppressFBWarnings({ "SF_SWITCH_FALLTHROUGH", "missing break is deliberate" }) private void pointChanged(Coords P, boolean closestPoly) { if (P.getZ() == 1.0) { // P.x = P.x; } else { P.setX(P.getX() / P.getZ()); } if (!isBooleanFunction()) { if (interval) { // don't let P move out of interval if (P.getX() < intervalMin) { P.setX(intervalMin); } else if (P.getX() > intervalMax) { P.setX(intervalMax); } } else { ExpressionNode exp = fun.getExpression(); // make sure point can't be dragged to undefined region for eg // If[3 <= x <= 5, x^2] if (exp.getOperation().equals(Operation.IF)) { ExpressionValue inequality = exp.getLeft().unwrap(); if (inequality.isExpressionNode()) { double bound; double epsilon = 0; ExpressionNode inequalityEn = (ExpressionNode) inequality; Operation op = inequalityEn.getOperation(); switch (op) { case AND_INTERVAL: if (bounds == null) { bounds = new double[2]; } GeoInterval.updateBoundaries(inequalityEn, bounds, dummy1, dummy2); if (P.getX() < bounds[0]) { P.setX(bounds[0]); } else if (P.getX() > bounds[1]) { P.setX(bounds[1]); } break; case LESS: case LESS_EQUAL: case GREATER: case GREATER_EQUAL: // make sure 2<x and x>2 both work if (inequalityEn .getLeft() instanceof FunctionVariable) { bound = inequalityEn.getRight() .evaluateDouble(); } else if (inequalityEn .getRight() instanceof FunctionVariable) { bound = inequalityEn.getLeft().evaluateDouble(); op = op.reverseLeftToRight(); } else { // shouldn't happen bound = Double.NaN; } switch (op) { case LESS: epsilon = Kernel.MIN_PRECISION; // fall through case LESS_EQUAL: if (P.getX() >= bound) { P.setX(bound - epsilon); } break; case GREATER: epsilon = Kernel.MIN_PRECISION; // fall through case GREATER_EQUAL: if (P.getX() < bound) { P.setX(bound + epsilon); } break; default: break; } default: break; } } } } PolyFunction polyFunction = closestPoly ? fun.expandToPolyFunction(fun.getExpression(), false, true) : null; if (polyFunction != null) { double val = AlgoDistancePointObject.closestValPoly( polyFunction, P.getX(), P.getY(), kernel); P.setX(val); P.setY(value(val)); } else { P.setY(value(P.getX())); } } else { pointChangedBoolean(true, P); } P.setZ(1.0); } @Override public void pointChanged(GeoPointND P) { pointChanged(P, true); } private void pointChanged(GeoPointND P, boolean closestPoly) { Coords coords = P.getCoordsInD2(); pointChanged(coords, closestPoly); // set path parameter for compatibility with // PathMoverGeneric P.setCoords2D(coords.getX(), coords.getY(), coords.getZ()); PathParameter pp = P.getPathParameter(); pp.t = coords.getX();// P.getX(); P.updateCoordsFrom2D(false, null); } private void pointChangedBoolean(boolean b, Coords P) { double px; boolean yfun = isFunctionOfY(); if (yfun) { if (b) { P.setX(0.0); } px = P.getY(); } else { if (b) { P.setY(0.0); } px = P.getX(); } double bestDist = Double.MAX_VALUE; getIneqs(); if (!this.evaluateBoolean(px)) { IneqTree ineqs = fun.getIneqs(); int ineqCount = ineqs.getSize(); for (int i = 0; i < ineqCount; i++) { for (GeoPoint point : ineqs.get(i).getZeros()) { if (Math.abs(point.getX() - px) < bestDist) { bestDist = Math.abs(point.getX() - px); if (yfun) { P.setY(point.getX()); } else { P.setX(point.getX()); } } } } } } @Override public boolean isOnPath(GeoPointND PI, double eps) { GeoPoint P = (GeoPoint) PI; if (P.getPath() == this) { return true; } if (!isBooleanFunction()) { return isDefined && Math .abs(fun.value(P.getInhomX()) - P.getInhomY()) <= eps; } double px = isFunctionOfY() ? P.getY() : P.getX(); if (P.getZ() != 1.0) { px = px / P.getZ(); } return evaluateBoolean(px); } @Override public void pathChanged(GeoPointND PI) { // if kernel doesn't use path/region parameters, do as if point changed // its coords if (!getKernel().usePathAndRegionParameters(PI)) { pointChanged(PI); return; } GeoPoint P = (GeoPoint) PI; PathParameter pp = P.getPathParameter(); P.setX(pp.t); pointChanged(P, false); } @Override public boolean isPath() { return true; } /** * Returns the smallest possible parameter value for this path (may be * Double.NEGATIVE_INFINITY) see TRAC-3 * * @version 2010-05-14 * @return smallest possible parameter value (may be * Double.NEGATIVE_INFINITY) */ @Override public double getMinParameter() { if (interval) { return Math.max(kernel.getViewsXMin(this), intervalMin); } return kernel.getViewsXMin(this); } /** * Returns the largest possible parameter value for this path (may be * Double.POSITIVE_INFINITY) see TRAC-3 * * @version 2010-05-14 * @return largest possible parameter value (may be * Double.POSITIVE_INFINITY) */ @Override public double getMaxParameter() { if (interval) { return Math.min(kernel.getViewsXMax(this), intervalMax); } return kernel.getViewsXMax(this); } @Override public PathMover createPathMover() { return new PathMoverGeneric(this); } @Override public boolean isClosedPath() { return false; } @Override final public boolean isCasEvaluableObject() { return true; } @Override public boolean isNumberValue() { return false; } @Override public boolean isTraceable() { return true; } @Override public boolean getTrace() { return trace; } @Override public void setTrace(boolean trace) { this.trace = trace; } // G.Sturr 2010-5-18 get/set spreadsheet trace not needed here /* * public void setSpreadsheetTrace(boolean spreadsheetTrace) { * this.spreadsheetTrace = spreadsheetTrace; } * * public boolean getSpreadsheetTrace() { return spreadsheetTrace; } */ @Override public GeoFunction getGeoFunction() { return this; } @Override public boolean isGeoFunction() { if (fun != null) { return !fun.isBooleanFunction(); } return true; } @Override public String getAssignmentLHS(StringTemplate tpl) { sbToString.setLength(0); sbToString.append(tpl.printVariableName(label)); sbToString.append(tpl.leftBracket()); sbToString.append(getVarString(tpl)); sbToString.append(tpl.rightBracket()); return sbToString.toString(); } @Override public boolean isGeoFunctionable() { return isGeoFunction(); } @Override public boolean isBooleanFunction() { if (fun != null) { return fun.isBooleanFunction(); } return false; } /** * changes variable interpretation: if swapped, the function is considered * to be x=f(y). */ public void swapEval() { evalSwapped = !evalSwapped; } @Override public void evaluateCurve(double t, double[] out) { if (evalSwapped) { out[1] = t; out[0] = value(t); } else { out[0] = t; out[1] = value(t); } } /** * Evaluates curvature for function: k(x) = f''/T^3, T = sqrt(1+(f')^2) * * @author Victor Franco Espino, Markus Hohenwarter */ @Override public double evaluateCurvature(double x) { if (fun == null) { return Double.NaN; } Function f1 = fun.getDerivative(1, true); Function f2 = fun.getDerivative(2, true); if (f1 == null || f2 == null) { return Double.NaN; } double f1eval = f1.value(x); double t = Math.sqrt(1 + f1eval * f1eval); double t3 = t * t * t; return f2.value(x) / t3; } @Override final public UnivariateFunction getUnivariateFunctionX() { return new UnivariateFunction() { @Override public double value(double t) { return t; } }; } @Override final public UnivariateFunction getUnivariateFunctionY() { return new UnivariateFunction() { @Override public double value(double t) { return GeoFunction.this.value(t); } }; } @Override public GeoVec2D evaluateCurve(double t) { return new GeoVec2D(kernel, t, value(t)); } @Override public String getVarString(StringTemplate tpl) { if (fun == null) { return tpl.printVariableName("x"); } return fun.getVarString(tpl); } @Override final public boolean isFunctionInX() { return true; } /* * public final GeoFunctionConditional getParentCondFun() { return * parentCondFun; } * * public final void setParentCondFun(GeoFunctionConditional parentCondFun) * { this.parentCondFun = parentCondFun; } */ // Michael Borcherds 2009-02-15 @Override public boolean isEqual(GeoElementND geo) { // support c==f for Line, Function if (geo.isGeoLine()) { return ((GeoLine) geo).isEqual(this); } if (!geo.isGeoFunction() || geo.getGeoClassType().equals(GeoClass.INTERVAL)) { return false; } GeoFunction geoFun = (GeoFunction) geo; if (differAt(this, geoFun, 0) || differAt(this, geoFun, 1)) { return false; } PolyFunction poly1 = getFunction() .expandToPolyFunction(getFunctionExpression(), false, true); if (poly1 != null) { PolyFunction poly2 = geoFun.getFunction().expandToPolyFunction( geoFun.getFunctionExpression(), false, true); if (poly2 != null) { return poly1.isEqual(poly2); } } // if poly1 and/or poly2 are null they /could/ still be equal // polynomials (or equal non-polynomials) // eg x^2 + 0*sin(x) == x^2 // so check with CAS (SLOW) return isDifferenceZeroInCAS(geo); } private static boolean differAt(GeoFunction f1, GeoFunction f2, double x) { double v1 = f1.value(x); double v2 = f2.value(x); if (!MyDouble.isFinite(v2) || Math.abs(v1) > 1E8) { return false; } if (!MyDouble.isFinite(v2) || Math.abs(v2) > 1E8) { return false; } return !Kernel.isEqual(v1, v2, Kernel.MIN_PRECISION); } /** * Sums two functions and stores the result to another * * @param resultFun * resulting function * @param fun1 * first addend * @param fun2 * second addend * @param op * operation * @return resultFun */ public static GeoFunction add(GeoFunction resultFun, GeoFunction fun1, GeoFunction fun2, Operation op) { Kernel kernel = fun1.getKernel(); FunctionVariable x1 = fun1.getFunction().getFunctionVariable(); FunctionVariable x2 = fun2.getFunction().getFunctionVariable(); FunctionVariable x = new FunctionVariable(kernel); ExpressionNode left = fun1.getFunctionExpression().getCopy(kernel); ExpressionNode right = fun2.getFunctionExpression().getCopy(kernel); ExpressionNode sum = new ExpressionNode(fun1.getKernel(), left.replace(x1, x), op, right.replace(x2, x)); Function f = new Function(sum, x); resultFun.setFunction(f); resultFun.setDefined(true); return resultFun; } /** * Applies an operation on first and second function and returns the result * * @param op * operation * @param lt * left argument of op * @param rt * right argument of op * @return resulting GeoFunction or GeFunctionNvar */ public static FunctionNVar operationSymb(Operation op, FunctionalNVar lt, FunctionalNVar rt) { Kernel kernel = lt.getFunction().getKernel(); TreeSet<String> varNames = new TreeSet<String>(); for (int i = 0; i < lt.getFunction().getVarNumber(); i++) { varNames.add(lt.getFunction().getVarString(i, StringTemplate.defaultTemplate)); } for (int i = 0; i < rt.getFunction().getVarNumber(); i++) { varNames.add(rt.getFunction().getVarString(i, StringTemplate.defaultTemplate)); } HashMap<String, FunctionVariable> varmap = new HashMap<String, FunctionVariable>(); for (String name : varNames) { varmap.put(name, new FunctionVariable(kernel, name)); } ExpressionNode ltExpr = toExpr(lt, varmap, kernel), rtExpr = toExpr(rt, varmap, kernel), sum = new ExpressionNode(kernel, ltExpr, op, rtExpr); FunctionNVar f = fromExpr(sum, varmap, varNames); f.initFunction(); return f; // AlgoDependentFunction adf = new // AlgoDependentFunction(fun1.getConstruction(),f); // return adf.getFunction(); } private static FunctionNVar fromExpr(ExpressionNode sum, HashMap<String, FunctionVariable> varmap, TreeSet<String> varNames) { int size = varmap.size(); if (size > 1) { FunctionVariable[] varArray = new FunctionVariable[size]; int i = 0; for (String name : varNames) { varArray[i] = varmap.get(name); i++; } FunctionNVar f = new FunctionNVar(sum, varArray); return f; // AlgoDependentFunctionNVar adf = new // AlgoDependentFunctionNVar(fun1.getConstruction(),null,f); // return adf.getFunction(); } Iterator<FunctionVariable> var = varmap.values().iterator(); return new Function(sum, var.next()); } private static ExpressionNode toExpr(Evaluate2Var lt, HashMap<String, FunctionVariable> varMap, Kernel kernel) { if (lt instanceof GeoFunction) { return new ExpressionNode(kernel, lt, Operation.FUNCTION, varMap .get(lt.getVarString(StringTemplate.defaultTemplate))); } if (lt instanceof GeoFunctionNVar) { MyList varList = new MyList(kernel); for (int i = 0; i < lt.getFunction().getVarNumber(); i++) { varList.addListElement(varMap.get(lt.getFunction() .getVarString(i, StringTemplate.defaultTemplate))); } return new ExpressionNode(kernel, lt, Operation.FUNCTION_NVAR, varList); } if (lt instanceof GeoNumeric) { return lt.wrap(); } if (lt instanceof FunctionNVar) { ExpressionNode ret = ((FunctionNVar) lt).getExpression(); for (int i = 0; i < ((FunctionNVar) lt).getVarNumber(); i++) { ret = ret .replace(((FunctionNVar) lt).getFunctionVariables()[i], varMap.get(((FunctionNVar) lt).getVarString(i, StringTemplate.defaultTemplate))) .wrap(); } return ret; } return null; } /** * Applies an operation on this function and number value * * @param op * operation * @param fun1 * function on which we want to apply this op * @param ev * value to apply * @param right * f op nv for true, nv op f for false * @return resulting function */ public static FunctionNVar applyNumberSymb(Operation op, Evaluate2Var fun1, ExpressionValue ev, boolean right) { ExpressionValue nv = ev; Kernel kernel = fun1.getFunction().getKernel(); TreeSet<String> varNames = new TreeSet<String>(); for (int i = 0; i < fun1.getFunction().getVarNumber(); i++) { varNames.add(fun1.getFunction().getVarString(i, StringTemplate.defaultTemplate)); } HashMap<String, FunctionVariable> varmap = new HashMap<String, FunctionVariable>(); for (String name : varNames) { varmap.put(name, new FunctionVariable(kernel, name)); } ExpressionNode sum, myExpr; myExpr = toExpr(fun1, varmap, kernel); if (nv instanceof ExpressionNode) { for (String name : varNames) { ((ExpressionNode) nv).replaceVariables(name, varmap.get(name)); } } else if (nv instanceof FunctionVariable) { nv = varmap.get(((FunctionVariable) nv) .toString(StringTemplate.defaultTemplate)); } if (right) { sum = new ExpressionNode(kernel, myExpr, op, nv); } else { sum = new ExpressionNode(kernel, nv, op, myExpr); } FunctionNVar f = fromExpr(sum, varmap, varNames); f.initFunction(); return f; } /** * Subtracts two functions and stores the result to another * * @param resultFun * resulting function * @param fun1 * minuend * @param fun2 * subtrahend * @return resultFun */ public static GeoFunction subtract(GeoFunction resultFun, GeoFunction fun1, GeoFunction fun2) { Kernel kernel = fun1.getKernel(); FunctionVariable x1 = fun1.getFunction().getFunctionVariable(); FunctionVariable x2 = fun2.getFunction().getFunctionVariable(); FunctionVariable x = new FunctionVariable(kernel); ExpressionNode left = fun1.getFunctionExpression().getCopy(kernel); ExpressionNode right = fun2.getFunctionExpression().getCopy(kernel); ExpressionNode sum = new ExpressionNode(fun1.getKernel(), left.replace(x1, x), Operation.MINUS, right.replace(x2, x)); Function f = new Function(sum, x); resultFun.setFunction(f); resultFun.setDefined(true); return resultFun; } /** * Multiplication of number and function. Needed in Fit[<List of * Points>,<List of Functions>] to make the result a linear combination of * existing functions; fit(x)=a*f(x)+b*g(x)+c*h(x)+.. * * @author Hans-Petter Ulven * @version 2010-02-22 * @param resultFun * Resulting function * @param number * number * @param fun * function * @return resultFun * */ public static GeoFunction mult(GeoFunction resultFun, double number, GeoFunction fun) { Kernel kernel = fun.getKernel(); MyDouble num = new MyDouble(kernel, number); FunctionVariable xold = fun.getFunction().getFunctionVariable(); FunctionVariable x = new FunctionVariable(kernel); ExpressionNode left = new ExpressionNode(kernel, num); ExpressionNode right = fun.getFunctionExpression().getCopy(kernel); ExpressionNode product = new ExpressionNode(kernel, left, Operation.MULTIPLY, right.replace(xold, x).wrap()); Function f = new Function(product, x); resultFun.setFunction(f); resultFun.setDefined(true); return resultFun; } /** * Returns true iff x is in the interval over-ridden in * GeoFunctionConditional * * @param x * point for evaluation * @return true iff x is in the interval */ public boolean evaluateCondition(double x) { if (!interval) { return true; } return x > intervalMin && x < intervalMax; } /** * Returns the limit * * @param x * point to evaluate the limit * @param direction * 1 for limit above, -1 for limit below, standard limit * otherwise * @return the limit */ public String getLimit(double x, int direction) { // get function and function variable string using temp variable // prefixes, // e.g. f(x) = a x^2 returns {"ggbtmpvara ggbtmpvarx^2", "ggbtmpvarx"} String[] funVarStr = getTempVarCASString(false); if (sb == null) { sb = new StringBuilder(); } else { sb.setLength(0); } sb.setLength(0); sb.append("Numeric(Limit"); if (direction == -1) { sb.append("Above"); } else if (direction == 1) { sb.append("Below"); } sb.append('('); sb.append(funVarStr[0]); // function expression sb.append(','); sb.append(funVarStr[1]); // function variable sb.append(','); sb.append(MyDouble.toString(x)); sb.append("),"); // increase precision to improve problems like // http://www.geogebra.org/trac/ticket/1106 sb.append("50)"); return sb.toString(); } /** * Adds vertical asymptotes to the StringBuilder over-ridden in * GeoFunctionConditional * * @param f * function whose asymptotes we are looking for * @param verticalSB * StringBuilder for the result * @param reverse * if true, we reverse the parent conditional function condition */ public void getVerticalAsymptotes(GeoFunction f, StringBuilder verticalSB, boolean reverse) { getVerticalAsymptotesStatic(this, f, verticalSB, reverse); } /** * Adds horizontal positive asymptotes to the StringBuilder over-ridden in * GeoFunctionConditional * * @param f * function whose asymptotes we are looking for * @param SB * StringBuilder for the result */ public void getHorizontalPositiveAsymptote(GeoFunction f, StringBuilder SB) { getHorizontalAsymptoteStatic(this, f, SB, true); } /** * Adds horizontal negative asymptotes to the StringBuilder over-ridden in * GeoFunctionConditional * * @param f * function whose asymptotes we are looking for * @param SB * StringBuilder for the result */ public void getHorizontalNegativeAsymptote(GeoFunction f, StringBuilder SB) { getHorizontalAsymptoteStatic(this, f, SB, false); } /** * Adds diagonal positive asymptotes to the StringBuilder over-ridden in * GeoFunctionConditional * * @param f * function whose asymptotes we are looking for * @param SB * StringBuilder for the result */ public void getDiagonalPositiveAsymptote(GeoFunction f, StringBuilder SB) { getDiagonalAsymptoteStatic(this, f, SB, true); } /** * Adds diagonal negative asymptotes to the StringBuilder over-ridden in * GeoFunctionConditional * * @param f * function whose asymptotes we are looking for * @param SB * StringBuilder for the result */ public void getDiagonalNegativeAsymptote(GeoFunction f, StringBuilder SB) { getDiagonalAsymptoteStatic(this, f, SB, false); } private static StringBuilder sb; /** * Adds diagonal asymptotes to the string builder SB * * @param f * function whose asymptotes we are looking for * @param parentFunction * parent function (in case of conditional functions) * @param SB * StringBuilder for the result * @param positiveInfinity * if true, we look for limit at positive infinity, for false, we * use negative infinity */ protected void getDiagonalAsymptoteStatic(GeoFunction f, GeoFunction parentFunction, StringBuilder SB, boolean positiveInfinity) { try { // get first derivative GeoFunction deriv = f.getGeoDerivative(1, false); // get function and function variable string using temp variable // prefixes, // e.g. f(x) = a x^2 returns {"ggbtmpvara ggbtmpvarx^2", // "ggbtmpvarx"} String[] derivVarStr = deriv.getTempVarCASString(false); String[] funVarStr = f.getTempVarCASString(false); if (sb == null) { sb = new StringBuilder(); } else { sb.setLength(0); } String gradientStrMinus = ""; String interceptStrMinus = ""; sb.setLength(0); sb.append("Limit("); sb.append(derivVarStr[0]); // derivative expression sb.append(','); sb.append(derivVarStr[1]); // derivative function variable sb.append(','); if (!positiveInfinity) { sb.append('-'); // -Infinity } sb.append(Unicode.INFINITY); sb.append(')'); gradientStrMinus = kernel.evaluateCachedGeoGebraCAS(sb.toString(), null); // Application.debug(sb.toString()+" = "+gradientStrMinus,1); double grad; try { grad = kernel.getAlgebraProcessor() .evaluateToDouble(gradientStrMinus, true, null); } catch (Exception e) { grad = 0; } if (!GeoFunction.CASError(gradientStrMinus, false) && !Kernel.isZero(grad)) { sb.setLength(0); sb.append("Limit("); sb.append(funVarStr[0]); // function expression sb.append(" - "); sb.append(gradientStrMinus); sb.append(" * "); sb.append(derivVarStr[1]); // derivative function variable sb.append(','); sb.append(derivVarStr[1]); // derivative function variable sb.append(','); if (!positiveInfinity) { sb.append('-'); // -Infinity } sb.append(Unicode.INFINITY); sb.append(')'); interceptStrMinus = kernel .evaluateCachedGeoGebraCAS(sb.toString(), null); // Application.debug(sb.toString()+" = "+interceptStrMinus,1); if (!GeoFunction.CASError(interceptStrMinus, false)) { sb.setLength(0); sb.append("y = "); sb.append(gradientStrMinus); sb.append(" * x +"); sb.append(interceptStrMinus); if (!SB.toString().endsWith(sb.toString())) { // not // duplicated if (SB.length() > 1) { SB.append(','); } SB.append(sb); // Application.debug("diagonal asymptote minus: y = // "+gradientStrMinus+"x + "+interceptStrMinus,1); } } } } catch (Throwable e) { e.printStackTrace(); } } /** * Adds horizontal asymptotes to the string builder SB * * @param f * function whose asymptotes we are looking for * @param parentFunction * parent function (in case of conditional functions) * @param SB * StringBuilder for the result * @param positiveInfinity * if true, we look for limit at positive infinity, for false, we * use negative infinity */ protected void getHorizontalAsymptoteStatic(GeoFunction f, GeoFunction parentFunction, StringBuilder SB, boolean positiveInfinity) { // get function and function variable string using temp variable // prefixes, // e.g. f(x) = a x^2 returns {"ggbtmpvara ggbtmpvarx^2", "ggbtmpvarx"} String[] funVarStr = f.getTempVarCASString(false); if (sb == null) { sb = new StringBuilder(); } else { sb.setLength(0); } sb.append("Limit("); sb.append(funVarStr[0]); // function expression sb.append(','); sb.append(funVarStr[1]); // function variable sb.append(','); if (!positiveInfinity) { sb.append('-'); // -Infinity } sb.append(Unicode.INFINITY); sb.append(")"); try { String limit = kernel.evaluateCachedGeoGebraCAS(sb.toString(), null) .trim(); // Application.debug(sb.toString()+" = "+limit,1); if (!GeoFunction.CASError(limit, false)) { // check not duplicated sb.setLength(0); sb.append("y="); sb.append(limit); if (!SB.toString().endsWith(sb.toString())) { // not duplicated if (SB.length() > 1) { SB.append(','); } SB.append(sb); } } } catch (Throwable t) { // nothing to do } } @Override public char getLabelDelimiter() { return isBooleanFunction() ? ':' : '='; } /** * Adds vertical asymptotes to the string builder VerticalSB * * @param f * function whose asymptotes we are looking for * @param parentFunction * parent function (in case of conditional functions) * @param verticalSB * StringBuilder for the result * @param reverseCondition * if true, we reverse the parent conditional function condition */ protected void getVerticalAsymptotesStatic(GeoFunction f, GeoFunction parentFunction, StringBuilder verticalSB, boolean reverseCondition) { // get function and function variable string using temp variable // prefixes, // e.g. f(x) = a x^2 returns {"ggbtmpvara ggbtmpvarx^2", "ggbtmpvarx"} StringTemplate tpl = StringTemplate.prefixedDefault; ExpressionNode exp = getFunctionExpression(); exp = getSubexpForAsymptote(exp); String[] funVarStr = { exp.getCASstring(tpl, false), getVarString(tpl) }; // solve 1/f(x) == 0 to find vertical asymptotes if (sb == null) { sb = new StringBuilder(); } else { sb.setLength(0); } sb.append("Solve("); // if (kernel.getCASType() == CasType.GIAC) { // Solve(1/(1/x)) "works" in Reduce but not in Giac sb.append("Simplify("); // } sb.append("1/("); sb.append(funVarStr[0]); // function expression with "ggbtmpvarx" as // function variable // if (kernel.getCASType() == CasType.GIAC) { sb.append(')'); // } sb.append(")=0"); sb.append(","); sb.append(funVarStr[1]); // function variable "ggbtmpvarx" sb.append(")"); try { String verticalAsymptotes = kernel .evaluateCachedGeoGebraCAS(sb.toString(), null); // Application.debug(sb.toString()+" = "+verticalAsymptotes,1); if (!GeoFunction.CASError(verticalAsymptotes, false) && verticalAsymptotes.length() > 2) { verticalAsymptotes = verticalAsymptotes.replace('{', ' '); verticalAsymptotes = verticalAsymptotes.replace('}', ' '); // verticalAsymptotes = verticalAsymptotes.replace('(',' '); // // eg (-1) // verticalAsymptotes = verticalAsymptotes.replace(')',' '); verticalAsymptotes = verticalAsymptotes.replaceAll("x==", ""); verticalAsymptotes = verticalAsymptotes.replaceAll("x =", ""); String[] verticalAsymptotesArray = verticalAsymptotes .split(","); // check they are really asymptotes for (int i = 0; i < verticalAsymptotesArray.length; i++) { // Application.debug(verticalAsymptotesArray[i]); boolean repeat = false; if (i > 0 && verticalAsymptotesArray.length > 1) { // check // for // repeats for (int j = 0; j < i; j++) { if (verticalAsymptotesArray[i] .equals(verticalAsymptotesArray[j])) { repeat = true; break; } } } boolean isInRange = false; try { // Application.debug(verticalAsymptotesArray[i]+""); if (verticalAsymptotesArray[i].trim().equals("")) { isInRange = false; // was complex root // isInRange = // parentFunction.evaluateCondition(StringUtil.parseDouble(verticalAsymptotesArray[i])); } else { isInRange = parentFunction.evaluateCondition(kernel .getAlgebraProcessor() .evaluateToNumeric( verticalAsymptotesArray[i], ErrorHelper.silent()) .getDouble()); } } catch (Exception e) { Log.warn( "Error parsing: " + verticalAsymptotesArray[i]); } if (reverseCondition) { isInRange = !isInRange; } if (!repeat && isInRange) { sb.setLength(0); sb.append("Numeric(Limit("); sb.append(funVarStr[0]); // function expression with // "ggbtmpvarx" as function // variable sb.append(','); sb.append(funVarStr[1]); // function variable // "ggbtmpvarx" sb.append(","); sb.append(verticalAsymptotesArray[i]); sb.append("))"); try { String limit = kernel.evaluateCachedGeoGebraCAS( sb.toString(), null); // Application.debug("checking for vertical // asymptote: "+sb.toString()+" = "+limit,1); if ("?".equals(limit) || !GeoFunction.CASError(limit, true)) { if (verticalSB.length() > 1) { verticalSB.append(','); } verticalSB.append("x="); verticalSB.append(verticalAsymptotesArray[i]); } } catch (Throwable e) { e.printStackTrace(); } } } } } catch (Throwable t) { t.printStackTrace(); } } private ExpressionNode getSubexpForAsymptote(ExpressionNode exp) { if (exp.getOperation() == Operation.EXP) { return getSubexpForAsymptote(exp.getLeftTree()); } else if (exp.getOperation() == Operation.PLUS || exp.getOperation() == Operation.MINUS) { if (!exp.getLeftTree().containsFreeFunctionVariable(null)) { return getSubexpForAsymptote(exp.getRightTree()); } if (!exp.getRightTree().containsFreeFunctionVariable(null)) { return getSubexpForAsymptote(exp.getLeftTree()); } } else if (exp.getOperation() == Operation.POWER && !exp.getLeftTree().containsFreeFunctionVariable(null)) { return getSubexpForAsymptote(exp.getRightTree()); } return exp; } final private static boolean CASError(String str, boolean allowInfinity) { String str1 = str; if (str1 == null || str1.length() == 0) { return true; } if ("?".equals(str1)) { return true; // undefined/NaN } // if (str.indexOf("%i") > -1 ) return true; // complex answer str1 = StringUtil.toLowerCase(str1); if (str1.charAt(0) == '\'') { return true; // maxima error eg 'diff( } if (!allowInfinity && str1.indexOf(Unicode.INFINITY) > -1) { return true; } if (str1.length() > 6) { if (str1.startsWith("limit")) { return true; } if (str1.startsWith("solve")) { return true; } if (str1.startsWith("undefined")) { return true; // if (!allowInfinity && str.indexOf("Infinity") > -1) return true; } } return false; } /** * Returns this function in currently set CAS print form. For example, * "a*x^2" */ @Override public String getCASString(StringTemplate tpl, boolean symbolic) { return fun.getExpression().getCASstring(tpl, symbolic); } /** * Returns geo and its function variable in currently set CAS print form * unsing temp variable prefixes. For example, f(x) = a x^2 returns { * "ggbtmpvara ggbtmpvarx^2", "ggbtmpvarx"} * * @param symbolic * true to keep variable names * @return {function string,var string} */ final public String[] getTempVarCASString(boolean symbolic) { StringTemplate tpl = StringTemplate.prefixedDefault; String[] ret = { getCASString(tpl, symbolic), getVarString(tpl) }; return ret; } /** * Converts this function to cartesian curve and stores result to given * curve * * @param curve * Curve to be stored to */ public void toGeoCurveCartesian(GeoCurveCartesian curve) { FunctionVariable t = new FunctionVariable(kernel, "t"); FunctionVariable x = fun.getFunctionVariable(); ExpressionNode yExp = (ExpressionNode) getFunction().getExpression() .deepCopy(kernel).replace(x, t); curve.setFunctionY(new Function(yExp, t)); Function varFun = new Function(new ExpressionNode(kernel, t), t); curve.setFunctionX(varFun); if (this.hasInterval()) { curve.setInterval(intervalMin, intervalMax); } else { double min = kernel.getXminForFunctions(); double max = kernel.getXmaxForFunctions(); curve.setInterval(min, max); curve.setHideRangeInFormula(true); } } /** * Creates a copy of this function with different function variable so that * both functions can be evaluated in separate threads * * @return copy of this function */ public GeoFunction threadSafeCopy() { if (fun == null) { return this; } FunctionVariable t = new FunctionVariable(kernel, "t"); FunctionVariable x = getFunction().getFunctionVariable(); ExpressionNode yExp = (ExpressionNode) getFunction().getExpression() .deepCopy(kernel).replace(x, t); return yExp.buildFunction(t); } /** * mirror at point P * * @param P * point */ final public void mirror(Coords P) { dilate(new MyDouble(kernel, -1.0), P); } @Override public void dilate(NumberValue r, Coords S) { double rd = r.getNumber().getDouble(), a = S.getX(), b = S.getY(); if (Kernel.isZero(rd)) { setUndefined(); return; } if (fun != null) { FunctionVariable oldX = fun.getFunctionVariable(); ExpressionNode newX = new ExpressionNode(kernel, new MyDouble(kernel, 1 / rd), Operation.MULTIPLY, new ExpressionNode(kernel, oldX, Operation.PLUS, new MyDouble(kernel, a * rd - a))); ExpressionNode oldY = fun.getExpression().replace(oldX, newX) .wrap(); if (!isBooleanFunction()) { fun.setExpression(new ExpressionNode(kernel, new ExpressionNode(kernel, oldY, Operation.MULTIPLY, r), Operation.PLUS, new MyDouble(kernel, -b * rd + b))); } else { fun.setExpression(oldY); } } } /* * gets shortest distance to point p for compound paths (returns *vertical* * distance for functions) */ @Override public double distance(GeoPoint p) { return Math.abs(value(p.getInhomX()) - p.getInhomY()); } @Override public boolean isInRegion(GeoPointND P) { return isInRegion(P.getX2D(), P.getY2D()); } @Override public boolean isInRegion(double x0, double y0) { if (isFunctionOfY()) { return evaluateBoolean(y0); } return evaluateBoolean(x0); } /** * @return true for functions of y (in 4.2 supported for ineqs only) */ public boolean isFunctionOfY() { return getVarString(StringTemplate.defaultTemplate).equals("y"); } /** * @return tru for functuins of z */ public boolean isFunctionOfZ() { return getVarString(StringTemplate.defaultTemplate).equals("z"); } @Override public void pointChangedForRegion(GeoPointND PI) { Coords P = PI.getCoordsInD2(); if (P.getZ() == 1.0) { // P.x = P.x; } else { P.setX(P.getX() / P.getZ()); } pointChangedBoolean(false, P); P.setZ(1.0); PI.setCoords2D(P.getX(), P.getY(), P.getZ()); // set path parameter for compatibility with // PathMoverGeneric RegionParameters pp = PI.getRegionParameters(); pp.setT1(P.getX()); pp.setT2(P.getY()); PI.updateCoordsFrom2D(false, null); } @Override public boolean isRegion() { return isBooleanFunction(); } @Override public void regionChanged(GeoPointND P) { pointChangedForRegion(P); } /** * Reset all inequalities (slow, involves parser) */ public void resetIneqs() { isInequality = fun.initIneqs(getFunctionExpression(), this); } @Override public IneqTree getIneqs() { if (fun.getIneqs() == null) { isInequality = fun.initIneqs(fun.getExpression(), this); } else if (isInequality == null) { isInequality = fun.getIneqs().isValid(); } return fun.getIneqs(); } /** * For inequalities. * * @return true iff should be drawn on x-Axis only */ @Override public boolean showOnAxis() { return showOnAxis; } /** * For inequalities. * * @param showOnAxis * true iff should be drawn on x-Axis only */ @Override public void setShowOnAxis(boolean showOnAxis) { this.showOnAxis = showOnAxis; } @Override public void update(boolean dragging) { updateGeo(!cons.isUpdateConstructionRunning(), dragging); kernel.notifyUpdate(this); } @Override public boolean isGeoFunctionBoolean() { return isBooleanFunction(); } @Override public boolean isLaTeXDrawableGeo() { return true; } @Override public boolean isGeoFunctionConditional() { ExpressionNode en = getFunctionExpression(); if (en == null) { return false; } return en.isConditional(); } @Override public String getFormulaString(StringTemplate tpl, boolean substituteNumbers) { String ret = ""; if (getFunctionExpression() != null && getFunctionExpression().isConditional()) { if (tpl.hasType(StringType.LATEX)) { ret = conditionalLaTeX(substituteNumbers, tpl); } } else if (this.isGeoFunction()) { if (isIndependent()) { ret = toValueString(tpl); } else { if (getFunction() == null) { ret = "?"; } else { ret = substituteNumbers ? getFunction().toValueString(tpl) : getFunction().toString(tpl); } } } else { return super.getFormulaString(tpl, substituteNumbers); } if ("".equals(ret)) { // eg Text[ (1,2), false] ret = toOutputValueString(tpl); } /* * we don't want to deal with list braces in here since * GeoList.toOutputValueString() takes care of it */ if (tpl.hasType(StringType.LATEX)) { if ("?".equals(ret)) { ret = "?"; } else if ((Unicode.INFINITY + "").equals(ret)) { ret = "\\infty"; } else if ((Unicode.MINUS_INFINITY + "").equals(ret)) { ret = "-\\infty"; } } return ret; } @Override public int getMinimumLineThickness() { return (isInequality != null && isInequality) ? 0 : 1; } /** * @return whether this function is inequality (more precisely logical * combination of inequalities) */ public boolean isInequality() { return (isInequality != null && isInequality) ? true : false; } /** * @return whether this function was obtained via freehand tool/command */ public boolean isFreehandFunction() { return getParentAlgorithm() instanceof AlgoFunctionFreehand; } /** * @return whether this function is from DataFunction[] */ public boolean isDataFunction() { return fun.getExpression().getOperation() == Operation.DATA; } @Override public FunctionVariable[] getFunctionVariables() { if (fun == null) { return null; } return fun.getFunctionVariables(); } @Override public void clearCasEvalMap(String key) { if (fun != null) { fun.clearCasEvalMap(key); } } /** * @param substituteNumbers * true to replace names by values * @param tpl * string template * @return LaTeX description of this function */ public String conditionalLaTeX(boolean substituteNumbers, StringTemplate tpl) { StringBuilder sbLaTeX = new StringBuilder(); ExpressionNode expr = getFunctionExpression(); if (expr.getOperation() == Operation.IF && !expr.getRight().wrap().isConditional()) { if (substituteNumbers) { sbLaTeX.append(expr.getRight() .toValueString(StringTemplate.latexTemplate)); sbLaTeX.append(", \\;\\;\\;\\; \\left("); sbLaTeX.append(expr.getLeft() .toValueString(StringTemplate.latexTemplate)); } else { sbLaTeX.append( expr.getRight().toString(StringTemplate.latexTemplate)); sbLaTeX.append(", \\;\\;\\;\\; \\left("); sbLaTeX.append( expr.getLeft().toString(StringTemplate.latexTemplate)); } sbLaTeX.append(" \\right)"); } else { ArrayList<ExpressionNode> cases = new ArrayList<ExpressionNode>(); ArrayList<Bounds> conditions = new ArrayList<Bounds>(); boolean complete = collectCases(expr, cases, conditions, new Bounds()); { int lastValid = conditions.size() - 1; while (lastValid >= 0 && !conditions.get(lastValid).isValid()) { lastValid--; } int firstValid = 0; while (firstValid < conditions.size() && !conditions.get(firstValid).isValid()) { firstValid++; } if (firstValid > lastValid) { sbLaTeX.append('?'); return sbLaTeX.toString(); } if (firstValid == lastValid) { sbLaTeX.append(cases.get(firstValid) .toLaTeXString(!substituteNumbers, tpl)); if (!complete) { sbLaTeX.append(", \\;\\;\\;\\; \\left("); sbLaTeX.append(conditions.get(firstValid).toLaTeXString( !substituteNumbers, getVarString(tpl), tpl)); sbLaTeX.append(" \\right)"); } return sbLaTeX.toString(); } sbLaTeX.append("\\left\\{\\begin{array}{ll} "); for (int i = firstValid; i <= lastValid; i++) { if (conditions.get(i).isValid()) { sbLaTeX.append(cases.get(i) .toLaTeXString(!substituteNumbers, tpl)); sbLaTeX.append("& : "); if (i == cases.size() - 1 && complete) { sbLaTeX.append("\\text{"); sbLaTeX.append(getLoc().getPlain("otherwise")); sbLaTeX.append("}"); } else { sbLaTeX.append(conditions.get(i).toLaTeXString( !substituteNumbers, getVarString(tpl), tpl)); if (i != lastValid) { sbLaTeX.append("\\\\ "); } } } } sbLaTeX.append(" \\end{array}\\right. "); } } return sbLaTeX.toString(); } private boolean collectCases(ExpressionNode condRoot, ArrayList<ExpressionNode> cases, ArrayList<Bounds> conditions, Bounds parentCond) { if (condRoot.getOperation() == Operation.IF_LIST) { MyList conds = (MyList) condRoot.getLeft().unwrap(); for (int i = 0; i < conds.size(); i++) { conditions.add(parentCond .addRestriction(conds.getListElement(i).wrap())); } MyList fns = (MyList) condRoot.getRight().unwrap(); for (int i = 0; i < fns.size(); i++) { cases.add(fns.getListElement(i).wrap()); } if (fns.size() > conds.size()) { conditions.add(parentCond); } return fns.size() > conds.size(); } boolean complete = condRoot.getOperation() == Operation.IF_ELSE; ExpressionNode condFun = complete ? ((MyNumberPair) condRoot.getLeft()).getX().wrap() : condRoot.getLeft().wrap(); ExpressionNode ifFun = complete ? ((MyNumberPair) condRoot.getLeft()).getY().wrap() : condRoot.getRight().wrap(); ExpressionNode elseFun = complete ? condRoot.getRight().wrap() : null; Bounds positiveCond = parentCond.addRestriction(condFun); Bounds negativeCond = !positiveCond.isValid() ? parentCond : parentCond.addRestriction(condFun.negation()); if (ifFun.isConditional()) { complete &= collectCases(ifFun, cases, conditions, positiveCond); } else { cases.add(ifFun); conditions.add(positiveCond); } if (elseFun != null && elseFun.isConditional()) { complete &= collectCases(elseFun, cases, conditions, negativeCond); } else if (elseFun != null) { cases.add(elseFun); conditions.add(negativeCond); } return complete; } /** * Container for condition tripples (upper bound, lower bound, other * conditions) * * @author Zbynek * */ class Bounds { private boolean lowerSharp, upperSharp; private Double lower, upper; private ExpressionNode condition; /** * Adds restrictions from the expression to current bounds * * @param e * expression * @return new bounds */ public Bounds addRestriction(ExpressionNode e) { if (e.getOperation().equals(Operation.AND) || e.getOperation().equals(Operation.AND_INTERVAL)) { return addRestriction(e.getLeftTree()) .addRestriction(e.getRightTree()); } Bounds b = new Bounds(); b.lower = lower; b.upper = upper; b.lowerSharp = lowerSharp; b.upperSharp = upperSharp; b.condition = condition;// If[x==1,1,If[x==2,3,4]] ExpressionValue lt = e.getLeft().unwrap(); ExpressionValue rt = e.getRight() == null ? null : e.getRight().unwrap(); boolean simple = e.getOperation() == Operation.GREATER || e.getOperation() == Operation.GREATER_EQUAL || e.getOperation() == Operation.LESS || e.getOperation() == Operation.LESS_EQUAL || e.getOperation() == Operation.EQUAL_BOOLEAN; if (simple && lt instanceof FunctionVariable && rt instanceof NumberValue && !(rt instanceof FunctionVariable)) { double d = ((NumberValue) rt).getDouble(); if (e.getOperation() == Operation.GREATER && (lower == null || lower <= d))// x > d { b.lower = d; b.lowerSharp = true; } else if ((e.getOperation() == Operation.GREATER_EQUAL || e.getOperation() == Operation.EQUAL_BOOLEAN) && (lower == null || lower < d))// x > d { b.lower = d; b.lowerSharp = false; } else if (e.getOperation() == Operation.LESS && (upper == null || upper >= d))// x > d { b.upper = d; b.upperSharp = true; } if ((e.getOperation() == Operation.LESS_EQUAL || e.getOperation() == Operation.EQUAL_BOOLEAN) && (upper == null || upper > d))// x > d { b.upper = d; b.upperSharp = false; } } else if (simple && rt instanceof FunctionVariable && lt instanceof NumberValue && !(lt instanceof FunctionVariable)) { double d = ((NumberValue) lt).getDouble(); if (e.getOperation() == Operation.LESS && (lower == null || lower <= d))// x > d { b.lower = d; b.lowerSharp = true; } else if ((e.getOperation() == Operation.LESS_EQUAL || e.getOperation() == Operation.EQUAL_BOOLEAN) && (lower == null || lower < d))// x > d { b.lower = d; b.lowerSharp = false; } else if (e.getOperation() == Operation.GREATER && (upper == null || upper >= d))// x > d { b.upper = d; b.upperSharp = true; } if ((e.getOperation() == Operation.GREATER_EQUAL || e.getOperation() == Operation.EQUAL_BOOLEAN) && (upper == null || upper > d))// x > d { b.upper = d; b.upperSharp = false; } } else { if (condition == null) { b.condition = e; } else { b.condition = condition.and(e); } } // If[x==1,2,If[x==3,4,5]] if (b.upper != null && b.lower != null && (b.condition != null) && Kernel.isEqual(b.upper.doubleValue(), b.lower.doubleValue())) { getFunction().getFunctionVariable().set(b.upper); ExpressionValue v = b.condition .evaluate(StringTemplate.defaultTemplate); if (v instanceof BooleanValue && ((BooleanValue) v).getBoolean()) { b.condition = null; } } // If[x==1,2,If[x>3,4,5]] if (b.condition != null && b.condition.getOperation() == Operation.NOT_EQUAL) { if (b.condition.getLeft() instanceof FunctionVariable && b.condition.getRight() instanceof MyDouble) { double d = ((MyDouble) b.condition.getRight()).getDouble(); if ((b.lower != null && d < b.lower) || (b.upper != null && d > b.upper)) { b.condition = null; } } else if (b.condition.getRight() instanceof FunctionVariable && b.condition.getLeft() instanceof MyDouble) { double d = ((MyDouble) b.condition.getLeft()).getDouble(); if ((b.lower != null && d < b.lower) || (b.upper != null && d > b.upper)) { b.condition = null; } } } return b; } /** * @return whether this can be true for some x (allows false negatives) */ public boolean isValid() { return lower == null || upper == null || lower <= upper; } /** * @param symbolic * true to keep variable names * @param varString * variable string * @param tpl * string template * @return LaTeX string */ public String toLaTeXString(boolean symbolic, String varString, StringTemplate tpl) { StringBuilder ret = new StringBuilder(); if (tpl.hasType(StringType.LATEX)) { if (upper == null && lower != null) { ret.append(varString); ret.append(" "); ret.append(lowerSharp ? ">" : Unicode.GREATER_EQUAL); ret.append(" "); ret.append(kernel.format(lower, tpl)); } else if (lower == null && upper != null) { ret.append(varString); ret.append(" "); ret.append(upperSharp ? "<" : Unicode.LESS_EQUAL); ret.append(" "); ret.append(kernel.format(upper, tpl)); } else if (lower != null && upper != null) { if (Kernel.isEqual(lower, upper) && !lowerSharp && !upperSharp) { ret.append(varString); ret.append(" = "); ret.append(kernel.format(lower, tpl)); } else { ret.append(kernel.format(lower, tpl)); ret.append(" "); ret.append(lowerSharp ? "<" : Unicode.LESS_EQUAL); ret.append(" "); ret.append(varString); ret.append(" "); ret.append(upperSharp ? "<" : Unicode.LESS_EQUAL); ret.append(" "); ret.append(kernel.format(upper, tpl)); } } // upper and lower are null, we only retrn condition right here else if (condition != null) { return condition.toLaTeXString(symbolic, tpl); } // we may still need to append condition if (condition != null) { ret.insert(0, "("); ret.append(")\\wedge \\left("); ret.append(condition.toLaTeXString(symbolic, tpl)); ret.append("\\right)"); } } else { // StringType.MATHML // <apply><lt/><ci>x</ci><cn>3</cn></apply> if (upper == null && lower != null) { ret.append("<apply>"); ret.append(lowerSharp ? "<gt/>" : "<geq/>"); ret.append("<ci>"); ret.append(varString); ret.append("</ci><cn>"); ret.append(kernel.format(lower, tpl)); ret.append("</cn></apply>"); } else if (lower == null && upper != null) { ret.append("<apply>"); ret.append(upperSharp ? "<lt/>" : "<leq/>"); ret.append("<ci>"); ret.append(varString); ret.append("</ci><cn>"); ret.append(kernel.format(upper, tpl)); ret.append("</cn></apply>"); } else if (lower != null && upper != null) { if (Kernel.isEqual(lower, upper) && !lowerSharp && !upperSharp) { ret.append("<apply>"); ret.append("<eq/>"); ret.append("<ci>"); ret.append(varString); ret.append("</ci><cn>"); ret.append(kernel.format(lower, tpl)); ret.append("</cn></apply>"); } else { if (lowerSharp == upperSharp) { ret.append("<apply>"); ret.append(lowerSharp ? "<lt/>" : "<leq/>"); ret.append("<cn>"); ret.append(kernel.format(lower, tpl)); ret.append("</cn>"); ret.append("<ci>"); ret.append(varString); ret.append("</ci>"); ret.append("<cn>"); ret.append(kernel.format(upper, tpl)); ret.append("</cn>"); ret.append("</apply>"); } else { // more complex for eg 3 < x <= 5 ret.append("<apply>");// <apply> ret.append("<and/>");// <and/> ret.append("<apply>");// <apply> ret.append(lowerSharp ? "<lt/>" : "<leq/>");// <lt/> ret.append("<cn>"); ret.append(kernel.format(lower, tpl)); ret.append("</cn>");// <cn>3</cn> ret.append("<ci>"); ret.append(varString); ret.append("</ci>");// <ci>x</ci> ret.append("</apply>");// </apply> ret.append("<apply>");// <apply> ret.append(upperSharp ? "<lt/>" : "<leq/>");// <leq/> ret.append("<ci>"); ret.append(varString); ret.append("</ci>");// <ci>x</ci> ret.append("<cn>"); ret.append(kernel.format(upper, tpl)); ret.append("</cn>"); // <cn>5</cn> ret.append("</apply>");// </apply> ret.append("</apply>");// </apply> } } } // upper and lower are null, just return condition else if (condition != null) { return condition.toLaTeXString(symbolic, tpl); } // we may still need to append condition if (condition != null) { // prepend ret.insert(0, "<apply><and/>"); ret.append(condition.toLaTeXString(symbolic, tpl)); ret.append("</apply>"); } } return ret.toString(); } } @Override public boolean hasDrawable3D() { return true; } @Override public boolean hasLineOpacity() { return true; } @Override public double[] newDoubleArray() { return new double[2]; } @Override public double[] getDefinedInterval(double a, double b) { return RealRootUtil.getDefinedInterval(getUnivariateFunctionY(), a, b); } @Override public double distanceMax(double[] p1, double[] p2) { return Math.max(Math.abs(p1[0] - p2[0]), Math.abs(p1[1] - p2[1])); } @Override final public HitType getLastHitType() { return HitType.ON_BOUNDARY; } @Override public ValueType getValueType() { return ValueType.FUNCTION; } @Override public void evaluatePoint(double u, double v, Coords3 point) { if (this.evaluateBoolean(u)) { point.set(u, v, 0); } else { point.set(u, v, Double.NaN); } } @Override public boolean evaluateNormal(Coords3 p, double u, double v, Coords3 normal) { // TODO Auto-generated method stub return false; } @Override public double getMinParameter(int i) { // TODO Auto-generated method stub return Double.NaN; } @Override public double getMaxParameter(int i) { // TODO Auto-generated method stub return Double.NaN; } @Override public void setDerivatives() { // TODO Auto-generated method stub } @Override public void resetDerivatives() { // TODO Auto-generated method stub } @Override public LevelOfDetail getLevelOfDetail() { // TODO Auto-generated method stub return LevelOfDetail.SPEED; } @Override public void setLevelOfDetail(LevelOfDetail lod) { // TODO Auto-generated method stub } /** * GGB-605 * * @param algo * algorithm to be used for value string instead of secret * expression */ public void setSecret(AlgoElement algo) { if (getFunction() != null) { getFunction().setSecret(algo); } } @Override public void printCASEvalMapXML(StringBuilder sbXML) { if (fun != null) { fun.printCASevalMapXML(sbXML); } } @Override public void updateCASEvalMap(TreeMap<String, String> map) { if (fun != null) { fun.updateCASEvalMap(map); } } @Override public double evaluate(double x, double y) { if (isFunctionOfY()) { return value(y); } return fun.value(x); } @Override public boolean needToShowBothRowsInAV() { if (hideDefinitionInAlgebra(getFunctionExpression())) { return false; } return super.needToShowBothRowsInAV(); } @Override public Function getFun(int i) { if (i > 1) { return new Function(new ExpressionNode(kernel, 0), fun.getFunctionVariable()); } return i == 1 ? fun : new Function(fun.getFunctionVariable().wrap(), fun.getFunctionVariable()); } /** * @param ex * expression * @return whether to hide first row in AV */ static boolean hideDefinitionInAlgebra(ExpressionNode ex) { return ex == null || Operation.includesFreehandOrData(ex.getOperation()) || ex.isSecret(); } @Override public boolean isGeoLocusable() { return getParentAlgorithm() instanceof AlgoFunctionFreehand; } /** * from GeoLocusable interface */ public int getPointLength() { return ((AlgoFunctionFreehand) getParentAlgorithm()).getPointLength(); } /** * from GeoLocusable interface */ public ArrayList<MyPoint> getPoints() { return ((AlgoFunctionFreehand) getParentAlgorithm()).getPoints(); } }