/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package jmathexpr.arithmetic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.TreeMap;
import jmathexpr.AbstractExpression;
import jmathexpr.Expression;
import jmathexpr.Precedence;
import jmathexpr.Variable;
import jmathexpr.arithmetic.natural.NaturalNumber;
import jmathexpr.arithmetic.natural.Naturals;
import jmathexpr.arithmetic.op.Addition;
import jmathexpr.arithmetic.op.Division;
import jmathexpr.arithmetic.op.Exponentiation;
import jmathexpr.arithmetic.op.Multiplication;
import jmathexpr.arithmetic.op.Negation;
import jmathexpr.arithmetic.op.Subtraction;
import jmathexpr.arithmetic.op.Sum;
import jmathexpr.arithmetic.pattern.PolynomialTermPattern;
import jmathexpr.op.Sign;
import jmathexpr.set.OrderedPair;
import jmathexpr.set.Set;
import jmathexpr.util.pattern.ExpressionPattern;
import jmathexpr.util.pattern.TerminationPattern;
import jmathexpr.util.rule.Rule;
/**
* This class represents a univariate polynomial. Its general form is
* a<sub>n</sub>x<sup>n</sup> + ... + a<sub>1</sub>x + a<sub>0</sub>.
*
* In this implementation only the coefficients are stored in a NavigableMap<NaturalNumber, Expression>,
* where the keys correspond to the degree of the terms.
*
* @author Elemér Furka
*/
public class Polynomial extends AbstractExpression {
/**
* The indeterminate.
*/
private final Variable x;
/**
* The coefficients mapped to the order of the corresponding term.
* Thus a<sub>n</sub> is stored as n -> a<sub>n</sub>.
*/
private final NavigableMap<NaturalNumber, Expression> coeffs;
private final NaturalNumber ZERO = Naturals.zero();
private final NaturalNumber ONE = Naturals.one();
/**
* Creates a new polynomial of the given indeterminate and having the specified
* terms.
*
* @param terms list of terms
* @param x the indeterminate
*/
public Polynomial(List<Expression> terms, Variable x) {
this.x = x;
coeffs = extractCoefficients(terms, x);
}
private Polynomial(NavigableMap<NaturalNumber, Expression> coeffs, Variable x) {
this.x = x;
this.coeffs = coeffs;
}
/**
* Converts the specified expression into a polynomial instance.
*
* @param expr an expression that can be considered as a polynomial
* @param x the indeterminate
* @return the expression converted into a polynomial instance
*/
public static Polynomial create(Expression expr, Variable x) {
Polynomial p = convert(expr, x);
if (p != null) {
return p;
}
throw new IllegalArgumentException("Cannot convert expression into a polynomial: " + expr);
}
private static Polynomial convert(Expression expr, Variable x) {
if (expr.isConstant()) {
return Polynomial.toPolynomial(expr, x);
} else if (expr instanceof Variable) {
return toPolynomial((Variable) expr, x);
} else if (expr instanceof Multiplication) {
return Polynomial.toPolynomial((Multiplication) expr, x);
} else if (expr instanceof Addition) {
return ((Addition) expr).toPolynomial(x);
} else if (expr instanceof Subtraction) {
return ((Subtraction) expr).toPolynomial(x);
} else if (expr instanceof Sum) {
return ((Sum) expr).toPolynomial(x);
} else if (expr instanceof Negation) {
Polynomial p = create(((Negation) expr).getChild(), x);
return p.negate();
}
return null;
}
@Override
public Expression evaluate() {
Expression xvalue = x.evaluate();
if (xvalue instanceof ANumber) {
return evaluate((ANumber) xvalue);
}
NavigableMap<NaturalNumber, Expression> map = new TreeMap();
Expression c;
for (NaturalNumber n : coeffs.descendingKeySet()) {
c = coeffs.get(n).evaluate();
if (!(c instanceof ANumber && ((ANumber) c).isZero())) {
map.put(n, c);
}
}
if (map.isEmpty()) {
map.put(ZERO, ZERO);
}
return new Polynomial(map, x);
}
private Expression evaluate(ANumber xvalue) {
Expression sum = null;
Expression c, term;
for (NaturalNumber n : coeffs.descendingKeySet()) {
c = coeffs.get(n).evaluate();
term = new Multiplication(c, new Exponentiation(xvalue, n));
if (sum == null) {
sum = new Sum(term);
} else {
sum = Sum.add(sum, term);
}
}
return sum.evaluate();
}
/**
* Returns the discriminant of this polynomial. For a quadratic polynomial
* ax^2 + bx + c it returns b^2 - 4ac.
*
* @return the discriminant of this polynomial
*/
public Expression discriminant() {
NaturalNumber two = Naturals.getInstance().create(2);
NaturalNumber four = Naturals.getInstance().create(4);
if (degree().equals(two)) {
Expression a = coeffs.get(two);
Expression b = coeffs.get(ONE);
Expression c = coeffs.get(ZERO);
return new Subtraction(new Exponentiation(b, two),
new Multiplication(four, new Multiplication(a, c)));
} else {
throw new UnsupportedOperationException("Cannot compute discriminant: " + this);
}
}
@Override
public boolean isConstant() {
return coeffs.lastKey().isZero();
}
@Override
public boolean contains(ExpressionPattern pattern) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public boolean isApplicable(Rule rule) {
boolean matches = rule.matches(this);
if (matches) {
rule.register(this);
}
return matches;
}
@Override
public Precedence getPrecedence() {
return Precedence.Addition;
}
/**
* Returns the coefficient of the term of the specified degree.
*
* @param degree the degree of the term
* @return the coefficient
*/
public Expression getCoefficient(long degree) {
Expression c = coeffs.get(Naturals.getInstance().create(degree));
return c != null ? c : ZERO;
}
/**
* Returns the term of the given degree or 0 if no such term exists.
*
* @param degree the degree of the requested term
* @return the requested term or 0
*/
public Expression getTerm(NaturalNumber degree) {
Expression c = coeffs.get(degree);
if (c != null) {
if (degree.equals(ZERO)) {
return c;
} else if (degree.equals(ONE)) {
return new Multiplication(c, x);
} else {
return new Multiplication(c, new Exponentiation(x, degree));
}
} else {
return ZERO;
}
}
/**
* Returns the lead term, the term that has the highest degree.
*
* @return the lead term
*/
public Expression lead() {
return getTerm(coeffs.lastKey());
}
/**
* Returns the coefficient of the lead term.
*
* @return the coefficient of the lead term
*/
public Expression leadCoefficient() {
return coeffs.lastEntry().getValue();
}
/**
* Performs an Euclidean division so that this polynomial A = BQ + R.
*
* @param b the divisor B
* @return the ordered pair (Q, R)
*/
public OrderedPair euclideanDivision(Polynomial b) {
if (b.isZero()) {
throw new IllegalArgumentException("Cannot divide by 0. Dividend: " + this);
}
NavigableMap<NaturalNumber, Expression> q = new TreeMap(); // quotient
NavigableMap<NaturalNumber, Expression> r = new TreeMap(); // reminder
NaturalNumber bdeg = b.degree();
Expression bc = b.leadCoefficient();
Expression c; // next coefficient
NaturalNumber n; // degree of the next term in q
Expression rp, bi; // temporary variables
NaturalNumber p; //temporary position
q.put(ZERO, ZERO);
r.putAll(this.coeffs);
while (!isZero(r) && bdeg.le(r.lastKey())) {
c = new Division(r.lastEntry().getValue(), bc).evaluate();
n = (NaturalNumber) r.lastKey().subtract(bdeg);
q.put(n, c);
for (NaturalNumber i : b.coeffs.descendingKeySet()) {
p = (NaturalNumber) n.add(i);
rp = r.get(p);
bi = b.coeffs.get(i);
rp = rp != null ? new Subtraction(rp, new Multiplication(c, bi)).evaluate()
: new Negation(new Multiplication(c, bi)).evaluate();
if (rp instanceof ANumber && ((ANumber) rp).isZero() && !p.isZero()) {
r.remove(p);
} else {
r.put(p, rp);
}
}
}
if (r.isEmpty()) {
r.put(ZERO, ZERO);
}
return new OrderedPair(new Polynomial(q, x), new Polynomial(r, x));
}
/**
* Subtract the specified polynomial (Q(X)) from this one (P(X)).
*
* @param subtrahend the subtrahend polynomial
* @return P(X) - Q(X)
*/
public Polynomial subtract(Polynomial subtrahend) {
NavigableMap<NaturalNumber, Expression> map = new TreeMap();
Expression minuend, c;
map.putAll(coeffs);
for (NaturalNumber n : subtrahend.coeffs.keySet()) {
minuend = coeffs.get(n);
if (minuend != null) {
c = new Subtraction(minuend, subtrahend.coeffs.get(n)).evaluate();
} else {
c = new Negation(subtrahend.coeffs.get(n)).evaluate();
}
if (c instanceof ANumber && ((ANumber) c).isZero()) {
map.remove(n);
} else {
map.put(n, c);
}
}
return new Polynomial(map, x);
}
/**
* Subtract an arbitrary expression from this polynomial. The result is a
* polynomial iff the subtrahend is (or can be converted to) also a polynomial.
*
* @param expr an arbitrary expression
* @return the result of the subtraction
*/
public Expression subtract(Expression expr) {
Polynomial p = convert(expr, x);
if (p != null) {
return subtract(p);
} else {
return new Subtraction(this, expr);
}
}
/**
* Performs a division with the given expression as the divisor.
*
* @param expr an arbitrary expression
* @return a new polynomial if the division can be performed or a new
* Division instance
*/
public Expression divide(Expression expr) {
if (expr.isConstant()) {
return divideByConstant(expr);
} else {
throw new UnsupportedOperationException("Division not yet supported: " + expr);
}
}
private Polynomial divideByConstant(Expression expr) {
NavigableMap<NaturalNumber, Expression> map = new TreeMap();
Expression c;
for (NaturalNumber n : coeffs.keySet()) {
c = coeffs.get(n);
map.put(n, new Division(c, expr).evaluate());
}
return new Polynomial(map, x);
}
/**
* Negates this polynomial by negating all coefficients.
*
* @return the negated polynomial
*/
public Polynomial negate() {
NavigableMap<NaturalNumber, Expression> map = new TreeMap();
Expression c;
for (NaturalNumber n : coeffs.keySet()) {
c = coeffs.get(n);
if (c instanceof ANumber) {
map.put(n, ((ANumber) c).negate());
} else {
map.put(n, new Negation(c));
}
}
return new Polynomial(map, x);
}
public Polynomial multiply(Expression multiplier) {
if (multiplier.isConstant()) {
NavigableMap<NaturalNumber, Expression> multiplied = new TreeMap();
for (NaturalNumber n : coeffs.keySet()) {
multiplied.put(n, new Multiplication(coeffs.get(n), multiplier).evaluate());
}
return new Polynomial(multiplied, x);
} else
throw new UnsupportedOperationException("Missing implementation: " + multiplier);
}
/**
* Returns the degree of this polynomial.
*
* @return the degree of this polynomial
*/
public NaturalNumber degree() {
return coeffs.lastKey();
}
/**
* Tests if this polynomial is the zero polynomial.
*
* @return true if this instance only consists of a zero constant term
*/
public boolean isZero() {
return isZero(coeffs);
}
private boolean isZero(NavigableMap<NaturalNumber, Expression> map) {
for (Expression t : map.values()) {
if (!(t instanceof ANumber) || !((ANumber) t).isZero()) {
return false;
}
}
return true;
}
@Override
public List<Expression> getChildren() {
List<Expression> children = new ArrayList();
for (NaturalNumber n : coeffs.descendingKeySet()) {
children.add(getTerm(n));
}
return Collections.unmodifiableList(children);
}
/**
* Tests if this polynomial matches the given addition.
*
* @param addition an addition pattern
* @return true if this polynomial matches the addition
*/
public boolean matches(Addition addition) {
if (coeffs.size() == 2) {
Expression lhs = getTerm(coeffs.lastKey());
Expression rhs = getTerm(coeffs.firstKey());
return ((ExpressionPattern) addition.lhs()).matches(lhs)
&& ((ExpressionPattern) addition.rhs()).matches(rhs);
} else {
return false;
}
}
/**
* Tests if this polynomial matches the given product.
*
* @param multiplication the product pattern
* @return true if this polynomial matches the pattern
*/
public boolean matches(Multiplication multiplication) {
if (coeffs.size() == 1) {
Expression term = getTerm(coeffs.lastKey());
return multiplication.matches(term);
} else {
return false;
}
}
/**
* Tests if this polynomial matches the given variable.
*
* @param var a variable pattern
* @return true if this polynomial has exactly one term x (= 1*x^1) and it
* equals to the given variable
*/
public boolean matches(Variable var) {
if (coeffs.size() == 1) {
Expression c = leadCoefficient();
return c instanceof ANumber && ((ANumber) c).isOne() && var.matches(x);
} else {
return false;
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
Expression c;
for (NaturalNumber n : coeffs.descendingKeySet()) {
c = coeffs.get(n);
if (result.length() == 0) {
result.append(toString(n, c));
} else if (c instanceof ANumber) {
if (((ANumber) c).isNegative()) {
c = ((ANumber) c).negate();
result.append(" - " + toString(n, c));
} else {
result.append(" + " + toString(n, c));
}
} else {
result.append(" + " + toString(n, c));
}
}
return result.toString();
}
private String toString(NaturalNumber n, Expression c) {
if (n.isZero()) {
return c.toString();
} else if (n.isOne()) {
if (c instanceof ANumber && ((ANumber) c).isOne()) {
return x.toString();
} else {
return String.format("%s%s", c, x);
}
} else if (c instanceof ANumber && ((ANumber) c).isOne()) {
return String.format("%s%s%s", x, Sign.Exponentiation, n);
} else {
return String.format("%s%s%s%s", c, x, Sign.Exponentiation, n);
}
}
@Override
public String toUnicode() {
return toString();
}
@Override
public boolean equals(Object object) {
if (object == null) return false;
if (this == object) return true;
if (object instanceof Polynomial) {
Polynomial other = (Polynomial) object;
return x.equals(other.x) && coeffs.equals(other.coeffs);
}
return false;
}
@Override
public int hashCode() {
int hash = 3;
hash = 67 * hash + Objects.hashCode(this.x);
hash = 67 * hash + Objects.hashCode(this.coeffs);
return hash;
}
private NavigableMap<NaturalNumber, Expression> extractCoefficients(
List<Expression> terms, Variable x) {
NavigableMap<NaturalNumber, Expression> map = new TreeMap(); // order -> coefficient
PolynomialTermPattern pt = new PolynomialTermPattern(x);
NaturalNumber n;
Expression c, c0;
for (Expression t : terms) {
if (pt.matches(t)) {
n = pt.exponent();
c = pt.coefficient();
} else {
throw new IllegalArgumentException(
String.format("Illegal polynomial term: %s (%s)", t, t.getClass()));
}
c0 = map.get(n);
if (c0 != null) {
c = new Addition(c0, c).evaluate();
}
map.put(n, c);
}
return map;
}
private static Polynomial toPolynomial(Multiplication term, Variable x) {
NavigableMap<NaturalNumber, Expression> coeffs = new TreeMap();
TerminationPattern c = Numbers.constant("c");
TerminationPattern n = Numbers.constant("n");
ExpressionPattern cxn = new Multiplication(c, new Exponentiation(x, n));
if (cxn.matches(term)) {
coeffs.put((NaturalNumber) n.hit(), c.hit());
} else {
throw new IllegalArgumentException("Illegal polynomial term: " + term);
}
return new Polynomial(coeffs, x);
}
private static Polynomial toPolynomial(Variable v, Variable x) {
if (v.equals(x)) {
NavigableMap<NaturalNumber, Expression> coeffs = new TreeMap();
coeffs.put(Naturals.one(), Naturals.one());
return new Polynomial(coeffs, x);
} else {
return Polynomial.toPolynomial((Expression) v, x);
}
}
private static Polynomial toPolynomial(Expression c, Variable x) {
NavigableMap<NaturalNumber, Expression> coeffs = new TreeMap();
coeffs.put(Naturals.zero(), c);
return new Polynomial(coeffs, x);
}
@Override
public Set domain() {
return x.domain();
}
@Override
public Set codomain() {
return x.codomain();
}
}