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 }