View Javadoc
1   /*
2    * Copyright (C) 2016 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.numerical.polynomials;
17  
18  import com.irurueta.algebra.ArrayUtils;
19  import com.irurueta.algebra.Complex;
20  import com.irurueta.numerical.NumericalException;
21  import com.irurueta.numerical.roots.FirstDegreePolynomialRootsEstimator;
22  import com.irurueta.numerical.roots.LaguerrePolynomialRootsEstimator;
23  import com.irurueta.numerical.roots.PolynomialRootsEstimator;
24  import com.irurueta.numerical.roots.SecondDegreePolynomialRootsEstimator;
25  import com.irurueta.numerical.roots.ThirdDegreePolynomialRootsEstimator;
26  import com.irurueta.numerical.signal.processing.Convolver1D;
27  
28  import java.io.Serializable;
29  import java.util.ArrayList;
30  
31  /**
32   * Contains a polynomial and common operations done with polynomials.
33   * This implementation only supports polynomials with real parameters.
34   */
35  public class Polynomial implements Serializable {
36  
37      /**
38       * Minimum derivative / integration order.
39       */
40      private static final int MIN_ORDER = 1;
41  
42      /**
43       * Minimum allowed length in polynomial parameters.
44       */
45      public static final int MIN_VALID_POLY_PARAMS_LENGTH = 1;
46  
47      /**
48       * Constant defining machine precision
49       */
50      public static final double EPS = 1e-10;
51  
52      /**
53       * Array containing parameters defining a polynomial.
54       * For a polynomial having the expression p(x) = a + b*x + c*x^2 + ...
55       * provided array must be [a, b, c, ...]
56       */
57      private double[] polyParams;
58  
59      /**
60       * Constructor.
61       * Creates a polynomial initialized to zero.
62       */
63      public Polynomial() {
64          polyParams = new double[MIN_VALID_POLY_PARAMS_LENGTH];
65      }
66  
67      /**
68       * Constructor.
69       *
70       * @param numberOfParameters number of parameters of polynomial to create.
71       * @throws IllegalArgumentException if number of parameters is less than 1.
72       */
73      public Polynomial(final int numberOfParameters) {
74          if (numberOfParameters < MIN_VALID_POLY_PARAMS_LENGTH) {
75              throw new IllegalArgumentException("at least 1 parameter is required");
76          }
77          polyParams = new double[numberOfParameters];
78      }
79  
80      /**
81       * Constructor.
82       * For a polynomial having the expression p(x) = a + b*x + c*x^2 + ...
83       * provided array must be [a, b, c, ...]
84       *
85       * @param polyParams parameters defining a polynomial.
86       * @throws IllegalArgumentException if provided array does not have at least
87       *                                  length 2.
88       */
89      public Polynomial(final double... polyParams) {
90          setPolyParams(polyParams);
91      }
92  
93      /**
94       * Gets array defining parameters of polynomial.
95       * A polynomial having the expression p(x) = a + b*x + c*x^2 + ...
96       * has an array of the form [a, b, c, ...].
97       *
98       * @return parameters defining a polynomial.
99       */
100     public double[] getPolyParams() {
101         return polyParams;
102     }
103 
104     /**
105      * Sets array defining parameters of polynomial.
106      * A polynomial having the expression p(x) = a + b*x + c*x^2 + ...
107      * has an array of the form [a, b, c, ...].
108      *
109      * @param polyParams array defining parameters of polynomial. Must have at
110      *                   least length 2.
111      * @throws IllegalArgumentException if provided array does not have at least
112      *                                  length 2.
113      */
114     public final void setPolyParams(final double... polyParams) {
115         if (polyParams.length < MIN_VALID_POLY_PARAMS_LENGTH) {
116             throw new IllegalArgumentException("must have at least length 1");
117         }
118 
119         this.polyParams = polyParams;
120     }
121 
122     /**
123      * Gets degree of polynomial.
124      *
125      * @return degree of polynomial.
126      */
127     public int getDegree() {
128         for (var i = polyParams.length - 1; i >= 1; i--) {
129             if (Math.abs(polyParams[i]) > EPS) {
130                 return i;
131             }
132         }
133 
134         return 0;
135     }
136 
137     /**
138      * Adds this polynomial to another one and stores the result into provided
139      * instance.
140      *
141      * @param other  other polynomial to be added.
142      * @param result instance where result will be stored.
143      */
144     @SuppressWarnings("Duplicates")
145     public void add(final Polynomial other, final Polynomial result) {
146         final var maxLength = Math.max(polyParams.length, other.polyParams.length);
147         final var minLength = Math.min(polyParams.length, other.polyParams.length);
148 
149         var resultPolyParams = result.polyParams;
150         if (resultPolyParams.length != maxLength) {
151             resultPolyParams = new double[maxLength];
152         }
153 
154         for (var i = 0; i < minLength; i++) {
155             resultPolyParams[i] = polyParams[i] + other.polyParams[i];
156         }
157 
158         if (polyParams.length > other.polyParams.length) {
159             // this is longer than other
160             System.arraycopy(polyParams, minLength, resultPolyParams, minLength, maxLength - minLength);
161         } else {
162             // other is longer than this
163             System.arraycopy(other.polyParams, minLength, resultPolyParams, minLength, maxLength - minLength);
164         }
165 
166         result.polyParams = resultPolyParams;
167     }
168 
169     /**
170      * Adds another polynomial to this polynomial.
171      *
172      * @param other other polynomial to be added.
173      */
174     public void add(final Polynomial other) {
175         add(other, this);
176     }
177 
178     /**
179      * Adds this polynomial to another one and returns a new polynomial as a
180      * result.
181      *
182      * @param other other polynomial to be added.
183      * @return a new polynomial containing the sum.
184      */
185     public Polynomial addAndReturnNew(final Polynomial other) {
186         final var length = Math.max(polyParams.length, other.polyParams.length);
187         final var result = new Polynomial(length);
188         add(other, result);
189 
190         return result;
191     }
192 
193     /**
194      * Subtract other polynomial from this one and stores the result into
195      * provided instance.
196      *
197      * @param other  other polynomial to be subtracted from this one.
198      * @param result instance where result will be stored.
199      */
200     @SuppressWarnings("Duplicates")
201     public void subtract(final Polynomial other, final Polynomial result) {
202         final var maxLength = Math.max(polyParams.length, other.polyParams.length);
203         final var minLength = Math.min(polyParams.length, other.polyParams.length);
204 
205         var resultPolyParams = result.polyParams;
206         if (resultPolyParams.length != maxLength) {
207             resultPolyParams = new double[maxLength];
208         }
209 
210         for (var i = 0; i < minLength; i++) {
211             resultPolyParams[i] = polyParams[i] - other.polyParams[i];
212         }
213 
214         if (polyParams.length > other.polyParams.length) {
215             // this is longer than other
216             System.arraycopy(polyParams, minLength, resultPolyParams, minLength, maxLength - minLength);
217         } else {
218             // other is longer than this
219             for (var i = minLength; i < maxLength; i++) {
220                 resultPolyParams[i] = -other.polyParams[i];
221             }
222         }
223 
224         result.polyParams = resultPolyParams;
225     }
226 
227     /**
228      * Subtracts another polynomial form this one.
229      *
230      * @param other other polynomial to be subtracted from this one.
231      */
232     public void subtract(final Polynomial other) {
233         subtract(other, this);
234     }
235 
236     /**
237      * Subtract other polynomial from this one and returns a new polynomial as a
238      * result.
239      *
240      * @param other other polynomial to be subtracted from this one.
241      * @return a new polynomial containing result of subtraction.
242      */
243     public Polynomial subtractAndReturnNew(final Polynomial other) {
244         final var length = Math.max(polyParams.length, other.polyParams.length);
245         final var result = new Polynomial(length);
246         subtract(other, result);
247 
248         return result;
249     }
250 
251     /**
252      * Multiplies two polynomials.
253      *
254      * @param other  other polynomial to multiply with.
255      * @param result instance where resulting polynomial will be stored.
256      */
257     public void multiply(final Polynomial other, final Polynomial result) {
258         final var thisLength = polyParams.length;
259         final var otherLength = other.polyParams.length;
260         final var resultLength = thisLength + otherLength - 1;
261         if (result.polyParams.length != resultLength || result == this) {
262             // if length does not match or result is stored in this polynomial,
263             // create new polynomial array of parameters
264             result.polyParams = Convolver1D.convolve(polyParams, other.polyParams);
265         } else {
266             // if length is the same, overwrite values
267             Convolver1D.convolve(polyParams, other.polyParams, result.polyParams);
268         }
269     }
270 
271     /**
272      * Multiplies this polynomial with another one.
273      *
274      * @param other other polynomial to multiply with.
275      */
276     public void multiply(final Polynomial other) {
277         multiply(other, this);
278     }
279 
280     /**
281      * Multiplies two polynomials and returns a new instance containing result.
282      *
283      * @param other other polynomial to multiply with.
284      * @return a new polynomial containing result of multiplication.
285      */
286     public Polynomial multiplyAndReturnNew(final Polynomial other) {
287         final var thisLength = polyParams.length;
288         final var otherLength = other.polyParams.length;
289         final var resultLength = thisLength + otherLength - 1;
290         final var result = new Polynomial(resultLength);
291         Convolver1D.convolve(polyParams, other.polyParams, result.polyParams);
292 
293         return result;
294     }
295 
296     /**
297      * Multiplies all parameters of this polynomial by a scalar and stores the
298      * result into provided polynomial instance.
299      *
300      * @param scalar scalar to multiply parameters with.
301      * @param result instance where result will be stored.
302      */
303     public void multiplyByScalar(final double scalar, final Polynomial result) {
304         var resultPolyParams = result.polyParams;
305         if (resultPolyParams.length != polyParams.length || result == this) {
306             resultPolyParams = new double[polyParams.length];
307         }
308         ArrayUtils.multiplyByScalar(polyParams, scalar, resultPolyParams);
309         result.polyParams = resultPolyParams;
310     }
311 
312     /**
313      * Multiplies all parameters of this polynomial by provided scalar.
314      *
315      * @param scalar scalar to multiply parameters with.
316      */
317     public void multiplyByScalar(final double scalar) {
318         multiplyByScalar(scalar, this);
319     }
320 
321     /**
322      * Multiplies all parameters of this polynomial by a scalar and returns a
323      * new polynomial containing the result.
324      *
325      * @param scalar scalar to multiply parameters with.
326      * @return a new polynomial containing the result of the operation.
327      */
328     public Polynomial multiplyByScalarAndReturnNew(final double scalar) {
329         final var result = new Polynomial(polyParams.length);
330         multiplyByScalar(scalar, result);
331         return result;
332     }
333 
334     /**
335      * Gets roots of polynomial.
336      *
337      * @return estimated roots of this polynomial
338      * @throws NumericalException if roots estimation fails.
339      */
340     public Complex[] getRoots() throws NumericalException {
341         final var degree = getDegree();
342 
343         final PolynomialRootsEstimator estimator;
344         switch (degree) {
345             case 0:
346                 // no roots
347                 return null;
348             case 1:
349                 // first degree
350                 estimator = new FirstDegreePolynomialRootsEstimator(polyParams);
351                 break;
352             case 2:
353                 // second degree
354                 estimator = new SecondDegreePolynomialRootsEstimator(polyParams);
355                 break;
356             case 3:
357                 // third degree
358                 estimator = new ThirdDegreePolynomialRootsEstimator(polyParams);
359                 break;
360             default:
361                 // greater degree
362 
363                 // copy real parameters into complex values
364                 final var params = new Complex[this.polyParams.length];
365                 for (int i = 0; i < this.polyParams.length; i++) {
366                     params[i] = new Complex(this.polyParams[i]);
367                 }
368                 estimator = new LaguerrePolynomialRootsEstimator(params);
369                 break;
370         }
371 
372         estimator.estimate();
373         return estimator.getRoots();
374     }
375 
376     /**
377      * Evaluates polynomial at provided value.
378      *
379      * @param x value to evaluate polynomial at.
380      * @return result of polynomial evaluation.
381      */
382     public double evaluate(final double x) {
383         var result = 0.0;
384         var powX = 1.0;
385         for (var polyParam : polyParams) {
386             result += polyParam * powX;
387             powX *= x;
388         }
389 
390         return result;
391     }
392 
393     /**
394      * Computes derivative of polynomial.
395      *
396      * @param result instance where derivative will be stored.
397      */
398     @SuppressWarnings("Duplicates")
399     public void derivative(final Polynomial result) {
400         final var resultLength = polyParams.length - 1;
401         final var resultLength2 = Math.max(resultLength, 1);
402 
403         var resultPolyParams = result.polyParams;
404         if (resultPolyParams.length != resultLength2 || result == this) {
405             resultPolyParams = new double[resultLength2];
406         }
407         if (resultLength == 0) {
408             resultPolyParams[0] = 0.0;
409         }
410 
411         for (int i = 0, j = 1; i < resultLength; i++, j++) {
412             resultPolyParams[i] = j * polyParams[j];
413         }
414 
415         result.polyParams = resultPolyParams;
416     }
417 
418     /**
419      * Replaces this instance by its derivative.
420      */
421     public void derivative() {
422         derivative(this);
423     }
424 
425     /**
426      * Computes derivative of polynomial.
427      *
428      * @return a new instance containing derivative.
429      */
430     public Polynomial derivativeAndReturnNew() {
431         final var resultLength = Math.max(polyParams.length - 1, 1);
432         final var result = new Polynomial(resultLength);
433         derivative(result);
434         return result;
435     }
436 
437     /**
438      * Evaluates derivative of polynomial at provided value.
439      *
440      * @param x value to evaluate derivative of polynomial at.
441      * @return result of evaluation of derivative.
442      */
443     public double evaluateDerivative(final double x) {
444         var result = 0.0;
445         var powX = 1.0;
446         for (var j = 1; j < polyParams.length; j++) {
447             result += j * polyParams[j] * powX;
448             powX *= x;
449         }
450 
451         return result;
452     }
453 
454     /**
455      * Computes second derivative of polynomial.
456      *
457      * @param result instance where second derivative will be stored.
458      */
459     @SuppressWarnings("Duplicates")
460     public void secondDerivative(final Polynomial result) {
461         final var resultLength = polyParams.length - 2;
462         final var resultLength2 = Math.max(resultLength, 1);
463 
464         var resultPolyParams = result.polyParams;
465         if (resultPolyParams.length != resultLength2 || result == this) {
466             resultPolyParams = new double[resultLength2];
467         }
468         if (resultLength == 0) {
469             resultPolyParams[0] = 0.0;
470         }
471 
472         for (int i = 0, j = 2, k = 1; i < resultLength; i++, j++, k++) {
473             resultPolyParams[i] = j * k * polyParams[j];
474         }
475 
476         result.polyParams = resultPolyParams;
477     }
478 
479     /**
480      * Replaces this instance by its second derivative.
481      */
482     public void secondDerivative() {
483         secondDerivative(this);
484     }
485 
486     /**
487      * Computes second derivative of polynomial.
488      *
489      * @return a new instance containing second derivative.
490      */
491     public Polynomial secondDerivativeAndReturnNew() {
492         final var resultLength = Math.max(polyParams.length - 2, 1);
493         final var result = new Polynomial(resultLength);
494         secondDerivative(result);
495         return result;
496     }
497 
498     /**
499      * Evaluates second derivative of polynomial at provided value.
500      *
501      * @param x value to evaluate second derivative of polynomial at.
502      * @return result of evaluation of second derivative.
503      */
504     public double evaluateSecondDerivative(final double x) {
505         var result = 0.0;
506         var powX = 1.0;
507         for (int j = 2, k = 1; j < polyParams.length; j++, k++) {
508             result += j * k * polyParams[j] * powX;
509             powX *= x;
510         }
511 
512         return result;
513     }
514 
515     /**
516      * Computes nth-order derivative of polynomial.
517      *
518      * @param order  order of derivative to compute. Must be at least 1.
519      * @param result instance where nth-order derivative will be stored.
520      * @throws IllegalArgumentException if provided order is less than 1.
521      */
522     @SuppressWarnings("Duplicates")
523     public void nthDerivative(final int order, final Polynomial result) {
524         if (order < MIN_ORDER) {
525             throw new IllegalArgumentException();
526         }
527 
528         final var resultLength = polyParams.length - order;
529         final var resultLength2 = Math.max(resultLength, 1);
530 
531         var resultPolyParams = result.polyParams;
532         if (resultPolyParams.length != resultLength2 || result == this) {
533             resultPolyParams = new double[resultLength2];
534         }
535         if (resultLength == 0) {
536             resultPolyParams[0] = 0.0;
537         }
538 
539         for (int i = 0, j = order; i < resultLength; i++, j++) {
540             var param = j;
541             for (var k = 1; k < order; k++) {
542                 param *= j - k;
543             }
544             resultPolyParams[i] = param * polyParams[j];
545         }
546 
547         result.polyParams = resultPolyParams;
548     }
549 
550     /**
551      * Replaces this instance by its nth-order derivative.
552      *
553      * @param order order of derivative to compute. Must be at least 1.
554      * @throws IllegalArgumentException if provided order is less than 1.
555      */
556     public void nthDerivative(final int order) {
557         nthDerivative(order, this);
558     }
559 
560     /**
561      * Computes nth-order derivative of polynomial.
562      *
563      * @param order order of derivative to compute. Must be at least 1.
564      * @return a new instance containing nth-order derivative.
565      * @throws IllegalArgumentException if provided order is less than 1.
566      */
567     public Polynomial nthDerivativeAndReturnNew(final int order) {
568         final var resultLength = Math.max(polyParams.length - order, 1);
569         final var result = new Polynomial(resultLength);
570         nthDerivative(order, result);
571         return result;
572     }
573 
574     /**
575      * Evaluates nth-derivative of polynomial at provided value.
576      *
577      * @param x     value to evaluate nth-derivative of polynomial at.
578      * @param order order of derivative to evaluate. Must be at least 1.
579      * @return result of evaluation of nth-derivative.
580      * @throws IllegalArgumentException if provided order is less than 1.
581      */
582     public double evaluateNthDerivative(final double x, final int order) {
583         if (order < MIN_ORDER) {
584             throw new IllegalArgumentException("order must be at least 1");
585         }
586 
587         var result = 0.0;
588         var powX = 1.0;
589         for (var i = order; i < polyParams.length; i++) {
590             var param = i;
591             for (var j = 1; j < order; j++) {
592                 param *= i - j;
593             }
594             result += param * polyParams[i] * powX;
595             powX *= x;
596         }
597 
598         return result;
599     }
600 
601     /**
602      * Computes polynomial containing the integration of current one.
603      * Because infinite polynomials exist with different constant values,
604      * constant term can be provided as well.
605      *
606      * @param result   instance where resulting polynomial will be stored.
607      * @param constant constant term.
608      */
609     public void integration(final Polynomial result, final double constant) {
610         final var resultLength = polyParams.length + 1;
611         var resultPolyParams = result.polyParams;
612         if (resultPolyParams.length != resultLength || result == this) {
613             resultPolyParams = new double[resultLength];
614         }
615 
616         resultPolyParams[0] = constant;
617         for (int i = 0, j = 1; i < polyParams.length; i++, j++) {
618             resultPolyParams[j] = polyParams[i] / j;
619         }
620 
621         result.polyParams = resultPolyParams;
622     }
623 
624     /**
625      * Computes polynomial containing the integration of current one and
626      * assuming a zero constant term.
627      *
628      * @param result instance where resulting polynomial will be stored.
629      */
630     public void integration(final Polynomial result) {
631         integration(result, 0.0);
632     }
633 
634     /**
635      * Updates this instance to contain its integration.
636      *
637      * @param constant constant term.
638      */
639     public void integration(final double constant) {
640         integration(this, constant);
641     }
642 
643     /**
644      * Updates this instance to contain its integration using a zero constant
645      * term.
646      */
647     public void integration() {
648         integration(this);
649     }
650 
651     /**
652      * Computes polynomial containing the integration of current one.
653      * Because infinite polynomials exist with different constant values,
654      * constant term can be provided as well.
655      *
656      * @param constant constant term.
657      * @return a new instance containing integration polynomial.
658      */
659     public Polynomial integrationAndReturnNew(final double constant) {
660         final var result = new Polynomial(polyParams.length + 1);
661         integration(result, constant);
662         return result;
663     }
664 
665     /**
666      * Computes polynomial containing the integration of current one and
667      * assuming a zero constant term.
668      *
669      * @return a new instance containing integration polynomial.
670      */
671     public Polynomial integrationAndReturnNew() {
672         return integrationAndReturnNew(0.0);
673     }
674 
675     /**
676      * Integrate polynomial within provided interval.
677      *
678      * @param startX start of integration interval.
679      * @param endX   end of integration interval.
680      * @return result of integration.
681      */
682     public double integrateInterval(final double startX, final double endX) {
683 
684         var resultStart = 0.0;
685         var resultEnd = 0.0;
686         var powStartX = startX;
687         var powEndX = endX;
688         double polyParam;
689         for (int i = 0, j = 1; i < polyParams.length; i++, j++) {
690             polyParam = polyParams[i] / j;
691             resultStart += polyParam * powStartX;
692             powStartX *= startX;
693 
694             resultEnd += polyParam * powEndX;
695             powEndX *= endX;
696         }
697 
698         return resultEnd - resultStart;
699     }
700 
701     /**
702      * Computes polynomial containing the nth-order integration of current one.
703      * Because infinite polynomials exist with different constant values,
704      * constant terms for each integration order can be provided as well.
705      *
706      * @param order     order of integration to compute. Must be at least 1.
707      * @param result    instance where resulting polynomial will be stored.
708      * @param constants constant terms for each integration order. Must have a
709      *                  length equal to order if provided.
710      * @throws IllegalArgumentException if provided order is less than 1 or if
711      *                                  constants does not have length equal to order.
712      */
713     public void nthIntegration(final int order, final Polynomial result, final double[] constants) {
714         if (order < MIN_ORDER) {
715             throw new IllegalArgumentException("order must be at least 1");
716         }
717         if (constants != null && constants.length != order) {
718             throw new IllegalArgumentException("length of constants must be order");
719         }
720         final var resultLength = polyParams.length + order;
721         var resultPolyParams = result.polyParams;
722         if (resultPolyParams.length != resultLength || result == this) {
723             resultPolyParams = new double[resultLength];
724         }
725 
726         for (var i = 0; i < order; i++) {
727             if (constants != null) {
728                 var param = 1;
729                 for (var k = 1; k <= i; k++) {
730                     param *= k;
731                 }
732                 resultPolyParams[i] = constants[i] / param;
733             } else {
734                 resultPolyParams[i] = 0.0;
735             }
736         }
737         for (int i = 0, j = order; i < polyParams.length; i++, j++) {
738             var param = j;
739             for (var k = 1; k < order; k++) {
740                 param *= j - k;
741             }
742             resultPolyParams[j] = polyParams[i] / param;
743         }
744 
745         result.polyParams = resultPolyParams;
746     }
747 
748     /**
749      * Computes polynomial containing the nth-order integration of current one.
750      *
751      * @param order  order of integration to compute. Must be at least 1.
752      * @param result instance where resulting polynomial will be stored.
753      * @throws IllegalArgumentException if provided order is less than 1.
754      */
755     public void nthIntegration(final int order, final Polynomial result) {
756         nthIntegration(order, result, null);
757     }
758 
759     /**
760      * Computes polynomial containing the nth-order integration of current one.
761      * Because infinite polynomials exist with different constant values,
762      * constant terms for each integration order can be provided as well.
763      *
764      * @param order     order of integration to compute. Must be at least 1.
765      * @param constants constant terms for each integration order. Must have a
766      *                  length equal to order if provided.
767      * @throws IllegalArgumentException if provided order is less than 1 or if
768      *                                  constants does not have length equal to order.
769      */
770     public void nthIntegration(final int order, final double[] constants) {
771         nthIntegration(order, this, constants);
772     }
773 
774     /**
775      * Computes polynomial containing the nth-order integration of current one.
776      *
777      * @param order order of integration to compute. Must be at least 1.
778      */
779     public void nthIntegration(final int order) {
780         nthIntegration(order, (double[]) null);
781     }
782 
783     /**
784      * Computes polynomial containing the nth-order integration of current one.
785      * Because infinite polynomials exist with different constant values,
786      * constant terms for each integration order can be provided as well.
787      *
788      * @param order     order of integration to compute. Must be at least 1.
789      * @param constants constant terms for each integration order. Must have a
790      *                  length equal to order if provided.
791      * @return a new polynomial containing the nth-order integration.
792      * @throws IllegalArgumentException if provided order is less than 1 or if
793      *                                  constants does not have length equal to order.
794      */
795     public Polynomial nthIntegrationAndReturnNew(final int order, final double[] constants) {
796         final var result = new Polynomial();
797         nthIntegration(order, result, constants);
798         return result;
799     }
800 
801     /**
802      * Computes polynomial containing the nth-order integration of current one.
803      *
804      * @param order order of integration to compute. Must be at least 1.
805      * @return a new polynomial containing the nth-order integration.
806      * @throws IllegalArgumentException if provided order is less than 1 or if
807      *                                  constants does not have length equal to order.
808      */
809     public Polynomial nthIntegrationAndReturnNew(final int order) {
810         return nthIntegrationAndReturnNew(order, null);
811     }
812 
813     /**
814      * Computes nth-integration over provided interval.
815      *
816      * @param startX    start of integration interval.
817      * @param endX      end of integration interval.
818      * @param order     order of integration. Must be at least 1.
819      * @param constants constant terms for each integration order. Must have a
820      *                  length equal to order if provided.
821      * @return result of integration.
822      * @throws IllegalArgumentException if provided order is less than 1 or if
823      *                                  constants does not have length equal to order.
824      */
825     public double nthOrderIntegrateInterval(
826             final double startX, final double endX, final int order, final double[] constants) {
827         if (order < MIN_ORDER) {
828             throw new IllegalArgumentException();
829         }
830         if (constants != null && constants.length != order) {
831             throw new IllegalArgumentException();
832         }
833 
834         var resultStart = 0.0;
835         var resultEnd = 0.0;
836         var powStartX = 1.0;
837         var powEndX = 1.0;
838         double polyParam;
839         for (var i = 0; i < order; i++) {
840             if (constants != null) {
841                 var param = 1;
842                 for (var k = 1; k <= i; k++) {
843                     param *= k;
844                 }
845                 polyParam = constants[i] / param;
846                 resultStart += polyParam * powStartX;
847                 resultEnd += polyParam * powEndX;
848             }
849             powStartX *= startX;
850             powEndX *= endX;
851         }
852 
853         for (int i = 0, j = order; i < polyParams.length; i++, j++) {
854             var param = j;
855             for (var k = 1; k < order; k++) {
856                 param *= j - k;
857             }
858             polyParam = polyParams[i] / param;
859             resultStart += polyParam * powStartX;
860             powStartX *= startX;
861 
862             resultEnd += polyParam * powEndX;
863             powEndX *= endX;
864         }
865 
866         return resultEnd - resultStart;
867     }
868 
869     /**
870      * Computes nth-integration over provided interval.
871      *
872      * @param startX start of integration interval.
873      * @param endX   end of integration interval.
874      * @param order  order of integration. Must be at least 1.
875      * @return result of integration.
876      * @throws IllegalArgumentException if provided order is less than 1.
877      */
878     public double nthOrderIntegrateInterval(final double startX, final double endX, final int order) {
879         return nthOrderIntegrateInterval(startX, endX, order, null);
880     }
881 
882     /**
883      * Trims polynomial to remove all terms above degree that can be neglected.
884      *
885      * @param result instance where result will be stored.
886      */
887     public void trim(final Polynomial result) {
888         final var degree = getDegree();
889         final var resultLength = degree + 1;
890 
891         final double[] resultPolyParams;
892         if (result.polyParams.length != resultLength) {
893             resultPolyParams = new double[resultLength];
894         } else {
895             resultPolyParams = result.polyParams;
896         }
897         System.arraycopy(polyParams, 0, resultPolyParams, 0, resultLength);
898 
899         result.polyParams = resultPolyParams;
900     }
901 
902     /**
903      * Trims this polynomial to remove all terms above degree that can be
904      * neglected.
905      */
906     public void trim() {
907         trim(this);
908     }
909 
910     /**
911      * Trims this polynomial to remove all terms above degree that can be
912      * neglected and returns the result as a new polynomial.
913      *
914      * @return a new trimmed polynomial.
915      */
916     public Polynomial trimAndReturnNew() {
917         final var result = new Polynomial();
918         trim(result);
919         return result;
920     }
921 
922     /**
923      * Normalizes parameters of this polynomial so that the array of parameters
924      * has unitary norm and stores result into provided instance.
925      * Normalization keeps location of real roots, but other roots or
926      * properties of polynomials might change.
927      *
928      * @param result instance where normalized polynomial will be stored.
929      */
930     public void normalize(final Polynomial result) {
931         var resultPolyParams = result.polyParams;
932         if (resultPolyParams.length != polyParams.length) {
933             resultPolyParams = new double[polyParams.length];
934         }
935         ArrayUtils.normalize(polyParams, resultPolyParams);
936         result.polyParams = resultPolyParams;
937     }
938 
939     /**
940      * Normalizes this polynomial so that the array of parameters has unitary
941      * norm.
942      * Normalization keeps location of real roots, but other roots or
943      * properties of polynomials might change.
944      */
945     public void normalize() {
946         normalize(this);
947     }
948 
949     /**
950      * Normalizes parameters of this polynomial so that the array of parameters
951      * has unitary norm and returns result as a new polynomial instance.
952      * Normalization keeps location of real roots, but other roots or
953      * properties of polynomials might change.
954      *
955      * @return a new normalized polynomial instance.
956      */
957     public Polynomial normalizeAndReturnNew() {
958         final var result = new Polynomial(polyParams.length);
959         normalize(result);
960         return result;
961     }
962 
963     /**
964      * Normalizes parameters of this polynomial so that the highest degree term
965      * becomes 1.0 and stores result into provided instance.
966      *
967      * @param result instance where result of normalization will be stored.
968      */
969     public void normalizeHighestDegreeTerm(final Polynomial result) {
970         final var degree = getDegree();
971         final var term = polyParams[degree];
972         var resultPolyParams = result.polyParams;
973         if (resultPolyParams.length != polyParams.length) {
974             resultPolyParams = new double[polyParams.length];
975         }
976         ArrayUtils.multiplyByScalar(polyParams, 1.0 / term, resultPolyParams);
977         result.polyParams = resultPolyParams;
978     }
979 
980     /**
981      * Normalizes parameters of this polynomial so that the highest degree term
982      * becomes 1.0.
983      */
984     public void normalizeHighestDegreeTerm() {
985         normalizeHighestDegreeTerm(this);
986     }
987 
988     /**
989      * Normalizes parameters of this polynomial so that the highest degree term
990      * becomes 1.0 and returns the result as a new instance.
991      *
992      * @return a new normalized polynomial.
993      */
994     public Polynomial normalizeHighestDegreeTermAndReturnNew() {
995         final var result = new Polynomial(polyParams.length);
996         normalizeHighestDegreeTerm(result);
997         return result;
998     }
999 
1000     /**
1001      * Gets location of maxima in this polynomial.
1002      *
1003      * @return location of maxima or null if polynomial has no maxima.
1004      * @throws NumericalException if maxima cannot be determined due to
1005      *                            numerical instabilities.
1006      */
1007     public double[] getMaxima() throws NumericalException {
1008         return getMaxima(EPS);
1009     }
1010 
1011     /**
1012      * Gets location of maxima in this polynomial.
1013      *
1014      * @param threshold threshold to allow possible small deviations in first
1015      *                  derivative respect to pure real roots. This should be a very small
1016      *                  positive value.
1017      * @return location of maxima or null if polynomial has no maxima.
1018      * @throws NumericalException       if maxima cannot be determined due to
1019      *                                  numerical instabilities.
1020      * @throws IllegalArgumentException if provided threshold is negative.
1021      */
1022     @SuppressWarnings("Duplicates")
1023     public double[] getMaxima(final double threshold) throws NumericalException {
1024         if (threshold < 0.0) {
1025             throw new IllegalArgumentException();
1026         }
1027 
1028         final var derivative = derivativeAndReturnNew();
1029 
1030         // roots of derivative contains either minima or maxima.
1031         final var derivativeRoots = derivative.getRoots();
1032         final var maxima = new ArrayList<Complex>();
1033         if (derivativeRoots != null) {
1034             for (var derivativeRoot : derivativeRoots) {
1035                 if (Math.abs(derivativeRoot.getImaginary()) > threshold) {
1036                     // root is imaginary (not allowed)
1037                     continue;
1038                 }
1039 
1040                 final var x = derivativeRoot.getReal();
1041                 final var secondDerivativeEval = evaluateSecondDerivative(x);
1042                 if (secondDerivativeEval < 0.0) {
1043                     // is maxima
1044                     maxima.add(derivativeRoot);
1045                 }
1046             }
1047         }
1048 
1049         // return real parts of maxima, since we only allow real roots of first
1050         // derivative
1051         if (maxima.isEmpty()) {
1052             return null;
1053         }
1054 
1055         final var result = new double[maxima.size()];
1056         int i = 0;
1057         for (final var m : maxima) {
1058             result[i] = m.getReal();
1059             i++;
1060         }
1061 
1062         return result;
1063     }
1064 
1065     /**
1066      * Gets location of minima in this polynomial.
1067      *
1068      * @return location of minima or null if polynomial has no minima.
1069      * @throws NumericalException if minima cannot be determined due to
1070      *                            numerical instabilities.
1071      */
1072     public double[] getMinima() throws NumericalException {
1073         return getMinima(EPS);
1074     }
1075 
1076     /**
1077      * Gets location of minima in this polynomial.
1078      *
1079      * @param threshold threshold to allow possible small deviations in first
1080      *                  derivative respect to pure real roots. This should be a very small
1081      *                  positive value.
1082      * @return location of minima or null if polynomial has no minima.
1083      * @throws NumericalException       if minima cannot be determined due to
1084      *                                  numerical instabilities.
1085      * @throws IllegalArgumentException if provided threshold is negative.
1086      */
1087     @SuppressWarnings("Duplicates")
1088     public double[] getMinima(double threshold) throws NumericalException {
1089         if (threshold < 0.0) {
1090             throw new IllegalArgumentException();
1091         }
1092 
1093         final var derivative = derivativeAndReturnNew();
1094 
1095         // roots of derivative contains either minima or maxima.
1096         final var derivativeRoots = derivative.getRoots();
1097         final var minima = new ArrayList<Complex>();
1098         if (derivativeRoots != null) {
1099             for (final var derivativeRoot : derivativeRoots) {
1100                 if (Math.abs(derivativeRoot.getImaginary()) > threshold) {
1101                     //root is imaginary (not allowed)
1102                     continue;
1103                 }
1104 
1105                 final var x = derivativeRoot.getReal();
1106                 final var secondDerivativeEval = evaluateSecondDerivative(x);
1107                 if (secondDerivativeEval >= 0.0) {
1108                     // is minima
1109                     minima.add(derivativeRoot);
1110                 }
1111             }
1112         }
1113 
1114         // return real parts of minima, since we only allow real roots of first
1115         // derivative
1116         if (minima.isEmpty()) {
1117             return null;
1118         }
1119 
1120         final var result = new double[minima.size()];
1121         var i = 0;
1122         for (final var m : minima) {
1123             result[i] = m.getReal();
1124             i++;
1125         }
1126 
1127         return result;
1128     }
1129 
1130     /**
1131      * Gets location of minima or maxima (i.e. extrema) in this polynomial.
1132      *
1133      * @return location of minima or maxima, or null if polynomial has no
1134      * minima or maxima.
1135      * @throws NumericalException if minima or maxima cannot be determined due
1136      *                            to numerical instabilities.
1137      */
1138     public double[] getExtrema() throws NumericalException {
1139         return getExtrema(EPS);
1140     }
1141 
1142     /**
1143      * Gets location of minima or maxima (i.e. extrema) in this polynomial.
1144      *
1145      * @param threshold threshold to allow possible small deviations in first
1146      *                  derivative respect to pure real roots. This should be a very small
1147      *                  positive value.
1148      * @return location of minima or maxima, or null if polynomial has no minima
1149      * or maxima.
1150      * @throws NumericalException       if minima or maxima cannot be determined due
1151      *                                  to numerical instabilities.
1152      * @throws IllegalArgumentException if provided threshold is negative.
1153      */
1154     @SuppressWarnings("DuplicatedCode")
1155     public double[] getExtrema(final double threshold) throws NumericalException {
1156         if (threshold < 0.0) {
1157             throw new IllegalArgumentException("threshold must be positive");
1158         }
1159 
1160         final var derivative = derivativeAndReturnNew();
1161 
1162         // roots of derivative contains either minima or maxima.
1163         final var derivativeRoots = derivative.getRoots();
1164         final var minimaOrMaxima = new ArrayList<Complex>();
1165         if (derivativeRoots != null) {
1166             for (final var derivativeRoot : derivativeRoots) {
1167                 if (Math.abs(derivativeRoot.getImaginary()) > threshold) {
1168                     // root is imaginary (not allowed)
1169                     continue;
1170                 }
1171 
1172                 minimaOrMaxima.add(derivativeRoot);
1173             }
1174         }
1175 
1176         // return real parts of roots, since we only allow real roots of first
1177         // derivative
1178         if (minimaOrMaxima.isEmpty()) {
1179             return null;
1180         }
1181 
1182         final var result = new double[minimaOrMaxima.size()];
1183         int i = 0;
1184         for (final var m : minimaOrMaxima) {
1185             result[i] = m.getReal();
1186             i++;
1187         }
1188 
1189         return result;
1190     }
1191 }