1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.numerics.integrate;
39
40 import java.util.logging.Logger;
41 import java.util.stream.IntStream;
42
43 import static java.lang.String.format;
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 public class Integrate1DNumeric {
59
60 private static final Logger logger = Logger.getLogger(Integrate1DNumeric.class.getName());
61
62
63
64 private static final double ONE_THIRD = (1.0 / 3.0);
65
66
67
68 private static final double BOOLE_FACTOR = (2.0 / 45.0);
69
70 private Integrate1DNumeric() {
71
72 }
73
74
75
76
77
78
79
80
81 public static double booles(DataSet data, IntegrationSide side) {
82 double area = 0;
83 int lb = 0;
84 int ub = data.numPoints() - 1;
85 if (data.halfWidthEnds()) {
86 area = trapezoidalEnds(data, side);
87 lb++;
88 ub--;
89 }
90 area += booles(data, side, lb, ub);
91 return area;
92 }
93
94
95
96
97
98
99
100
101
102
103 public static double booles(DataSet data, IntegrationSide side, int lb, int ub) {
104 double area = 0;
105 double width = data.binWidth();
106 double[] points = data.getAllFxPoints();
107 int increment = 4;
108
109 int nBins = (ub - lb) / increment;
110 int lowerNeglected = 0;
111 int upperNeglected = 0;
112
113 switch (side) {
114 case RIGHT -> {
115 for (int i = ub; i > (lb + increment - 1); i -= increment) {
116 area += (7 * points[i] + 32 * points[i - 1] + 12 * points[i - 2]
117 + 32 * points[i - 3] + 7 * points[i - 4]);
118 }
119 lowerNeglected = lb;
120 upperNeglected = ub - (increment * nBins);
121 }
122 case LEFT -> {
123 for (int i = lb; i < (ub - increment + 1); i += increment) {
124 area += (7 * points[i] + 32 * points[i + 1] + 12 * points[i + 2]
125 + 32 * points[i + 3] + 7 * points[i + 4]);
126 }
127 lowerNeglected = lb + (increment * nBins);
128 upperNeglected = ub;
129 }
130 }
131 area *= BOOLE_FACTOR;
132 area *= width;
133
134 area += finishIntegration(data, side, lowerNeglected, upperNeglected, IntegrationType.BOOLE);
135 return area;
136 }
137
138
139
140
141
142
143
144
145
146 public static double boolesParallel(DataSet data, IntegrationSide side) {
147 double area = 0;
148 int lb = 0;
149 int ub = data.numPoints() - 1;
150 if (data.halfWidthEnds()) {
151 area = trapezoidalEnds(data, side);
152 lb++;
153 ub--;
154 }
155 area += boolesParallel(data, side, lb, ub);
156 return area;
157 }
158
159
160
161
162
163
164
165
166
167
168
169 public static double boolesParallel(DataSet data, IntegrationSide side, int lb, int ub) {
170 double area = 0.0;
171 double width = data.binWidth();
172 double[] points = data.getAllFxPoints();
173 int increment = 4;
174
175 int nBins = (ub - lb) / increment;
176 int lowerNeglected = 0;
177 int upperNeglected = 0;
178
179 switch (side) {
180 case RIGHT -> {
181 lowerNeglected = lb;
182 upperNeglected = ub - (increment * nBins);
183 area =
184 IntStream.range(0, nBins).parallel().mapToDouble(
185 (int i) -> {
186 int fromPoint = ub - (increment * i);
187 return (7 * points[fromPoint - 4]
188 + 32 * points[fromPoint - 3]
189 + 12 * points[fromPoint - 2]
190 + 32 * points[fromPoint - 1]
191 + 7 * points[fromPoint]);
192 }).sum();
193 }
194 case LEFT -> {
195 lowerNeglected = lb + (increment * nBins);
196 upperNeglected = ub;
197 area = IntStream.range(0, nBins).parallel().mapToDouble(
198 (int i) -> {
199 int fromPoint = lb + (increment * i);
200 return (7 * points[fromPoint]
201 + 32 * points[fromPoint + 1]
202 + 12 * points[fromPoint + 2]
203 + 32 * points[fromPoint + 3]
204 + 7 * points[fromPoint + 4]);
205 }).sum();
206 }
207 }
208 area *= BOOLE_FACTOR;
209 area *= width;
210
211 area += finishIntegration(data, side, lowerNeglected, upperNeglected, IntegrationType.BOOLE);
212 return area;
213 }
214
215
216
217
218
219
220
221
222
223
224 public static double[] generateXPoints(double lb, double ub, int nPoints, boolean halfWidthEnds) {
225 if (lb >= ub) {
226 throw new IllegalArgumentException("ub must be greater than lb");
227 }
228 double[] points = new double[nPoints];
229 int sepDivisor = halfWidthEnds ? nPoints - 2 : nPoints - 1;
230 double sep = (ub - lb) / ((double) sepDivisor);
231
232 if (halfWidthEnds) {
233 points[0] = lb;
234 points[nPoints - 1] = ub;
235 for (int i = 1; i < nPoints - 1; i++) {
236 points[i] = lb + ((double) i) * sep - 0.5 * sep;
237 }
238 } else {
239 for (int i = 0; i < nPoints; i++) {
240 points[i] = lb + ((double) i) * sep;
241 }
242 }
243 return points;
244 }
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261 public static double[] integrateByBins(
262 DataSet data, IntegrationSide side, IntegrationType maxType) {
263 boolean halfWide = data.halfWidthEnds();
264 double[] fX = data.getAllFxPoints();
265 int numPoints = data.numPoints();
266 double width = data.binWidth();
267 double[] values = new double[numPoints];
268
269 int lb = halfWide ? 1 : 0;
270 int ub = halfWide ? numPoints - 2 : numPoints - 1;
271
272 int increment = maxType.pointsNeeded() - 1;
273 increment = Math.max(1, increment);
274
275 switch (side) {
276 case RIGHT -> {
277 values[ub] = width * fX[ub];
278
279
280
281
282 for (int i = ub - 1; i >= lb; i--) {
283 int fromUB = ub - i - 1;
284 fromUB /= increment;
285 fromUB *= increment;
286 int lastBegin = ub - fromUB;
287 double val = finishIntegration(data, side, i, lastBegin, maxType);
288 val -= finishIntegration(data, side, i + 1, lastBegin, maxType);
289 values[i] = val;
290 }
291 values[ub - 1] -= values[ub];
292 if (halfWide) {
293 if (maxType == IntegrationType.RECTANGULAR) {
294 values[1] += 0.5 * width * fX[1];
295 values[numPoints - 1] = (0.5 * width * fX[numPoints - 1]);
296 } else {
297 values[0] = 0.25 * width * fX[0];
298 values[1] += 0.25 * width * fX[1];
299 values[numPoints - 2] += (0.25 * width * fX[numPoints - 2]);
300 values[numPoints - 1] = (0.25 * width * fX[numPoints - 1]);
301 }
302 }
303 }
304 case LEFT -> {
305 int shift = halfWide ? 1 : 0;
306 values[lb] = width * fX[lb];
307
308
309
310
311
312 for (int i = lb + 1; i <= ub; i++) {
313
314 int lastBegin = ((i - 1 - shift) / increment);
315 lastBegin *= increment;
316 lastBegin += shift;
317 double val = finishIntegration(data, side, lastBegin, i, maxType);
318 val -= finishIntegration(data, side, lastBegin, i - 1, maxType);
319 values[i] = val;
320 }
321 values[lb + 1] -= values[lb];
322 if (halfWide) {
323 if (maxType == IntegrationType.RECTANGULAR) {
324 values[0] = 0.5 * width * fX[0];
325 values[numPoints - 2] += (0.5 * width * fX[numPoints - 2]);
326 } else {
327 values[0] = 0.25 * width * fX[0];
328 values[1] += 0.25 * width * fX[1];
329 values[numPoints - 2] += (0.25 * width * fX[numPoints - 2]);
330 values[numPoints - 1] = (0.25 * width * fX[numPoints - 1]);
331 }
332 }
333 }
334 }
335
336 return values;
337 }
338
339
340
341
342
343
344
345
346
347 public static double integrateData(DataSet data, IntegrationSide side, IntegrationType type) {
348 switch (type) {
349 case RECTANGULAR -> {
350 return rectangular(data, side);
351 }
352 case TRAPEZOIDAL -> {
353 return trapezoidal(data, side);
354 }
355 case SIMPSONS -> {
356 return simpsons(data, side);
357 }
358 case BOOLE -> {
359 return booles(data, side);
360 }
361 }
362 logger.warning(
363 format(" Integration type %s not recognized! Defaulting to Simpson's integration", type));
364 return simpsons(data, side);
365 }
366
367
368
369
370
371
372
373
374
375 public static double rectangular(DataSet data, IntegrationSide side) {
376 double area = 0;
377 int lb = 0;
378 int ub = data.numPoints() - 1;
379 if (data.halfWidthEnds()) {
380 area = rectangularEnds(data, side);
381 ++lb;
382 --ub;
383 }
384 area += rectangular(data, side, lb, ub);
385 return area;
386 }
387
388
389
390
391
392
393
394
395
396
397
398 public static double rectangular(DataSet data, IntegrationSide side, int lb, int ub) {
399 double area = 0;
400 double width = data.binWidth();
401 double[] points = data.getAllFxPoints();
402 assert ub > lb;
403 assert ub < points.length;
404
405 switch (side) {
406 case RIGHT -> {
407 for (int i = ub; i > lb; i--) {
408 area += (width * points[i]);
409 }
410 }
411 case LEFT -> {
412 for (int i = lb; i < ub; i++) {
413 area += width * points[i];
414 }
415 }
416 }
417
418 return area;
419 }
420
421
422
423
424
425
426
427
428
429 public static double rectangularEnds(DataSet data, IntegrationSide side) {
430 double width = 0.5 * data.binWidth();
431 double area = 0;
432 int nPoints = data.numPoints();
433 switch (side) {
434 case LEFT -> {
435 area = data.getFxPoint(0) * width;
436 area += (data.getFxPoint(nPoints - 2) * width);
437 }
438 case RIGHT -> {
439 area = data.getFxPoint(1) * width;
440 area += (data.getFxPoint(nPoints - 1) * width);
441 }
442 }
443 return area;
444 }
445
446
447
448
449
450
451
452
453
454
455 public static double rectangularParallel(DataSet data, IntegrationSide side) {
456 double area = 0;
457 int lb = 0;
458 int ub = data.numPoints() - 1;
459 if (data.halfWidthEnds()) {
460 area = rectangularEnds(data, side);
461 ++lb;
462 --ub;
463 }
464 area += rectangularParallel(data, side, lb, ub);
465 return area;
466 }
467
468
469
470
471
472
473
474
475
476
477
478
479 public static double rectangularParallel(DataSet data, IntegrationSide side, int lb, int ub) {
480 double width = data.binWidth();
481 double[] points = data.getAllFxPoints();
482 assert ub > lb;
483 assert ub < points.length;
484
485 if (side == IntegrationSide.RIGHT) {
486 ++ub;
487 ++lb;
488 }
489 return IntStream.range(lb, ub).parallel().mapToDouble(
490 (int i) -> points[i] * width).sum();
491 }
492
493
494
495
496
497
498
499
500 public static double simpsons(DataSet data, IntegrationSide side) {
501 double area = 0;
502 int lb = 0;
503 int ub = data.numPoints() - 1;
504 if (data.halfWidthEnds()) {
505 area = trapezoidalEnds(data, side);
506 lb++;
507 ub--;
508 }
509 area += simpsons(data, side, lb, ub);
510 return area;
511 }
512
513
514
515
516
517
518
519
520
521
522 public static double simpsons(DataSet data, IntegrationSide side, int lb, int ub) {
523 double area = 0;
524 double width = data.binWidth();
525 double[] points = data.getAllFxPoints();
526 int increment = 2;
527
528 int nBins = (ub - lb) / increment;
529 int lowerNeglected = 0;
530 int upperNeglected = 0;
531
532 switch (side) {
533 case RIGHT -> {
534 for (int i = ub; i > (lb + increment - 1); i -= increment) {
535 area += points[i] + (4 * points[i - 1]) + points[i - 2];
536 }
537 lowerNeglected = lb;
538 upperNeglected = ub - (increment * nBins);
539 }
540 case LEFT -> {
541 for (int i = lb; i < (ub - increment + 1); i += increment) {
542 area += points[i] + (4 * points[i + 1]) + points[i + 2];
543 }
544 lowerNeglected = lb + (increment * nBins);
545 upperNeglected = ub;
546 }
547 }
548
549 area *= ONE_THIRD;
550 area *= width;
551
552 area += finishIntegration(data, side, lowerNeglected, upperNeglected, IntegrationType.SIMPSONS);
553 return area;
554 }
555
556
557
558
559
560
561
562
563
564 public static double simpsonsParallel(DataSet data, IntegrationSide side) {
565 double area = 0;
566 int lb = 0;
567 int ub = data.numPoints() - 1;
568 if (data.halfWidthEnds()) {
569 area = trapezoidalEnds(data, side);
570 lb++;
571 ub--;
572 }
573 area += simpsonsParallel(data, side, lb, ub);
574 return area;
575 }
576
577
578
579
580
581
582
583
584
585
586
587 public static double simpsonsParallel(DataSet data, IntegrationSide side, int lb, int ub) {
588 double area = 0;
589 double width = data.binWidth();
590 double[] points = data.getAllFxPoints();
591 int increment = 2;
592 int nBins = (ub - lb) / increment;
593 int lowerNeglected = 0;
594 int upperNeglected = 0;
595
596 switch (side) {
597 case RIGHT -> {
598 lowerNeglected = lb;
599 upperNeglected = ub - (increment * nBins);
600 area = IntStream.range(0, nBins).parallel().mapToDouble(
601 (int i) -> {
602 int fromPoint = ub - (increment * i);
603 return (points[fromPoint - 2] + 4 * points[fromPoint - 1] + points[fromPoint]);
604 }).sum();
605 }
606 case LEFT -> {
607 area = IntStream.range(0, nBins).parallel().mapToDouble(
608 (int i) -> {
609 int fromPoint = lb + (increment * i);
610 return (points[fromPoint] + 4 * points[fromPoint + 1] + points[fromPoint + 2]);
611 }).sum();
612 lowerNeglected = lb + (increment * nBins);
613 upperNeglected = ub;
614 }
615 }
616
617 area *= ONE_THIRD;
618 area *= width;
619 area += finishIntegration(data, side, lowerNeglected, upperNeglected, IntegrationType.SIMPSONS);
620 return area;
621 }
622
623
624
625
626
627
628
629
630
631 public static double trapezoidal(DataSet data, IntegrationSide side) {
632 double area = 0;
633 int lb = 0;
634 int ub = data.numPoints() - 1;
635 if (data.halfWidthEnds()) {
636 area = trapezoidalEnds(data, side);
637 lb++;
638 ub--;
639 }
640 area += trapezoidal(data, side, lb, ub);
641 return area;
642 }
643
644
645
646
647
648
649
650
651
652
653
654 public static double trapezoidal(DataSet data, IntegrationSide side, int lb, int ub) {
655 double width = data.binWidth();
656 double[] points = data.getAllFxPoints();
657
658 double area = 0.5 * points[lb];
659 area += 0.5 * points[ub];
660 for (int i = lb + 1; i < ub; i++) {
661 area += points[i];
662 }
663 area *= width;
664 return area;
665 }
666
667
668
669
670
671
672
673
674 public static double trapezoidalEnds(DataSet data, IntegrationSide side) {
675 double width = 0.5 * data.binWidth();
676 int nPts = data.numPoints();
677 double area = data.getFxPoint(0) + data.getFxPoint(1)
678 + data.getFxPoint(nPts - 2) + data.getFxPoint(nPts - 1);
679 area *= (0.5 * width);
680 return area;
681 }
682
683
684
685
686
687
688
689
690
691
692 public static double trapezoidalParallel(DataSet data, IntegrationSide side) {
693 double area = 0;
694 int lb = 0;
695 int ub = data.numPoints() - 1;
696 if (data.halfWidthEnds()) {
697 area = trapezoidalEnds(data, side);
698 lb++;
699 ub--;
700 }
701 area += trapezoidalParallel(data, side, lb, ub);
702 return area;
703 }
704
705
706
707
708
709
710
711
712
713
714
715
716 public static double trapezoidalParallel(DataSet data, IntegrationSide side, int lb, int ub) {
717 double width = data.binWidth();
718 double[] points = data.getAllFxPoints();
719
720 double area = 0.5 * points[lb];
721 area += 0.5 * points[ub];
722 area += IntStream.range(lb + 1, ub).parallel().mapToDouble(
723 (int i) -> points[i]).sum();
724 area *= width;
725 return area;
726 }
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742 private static double finishIntegration(
743 DataSet data, IntegrationSide side, int lb, int ub, IntegrationType type) {
744 int totPoints = (ub - lb);
745
746 int perBin = type.pointsNeeded();
747 int increment = perBin - 1;
748 increment = Math.max(1, increment);
749
750 int nBins = totPoints / increment;
751 int remainder = totPoints % increment;
752
753 IntegrateWindow intMode = switch (type) {
754 case BOOLE -> Integrate1DNumeric::booles;
755 case SIMPSONS -> Integrate1DNumeric::simpsons;
756 case RECTANGULAR -> Integrate1DNumeric::rectangular;
757 case TRAPEZOIDAL -> Integrate1DNumeric::trapezoidal;
758 };
759
760 double area = 0.0;
761
762 switch (side) {
763 case RIGHT -> {
764 for (int i = ub; i > (lb - 1 + increment); i -= increment) {
765 area += intMode.toArea(data, side, (i - increment), i);
766 }
767 ub -= (nBins * increment);
768 }
769 case LEFT -> {
770 for (int i = lb; i < (ub + 1 - increment); i += increment) {
771 area += intMode.toArea(data, side, i, (i + increment));
772 }
773 lb += (nBins * increment);
774 }
775 }
776
777 assert remainder == (ub - lb);
778 switch (remainder) {
779 case 0:
780 break;
781 case 1:
782 area += trapezoidal(data, side, lb, ub);
783 break;
784 case 2:
785 case 3:
786 area += simpsons(data, side, lb, ub);
787 break;
788 case 4:
789 default:
790 throw new IllegalArgumentException("This should not be currently possible.");
791 }
792 return area;
793 }
794
795
796
797
798
799
800
801
802
803
804
805
806 public enum IntegrationSide {
807
808
809
810 LEFT,
811
812
813
814 RIGHT
815 }
816
817
818
819
820 public enum IntegrationType {
821
822
823
824 RECTANGULAR(1),
825
826
827
828 TRAPEZOIDAL(2),
829
830
831
832 SIMPSONS(3),
833
834
835
836 BOOLE(5);
837
838 private final int pointsNeeded;
839
840
841
842
843
844
845 IntegrationType(int points) {
846 pointsNeeded = points;
847 }
848
849
850
851
852
853
854 public final int pointsNeeded() {
855 return pointsNeeded;
856 }
857 }
858
859
860
861
862 @FunctionalInterface
863 private interface IntegrateWindow {
864
865
866
867
868
869
870
871
872
873
874 double toArea(DataSet data, IntegrationSide side, int lb, int ub);
875 }
876 }