/*
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 private 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 org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.util.debug.HasDebugString;
/**
* An Polynomial is a list of Terms
*/
public class Polynomial implements HasDebugString {
private ArrayList<Term> terms = new ArrayList<Term>();
private Kernel kernel;
/**
* @param kernel
* kernel
*/
private Polynomial(Kernel kernel) {
this.kernel = kernel;
}
/**
* @param kernel
* kernel
* @param t
* single term
*/
Polynomial(Kernel kernel, Term t) {
this(kernel);
terms.add(t);
}
/**
* @param kernel
* kernel
* @param vars
* variables string (to create one term)
*/
Polynomial(Kernel kernel, String vars) {
this(kernel);
terms.add(new Term(kernel, 1.0d, vars));
}
/**
* @param kernel
* kernel
* @param poly
* polynomial to copy
*/
Polynomial(Kernel kernel, Polynomial poly) {
this(kernel);
// Application.debug("poly copy constructor input: " + poly);
for (int i = 0; i < poly.length(); i++) {
append(new Term(poly.terms.get(i), kernel));
}
// Application.debug("poly copy constructor output: " + this);
}
/**
* @param i
* index
* @return i-th term
*/
Term getTerm(int i) {
return terms.get(i);
}
/**
* @return number of terms
*/
int length() {
return terms.size();
}
/**
* @return true if there are no terms
*/
boolean isEmpty() {
return (terms.size() == 0);
}
/**
* Returns true if this polynomial equals "1 var"
*
* @param var
* variable name
* @return true if this polynomial equals "1 var"
*/
boolean isVar(String var) {
if (length() != 1) {
return false;
}
try {
Term t = terms.get(0);
return (t.getVars().equals(var) && t.getCoefficient().isConstant()
&& t.getCoefficient().evaluateDouble() == 1.0);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* append a single term
*/
private void append(Term t) {
terms.add(t);
}
/**
* add another Polynomial
*
* @param e
* addend
* @param eq
* equation to get feedback when simplification fails
*/
void add(Polynomial e, Equation eq) {
for (int i = 0; i < e.length(); i++) {
append(e.getTerm(i));
}
simplify(eq);
}
/**
* subtract another Polynomial
*
* @param e
* subtrahend
*/
private void sub(Polynomial e, Equation eq) {
Polynomial temp = new Polynomial(kernel, e);
temp.multiply(-1.0d);
add(temp, eq); // append -e
}
/**
* add a Number
*
* @param number
* constant addend
*/
private void add(ExpressionValue number, Equation equ) {
append(new Term(number, ""));
simplify(equ); // add up parts with same variables
}
/**
* subtract a Number
*
* @param number
* constant subtrahend
*/
private void sub(ExpressionValue number, Equation equ) {
Term subTerm = new Term(number, "");
subTerm.multiply(new MyDouble(kernel, -1.0d), kernel);
append(subTerm);
simplify(equ); // add up parts with same variables
}
/**
* multiply with another Polynomial store result in this Polynomial
*
* @param e
* factor
*/
private void multiply(Polynomial e, Equation equ) {
ArrayList<Term> temp = new ArrayList<Term>();
int i, j;
Term ti, newTerm;
// multiply every term of this Polynomial
// with every term of Polynomial e
for (i = 0; i < length(); i++) {
ti = getTerm(i);
for (j = 0; j < e.length(); j++) {
newTerm = new Term(ti, kernel);
newTerm.multiply(e.getTerm(j), kernel);
temp.add(newTerm);
}
}
terms = temp;
simplify(equ);
}
/**
* multiply every term with a double store result in this Polynomial
*
* @param number
* constant factor
*/
private void multiply(ExpressionValue number) {
for (int i = 0; i < length(); i++) {
terms.get(i).multiply(number, kernel);
}
}
/**
* divide every term with a ExpressionValue store result in this Polynomial
*
* @param number
* constant divisor
*/
private void divide(ExpressionValue number) {
for (int i = 0; i < length(); i++) {
getTerm(i).divide(number, kernel);
}
}
/**
* multiply every term with a double store result in this Polynomial
*
* @param d
* constant factor
*/
void multiply(double d) {
multiply(new MyDouble(kernel, d));
}
/**
* compute Polynomial^power store result in this Polynomial
*
* @param p
* exponent
*/
private void power(int p, Equation eq) {
if (p == 0) {
terms.clear(); // drop everything
append(new Term(new MyDouble(kernel, 1), ""));
return;
}
// correctness is handled elsewhere
if (p < 2 || p == Integer.MAX_VALUE) {
return;
}
Polynomial exp = new Polynomial(kernel, this);
multiply(exp, eq);
power(p / 2, eq);
if (MyDouble.isOdd(p)) {
multiply(exp, eq);
}
}
/**
* returns the sum of constant numbers in this Polynomial <BR>
* returns 0 if there is no constant number
*
* @return the sum of constant numbers in this Polynomial
*/
public ExpressionValue getConstantCoefficient() {
// Constants are coefficients without variables
return getCoefficient("");
}
/**
* returns the sum of coefficients of variables in this Polynomial <BR>
* returns 0 if variable does not occur <BR>
* example: 3x -5y getCoefficient("y") returns -5.0 <BR>
* 3x -72zz +5y +3zz getCoefficient("zz") returns -69.0 <BR>
*
* @param variables
* variables string
* @return coefficient
*/
public ExpressionValue getCoefficient(String variables) {
Term t, newTerm = new Term(new MyDouble(kernel, 0.0), variables);
// add all coefficients of the wanted variables
for (int i = 0; i < length(); i++) {
t = getTerm(i);
if (t.getVars().equals(variables)) {
newTerm.addToCoefficient(t.coefficient, kernel);
}
}
return newTerm.coefficient;
}
/**
* @param variables
* variables string
* @return coefficient value
*/
public double getCoeffValue(String variables) {
return getCoefficient(variables).evaluateDouble();
}
/**
* simplifies this Polynomial so that every variable only occurs once.
* example: simplify() on { (4,"xxy"), (7,"xy"), (-84.0,"xx"), (3,"xy") })
* changes the Polynomial to { (4,"xxy"), (10,"xy"), (-84.0,"xx") }
*
* @param eq
* equation to get feedback when simplification fails
*/
void simplify(Equation eq) {
// Application.debug("simplify " + this);
ArrayList<Term> list;
Object[] t;
Term ti, tj;
String vars;
int i, j, len;
list = new ArrayList<Term>(); // for the simplified terms
t = terms.toArray(); // copy term references to array
len = t.length;
// terms may contain terms with same variables
// example: {3 x, 5 y, -7 x} should be simplified to {-4 x, 5 y}
for (i = 0; i < len; i++) {
ti = (Term) t[i];
if (ti != null) {
vars = ti.getVars();
// search for terms with same variable part
for (j = i + 1; j < len; j++) {
tj = (Term) t[j];
if (tj != null && vars.equals(tj.getVars())) {
ti.addToCoefficient(tj.coefficient, kernel);
t[j] = null;
}
}
if (!(ti.coefficient.evaluate(
StringTemplate.defaultTemplate) instanceof NumberValue)) {
if (eq != null) {
eq.setIsPolynomial(false);
}
return;
/*
* throw new
* MyError(kernel.getApplication().getLocalization(),
* ti.coefficient
* .evaluate(StringTemplate.defaultTemplate).toString
* (StringTemplate.defaultTemplate));
*/
}
// add simplified term to list
if (!ti.coefficient.isConstant()
|| ti.coefficient.evaluateDouble() != 0.0) {
list.add(ti);
}
}
}
// if nothing is left, keep a term with 0
if (list.size() == 0) {
list.add(new Term(new MyDouble(kernel, 0.0), ""));
}
// sort the list
// java.util.Collections.sort( list );
terms = list;
// Application.debug("simplified to " + this);
}
/**
* @param var
* variable name
* @return true iff contains var
*/
boolean contains(String var) {
Iterator<Term> i = terms.iterator();
while (i.hasNext()) {
if (i.next().contains(var)) {
return true;
}
}
return false;
}
/**
* @return the degree of the Polynomial (max length of variables in a Term)
*/
public int degree() {
// a quadratic Polynomial may only have terms with one or two variables
// or constant terms
int deg = 0;
int varLen;
if (terms.size() == 0) {
return -1;
}
Iterator<Term> i = terms.iterator();
while (i.hasNext()) {
varLen = i.next().degree();
if (varLen > deg) {
deg = varLen;
}
}
return deg;
}
/**
* eg isFreeOf('z') to check no terms containing z, z^2 etc
*
* @param var
* variable name
* @return true if does not contain var
*/
public boolean isFreeOf(char var) {
if (terms.size() == 0) {
return true;
}
Iterator<Term> i = terms.iterator();
while (i.hasNext()) {
Term t = i.next();
if (t.degree(var) > 0) {
return false;
}
}
return true;
}
/**
* @return 3 for eg x^3 y^2
*/
int singleDegree() {
// a quadratic Polynomial may only have terms with one or two variables
// or constant terms
int deg = 0;
int varLen;
if (terms.size() == 0) {
return -1;
}
Iterator<Term> i = terms.iterator();
while (i.hasNext()) {
Term t = i.next();
varLen = t.degree('x');
if (varLen > deg) {
deg = varLen;
}
varLen = t.degree('y');
if (varLen > deg) {
deg = varLen;
}
varLen = t.degree('z');
if (varLen > deg) {
deg = varLen;
}
}
return deg;
}
@Override
public String toString() {
return "POLY" + toString(StringTemplate.defaultTemplate);
}
private String toString(StringTemplate tpl) {
int size = terms.size();
if (size == 0) {
return null;
}
StringBuilder sb = new StringBuilder();
String termStr;
boolean first = true;
for (int i = 0; i < size; i++) {
termStr = terms.get(i).toString(tpl);
if (termStr != null && termStr.length() > 0) {
if (first) {
sb.append(termStr);
first = false;
} else {
if (termStr.charAt(0) == '-') {
sb.append(" - ");
sb.append(termStr.substring(1));
} else {
sb.append(" + ");
sb.append(termStr);
}
}
}
}
return sb.toString();
}
/**
*
* @return Coefficient matrix of this polynomial (in x and y)
*/
public ExpressionValue[][] getCoeff() {
simplify(null);
Iterator<Term> it = terms.iterator();
// TODO implement support for z as var
int degX = 0;
int degY = 0;
while (it.hasNext()) {
Term t = it.next();
degX = Math.max(degX, t.degree('x'));
degY = Math.max(degY, t.degree('y'));
}
ExpressionValue[][] coeff = new ExpressionValue[degX + 1][degY + 1];
it = terms.iterator();
while (it.hasNext()) {
Term t = it.next();
coeff[t.degree('x')][t.degree('y')] = t.getCoefficient();
}
return coeff;
}
private HashSet<GeoElement> getVariables() {
HashSet<GeoElement> temp, vars = new HashSet<GeoElement>();
Iterator<Term> i = terms.iterator();
while (i.hasNext()) {
temp = i.next().getCoefficient().getVariables();
if (temp != null) {
vars.addAll(temp);
}
}
return vars;
}
/**
* @return whether this depends on geos
*/
public boolean isConstant() {
HashSet<GeoElement> vars = getVariables();
return (vars == null || vars.size() == 0);
}
/**
* Converts expression node to polynomial
*
* @param lhs
* expression to be converted
* @param eqn
* equation -- used for setting the dependsOnFunction flag
* @return polynomial
*/
static Polynomial fromNode(ExpressionNode lhs, Equation eqn) {
ExpressionNode leftEN = lhs.getCopy(lhs.getKernel());
Polynomial poly = leftEN.makePolynomialTree(eqn);
// Log.debug("Coefficients:");
// Log.debug(poly);
return poly;
}
/**
* Applies an operation
*
* @param operation
* operation
* @param rt
* second parameter
* @param equ
* equation to get feedback when simplification fails
* @return result as polynomial
*/
Polynomial apply(Operation operation, Polynomial rt, Equation equ) {
switch (operation) {
case PLUS:
this.add(rt, equ);
break;
case MINUS:
this.sub(rt, equ);
break;
case MULTIPLY_OR_FUNCTION:
case MULTIPLY:
this.multiply(rt, equ);
break;
case DIVIDE:
case POWER:
if (rt.degree() != 0) {
equ.setIsPolynomial(false);
return rt;
}
return apply(operation, rt.getConstantCoefficient(), equ);
default:
break;
}
return this;
}
/**
* Applies an operation
*
* @param operation
* operation
* @param rt
* second parameter
* @param equ
* equation to get feedback when simplification fails
* @return result as polynomial
*/
Polynomial apply(Operation operation, ExpressionValue rt, Equation equ) {
switch (operation) {
case PLUS:
this.add(rt, equ);
break;
case MINUS:
this.sub(rt, equ);
break;
case MULTIPLY_OR_FUNCTION:
case MULTIPLY:
this.multiply(rt);
break;
case POWER:
double power = rt.evaluateDouble();
if (Inspecting.dynamicGeosFinder.check(rt)) {
if (!(rt.evaluate(
StringTemplate.defaultTemplate) instanceof NumberValue)) {
equ.setIsPolynomial(false);
} else {
this.power((int) power, equ);
}
equ.addVariableDegree(rt);
} else if (this.degree() == 0) {
terms.get(0).coefficient = terms.get(0).coefficient.wrap()
.power(rt);
} else if (!Kernel.isInteger(power) || Kernel.isGreater(0, power)) {
equ.setIsPolynomial(false);
} else {
this.power((int) power, equ);
}
break;
case DIVIDE:
this.divide(rt);
break;
default:
break;
}
return this;
}
@Override
public String getDebugString() {
StringBuilder sb = new StringBuilder();
ExpressionValue[][] coeff = getCoeff();
for (int i = 0; i < coeff.length; i++) {
for (int j = 0; j < coeff[i].length; j++) {
sb.append(ValidExpression.debugString(coeff[i][j]));
sb.append('\n');
}
}
return sb.toString();
}
} // end of class Polynomial