/*
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.advanced;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.algos.AlgoFractionText;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.ExpressionValue;
import org.geogebra.common.kernel.arithmetic.Function;
import org.geogebra.common.kernel.arithmetic.FunctionVariable;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoFunction;
import org.geogebra.common.plugin.Operation;
/**
* Inverts a function only works if there is one "x" in the function
*
* works by analyzing the EXpressionNode and reversing it
*
* doesn't take account of domain/range so sin inverts to arcsin, sqrt(x) to x^2
*
* @author Michael Borcherds
*/
public class AlgoFunctionInvert extends AlgoElement {
private GeoFunction f; // input
private GeoFunction g; // output
private boolean numeric;
public AlgoFunctionInvert(Construction cons, GeoFunction f, boolean numeric) {
super(cons);
this.f = f;
this.numeric = numeric;
g = new GeoFunction(cons);
setInputOutput(); // for AlgoElement
compute();
}
@Override
public Commands getClassName() {
return numeric ? Commands.NInvert : Commands.Invert;
}
// for AlgoElement
@Override
protected void setInputOutput() {
input = new GeoElement[1];
input[0] = f;
super.setOutputLength(1);
super.setOutput(0, g);
setDependencies(); // done by AlgoElement
}
public GeoFunction getResult() {
return g;
}
@Override
public final void compute() {
if (!f.isDefined()) {
g.setUndefined();
return;
}
ExpressionValue root = f.getFunctionExpression();
if (root == null || root.isConstant()) {
// eg f(x) = 2
g.setUndefined();
return;
}
FunctionVariable oldFV = f.getFunction().getFunctionVariable();
// make sure sin(y) inverts to arcsin(y)
FunctionVariable x = new FunctionVariable(kernel,
oldFV.getSetVarString());
ExpressionNode newRoot = invert(root, oldFV, x, kernel);
if (newRoot == null) {// root not invertible
g.setUndefined();
return;
}
Function tempFun = new Function(newRoot, x);
tempFun.initFunction();
g.setDefined(true);
g.setFunction(tempFun);
if (numeric) {
g.setSecret(this);
}
}
/**
* @param root0
* root element
* @param oldFV
* x variable of invered function
* @param x
* x variable of target
* @param kernel
* kernel
* @return inverted expression
*/
public static ExpressionNode invert(ExpressionValue root0,
FunctionVariable oldFV, FunctionVariable x, Kernel kernel) {
boolean fvLeft;
ExpressionNode newRoot = x.wrap();
ExpressionValue root = root0.unwrap();
while (root != null && !root.isLeaf() && root.isExpressionNode()) {
ExpressionValue left = ((ExpressionNode) root).getLeft().unwrap();
ExpressionValue right = ((ExpressionNode) root).getRight().unwrap();
Operation op;
switch (op = ((ExpressionNode) root).getOperation()) {
case SIN:
case COS:
case TAN:
case ARCSIND:
case ARCSIN:
case ARCCOS:
case ARCTAN:
case SINH:
case COSH:
case TANH:
case ASINH:
case ACOSH:
case ATANH:
case EXP:
case LOG:
newRoot = new ExpressionNode(kernel, newRoot,
Operation.inverse(op), null);
root = left;
break;
case COT:
// acot(x) can be written as atan(1/x)
newRoot = new ExpressionNode(kernel,
new ExpressionNode(kernel, new MyDouble(kernel, 1.0),
Operation.DIVIDE, newRoot),
Operation.ARCTAN, null);
root = left;
break;
case SEC:
// asec(x) can be written as acos(1/x)
newRoot = new ExpressionNode(kernel,
new ExpressionNode(kernel, new MyDouble(kernel, 1.0),
Operation.DIVIDE, newRoot),
Operation.ARCCOS, null);
root = left;
break;
case CSC:
// acsc(x) can be written as asin(1/x)
newRoot = new ExpressionNode(kernel,
new ExpressionNode(kernel, new MyDouble(kernel, 1.0),
Operation.DIVIDE, newRoot),
Operation.ARCSIN, null);
root = left;
break;
case COTH:
// acoth(x) can be written as atanh(1/x)
newRoot = new ExpressionNode(kernel,
new ExpressionNode(kernel, new MyDouble(kernel, 1.0),
Operation.DIVIDE, newRoot),
Operation.ATANH, null);
root = left;
break;
case SECH:
// asech(x) can be written as acosh(1/x)
newRoot = new ExpressionNode(kernel,
new ExpressionNode(kernel, new MyDouble(kernel, 1.0),
Operation.DIVIDE, newRoot),
Operation.ACOSH, null);
root = left;
break;
case CSCH:
// acsch(x) can be written as asinh(1/x)
newRoot = new ExpressionNode(kernel,
new ExpressionNode(kernel, new MyDouble(kernel, 1.0),
Operation.DIVIDE, newRoot),
Operation.ASINH, null);
root = left;
break;
case CBRT:
newRoot = new ExpressionNode(kernel, newRoot, Operation.POWER,
new MyDouble(kernel, 3.0));
root = left;
break;
case SQRT:
case SQRT_SHORT:
newRoot = new ExpressionNode(kernel, newRoot, Operation.POWER,
new MyDouble(kernel, 2.0));
root = left;
break;
case LOG2:
newRoot = new ExpressionNode(kernel, new MyDouble(kernel, 2.0),
Operation.POWER, newRoot);
root = left;
break;
case LOG10:
newRoot = new ExpressionNode(kernel, new MyDouble(kernel, 10.0),
Operation.POWER, newRoot);
root = left;
break;
case LOGB:
if ((fvLeft = left.contains(oldFV))
&& (right.contains(oldFV))) {
return null;
}
if (fvLeft) {
newRoot = new ExpressionNode(kernel, right, Operation.POWER,
new ExpressionNode(kernel, 1).divide(newRoot));
root = left;
} else {
newRoot = new ExpressionNode(kernel, left, Operation.POWER,
newRoot);
root = right;
}
break;
case POWER:
if (!left.contains(oldFV)) {
newRoot = new ExpressionNode(kernel, left, Operation.LOGB,
newRoot);
root = right;
} else if (!right.contains(oldFV)) {
if (right instanceof NumberValue) {
double index = (((NumberValue) (right
.evaluate(StringTemplate.maxPrecision)))
.getDouble());
if (Kernel.isEqual(index, 3)) {
// inverse of x^3 is cbrt(x)
newRoot = new ExpressionNode(kernel, newRoot,
Operation.CBRT, null);
} else if (Kernel.isEqual(index, 2)) {
// inverse of x^2 is sqrt(x)
newRoot = new ExpressionNode(kernel, newRoot,
Operation.SQRT, null);
} else if (Kernel.isEqual(index, -1)) {
// inverse of x^-1 is x^-1
newRoot = new ExpressionNode(kernel, newRoot,
Operation.POWER,
new MyDouble(kernel, -1.0));
} else if (right.isExpressionNode()
&& ((ExpressionNode) right).getOperation()
.equals(Operation.DIVIDE)) {
// special case for x^(a/b) convert to x^(b/a)
// AbstractApplication.debug("special case for
// x^(a/b) convert to x^(b/a)");
ExpressionValue num = ((ExpressionNode) right)
.getLeft();
ExpressionValue den = ((ExpressionNode) right)
.getRight();
newRoot = new ExpressionNode(kernel, newRoot,
Operation.POWER, new ExpressionNode(kernel,
den, Operation.DIVIDE, num));
} else {
// inverse of x^a is x^(1/a)
// check if its a rational with small denominator
// (eg not over 999)
double[] frac = AlgoFractionText.decimalToFraction(
index, Kernel.STANDARD_PRECISION);
// make sure the minus is at the top of the new
// fraction
if (frac[0] < 0) {
frac[0] *= -1;
frac[1] *= -1;
}
if (frac[1] == 0 || frac[0] == 0) {
return null;
} else if (frac[0] < 100 && frac[1] < 100) {
// nice form for x^(23/45)
newRoot = new ExpressionNode(kernel, newRoot,
Operation.POWER,
new ExpressionNode(kernel,
new MyDouble(kernel, frac[1]),
Operation.DIVIDE,
new MyDouble(kernel, frac[0])));
} else {
// just use decimals for fractions like 101/43
newRoot = new ExpressionNode(kernel, newRoot,
Operation.POWER,
new MyDouble(kernel, 1.0 / index));
}
}
} else {
// inverse of x^a is x^(1/a)
newRoot = new ExpressionNode(kernel, newRoot,
Operation.POWER,
new ExpressionNode(kernel,
new MyDouble(kernel, 1.0),
Operation.DIVIDE, right));
}
root = left;
} else {
// AbstractApplication.debug("failed at POWER");
return null;
}
break;
case PLUS:
case MULTIPLY:
if ((fvLeft = left.contains(oldFV))
&& (right.contains(oldFV))) {
return null;
}
// AbstractApplication.debug("left"+((ExpressionNode)
// root).getLeft().isConstant());
// AbstractApplication.debug("right"+((ExpressionNode)
// root).getRight().isConstant());
if (!fvLeft) {
newRoot = new ExpressionNode(kernel, newRoot,
Operation.inverse(op), left);
root = right;
} else {
newRoot = new ExpressionNode(kernel, newRoot,
Operation.inverse(op), right);
root = left;
}
break;
case MINUS:
case DIVIDE:
if ((fvLeft = left.contains(oldFV))
&& (right.contains(oldFV))) {
return null;
}
// AbstractApplication.debug("left"+((ExpressionNode)
// root).getLeft().isConstant());
// AbstractApplication.debug("right"+((ExpressionNode)
// root).getRight().isConstant());
if (!fvLeft) {
// inverse of 3-x is 3-x
newRoot = new ExpressionNode(kernel, left, op, newRoot);
root = right;
} else {
if (op.equals(Operation.DIVIDE)) {
// inverse of x/3 is 3*x (not x*3)
newRoot = new ExpressionNode(kernel, right,
Operation.inverse(op), newRoot);
} else {
// inverse of x-3 is x+3
newRoot = new ExpressionNode(kernel, newRoot,
Operation.inverse(op), right);
}
root = left;
}
break;
default: // eg ABS, CEIL etc
// AbstractApplication.debug("failed at"+ ((ExpressionNode)
// root).getOperation().toString());
return null;
}
}
return newRoot;
}
}