1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
33
34
35 public class Polynomial implements Serializable {
36
37
38
39
40 private static final int MIN_ORDER = 1;
41
42
43
44
45 public static final int MIN_VALID_POLY_PARAMS_LENGTH = 1;
46
47
48
49
50 public static final double EPS = 1e-10;
51
52
53
54
55
56
57 private double[] polyParams;
58
59
60
61
62
63 public Polynomial() {
64 polyParams = new double[MIN_VALID_POLY_PARAMS_LENGTH];
65 }
66
67
68
69
70
71
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
82
83
84
85
86
87
88
89 public Polynomial(final double... polyParams) {
90 setPolyParams(polyParams);
91 }
92
93
94
95
96
97
98
99
100 public double[] getPolyParams() {
101 return polyParams;
102 }
103
104
105
106
107
108
109
110
111
112
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
124
125
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
139
140
141
142
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
160 System.arraycopy(polyParams, minLength, resultPolyParams, minLength, maxLength - minLength);
161 } else {
162
163 System.arraycopy(other.polyParams, minLength, resultPolyParams, minLength, maxLength - minLength);
164 }
165
166 result.polyParams = resultPolyParams;
167 }
168
169
170
171
172
173
174 public void add(final Polynomial other) {
175 add(other, this);
176 }
177
178
179
180
181
182
183
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
195
196
197
198
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
216 System.arraycopy(polyParams, minLength, resultPolyParams, minLength, maxLength - minLength);
217 } else {
218
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
229
230
231
232 public void subtract(final Polynomial other) {
233 subtract(other, this);
234 }
235
236
237
238
239
240
241
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
253
254
255
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
263
264 result.polyParams = Convolver1D.convolve(polyParams, other.polyParams);
265 } else {
266
267 Convolver1D.convolve(polyParams, other.polyParams, result.polyParams);
268 }
269 }
270
271
272
273
274
275
276 public void multiply(final Polynomial other) {
277 multiply(other, this);
278 }
279
280
281
282
283
284
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
298
299
300
301
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
314
315
316
317 public void multiplyByScalar(final double scalar) {
318 multiplyByScalar(scalar, this);
319 }
320
321
322
323
324
325
326
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
336
337
338
339
340 public Complex[] getRoots() throws NumericalException {
341 final var degree = getDegree();
342
343 final PolynomialRootsEstimator estimator;
344 switch (degree) {
345 case 0:
346
347 return null;
348 case 1:
349
350 estimator = new FirstDegreePolynomialRootsEstimator(polyParams);
351 break;
352 case 2:
353
354 estimator = new SecondDegreePolynomialRootsEstimator(polyParams);
355 break;
356 case 3:
357
358 estimator = new ThirdDegreePolynomialRootsEstimator(polyParams);
359 break;
360 default:
361
362
363
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
378
379
380
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
395
396
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
420
421 public void derivative() {
422 derivative(this);
423 }
424
425
426
427
428
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
439
440
441
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
456
457
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
481
482 public void secondDerivative() {
483 secondDerivative(this);
484 }
485
486
487
488
489
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
500
501
502
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
517
518
519
520
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
552
553
554
555
556 public void nthDerivative(final int order) {
557 nthDerivative(order, this);
558 }
559
560
561
562
563
564
565
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
576
577
578
579
580
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
603
604
605
606
607
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
626
627
628
629
630 public void integration(final Polynomial result) {
631 integration(result, 0.0);
632 }
633
634
635
636
637
638
639 public void integration(final double constant) {
640 integration(this, constant);
641 }
642
643
644
645
646
647 public void integration() {
648 integration(this);
649 }
650
651
652
653
654
655
656
657
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
667
668
669
670
671 public Polynomial integrationAndReturnNew() {
672 return integrationAndReturnNew(0.0);
673 }
674
675
676
677
678
679
680
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
703
704
705
706
707
708
709
710
711
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
750
751
752
753
754
755 public void nthIntegration(final int order, final Polynomial result) {
756 nthIntegration(order, result, null);
757 }
758
759
760
761
762
763
764
765
766
767
768
769
770 public void nthIntegration(final int order, final double[] constants) {
771 nthIntegration(order, this, constants);
772 }
773
774
775
776
777
778
779 public void nthIntegration(final int order) {
780 nthIntegration(order, (double[]) null);
781 }
782
783
784
785
786
787
788
789
790
791
792
793
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
803
804
805
806
807
808
809 public Polynomial nthIntegrationAndReturnNew(final int order) {
810 return nthIntegrationAndReturnNew(order, null);
811 }
812
813
814
815
816
817
818
819
820
821
822
823
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
871
872
873
874
875
876
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
884
885
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
904
905
906 public void trim() {
907 trim(this);
908 }
909
910
911
912
913
914
915
916 public Polynomial trimAndReturnNew() {
917 final var result = new Polynomial();
918 trim(result);
919 return result;
920 }
921
922
923
924
925
926
927
928
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
941
942
943
944
945 public void normalize() {
946 normalize(this);
947 }
948
949
950
951
952
953
954
955
956
957 public Polynomial normalizeAndReturnNew() {
958 final var result = new Polynomial(polyParams.length);
959 normalize(result);
960 return result;
961 }
962
963
964
965
966
967
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
982
983
984 public void normalizeHighestDegreeTerm() {
985 normalizeHighestDegreeTerm(this);
986 }
987
988
989
990
991
992
993
994 public Polynomial normalizeHighestDegreeTermAndReturnNew() {
995 final var result = new Polynomial(polyParams.length);
996 normalizeHighestDegreeTerm(result);
997 return result;
998 }
999
1000
1001
1002
1003
1004
1005
1006
1007 public double[] getMaxima() throws NumericalException {
1008 return getMaxima(EPS);
1009 }
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
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
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
1037 continue;
1038 }
1039
1040 final var x = derivativeRoot.getReal();
1041 final var secondDerivativeEval = evaluateSecondDerivative(x);
1042 if (secondDerivativeEval < 0.0) {
1043
1044 maxima.add(derivativeRoot);
1045 }
1046 }
1047 }
1048
1049
1050
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
1067
1068
1069
1070
1071
1072 public double[] getMinima() throws NumericalException {
1073 return getMinima(EPS);
1074 }
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
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
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
1102 continue;
1103 }
1104
1105 final var x = derivativeRoot.getReal();
1106 final var secondDerivativeEval = evaluateSecondDerivative(x);
1107 if (secondDerivativeEval >= 0.0) {
1108
1109 minima.add(derivativeRoot);
1110 }
1111 }
1112 }
1113
1114
1115
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
1132
1133
1134
1135
1136
1137
1138 public double[] getExtrema() throws NumericalException {
1139 return getExtrema(EPS);
1140 }
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
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
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
1169 continue;
1170 }
1171
1172 minimaOrMaxima.add(derivativeRoot);
1173 }
1174 }
1175
1176
1177
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 }