PolynomialsUtils.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.analysis.polynomials;

  22. import java.util.ArrayList;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;

  26. import org.hipparchus.fraction.BigFraction;
  27. import org.hipparchus.util.CombinatoricsUtils;
  28. import org.hipparchus.util.FastMath;

  29. /**
  30.  * A collection of static methods that operate on or return polynomials.
  31.  *
  32.  */
  33. public class PolynomialsUtils {

  34.     /** Coefficients for Chebyshev polynomials. */
  35.     private static final List<BigFraction> CHEBYSHEV_COEFFICIENTS;

  36.     /** Coefficients for Hermite polynomials. */
  37.     private static final List<BigFraction> HERMITE_COEFFICIENTS;

  38.     /** Coefficients for Laguerre polynomials. */
  39.     private static final List<BigFraction> LAGUERRE_COEFFICIENTS;

  40.     /** Coefficients for Legendre polynomials. */
  41.     private static final List<BigFraction> LEGENDRE_COEFFICIENTS;

  42.     /** Coefficients for Jacobi polynomials. */
  43.     private static final Map<JacobiKey, List<BigFraction>> JACOBI_COEFFICIENTS;

  44.     static {

  45.         // initialize recurrence for Chebyshev polynomials
  46.         // T0(X) = 1, T1(X) = 0 + 1 * X
  47.         CHEBYSHEV_COEFFICIENTS = new ArrayList<>();
  48.         CHEBYSHEV_COEFFICIENTS.add(BigFraction.ONE);
  49.         CHEBYSHEV_COEFFICIENTS.add(BigFraction.ZERO);
  50.         CHEBYSHEV_COEFFICIENTS.add(BigFraction.ONE);

  51.         // initialize recurrence for Hermite polynomials
  52.         // H0(X) = 1, H1(X) = 0 + 2 * X
  53.         HERMITE_COEFFICIENTS = new ArrayList<>();
  54.         HERMITE_COEFFICIENTS.add(BigFraction.ONE);
  55.         HERMITE_COEFFICIENTS.add(BigFraction.ZERO);
  56.         HERMITE_COEFFICIENTS.add(BigFraction.TWO);

  57.         // initialize recurrence for Laguerre polynomials
  58.         // L0(X) = 1, L1(X) = 1 - 1 * X
  59.         LAGUERRE_COEFFICIENTS = new ArrayList<>();
  60.         LAGUERRE_COEFFICIENTS.add(BigFraction.ONE);
  61.         LAGUERRE_COEFFICIENTS.add(BigFraction.ONE);
  62.         LAGUERRE_COEFFICIENTS.add(BigFraction.MINUS_ONE);

  63.         // initialize recurrence for Legendre polynomials
  64.         // P0(X) = 1, P1(X) = 0 + 1 * X
  65.         LEGENDRE_COEFFICIENTS = new ArrayList<>();
  66.         LEGENDRE_COEFFICIENTS.add(BigFraction.ONE);
  67.         LEGENDRE_COEFFICIENTS.add(BigFraction.ZERO);
  68.         LEGENDRE_COEFFICIENTS.add(BigFraction.ONE);

  69.         // initialize map for Jacobi polynomials
  70.         JACOBI_COEFFICIENTS = new HashMap<>();

  71.     }

  72.     /**
  73.      * Private constructor, to prevent instantiation.
  74.      */
  75.     private PolynomialsUtils() {
  76.     }

  77.     /**
  78.      * Create a Chebyshev polynomial of the first kind.
  79.      * <p><a href="https://en.wikipedia.org/wiki/Chebyshev_polynomials">Chebyshev
  80.      * polynomials of the first kind</a> are orthogonal polynomials.
  81.      * They can be defined by the following recurrence relations:</p><p>
  82.      * \(
  83.      *    T_0(x) = 1 \\
  84.      *    T_1(x) = x \\
  85.      *    T_{k+1}(x) = 2x T_k(x) - T_{k-1}(x)
  86.      * \)
  87.      * </p>
  88.      * @param degree degree of the polynomial
  89.      * @return Chebyshev polynomial of specified degree
  90.      */
  91.     public static PolynomialFunction createChebyshevPolynomial(final int degree) {
  92.         return buildPolynomial(degree, CHEBYSHEV_COEFFICIENTS,
  93.                 new RecurrenceCoefficientsGenerator() {
  94.             /** Fixed recurrence coefficients. */
  95.             private final BigFraction[] coeffs = { BigFraction.ZERO, BigFraction.TWO, BigFraction.ONE };
  96.             /** {@inheritDoc} */
  97.             @Override
  98.             public BigFraction[] generate(int k) {
  99.                 return coeffs.clone();
  100.             }
  101.         });
  102.     }

  103.     /**
  104.      * Create a Hermite polynomial.
  105.      * <p><a href="http://mathworld.wolfram.com/HermitePolynomial.html">Hermite
  106.      * polynomials</a> are orthogonal polynomials.
  107.      * They can be defined by the following recurrence relations:</p><p>
  108.      * \(
  109.      *  H_0(x) = 1 \\
  110.      *  H_1(x) = 2x \\
  111.      *  H_{k+1}(x) = 2x H_k(X) - 2k H_{k-1}(x)
  112.      * \)
  113.      * </p>

  114.      * @param degree degree of the polynomial
  115.      * @return Hermite polynomial of specified degree
  116.      */
  117.     public static PolynomialFunction createHermitePolynomial(final int degree) {
  118.         return buildPolynomial(degree, HERMITE_COEFFICIENTS,
  119.                 new RecurrenceCoefficientsGenerator() {
  120.             /** {@inheritDoc} */
  121.             @Override
  122.             public BigFraction[] generate(int k) {
  123.                 return new BigFraction[] {
  124.                         BigFraction.ZERO,
  125.                         BigFraction.TWO,
  126.                         new BigFraction(2 * k)};
  127.             }
  128.         });
  129.     }

  130.     /**
  131.      * Create a Laguerre polynomial.
  132.      * <p><a href="http://mathworld.wolfram.com/LaguerrePolynomial.html">Laguerre
  133.      * polynomials</a> are orthogonal polynomials.
  134.      * They can be defined by the following recurrence relations:</p><p>
  135.      * \(
  136.      *   L_0(x) = 1 \\
  137.      *   L_1(x) = 1 - x \\
  138.      *   (k+1) L_{k+1}(x) = (2k + 1 - x) L_k(x) - k L_{k-1}(x)
  139.      * \)
  140.      * </p>
  141.      * @param degree degree of the polynomial
  142.      * @return Laguerre polynomial of specified degree
  143.      */
  144.     public static PolynomialFunction createLaguerrePolynomial(final int degree) {
  145.         return buildPolynomial(degree, LAGUERRE_COEFFICIENTS,
  146.                 new RecurrenceCoefficientsGenerator() {
  147.             /** {@inheritDoc} */
  148.             @Override
  149.             public BigFraction[] generate(int k) {
  150.                 final int kP1 = k + 1;
  151.                 return new BigFraction[] {
  152.                         new BigFraction(2 * k + 1, kP1),
  153.                         new BigFraction(-1, kP1),
  154.                         new BigFraction(k, kP1)};
  155.             }
  156.         });
  157.     }

  158.     /**
  159.      * Create a Legendre polynomial.
  160.      * <p><a href="http://mathworld.wolfram.com/LegendrePolynomial.html">Legendre
  161.      * polynomials</a> are orthogonal polynomials.
  162.      * They can be defined by the following recurrence relations:</p><p>
  163.      * \(
  164.      *   P_0(x) = 1 \\
  165.      *   P_1(x) = x \\
  166.      *   (k+1) P_{k+1}(x) = (2k+1) x P_k(x) - k P_{k-1}(x)
  167.      * \)
  168.      * </p>
  169.      * @param degree degree of the polynomial
  170.      * @return Legendre polynomial of specified degree
  171.      */
  172.     public static PolynomialFunction createLegendrePolynomial(final int degree) {
  173.         return buildPolynomial(degree, LEGENDRE_COEFFICIENTS,
  174.                                new RecurrenceCoefficientsGenerator() {
  175.             /** {@inheritDoc} */
  176.             @Override
  177.             public BigFraction[] generate(int k) {
  178.                 final int kP1 = k + 1;
  179.                 return new BigFraction[] {
  180.                         BigFraction.ZERO,
  181.                         new BigFraction(k + kP1, kP1),
  182.                         new BigFraction(k, kP1)};
  183.             }
  184.         });
  185.     }

  186.     /**
  187.      * Create a Jacobi polynomial.
  188.      * <p><a href="http://mathworld.wolfram.com/JacobiPolynomial.html">Jacobi
  189.      * polynomials</a> are orthogonal polynomials.
  190.      * They can be defined by the following recurrence relations:</p><p>
  191.      * \(
  192.      *    P_0^{vw}(x) = 1 \\
  193.      *    P_{-1}^{vw}(x) = 0 \\
  194.      *    2k(k + v + w)(2k + v + w - 2) P_k^{vw}(x) = \\
  195.      *    (2k + v + w - 1)[(2k + v + w)(2k + v + w - 2) x + v^2 - w^2] P_{k-1}^{vw}(x) \\
  196.      *  - 2(k + v - 1)(k + w - 1)(2k + v + w) P_{k-2}^{vw}(x)
  197.      * \)
  198.      * </p>
  199.      * @param degree degree of the polynomial
  200.      * @param v first exponent
  201.      * @param w second exponent
  202.      * @return Jacobi polynomial of specified degree
  203.      */
  204.     public static PolynomialFunction createJacobiPolynomial(final int degree, final int v, final int w) {

  205.         // select the appropriate list
  206.         final JacobiKey key = new JacobiKey(v, w);

  207.         if (!JACOBI_COEFFICIENTS.containsKey(key)) {

  208.             // allocate a new list for v, w
  209.             final List<BigFraction> list = new ArrayList<>();
  210.             JACOBI_COEFFICIENTS.put(key, list);

  211.             // Pv,w,0(x) = 1;
  212.             list.add(BigFraction.ONE);

  213.             // P1(x) = (v - w) / 2 + (2 + v + w) * X / 2
  214.             list.add(new BigFraction(v - w, 2));
  215.             list.add(new BigFraction(2 + v + w, 2));

  216.         }

  217.         return buildPolynomial(degree, JACOBI_COEFFICIENTS.get(key),
  218.                                new RecurrenceCoefficientsGenerator() {
  219.             /** {@inheritDoc} */
  220.             @Override
  221.             public BigFraction[] generate(int k) {
  222.                 k++;
  223.                 final int kvw      = k + v + w;
  224.                 final int twoKvw   = kvw + k;
  225.                 final int twoKvwM1 = twoKvw - 1;
  226.                 final int twoKvwM2 = twoKvw - 2;
  227.                 final int den      = 2 * k *  kvw * twoKvwM2;

  228.                 return new BigFraction[] {
  229.                     new BigFraction(twoKvwM1 * (v * v - w * w), den),
  230.                     new BigFraction(twoKvwM1 * twoKvw * twoKvwM2, den),
  231.                     new BigFraction(2 * (k + v - 1) * (k + w - 1) * twoKvw, den)
  232.                 };
  233.             }
  234.         });

  235.     }

  236.     /**
  237.      * Compute the coefficients of the polynomial \(P_s(x)\)
  238.      * whose values at point {@code x} will be the same as the those from the
  239.      * original polynomial \(P(x)\) when computed at {@code x + shift}.
  240.      * <p>
  241.      * More precisely, let \(\Delta = \) {@code shift} and let
  242.      * \(P_s(x) = P(x + \Delta)\).  The returned array
  243.      * consists of the coefficients of \(P_s\).  So if \(a_0, ..., a_{n-1}\)
  244.      * are the coefficients of \(P\), then the returned array
  245.      * \(b_0, ..., b_{n-1}\) satisfies the identity
  246.      * \(\sum_{i=0}^{n-1} b_i x^i = \sum_{i=0}^{n-1} a_i (x + \Delta)^i\) for all \(x\).
  247.      *
  248.      * @param coefficients Coefficients of the original polynomial.
  249.      * @param shift Shift value.
  250.      * @return the coefficients \(b_i\) of the shifted
  251.      * polynomial.
  252.      */
  253.     public static double[] shift(final double[] coefficients,
  254.                                  final double shift) {
  255.         final int dp1 = coefficients.length;
  256.         final double[] newCoefficients = new double[dp1];

  257.         // Pascal triangle.
  258.         final int[][] coeff = new int[dp1][dp1];
  259.         for (int i = 0; i < dp1; i++){
  260.             for(int j = 0; j <= i; j++){
  261.                 coeff[i][j] = (int) CombinatoricsUtils.binomialCoefficient(i, j);
  262.             }
  263.         }

  264.         // First polynomial coefficient.
  265.         for (int i = 0; i < dp1; i++){
  266.             newCoefficients[0] += coefficients[i] * FastMath.pow(shift, i);
  267.         }

  268.         // Superior order.
  269.         final int d = dp1 - 1;
  270.         for (int i = 0; i < d; i++) {
  271.             for (int j = i; j < d; j++){
  272.                 newCoefficients[i + 1] += coeff[j + 1][j - i] *
  273.                     coefficients[j + 1] * FastMath.pow(shift, j - i);
  274.             }
  275.         }

  276.         return newCoefficients;
  277.     }


  278.     /** Get the coefficients array for a given degree.
  279.      * @param degree degree of the polynomial
  280.      * @param coefficients list where the computed coefficients are stored
  281.      * @param generator recurrence coefficients generator
  282.      * @return coefficients array
  283.      */
  284.     private static PolynomialFunction buildPolynomial(final int degree,
  285.                                                       final List<BigFraction> coefficients,
  286.                                                       final RecurrenceCoefficientsGenerator generator) {

  287.         synchronized (coefficients) {
  288.             final int maxDegree = (int) FastMath.floor(FastMath.sqrt(2 * coefficients.size())) - 1;
  289.             if (degree > maxDegree) {
  290.                 computeUpToDegree(degree, maxDegree, generator, coefficients);
  291.             }
  292.         }

  293.         // coefficient  for polynomial 0 is  l [0]
  294.         // coefficients for polynomial 1 are l [1] ... l [2] (degrees 0 ... 1)
  295.         // coefficients for polynomial 2 are l [3] ... l [5] (degrees 0 ... 2)
  296.         // coefficients for polynomial 3 are l [6] ... l [9] (degrees 0 ... 3)
  297.         // coefficients for polynomial 4 are l[10] ... l[14] (degrees 0 ... 4)
  298.         // coefficients for polynomial 5 are l[15] ... l[20] (degrees 0 ... 5)
  299.         // coefficients for polynomial 6 are l[21] ... l[27] (degrees 0 ... 6)
  300.         // ...
  301.         final int start = degree * (degree + 1) / 2;

  302.         final double[] a = new double[degree + 1];
  303.         for (int i = 0; i <= degree; ++i) {
  304.             a[i] = coefficients.get(start + i).doubleValue();
  305.         }

  306.         // build the polynomial
  307.         return new PolynomialFunction(a);

  308.     }

  309.     /** Compute polynomial coefficients up to a given degree.
  310.      * @param degree maximal degree
  311.      * @param maxDegree current maximal degree
  312.      * @param generator recurrence coefficients generator
  313.      * @param coefficients list where the computed coefficients should be appended
  314.      */
  315.     private static void computeUpToDegree(final int degree, final int maxDegree,
  316.                                           final RecurrenceCoefficientsGenerator generator,
  317.                                           final List<BigFraction> coefficients) {

  318.         int startK = (maxDegree - 1) * maxDegree / 2;
  319.         for (int k = maxDegree; k < degree; ++k) {

  320.             // start indices of two previous polynomials Pk(X) and Pk-1(X)
  321.             int startKm1 = startK;
  322.             startK += k;

  323.             // Pk+1(X) = (a[0] + a[1] X) Pk(X) - a[2] Pk-1(X)
  324.             BigFraction[] ai = generator.generate(k);

  325.             BigFraction ck     = coefficients.get(startK);
  326.             BigFraction ckm1   = coefficients.get(startKm1);

  327.             // degree 0 coefficient
  328.             coefficients.add(ck.multiply(ai[0]).subtract(ckm1.multiply(ai[2])));

  329.             // degree 1 to degree k-1 coefficients
  330.             for (int i = 1; i < k; ++i) {
  331.                 final BigFraction ckPrev = ck;
  332.                 ck     = coefficients.get(startK + i);
  333.                 ckm1   = coefficients.get(startKm1 + i);
  334.                 coefficients.add(ck.multiply(ai[0]).add(ckPrev.multiply(ai[1])).subtract(ckm1.multiply(ai[2])));
  335.             }

  336.             // degree k coefficient
  337.             final BigFraction ckPrev = ck;
  338.             ck = coefficients.get(startK + k);
  339.             coefficients.add(ck.multiply(ai[0]).add(ckPrev.multiply(ai[1])));

  340.             // degree k+1 coefficient
  341.             coefficients.add(ck.multiply(ai[1]));

  342.         }

  343.     }

  344.     /** Interface for recurrence coefficients generation. */
  345.     private interface RecurrenceCoefficientsGenerator {
  346.         /**
  347.          * Generate recurrence coefficients.
  348.          * @param k highest degree of the polynomials used in the recurrence
  349.          * @return an array of three coefficients such that
  350.          * \( P_{k+1}(x) = (a[0] + a[1] x) P_k(x) - a[2] P_{k-1}(x) \)
  351.          */
  352.         BigFraction[] generate(int k);
  353.     }

  354. }