/*
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.arithmetic;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.arithmetic3D.MyVec3DNode;
import org.geogebra.common.kernel.commands.EvalInfo;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.util.debug.Log;
import org.geogebra.common.util.lang.Unicode;
/**
* stores left and right hand side of an equation as Exprssions
*/
public class Equation extends ValidExpression implements EquationValue {
private ExpressionNode lhs;
private ExpressionNode rhs;
private Polynomial leftPoly, rightPoly; // polynomial in normalForm
private Polynomial normalForm; // polynomial in normalForm
private boolean isFunctionDependent; // Equation depends (non-constant) on
// functions (set in InitEquation)
private Kernel kernel;
private boolean forcePlane = false, forceLine = false;
private boolean forceConic = false, forceImplicitPoly = false;
private boolean forceQuadric = false, forceSurface = false;
private ArrayList<ExpressionValue> variableDegrees = null;
private boolean isPolynomial = true;
/**
* check whether ExpressionNodes are evaluable to instances of Polynomial or
* NumberValue and build an Equation out of them
*
* @param kernel
* kernel
* @param lhs
* LHS
* @param rhs
* RHS
*/
public Equation(Kernel kernel, ExpressionValue lhs, ExpressionValue rhs) {
if (lhs.isExpressionNode()) {
this.lhs = (ExpressionNode) lhs;
} else {
this.lhs = new ExpressionNode(kernel, lhs);
}
if (rhs.isExpressionNode()) {
this.rhs = (ExpressionNode) rhs;
} else {
this.rhs = new ExpressionNode(kernel, rhs);
}
this.kernel = kernel;
isFunctionDependent = false;
}
/**
* @return RHS of this equation
*/
public ExpressionNode getRHS() {
return rhs;
}
/**
* @param rhs
* new RHS
*/
public void setRHS(ExpressionNode rhs) {
if (rhs != null) {
this.rhs = rhs;
}
}
/**
* @return LHS of this equation
*/
public ExpressionNode getLHS() {
return lhs;
}
/**
* @param lhs
* new LHS
*/
public void setLHS(ExpressionNode lhs) {
if (lhs != null) {
this.lhs = lhs;
}
}
/**
* Force this to evaluate to line
*/
public void setForceLine() {
// this expression should be considered as a line, not a plane
forceLine = true;
}
/**
* @return true if this is forced to evaluate to line
*/
final public boolean isForcedLine() {
return forceLine;
}
/**
* Force this to evaluate to plane
*/
final public void setForcePlane() {
// this expression should be considered as a plane, not a line
forcePlane = true;
}
/**
* @return true if this is forced to evaluate to plane
*/
final public boolean isForcedPlane() {
return forcePlane;
}
/**
* Force this to evaluate to quadric
*/
final public void setForceQuadric() {
// this expression should be considered as a quadric, not a conic
forceQuadric = true;
}
/**
* Force this to evaluate to quadric
*/
final public void setForceSurface() {
// this expression should be considered as a surface, not implicit poly
forceSurface = true;
}
/**
* @return true if this is forced to evaluate to quadric
*/
final public boolean isForcedQuadric() {
return forceQuadric;
}
/**
* @return true if this is forced to evaluate to quadric
*/
final public boolean isForcedSurface() {
return forceSurface;
}
/**
* @return true if this is forced to evaluate to conic
*/
public boolean isForcedConic() {
return forceConic;
}
/**
* Force this to evaluate to conic
*/
public void setForceConic() {
this.forceConic = true;
}
/**
* @return true if this is forced to evaluate to implicit poly
*/
public boolean isForcedImplicitPoly() {
return forceImplicitPoly;
}
/**
* Force this to evaluate to implicit poly
*/
public void setForceImplicitPoly() {
this.forceImplicitPoly = true;
}
/**
* Adds/subtracts/muliplies/divides ev to this equation to get lhs + ev =
* rhs = ev
*
* @param operation
* operation to apply
* @param ev
* other operand
* @param switchOrder
* true to compute other * this
*/
public void applyOperation(Operation operation, ExpressionValue ev,
boolean switchOrder) {
ExpressionValue left, right;
if (ev instanceof Equation) {
Equation equ = (Equation) ev;
left = equ.lhs;
right = equ.rhs;
} else {
left = ev;
right = ev;
}
if (switchOrder) {
// ev <operation> equ
lhs = new ExpressionNode(kernel, left, operation, lhs);
rhs = new ExpressionNode(kernel, right, operation, rhs);
} else {
// equ <operation> ev
lhs = new ExpressionNode(kernel, lhs, operation, left);
rhs = new ExpressionNode(kernel, rhs, operation, right);
}
}
/**
* Call this method to check that this is a valid equation. May throw
* MyError (InvalidEquation).
*/
public void initEquation() {
EvalInfo info = new EvalInfo(false);
// resolve variables in lhs
if (lhs.isLeaf() && lhs.getLeft().isVariable()) {
// avoid auto creation of GeoElement when lhs is a single variable
// e.g. A4 = x^2
Variable leftVar = (Variable) lhs.getLeft();
lhs.setLeft(leftVar.resolve(false, true)); // don't allow auto
// creation of variables
} else {
// standard case for lhs
lhs.resolveVariables(info);
}
// resolve variables in rhs
rhs.resolveVariables(info);
// simplify the both sides to single polynomials
this.isPolynomial = true;
this.variableDegrees = null;
FunctionVariable xVar = new FunctionVariable(kernel, "x"),
yVar = new FunctionVariable(kernel, "y"),
zVar = new FunctionVariable(kernel, "z");
fixStructure(lhs, xVar, yVar, zVar);
fixStructure(rhs, xVar, yVar, zVar);
leftPoly = Polynomial.fromNode(lhs, this);
rightPoly = Polynomial.fromNode(rhs, this);
// bring to normal form left - right = 0
normalForm = new Polynomial(kernel, rightPoly);
normalForm.multiply(-1.0d);
normalForm.add(leftPoly, this);
}
private static void fixStructure(ExpressionNode expression,
FunctionVariable xVar, FunctionVariable yVar,
FunctionVariable zVar) {
// try to replace x(x+1) by x*(x+1)
undecided.clear();
expression.replaceXYZnodes(xVar, yVar, zVar, undecided);
for (ExpressionNode en : undecided) {
en.setOperation(Operation.MULTIPLY);
}
undecided.clear();
}
private static ArrayList<ExpressionNode> undecided = new ArrayList<ExpressionNode>();
/**
* @param isFunctionDependent
* true iff contains functions
*/
public void setFunctionDependent(boolean isFunctionDependent) {
this.isFunctionDependent = isFunctionDependent;
}
/**
* @return true iff contains functions
*/
public boolean isFunctionDependent() {
return isFunctionDependent;
}
/**
* @return LHS-RHS
*/
public Polynomial getNormalForm() {
return normalForm;
}
/**
* @return the degree of the equation's normalform (max length of variables
* in a Term of the normalform)
*/
public int degree() {
return normalForm.degree();
}
/**
* @return the max degree on a single var, eg 3 for x^3 y^2
*/
public int singleDegree() {
return normalForm.singleDegree();
}
/**
* @param variables
* variable string
* @return coefficient
*/
public ExpressionValue getCoefficient(String variables) {
return normalForm.getCoefficient(variables);
}
/**
* @param variables
* variable string
* @return coefficient
*/
public double getCoeffValue(String variables) {
ExpressionValue ev = getCoefficient(variables);
try {
NumberValue nv = (NumberValue) ev;
return nv.getDouble();
} catch (Exception e) {
Log.warn("getCoeffValue(" + variables + ") failed:" + e);
return Double.NaN;
}
}
/**
* @return GeoElement variables
*/
final public GeoElement[] getGeoElementVariables() {
Set<GeoElement> varSet;
Set<GeoElement> leftVars = lhs.getVariables();
Set<GeoElement> rightVars = rhs.getVariables();
if (leftVars == null) {
varSet = rightVars;
} else if (rightVars == null) {
varSet = leftVars;
} else {
leftVars.addAll(rightVars);
varSet = leftVars;
}
if (varSet == null) {
return new GeoElement[0];
}
Iterator<GeoElement> i = varSet.iterator();
GeoElement[] ret = new GeoElement[varSet.size()];
int j = 0;
while (i.hasNext()) {
ret[j++] = i.next();
}
return ret;
}
/**
* @param var
* variable in which this could be explicit
* @return true if this Equation is explicit (lhs is "+1var" and rhs does
* not contain var) or (rhs is "+1var" and lhs does not contain var)
*/
public boolean isExplicit(String var) {
Polynomial lhsp = leftPoly;
Polynomial rhsp = rightPoly;
// var = ... || ... = var
return (lhsp.length() == 1
&& lhsp.getCoefficient(var).evaluateDouble() == 1
&& !rhsp.contains(var))
|| (rhsp.length() == 1
&& rhsp.getCoefficient(var).evaluateDouble() == 1
&& !lhsp.contains(var));
}
/**
* @return true if this Equation is implicit (not explicit)
*/
public boolean isImplicit() {
return !isExplicit("x") && !isExplicit("y") && !isExplicit("z");
}
@Override
public boolean contains(ExpressionValue ev) {
return lhs.contains(ev) || rhs.contains(ev);
}
@Override
public Equation deepCopy(Kernel kernel1) {
Equation ret = new Equation(kernel1, lhs.getCopy(kernel1),
rhs.getCopy(kernel1));
ret.forceConic = forceConic;
ret.forceLine = forceLine;
ret.forcePlane = forcePlane;
ret.forceQuadric = forceQuadric;
ret.forceImplicitPoly = forceImplicitPoly;
ret.forceSurface = forceSurface;
ret.isPolynomial = isPolynomial;
return ret;
}
/**
* Reset flags for forcing result type
*/
public void resetFlags() {
forceConic = false;
forceLine = false;
forcePlane = false;
forceQuadric = false;
forceImplicitPoly = false;
forceSurface = false;
}
@Override
public GeoElement evaluate(StringTemplate tpl) {
boolean oldFlag = kernel.getConstruction().isSuppressLabelsActive();
kernel.getConstruction().setSuppressLabelCreation(true);
GeoElement ge = kernel.getAlgebraProcessor().processEquation(this,
this.wrap(), new EvalInfo(false))[0];
kernel.getConstruction().setSuppressLabelCreation(oldFlag);
return ge;
}
@Override
public HashSet<GeoElement> getVariables() {
HashSet<GeoElement> leftVars = lhs.getVariables();
HashSet<GeoElement> rightVars = rhs.getVariables();
if (leftVars == null) {
return rightVars;
} else if (rightVars == null) {
return leftVars;
} else {
leftVars.addAll(rightVars);
return leftVars;
}
}
@Override
public boolean isConstant() {
return lhs.isConstant() && rhs.isConstant();
}
@Override
public boolean isLeaf() {
return false;
}
@Override
public boolean isNumberValue() {
return false;
}
@Override
public void resolveVariables(EvalInfo info) {
if ("X".equals(lhs.toString(StringTemplate.defaultTemplate))
&& kernel.lookupLabel("X") == null) {
return;
}
lhs.resolveVariables(info);
rhs.resolveVariables(info);
}
@Override
public String toLaTeXString(boolean symbolic, StringTemplate tpl) {
StringBuilder sb = new StringBuilder();
if (lhs != null) {
sb.append(lhs.toLaTeXString(symbolic, tpl));
} else {
sb.append('0');
}
sb.append(" = ");
if (rhs != null) {
sb.append(rhs.toLaTeXString(symbolic, tpl));
} else {
sb.append('0');
}
return sb.toString();
}
@Override
final public String toValueString(StringTemplate tpl) {
StringBuilder sb = new StringBuilder();
if (lhs != null) {
sb.append(lhs.toValueString(tpl));
} else {
sb.append('0');
}
if (tpl.getStringType().isGiac()) {
if (lhs.evaluatesToList() || rhs.evaluatesToList()) {
// %= stops {1,2}={3,4} being turned into {1=3,2=4}
sb.append("%=");
} else {
sb.append("=");
}
} else {
// equal sign
sb.append(" = ");
}
if (rhs != null) {
sb.append(rhs.toValueString(tpl));
} else {
sb.append('0');
}
return sb.toString();
}
@Override
public String toString(StringTemplate tpl) {
return toString(tpl, this.lhs);
}
/**
*
* @param tpl
* string template
* @param lhs1
* left hand side
* @return string description
*/
public String toString(StringTemplate tpl, ExpressionNode lhs1) {
StringBuilder sb = new StringBuilder();
// left hand side
if (lhs1 != null) {
sb.append(lhs1.toString(tpl));
} else {
sb.append('0');
}
if (tpl.getStringType().isGiac()) {
if (lhs1.evaluatesToList() || rhs.evaluatesToList()) {
// %= stops {1,2}={3,4} being turned into {1=3,2=4}
sb.append("%=");
} else {
sb.append("=");
}
} else {
// equal sign
sb.append(" = ");
}
// right hand side
if (rhs != null) {
sb.append(rhs.toString(tpl));
} else {
sb.append('0');
}
return sb.toString();
}
@Override
public String getAssignmentOperator() {
return ": ";
}
@Override
public String getAssignmentOperatorLaTeX() {
return ": \\, ";
}
@Override
public String toOutputValueString(StringTemplate tpl) {
return toValueString(tpl);
}
/**
* @return kernel
*/
public Kernel getKernel() {
return kernel;
}
@Override
public ExpressionValue traverse(Traversing t) {
ExpressionValue v = t.process(this);
if (v != this) {
return v;
}
lhs = lhs.traverse(t).wrap();
rhs = rhs.traverse(t).wrap();
return v;
}
@Override
public boolean inspect(Inspecting t) {
return t.check(this) || lhs.inspect(t) || rhs.inspect(t);
}
/**
*
* @return says if the original expression contains "z"
*/
public boolean containsZ() {
return containsVar(lhs, 'z') || containsVar(rhs, 'z');
}
private static final boolean containsVar(ExpressionValue v, char var) {
if (v == null) {
return false;
}
if (v instanceof ExpressionNode) {
ExpressionNode node = (ExpressionNode) v;
if (containsVar(node.getLeft(), var)) {
return true;
}
return containsVar(node.getRight(), var);
}
if (v instanceof FunctionVariable) {
String vVar = ((FunctionVariable) v).getSetVarString();
return vVar.length() == 1 && vVar.charAt(0) == var;
}
if (v instanceof MyVecNode) {
MyVecNode vec = (MyVecNode) v;
return containsVar(vec.getX(), var) || containsVar(vec.getY(), var);
}
if (v instanceof MyVec3DNode) {
MyVec3DNode vec = (MyVec3DNode) v;
return containsVar(vec.getX(), var) || containsVar(vec.getY(), var)
|| containsVar(vec.getZ(), var);
}
if (v instanceof MyList) {
MyList list = (MyList) v;
for (int i = 0; i < list.size(); i++) {
if (containsVar(list.getListElement(i), var)) {
return true;
}
}
return false;
}
if (v instanceof Polynomial) {
return !((Polynomial) v).isFreeOf(var);
}
return false;
}
/**
* @param gn
* sub-expression with variable degree
*/
public void addVariableDegree(ExpressionValue gn) {
if (this.variableDegrees == null) {
this.variableDegrees = new ArrayList<ExpressionValue>();
}
this.variableDegrees.add(gn);
}
/**
* @return whether this has terms with variable degree (x^n)
*/
public boolean hasVariableDegree() {
return variableDegrees != null;
}
/**
* @param b
* whether this may be polynomial
*/
public void setIsPolynomial(boolean b) {
this.isPolynomial = b;
}
/**
* @return whether this is currently polynomial, ie whether all terms are
* x^t where t is either non-negative integer variable or constant
*/
public boolean isPolynomial() {
if (!isPolynomial) {
return false;
}
if (this.variableDegrees == null) {
return true;
}
for (ExpressionValue ev : this.variableDegrees) {
double exp = ev.evaluateDouble();
if (!Kernel.isInteger(exp) || Kernel.isGreater(0, exp)) {
return false;
}
}
return true;
}
/**
* @return whether this may be polynomial, ie all terms are x^t but some t
* may be non-integer variable
*/
public boolean mayBePolynomial() {
return isPolynomial;
}
@Override
public ExpressionNode wrap() {
return new ExpressionNode(kernel, this);
}
@Override
public ValueType getValueType() {
return ValueType.EQUATION;
}
/**
* @return either this equation or assignment (eg i=7 is assignment; x=y,
* e=x are equations).
*/
public ValidExpression equationOrAssignment() {
if (!lhs.unwrap().isExpressionNode()
&& (!rhs.containsFreeFunctionVariable(null) || rhsHasLists())) {
// assignment, e.g. z = 23
if (lhs.isSingleVariable()) {
rhs.setLabel(((Variable) lhs
.evaluate(StringTemplate.defaultTemplate))
.getName(StringTemplate.defaultTemplate));
return rhs;
}
// special case: e = 2 should be an assignment
// but an undefined "e" has been read as the Euler constant already
else if (Unicode.EULER_STRING
.equals(lhs.toString(StringTemplate.defaultTemplate))) {
rhs.setLabel("e");
return rhs;
}
// special case: i = 2 should be an assignment
// but an undefined "i" has been read as the imaginary unit already
else if (lhs.isImaginaryUnit()) {
rhs.setLabel("i");
return rhs;
}
// special case: z = 2 should be an assignment when 3D view is not
// present
else if (kernel.isZvarAllowed() && "z"
.equals(lhs.toString(StringTemplate.defaultTemplate))) {
rhs.setLabel("z");
return rhs;
}
}
return this;
}
private boolean rhsHasLists() {
Inspecting check = new Inspecting() {
public boolean check(ExpressionValue v) {
if (!v.isExpressionNode()) {
return false;
}
ExpressionNode n = (ExpressionNode) v;
return n.getOperation() != Operation.FUNCTION_NVAR
&& n.getOperation() != Operation.ELEMENT_OF
&& (n.getLeft() instanceof MyList
|| n.getRight() instanceof MyList);
}
};
return rhs.inspect(check);
}
/**
* @return degree; overriden by forcedLine, forceConic, forceImplicitPoly
*/
public int preferredDegree() {
if (isForcedLine() || isForcedPlane()) {
return 1;
} else if (isForcedConic() || isForcedQuadric()) {
return 2;
} else if (isForcedImplicitPoly() || isForcedSurface()) {
return 7;
} else {
return degree();
}
}
@Override
public Equation getEquation() {
return this;
}
/**
* @param name
* var name
* @return whether such variable is free in this equation (eg for x(x=y)*x=1
* we have free x and y is bound inside nested equation)
*/
public boolean containsFreeFunctionVariable(String name) {
return lhs.containsFreeFunctionVariable(name)
|| rhs.containsFreeFunctionVariable(name);
}
/**
* @param sb
* xml builder
* @param string
* equation style
*/
public static void appendType(StringBuilder sb, String string) {
sb.append("\t<eqnStyle style=\"");
sb.append(string);
sb.append("\"/>\n");
}
} // end of class Equation