/*
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.
*/
/*
* AlgoDependentNumber.java
*
* Created on 30. August 2001, 21:37
*/
package org.geogebra.common.kernel.algos;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import org.geogebra.common.cas.GeoGebraCAS;
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.ExpressionValue;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.arithmetic.MySpecialDouble;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.arithmetic.ValidExpression;
import org.geogebra.common.kernel.geos.GeoAngle;
import org.geogebra.common.kernel.geos.GeoAngle.AngleStyle;
import org.geogebra.common.kernel.geos.GeoDummyVariable;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.geos.GeoSegment;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.prover.NoSymbolicParametersException;
import org.geogebra.common.kernel.prover.PolynomialNode;
import org.geogebra.common.kernel.prover.polynomial.PPolynomial;
import org.geogebra.common.kernel.prover.polynomial.PTerm;
import org.geogebra.common.kernel.prover.polynomial.PVariable;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.util.debug.Log;
/**
* Algorithm for dependent numbers, e.g. c = a + b.
*
* @author Markus Hohenwarter
*/
public class AlgoDependentNumber extends AlgoElement
implements DependentAlgo, SymbolicParametersBotanaAlgo {
private GeoNumberValue number; // output
private PVariable[] botanaVars;
private PPolynomial[] botanaPolynomials;
/*
* Rewrite formulas appearing in other geos to contain GeoGebra definitions.
* E.g. when entering a+2b, convert this formula to Segment[A,B] +
* 2Segment[C,D] in all other occurrences. Sometimes this is not what we
* want, e.g. on creating formulas from the prover automatically.
*/
private boolean rewriteFormula = true;
private Set<GeoSegment> allSegmentsFromExpression = new HashSet<GeoSegment>();
private ArrayList<Entry<GeoElement, PVariable>> segVarPairs = new ArrayList<Entry<GeoElement, PVariable>>();
private int nrOfMaxDecimals = 0;
/**
* Creates new AlgoDependentNumber
*
* @param cons
* construction
*
* @param root
* expression defining the result
* @param isAngle
* true for angles
*/
public AlgoDependentNumber(Construction cons, ExpressionNode root,
boolean isAngle) {
this(cons, root, isAngle, null, true);
}
/**
* Creates new AlgoDependentNumber
*
* @param cons
* construction
* @param root
* expression defining the result
* @param isAngle
* true for angles
* @param evaluate
* pre-evaluated result
*/
public AlgoDependentNumber(Construction cons, ExpressionNode root,
boolean isAngle, ExpressionValue evaluate) {
this(cons, root, isAngle, evaluate, true);
}
/**
* Creates new AlgoDependentNumber
*
* @param cons
* construction
* @param root
* expression defining the result
* @param isAngle
* true for angles
* @param evaluate
* pre-evaluated result
* @param addToConstructionList
* add object to the construction list
*/
public AlgoDependentNumber(Construction cons, ExpressionNode root,
boolean isAngle, ExpressionValue evaluate,
boolean addToConstructionList) {
this(cons, root, isAngle, evaluate, addToConstructionList, true);
}
/**
* Creates new AlgoDependentNumber
*
* @param cons
* construction
* @param root
* expression defining the result
* @param isAngle
* true for angles
* @param evaluate
* pre-evaluated result
* @param addToConstructionList
* add object to the construction list
* @param rewrite
* rewrite the related formulas in other geos
*/
public AlgoDependentNumber(Construction cons, ExpressionNode root,
boolean isAngle, ExpressionValue evaluate,
boolean addToConstructionList, boolean rewrite) {
super(cons, addToConstructionList);
rewriteFormula = rewrite;
// simplify constant integers, e.g. -1 * 300 becomes -300
if (rewriteFormula) {
root.simplifyConstantIntegers();
}
if (evaluate instanceof GeoNumberValue) {
// fix error with a=7, b = a renaming a instead of creating b
number = (GeoNumberValue) ((GeoNumberValue) evaluate)
.copyInternal(cons);
// just a copy of segment / polygon / arc, not eg. list1(1)
if (!number.isGeoNumeric() && root.unwrap().isGeoElement()) {
number.setEuclidianVisible(false);
}
} else if (isAngle) {
number = new GeoAngle(cons);
// check fileloading to make loading old files (<=4.2) works
// no allowReflexAngle or forceReflexAngle in XML by default
if (!cons.isFileLoading() || cons.isAllowUnboundedAngles()) {
// make sure eg summing angles of polygon a+b+c+d gives correct
// answer
((GeoAngle) number).setAngleStyle(AngleStyle.UNBOUNDED);
}
// dependent angles are not drawable
((GeoAngle) number).setDrawable(false, false);
} else {
number = new GeoNumeric(cons);
}
number.toGeoElement().setDefinition(root);
setInputOutput(); // for AlgoElement
// compute value of dependent number
compute();
}
@Override
public Algos getClassName() {
return Algos.Expression;
}
// for AlgoElement
@Override
protected void setInputOutput() {
input = number.getDefinition().getGeoElementVariables();
if (input == null) {
input = new GeoElement[0];
}
setOutputLength(1);
setOutput(0, number.toGeoElement());
setDependencies(); // done by AlgoElement
}
/**
* @return resulting number
*/
public GeoNumberValue getNumber() {
return number;
}
@Override
public ExpressionNode getExpression() {
return number.getDefinition();
}
// calc the current value of the arithmetic tree
@Override
public final void compute() {
if (!rewriteFormula) {
return;
}
try {
NumberValue nv = (NumberValue) number.getDefinition()
.evaluate(StringTemplate.defaultTemplate);
ExpressionNode def = number.getDefinition();
if (number instanceof GeoNumeric) {
((GeoNumeric) number).setValue(nv.getDouble());
} else {
number.set(nv.toGeoElement(cons));
}
number.setDefinition(def);
} catch (Throwable e) {
number.setUndefined();
}
}
@Override
final public String toString(StringTemplate tpl) {
// was defined as e.g. r = 5a - 3b
// return 5a - 3b
return number.getDefinition().toString(tpl);
}
@Override
public PVariable[] getBotanaVars(GeoElementND geo) {
return botanaVars;
}
/**
* Add Botana variables manually in case of building an AlgoDependentNumber
* externally.
*
* @param vars
* the used Botana variables in the expression to be built
*/
public void setBotanaVars(PVariable[] vars) {
botanaVars = vars;
}
@Override
public PPolynomial[] getBotanaPolynomials(GeoElementND geo)
throws NoSymbolicParametersException {
if (botanaPolynomials != null) {
return botanaPolynomials;
}
ExpressionNode definition = this.getExpression();
traverseExpression(definition);
if (botanaVars == null) {
botanaVars = new PVariable[segVarPairs.size() + 1];
// variable for the expression
botanaVars[0] = new PVariable(kernel);
if (!segVarPairs.isEmpty()) {
Iterator<Entry<GeoElement, PVariable>> it = segVarPairs
.iterator();
int k = 1;
while (it.hasNext()) {
Entry<GeoElement, PVariable> curr = it.next();
botanaVars[k] = curr.getValue();
k++;
}
} else {
throw new NoSymbolicParametersException();
}
}
PolynomialNode polyNode = new PolynomialNode();
buildPolynomialTree(definition, polyNode);
GeoGebraCAS cas = (GeoGebraCAS) getKernel().getGeoGebraCAS();
String exprGiacStr = "";
// expand(lcm(denom(coeff(gg)))*gg);
// see also CASgiac.createEliminateScript()
String gg = definition.toString(StringTemplate.giacTemplate)
+ "-ggbtmpvar" + botanaVars[0];
exprGiacStr = "expand(lcm(denom(coeff(" + gg + ")))*(" + gg + "))";
nrOfMaxDecimals = 0;
String strForGiac = getStrForGiac(exprGiacStr);
try {
String giacOutput = cas.getCurrentCAS().evaluateRaw(strForGiac);
giacOutput = giacOutput.substring(1, giacOutput.length() - 1)
.replaceAll(Kernel.TMP_VARIABLE_PREFIX2, "");
// also decrypting variable names
ValidExpression resultVE = (getKernel().getGeoGebraCAS())
.getCASparser().parseGeoGebraCASInputAndResolveDummyVars(
giacOutput, getKernel(), null);
polyNode = new PolynomialNode();
buildPolynomialTree((ExpressionNode) resultVE, polyNode);
expressionNodeToPolynomial((ExpressionNode) resultVE, polyNode);
while (polyNode.getPoly() == null) {
expressionNodeToPolynomial((ExpressionNode) resultVE, polyNode);
}
botanaPolynomials = new PPolynomial[botanaVars.length];
botanaPolynomials[0] = polyNode.getPoly();
if (!segVarPairs.isEmpty()) {
Iterator<Entry<GeoElement, PVariable>> it = segVarPairs
.iterator();
int k = 1;
while (it.hasNext()) {
Entry<GeoElement, PVariable> curr = it.next();
PVariable[] currBotVars = ((GeoSegment) curr.getKey())
.getBotanaVars(geo);
PPolynomial seg = new PPolynomial(curr.getValue());
botanaPolynomials[k] = seg.multiply(seg)
.subtract(PPolynomial.sqrDistance(currBotVars[0],
currBotVars[1], currBotVars[2],
currBotVars[3]));
k++;
}
}
allSegmentsFromExpression = new HashSet<GeoSegment>();
// remove variables as geoSegment names
if (rewriteFormula && !segVarPairs.isEmpty()) {
Iterator<Entry<GeoElement, PVariable>> it = segVarPairs
.iterator();
while (it.hasNext()) {
Entry<GeoElement, PVariable> curr = it.next();
GeoSegment currGeoSeg = (GeoSegment) curr.getKey();
currGeoSeg.setLabelSet(false);
}
}
segVarPairs = new ArrayList<Entry<GeoElement, PVariable>>();
return botanaPolynomials;
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
throw new NoSymbolicParametersException();
}
private void traverseExpression(ExpressionNode node)
throws NoSymbolicParametersException {
// Log.debug(node.toString());
if (node.getLeft() != null && ((node.getLeft().isGeoElement()
&& node.getLeft() instanceof GeoSegment)
|| node.getLeft() instanceof GeoDummyVariable)) {
processNode(node.getLeft());
}
if (node.getRight() != null && ((node.getRight().isGeoElement()
&& node.getRight() instanceof GeoSegment)
|| node.getRight() instanceof GeoDummyVariable)) {
processNode(node.getRight());
}
if (node.getLeft() != null && node.getLeft().isExpressionNode()) {
traverseExpression((ExpressionNode) node.getLeft());
}
if (node.getRight() != null && node.getRight().isExpressionNode()) {
traverseExpression((ExpressionNode) node.getRight());
}
if (node.getLeft() != null && node.getLeft().isExpressionNode()
&& node.getRight() != null
&& node.getRight().isExpressionNode()) {
return;
}
// case number with segment, eg. 2*a^2
if (node.getLeft() instanceof MyDouble
&& node.getRight().isExpressionNode()
&& (node.getOperation() == Operation.DIVIDE
|| node.getOperation() == Operation.MULTIPLY)) {
return;
}
// case segment with number, eg. a^2*1,5
if (node.getRight() instanceof MyDouble
&& node.getLeft().isExpressionNode()) {
return;
}
}
private void processNode(ExpressionValue ev) {
GeoSegment s = null;
if (ev instanceof GeoDummyVariable) {
GeoDummyVariable v = (GeoDummyVariable) ev;
GeoElement e = v.getElementWithSameName();
if (e instanceof GeoSegment) {
s = (GeoSegment) e;
}
} else if (ev instanceof GeoSegment) {
s = (GeoSegment) ev;
}
if (s != null) {
PVariable currentVar = new PVariable(kernel);
/*
* This is voodoo magic here. We may need a different solution
* rather than playing with the label. TODO.
*/
boolean suppress = cons.isSuppressLabelsActive();
cons.setSuppressLabelCreation(false);
if (s.getLabelSimple() == null) {
s.setLabel(currentVar.toString());
s.setAuxiliaryObject(true);
s.setEuclidianVisible(false);
s.update();
}
cons.setSuppressLabelCreation(suppress);
Entry<GeoElement, PVariable> pair = new AbstractMap.SimpleEntry<GeoElement, PVariable>(
s, currentVar);
searchSegVarPair(pair);
allSegmentsFromExpression.add(s);
}
}
private void searchSegVarPair(Entry<GeoElement, PVariable> pair) {
if (!segVarPairs.isEmpty()) {
Iterator<Entry<GeoElement, PVariable>> it = segVarPairs.iterator();
int k = 0;
while (it.hasNext()) {
Entry<GeoElement, PVariable> curr = it.next();
if (curr.getKey().equals(pair.getKey())
&& curr.getValue().equals(pair.getValue())) {
break;
}
k++;
}
if (k == segVarPairs.size()) {
segVarPairs.add(pair);
}
} else {
segVarPairs.add(pair);
}
}
/**
* Attempt to create a Polynomial from a PolynomialNode by using the same
* object stored as ExpressionNode as well. The output will be put into
* polyNode.poly and can be retrieved by using polyNode.getPoly(). The
* PolynomialNode has to be created by buildPolynomialTree(expNode,
* polyNode) first. It is possible that the process will not be successful
* for the first run. In such cases multiple runs should be performed until
* polyNode.poly is not null.
*
* @param expNode
* ExpressionNode presentation of the polynomial
* @param polyNode
* PolynomialNode presentation of the polynomial
* @throws NoSymbolicParametersException
* if the conversion is not possible for some reason (maybe
* because of unhandled cases)
*
* @author Csilla Solyom-Gecse
* @author Zoltan Kovacs
*
* TODO: Find a more elegant way to do that.
*/
public void expressionNodeToPolynomial(ExpressionNode expNode,
PolynomialNode polyNode) throws NoSymbolicParametersException {
if (polyNode.getPoly() != null) {
return;
}
if (polyNode.getLeft() != null && polyNode.getRight() == null
&& polyNode.getOperation() == Operation.NO_OPERATION) {
PPolynomial leftPoly = polyNode.getLeft().getPoly();
polyNode.setPoly(leftPoly);
}
if (polyNode.getLeft() != null && polyNode.getLeft().getPoly() != null
&& polyNode.getRight() != null
&& polyNode.getRight().getPoly() != null) {
PPolynomial leftPoly = polyNode.getLeft().getPoly();
PPolynomial rightPoly = polyNode.getRight().getPoly();
switch (polyNode.getOperation()) {
case PLUS:
polyNode.setPoly(leftPoly.add(rightPoly));
break;
case MINUS:
polyNode.setPoly(leftPoly.subtract(rightPoly));
break;
case MULTIPLY:
polyNode.setPoly(leftPoly.multiply(rightPoly));
break;
case POWER:
/* It must fit in Long. If not, it will take forever. */
Long pow = polyNode.getRight().evaluateLong();
if (pow != null) {
PPolynomial poly = leftPoly;
for (Integer i = 1; i < pow; i++) {
poly = poly.multiply(leftPoly);
}
polyNode.setPoly(poly);
}
break;
default:
throw new NoSymbolicParametersException();
}
}
if (expNode.getLeft().isExpressionNode()
&& polyNode.getLeft().getPoly() == null) {
expressionNodeToPolynomial((ExpressionNode) expNode.getLeft(),
polyNode.getLeft());
}
if (expNode.getRight() != null && expNode.getRight().isExpressionNode()
&& polyNode.getRight().getPoly() == null) {
expressionNodeToPolynomial((ExpressionNode) expNode.getRight(),
polyNode.getRight());
}
if (expNode.getLeft() instanceof MyDouble
&& polyNode.getLeft().getPoly() == null) {
BigInteger coeff = new BigDecimal(
expNode.getLeft().evaluateDouble()).toBigInteger();
polyNode.getLeft().setPoly(new PPolynomial(coeff));
}
if (expNode.getRight() instanceof MyDouble
&& polyNode.getRight().getPoly() == null) {
BigInteger coeff = new BigDecimal(
expNode.getRight().evaluateDouble()).toBigInteger();
polyNode.getRight().setPoly(new PPolynomial(coeff));
}
if (expNode.getLeft() instanceof MyDouble
&& expNode.getRight() instanceof GeoDummyVariable) {
BigInteger coeff = new BigDecimal(
expNode.getLeft().evaluateDouble()).toBigInteger();
PVariable v = getVarOfGeoDummy(expNode.getRight()
.toString(StringTemplate.defaultTemplate));
if (v != null) {
PTerm t = new PTerm(v);
polyNode.setPoly(new PPolynomial(coeff, t));
return;
}
}
}
/**
* Creates a PolynomialNode from an ExpressionNode.
*
* @param expNode
* ExpressionNode presentation of the polynomial
* @param polyNode
* PolynomialNode presentation of the polynomial
* @throws NoSymbolicParametersException
* if the conversion is not possible for some reason (maybe
* because of unhandled cases)
*
* @author Csilla Solyom-Gecse
* @author Zoltan Kovacs
*
* TODO: Find a more elegant way to do that.
*/
public void buildPolynomialTree(ExpressionNode expNode,
PolynomialNode polyNode) throws NoSymbolicParametersException {
// Log.debug(expNode.toString());
if (expNode == null) {
return;
}
// simplify polynomial if the left and right sides are numbers
if (expNode.getLeft() instanceof MyDouble
&& expNode.getRight() instanceof MyDouble) {
double d1 = expNode.getLeft().evaluateDouble();
double d2 = expNode.getRight().evaluateDouble();
Double d;
switch (expNode.getOperation()) {
case PLUS:
d = d1 + d2;
break;
case MINUS:
d = d1 - d2;
break;
case MULTIPLY:
d = d1 * d2;
break;
case POWER:
d = Math.pow(d1, d2);
break;
case DIVIDE:
d = d1 / d2;
String[] splitter = d.toString().split("\\.");
if (nrOfMaxDecimals < splitter[1].length()) {
nrOfMaxDecimals = splitter[1].length();
}
break;
default:
throw new NoSymbolicParametersException();
}
BigInteger i;
// if in the expression exists rational number with n decimals
// (if there's more than one rational number, then n is the max of
// decimal numbers)
// than multiply the coefficient with 10^n
if (nrOfMaxDecimals != 0) {
i = new BigDecimal(d * Math.pow(10, nrOfMaxDecimals))
.toBigInteger();
Log.error(
"Possible numerical error in converting formula coefficients to integer");
/* TODO: check if this conversion is really correct */
} else {
i = new BigDecimal(d).toBigInteger();
}
polyNode.setPoly(new PPolynomial(i));
return;
}
polyNode.setOperation(expNode.getOperation());
if (expNode.getLeft() != null) {
polyNode.setLeft(new PolynomialNode());
if (expNode.getLeft().isExpressionNode()) {
buildPolynomialTree((ExpressionNode) expNode.getLeft(),
polyNode.getLeft());
} else {
if (expNode.getLeft() instanceof GeoDummyVariable) {
polyNode.getLeft().setPoly(new PPolynomial(getVarOfGeoDummy(
((GeoDummyVariable) expNode.getLeft()).toString(
StringTemplate.defaultTemplate))));
}
if (expNode.getLeft() instanceof MySpecialDouble) {
Double d = expNode.getLeft().evaluateDouble();
BigInteger i;
// if in the expression exists rational number with n decimals
// (if there's more than one rational number, then n is the max of
// decimal numbers)
// than multiply the coefficient with 10^n
if (nrOfMaxDecimals != 0) {
i = new BigDecimal(d * Math.pow(10, nrOfMaxDecimals))
.toBigInteger();
Log.error(
"Possible numerical error in converting formula coefficients to integer");
/* TODO: check if this conversion is really correct */
} else {
i = new BigDecimal(d).toBigInteger();
}
polyNode.getLeft().setPoly(new PPolynomial(i));
}
}
}
if (expNode.getRight() != null) {
polyNode.setRight(new PolynomialNode());
if (expNode.getRight().isExpressionNode()) {
buildPolynomialTree((ExpressionNode) expNode.getRight(),
polyNode.getRight());
} else {
if (expNode.getRight() instanceof GeoDummyVariable) {
try {
polyNode.getRight().setPoly(new PPolynomial(
getVarOfGeoDummy(((GeoDummyVariable) expNode
.getRight()).toString(
StringTemplate.defaultTemplate))));
} catch (Exception e) {
throw new NoSymbolicParametersException();
}
}
if (expNode.getRight() instanceof MySpecialDouble) {
// see also AlgoDependentBoolean
double d = expNode.getRight().evaluateDouble();
BigInteger i;
// simplify the polynomial if in expression is product of
// numbers
if (polyNode.getLeft().getPoly() != null
&& polyNode.getLeft().getPoly().isConstant()) {
switch (polyNode.getOperation()) {
case MULTIPLY:
i = polyNode.getLeft().getPoly()
.getConstant().multiply(new BigInteger(Long.toString((long) d)));
break;
case DIVIDE:
i = BigInteger.ONE;
break;
default:
throw new NoSymbolicParametersException();
}
polyNode.setPoly(new PPolynomial(i));
return;
}
// if in the expression exists rational number with n
// decimals
// (if there's more than one rational number, then n is the
// max of decimal numbers)
// than multiply the coefficient with 10^n
if (nrOfMaxDecimals != 0
&& expNode.getOperation() != Operation.POWER) {
i = new BigInteger(Long.toString(((long) (d * Math.pow(10, nrOfMaxDecimals)))));
} else {
i = new BigInteger(Long.toString(((long) d)));
}
polyNode.getRight().setPoly(new PPolynomial(i));
}
}
}
}
private PVariable getVarOfGeoDummy(String str) {
for (PVariable variable : botanaVars) {
if (variable.getName().equals(str)) {
return variable;
}
}
// It's possible that the variable is in segVarPairs.
Iterator<Entry<GeoElement, PVariable>> it = segVarPairs.iterator();
while (it.hasNext()) {
Entry<GeoElement, PVariable> e = it.next();
GeoElement ge = e.getKey();
if (ge.getLabelSimple().equals(str)) {
return e.getValue();
}
}
// This will cause a NPE (should not happen):
Log.error("Internal error in AlgoDependentNumber");
return null;
}
private String getStrForGiac(String str) {
StringBuilder strForGiac = new StringBuilder();
strForGiac.append("eliminate([");
strForGiac.append(str);
StringBuilder labelsStr = new StringBuilder();
Iterator<GeoSegment> it = allSegmentsFromExpression.iterator();
labelsStr.append(Kernel.TMP_VARIABLE_PREFIX);
labelsStr.append(botanaVars[0].toString());
strForGiac.append(",");
strForGiac.append(Kernel.TMP_VARIABLE_PREFIX);
strForGiac.append(botanaVars[0].toString());
strForGiac.append("=");
strForGiac.append(botanaVars[0].toString());
while (it.hasNext()) {
GeoSegment currSeg = it.next();
labelsStr.append(",");
labelsStr.append(Kernel.TMP_VARIABLE_PREFIX);
labelsStr.append(currSeg.getLabelSimple());
/*
* Use encrypted variable names here to prevent Giac translating
* them later to certain constants like e or i.
*/
strForGiac.append(",");
strForGiac.append(Kernel.TMP_VARIABLE_PREFIX);
strForGiac.append(currSeg.getLabelSimple());
strForGiac.append("=");
strForGiac.append(Kernel.TMP_VARIABLE_PREFIX2);
strForGiac.append(currSeg.getLabelSimple());
}
strForGiac.append("],[");
strForGiac.append(labelsStr);
strForGiac.append("])");
return strForGiac.toString();
}
}