1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.rng.examples.jmh.sampling.distribution;
19
20 import org.apache.commons.rng.UniformRandomProvider;
21 import org.apache.commons.rng.sampling.distribution.ContinuousSampler;
22 import org.apache.commons.rng.sampling.distribution.StableSampler;
23 import org.apache.commons.rng.sampling.distribution.ZigguratSampler;
24 import org.apache.commons.rng.simple.RandomSource;
25 import org.openjdk.jmh.annotations.Benchmark;
26 import org.openjdk.jmh.annotations.BenchmarkMode;
27 import org.openjdk.jmh.annotations.Fork;
28 import org.openjdk.jmh.annotations.Measurement;
29 import org.openjdk.jmh.annotations.Mode;
30 import org.openjdk.jmh.annotations.OutputTimeUnit;
31 import org.openjdk.jmh.annotations.Param;
32 import org.openjdk.jmh.annotations.Scope;
33 import org.openjdk.jmh.annotations.Setup;
34 import org.openjdk.jmh.annotations.State;
35 import org.openjdk.jmh.annotations.Warmup;
36
37 import java.util.concurrent.TimeUnit;
38
39
40
41
42
43
44
45
46
47 @BenchmarkMode(Mode.AverageTime)
48 @OutputTimeUnit(TimeUnit.NANOSECONDS)
49 @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
50 @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
51 @State(Scope.Benchmark)
52 @Fork(value = 1, jvmArgs = {"-server", "-Xms128M", "-Xmx128M"})
53 public class StableSamplerPerformance {
54
55 static final String BASELINE = "baseline";
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 abstract static class StableRandomGenerator implements ContinuousSampler {
93
94 static final double LOWER = Double.NEGATIVE_INFINITY;
95
96 static final double UPPER = Double.POSITIVE_INFINITY;
97
98 static final double PI_2 = Math.PI / 2;
99
100 static final double PI_4 = Math.PI / 4;
101
102 static final double PI_2_SCALED = 0x1.0p-54 * Math.PI;
103
104 static final double PI_4_SCALED = 0x1.0p-55 * Math.PI;
105
106 private final UniformRandomProvider rng;
107
108 private final ContinuousSampler expSampler;
109
110
111
112
113
114 StableRandomGenerator(final UniformRandomProvider rng) {
115 this.rng = rng;
116 expSampler = ZigguratSampler.Exponential.of(rng);
117 }
118
119
120
121
122
123
124
125 double getOmega() {
126 return expSampler.sample();
127 }
128
129
130
131
132
133
134
135 double getPhi() {
136 final double x = (rng.nextLong() >> 10) * PI_2_SCALED;
137 if (x == -PI_2) {
138 return getPhi();
139 }
140 return x;
141 }
142
143
144
145
146
147
148
149 double getPhiBy2() {
150 final double x = (rng.nextLong() >> 10) * PI_4_SCALED;
151 if (x == -PI_4) {
152 return getPhiBy2();
153 }
154 return x;
155 }
156
157
158
159
160
161
162
163
164
165
166
167
168 static ContinuousSampler of(UniformRandomProvider rng,
169 double alpha,
170 double beta,
171 boolean weron) {
172
173
174
175
176 if (weron) {
177 if (beta == 0.0) {
178 return new Beta0WeronStableRandomGenerator(rng, alpha);
179 }
180 if (alpha == 1.0) {
181 return new Alpha1WeronStableRandomGenerator(rng, alpha);
182 }
183 return new WeronStableRandomGenerator(rng, alpha, beta);
184 }
185
186 if (beta == 0.0) {
187 return new Beta0CMSStableRandomGenerator(rng, alpha);
188 }
189 if (alpha == 1.0) {
190 return new Alpha1CMSStableRandomGenerator(rng, alpha);
191 }
192 return new CMSStableRandomGenerator(rng, alpha, beta);
193 }
194
195
196
197
198
199
200
201
202
203 static double clipToSupport(double x, double lower, double upper) {
204 if (x < lower) {
205 return lower;
206 }
207 return x < upper ? x : upper;
208 }
209 }
210
211
212
213
214
215 private static class BaselineStableRandomGenerator extends StableRandomGenerator {
216
217
218
219 BaselineStableRandomGenerator(UniformRandomProvider rng) {
220 super(rng);
221 }
222
223 @Override
224 public double sample() {
225 return getOmega() * getPhi();
226 }
227 }
228
229
230
231
232
233
234
235 private static class WeronStableRandomGenerator extends StableRandomGenerator {
236
237 protected final double lower;
238
239 protected final double upper;
240
241 protected final double eps;
242
243 protected final double meps1;
244
245 protected final double zeta;
246
247 protected final double atanZeta;
248
249 protected final double scale;
250
251 protected final double inv1mEps;
252
253 protected final double epsDiv1mEps;
254
255
256
257
258
259
260 WeronStableRandomGenerator(UniformRandomProvider rng, double alpha, double beta) {
261 super(rng);
262
263 eps = 1 - alpha;
264
265
266 meps1 = 1 - eps;
267
268
269 if (meps1 > 1) {
270
271 zeta = beta * Math.tan((2 - meps1) * PI_2);
272 } else {
273 zeta = -beta * Math.tan(meps1 * PI_2);
274 }
275
276
277
278 atanZeta = Math.atan(-zeta);
279 scale = Math.pow(1 + zeta * zeta, 0.5 / meps1);
280 inv1mEps = 1.0 / meps1;
281 epsDiv1mEps = inv1mEps - 1;
282
283
284 if (alpha < 1 && Math.abs(beta) == 1) {
285 if (beta == 1) {
286
287 lower = zeta;
288 upper = UPPER;
289 } else {
290
291 lower = LOWER;
292 upper = zeta;
293 }
294 } else {
295 lower = LOWER;
296 upper = UPPER;
297 }
298 }
299
300 @Override
301 public double sample() {
302 final double w = getOmega();
303 final double phi = getPhi();
304
305
306
307
308
309 double t1 = Math.sin(meps1 * phi + atanZeta) / Math.pow(Math.cos(phi), inv1mEps);
310 double t2 = Math.pow(Math.cos(eps * phi - atanZeta) / w, epsDiv1mEps);
311
312
313
314 double unbox1 = 1;
315 double unbox2 = 1;
316 if (Double.isInfinite(t1)) {
317 t1 = Math.copySign(Double.MAX_VALUE, t1);
318 unbox1 = unbox2 = Double.MAX_VALUE;
319 }
320 if (Double.isInfinite(t2)) {
321 t2 = Math.copySign(Double.MAX_VALUE, t2);
322 unbox1 = unbox2 = Double.MAX_VALUE;
323 }
324
325 return clipToSupport(t1 * t2 * unbox1 * unbox2 * scale + zeta, lower, upper);
326 }
327 }
328
329
330
331
332
333
334
335 private static class Alpha1WeronStableRandomGenerator extends StableRandomGenerator {
336
337 private final double beta;
338
339
340
341
342
343 Alpha1WeronStableRandomGenerator(UniformRandomProvider rng, double beta) {
344 super(rng);
345 this.beta = beta;
346 }
347
348 @Override
349 public double sample() {
350 final double w = getOmega();
351 final double phi = getPhi();
352
353
354
355 final double betaPhi = PI_2 + beta * phi;
356 final double x = (betaPhi * Math.tan(phi) -
357 beta * Math.log(PI_2 * w * Math.cos(phi) / betaPhi)) / PI_2;
358
359 return x;
360 }
361 }
362
363
364
365
366
367
368 private static class Beta0WeronStableRandomGenerator extends StableRandomGenerator {
369
370 protected final double eps;
371
372 protected final double meps1;
373
374 protected final double inv1mEps;
375
376 protected final double epsDiv1mEps;
377
378
379
380
381
382 Beta0WeronStableRandomGenerator(UniformRandomProvider rng, double alpha) {
383 super(rng);
384 eps = 1 - alpha;
385 meps1 = 1 - eps;
386 inv1mEps = 1.0 / meps1;
387 epsDiv1mEps = inv1mEps - 1;
388 }
389
390 @Override
391 public double sample() {
392 final double w = getOmega();
393 final double phi = getPhi();
394
395 double t1 = Math.sin(meps1 * phi) / Math.pow(Math.cos(phi), inv1mEps);
396 double t2 = Math.pow(Math.cos(eps * phi) / w, epsDiv1mEps);
397
398 double unbox1 = 1;
399 double unbox2 = 1;
400 if (Double.isInfinite(t1)) {
401 t1 = Math.copySign(Double.MAX_VALUE, t1);
402 unbox1 = unbox2 = Double.MAX_VALUE;
403 }
404 if (Double.isInfinite(t2)) {
405 t2 = Math.copySign(Double.MAX_VALUE, t2);
406 unbox1 = unbox2 = Double.MAX_VALUE;
407 }
408
409 return t1 * t2 * unbox1 * unbox2;
410 }
411 }
412
413
414
415
416
417
418
419 static class CMSStableRandomGenerator extends WeronStableRandomGenerator {
420
421 private static final double HALF = 0.5;
422
423 private final double tau;
424
425
426
427
428
429
430 CMSStableRandomGenerator(UniformRandomProvider rng, double alpha, double beta) {
431 super(rng, alpha, beta);
432
433
434
435
436
437
438
439
440
441 if (eps == 0) {
442
443 tau = beta / PI_2;
444 } else if (Math.abs(eps) < HALF) {
445
446 tau = eps * beta / Math.tan(eps * PI_2);
447 } else {
448
449
450
451
452 if (meps1 > 1) {
453 tau = eps * beta * -Math.tan((2 - meps1) * PI_2);
454 } else {
455 tau = eps * beta * Math.tan(meps1 * PI_2);
456 }
457 }
458
459 }
460
461 @Override
462 public double sample() {
463 final double phiby2 = getPhiBy2();
464 final double w = getOmega();
465
466
467
468
469
470
471
472
473
474
475
476
477
478 final double a = Math.tan(phiby2);
479 final double b = Math.tan(eps * phiby2);
480 final double bb = b == 0 ? 1 : b / (eps * phiby2);
481
482
483 final double da = a * a;
484 final double db = b * b;
485 final double a2 = 1 - da;
486 final double a2p = 1 + da;
487 final double b2 = 1 - db;
488 final double b2p = 1 + db;
489
490
491 final double numerator = b2 + 2 * phiby2 * bb * tau;
492 final double z = a2p * numerator / (w * a2 * b2p);
493
494
495 final double alogz = Math.log(z);
496 final double d = D2Source.d2(epsDiv1mEps * alogz) * (alogz * inv1mEps);
497
498
499 final double f = (2 * ((a - b) * (1 + a * b) - phiby2 * tau * bb * (b * a2 - 2 * a))) /
500 (a2 * b2p);
501
502
503 final double x = (1 + eps * d) * f + tau * d;
504
505
506 if (lower < x && x < upper) {
507 return x;
508 }
509
510
511
512 return 0;
513 }
514 }
515
516
517
518
519
520
521
522
523
524
525 static class Alpha1CMSStableRandomGenerator extends StableRandomGenerator {
526
527 private final double tau;
528
529
530
531
532
533 Alpha1CMSStableRandomGenerator(UniformRandomProvider rng, double beta) {
534 super(rng);
535 tau = beta / PI_2;
536 }
537
538 @Override
539 public double sample() {
540 final double phiby2 = getPhiBy2();
541 final double w = getOmega();
542
543
544 final double a = Math.tan(phiby2);
545
546
547 final double da = a * a;
548 final double a2 = 1 - da;
549 final double a2p = 1 + da;
550
551
552 final double z = a2p * (1 + 2 * phiby2 * tau) / (w * a2);
553
554
555 final double d = Math.log(z);
556
557
558 final double f = (2 * (a - phiby2 * tau * (-2 * a))) / a2;
559
560
561 return f + tau * d;
562 }
563 }
564
565
566
567
568
569
570
571
572
573
574 static class Beta0CMSStableRandomGenerator extends Beta0WeronStableRandomGenerator {
575
576
577
578
579 Beta0CMSStableRandomGenerator(UniformRandomProvider rng, double alpha) {
580 super(rng, alpha);
581 }
582
583 @Override
584 public double sample() {
585 final double phiby2 = getPhiBy2();
586 final double w = getOmega();
587
588
589 final double a = Math.tan(phiby2);
590 final double b = Math.tan(eps * phiby2);
591
592 final double da = a * a;
593 final double db = b * b;
594 final double a2 = 1 - da;
595 final double a2p = 1 + da;
596 final double b2 = 1 - db;
597 final double b2p = 1 + db;
598
599 final double z = a2p * b2 / (w * a2 * b2p);
600
601
602 final double alogz = Math.log(z);
603 final double d = D2Source.d2(epsDiv1mEps * alogz) * (alogz * inv1mEps);
604
605
606 final double f = (2 * ((a - b) * (1 + a * b))) / (a2 * b2p);
607
608
609 final double x = (1 + eps * d) * f;
610
611
612 if (LOWER < x && x < UPPER) {
613 return x;
614 }
615
616
617
618 return 0;
619 }
620 }
621
622
623
624
625 public abstract static class SamplerSource {
626
627
628
629
630
631
632
633
634 @Param({"XO_RO_SHI_RO_128_PP",
635 "MWC_256",
636 "JDK"})
637 private String randomSourceName;
638
639
640
641
642
643
644 public UniformRandomProvider getRNG() {
645 return RandomSource.valueOf(randomSourceName).create();
646 }
647
648
649
650
651 public abstract ContinuousSampler getSampler();
652 }
653
654
655
656
657 @State(Scope.Benchmark)
658 public static class UniformRandomSource extends SamplerSource {
659
660 private static final double NEG_PI_4 = -Math.PI / 4;
661
662 private static final double PI_4_SCALED = 0x1.0p-55 * Math.PI;
663
664
665 @Param({BASELINE, "nextDoubleNot0", "nextDoubleNot0Recurse",
666 "nextLongNot0", "nextDoubleShifted", "nextLongShifted",
667 "signedShift", "signedShiftPi4", "signedShiftPi4b"})
668 private String method;
669
670
671 private ContinuousSampler sampler;
672
673
674 @Override
675 public ContinuousSampler getSampler() {
676 return sampler;
677 }
678
679
680 @Setup
681 public void setup() {
682 final UniformRandomProvider rng = getRNG();
683 if (BASELINE.equals(method)) {
684 sampler = rng::nextDouble;
685 } else if ("nextDoubleNot0".equals(method)) {
686 sampler = () -> {
687
688 double x;
689 do {
690 x = rng.nextDouble();
691 } while (x == 0);
692 return x - 0.5;
693 };
694 } else if ("nextDoubleNot0Recurse".equals(method)) {
695 sampler = new ContinuousSampler() {
696 @Override
697 public double sample() {
698
699
700
701 final double x = rng.nextDouble();
702 if (x == 0) {
703 return sample();
704 }
705 return x - 0.5;
706 }
707 };
708 } else if ("nextLongNot0".equals(method)) {
709 sampler = () -> {
710
711 long x;
712 do {
713 x = rng.nextLong() >>> 11;
714 } while (x == 0);
715 return x * 0x1.0p-53 - 0.5;
716 };
717 } else if ("nextDoubleShifted".equals(method)) {
718 sampler = () -> {
719
720
721 return 0x1.0p-53 + (rng.nextLong() >>> 12) * 0x1.0p-52 - 0.5;
722 };
723 } else if ("nextLongShifted".equals(method)) {
724 sampler = () -> {
725
726
727 return ((rng.nextLong() >>> 11) | 0x1L) * 0x1.0p-53 - 0.5;
728 };
729 } else if ("signedShift".equals(method)) {
730 sampler = new ContinuousSampler() {
731 @Override
732 public double sample() {
733
734
735
736 final double x = (rng.nextLong() >> 10) * 0x1.0p-54;
737 if (x == -0.5) {
738
739 return sample();
740 }
741 return x;
742 }
743 };
744 } else if ("signedShiftPi4".equals(method)) {
745
746
747 sampler = new ContinuousSampler() {
748 @Override
749 public double sample() {
750
751
752
753 final double x = (rng.nextLong() >> 10) * PI_4_SCALED;
754 if (x == NEG_PI_4) {
755
756 return sample();
757 }
758 return x;
759 }
760 };
761 } else if ("signedShiftPi4b".equals(method)) {
762
763
764 sampler = new ContinuousSampler() {
765 @Override
766 public double sample() {
767 final long x = rng.nextLong();
768 if (x == Long.MIN_VALUE) {
769
770 return sample();
771 }
772
773
774
775 return (x >> 10) * PI_4_SCALED;
776 }
777 };
778 } else {
779 throw new IllegalStateException("Unknown method: " + method);
780 }
781 }
782 }
783
784
785
786
787
788
789
790
791 @State(Scope.Benchmark)
792 public static class TanSource extends SamplerSource {
793
794 private static final double PI_2 = Math.PI / 2;
795
796 private static final double PI_4 = Math.PI / 4;
797
798 private static final double FOUR_PI = 4 / Math.PI;
799
800
801 @Param({BASELINE, "tan", "tan4283", "tan4288", "tan4288b", "tan4288c"})
802 private String method;
803
804
805 private ContinuousSampler sampler;
806
807
808 @Override
809 public ContinuousSampler getSampler() {
810 return sampler;
811 }
812
813
814 @Setup
815 public void setup() {
816 final UniformRandomProvider rng = getRNG();
817 if (BASELINE.equals(method)) {
818 sampler = () -> {
819
820 return PI_2 * (rng.nextDouble() - 0.5);
821 };
822 } else if ("tan".equals(method)) {
823 sampler = () -> {
824 final double x = PI_2 * (rng.nextDouble() - 0.5);
825
826 return x == 0 ? 1.0 : Math.tan(x) / x;
827 };
828 } else if ("tan4283".equals(method)) {
829 sampler = () -> {
830 final double x = PI_2 * (rng.nextDouble() - 0.5);
831 return x * tan4283(x);
832 };
833 } else if ("tan4288".equals(method)) {
834 sampler = () -> {
835 final double x = PI_2 * (rng.nextDouble() - 0.5);
836 return x * tan4288(x);
837 };
838 } else if ("tan4288b".equals(method)) {
839 sampler = () -> {
840 final double x = PI_2 * (rng.nextDouble() - 0.5);
841 return x * tan4288b(x);
842 };
843 } else if ("tan4288c".equals(method)) {
844 sampler = () -> {
845 final double x = PI_2 * (rng.nextDouble() - 0.5);
846 return x * tan4288c(x);
847 };
848 } else {
849 throw new IllegalStateException("Unknown tan method: " + method);
850 }
851 }
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879 static double tan4283(double x) {
880 double xa = Math.abs(x);
881 if (xa > PI_4) {
882 return Math.tan(x) / x;
883 }
884
885
886
887 final double p0 = 0.129221035031569917e3;
888 final double p1 = -0.8876623770211723e1;
889 final double p2 = 0.52864445522248e-1;
890 final double q0 = 0.164529331810168605e3;
891 final double q1 = -0.45132056100598961e2;
892
893
894 xa = xa / PI_4;
895 final double xx = xa * xa;
896
897
898 return (p0 + xx * (p1 + xx * p2)) / (PI_4 * (q0 + xx * (q1 + xx )));
899 }
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917 static double tan4288(double x) {
918 double xa = Math.abs(x);
919 if (xa > PI_4) {
920 return Math.tan(x) / x;
921 }
922
923
924
925
926 final double p0 = -0.5712939549476836914932149599e+10;
927 final double p1 = +0.4946855977542506692946040594e+9;
928 final double p2 = -0.9429037070546336747758930844e+7;
929 final double p3 = +0.5282725819868891894772108334e+5;
930 final double p4 = -0.6983913274721550913090621370e+2;
931
932 final double q0 = -0.7273940551075393257142652672e+10;
933 final double q1 = +0.2125497341858248436051062591e+10;
934 final double q2 = -0.8000791217568674135274814656e+8;
935 final double q3 = +0.8232855955751828560307269007e+6;
936 final double q4 = -0.2396576810261093558391373322e+4;
937
938
939 xa = xa * FOUR_PI;
940 final double xx = xa * xa;
941
942
943 return (p0 + xx * (p1 + xx * (p2 + xx * (p3 + xx * p4)))) /
944 (PI_4 * (q0 + xx * (q1 + xx * (q2 + xx * (q3 + xx * (q4 + xx ))))));
945 }
946
947
948
949
950
951
952
953
954
955
956
957
958
959 static double tan4288b(double x) {
960 double xa = Math.abs(x);
961 if (xa > PI_4) {
962 return Math.tan(x) / x;
963 }
964
965 final double p0 = -0.5712939549476836914932149599e+10;
966 final double p1 = +0.4946855977542506692946040594e+9;
967 final double p2 = -0.9429037070546336747758930844e+7;
968 final double p3 = +0.5282725819868891894772108334e+5;
969 final double p4 = -0.6983913274721550913090621370e+2;
970
971 final double q0 = -0.7273940551075393257142652672e+10;
972 final double q1 = +0.2125497341858248436051062591e+10;
973 final double q2 = -0.8000791217568674135274814656e+8;
974 final double q3 = +0.8232855955751828560307269007e+6;
975 final double q4 = -0.2396576810261093558391373322e+4;
976
977
978 xa = xa * FOUR_PI;
979
980
981
982
983 final double x2 = xa * xa;
984 final double x4 = x2 * x2;
985 final double x6 = x4 * x2;
986 final double x8 = x4 * x4;
987
988 return (p0 + x2 * p1 + x4 * p2 + x6 * p3 + x8 * p4) /
989 (PI_4 * (q0 + x2 * q1 + x4 * q2 + x6 * q3 + x8 * q4 + x8 * x2));
990 }
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004 static double tan4288c(double x) {
1005 if (Math.abs(x) > PI_4) {
1006 return Math.tan(x) / x;
1007 }
1008
1009 final double p0 = -0.5712939549476836914932149599e+10;
1010 final double p1 = +0.4946855977542506692946040594e+9;
1011 final double p2 = -0.9429037070546336747758930844e+7;
1012 final double p3 = +0.5282725819868891894772108334e+5;
1013 final double p4 = -0.6983913274721550913090621370e+2;
1014
1015 final double q0 = -0.7273940551075393257142652672e+10;
1016 final double q1 = +0.2125497341858248436051062591e+10;
1017 final double q2 = -0.8000791217568674135274814656e+8;
1018 final double q3 = +0.8232855955751828560307269007e+6;
1019 final double q4 = -0.2396576810261093558391373322e+4;
1020
1021
1022 final double xi = x * FOUR_PI;
1023
1024
1025
1026
1027 final double x2 = xi * xi;
1028 final double x4 = x2 * x2;
1029 final double x6 = x4 * x2;
1030 final double x8 = x4 * x4;
1031
1032
1033 return (x8 * p4 + x6 * p3 + x4 * p2 + x2 * p1 + p0) /
1034 (PI_4 * (x8 * x2 + x8 * q4 + x6 * q3 + x4 * q2 + x2 * q1 + q0));
1035 }
1036 }
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048 @State(Scope.Benchmark)
1049 public static class D2Source extends SamplerSource {
1050
1051 @Param({BASELINE, "expm1", "expm1b", "exp", "hybrid"})
1052 private String method;
1053
1054 @Param({"0.12345", "1.2345", "12.345", "123.45"})
1055 private double scale;
1056
1057
1058 private ContinuousSampler sampler;
1059
1060
1061 @Override
1062 public ContinuousSampler getSampler() {
1063 return sampler;
1064 }
1065
1066
1067 @Setup
1068 public void setup() {
1069 final double s = scale;
1070 final UniformRandomProvider rng = getRNG();
1071 if (BASELINE.equals(method)) {
1072 sampler = () -> s * (rng.nextDouble() - 0.5);
1073 } else if ("expm1".equals(method)) {
1074 sampler = () -> expm1(s * (rng.nextDouble() - 0.5));
1075 } else if ("expm1b".equals(method)) {
1076 sampler = () -> expm1b(s * (rng.nextDouble() - 0.5));
1077 } else if ("exp".equals(method)) {
1078 sampler = () -> exp(s * (rng.nextDouble() - 0.5));
1079 } else if ("hybrid".equals(method)) {
1080 sampler = () -> hybrid(s * (rng.nextDouble() - 0.5));
1081 } else {
1082 throw new IllegalStateException("Unknown d2 method: " + method);
1083 }
1084 }
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102 static double d2(double x) {
1103 return hybrid(x);
1104 }
1105
1106
1107
1108
1109
1110
1111
1112 static double expm1(double x) {
1113
1114 final double d2 = Math.expm1(x) / x;
1115 if (Double.isNaN(d2)) {
1116
1117 if (x == 0) {
1118 return 1.0;
1119 }
1120
1121 return x;
1122 }
1123 return d2;
1124 }
1125
1126
1127
1128
1129
1130
1131
1132 static double expm1b(double x) {
1133
1134 if (x == 0) {
1135 return 1;
1136 }
1137 if (x == Double.POSITIVE_INFINITY) {
1138 return x;
1139 }
1140 return Math.expm1(x) / x;
1141 }
1142
1143
1144
1145
1146
1147
1148
1149 static double exp(double x) {
1150
1151
1152 final double d2 = (Math.exp(x) - 1) / x;
1153 if (Double.isNaN(d2)) {
1154
1155 if (x == 0) {
1156 return 1.0;
1157 }
1158
1159 return x;
1160 }
1161 return d2;
1162 }
1163
1164
1165
1166
1167
1168
1169
1170 static double hybrid(double x) {
1171
1172
1173 if (Math.abs(x) < 0.5) {
1174 if (x == 0) {
1175 return 1;
1176 }
1177 return Math.expm1(x) / x;
1178 }
1179
1180
1181 if (x == Double.POSITIVE_INFINITY) {
1182 return x;
1183 }
1184 return (Math.exp(x) - 1) / x;
1185 }
1186 }
1187
1188
1189
1190
1191 @State(Scope.Benchmark)
1192 public static class BaselineSamplerSource extends SamplerSource {
1193
1194 private ContinuousSampler sampler;
1195
1196
1197 @Override
1198 public ContinuousSampler getSampler() {
1199 return sampler;
1200 }
1201
1202
1203 @Setup
1204 public void setup() {
1205 sampler = new BaselineStableRandomGenerator(getRNG());
1206 }
1207 }
1208
1209
1210
1211
1212
1213 public abstract static class StableSamplerSource extends SamplerSource {
1214
1215 @Param({"CMS", "CMS_weron", "CMS_tan"})
1216 private String samplerType;
1217
1218
1219 private ContinuousSampler sampler;
1220
1221
1222
1223
1224
1225
1226 abstract double getAlpha();
1227
1228
1229
1230
1231
1232
1233 abstract double getBeta();
1234
1235
1236 @Override
1237 public ContinuousSampler getSampler() {
1238 return sampler;
1239 }
1240
1241
1242 @Setup
1243 public void setup() {
1244 final UniformRandomProvider rng = getRNG();
1245 if ("CMS".equals(samplerType)) {
1246 sampler = StableSampler.of(rng, getAlpha(), getBeta());
1247 } else if ("CMS_weron".equals(samplerType)) {
1248 sampler = StableRandomGenerator.of(rng, getAlpha(), getBeta(), true);
1249 } else if ("CMS_tan".equals(samplerType)) {
1250 sampler = StableRandomGenerator.of(rng, getAlpha(), getBeta(), false);
1251 } else {
1252 throw new IllegalStateException("Unknown sampler: " + samplerType);
1253 }
1254 }
1255 }
1256
1257
1258
1259
1260 @State(Scope.Benchmark)
1261 public static class Alpha1StableSamplerSource extends StableSamplerSource {
1262
1263 @Param({"0.1", "0.4", "0.7"})
1264 private double beta;
1265
1266
1267 @Override
1268 double getAlpha() {
1269 return 1;
1270 }
1271
1272
1273 @Override
1274 double getBeta() {
1275 return beta;
1276 }
1277 }
1278
1279
1280
1281
1282 @State(Scope.Benchmark)
1283 public static class Beta0StableSamplerSource extends StableSamplerSource {
1284
1285 @Param({
1286
1287
1288
1289
1290 "0.3", "0.5", "0.7", "0.9", "1.1", "1.3", "1.5", "1.7"
1291 })
1292 private double alpha;
1293
1294
1295 @Override
1296 double getAlpha() {
1297 return alpha;
1298 }
1299
1300
1301 @Override
1302 double getBeta() {
1303 return 0;
1304 }
1305 }
1306
1307
1308
1309
1310 @State(Scope.Benchmark)
1311 public static class GeneralStableSamplerSource extends Beta0StableSamplerSource {
1312
1313 @Param({"0.1", "0.4", "0.7"})
1314 private double beta;
1315
1316
1317 @Override
1318 double getBeta() {
1319 return beta;
1320 }
1321 }
1322
1323
1324
1325
1326
1327
1328
1329
1330 @Benchmark
1331 public double nextUniformDeviate(UniformRandomSource source) {
1332 return source.getSampler().sample();
1333 }
1334
1335
1336
1337
1338
1339
1340
1341
1342 @Benchmark
1343 public double nextTan(TanSource source) {
1344 return source.getSampler().sample();
1345 }
1346
1347
1348
1349
1350
1351
1352
1353
1354 @Benchmark
1355 public double nextD2(D2Source source) {
1356 return source.getSampler().sample();
1357 }
1358
1359
1360
1361
1362
1363
1364
1365 @Benchmark
1366 public double sampleBaseline(BaselineSamplerSource source) {
1367 return source.getSampler().sample();
1368 }
1369
1370
1371
1372
1373
1374
1375
1376 @Benchmark
1377 public double sampleAlpha1(Alpha1StableSamplerSource source) {
1378 return source.getSampler().sample();
1379 }
1380
1381
1382
1383
1384
1385
1386
1387 @Benchmark
1388 public double sampleBeta0(Beta0StableSamplerSource source) {
1389 return source.getSampler().sample();
1390 }
1391
1392
1393
1394
1395
1396
1397
1398 @Benchmark
1399 public double sample(GeneralStableSamplerSource source) {
1400 return source.getSampler().sample();
1401 }
1402 }