/* 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.algos; import java.util.HashSet; import java.util.Iterator; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants.StringType; import org.geogebra.common.kernel.arithmetic.ExpressionNodeEvaluator; 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.ListValue; 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.geos.GeoCasCell; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.kernelND.GeoCurveCartesianND; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; /** * This class is only needed to handle dependencies * * @author Markus Hohenwarter */ public class AlgoDependentFunction extends AlgoElement implements DependentAlgo { /** input */ protected Function fun; /** output */ protected GeoFunction f; private Function expandedFun; private ExpressionNode expression; private boolean expContainsFunctions; // expression contains functions private HashSet<GeoElement> unconditionalInput; private boolean fast; /** * @param cons * construction * @param fun * input function * @param addToConsList * whether to add this to construction list */ public AlgoDependentFunction(Construction cons, Function fun, boolean addToConsList) { this(cons, fun, addToConsList, false); } /** * @param cons * construction * @param fun * input function * @param addToConsList * whether to add this to construction list * @param fast * use fast derivatives */ public AlgoDependentFunction(Construction cons, Function fun, boolean addToConsList, boolean fast) { super(cons, false); fun.initFunction(); if (addToConsList) { cons.addToConstructionList(this, false); } this.fast = fast || !cons.getApplication().getSettings() .getCasSettings().isEnabled(); this.fun = fun; f = new GeoFunction(cons, false); f.setFunction(fun); f.setConstructionDefaults(); // look for FUNCTION or DERIVATIVE nodes in function expression = fun.getExpression(); expContainsFunctions = containsFunctions(expression); if (expContainsFunctions) { expandedFun = new Function(fun, kernel); } setInputOutput(); // for AlgoElement compute(); } /** * @param cons * construction */ protected AlgoDependentFunction(Construction cons) { super(cons); } @Override public Algos getClassName() { return Algos.Expression; } // for AlgoElement @Override protected void setInputOutput() { input = fun.getGeoElementVariables(); unconditionalInput = fun.getFunctionExpression().getUnconditionalVars(); super.setOutputLength(1); super.setOutput(0, f); setDependencies(); // done by AlgoElement } /** * @return resulting function */ public GeoFunction getFunction() { return f; } @Override public final void compute() { // evaluation of function will be done in view (see // geogebra.euclidian.DrawFunction) // check if function is defined boolean isDefined = inputDefined(); f.setDefined(isDefined); if (isDefined && expContainsFunctions) { // expand the functions and derivatives in expression tree ExpressionValue ev = null; try { // needed for eg f(x)=floor(x) f'(x) // boolean internationalizeDigits = // Kernel.internationalizeDigits; // Kernel.internationalizeDigits = false; // TODO: seems that we never read internationalize digits flag // here ... ev = expandFunctionDerivativeNodes(expression.deepCopy(kernel), this.fast, f.getFunctionVariables()); // Kernel.internationalizeDigits = internationalizeDigits; } catch (Exception e) { e.printStackTrace(); Log.debug("derivative failed"); } if (ev == null) { f.setUndefined(); return; } ExpressionNode node; if (ev.isExpressionNode()) { node = (ExpressionNode) ev; } else { node = new ExpressionNode(kernel, ev); } expandedFun.setExpression(node); f.setFunction(expandedFun); // If the label is not set (first run of compute) // isFillable will take care of updating ineqs if (f.isBooleanFunction() && f.isLabelSet()) { f.resetIneqs(); } } else if (f.isBooleanFunction()) { f.getFunction().updateIneqs(); } } private boolean inputDefined() { if (this.unconditionalInput == null) { for (int i = 0; i < input.length; i++) { if (!input[i].isDefined()) { return false; } } return true; } Iterator<GeoElement> it = this.unconditionalInput.iterator(); while (it.hasNext()) { if (!it.next().isDefined()) { return false; } } return true; } /** * Expands all FUNCTION and DERIVATIVE nodes in the given expression. * * @param in * expression to expand (only ExpressionNodes are affected) * @param fast * use fast derivatives * @param vars * function variables * * @return new ExpressionNode as result */ public static ExpressionValue expandFunctionDerivativeNodes( ExpressionValue in, boolean fast, FunctionVariable[] vars) { ExpressionValue ev = expandFunctionDerivativeNodes(in, fast); ExpressionNode en = ev.wrap(); for (int i = 0; i < vars.length; i++) { en.replaceVariables(vars[i].getSetVarString(), vars[i]); } return ev; } /** * Expands all FUNCTION and DERIVATIVE nodes in the given expression. * * @param ev * expression to expand (only ExpressionNodes are affected) * @param fast * use fast derivatives * * @return new ExpressionNode as result */ public static ExpressionValue expandFunctionDerivativeNodes( ExpressionValue ev, boolean fast) { if (ev == null) { return null; } if (ev.isExpressionNode()) { ExpressionNode node = (ExpressionNode) ev; ExpressionValue leftValue = node.getLeft().unwrap(); switch (node.getOperation()) { case FUNCTION: // could be DERIVATIVE node if (leftValue.isExpressionNode()) { leftValue = expandFunctionDerivativeNodes(leftValue, fast); if (leftValue == null) { return null; } node.setLeft(leftValue); if (leftValue.isExpressionNode()) { return node; } } // we do NOT expand GeoFunctionConditional objects in expression // tree return substituteFunction((Functional) leftValue, node.getRight(), fast); case FUNCTION_NVAR: // make sure we expand $ in $A1(x,y) if (leftValue.isExpressionNode()) { leftValue = expandFunctionDerivativeNodes(leftValue, fast); node.setLeft(leftValue); if (leftValue.isExpressionNode()) { return node; } } if (!(leftValue instanceof FunctionalNVar)) { return null; } ExpressionValue ret = expandFunctionalNVar(leftValue, node.getRight(), 0, fast); return ret == null ? ev : ret; case DERIVATIVE: // don't expand derivative of GeoFunctionConditional if (leftValue.isGeoElement() && ((GeoElement) leftValue) .isGeoFunctionConditional()) { return node; } int order = (int) Math .round(((NumberValue) node.getRight()).getDouble()); if (leftValue.isExpressionNode() && (((ExpressionNode) leftValue) .getOperation() == Operation.$VAR_COL || ((ExpressionNode) leftValue) .getOperation() == Operation.$VAR_ROW || ((ExpressionNode) leftValue) .getOperation() == Operation.$VAR_ROW_COL)) { leftValue = ((ExpressionNode) leftValue).getLeft(); } if (leftValue instanceof GeoCasCell) { return ((GeoCasCell) leftValue).getGeoDerivative(order, fast); } if (leftValue instanceof GeoCurveCartesianND) { return ((GeoCurveCartesianND) leftValue) .getGeoDerivative(order); } return ((Functional) leftValue).getGeoDerivative(order, fast); case ELEMENT_OF: // list(x,x) cannot be expanded ExpressionValue rt = node.getRight().unwrap(); if (rt instanceof ListValue) { ListValue list = (ListValue) rt; int constants = list.size(); for (int i = 0; i < list.size() - 1; i++) { if (list.getListElement(i).wrap() .containsFreeFunctionVariable(null)) { constants = i; break; } } Log.debug(constants + "/" + list.size()); ExpressionNodeEvaluator expev = ((GeoList) leftValue) .getKernel().getExpressionNodeEvaluator(); ExpressionValue res = expev.handleElementOf(leftValue, node.getRight(), 1); if (res instanceof Functional && constants >= list.size() - 1) { return substituteFunction(((Functional) res), list.getListElement(list.size() - 1), fast); } if (res instanceof FunctionalNVar && constants >= list.size() - ((FunctionalNVar) res) .getFunctionVariables().length) { ret = expandFunctionalNVar(res, node.getRight(), list.size() - ((FunctionalNVar) res) .getFunctionVariables().length, fast); return ret == null ? ev : ret; } if (!(res instanceof FunctionalNVar)) { return res; } Log.debug("Cannot expand"); } // element of with no-list rhs: weird, don't expand return node; // remove spreadsheet $ references, i.e. $A1 -> A1 case $VAR_ROW: case $VAR_COL: case $VAR_ROW_COL: return leftValue; default: // recursive calls node.setLeft(expandFunctionDerivativeNodes(leftValue, fast)); node.setRight( expandFunctionDerivativeNodes(node.getRight(), fast)); return node; } } else if (ev instanceof MyNumberPair) { ((MyNumberPair) ev).setX(expandFunctionDerivativeNodes( ((MyNumberPair) ev).getX(), fast)); ((MyNumberPair) ev).setY(expandFunctionDerivativeNodes( ((MyNumberPair) ev).getY(), fast)); // for f,g,h functions make sure f(g,h) expands to f(g(x),h(x)) } else if (ev.unwrap() instanceof FunctionalNVar) { return ((FunctionalNVar) ev.unwrap()).getFunctionExpression() .deepCopy(((FunctionalNVar) ev.unwrap()).getKernel()); } return ev; } private static ExpressionValue expandFunctionalNVar( ExpressionValue leftValue, ExpressionValue right, int offset, boolean fast) { FunctionNVar funN = ((FunctionalNVar) leftValue).getFunction(); FunctionVariable[] xy = funN.getFunctionVariables(); // don't destroy the function ExpressionNode funNExpression = funN.getExpression() .getCopy(funN.getKernel()); // with f(A) where A is a point we should not get there, but // still if (!(right instanceof MyList)) { return null; } // now replace every x in function by the expanded argument for (int i = 0; i < xy.length; i++) { funNExpression = funNExpression.replace(xy[i], expandFunctionDerivativeNodes( ((MyList) right).getListElement(i + offset), fast)) .wrap(); } return (funNExpression); } private static ExpressionValue substituteFunction(Functional leftValue, ExpressionValue right, boolean fast) { Function fun = leftValue.getFunction(); FunctionVariable x = fun.getFunctionVariable(); // don't destroy the function ExpressionNode funcExpression = fun.getExpression() .getCopy(fun.getKernel()); // now replace every x in function by the expanded argument return funcExpression.replace(x, expandFunctionDerivativeNodes(right, fast).wrap()); } /** * @param ev * expression * @return whether given expression contains operation FUNCTION, * FUNCTION_NVAR or DERIVATIVE */ public static boolean containsFunctions(ExpressionValue ev) { if (ev != null && ev.isExpressionNode()) { ExpressionNode node = (ExpressionNode) ev; Operation op = node.getOperation(); if (op.equals(Operation.FUNCTION_NVAR) || op.equals(Operation.FUNCTION) || op.equals(Operation.DERIVATIVE)) { return true; } // list(1,x) is function dependent, list(1,2) is not if (op.equals(Operation.ELEMENT_OF)) { return true; } return containsFunctions(node.getLeft()) || containsFunctions(node.getRight()); } return false; } private StringBuilder sb; @Override public String toString(StringTemplate tpl) { if (sb == null) { sb = new StringBuilder(); } else { sb.setLength(0); } if (f.isLabelSet() && !tpl.isHideLHS() && (!f.isBooleanFunction() || tpl.hasType(StringType.GEOGEBRA_XML))) { sb.append(f.getLabel(tpl)); sb.append("("); sb.append(f.getVarString(tpl)); sb.append(") = "); } sb.append(fun.toString(tpl)); return sb.toString(); } /*** * checks to see if this is an nth derivative, and return an appropriate * label eg f''' for 3rd derivative * * @param fun * function * @return label */ public static String getDerivativeLabel(Function fun) { ExpressionNode expr = fun.getExpression().unwrap().wrap(); // f'(x+3) should use default label if (expr.getRight() != null && !(expr.getRight().unwrap() instanceof FunctionVariable)) { return null; } // f'(x) should be called f' ExpressionValue exprL = expr.getLeft(); if (exprL.isExpressionNode()) { ExpressionNode enLL = (ExpressionNode) expr.getLeft(); if (enLL.getOperation().equals(Operation.DERIVATIVE)) { if (enLL.getLeft().isGeoElement()) { GeoElement geo = (GeoElement) enLL.getLeft(); if (geo.isLabelSet()) { ExpressionValue evR = (enLL.getRight()); if (evR instanceof NumberValue) { NumberValue num = (NumberValue) evR; double val = num.getDouble(); if (val > 0d && Kernel.isInteger(val)) { // eg f''' if val == 3 return geo.getLabelSimple() + StringUtil.string("'", (int) val); // eg // f'''' } } } } } } return null; } @Override public ExpressionNode getExpression() { return expression; } }