package org.nevec.rjm; import java.math.BigInteger; import java.math.MathContext; import java.math.RoundingMode; import java.util.Random; import java.util.Scanner; import java.util.Vector; import it.cavallium.warppi.util.Error; /** * A one-parameter polynomial with rational coefficients. * Alternatively to be interpreted as a sequence which has the polynomial as an * (approximate) * generating function. * * @since 2006-06-25 * @author Richard J. Mathar */ class RatPoly { /** * The list of all coefficients, ascending exponents. Starting with a0, then * a1, representing * a value a0+a1*x+a2*x^2+a3*x^3+... */ protected Vector a; /** * Default ctor. * Initializes the zero-valued polynomial x=0. */ public RatPoly() { a = new Vector<>(); } /* ctor */ /** * Constructor with an explicit list of coefficients. * * @param L * the coefficients a0, a1, a2, a3,.., A deep copy of the these * is created. */ public RatPoly(final Vector L) { a = new Vector<>(); for (int i = 0; i < L.size(); i++) { a.add(L.elementAt(i).clone()); } simplify(); } /* ctor */ /** * Constructor with a comma-separated list as the list of coefficients. * * @param L * the string of the form a0,a1,a2,a3 with the coefficients */ public RatPoly(final String L) throws NumberFormatException { a = new Vector<>(); final Scanner sc = new Scanner(L); sc.useDelimiter(","); while (sc.hasNext()) { final String tok = sc.next(); a.add(new Rational(tok)); } simplify(); sc.close(); } /* ctor */ /** * Constructor from a hypergeometric series. * * @param A * the list of values in the numerator of AFB * @param B * the list of values in the denominator of AFB * @param nmax * the order of the truncated polynomial representation * @throws Error * @since 2008-11-13 */ public RatPoly(final Vector A, final Vector B, final int nmax) throws Error { /* * To allow common initialization with the signature below, * the main body is assembled in a separate function. */ init(A, B, nmax); } /** * Constructor from a hypergeometric series. * * @param A * the list of values in the numerator of AFB. * At least one of these values must be a negative integer, which * implicitly determines * the order of the new polynomial. * @param B * the list of values in the denominator of AFB * @throws Error * @since 2009-08-05 */ public RatPoly(final Vector A, final Vector B) throws Error { BigInteger Nmax = BigInteger.ONE.negate(); for (int j = 0; j < A.size(); j++) { if (A.elementAt(j).compareTo(BigInteger.ZERO) <= 0) { if (Nmax.compareTo(BigInteger.ZERO) < 0) { Nmax = A.elementAt(j).negate(); } else { Nmax = Nmax.min(A.elementAt(j).negate()); } } } if (Nmax.compareTo(BigInteger.ZERO) < 0) { throw new ArithmeticException("Infinite Number of Terms in Series " + Nmax.toString()); } final int nmax = Nmax.intValue() - 1; init(A, B, nmax); } /* ctor */ /** * Constructor from a hypergeometric series. * * @param A * the list of values in the numerator of AFB * @param B * the list of values in the denominator of AFB * @param nmax * the order of the truncated polynomial representation * @throws Error * @since 2008-11-13 */ protected void init(final Vector A, final Vector B, final int nmax) throws Error { a = new Vector<>(); final Factorial f = new Factorial(); for (int n = 0; n <= nmax; n++) { Rational c = new Rational(1, 1); for (int j = 0; j < A.size(); j++) { final Rational aEl = new Rational(A.elementAt(j)); c = c.multiply(aEl.Pochhammer(n)); } for (int j = 0; j < B.size(); j++) { final Rational bEl = new Rational(B.elementAt(j)); c = c.divide(bEl.Pochhammer(n)); } c = c.divide(f.at(n)); a.add(c); } simplify(); } /* init */ /** * Create a copy of this. * * @since 2008-11-07 */ @Override @SuppressWarnings("unchecked") public RatPoly clone() { final RatPoly clo = new RatPoly(); clo.a = (Vector) a.clone(); return clo; } /* clone */ /** * Retrieve a polynomial coefficient. * * @param n * the zero-based index of the coefficient. n=0 for the constant * term. * @return the polynomial coefficient in front of x^n. */ public Rational at(final int n) { if (n < a.size()) { return a.elementAt(n); } else { return new Rational(0, 1); } } /* at */ /** * Horner scheme to find the function value at the argument x * * @param x * The argument of the polynomial * @param mc * The context determining the precision of the value returned. * @since 2008-10-26 */ public BigComplex valueOf(final BigComplex x, final MathContext mc) { /* result is initialized to zero */ BigComplex f = new BigComplex(); for (int i = degree(); i >= 0; i--) { f = f.multiply(x, mc).add(a.elementAt(i).BigDecimalValue(mc)); } return f; } /* valueOf */ /** * Horner scheme to find the function value at the argument x * * @param x * The argument of the polynomial * @since 2008-11-13 */ public Rational valueOf(final Rational x) { /* result is initialized to zero */ Rational f = new Rational(0, 1); for (int i = degree(); i >= 0; i--) { f = f.multiply(x).add(a.elementAt(i)); } return f; } /* valueOf */ /** * Horner scheme to find the function value at the argument x * * @param x * The argument of the polynomial * @since 2008-11-13 */ public Rational valueOf(final int x) { return valueOf(new Rational(x, 1)); } /* valueOf */ /** * Horner scheme to evaluate the function at the argument x * * @param x * The argument of the polynomial * @since 2010-08-27 */ public Rational valueOf(final BigInteger x) { return valueOf(new Rational(x)); } /* valueOf */ /* * Set a polynomial coefficient. * * @param n the zero-based index of the coefficient. n=0 for the constant * term. * If the polynomial has not yet the degree to need this coefficient, * the intermediate coefficients are implicitly set to zero. * * @param value the new value of the coefficient. */ public void set(final int n, final Rational value) { if (n < a.size()) { a.set(n, value); } else { /* * fill intermediate powers with coefficients of zero */ while (a.size() < n) { a.add(new Rational(0, 1)); } a.add(value); } } /* set */ /** * Set a polynomial coefficient. * * @param n * the zero-based index of the coefficient. n=0 for the constant * term. * If the polynomial has not yet the degree to need this * coefficient, * the intermediate coefficients are implicitly set to zero. * @param value * the new value of the coefficient. */ public void set(final int n, final BigInteger value) { final Rational val2 = new Rational(value, BigInteger.ONE); set(n, val2); } /* set */ /** * Set a polynomial coefficient. * * @param n * the zero-based index of the coefficient. n=0 for the constant * term. * If the polynomial has not yet the degree to need this * coefficient, * the intermediate coefficients are implicitly set to zero. * @param value * the new value of the coefficient. */ public void set(final int n, final int value) { final Rational val2 = new Rational(value, 1); set(n, val2); } /* set */ /* * Set to the taylor series of exp(x) up to degree nmax. * * @param nmax the maximum polynomial degree */ public void setExp(final int nmax) { a.clear(); final Factorial factorial = new Factorial(); for (int n = 0; n <= nmax; n++) { set(n, new Rational(BigInteger.ONE, factorial.at(n))); } } /* setExp */ /** * Set to the taylor series representing 0+x. */ public void setx() { a.clear(); /* coefficient 0/1=0 */ a.add(new Rational(0, 1)); /* coefficient 1/1=1 */ a.add(new Rational(1, 1)); } /* setx */ /** * Count of coefficients. One more than the degree of the polynomial. * * @return the number of polynomial coefficients. */ public int size() { return a.size(); } /* size */ /** * Polynomial degree. * * @return the polynomial degree. */ public int degree() { return a.size() - 1; } /* degree */ /** * Lower Polynomial degree. * * @return The smallest exponent n such that [x^n] of the polynomial is * nonzero. * If the polynmial is identical zero, the result is (still) 0. * @since 2010-08-27 */ public int ldegree() { for (int n = 0; n < a.size(); n++) { if (a.elementAt(n).compareTo(BigInteger.ZERO) != 0) { return n; } } return 0; } /* ldegree */ /** * Multiply by a constant factor. * * @param val * the factor * @return the product of this with the factor. * All coefficients of this have been multiplied individually by the * factor. */ public RatPoly multiply(final Rational val) { final RatPoly resul = new RatPoly(); if (val.compareTo(BigInteger.ZERO) != 0) { for (int n = 0; n < a.size(); n++) { resul.set(n, a.elementAt(n).multiply(val)); } } return resul; } /* multiply */ /** * Multiply by a constant factor. * * @param val * the factor * @return the product of this with the factor. * All coefficients of this have been multiplied individually by the * factor. * @since 2010-08-27 */ public RatPoly multiply(final BigInteger val) { final RatPoly resul = new RatPoly(); if (val.compareTo(BigInteger.ZERO) != 0) { for (int n = 0; n < a.size(); n++) { resul.set(n, a.elementAt(n).multiply(val)); } } return resul; } /* multiply */ /** * Multiply by another polynomial * * @param val * the other polynomial * @return the product of this with the other polynomial */ public RatPoly multiply(final RatPoly val) { final RatPoly resul = new RatPoly(); /* * the degree of the result is the sum of the two degrees. */ final int nmax = degree() + val.degree(); for (int n = 0; n <= nmax; n++) { Rational coef = new Rational(0, 1); for (int nleft = 0; nleft <= n; nleft++) { coef = coef.add(at(nleft).multiply(val.at(n - nleft))); } resul.set(n, coef); } resul.simplify(); return resul; } /* multiply */ /** * Raise to a positive power. * * @param n * The non-negative exponent of the power * @return The n-th power of this. */ public RatPoly pow(final int n) throws ArithmeticException { RatPoly resul = new RatPoly("1"); if (n < 0) { throw new ArithmeticException("negative polynomial power " + n); } else { /* * this ought probably be done with some binary representation * of the power and a smaller number of multiplications. */ for (int i = 1; i <= n; i++) { resul = resul.multiply(this); } resul.simplify(); return resul; } } /* pow */ /** * Raise to a rational power. * The result is the taylor expansion of this, truncated at the first * term that remains undetermined based on the current number of * coefficients. * * @param r * the exponent of the power * @return This^r . * @throws Error * @since 2009-05-18 */ public RatPoly pow(final Rational r) throws ArithmeticException, Error { /* * split (a0+a1*x+a2*x^2+...)^r = a0^r*(1+a1/a0*r+a2/a0*r^2+..)^r */ Rational f = at(0); f = f.pow(r); /* * scale the polynomial by division through the expansion coefficient of * the absolute term */ final RatPoly red = divide(a.elementAt(0)); /* * and remove the leading term (now equal to 1) */ red.set(0, 0); /* * Binomial expansion of the rest. sum_{l=0..infinity} * binomial(r,l)*red^l */ RatPoly resul = new RatPoly("1"); final int d = degree(); for (int l = 1; l <= d; l++) { final Rational b = Rational.binomial(r, l); resul = resul.add(red.pow(l).multiply(b)); } return resul.multiply(f); } /* pow */ /** * Add another polynomial * * @param val * The other polynomial * @return The sum of this and the other polynomial * @since 2008-10-25 */ public RatPoly add(final RatPoly val) { final RatPoly resul = new RatPoly(); /* * the degree of the result is the larger of the two degrees (before * simplify() at least). */ final int nmax = degree() > val.degree() ? degree() : val.degree(); for (int n = 0; n <= nmax; n++) { final Rational coef = at(n).add(val.at(n)); resul.set(n, coef); } resul.simplify(); return resul; } /* add */ /** * Subtract another polynomial * * @param val * The other polynomial * @return The difference between this and the other polynomial * @since 2008-10-25 */ public RatPoly subtract(final RatPoly val) { final RatPoly resul = new RatPoly(); /* * the degree of the result is the larger of the two degrees (before * simplify() at least). */ final int nmax = degree() > val.degree() ? degree() : val.degree(); for (int n = 0; n <= nmax; n++) { final Rational coef = at(n).subtract(val.at(n)); resul.set(n, coef); } resul.simplify(); return resul; } /* subtract */ /** * Divide by a constant. * * @param val * the constant through which the coefficients will be divided. * @return the Taylor expansion of this/val . * @throws Error * @since 2009-05-18 */ public RatPoly divide(final Rational val) throws Error { if (val.compareTo(Rational.ZERO) != 0) { final RatPoly resul = new RatPoly(); for (int n = 0; n < a.size(); n++) { resul.set(n, a.elementAt(n).divide(val)); } return resul; } else { throw new ArithmeticException("Cannot divide " + toPString() + " through zero."); } } /* divide */ /** * Divide by another polynomial. * * @param val * the other polynomial * @param nmax * the maximum degree of the Taylor expansion of the result. * @return the Taylor expansion of this/val up to degree nmax. * @throws Error */ public RatPoly divide(final RatPoly val, final int nmax) throws Error { final RatPoly num = this; final RatPoly denom = val; /* * divide by a common smallest power/degree */ while (num.at(0).compareTo(BigInteger.ZERO) == 0 && denom.at(0).compareTo(BigInteger.ZERO) == 0) { num.a.remove(0); denom.a.remove(0); if (num.size() <= 1 || denom.size() <= 1) { break; } } final RatPoly resul = new RatPoly(); /* * todo: If the polynomial division is exact, we could leave * the loop earlier, indeed */ for (int n = 0; n <= nmax; n++) { Rational coef = num.at(n); for (int nres = 0; nres < n; nres++) { coef = coef.subtract(resul.at(nres).multiply(denom.at(n - nres))); } coef = coef.divide(denom.at(0)); resul.set(n, coef); } resul.simplify(); return resul; } /* divide */ /** * Divide by another polynomial. * * @param val * the other polynomial * @return A vector with [0] containg the polynomial of degree which is the * difference of thisdegree and the degree of val. [1] the remainder * polynomial. * This = returnvalue[0] + returnvalue[1]/val . * @throws Error * @since 2012-03-01 */ public RatPoly[] divideAndRemainder(final RatPoly val) throws Error { final RatPoly[] ret = new RatPoly[2]; /* * remove any high-order zeros */ final RatPoly valSimpl = val.clone(); valSimpl.simplify(); final RatPoly thisSimpl = clone(); thisSimpl.simplify(); /* * catch the case with val equal to zero */ if (valSimpl.degree() == 0 && valSimpl.a.firstElement().compareTo(Rational.ZERO) == 0) { throw new ArithmeticException("Division through zero polynomial"); } /* * degree of this smaller than degree of val: remainder is this */ if (thisSimpl.degree() < valSimpl.degree()) { /* * leading polynomial equals zero */ ret[0] = new RatPoly(); ret[1] = thisSimpl; } else { /* * long division. Highest degree by dividing the highest degree * of this thru val. */ ret[0] = new RatPoly(); ret[0].set(thisSimpl.degree() - valSimpl.degree(), thisSimpl.a.lastElement().divide(valSimpl.a.lastElement())); /* * recurrences: build this - val*(1-termresult) and feed this * into another round of division. Have intermediate * ret[0]+ret[1]/val. */ ret[1] = thisSimpl.subtract(ret[0].multiply(valSimpl)); /* * any remainder left ? */ if (ret[1].degree() < valSimpl.degree()) { ; } else { final RatPoly rem[] = ret[1].divideAndRemainder(val); ret[0] = ret[0].add(rem[0]); ret[1] = rem[1]; } } return ret; } /* divideAndRemainder */ /** * Print as a comma-separated list of coefficients. * * @return The representation a0,a1,a2,a3,... * This is a sort of opposite of the ctor that takes a string as an * argument. * @since 2008-10-25 */ @Override public String toString() { String str = new String(); for (int n = 0; n < a.size(); n++) { if (n == 0) { str += a.elementAt(n).toString(); } else { str += "," + a.elementAt(n).toString(); } } /* * print at least a sole zero */ if (str.length() == 0) { str = "0"; } return str; } /* toString */ /** * Print as a polyomial in x. * * @return To representation a0+a1*x+a2*x^2+... * This does not print the terms with coefficients equal to zero. * @since 2008-10-26 */ public String toPString() { String str = new String(); for (int n = 0; n < a.size(); n++) { final BigInteger num = a.elementAt(n).a; if (num.compareTo(BigInteger.ZERO) != 0) { str += " "; if (num.compareTo(BigInteger.ZERO) > 0) { str += "+"; } str += a.elementAt(n).toString(); if (n > 0) { str += "*x"; if (n > 1) { str += "^" + n; } } } } /* * print at least a sole zero */ if (str.length() == 0) { str = "0"; } return str; } /* toPString */ /** * Simplify the representation. * Trailing values with zero coefficients (at high powers) are deleted. * This modifies the polynomial on the stop (does not return another * instance) */ private void simplify() { int n = a.size() - 1; if (n >= 0) { while (a.elementAt(n).compareTo(BigInteger.ZERO) == 0) { a.remove(n); if (--n < 0) { break; } } } } /* simplify */ /** * First derivative. * * @return The first derivative with respect to the indeterminate variable. * @since 2008-10-26 */ public RatPoly derive() { if (a.size() <= 1) { /* * derivative of the constant is just zero */ return new RatPoly(); } else { final RatPoly d = new RatPoly(); for (int i = 1; i <= degree(); i++) { final Rational c = a.elementAt(i).multiply(i); d.set(i - 1, c); } return d; } } /* derive */ /** * Scale coefficients such that the coefficient in front of the maximum * degree is unity. * * @return The scaled polynomial * @throws Error * @since 2008-10-26 */ public RatPoly monic() throws Error { final RatPoly m = new RatPoly(); final int d = degree(); for (int i = 0; i <= d; i++) { final Rational c = a.elementAt(i).divide(a.elementAt(d)); m.set(i, c); } return m; } /* monic */ /** * Mobius transform. * * @param maxdeg * the maximum polynomial degree of the result * @return the sequence of coefficients is the Mobius transform of the * original sequence. * @since 2008-12-02 */ public RatPoly mobiusT(final int maxdeg) { /* * Start with the polynomial 0 */ final RatPoly r = new RatPoly(); for (int i = 1; i <= maxdeg; i++) { Rational c = new Rational(); for (int d = 1; d <= i && d < a.size(); d++) { if (i % d == 0) { final Ifactor m = new Ifactor(i / d); c = c.add(a.elementAt(d).multiply(m.moebius())); } } r.set(i, c); } r.simplify(); return r; } /* mobiusT */ /** * Inverse Mobius transform. * * @param maxdeg * the maximum polynomial degree of the result * @return the sequence of coefficients is the inverse Mobius transform of * the original sequence. * @since 2008-12-02 */ public RatPoly mobiusTInv(final int maxdeg) { /* * Start with the polynomial 0 */ final RatPoly r = new RatPoly(); for (int i = 1; i <= maxdeg; i++) { Rational c = new Rational(); for (int d = 1; d <= i && d < a.size(); d++) { if (i % d == 0) { c = c.add(a.elementAt(d)); } } r.set(i, c); } r.simplify(); return r; } /* mobiusTInv */ /** * Binomial transform. * * @param maxdeg * the maximum polynomial degree of the result * @return the sequence of coefficients is the binomial transform of the * original sequence. * @since 2008-10-26 */ public RatPoly binomialT(final int maxdeg) { final RatPoly r = new RatPoly(); for (int i = 0; i <= maxdeg; i++) { Rational c = new Rational(0, 1); for (int j = 0; j <= i && j < a.size(); j++) { c = c.add(a.elementAt(j).multiply(BigIntegerMath.binomial(i, j))); } r.set(i, c); } r.simplify(); return r; } /* binomialT */ /** * Inverse Binomial transform. * * @param maxdeg * the maximum polynomial degree of the result * @return the sequence of coefficients is the inverse binomial transform of * the original sequence. * @since 2008-10-26 */ public RatPoly binomialTInv(final int maxdeg) { final RatPoly r = new RatPoly(); for (int i = 0; i <= maxdeg; i++) { Rational c = new Rational(0, 1); for (int j = 0; j <= i && j < a.size(); j++) { if ((j + i) % 2 != 0) { c = c.subtract(a.elementAt(j).multiply(BigIntegerMath.binomial(i, j))); } else { c = c.add(a.elementAt(j).multiply(BigIntegerMath.binomial(i, j))); } } r.set(i, c); } r.simplify(); return r; } /* binomialTInv */ /** * Truncate polynomial degree. * * @param newdeg * The maximum degree of the result. * @return The polynomial with all coefficients beyond deg set to zero. * If newdeg =3, for example the polynomial returned has at most * degree 3. * If newdeg is larger than the degree of this, zeros (at the higher * orders of x) * are appended. That polynomial would have formal degree larger * than this. * @since 2008-10-26 */ public RatPoly trunc(final int newdeg) { final RatPoly t = new RatPoly(); for (int i = 0; i <= newdeg; i++) { t.set(i, at(i)); } t.simplify(); return t; } /* trunc */ /** * Generate the roots of the polynomial in floating point arithmetic. * * @see Durand * Kerner method * @param the * number of floating point digits * @throws Error * @since 2008-10-26 */ public Vector roots(final int digits) throws Error { final RatPoly mon = monic(); final Random rand = new Random(); final MathContext mc = new MathContext(digits + 3, RoundingMode.DOWN); Vector res = new Vector<>(); final int d = mon.degree(); double randRad = 0.; for (int i = 0; i <= d; i++) { /* scale coefficient at maximum degree */ final double absi = Math.abs(mon.at(i).doubleValue()); if (absi > randRad) { randRad = absi; } } randRad += 1.0; /* * initial values randomly in radius 1+randRad */ for (int i = 0; i < d; i++) { final double rad = randRad * rand.nextDouble(); final double phi = 2.0 * 3.14159 * rand.nextDouble(); res.add(i, new BigComplex(rad * Math.cos(phi), rad * Math.sin(phi))); } /* * iterate until convr indicates that all values changed by less than * the digits * precision indicates. */ boolean convr = false; for (; !convr;)// ORIGINAL LINE: for(int itr =0 ; ! convr ; itr++) { convr = true; final Vector resPlus = new Vector<>(); for (int v = 0; v < d; v++) { /* * evaluate f(x)/(x-root1)/(x-root2)/... (x-rootdegr), Newton * method */ BigComplex thisx = res.elementAt(v); BigComplex nv = mon.valueOf(thisx, mc); for (int j = 0; j < d; j++) { if (j != v) { nv = nv.divide(thisx.subtract(res.elementAt(j)), mc); } } /* is this value converged ? */ if (nv.abs(mc).doubleValue() > thisx.abs(mc).doubleValue() * Math.pow(10.0, -digits)) { convr = false; } thisx = thisx.subtract(nv); /* If unstable, start over */ if (thisx.abs(MathContext.DECIMAL32).doubleValue() > randRad) { return roots(digits); } resPlus.add(thisx); } res = resPlus; } return res; } /* roots */ /** * Generate the integer roots of the polynomial. * * @return The vector of integer roots, with multiplicity. * The shows alternatingly first a root then its multiplicty, then * another root and multiplicty etc. * @since 2008-10-26 */ public Vector iroots() { /* The vector of the roots */ final Vector res = new Vector<>(); final int lowd = ldegree(); if (lowd == 0 && a.elementAt(0).compareTo(BigInteger.ZERO) == 0) { /* * Case of polynomial identical to zero: * reported as a simple root of value 0. */ res.add(BigInteger.ZERO); res.add(BigInteger.ONE); return res; } /* * multiply all coefs with the lcm() to get an integer polynomial * start with denominator of first non-zero coefficient. */ BigInteger lcmDeno = a.elementAt(lowd).b; for (int i = lowd + 1; i < degree(); i++) { lcmDeno = BigIntegerMath.lcm(lcmDeno, a.elementAt(i).b); } /* * and eventually get the integer polynomial by ignoring the * denominators */ final Vector ipo = new Vector<>(); for (int i = 0; i < a.size(); i++) { final BigInteger d = a.elementAt(i).a.multiply(lcmDeno).divide(a.elementAt(i).b); ipo.add(d); } final BigIntegerPoly p = new BigIntegerPoly(ipo); /* * collect the integer roots (multiple roots only once). Since we * removed the zero already above, cand does not contain zeros. */ final Vector cand = p.iroots(); for (int i = 0; i < cand.size(); i++) { final BigInteger r = cand.elementAt(i); final int deg = p.rootDeg(r); res.add(r); res.add(new BigInteger("" + deg)); } return res; } /* iroots */ } /* RatPoly */