View Javadoc
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  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.analysis.polynomials;
23  
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.hipparchus.fraction.BigFraction;
30  import org.hipparchus.util.CombinatoricsUtils;
31  import org.hipparchus.util.FastMath;
32  
33  /**
34   * A collection of static methods that operate on or return polynomials.
35   *
36   */
37  public class PolynomialsUtils {
38  
39      /** Coefficients for Chebyshev polynomials. */
40      private static final List<BigFraction> CHEBYSHEV_COEFFICIENTS;
41  
42      /** Coefficients for Hermite polynomials. */
43      private static final List<BigFraction> HERMITE_COEFFICIENTS;
44  
45      /** Coefficients for Laguerre polynomials. */
46      private static final List<BigFraction> LAGUERRE_COEFFICIENTS;
47  
48      /** Coefficients for Legendre polynomials. */
49      private static final List<BigFraction> LEGENDRE_COEFFICIENTS;
50  
51      /** Coefficients for Jacobi polynomials. */
52      private static final Map<JacobiKey, List<BigFraction>> JACOBI_COEFFICIENTS;
53  
54      static {
55  
56          // initialize recurrence for Chebyshev polynomials
57          // T0(X) = 1, T1(X) = 0 + 1 * X
58          CHEBYSHEV_COEFFICIENTS = new ArrayList<>();
59          CHEBYSHEV_COEFFICIENTS.add(BigFraction.ONE);
60          CHEBYSHEV_COEFFICIENTS.add(BigFraction.ZERO);
61          CHEBYSHEV_COEFFICIENTS.add(BigFraction.ONE);
62  
63          // initialize recurrence for Hermite polynomials
64          // H0(X) = 1, H1(X) = 0 + 2 * X
65          HERMITE_COEFFICIENTS = new ArrayList<>();
66          HERMITE_COEFFICIENTS.add(BigFraction.ONE);
67          HERMITE_COEFFICIENTS.add(BigFraction.ZERO);
68          HERMITE_COEFFICIENTS.add(BigFraction.TWO);
69  
70          // initialize recurrence for Laguerre polynomials
71          // L0(X) = 1, L1(X) = 1 - 1 * X
72          LAGUERRE_COEFFICIENTS = new ArrayList<>();
73          LAGUERRE_COEFFICIENTS.add(BigFraction.ONE);
74          LAGUERRE_COEFFICIENTS.add(BigFraction.ONE);
75          LAGUERRE_COEFFICIENTS.add(BigFraction.MINUS_ONE);
76  
77          // initialize recurrence for Legendre polynomials
78          // P0(X) = 1, P1(X) = 0 + 1 * X
79          LEGENDRE_COEFFICIENTS = new ArrayList<>();
80          LEGENDRE_COEFFICIENTS.add(BigFraction.ONE);
81          LEGENDRE_COEFFICIENTS.add(BigFraction.ZERO);
82          LEGENDRE_COEFFICIENTS.add(BigFraction.ONE);
83  
84          // initialize map for Jacobi polynomials
85          JACOBI_COEFFICIENTS = new HashMap<>();
86  
87      }
88  
89      /**
90       * Private constructor, to prevent instantiation.
91       */
92      private PolynomialsUtils() {
93      }
94  
95      /**
96       * Create a Chebyshev polynomial of the first kind.
97       * <p><a href="https://en.wikipedia.org/wiki/Chebyshev_polynomials">Chebyshev
98       * polynomials of the first kind</a> are orthogonal polynomials.
99       * They can be defined by the following recurrence relations:</p><p>
100      * \(
101      *    T_0(x) = 1 \\
102      *    T_1(x) = x \\
103      *    T_{k+1}(x) = 2x T_k(x) - T_{k-1}(x)
104      * \)
105      * </p>
106      * @param degree degree of the polynomial
107      * @return Chebyshev polynomial of specified degree
108      */
109     public static PolynomialFunction createChebyshevPolynomial(final int degree) {
110         return buildPolynomial(degree, CHEBYSHEV_COEFFICIENTS,
111                 new RecurrenceCoefficientsGenerator() {
112             /** Fixed recurrence coefficients. */
113             private final BigFraction[] coeffs = { BigFraction.ZERO, BigFraction.TWO, BigFraction.ONE };
114             /** {@inheritDoc} */
115             @Override
116             public BigFraction[] generate(int k) {
117                 return coeffs;
118             }
119         });
120     }
121 
122     /**
123      * Create a Hermite polynomial.
124      * <p><a href="http://mathworld.wolfram.com/HermitePolynomial.html">Hermite
125      * polynomials</a> are orthogonal polynomials.
126      * They can be defined by the following recurrence relations:</p><p>
127      * \(
128      *  H_0(x) = 1 \\
129      *  H_1(x) = 2x \\
130      *  H_{k+1}(x) = 2x H_k(X) - 2k H_{k-1}(x)
131      * \)
132      * </p>
133 
134      * @param degree degree of the polynomial
135      * @return Hermite polynomial of specified degree
136      */
137     public static PolynomialFunction createHermitePolynomial(final int degree) {
138         return buildPolynomial(degree, HERMITE_COEFFICIENTS,
139                 new RecurrenceCoefficientsGenerator() {
140             /** {@inheritDoc} */
141             @Override
142             public BigFraction[] generate(int k) {
143                 return new BigFraction[] {
144                         BigFraction.ZERO,
145                         BigFraction.TWO,
146                         new BigFraction(2 * k)};
147             }
148         });
149     }
150 
151     /**
152      * Create a Laguerre polynomial.
153      * <p><a href="http://mathworld.wolfram.com/LaguerrePolynomial.html">Laguerre
154      * polynomials</a> are orthogonal polynomials.
155      * They can be defined by the following recurrence relations:</p><p>
156      * \(
157      *   L_0(x) = 1 \\
158      *   L_1(x) = 1 - x \\
159      *   (k+1) L_{k+1}(x) = (2k + 1 - x) L_k(x) - k L_{k-1}(x)
160      * \)
161      * </p>
162      * @param degree degree of the polynomial
163      * @return Laguerre polynomial of specified degree
164      */
165     public static PolynomialFunction createLaguerrePolynomial(final int degree) {
166         return buildPolynomial(degree, LAGUERRE_COEFFICIENTS,
167                 new RecurrenceCoefficientsGenerator() {
168             /** {@inheritDoc} */
169             @Override
170             public BigFraction[] generate(int k) {
171                 final int kP1 = k + 1;
172                 return new BigFraction[] {
173                         new BigFraction(2 * k + 1, kP1),
174                         new BigFraction(-1, kP1),
175                         new BigFraction(k, kP1)};
176             }
177         });
178     }
179 
180     /**
181      * Create a Legendre polynomial.
182      * <p><a href="http://mathworld.wolfram.com/LegendrePolynomial.html">Legendre
183      * polynomials</a> are orthogonal polynomials.
184      * They can be defined by the following recurrence relations:</p><p>
185      * \(
186      *   P_0(x) = 1 \\
187      *   P_1(x) = x \\
188      *   (k+1) P_{k+1}(x) = (2k+1) x P_k(x) - k P_{k-1}(x)
189      * \)
190      * </p>
191      * @param degree degree of the polynomial
192      * @return Legendre polynomial of specified degree
193      */
194     public static PolynomialFunction createLegendrePolynomial(final int degree) {
195         return buildPolynomial(degree, LEGENDRE_COEFFICIENTS,
196                                new RecurrenceCoefficientsGenerator() {
197             /** {@inheritDoc} */
198             @Override
199             public BigFraction[] generate(int k) {
200                 final int kP1 = k + 1;
201                 return new BigFraction[] {
202                         BigFraction.ZERO,
203                         new BigFraction(k + kP1, kP1),
204                         new BigFraction(k, kP1)};
205             }
206         });
207     }
208 
209     /**
210      * Create a Jacobi polynomial.
211      * <p><a href="http://mathworld.wolfram.com/JacobiPolynomial.html">Jacobi
212      * polynomials</a> are orthogonal polynomials.
213      * They can be defined by the following recurrence relations:</p><p>
214      * \(
215      *    P_0^{vw}(x) = 1 \\
216      *    P_{-1}^{vw}(x) = 0 \\
217      *    2k(k + v + w)(2k + v + w - 2) P_k^{vw}(x) = \\
218      *    (2k + v + w - 1)[(2k + v + w)(2k + v + w - 2) x + v^2 - w^2] P_{k-1}^{vw}(x) \\
219      *  - 2(k + v - 1)(k + w - 1)(2k + v + w) P_{k-2}^{vw}(x)
220      * \)
221      * </p>
222      * @param degree degree of the polynomial
223      * @param v first exponent
224      * @param w second exponent
225      * @return Jacobi polynomial of specified degree
226      */
227     public static PolynomialFunction createJacobiPolynomial(final int degree, final int v, final int w) {
228 
229         // select the appropriate list
230         final JacobiKey key = new JacobiKey(v, w);
231 
232         if (!JACOBI_COEFFICIENTS.containsKey(key)) {
233 
234             // allocate a new list for v, w
235             final List<BigFraction> list = new ArrayList<>();
236             JACOBI_COEFFICIENTS.put(key, list);
237 
238             // Pv,w,0(x) = 1;
239             list.add(BigFraction.ONE);
240 
241             // P1(x) = (v - w) / 2 + (2 + v + w) * X / 2
242             list.add(new BigFraction(v - w, 2));
243             list.add(new BigFraction(2 + v + w, 2));
244 
245         }
246 
247         return buildPolynomial(degree, JACOBI_COEFFICIENTS.get(key),
248                                new RecurrenceCoefficientsGenerator() {
249             /** {@inheritDoc} */
250             @Override
251             public BigFraction[] generate(int k) {
252                 k++;
253                 final int kvw      = k + v + w;
254                 final int twoKvw   = kvw + k;
255                 final int twoKvwM1 = twoKvw - 1;
256                 final int twoKvwM2 = twoKvw - 2;
257                 final int den      = 2 * k *  kvw * twoKvwM2;
258 
259                 return new BigFraction[] {
260                     new BigFraction(twoKvwM1 * (v * v - w * w), den),
261                     new BigFraction(twoKvwM1 * twoKvw * twoKvwM2, den),
262                     new BigFraction(2 * (k + v - 1) * (k + w - 1) * twoKvw, den)
263                 };
264             }
265         });
266 
267     }
268 
269     /**
270      * Compute the coefficients of the polynomial \(P_s(x)\)
271      * whose values at point {@code x} will be the same as the those from the
272      * original polynomial \(P(x)\) when computed at {@code x + shift}.
273      * <p>
274      * More precisely, let \(\Delta = \) {@code shift} and let
275      * \(P_s(x) = P(x + \Delta)\).  The returned array
276      * consists of the coefficients of \(P_s\).  So if \(a_0, ..., a_{n-1}\)
277      * are the coefficients of \(P\), then the returned array
278      * \(b_0, ..., b_{n-1}\) satisfies the identity
279      * \(\sum_{i=0}^{n-1} b_i x^i = \sum_{i=0}^{n-1} a_i (x + \Delta)^i\) for all \(x\).
280      *
281      * @param coefficients Coefficients of the original polynomial.
282      * @param shift Shift value.
283      * @return the coefficients \(b_i\) of the shifted
284      * polynomial.
285      */
286     public static double[] shift(final double[] coefficients,
287                                  final double shift) {
288         final int dp1 = coefficients.length;
289         final double[] newCoefficients = new double[dp1];
290 
291         // Pascal triangle.
292         final int[][] coeff = new int[dp1][dp1];
293         for (int i = 0; i < dp1; i++){
294             for(int j = 0; j <= i; j++){
295                 coeff[i][j] = (int) CombinatoricsUtils.binomialCoefficient(i, j);
296             }
297         }
298 
299         // First polynomial coefficient.
300         for (int i = 0; i < dp1; i++){
301             newCoefficients[0] += coefficients[i] * FastMath.pow(shift, i);
302         }
303 
304         // Superior order.
305         final int d = dp1 - 1;
306         for (int i = 0; i < d; i++) {
307             for (int j = i; j < d; j++){
308                 newCoefficients[i + 1] += coeff[j + 1][j - i] *
309                     coefficients[j + 1] * FastMath.pow(shift, j - i);
310             }
311         }
312 
313         return newCoefficients;
314     }
315 
316 
317     /** Get the coefficients array for a given degree.
318      * @param degree degree of the polynomial
319      * @param coefficients list where the computed coefficients are stored
320      * @param generator recurrence coefficients generator
321      * @return coefficients array
322      */
323     private static PolynomialFunction buildPolynomial(final int degree,
324                                                       final List<BigFraction> coefficients,
325                                                       final RecurrenceCoefficientsGenerator generator) {
326 
327         synchronized (coefficients) {
328             final int maxDegree = (int) FastMath.floor(FastMath.sqrt(2 * coefficients.size())) - 1;
329             if (degree > maxDegree) {
330                 computeUpToDegree(degree, maxDegree, generator, coefficients);
331             }
332         }
333 
334         // coefficient  for polynomial 0 is  l [0]
335         // coefficients for polynomial 1 are l [1] ... l [2] (degrees 0 ... 1)
336         // coefficients for polynomial 2 are l [3] ... l [5] (degrees 0 ... 2)
337         // coefficients for polynomial 3 are l [6] ... l [9] (degrees 0 ... 3)
338         // coefficients for polynomial 4 are l[10] ... l[14] (degrees 0 ... 4)
339         // coefficients for polynomial 5 are l[15] ... l[20] (degrees 0 ... 5)
340         // coefficients for polynomial 6 are l[21] ... l[27] (degrees 0 ... 6)
341         // ...
342         final int start = degree * (degree + 1) / 2;
343 
344         final double[] a = new double[degree + 1];
345         for (int i = 0; i <= degree; ++i) {
346             a[i] = coefficients.get(start + i).doubleValue();
347         }
348 
349         // build the polynomial
350         return new PolynomialFunction(a);
351 
352     }
353 
354     /** Compute polynomial coefficients up to a given degree.
355      * @param degree maximal degree
356      * @param maxDegree current maximal degree
357      * @param generator recurrence coefficients generator
358      * @param coefficients list where the computed coefficients should be appended
359      */
360     private static void computeUpToDegree(final int degree, final int maxDegree,
361                                           final RecurrenceCoefficientsGenerator generator,
362                                           final List<BigFraction> coefficients) {
363 
364         int startK = (maxDegree - 1) * maxDegree / 2;
365         for (int k = maxDegree; k < degree; ++k) {
366 
367             // start indices of two previous polynomials Pk(X) and Pk-1(X)
368             int startKm1 = startK;
369             startK += k;
370 
371             // Pk+1(X) = (a[0] + a[1] X) Pk(X) - a[2] Pk-1(X)
372             BigFraction[] ai = generator.generate(k);
373 
374             BigFraction ck     = coefficients.get(startK);
375             BigFraction ckm1   = coefficients.get(startKm1);
376 
377             // degree 0 coefficient
378             coefficients.add(ck.multiply(ai[0]).subtract(ckm1.multiply(ai[2])));
379 
380             // degree 1 to degree k-1 coefficients
381             for (int i = 1; i < k; ++i) {
382                 final BigFraction ckPrev = ck;
383                 ck     = coefficients.get(startK + i);
384                 ckm1   = coefficients.get(startKm1 + i);
385                 coefficients.add(ck.multiply(ai[0]).add(ckPrev.multiply(ai[1])).subtract(ckm1.multiply(ai[2])));
386             }
387 
388             // degree k coefficient
389             final BigFraction ckPrev = ck;
390             ck = coefficients.get(startK + k);
391             coefficients.add(ck.multiply(ai[0]).add(ckPrev.multiply(ai[1])));
392 
393             // degree k+1 coefficient
394             coefficients.add(ck.multiply(ai[1]));
395 
396         }
397 
398     }
399 
400     /** Interface for recurrence coefficients generation. */
401     private interface RecurrenceCoefficientsGenerator {
402         /**
403          * Generate recurrence coefficients.
404          * @param k highest degree of the polynomials used in the recurrence
405          * @return an array of three coefficients such that
406          * \( P_{k+1}(x) = (a[0] + a[1] x) P_k(x) - a[2] P_{k-1}(x) \)
407          */
408         BigFraction[] generate(int k);
409     }
410 
411 }