1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.rng.sampling.distribution;
18
19 import java.util.function.Supplier;
20 import org.apache.commons.rng.UniformRandomProvider;
21 import org.apache.commons.rng.core.source64.SplitMix64;
22 import org.apache.commons.rng.sampling.RandomAssert;
23 import org.apache.commons.rng.sampling.distribution.StableSampler.Beta0CMSStableSampler;
24 import org.apache.commons.rng.sampling.distribution.StableSampler.Beta0WeronStableSampler;
25 import org.apache.commons.rng.sampling.distribution.StableSampler.CMSStableSampler;
26 import org.apache.commons.rng.sampling.distribution.StableSampler.SpecialMath;
27 import org.apache.commons.rng.sampling.distribution.StableSampler.WeronStableSampler;
28 import org.apache.commons.rng.simple.RandomSource;
29 import org.junit.jupiter.api.Assertions;
30 import org.junit.jupiter.api.Test;
31
32 /**
33 * Tests for the class {@link StableSampler}.
34 *
35 * <p>Note: Samples from the stable distribution are tested in
36 * {@link ContinuousSamplerParametricTest}.
37 *
38 * <p>This contains tests for the assumptions made by the {@link StableSampler} implementation
39 * of the Chambers-Mallows-Stuck (CMS) method as described in
40 * Chambers, Mallows & Stuck (1976) "A Method for Simulating Stable Random Variables".
41 * Journal of the American Statistical Association. 71 (354): 340–344.
42 *
43 * <p>The test class contains copy implementations of the routines in the {@link StableSampler}
44 * to test the algorithms with various parameters. This avoids excess manipulation
45 * of the RNG provided to the stable sampler to test edge cases and also allows
46 * calling the algorithm with values that are eliminated by the sampler (e.g. u=0).
47 *
48 * <p>Some tests of the sampler are performed that manipulate the underlying RNG to create
49 * extreme values for the random deviates. This hits edges cases where the computation has
50 * to be corrected.
51 */
52 class StableSamplerTest {
53 /** pi / 2. */
54 private static final double PI_2 = Math.PI / 2;
55 /** pi / 4. */
56 private static final double PI_4 = Math.PI / 4;
57 /** pi/4 scaled by 2^-53. */
58 private static final double PI_4_SCALED = 0x1.0p-55 * Math.PI;
59 /** The interval between successive values of a uniform variate u.
60 * This is the gap between the 2^53 dyadic rationals in [0, 1). */
61 private static final double DU = 0x1.0p-53;
62 /** The smallest non-zero sample from the ZigguratSampler.Exponential sampler. */
63 private static final double SMALL_W = 6.564735882096453E-19;
64 /** A tail sample from the ZigguratSampler.Exponential after 1 recursions of the sample method. */
65 private static final double TAIL_W = 7.569274694148063;
66 /** A largest sample from the ZigguratSampler.Exponential after 4 recursions of the sample method. */
67 private static final double LARGE_W = 4 * TAIL_W;
68 /** The smallest value for alpha where 1 - (1-alpha) = alpha. */
69 private static final double SMALLEST_ALPHA = 1.0 - Math.nextDown(1.0);
70
71 private static final double VALID_ALPHA = 1.23;
72 private static final double VALID_BETA = 0.23;
73 private static final double VALID_GAMMA = 2.34;
74 private static final double VALID_DELTA = 3.45;
75
76 @Test
77 void testAlphaZeroThrows() {
78 assertConstructorThrows(0.0, VALID_BETA, VALID_GAMMA, VALID_DELTA);
79 }
80
81 @Test
82 void testAlphaBelowZeroThrows() {
83 assertConstructorThrows(Math.nextDown(0.0), VALID_BETA, VALID_GAMMA, VALID_DELTA);
84 }
85
86 @Test
87 void testAlphaTooCloseToZeroThrows() {
88 // The realistic range for alpha is not Double.MIN_VALUE.
89 // The number 1 - alpha must not be 1.
90 // This is valid
91 final UniformRandomProvider rng = new SplitMix64(0L);
92 StableSampler s = StableSampler.of(rng, SMALLEST_ALPHA, VALID_BETA, VALID_GAMMA, VALID_DELTA);
93 Assertions.assertNotNull(s);
94
95 // Smaller than this is still above zero but 1 - alpha == 1
96 final double alphaTooSmall = SMALLEST_ALPHA / 2;
97 Assertions.assertNotEquals(0.0, alphaTooSmall, "Expected alpha to be positive");
98 Assertions.assertEquals(1.0, 1 - alphaTooSmall, "Expected rounding to 1");
99
100 // Because alpha is effectively zero this will throw
101 assertConstructorThrows(alphaTooSmall, VALID_BETA, VALID_GAMMA, VALID_DELTA);
102 }
103
104 @Test
105 void testAlphaAboveTwoThrows() {
106 assertConstructorThrows(Math.nextUp(2.0), VALID_BETA, VALID_GAMMA, VALID_DELTA);
107 }
108
109 @Test
110 void testAlphaNaNThrows() {
111 assertConstructorThrows(Double.NaN, VALID_BETA, VALID_GAMMA, VALID_DELTA);
112 }
113
114 @Test
115 void testBetaBelowMinusOneThrows() {
116 assertConstructorThrows(VALID_ALPHA, Math.nextDown(-1.0), VALID_GAMMA, VALID_DELTA);
117 }
118
119 @Test
120 void testBetaAboveOneThrows() {
121 assertConstructorThrows(VALID_ALPHA, Math.nextUp(1.0), VALID_GAMMA, VALID_DELTA);
122 }
123
124 @Test
125 void testBetaNaNThrows() {
126 assertConstructorThrows(VALID_ALPHA, Double.NaN, VALID_GAMMA, VALID_DELTA);
127 }
128
129 @Test
130 void testGammaNotStrictlyPositiveThrows() {
131 assertConstructorThrows(VALID_ALPHA, VALID_BETA, 0.0, VALID_DELTA);
132 }
133
134 @Test
135 void testGammaInfThrows() {
136 assertConstructorThrows(VALID_ALPHA, VALID_BETA, Double.POSITIVE_INFINITY, VALID_DELTA);
137 }
138
139 @Test
140 void testGammaNaNThrows() {
141 assertConstructorThrows(VALID_ALPHA, VALID_BETA, Double.NaN, VALID_DELTA);
142 }
143
144 @Test
145 void testDeltaInfThrows() {
146 assertConstructorThrows(VALID_ALPHA, VALID_BETA, VALID_GAMMA, Double.POSITIVE_INFINITY);
147 }
148
149 @Test
150 void testDeltaNegInfThrows() {
151 assertConstructorThrows(VALID_ALPHA, VALID_BETA, VALID_GAMMA, Double.NEGATIVE_INFINITY);
152 }
153
154 @Test
155 void testDeltaNaNThrows() {
156 assertConstructorThrows(VALID_ALPHA, VALID_BETA, VALID_GAMMA, Double.NaN);
157 }
158
159 /**
160 * Asserts the stable sampler factory constructor throws an {@link IllegalArgumentException}.
161 *
162 * @param alpha Stability parameter. Must be in range {@code (0, 2]}.
163 * @param beta Skewness parameter. Must be in range {@code [-1, 1]}.
164 * @param gamma Scale parameter. Must be strictly positive and finite.
165 * @param delta Location parameter. Must be finite.
166 */
167 private static void assertConstructorThrows(double alpha, double beta, double gamma, double delta) {
168 final UniformRandomProvider rng = new SplitMix64(0L);
169 Assertions.assertThrows(IllegalArgumentException.class,
170 () -> StableSampler.of(rng, alpha, beta, gamma, delta));
171 }
172
173 /**
174 * Assumption test:
175 * Test the limits of the value {@code tau} at the extreme limits of {@code alpha}.
176 * The expression is evaluated against the original CMS algorithm. The method
177 * has been updated to ensure symmetry around zero.
178 *
179 * <p>The test demonstrates that tau can be zero even when beta is not zero. Thus
180 * the choice of a beta=0 sampler must check tau and not beta.
181 */
182 @Test
183 void testTauLimits() {
184 // At the limit of beta, tau ranges from 2/pi to 0 as alpha moves away from 1.
185 final double beta = 1;
186
187 // alpha -> 2: tau -> 0
188 // alpha -> 0: tau -> 0
189 Assertions.assertEquals(0.0, CMSStableSampler.getTau(2, beta));
190 Assertions.assertEquals(0.0, CMSStableSampler.getTau(0, beta));
191
192 // Full range over 0 to 2.
193 for (int i = 0; i <= 512; i++) {
194 // This is a power of 2 so the symmetric test uses an exact mirror
195 final double alpha = (double) i / 256;
196 final double tau = CMSStableSampler.getTau(alpha, beta);
197 final double expected = getTauOriginal(alpha, beta);
198 Assertions.assertEquals(expected, tau, 1e-15);
199
200 // Symmetric
201 Assertions.assertEquals(tau, CMSStableSampler.getTau(2 - alpha, beta));
202 }
203
204 // alpha -> 1: tau -> beta / (pi / 2) = 0.6366
205 final double limit = beta / PI_2;
206 Assertions.assertEquals(limit, CMSStableSampler.getTau(1, beta));
207 for (double alpha : new double[] {1.01, 1 + 1e-6, 1, 1 - 1e-6, 0.99}) {
208 final double tau = CMSStableSampler.getTau(alpha, beta);
209 final double expected = getTauOriginal(alpha, beta);
210 Assertions.assertEquals(expected, tau, 1e-15);
211 // Approach the limit
212 Assertions.assertEquals(limit, tau, Math.abs(1 - alpha) + 1e-15);
213 }
214
215 // It can be zero if beta is zero or close to zero when alpha != 1.
216 // This requires we check tau==0 instead of beta==0 to switch to
217 // a beta = 0 sampler.
218 Assertions.assertEquals(0.0, CMSStableSampler.getTau(1.3, 0.0));
219 Assertions.assertEquals(0.0, CMSStableSampler.getTau(1.5, Double.MIN_VALUE));
220 Assertions.assertNotEquals(0.0, CMSStableSampler.getTau(1.0, Double.MIN_VALUE));
221
222 // The sign of beta determines the sign of tau.
223 Assertions.assertEquals(0.5, CMSStableSampler.getTau(1.5, beta));
224 Assertions.assertEquals(0.5, CMSStableSampler.getTau(0.5, beta));
225 Assertions.assertEquals(-0.5, CMSStableSampler.getTau(1.5, -beta));
226 Assertions.assertEquals(-0.5, CMSStableSampler.getTau(0.5, -beta));
227
228 // Check monototic at the transition point to switch to a different computation.
229 final double tau1 = CMSStableSampler.getTau(Math.nextDown(1.5), 1);
230 final double tau2 = CMSStableSampler.getTau(1.5, 1);
231 final double tau3 = CMSStableSampler.getTau(Math.nextUp(1.5), 1);
232 Assertions.assertTrue(tau1 > tau2);
233 Assertions.assertTrue(tau2 > tau3);
234 // Test symmetry at the transition
235 Assertions.assertEquals(tau1, CMSStableSampler.getTau(2 - Math.nextDown(1.5), 1));
236 Assertions.assertEquals(tau2, CMSStableSampler.getTau(0.5, 1));
237 Assertions.assertEquals(tau3, CMSStableSampler.getTau(2 - Math.nextUp(1.5), 1));
238 }
239
240 /**
241 * Gets tau using the original method from the CMS algorithm implemented in the
242 * program RSTAB. This does not use {@link SpecialMath#tan2(double)} but uses
243 * {@link Math#tan(double)} to implement {@code tan(x) / x}.
244 *
245 * @param alpha alpha
246 * @param beta the beta
247 * @return tau
248 */
249 private static double getTauOriginal(double alpha, double beta) {
250 final double eps = 1 - alpha;
251 // Compute RSTAB prefactor
252 double tau;
253
254 // Use the method from Chambers et al (1976).
255 // TAN2(x) = tan(x) / x
256 // PIBY2 = pi / 2
257 // Comments are the FORTRAN code from the RSTAB routine.
258
259 if (eps > -0.99) {
260 // TAU = BPRIME / (TAN2(EPS * PIBY2) * PIBY2)
261 final double tan2 = eps == 0 ? 1 : Math.tan(eps * PI_2) / (eps * PI_2);
262 tau = beta / (tan2 * PI_2);
263 } else {
264 // TAU = BPRIME * PIBY2 * EPS * (1.-EPS) * TAN2 ((1. -EPS) * PIBY2)
265 final double meps1 = 1 - eps;
266 final double tan2 = Math.tan(meps1 * PI_2) / (meps1 * PI_2);
267 tau = beta * PI_2 * eps * meps1 * tan2;
268 }
269
270 return tau;
271 }
272
273 /**
274 * Assumption test:
275 * Test the value {@code a2} is not zero. Knowing {@code a2} is not zero simplifies
276 * correction of non-finite results from the CMS algorithm.
277 */
278 @Test
279 void testA2IsNotZero() {
280 // The extreme limit of the angle phiby2. This is ignored by the sampler
281 // as it can result in cancellation of terms and invalid results.
282 final double p0 = getU(Long.MIN_VALUE);
283 Assertions.assertEquals(-PI_4, p0);
284
285 // These are the limits to generate (-pi/4, pi/4)
286 final double p1 = getU(Long.MIN_VALUE + (1 << 10));
287 final double p2 = getU(Long.MAX_VALUE);
288 Assertions.assertNotEquals(-PI_4, p1);
289 Assertions.assertNotEquals(PI_4, p2);
290 Assertions.assertEquals(-PI_4 + PI_4 * DU, p1);
291 Assertions.assertEquals(PI_4 - PI_4 * DU, p2);
292
293 for (double phiby2 : new double[] {p1, p2}) {
294 // phiby2 in (-pi/4, pi/4)
295 // a in (-1, 1)
296 final double a = phiby2 * SpecialMath.tan2(phiby2);
297 Assertions.assertEquals(Math.copySign(Math.nextDown(1.0), phiby2), a);
298 final double da = a * a;
299 final double a2 = 1 - da;
300 // The number is close to but not equal to zero
301 Assertions.assertNotEquals(0.0, a2);
302 // The minimum value of a2 is 2.220E-16 = 2^-52
303 Assertions.assertEquals(0x1.0p-52, a2);
304 }
305 }
306
307 /**
308 * Assumption test:
309 * Test the value of the numerator used to compute z. If this is negative then
310 * computation of log(z) creates a NaN. This effect occurs when the uniform
311 * random deviate u is either 0 or 1 and beta is -1 or 1. The effect is reduced
312 * when u is in the range {@code (0, 1)} but not eliminated. The test
313 * demonstrates: (a) the requirement to check z during the sample method when
314 * {@code alpha!=1}; and (b) when {@code alpha=1} then z cannot be zero when u
315 * is in the open interval {@code (0, 1)}.
316 */
317 @Test
318 void testZIsNotAlwaysAboveZero() {
319 // A long is used to create phi/2:
320 // The next to limit values for the phi/2
321 final long x00 = Long.MIN_VALUE;
322 final long x0 = Long.MIN_VALUE + (1 << 10);
323 final long x1 = Long.MAX_VALUE;
324 Assertions.assertEquals(-PI_4, getU(x00));
325 Assertions.assertEquals(-PI_4 + DU * PI_4, getU(x0));
326 Assertions.assertEquals(PI_4 - DU * PI_4, getU(x1));
327 // General case numerator:
328 // b2 + 2 * phiby2 * bb * tau
329 // To generate 0 numerator requires:
330 // b2 == -2 * phiby2 * bb * tau
331 //
332 // The expansion of the terms is:
333 // 1 - tan^2(eps * phi/2)
334 // == -phi * tan2(eps * phi/2) * beta
335 // -----------------------
336 // tan2(eps * pi/2) * pi/2
337 //
338 // == -2 * phi * tan2(eps * phi/2) * beta
339 // -----------------------------------
340 // pi * tan2(eps * pi/2)
341 //
342 // == -2 * phi * tan(eps * phi/2) / (eps * phi/2) * beta
343 // --------------------------------------------------
344 // pi * tan(eps * pi/2) / (eps * pi/2)
345 //
346 // if phi/2 = pi/4, x = eps * phi/2:
347 // == -2 * pi/4 * tan(x) / (x) * beta
348 // ---------------------------------
349 // pi * tan(2x) / (2x)
350 //
351 // This is a known double-angle identity for tan:
352 // 1 - tan^2(x) == -2 tan(x) * beta
353 // ---------
354 // tan(2x)
355 // Thus if |beta|=1 with opposite sign to phi, and |phi|=pi/2
356 // the numerator is zero for all alpha.
357 // This is not true due to floating-point
358 // error but the following cases are known to exhibit the result.
359
360 Assertions.assertEquals(0.0, computeNumerator(0.859375, 1, x00));
361 // Even worse to have negative as the log(-ve) = nan
362 Assertions.assertTrue(0.0 > computeNumerator(0.9375, 1, x00));
363 Assertions.assertTrue(0.0 > computeNumerator(1.90625, 1, x00));
364
365 // As phi reduces in magnitude the equality fails.
366 // The numerator=0 can often be corrected
367 // with the next random variate from the range limit.
368 Assertions.assertTrue(0.0 < computeNumerator(0.859375, 1, x0));
369 Assertions.assertTrue(0.0 < computeNumerator(0.9375, 1, x0));
370 Assertions.assertTrue(0.0 < computeNumerator(1.90625, 1, x0));
371
372 // WARNING:
373 // Even when u is not at the limit floating point error can still create
374 // a bad numerator. This is rare but shows we must still detect this edge
375 // case.
376 Assertions.assertTrue(0.0 > computeNumerator(0.828125, 1, x0));
377 Assertions.assertTrue(0.0 > computeNumerator(1.291015625, -1, x1));
378
379 // beta=0 case the numerator reduces to b2:
380 // b2 = 1 - tan^2((1-alpha) * phi/2)
381 // requires tan(x)=1; x=pi/4.
382 // Note: tan(x) = x * SpecialMath.tan2(x) returns +/-1 for u = +/-pi/4.
383 Assertions.assertEquals(-1, SpecialMath.tan2(getU(x00)) * getU(x00));
384 // Using the next value in the range this is not an issue.
385 // The beta=0 sampler does not have to check for z=0.
386 Assertions.assertTrue(-1 < SpecialMath.tan2(getU(x0)) * getU(x0));
387 Assertions.assertTrue(1 > SpecialMath.tan2(getU(x1)) * getU(x1));
388 // Use alpha=2 so 1-alpha (eps) is at the limit
389 final double beta = 0;
390 Assertions.assertEquals(0.0, computeNumerator(2, beta, x00));
391 Assertions.assertTrue(0.0 < computeNumerator(2, beta, x0));
392 Assertions.assertTrue(0.0 < computeNumerator(2, beta, x1));
393 Assertions.assertTrue(0.0 < computeNumerator(Math.nextDown(2), beta, x0));
394 Assertions.assertTrue(0.0 < computeNumerator(Math.nextDown(2), beta, x1));
395
396 // alpha=1 case the numerator reduces to:
397 // 1 + 2 * phi/2 * tau
398 // A zero numerator requires:
399 // 2 * phiby2 * tau = -1
400 //
401 // tau = 2 * beta / pi
402 // phiby2 = -pi / (2 * 2 * beta)
403 // beta = 1 => phiby2 = -pi/4
404 // beta = -1 => phiby2 = pi/4
405 // The alpha=1 sampler does not have to check for z=0 if phiby2 excludes -pi/4.
406 final double alpha = 1;
407 Assertions.assertEquals(0.0, computeNumerator(alpha, 1, x00));
408 // Next value of u computes above zero
409 Assertions.assertTrue(0.0 < computeNumerator(alpha, 1, x0));
410 Assertions.assertTrue(0.0 < computeNumerator(alpha, -1, x1));
411 // beta < 1 => u < 0
412 // beta > -1 => u > 1
413 // z=0 not possible with any other beta
414 Assertions.assertTrue(0.0 < computeNumerator(alpha, Math.nextUp(-1), x00));
415 }
416
417 /**
418 * Compute the numerator value for the z coefficient in the CMS algorithm.
419 *
420 * @param alpha Stability parameter. Must be in range {@code (0, 2]}.
421 * @param beta Skewness parameter. Must be in range {@code [-1, 1]}.
422 * @param x The random long used to generate the uniform deviate in the range {@code [-pi/4, pi/4)}.
423 * @return numerator
424 */
425 private static double computeNumerator(double alpha, double beta, long x) {
426 final double phiby2 = getU(x);
427 final double eps = 1 - alpha;
428 final double tau = CMSStableSampler.getTau(alpha, beta);
429
430 final double bb = SpecialMath.tan2(eps * phiby2);
431 final double b = eps * phiby2 * bb;
432 // Compute some necessary subexpressions
433 final double db = b * b;
434 final double b2 = 1 - db;
435 // Compute z coefficient numerator.
436 return b2 + 2 * phiby2 * bb * tau;
437 }
438
439 /**
440 * Assumption test:
441 * Test the CMS algorithm can compute the value {@code d} without creating a NaN
442 * when {@code z} is any non-zero finite value. When the value {@code z} is zero or infinite
443 * the computation may multiply infinity by zero and create NaN.
444 */
445 @Test
446 void testComputeDWhenZIsFiniteNonZero() {
447 final double[] zs = {Double.MIN_VALUE, Double.MAX_VALUE};
448
449 final double[] alphas = {2, 1.5, 1 + 1e-6, 1, 1 - 1e-6, 0.5, 0.01, 1e-10, SMALLEST_ALPHA};
450 for (final double alpha : alphas) {
451 // Finite z
452 for (final double z : zs) {
453 // The result may be infinite, but not NaN
454 Assertions.assertNotEquals(Double.NaN, computeD(alpha, z));
455 }
456
457 // May be invalid with z=0 or z=inf as some combinations multiply the
458 // infinity by 0 to create NaN.
459
460 // When z=0, log(z) = -inf, d = d2(sign(1-alpha) * -inf) * -inf
461 final double d0 = computeD(alpha, 0);
462 if (alpha < 1) {
463 // d2(-inf) * -inf = 0 * -inf = NaN
464 Assertions.assertEquals(Double.NaN, d0);
465 } else if (alpha == 1) {
466 // d2(0 * -inf) -> NaN
467 Assertions.assertEquals(Double.NaN, d0);
468 } else {
469 // alpha > 1
470 // d2(inf) * -inf = -inf
471 Assertions.assertEquals(Double.NEGATIVE_INFINITY, d0);
472 }
473
474 // When z=inf, log(z) = inf, d = d2(sign(1-alpha) * inf) * inf
475 final double di = computeD(alpha, Double.POSITIVE_INFINITY);
476 if (alpha < 1) {
477 // d2(inf) * inf = inf
478 Assertions.assertEquals(Double.POSITIVE_INFINITY, di);
479 } else if (alpha == 1) {
480 // d2(0 * inf) -> NaN
481 Assertions.assertEquals(Double.NaN, di);
482 } else {
483 // alpha > 1
484 // d2(-inf) * inf = 0 * inf = NaN
485 Assertions.assertEquals(Double.NaN, di);
486 }
487 }
488 }
489
490 /**
491 * Compute the {@code d} value in the CMS algorithm.
492 *
493 * @param alpha alpha
494 * @param z z
495 * @return d
496 */
497 private static double computeD(double alpha, double z) {
498 final double alogz = Math.log(z);
499 final double eps = 1 - alpha;
500 final double meps1 = 1 - eps;
501 return SpecialMath.d2(eps * alogz / meps1) * (alogz / meps1);
502 }
503
504 /**
505 * Assumption test:
506 * Test the sin(alpha * phi + atan(-zeta)) term can be zero.
507 * This applies to the Weron formula.
508 */
509 @Test
510 void testSinAlphaPhiMinusAtanZeta() {
511 // Note sin(alpha * phi + atan(-zeta)) is zero when:
512 // alpha * phi = -atan(-zeta)
513 // tan(-alpha * phi) = -zeta
514 // = beta * tan(alpha * pi / 2)
515 // beta = tan(-alpha * phi) / tan(alpha * pi / 2)
516 // Find a case where the result is zero...
517 for (double alpha : new double[] {0.25, 0.125}) {
518 for (double phi : new double[] {PI_4, PI_4 / 2}) {
519 double beta = Math.tan(-alpha * phi) / Math.tan(alpha * PI_2);
520 double zeta = -beta * Math.tan(alpha * PI_2);
521 double atanZeta = Math.atan(-zeta);
522 Assertions.assertEquals(0.0, alpha * phi + atanZeta);
523 }
524 }
525 }
526
527 /**
528 * Assumption test:
529 * Test the cos(phi - alpha * (phi + xi)) term is positive.
530 * This applies to the Weron formula.
531 */
532 @Test
533 void testCosPhiMinusAlphaPhiXi() {
534 // This is the extreme of cos(x) that should be used
535 final double cosPi2 = Math.cos(PI_2);
536 // The function is symmetric
537 Assertions.assertEquals(cosPi2, Math.cos(-PI_2));
538 // As pi is an approximation then the cos value is not exactly 0
539 Assertions.assertTrue(cosPi2 > 0);
540
541 final UniformRandomProvider rng = RandomAssert.createRNG();
542
543 // The term is mirrored around 1 so use extremes between 1 and 0
544 final double[] alphas = {1, Math.nextDown(1), 0.99, 0.5, 0.1, 0.05, 0.01, DU};
545 // Longs to generate extremes for the angle phi. This is mirrored
546 // by negation is the assert method so use values to create phi in [0, pi/2).
547 final long[] xs = {0, 1 << 10, Long.MIN_VALUE >>> 1, Long.MAX_VALUE};
548 for (final double alpha : alphas) {
549 for (final long x : xs) {
550 assertCosPhiMinusAlphaPhiXi(alpha, x);
551 assertCosPhiMinusAlphaPhiXi(2 - alpha, x);
552 }
553 for (int j = 0; j < 1000; j++) {
554 final long x = rng.nextLong();
555 assertCosPhiMinusAlphaPhiXi(alpha, x);
556 assertCosPhiMinusAlphaPhiXi(2 - alpha, x);
557 }
558 }
559 // Random alpha
560 for (int i = 0; i < 1000; i++) {
561 final double alpha = rng.nextDouble();
562 for (final long x : xs) {
563 assertCosPhiMinusAlphaPhiXi(alpha, x);
564 assertCosPhiMinusAlphaPhiXi(2 - alpha, x);
565 }
566 for (int j = 0; j < 1000; j++) {
567 final long x = rng.nextLong();
568 assertCosPhiMinusAlphaPhiXi(alpha, x);
569 assertCosPhiMinusAlphaPhiXi(2 - alpha, x);
570 }
571 }
572
573 // Enumerate alpha
574 for (int i = 0; i <= 1023; i++) {
575 final double alpha = (double) i / 1023;
576 for (final long x : xs) {
577 assertCosPhiMinusAlphaPhiXi(alpha, x);
578 assertCosPhiMinusAlphaPhiXi(2 - alpha, x);
579 }
580 }
581 }
582
583 /**
584 * Assert the cos(phi - alpha * (phi + xi)) term is positive.
585 * This asserts the term (phi - alpha * (phi + xi)) is in the interval (-pi/2, pi/2) when
586 * beta is the extreme of +/-1.
587 *
588 * @param alpha alpha
589 * @param x the long used to create the uniform deviate
590 */
591 private static void assertCosPhiMinusAlphaPhiXi(double alpha, long x) {
592 // Update for symmetry around alpha = 1
593 final double eps = 1 - alpha;
594 final double meps1 = 1 - eps;
595
596 // zeta = -beta * tan(alpha * pi / 2)
597 // xi = atan(-zeta) / alpha
598 // Compute phi - alpha * (phi + xi).
599 // This value must be in (-pi/2, pi/2).
600 // The term expands to:
601 // phi - alpha * (phi + xi)
602 // = phi - alpha * phi - atan(-zeta)
603 // = (1-alpha) * phi - atan(-zeta)
604 // When beta = +/-1,
605 // atanZeta = +/-alpha * pi/2 if alpha < 1
606 // atanZeta = +/-(2-alpha) * pi/2 if alpha > 1
607 // alpha=1 => always +/-pi/2
608 // alpha=0,2 => always +/-phi
609 // Values in between use the addition:
610 // (1-alpha) * phi +/- alpha * pi/2
611 // Since (1-alpha) is exact and alpha = 1 - (1-alpha) the addition
612 // cannot exceed pi/2.
613
614 // Avoid the round trip using tan and arctan when beta is +/- 1
615 // zeta = -beta * Math.tan(alpha * pi / 2);
616 // atan(-zeta) = alpha * pi / 2
617
618 final double alphaPi2;
619 if (meps1 > 1) {
620 // Avoid calling tan outside the domain limit [-pi/2, pi/2].
621 alphaPi2 = -(2 - meps1) * PI_2;
622 } else {
623 alphaPi2 = meps1 * PI_2;
624 }
625
626 // Compute eps * phi +/- alpha * pi / 2
627 // Test it is in the interval (-pi/2, pi/2)
628 double phi = getU(x) * 2;
629 double value = eps * phi + alphaPi2;
630 Assertions.assertTrue(value <= PI_2);
631 Assertions.assertTrue(value >= -PI_2);
632 value = eps * phi - alphaPi2;
633 Assertions.assertTrue(value <= PI_2);
634 Assertions.assertTrue(value >= -PI_2);
635
636 // Mirror the deviate
637 phi = -phi;
638 value = eps * phi + alphaPi2;
639 Assertions.assertTrue(value <= PI_2);
640 Assertions.assertTrue(value >= -PI_2);
641 value = eps * phi - alphaPi2;
642 Assertions.assertTrue(value <= PI_2);
643 Assertions.assertTrue(value >= -PI_2);
644 }
645 /**
646 * Assumption test:
647 * Test the sin(alpha * phi) term is only zero when phi is zero.
648 * This applies to the Weron formula when {@code beta = 0}.
649 */
650 @Test
651 void testSinAlphaPhi() {
652 // Smallest non-zero phi.
653 // getU creates in the domain (-pi/4, pi/4) so double the angle.
654 for (final double phi : new double[] {getU(-1) * 2, getU(1 << 10) * 2}) {
655 final double x = Math.sin(SMALLEST_ALPHA * phi);
656 Assertions.assertNotEquals(0.0, x);
657 // Value is actually:
658 Assertions.assertEquals(1.9361559566769725E-32, Math.abs(x));
659 }
660 }
661
662 /**
663 * Assumption test:
664 * Test functions to compute {@code (exp(x) - 1) / x}. This tests the use of
665 * {@link Math#expm1(double)} and {@link Math#exp(double)} to determine if the switch
666 * point to the high precision version is monotonic.
667 */
668 @Test
669 void testExpM1() {
670 // Test monotonic at the switch point
671 Assertions.assertEquals(d2(0.5), d2b(0.5));
672 // When positive x -> 0 the value smaller bigger.
673 Assertions.assertTrue(d2(Math.nextDown(0.5)) <= d2b(0.5));
674 Assertions.assertEquals(d2(-0.5), d2b(-0.5));
675 // When negative x -> 0 the value gets bigger.
676 Assertions.assertTrue(d2(-Math.nextDown(0.5)) >= d2b(-0.5));
677 // Potentially the next power of 2 could be used based on ULP errors but
678 // the switch is not monotonic.
679 Assertions.assertFalse(d2(Math.nextDown(0.25)) <= d2b(0.25));
680 }
681
682 /**
683 * This is not a test.
684 *
685 * <p>This outputs a report of the mean ULP difference between
686 * using {@link Math#expm1(double)} and {@link Math#exp(double)} to evaluate
687 * {@code (exp(x) - 1) / x}. This helps choose the switch point to avoid the computationally
688 * expensive expm1 function.
689 */
690 //@Test
691 void expm1ULPReport() {
692 // Create random doubles with a given exponent. Compute the mean and max ULP difference.
693 final UniformRandomProvider rng = RandomAssert.createRNG();
694 // For a quicker report set to <= 2^20.
695 final int size = 1 << 30;
696 // Create random doubles using random bits in the 52-bit mantissa.
697 final long mask = (1L << 52) - 1;
698 // Note:
699 // The point at which there *should* be no difference between the two is when
700 // exp(x) - 1 == exp(x). This will occur at exp(x)=2^54, x = ln(2^54) = 37.43.
701 Assertions.assertEquals(((double) (1L << 54)) - 1, (double) (1L << 54));
702 // However since expm1 and exp are only within 1 ULP of the exact result differences
703 // still occur above this threshold.
704 // 2^6 = 64; 2^-4 = 0.0625
705 for (int signedExp = 6; signedExp >= -4; signedExp--) {
706 // The exponent must be unsigned so + 1023 to the signed exponent
707 final long exp = (signedExp + 1023L) << 52;
708 // Test we are creating the correct numbers
709 Assertions.assertEquals(signedExp, Math.getExponent(Double.longBitsToDouble(exp)));
710 Assertions.assertEquals(signedExp, Math.getExponent(Double.longBitsToDouble((-1 & mask) | exp)));
711 // Get the average and max ulp
712 long sum1 = 0;
713 long sum2 = 0;
714 long max1 = 0;
715 long max2 = 0;
716 for (int i = size; i-- > 0;) {
717 final long bits = rng.nextLong() & mask;
718 final double x = Double.longBitsToDouble(bits | exp);
719 final double x1 = d2(x);
720 final double x2 = d2b(x);
721 final double x1b = d2(-x);
722 final double x2b = d2b(-x);
723 final long ulp1 = Math.abs(Double.doubleToRawLongBits(x1) - Double.doubleToRawLongBits(x2));
724 final long ulp2 = Math.abs(Double.doubleToRawLongBits(x1b) - Double.doubleToRawLongBits(x2b));
725 sum1 += ulp1;
726 sum2 += ulp2;
727 if (max1 < ulp1) {
728 max1 = ulp1;
729 }
730 if (max2 < ulp2) {
731 max2 = ulp2;
732 }
733 }
734 // CHECKSTYLE: stop Regexp
735 System.out.printf("%-6s %2d %-24s (%d) %-24s (%d)%n",
736 Double.longBitsToDouble(exp), signedExp,
737 (double) sum1 / size, max1, (double) sum2 / size, max2);
738 // CHECKSTYLE: resume Regexp
739 }
740 }
741
742 /**
743 * Evaluate {@code (exp(x) - 1) / x} using {@link Math#expm1(double)}.
744 * For {@code x} in the range {@code [-inf, inf]} returns
745 * a result in {@code [0, inf]}.
746 *
747 * <ul>
748 * <li>For {@code x=-inf} this returns {@code 0}.
749 * <li>For {@code x=0} this returns {@code 1}.
750 * <li>For {@code x=inf} this returns {@code inf}.
751 * </ul>
752 *
753 * <p> This corrects {@code 0 / 0} and {@code inf / inf} division from
754 * {@code NaN} to either {@code 1} or the upper bound respectively.
755 *
756 * @param x value to evaluate
757 * @return {@code (exp(x) - 1) / x}.
758 */
759 private static double d2(double x) {
760 // Here we use a conditional to detect both edge cases, which are then corrected.
761 final double d2 = Math.expm1(x) / x;
762 if (Double.isNaN(d2)) {
763 // Correct edge cases.
764 if (x == 0) {
765 return 1.0;
766 }
767 // x must have been +infinite or NaN
768 return x;
769 }
770 return d2;
771 }
772
773 /**
774 * Evaluate {@code (exp(x) - 1) / x} using {@link Math#exp(double)}.
775 * For {@code x} in the range {@code [-inf, inf]} returns
776 * a result in {@code [0, inf]}.
777 *
778 * <ul>
779 * <li>For {@code x=-inf} this returns {@code 0}.
780 * <li>For {@code x=0} this returns {@code 1}.
781 * <li>For {@code x=inf} this returns {@code inf}.
782 * </ul>
783 *
784 * <p> This corrects {@code 0 / 0} and {@code inf / inf} division from
785 * {@code NaN} to either {@code 1} or the upper bound respectively.
786 *
787 * @param x value to evaluate
788 * @return {@code (exp(x) - 1) / x}.
789 */
790 private static double d2b(double x) {
791 // Here we use a conditional to detect both edge cases, which are then corrected.
792 final double d2 = (Math.exp(x) - 1) / x;
793 if (Double.isNaN(d2)) {
794 // Correct edge cases.
795 if (x == 0) {
796 return 1.0;
797 }
798 // x must have been +infinite or NaN
799 return x;
800 }
801 return d2;
802 }
803
804 /**
805 * Test the special d2 function returns {@code (exp(x) - 1) / x}.
806 * The limits of the function are {@code [0, inf]} and it should return 1 when x=0.
807 */
808 @Test
809 void testD2() {
810 for (final double x : new double[] {Double.MAX_VALUE, Math.log(Double.MAX_VALUE), 10, 5, 1, 0.5, 0.1, 0.05, 0.01}) {
811 Assertions.assertEquals(Math.expm1(x) / x, SpecialMath.d2(x), 1e-15);
812 Assertions.assertEquals(Math.expm1(-x) / -x, SpecialMath.d2(-x), 1e-15);
813 }
814
815 // Negative infinity computes without correction
816 Assertions.assertEquals(0.0, Math.expm1(Double.NEGATIVE_INFINITY) / Double.NEGATIVE_INFINITY);
817 Assertions.assertEquals(0.0, SpecialMath.d2(Double.NEGATIVE_INFINITY));
818
819 // NaN is returned (i.e. no correction)
820 Assertions.assertEquals(Double.NaN, SpecialMath.d2(Double.NaN));
821
822 // Edge cases for z=0 or z==inf require correction
823 Assertions.assertEquals(Double.NaN, Math.expm1(0) / 0.0);
824 Assertions.assertEquals(Double.NaN, Math.expm1(Double.POSITIVE_INFINITY) / Double.POSITIVE_INFINITY);
825 // Corrected in the special function
826 Assertions.assertEquals(1.0, SpecialMath.d2(0.0));
827 Assertions.assertEquals(Double.POSITIVE_INFINITY, SpecialMath.d2(Double.POSITIVE_INFINITY));
828 }
829
830 /**
831 * Test the tan2 function returns {@code tan(x) / x}.
832 */
833 @Test
834 void testTan2() {
835 // Test the value of tan(x) when the angle is generated in the open interval (-pi/4, pi/4)
836 for (final long x : new long[] {Long.MIN_VALUE + (1 << 10), Long.MAX_VALUE}) {
837 final double phiby2 = getU(x);
838 Assertions.assertEquals(PI_4 - DU * PI_4, Math.abs(phiby2));
839 final double a = phiby2 * SpecialMath.tan2(phiby2);
840 // Check this is not 1
841 Assertions.assertNotEquals(1, Math.abs(a));
842 Assertions.assertTrue(Math.abs(a) < 1.0);
843 }
844
845 // At pi/4 the function reverts to Math.tan(x) / x. Test through the transition.
846 final double pi = Math.PI;
847 for (final double x : new double[] {pi, pi / 2, pi / 3.99, pi / 4, pi / 4.01, pi / 8, pi / 16}) {
848 final double y = Math.tan(x) / x;
849 Assertions.assertEquals(y, SpecialMath.tan2(x), Math.ulp(y));
850 }
851
852 // Test this closely matches the JDK tan function.
853 // Test uniformly between 0 and pi / 4.
854 // Count the errors with the ULP difference.
855 // Get max ULP and mean ULP. Do this for both tan(x) and tan(x)/x functions.
856 final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create(0x1647816481684L);
857 int count = 0;
858 long ulp = 0;
859 long max = 0;
860 long ulp2 = 0;
861 long max2 = 0;
862 for (int i = 0; i < 1000; i++) {
863 final double x = rng.nextDouble() * PI_4;
864 count++;
865 final double tanx = Math.tan(x);
866 final double tan2x = SpecialMath.tan2(x);
867 // Test tan(x)
868 double y = x * tan2x;
869 if (y != tanx) {
870 final long u = Math.abs(Double.doubleToRawLongBits(tanx) - Double.doubleToRawLongBits(y));
871 if (max < u) {
872 max = u;
873 }
874 ulp += u;
875 // Within 4 ulp. Note tan(x) is within 1 ulp of the result. So this
876 // is max 5 ulp from the result.
877 Assertions.assertEquals(tanx, y, 4 * Math.ulp(tanx));
878 }
879 // Test tan(x) / x
880 y = tanx / x;
881 if (y != tan2x) {
882 final long u = Math.abs(Double.doubleToRawLongBits(tan2x) - Double.doubleToRawLongBits(y));
883 if (max2 < u) {
884 max2 = u;
885 }
886 ulp2 += u;
887 // Within 3 ulp.
888 Assertions.assertEquals(y, tan2x, 3 * Math.ulp(y));
889 }
890 }
891 // Mean (max) ULP is very low
892 // 2^30 random samples in [0, pi / 4)
893 // tan(x) tan(x) / x
894 // tan4283 93436.25534446817 201185 : 68313.16171793547 128079
895 // tan4288c 0.5905972588807344 4 : 0.4047176940366626 3
896 Assertions.assertTrue((double) ulp / count < 0.6, "Mean ULP to tan(x) is too high");
897 Assertions.assertTrue((double) ulp2 / count < 0.45, "Mean ULP to tan(x) / x is too high");
898 // If the value is under 1 then the sampler will break due to cancellation errors.
899 Assertions.assertEquals(1.0, SpecialMath.tan2(0.0), "Must be exact tan(x) / x at x=0");
900 Assertions.assertEquals(4 / Math.PI, SpecialMath.tan2(PI_4), Math.ulp(4 / Math.PI));
901 Assertions.assertEquals(1.0, PI_4 * SpecialMath.tan2(PI_4), Math.ulp(1.0));
902 // If this is above 1 then the sampler will break. Test at the switch point pi/4.
903 Assertions.assertTrue(1.0 >= PI_4 * SpecialMath.tan2(PI_4));
904 Assertions.assertTrue(1.0 >= PI_4 * SpecialMath.tan2(Math.nextDown(PI_4)));
905 // Monotonic function at the transition
906 Assertions.assertTrue(SpecialMath.tan2(Math.nextUp(PI_4)) >= SpecialMath.tan2(PI_4));
907 }
908
909 /**
910 * Assumption test:
911 * Demonstrate the CMS algorithm matches the Weron formula when {@code alpha != 1}.
912 * This shows the two are equivalent; they should match as the formulas are rearrangements.
913 */
914 @Test
915 void testSamplesWithAlphaNot1() {
916 // Use non-extreme parameters. beta and u are negated so use non-redundant values
917 final double[] alphas = {0.3, 0.9, 1.1, 1.5};
918 final double[] betas = {-1, -0.5, -0.3, 0};
919 final double[] ws = {0.1, 1, 3};
920 final double[] us = {0.1, 0.25, 0.5, 0.8};
921
922 final double relative = 1e-5;
923 final double absolute = 1e-10;
924 for (final double alpha : alphas) {
925 for (final double beta : betas) {
926 for (final double w : ws) {
927 for (final double u : us) {
928 final double x = sampleCMS(alpha, beta, w, u);
929 final double y = sampleWeronAlphaNot1(alpha, beta, w, u);
930 Assertions.assertEquals(x, y, Math.max(absolute, Math.abs(x) * relative));
931 // Test symmetry
932 final double z = sampleCMS(alpha, -beta, w, 1 - u);
933 Assertions.assertEquals(x, -z, 0.0);
934 }
935 }
936 }
937 }
938 }
939
940 /**
941 * Assumption test:
942 * Demonstrate the CMS algorithm matches the Weron formula when {@code alpha == 1}.
943 * This shows the two are equivalent; they should match as the formulas are rearrangements.
944 */
945 @Test
946 void testSamplesWithAlpha1() {
947 // Use non-extreme parameters. beta and u are negated so use non-redundant values
948 final double[] betas = {-1, -0.5, -0.3, 0};
949 final double[] ws = {0.1, 1, 3};
950 final double[] us = {0.1, 0.25, 0.5, 0.8};
951
952 final double relative = 1e-5;
953 final double absolute = 1e-10;
954 final double alpha = 1;
955 for (final double beta : betas) {
956 for (final double w : ws) {
957 for (final double u : us) {
958 final double x = sampleCMS(alpha, beta, w, u);
959 final double y = sampleWeronAlpha1(beta, w, u);
960 Assertions.assertEquals(x, y, Math.max(absolute, Math.abs(x) * relative));
961 // Test symmetry
962 final double z = sampleCMS(alpha, -beta, w, 1 - u);
963 Assertions.assertEquals(x, -z, 0.0);
964 }
965 }
966 }
967 }
968
969 /**
970 * Assumption test:
971 * Demonstrate the CMS formula is continuous as {@code alpha -> 1}.
972 * Demonstrate the Weron formula is not continuous as {@code alpha -> 1}.
973 */
974 @Test
975 void testConvergenceWithAlphaCloseTo1() {
976 final double[] betas = {-1, -0.5, 0, 0.3, 1};
977 final double[] ws = {0.1, 1, 10};
978 final double[] us = {0.1, 0.25, 0.5, 0.8};
979 final int steps = 30;
980
981 // Start with alpha not close to 0. The value 0.0625 is a power of 2 so is scaled
982 // exactly by dividing by 2. With 30 steps this ranges from 2^-4 to 2^-34 leaving alpha:
983 // 1.0625 -> 1.0000000000582077 or
984 // 0.9375 -> 0.9999999999417923.
985 for (double deltaStart : new double[] {-0.0625, 0.0625}) {
986 // As alpha approaches 1 the value should approach the value when alpha=0.
987 // Count the number of times it get further away with a change of alpha.
988 int cmsCount = 0;
989 int weronCount = 0;
990
991 for (final double beta : betas) {
992 for (final double w : ws) {
993 for (final double u : us) {
994 // CMS formulas
995 double x0 = sampleCMS(1, beta, w, u);
996 Assertions.assertTrue(Double.isFinite(x0), "Target must be finite");
997
998 // Sample should approach x0 as alpha approaches 1
999 double delta = deltaStart;
1000 double dx = Math.abs(x0 - sampleCMS(1 + delta, beta, w, u));
1001 for (int i = 0; i < steps; i++) {
1002 delta /= 2;
1003 final double dx2 = Math.abs(x0 - sampleCMS(1 + delta, beta, w, u));
1004 if (dx2 > dx) {
1005 cmsCount++;
1006 }
1007 dx = dx2;
1008 }
1009
1010 // Weron formulas
1011 x0 = sampleWeronAlpha1(beta, w, u);
1012 Assertions.assertTrue(Double.isFinite(x0), "Target must be finite");
1013
1014 // Sample should approach x0 as alpha approaches 1
1015 delta = deltaStart;
1016 dx = Math.abs(x0 - sampleWeronAlphaNot1(1 + delta, beta, w, u));
1017 for (int i = 0; i < steps; i++) {
1018 delta /= 2;
1019 final double dx2 = Math.abs(x0 - sampleWeronAlphaNot1(1 + delta, beta, w, u));
1020 if (dx2 > dx) {
1021 weronCount++;
1022 }
1023 dx = dx2;
1024 }
1025 }
1026 }
1027 }
1028
1029 // The CMS formala monotonically converges
1030 Assertions.assertEquals(0, cmsCount);
1031 // The weron formula does not monotonically converge
1032 // (difference to the target can be bigger when alpha moves closer to 1).
1033 Assertions.assertTrue(weronCount > 200);
1034 }
1035 }
1036
1037 /**
1038 * Test extreme inputs to the CMS algorithm where {@code alpha != 1} and/or
1039 * {@code beta != 0}. These demonstrate cases where the parameters and the
1040 * random variates will create non-finite samples. The test checks that the Weron
1041 * formula can create an appropriate sample for all cases where the CMS formula fails.
1042 */
1043 @Test
1044 void testExtremeInputsToSample() {
1045 // Demonstrate instability when w = 0
1046 Assertions.assertEquals(Double.NaN, sampleCMS(1.3, 0.7, 0, 0.25));
1047 Assertions.assertTrue(Double.isFinite(sampleCMS(1.3, 0.7, SMALL_W, 0.25)));
1048
1049 // Demonstrate instability when u -> 0 or 1, and |beta| = 1
1050 Assertions.assertEquals(Double.NaN, sampleCMS(1.1, 1.0, 0.1, 0));
1051 Assertions.assertTrue(Double.isFinite(sampleCMS(1.1, 1.0, 0.1, DU)));
1052
1053 // Demonstrate instability when alpha -> 0
1054
1055 // Small alpha does not tolerate very small w.
1056 Assertions.assertEquals(Double.NaN, sampleCMS(0.01, 0.7, SMALL_W, 0.5));
1057
1058 // Very small alpha does not tolerate u approaching 0 or 1 (depending on the
1059 // skew)
1060 Assertions.assertEquals(Double.NaN, sampleCMS(1e-5, 0.7, 1.0, 1e-4));
1061 Assertions.assertEquals(Double.NaN, sampleCMS(1e-5, -0.7, 1.0, 1 - 1e-4));
1062
1063 final double[] alphas = {Math.nextDown(2), 1.3, 1.1, Math.nextUp(1), 1, Math.nextDown(1), 0.7, 0.1, 0.05, 0.01, 0x1.0p-16};
1064 final double[] betas = {1, 0.9, 0.001, 0};
1065 // Avoid zero for the exponential sample.
1066 // Test the smallest non-zero sample from the ArhensDieter exponential sampler,
1067 // and the largest sample.
1068 final double[] ws = {0, SMALL_W, 0.001, 1, 10, LARGE_W};
1069 // The algorithm requires a uniform deviate in (0, 1).
1070 // Use extremes of the 2^53 dyadic rationals in (0, 1) up to the symmetry limit
1071 // (i.e. 0.5).
1072 final double[] us = {DU, 2 * DU, 0.0001, 0.5 - DU, 0.5};
1073
1074 int nan1 = 0;
1075
1076 for (final double alpha : alphas) {
1077 for (final double beta : betas) {
1078 if (alpha == 1 && beta == 0) {
1079 // Ignore the Cauchy case
1080 continue;
1081 }
1082 // Get the support of the distribution. This is not -> +/-infinity
1083 // when alpha < 1 and beta = +/-1.
1084 final double[] support = getSupport(alpha, beta);
1085 final double lower = support[0];
1086 final double upper = support[1];
1087 for (final double w : ws) {
1088 for (final double u : us) {
1089 final double x1 = sampleCMS(alpha, beta, w, u);
1090 final double x2 = sampleWeron(alpha, beta, w, u);
1091
1092 if (Double.isNaN(x1)) {
1093 nan1++;
1094 }
1095 // The edge-case corrected Weron formula should not fail
1096 Assertions.assertNotEquals(Double.NaN, x2);
1097
1098 // Check symmetry of each formula.
1099 // Use a delta of zero to allow equality of 0.0 and -0.0.
1100 Assertions.assertEquals(x1, 0.0 - sampleCMS(alpha, -beta, w, 1 - u), 0.0);
1101 Assertions.assertEquals(x2, 0.0 - sampleWeron(alpha, -beta, w, 1 - u), 0.0);
1102
1103 if (Double.isInfinite(x1) && x1 != x2) {
1104 // Check the Weron correction for extreme samples.
1105 // The result should be at the correct *finite* support bounds.
1106 // Note: This applies when alpha < 1 and beta = +/-1.
1107 Assertions.assertTrue(lower <= x2 && x2 <= upper);
1108 }
1109 }
1110 }
1111 }
1112 }
1113
1114 // The CMS algorithm is expected to fail some cases
1115 Assertions.assertNotEquals(0, nan1);
1116 }
1117
1118 /**
1119 * Create a sample from a stable distribution. This is an implementation of the CMS
1120 * algorithm to allow exploration of various input values. The algorithm matches that
1121 * in the {@link CMSStableSampler} with the exception that the uniform variate
1122 * is provided in {@code (0, 1)}, not{@code (-pi/4, pi/4)}.
1123 *
1124 * @param alpha Stability parameter. Must be in the interval {@code (0, 2]}.
1125 * @param beta Skewness parameter. Must be in the interval {@code [-1, 1]}.
1126 * @param w Exponential variate
1127 * @param u Uniform variate
1128 * @return the sample
1129 */
1130 private static double sampleCMS(double alpha, double beta, double w, double u) {
1131 final double phiby2 = PI_2 * (u - 0.5);
1132 final double eps = 1 - alpha;
1133 // Do not use alpha in place of 1 - eps. When alpha < 0.5, 1 - eps == alpha is not
1134 // always true as the reverse is not exact.
1135 final double meps1 = 1 - eps;
1136
1137 // Compute RSTAB prefactor
1138 final double tau = CMSStableSampler.getTau(alpha, beta);
1139
1140 // Generic stable distribution that is continuous as alpha -> 1.
1141 // This is a trigonomic rearrangement of equation 4.1 from Chambers et al (1976)
1142 // as implemented in the Fortran program RSTAB.
1143 // Uses the special functions:
1144 // tan2 = tan(x) / x
1145 // d2 = (exp(x) - 1) / x
1146 // Here tan2 is implemented using an high precision approximation.
1147
1148 // Compute some tangents
1149 // Limits for |phi/2| < pi/4
1150 // a in (-1, 1)
1151 final double a = phiby2 * SpecialMath.tan2(phiby2);
1152 // bb in [1, 4/pi)
1153 final double bb = SpecialMath.tan2(eps * phiby2);
1154 // b in (-1, 1)
1155 final double b = eps * phiby2 * bb;
1156 // Compute some necessary subexpressions
1157 final double da = a * a;
1158 final double db = b * b;
1159 // a2 in (0, 1]
1160 final double a2 = 1 - da;
1161 // a2p in [1, 2)
1162 final double a2p = 1 + da;
1163 // b2 in (0, 1]
1164 final double b2 = 1 - db;
1165 // b2p in [1, 2)
1166 final double b2p = 1 + db;
1167 // Compute coefficient.
1168 // Note:
1169 // Avoid z <= 0 to avoid log(z) as negative infinity or nan.
1170 // This occurs when |phiby2| -> +/-pi/4 and |beta| -> 1.
1171 // Problems:
1172 // numerator=0 => z=0
1173 // denominator=0 => z=inf
1174 // numerator=denominator=0 => z=nan
1175 // 1. w or a2 are zero so the denominator is zero. w can be a rare exponential sample.
1176 // a2 -> zero if the uniform deviate is 0 or 1 and angle is |pi/4|.
1177 // If w -> 0 and |u-0.5| -> 0.5 then the product of w * a2 can be zero.
1178 // 2. |eps|=1, phiby2=|pi/4| => bb=4/pi, b=1, b2=0; if tau=0 then the numerator is zero.
1179 // This requires beta=0.
1180
1181 final double z = a2p * (b2 + 2 * phiby2 * bb * tau) / (w * a2 * b2p);
1182 // Compute the exponential-type expression
1183 final double alogz = Math.log(z);
1184 final double d = SpecialMath.d2(eps * alogz / meps1) * (alogz / meps1);
1185
1186 // Compute stable
1187 return (1 + eps * d) *
1188 (2 * ((a - b) * (1 + a * b) - phiby2 * tau * bb * (b * a2 - 2 * a))) /
1189 (a2 * b2p) + tau * d;
1190 }
1191
1192 /**
1193 * Create a sample from a stable distribution. This is an implementation of the Weron formula.
1194 * The formula has been modified when alpha != 1 to return the 0-parameterization result and
1195 * correct extreme samples to +/-infinity.
1196 *
1197 * @param alpha Stability parameter. Must be in the interval {@code (0, 2]}.
1198 * @param beta Skewness parameter. Must be in the interval {@code [-1, 1]}.
1199 * @param w Exponential variate
1200 * @param u Uniform variate
1201 * @return the sample
1202 * @see <a href="https://doi.org/10.1016%2F0167-7152%2895%2900113-1">Weron, R (1996).
1203 * "On the Chambers-Mallows-Stuck method for simulating skewed stable random variables".
1204 * Statistics & Probability Letters. 28 (2): 165–171.</a>
1205 */
1206 private static double sampleWeron(double alpha, double beta, double w, double u) {
1207 return alpha == 1 ? sampleWeronAlpha1(beta, w, u) : sampleWeronAlphaNot1(alpha, beta, w, u);
1208 }
1209
1210 /**
1211 * Create a sample from a stable distribution. This is an implementation of the
1212 * Weron {@code alpha != 1} formula. The formula has been modified to return the
1213 * 0-parameterization result and correct extreme samples to +/-infinity. The
1214 * algorithm matches that in the {@link WeronStableSampler} with the exception that
1215 * the uniform variate is provided in {@code (0, 1)}, not{@code (-pi/2, pi/2)}.
1216 *
1217 * <p>Due to the increasingly large shift (up to 1e16) as {@code alpha -> 1}
1218 * that is used to move the result to the 0-parameterization the samples around
1219 * the mode of the distribution have large cancellation and a reduced number of
1220 * bits in the sample value.
1221 *
1222 * @param alpha Stability parameter. Must be in the interval {@code (0, 2]}.
1223 * @param beta Skewness parameter. Must be in the interval {@code [-1, 1]}.
1224 * @param w Exponential variate
1225 * @param u Uniform variate
1226 * @return the sample
1227 * @see <a href="https://doi.org/10.1016%2F0167-7152%2895%2900113-1">Weron, R
1228 * (1996).
1229 * "On the Chambers-Mallows-Stuck method for simulating skewed stable random variables".
1230 * Statistics & Probability Letters. 28 (2): 165–171.</a>
1231 */
1232 private static double sampleWeronAlphaNot1(double alpha, double beta, double w, double u) {
1233 // Update for symmetry around alpha = 1
1234 final double eps = 1 - alpha;
1235 final double meps1 = 1 - eps;
1236
1237 double zeta;
1238 if (meps1 > 1) {
1239 zeta = beta * Math.tan((2 - meps1) * PI_2);
1240 } else {
1241 zeta = -beta * Math.tan(meps1 * PI_2);
1242 }
1243
1244 final double scale = Math.pow(1 + zeta * zeta, 0.5 / meps1);
1245 final double invAlpha = 1.0 / meps1;
1246 final double invAlphaM1 = invAlpha - 1;
1247
1248 final double phi = Math.PI * (u - 0.5);
1249
1250 // Generic stable distribution.
1251
1252 // Note: Adding back zeta creates the 0-parameterization defined in Nolan (1998):
1253 // X ~ S0_alpha(s,beta,u0) with s=1, u0=0 for a standard random variable.
1254 // As alpha -> 1 the translation zeta to create the stable deviate
1255 // in the 0-parameterization is increasingly large as tan(pi/2) -> infinity.
1256 // The max translation is approximately 1e16.
1257 // Without this translation the stable deviate is in the 1-parameterization
1258 // and the function is not continuous with respect to alpha.
1259 // Due to the large zeta when alpha -> 1 the number of bits of the output variable
1260 // are very low due to cancellation.
1261
1262 // As alpha -> 0 or 2 then zeta -> 0 and cancellation is not relevant.
1263 // The formula can be modified for infinite terms to compute a result for extreme
1264 // deviates u and w when the CMS formula fails.
1265
1266 // Note the following term is subject to floating point error:
1267 // final double xi = Math.atan(-zeta) / alpha;
1268 // final double alphaPhiXi = alpha * (phi + xi);
1269 // This is required: cos(phi - alphaPhiXi) > 0 => phi - alphaPhiXi in (-pi/2, pi/2).
1270 // Thus we compute atan(-zeta) and use it to compute two terms:
1271 // [1] alpha * (phi + xi) = alpha * (phi + atan(-zeta) / alpha) = alpha * phi + atan(-zeta)
1272 // [2] phi - alpha * (phi + xi) = phi - alpha * phi - atan(-zeta) = (1-alpha) * phi - atan(-zeta)
1273 final double atanZeta = Math.atan(-zeta);
1274
1275 // Compute terms
1276 // Either term can be infinite or 0. Certain parameters compute 0 * inf.
1277 // t1=inf occurs alpha -> 0.
1278 // t1=0 occurs when beta = tan(-alpha * phi) / tan(alpha * pi / 2).
1279 // t2=inf occurs when w -> 0 and alpha -> 0.
1280 // t2=0 occurs when alpha -> 0 and phi -> pi/2.
1281 // Detect zeros and return as zeta.
1282
1283 // Note sin(alpha * phi + atanZeta) is zero when:
1284 // alpha * phi = -atan(-zeta)
1285 // tan(-alpha * phi) = -zeta
1286 // = beta * tan(alpha * pi / 2)
1287 // Since |phi| < pi/2 this requires beta to have an opposite sign to phi
1288 // and a magnitude < 1. This is possible and in this case avoid a possible
1289 // 0 / 0 by setting the result as if term t1=0 and the result is zeta.
1290 double t1 = Math.sin(meps1 * phi + atanZeta);
1291 if (t1 == 0) {
1292 return zeta;
1293 }
1294 // Since cos(phi) is in (0, 1] this term will not create a
1295 // large magnitude to create t1 = 0.
1296 t1 /= Math.pow(Math.cos(phi), invAlpha);
1297
1298 // Iff Math.cos(eps * phi - atanZeta) is zero then 0 / 0 can occur if w=0.
1299 // Iff Math.cos(eps * phi - atanZeta) is below zero then NaN will occur
1300 // in the power function. These cases are avoided by u=(0,1) and direct
1301 // use of arctan(-zeta).
1302 final double t2 = Math.pow(Math.cos(eps * phi - atanZeta) / w, invAlphaM1);
1303 if (t2 == 0) {
1304 return zeta;
1305 }
1306
1307 return t1 * t2 * scale + zeta;
1308 }
1309
1310 /**
1311 * Create a sample from a stable distribution. This is an implementation of the Weron
1312 * {@code alpha == 1} formula. The algorithm matches that
1313 * in the {@link Alpha1StableSampler} with the exception that
1314 * the uniform variate is provided in {@code (0, 1)}, not{@code (-pi/2, pi/2)}.
1315 *
1316 * @param alpha Stability parameter. Must be in the interval {@code (0, 2]}.
1317 * @param beta Skewness parameter. Must be in the interval {@code [-1, 1]}.
1318 * @param w Exponential variate
1319 * @param u Uniform variate
1320 * @return the sample
1321 * @see <a href="https://doi.org/10.1016%2F0167-7152%2895%2900113-1">Weron, R (1996).
1322 * "On the Chambers-Mallows-Stuck method for simulating skewed stable random variables".
1323 * Statistics & Probability Letters. 28 (2): 165–171.</a>
1324 */
1325 private static double sampleWeronAlpha1(double beta, double w, double u) {
1326 // phi in (-pi/2, pi/2)
1327 final double phi = Math.PI * (u - 0.5);
1328
1329 // Generic stable distribution with alpha = 1
1330 final double betaPhi = PI_2 + beta * phi;
1331 return (betaPhi * Math.tan(phi) -
1332 beta * Math.log(PI_2 * w * Math.cos(phi) / betaPhi)) / PI_2;
1333 }
1334
1335 /*******************************/
1336 /* Tests for the StableSampler */
1337 /*******************************/
1338
1339 /**
1340 * Test the general CMS sampler when the random generator outputs create
1341 * deviates that cause the value {@code z} to be negative.
1342 */
1343 @Test
1344 void testSamplesWithZBelow0() {
1345 // Call the CMS algorithm with u->1; phi/2 -> pi/4.
1346 // The value with all bits set generates phi/2 -> pi/4.
1347 // Add a long to create a big value for w of 5.
1348 // The parameters create cancellation in the numerator of z to create a negative z.
1349 final long[] longs = {Long.MAX_VALUE, -6261465550279131136L};
1350
1351 final double phiby2 = PI_4 - PI_4 * DU;
1352 final double w = 5.0;
1353 assertUWSequence(new double[] {
1354 phiby2, w,
1355 }, longs);
1356
1357 // The alpha parameter has been identified via a search with beta=-1.
1358 // See testZIsNotAlwaysAboveZero()
1359 final double alpha = 1.291015625;
1360 final double beta = -1;
1361 Assertions.assertTrue(0.0 > computeNumerator(alpha, beta, Long.MAX_VALUE));
1362
1363 // z will be negative. Repeat computation assumed to be performed by the sampler.
1364 // This ensures the test should be updated if the sampler implementation changes.
1365 final double eps = 1 - alpha;
1366 final double tau = CMSStableSampler.getTau(alpha, beta);
1367 final double a = phiby2 * SpecialMath.tan2(phiby2);
1368 final double bb = SpecialMath.tan2(eps * phiby2);
1369 final double b = eps * phiby2 * bb;
1370 final double da = a * a;
1371 final double db = b * b;
1372 final double a2 = 1 - da;
1373 final double a2p = 1 + da;
1374 final double b2 = 1 - db;
1375 final double b2p = 1 + db;
1376 final double z = a2p * (b2 + 2 * phiby2 * bb * tau) / (w * a2 * b2p);
1377 Assertions.assertTrue(0.0 > z);
1378
1379 final StableSampler sampler = StableSampler.of(createRngWithSequence(longs), alpha, beta);
1380 // It should not be NaN or infinite
1381 Assertions.assertTrue(Double.isFinite(sampler.sample()), "Sampler did not recover");
1382 }
1383
1384 /**
1385 * Test the general CMS sampler when the random generator outputs create
1386 * deviates that cause the value {@code z} to be infinite.
1387 */
1388 @Test
1389 void testSamplesWithZInfinite() {
1390 // Call the CMS algorithm with w=0 (and phi/2 is not extreme).
1391 final long[] longs = {Long.MIN_VALUE >>> 1, 0};
1392
1393 assertUWSequence(new double[] {
1394 PI_4 / 2, 0,
1395 }, longs);
1396
1397 for (final double alpha : new double[] {0.789, 1, 1.23}) {
1398 // Test all directions
1399 for (final double beta : new double[] {-0.56, 0, 0.56}) {
1400 // Ignore Cauchy case which does not use the exponential deviate
1401 if (alpha == 1 && beta == 0) {
1402 continue;
1403 }
1404 final StableSampler sampler = StableSampler.of(createRngWithSequence(longs), alpha, beta);
1405 final double x = sampler.sample();
1406 // It should not be NaN
1407 Assertions.assertFalse(Double.isNaN(x), "Sampler did not recover");
1408 if (beta != 0) {
1409 // The sample is extreme so should be at a limit of the support
1410 if (alpha < 0) {
1411 // Effectively +/- infinity
1412 Assertions.assertEquals(Math.copySign(Double.POSITIVE_INFINITY, beta), x);
1413 } else if (alpha > 1) {
1414 // At the distribution mean
1415 final double[] support = getSupport(alpha, beta);
1416 final double mu = support[2];
1417 Assertions.assertEquals(mu, x);
1418 }
1419 }
1420 }
1421 }
1422 }
1423
1424 /**
1425 * Test the CMS sampler when the random generator outputs create
1426 * deviates that cause the value {@code d} to be infinite.
1427 */
1428 @Test
1429 void testSamplesWithDInfinite() {
1430 // beta != 0 but with low skew to allow the direction switch in
1431 // phi/2 to create opposite directions.
1432 testSamplesWithDInfinite(0.01);
1433 testSamplesWithDInfinite(-0.01);
1434 }
1435
1436 /**
1437 * Test the {@code beta=0} CMS sampler when the random generator outputs create
1438 * deviates that cause the value {@code d} to be infinite.
1439 */
1440 @Test
1441 void testBeta0SamplesWithDInfinite() {
1442 testSamplesWithDInfinite(0.0);
1443 }
1444
1445 /**
1446 * Test the CMS sampler when the random generator outputs create deviates that
1447 * cause the value {@code d} to be infinite. This applies to the general sampler
1448 * or the sampler with {@code beta=0}.
1449 *
1450 * @param beta beta (should be close or equal to zero to allow direction changes due to the
1451 * angle phi/2 to be detected)
1452 */
1453 private static void testSamplesWithDInfinite(double beta) {
1454 // Set-up the random deviate u to be close to -pi/4 (low), pi/4 (high) and 0.5.
1455 // The extreme values for u create terms during error correction that are infinite.
1456 final long xuLo = Long.MIN_VALUE + (1024 << 10);
1457 final long xuHi = Long.MAX_VALUE - (1023 << 10);
1458 // Call sampler with smallest possible w that is not 0. This creates a finite z
1459 // but an infinite d due to the use of alpha -> 0.
1460 final long x = 3L;
1461 final long[] longs = {xuLo, x, xuHi, x, 0, x};
1462
1463 assertUWSequence(new double[] {
1464 -PI_4 + 1024 * DU * PI_4, SMALL_W,
1465 PI_4 - 1024 * DU * PI_4, SMALL_W,
1466 0.0, SMALL_W
1467 }, longs);
1468
1469 // alpha must be small to create infinite d and beta with low skew
1470 // to allow the direction switch in phi/2 to create opposite directions.
1471 // If the skew is too large then the skew dominates the direction.
1472 // When u=0.5 then f=0 and a standard sum of (1 + eps * d) * f + tau * d
1473 // with d=inf would cause inf * 0 = NaN.
1474 final double alpha = 0.03;
1475 final StableSampler sampler = StableSampler.of(createRngWithSequence(longs), alpha, beta);
1476 final double x1 = sampler.sample();
1477 final double x2 = sampler.sample();
1478 final double x3 = sampler.sample();
1479 // Expect the limit of the support (the direction is controlled by extreme phi)
1480 final double max = Double.POSITIVE_INFINITY;
1481 Assertions.assertEquals(-max, x1);
1482 Assertions.assertEquals(max, x2);
1483 // Expect the sampler to avoid inf * 0
1484 Assertions.assertNotEquals(Double.NaN, x3);
1485 // When f=0 the sample should be in the middle (beta=0) or skewed in the direction of beta
1486 if (beta == 0) {
1487 // In the middle
1488 Assertions.assertEquals(0.0, x3);
1489 } else {
1490 // At the support limit
1491 Assertions.assertEquals(Math.copySign(max, beta), x3);
1492 }
1493 }
1494
1495 /**
1496 * Test the {@code alpha=1} CMS sampler when the random generator outputs create
1497 * deviates that cause the value {@code phi/2} to be at the extreme limits.
1498 */
1499 @Test
1500 void testAlpha1SamplesWithExtremePhi() {
1501 // The numerator is:
1502 // 1 + 2 * phiby2 * tau
1503 // tau = beta / pi/2 when alpha=1
1504 // = +/-2 / pi when alpha=1, beta = +/-1
1505 // This should not create zero if phi/2 is not pi/4.
1506 // Test the limits of phi/2 to check samples are finite.
1507
1508 // Add a long to create an ordinary value for w of 1.0.
1509 // u -> -pi/4
1510 final long[] longs1 = {Long.MIN_VALUE + (1 << 10), 2703662416942444033L};
1511 assertUWSequence(new double[] {
1512 -PI_4 + PI_4 * DU, 1.0,
1513 }, longs1);
1514 final StableSampler sampler1 = StableSampler.of(createRngWithSequence(longs1), 1.0, 1.0);
1515 final double x1 = sampler1.sample();
1516 Assertions.assertTrue(Double.isFinite(x1), "Sampler did not recover");
1517
1518 // u -> pi/4
1519 final long[] longs2 = {Long.MAX_VALUE, 2703662416942444033L};
1520 assertUWSequence(new double[] {
1521 PI_4 - PI_4 * DU, 1.0,
1522 }, longs2);
1523 final StableSampler sampler2 = StableSampler.of(createRngWithSequence(longs2), 1.0, -1.0);
1524 final double x2 = sampler2.sample();
1525 Assertions.assertTrue(Double.isFinite(x2), "Sampler did not recover");
1526
1527 // Sample should be a reflection
1528 Assertions.assertEquals(x1, -x2);
1529 }
1530
1531 /**
1532 * Test the support of the distribution when {@code gamma = 1} and
1533 * {@code delta = 0}. A non-infinite support applies when {@code alpha < 0} and
1534 * {@code |beta| = 1}.
1535 */
1536 @Test
1537 void testSupport() {
1538 testSupport(1.0, 0.0);
1539 }
1540
1541 /**
1542 * Test the support of the distribution when {@code gamma != 1} and
1543 * {@code delta != 0}. A non-infinite support applies when {@code alpha < 0} and
1544 * {@code |beta| = 1}.
1545 */
1546 @Test
1547 void testSupportWithTransformation() {
1548 // This tests extreme values which should not create NaN results
1549 for (final double gamma : new double[] {0.78, 1.23, Double.MAX_VALUE, Double.MIN_VALUE}) {
1550 for (final double delta : new double[] {0.43, 12.34, Double.MAX_VALUE}) {
1551 testSupport(gamma, delta);
1552 testSupport(gamma, -delta);
1553 }
1554 }
1555 }
1556
1557 /**
1558 * Test the support of the distribution. This applies when {@code alpha < 0} and
1559 * {@code |beta| = 1}.
1560 *
1561 * @param alpha Stability parameter. Must be in range {@code (0, 2]}.
1562 * @param beta Skewness parameter. Must be in range {@code [-1, 1]}.
1563 * @param gamma Scale parameter. Must be strictly positive and finite.
1564 * @param delta Location parameter. Must be finite.
1565 */
1566 private static void testSupport(double gamma, double delta) {
1567 // When alpha is small (<=0.1) the computation becomes limited by floating-point precision.
1568 final double[] alphas = {2.0, 1.5, 1.0, Math.nextDown(1), 0.99, 0.75, 0.5, 0.25, 0.1, 0.01};
1569 for (final double alpha : alphas) {
1570 testSupport(alpha, 1, gamma, delta);
1571 testSupport(alpha, -1, gamma, delta);
1572 }
1573 }
1574 /**
1575 * Test the support of the distribution. This applies when {@code alpha < 0} and
1576 * {@code |beta| = 1}.
1577 *
1578 * @param alpha Stability parameter. Must be in range {@code (0, 2]}.
1579 * @param beta Skewness parameter. Must be in range {@code [-1, 1]}.
1580 * @param gamma Scale parameter. Must be strictly positive and finite.
1581 * @param delta Location parameter. Must be finite.
1582 */
1583 private static void testSupport(double alpha, double beta, double gamma, double delta) {
1584 // This is the inclusive bounds (no infinite values)
1585 final double[] support = getSupport(alpha, beta);
1586 // Do not scale the max value. It acts as an effective infinity.
1587 double lower;
1588 if (support[0] == -Double.MAX_VALUE) {
1589 lower = Double.NEGATIVE_INFINITY;
1590 } else {
1591 lower = support[0] * gamma + delta;
1592 }
1593 double upper;
1594 if (support[1] == Double.MAX_VALUE) {
1595 upper = Double.POSITIVE_INFINITY;
1596 } else {
1597 upper = support[1] * gamma + delta;
1598 }
1599 // Create an RNG that will generate extreme values:
1600 // Here we use 4 recursions into the tail of the exponential. The large exponential
1601 // deviate is approximately 30.3.
1602 final long[] longs = new long[] {
1603 // Note: Add a long of Long.MIN_VALUE to test the sampler ignores this value.
1604 // Hits edge case for generation of phi/4 in (-pi/4, pi/4)
1605 Long.MIN_VALUE,
1606
1607 // phi/2 -> -pi/4, w=0
1608 Long.MIN_VALUE + (1 << 10), 0,
1609 // phi/2 -> -pi/4, w=large
1610 Long.MIN_VALUE + (1 << 10), -1, -1, -1, -1, -1, -1, -1, -1, 0,
1611 // phi/2 -> pi/4, w=0
1612 Long.MAX_VALUE, 0,
1613 // phi/2 -> pi/4, w=large
1614 Long.MAX_VALUE, -1, -1, -1, -1, -1, -1, -1, -1, 0,
1615 // phi/2=0, w=0
1616 0, 0,
1617 // phi/2=0, w=inf
1618 0, -1, -1, -1, -1, -1, -1, -1, -1, 0,
1619
1620 // Add non extreme exponential deviate to test only extreme u
1621 // phi/2 -> -pi/4, w=1
1622 Long.MIN_VALUE + (1 << 10), 2703662416942444033L,
1623 // phi/2 -> pi/4, w=1
1624 Long.MAX_VALUE, 2703662416942444033L,
1625 // phi/2=0, w=1
1626 0, 2703662416942444033L,
1627
1628 // Add non extreme uniform deviate to test only extreme w
1629 // phi/2=pi/5, w=0
1630 Long.MIN_VALUE >> 1, 0,
1631 // phi/2=pi/5, w=large
1632 Long.MIN_VALUE >> 1, -1, -1, -1, -1, -1, -1, -1, -1, 0,
1633 // phi/2=pi/5, w=0
1634 Long.MIN_VALUE >>> 1, 0,
1635 // phi/2=pi/5, w=large
1636 Long.MIN_VALUE >>> 1, -1, -1, -1, -1, -1, -1, -1, -1, 0,
1637 };
1638
1639 // Validate series
1640 final double phiby2low = -PI_4 + PI_4 * DU;
1641 final double phiby2high = PI_4 - PI_4 * DU;
1642 assertUWSequence(new double[] {
1643 phiby2low, 0,
1644 phiby2low, LARGE_W,
1645 phiby2high, 0,
1646 phiby2high, LARGE_W,
1647 0, 0,
1648 0, LARGE_W,
1649 phiby2low, 1.0,
1650 phiby2high, 1.0,
1651 0, 1.0,
1652 -PI_4 / 2, 0,
1653 -PI_4 / 2, LARGE_W,
1654 PI_4 / 2, 0,
1655 PI_4 / 2, LARGE_W,
1656 }, longs);
1657
1658 final StableSampler sampler = StableSampler.of(
1659 createRngWithSequence(longs), alpha, beta, gamma, delta);
1660 for (int i = 0; i < 100; i++) {
1661 final double x = sampler.sample();
1662 if (!(lower <= x && x <= upper)) {
1663 Assertions.fail(String.format("Invalid sample. alpha=%s, beta=%s, gamma=%s, delta=%s [%s, %s] x=%s",
1664 alpha, beta, gamma, delta, lower, upper, x));
1665 }
1666 }
1667 }
1668
1669 /**
1670 * Gets the support of the distribution. This returns the inclusive bounds. So exclusive
1671 * infinity is computed as the maximum finite value. Compute the value {@code mu} which is the
1672 * mean of the distribution when {@code alpha > 1}.
1673 *
1674 * <pre>
1675 * x in [mu, +inf) if alpha < 1, beta = 1
1676 * x in (-inf, mu] if alpha < 1, beta = -1
1677 * x in (-inf, -inf) otherwise
1678 * </pre>
1679 *
1680 * @param alpha the alpha
1681 * @param beta the beta
1682 * @return the support ({lower, upper, mu})
1683 */
1684 private static double[] getSupport(double alpha, double beta) {
1685 // Convert alpha as used by the sampler
1686 double eps = 1 - alpha;
1687 double meps1 = 1 - eps;
1688
1689 // Since pi is approximate the symmetry is lost by wrapping.
1690 // Keep within the domain using (2-alpha).
1691 double mu;
1692 if (alpha > 1) {
1693 mu = beta * Math.tan((2 - meps1) * PI_2);
1694 } else {
1695 // Special case where tan(pi/4) is not 1 (it is Math.nextDown(1.0)).
1696 // This is needed when testing the Levy case during sampling.
1697 if (alpha == 0.5) {
1698 mu = -beta;
1699 } else {
1700 mu = -beta * Math.tan(meps1 * PI_2);
1701 }
1702 }
1703
1704 // Standard support
1705 double lower = -Double.MAX_VALUE;
1706 double upper = Double.MAX_VALUE;
1707 if (meps1 < 1) {
1708 if (beta == 1) {
1709 // alpha < 0, beta = 1
1710 lower = mu;
1711 } else if (beta == -1) {
1712 // alpha < 0, beta = -1
1713 upper = mu;
1714 }
1715 }
1716 return new double[] {lower, upper, mu};
1717 }
1718
1719 /**
1720 * Assumption test:
1721 * Test the random deviates u and w can be generated by manipulating the RNG.
1722 */
1723 @Test
1724 void testRandomDeviatesUandW() {
1725 // Extremes of the uniform deviate generated using the same method as the sampler
1726 final double d = DU * PI_4;
1727 // Test in (-pi/4, pi/4)
1728 Assertions.assertNotEquals(-PI_4, getU(createRngWithSequence(Long.MIN_VALUE)));
1729 Assertions.assertEquals(-PI_4 + d, getU(createRngWithSequence(Long.MIN_VALUE + (1 << 10))));
1730 Assertions.assertEquals(-PI_4 / 2, getU(createRngWithSequence(Long.MIN_VALUE >> 1)));
1731 Assertions.assertEquals(-d, getU(createRngWithSequence(-1)));
1732 Assertions.assertEquals(0.0, getU(createRngWithSequence(0)));
1733 Assertions.assertEquals(d, getU(createRngWithSequence(1 << 10)));
1734 Assertions.assertEquals(PI_4 / 2, getU(createRngWithSequence(Long.MIN_VALUE >>> 1)));
1735 Assertions.assertEquals(PI_4 - d, getU(createRngWithSequence(Long.MAX_VALUE)));
1736
1737 // Extremes of the exponential sampler
1738 Assertions.assertEquals(0, ZigguratSampler.Exponential.of(
1739 createRngWithSequence(0L)).sample());
1740 Assertions.assertEquals(SMALL_W, ZigguratSampler.Exponential.of(
1741 createRngWithSequence(3)).sample());
1742 Assertions.assertEquals(0.5, ZigguratSampler.Exponential.of(
1743 createRngWithSequence(1446480648965178882L)).sample());
1744 Assertions.assertEquals(1.0, ZigguratSampler.Exponential.of(
1745 createRngWithSequence(2703662416942444033L)).sample());
1746 Assertions.assertEquals(2.5, ZigguratSampler.Exponential.of(
1747 createRngWithSequence(6092639261715210240L)).sample());
1748 Assertions.assertEquals(5.0, ZigguratSampler.Exponential.of(
1749 createRngWithSequence(-6261465550279131136L)).sample());
1750 Assertions.assertEquals(TAIL_W, ZigguratSampler.Exponential.of(
1751 createRngWithSequence(-1, -1, 0)).sample());
1752 Assertions.assertEquals(3 * TAIL_W, ZigguratSampler.Exponential.of(
1753 createRngWithSequence(-1, -1, -1, -1, -1, -1, 0)).sample(), 1e-14);
1754 }
1755
1756 /**
1757 * Gets a uniform random variable in {@code (-pi/4, pi/4)}.
1758 *
1759 * <p>Copied from the StableSampler for testing. In the main sampler the variable u
1760 * is named either {@code phi} in {@code (-pi/2, pi/2)}, or
1761 * {@code phiby2} in {@code (-pi/4, pi/4)}. Here we test phiby2 for the CMS algorithm.
1762 *
1763 * @return u
1764 */
1765 private static double getU(UniformRandomProvider rng) {
1766 final double x = getU(rng.nextLong());
1767 if (x == -PI_4) {
1768 return getU(rng);
1769 }
1770 return x;
1771 }
1772
1773 /**
1774 * Gets a uniform random variable in {@code [-pi/4, pi/4)} from a long value.
1775 *
1776 * <p>Copied from the StableSampler for testing. In the main sampler the variable u
1777 * is named either {@code phi} in {@code (-pi/2, pi/2)}, or
1778 * {@code phiby2} in {@code (-pi/4, pi/4)}. Here we test phiby2 for the CMS algorithm.
1779 *
1780 * <p>Examples of different output where {@code d} is the gap between values of {@code phi/2}
1781 * and is equal to {@code pi * 2^-55 = pi/4 * 2^-53}:
1782 *
1783 * <pre>
1784 * Long.MIN_VALUE -pi/4
1785 * Long.MIN_VALUE + (1 << 10) -pi/4 + d
1786 * Long.MIN_VALUE >> 1 -pi/5
1787 * -1 -d
1788 * 0 0.0
1789 * 1 << 10 d
1790 * Long.MIN_VALUE >>> 1 pi/5
1791 * Long.MAX_VALUE pi/4 - d
1792 * </pre>
1793 *
1794 * @return u
1795 */
1796 private static double getU(long x) {
1797 return (x >> 10) * PI_4_SCALED;
1798 }
1799
1800 /**
1801 * Creates a RNG that will return the provided output for the next double and
1802 * next long functions. When the sequence is complete a valid random output
1803 * continues.
1804 *
1805 * <p>The sampler generates (in order):
1806 * <ol>
1807 * <li>{@code phi/2} in {@code (-pi/4, pi/4)} using long values from the RNG.
1808 * <li>{@code w} using the {@link ZigguratSampler.Exponential}.
1809 * This uses a long values from the RNG.
1810 * </ol>
1811 *
1812 * <p>Careful control of the sequence can generate any value for {@code w} and {@code phi/2}.
1813 * The sampler creates a uniform deviate first, then an exponential deviate second.
1814 * Examples of different output where {@code d} is the gap between values of {@code phi/2}
1815 * and is equal to {@code pi * 2^-55 = pi/4 * 2^-53}:
1816 *
1817 * <pre>
1818 * longs phi/2 w
1819 * Long.MIN_VALUE try again [1]
1820 * Long.MIN_VALUE + (1 << 10) -pi/4 + d
1821 * Long.MIN_VALUE >> 1 -pi/5
1822 * -1 -d
1823 * 0 0.0 0
1824 * 1 << 10 d
1825 * Long.MIN_VALUE >>> 1 pi/5
1826 * Long.MAX_VALUE pi/4 - d
1827 * 3 6.564735882096453E-19
1828 * 1446480648965178882L 0.5
1829 * 2703662416942444033L 1.0
1830 * 6092639261715210240L 2.5
1831 * -6261465550279131136L 5.0
1832 * -1, -1, 0 7.569274694148063
1833 * -1L * 2n, 0 n * 7.569274694148063 [2]
1834 * </pre>
1835 *
1836 * <ol>
1837 * <li>When phi/2=-pi/4 the method will ignore the value and obtain another long value.
1838 * <li>To create a large value for the exponential sampler requires recursion. Each input
1839 * of 2 * -1L will add 7.569274694148063 to the total. A long of zero will stop recursion.
1840 * </ol>
1841 *
1842 * @param longs the initial sequence of longs
1843 * @return the uniform random provider
1844 */
1845 private static UniformRandomProvider createRngWithSequence(final long... longs) {
1846 // Note:
1847 // The StableSampler uniform deviate is generated from a long.
1848 // It is ignored if zero, a value of 1 << 11 generates the smallest value (2^-53).
1849 //
1850 // The ZigguratSampler.Exponential uses a single long value >98% of the time.
1851 // To create a certain value x the input y can be obtained by reversing the
1852 // computation of the corresponding precomputed factor X. The lowest 8 bits of y
1853 // choose the index i into X so must be set as the lowest bits.
1854 // The long is shifted right 1 before multiplying by X so this must be reversed.
1855 //
1856 // To find y to obtain the sample x use:
1857 // double[] X = { /* from ZigguratSampler.Exponential */ }
1858 // double x = 1.0; // or any other value < 7.5
1859 // for (int i = 0; i < X.length; i++) {
1860 // // Add back the index to the lowest 8 bits.
1861 // // This will work if the number is so big that the lower bits
1862 // // are zerod when casting the 53-bit mantissa to a long.
1863 // long y = ((long) (x / X[i]) << 1) + i;
1864 // if ((y >>> 1) * X[i] == x) {
1865 // // Found y!
1866 // }
1867 // }
1868
1869 // Start with a valid RNG.
1870 // This is required for nextDouble() since invoking super.nextDouble() when
1871 // the sequence has expired will call nextLong() and may use the intended
1872 // sequence of longs.
1873 final UniformRandomProvider rng = RandomAssert.seededRNG();
1874
1875 // A RNG with the provided output
1876 return new SplitMix64(0L) {
1877 private int l;
1878
1879 @Override
1880 public long nextLong() {
1881 if (l == longs.length) {
1882 return rng.nextLong();
1883 }
1884 return longs[l++];
1885 }
1886 };
1887 }
1888
1889 /**
1890 * Assert the sequence of output from a uniform deviate and exponential deviate
1891 * created using the same method as the sampler.
1892 *
1893 * <p>The RNG is created using
1894 * {@link #createRngWithSequence(long[])}. See the method javadoc for
1895 * examples of how to generate different deviate values.
1896 *
1897 * @param expected the expected output (u1, w1, u2, w2, u3, w3, ...)
1898 * @param longs the initial sequence of longs
1899 */
1900 private static void assertUWSequence(double[] expected, long[] longs) {
1901 final UniformRandomProvider rng = createRngWithSequence(longs);
1902
1903 // Validate series
1904 final SharedStateContinuousSampler exp = ZigguratSampler.Exponential.of(rng);
1905 for (int i = 0; i < expected.length; i += 2) {
1906 final int j = i / 2;
1907 Assertions.assertEquals(expected[i], getU(rng), () -> j + ": Incorrect u");
1908 if (i + 1 < expected.length) {
1909 Assertions.assertEquals(expected[i + 1], exp.sample(), () -> j + ": Incorrect w");
1910 }
1911 }
1912 }
1913
1914 /**
1915 * Test the sampler output is a continuous function of {@code alpha} and {@code beta}.
1916 * This test verifies the switch to the dedicated {@code alpha=1} or {@code beta=0}
1917 * samplers computes a continuous function of the parameters.
1918 */
1919 @Test
1920 void testSamplerOutputIsContinuousFunction() {
1921 // Test alpha passing through 1 when beta!=0 (switch to an alpha=1 sampler)
1922 for (final double beta : new double[] {0.5, 0.2, 0.1, 0.001}) {
1923 testSamplerOutputIsContinuousFunction(1 + 8096 * DU, beta, 1.0, beta, 1 - 8096 * DU, beta, 0);
1924 testSamplerOutputIsContinuousFunction(1 + 1024 * DU, beta, 1.0, beta, 1 - 1024 * DU, beta, 1);
1925 // Not perfect when alpha -> 1
1926 testSamplerOutputIsContinuousFunction(1 + 128 * DU, beta, 1.0, beta, 1 - 128 * DU, beta, 1);
1927 testSamplerOutputIsContinuousFunction(1 + 16 * DU, beta, 1.0, beta, 1 - 16 * DU, beta, 4);
1928 // This works with ulp=0. Either this is a lucky random seed or because the approach
1929 // to 1 creates equal output.
1930 testSamplerOutputIsContinuousFunction(1 + DU, beta, 1.0, beta, 1 - DU, beta, 0);
1931 }
1932 // Test beta passing through 0 when alpha!=1 (switch to a beta=0 sampler)
1933 for (final double alpha : new double[] {1.5, 1.2, 1.1, 1.001}) {
1934 testSamplerOutputIsContinuousFunction(alpha, 8096 * DU, alpha, 0, alpha, -8096 * DU, 0);
1935 testSamplerOutputIsContinuousFunction(alpha, 1024 * DU, alpha, 0, alpha, -1024 * DU, 0);
1936 testSamplerOutputIsContinuousFunction(alpha, 128 * DU, alpha, 0, alpha, -128 * DU, 1);
1937 // Not perfect when beta is very small
1938 testSamplerOutputIsContinuousFunction(alpha, 16 * DU, alpha, 0, alpha, -16 * DU, 64);
1939 testSamplerOutputIsContinuousFunction(alpha, DU, alpha, 0, alpha, -DU, 4);
1940 }
1941
1942 // Note: No test for transition to the Cauchy case (alpha=1, beta=0).
1943 // Requires a RNG that discards output that would be used to create a exponential
1944 // deviate. Just create one each time a request for nextLong is performed and
1945 // ensure nextLong >>> 11 is not zero.
1946
1947 // When the parameters create a special case sampler this will not work.
1948 // alpha passing through 0.5 when beta=1 (Levy sampler)
1949 // alpha -> 2 as the sampler (Gaussian sampler).
1950 }
1951
1952 /**
1953 * Test sampler output is a continuous function of {@code alpha} and
1954 * {@code beta}. Create 3 samplers with the same RNG and test the middle sampler
1955 * computes a value between the upper and lower sampler.
1956 *
1957 * @param alpha1 lower sampler alpha
1958 * @param beta1 lower sampler beta
1959 * @param alpha2 middle sampler alpha
1960 * @param beta2 middle sampler beta
1961 * @param alpha3 upper sampler alpha
1962 * @param beta3 upper sampler beta
1963 */
1964 private static void testSamplerOutputIsContinuousFunction(double alpha1, double beta1,
1965 double alpha2, double beta2,
1966 double alpha3, double beta3,
1967 int ulp) {
1968 final long seed = 0x62738468L;
1969 final UniformRandomProvider rng1 = RandomSource.XO_RO_SHI_RO_128_PP.create(seed);
1970 final UniformRandomProvider rng2 = RandomSource.XO_RO_SHI_RO_128_PP.create(seed);
1971 final UniformRandomProvider rng3 = RandomSource.XO_RO_SHI_RO_128_PP.create(seed);
1972 final StableSampler sampler1 = StableSampler.of(rng1, alpha1, beta1);
1973 final StableSampler sampler2 = StableSampler.of(rng2, alpha2, beta2);
1974 final StableSampler sampler3 = StableSampler.of(rng3, alpha3, beta3);
1975 final Supplier<String> msg = () -> String.format("alpha=%s, beta=%s", alpha2, beta2);
1976 for (int i = 0; i < 1000; i++) {
1977 final double x1 = sampler1.sample();
1978 final double x2 = sampler2.sample();
1979 final double x3 = sampler3.sample();
1980 // x2 should be in between x1 and x3
1981 if (x3 > x1) {
1982 if (x2 > x3) {
1983 // Should be the same
1984 Assertions.assertEquals(x3, x2, ulp * Math.ulp(x3), msg);
1985 } else if (x2 < x1) {
1986 Assertions.assertEquals(x1, x2, ulp * Math.ulp(x1), msg);
1987 }
1988 } else if (x3 < x1) {
1989 if (x2 < x3) {
1990 // Should be the same
1991 Assertions.assertEquals(x3, x2, ulp * Math.ulp(x3), msg);
1992 } else if (x2 > x1) {
1993 Assertions.assertEquals(x1, x2, ulp * Math.ulp(x1), msg);
1994 }
1995 }
1996 }
1997 }
1998
1999 /**
2000 * Test the SharedStateSampler implementation for each case using a different implementation.
2001 */
2002 @Test
2003 void testSharedStateSampler() {
2004 // Gaussian case
2005 testSharedStateSampler(2.0, 0.0);
2006 // Cauchy case
2007 testSharedStateSampler(1.0, 0.0);
2008 // Levy case
2009 testSharedStateSampler(0.5, 1.0);
2010 // Hit code coverage of alpha=0.5 (Levy case) but beta != 1
2011 testSharedStateSampler(0.5, 0.1);
2012 // Beta 0 (symmetric) case
2013 testSharedStateSampler(1.3, 0.0);
2014 // Alpha 1 case
2015 testSharedStateSampler(1.0, 0.23);
2016 // Alpha close to 1
2017 testSharedStateSampler(Math.nextUp(1.0), 0.23);
2018 // General case
2019 testSharedStateSampler(1.3, 0.1);
2020 // Small alpha cases
2021 testSharedStateSampler(1e-5, 0.1);
2022 testSharedStateSampler(1e-5, 0.0);
2023 // Large alpha case.
2024 // This hits code coverage for computing tau from (1-alpha) -> -1
2025 testSharedStateSampler(1.99, 0.1);
2026 }
2027
2028 /**
2029 * Test the SharedStateSampler implementation. This tests with and without the
2030 * {@code gamma} and {@code delta} parameters.
2031 *
2032 * @param alpha Alpha.
2033 * @param beta Beta.
2034 */
2035 private static void testSharedStateSampler(double alpha, double beta) {
2036 final UniformRandomProvider rng1 = RandomAssert.seededRNG();
2037 final UniformRandomProvider rng2 = RandomAssert.seededRNG();
2038 StableSampler sampler1 = StableSampler.of(rng1, alpha, beta);
2039 StableSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
2040 RandomAssert.assertProduceSameSequence(sampler1, sampler2);
2041 // Test shifted
2042 sampler1 = StableSampler.of(rng1, alpha, beta, 1.3, 13.2);
2043 sampler2 = sampler1.withUniformRandomProvider(rng2);
2044 RandomAssert.assertProduceSameSequence(sampler1, sampler2);
2045 }
2046
2047 /**
2048 * Test the implementation of the transformed sampler (scaled and translated).
2049 */
2050 @Test
2051 void testTransformedSampler() {
2052 // Gaussian case
2053 // The Gaussian case has its own scaling where the StdDev is gamma * sqrt(2).
2054 // (N(x) * sqrt(2)) * gamma != N(x) * (sqrt(2) * gamma)
2055 // Test with a delta
2056 testTransformedSampler(2.0, 0.0, 1);
2057 // Cauchy case
2058 testTransformedSampler(1.0, 0.0);
2059 // Levy case
2060 testTransformedSampler(0.5, 1.0);
2061 // Symmetric case
2062 testTransformedSampler(1.3, 0.0);
2063 // Alpha 1 case
2064 testTransformedSampler(1.0, 0.23);
2065 // Alpha close to 1
2066 testTransformedSampler(Math.nextUp(1.0), 0.23);
2067 // General case
2068 testTransformedSampler(1.3, 0.1);
2069 // Small alpha case
2070 testTransformedSampler(1e-5, 0.1);
2071 // Large alpha case.
2072 // This hits the case for computing tau from (1-alpha) -> -1.
2073 testTransformedSampler(1.99, 0.1);
2074 }
2075
2076 /**
2077 * Test the implementation of the transformed sampler (scaled and translated).
2078 * The transformed output must match exactly.
2079 *
2080 * @param alpha Alpha.
2081 * @param beta Beta.
2082 */
2083 private static void testTransformedSampler(double alpha, double beta) {
2084 testTransformedSampler(alpha, beta, 0);
2085 }
2086
2087 /**
2088 * Test the implementation of the transformed sampler (scaled and translated).
2089 * The transformed output must match within the provided ULP.
2090 *
2091 * @param alpha Alpha.
2092 * @param beta Beta.
2093 * @param ulp Allowed ULP difference.
2094 */
2095 private static void testTransformedSampler(double alpha, double beta, int ulp) {
2096 final UniformRandomProvider[] rngs = RandomAssert.createRNG(2);
2097 final UniformRandomProvider rng1 = rngs[0];
2098 final UniformRandomProvider rng2 = rngs[1];
2099 final double gamma = 3.4;
2100 final double delta = -17.3;
2101 final StableSampler sampler1 = StableSampler.of(rng1, alpha, beta, gamma, delta);
2102 final ContinuousSampler sampler2 = createTransformedSampler(rng2, alpha, beta, gamma, delta);
2103 if (ulp == 0) {
2104 RandomAssert.assertProduceSameSequence(sampler1, sampler2);
2105 } else {
2106 for (int i = 0; i < 10; i++) {
2107 final double x1 = sampler1.sample();
2108 final double x2 = sampler2.sample();
2109 Assertions.assertEquals(x1, x2, ulp * Math.ulp(x1));
2110 }
2111 }
2112 }
2113
2114 /**
2115 * Create a transformed sampler from a normalized sampler scaled and translated by
2116 * gamma and delta.
2117 *
2118 * @param rng Source of randomness.
2119 * @param alpha Alpha.
2120 * @param beta Beta.
2121 * @param gamma Gamma.
2122 * @param delta Delta.
2123 * @return the transformed sampler
2124 */
2125 private static ContinuousSampler createTransformedSampler(UniformRandomProvider rng,
2126 double alpha, double beta,
2127 final double gamma, final double delta) {
2128 final StableSampler delegate = StableSampler.of(rng, alpha, beta);
2129 return new ContinuousSampler() {
2130 @Override
2131 public double sample() {
2132 return gamma * delegate.sample() + delta;
2133 }
2134 };
2135 }
2136
2137 /**
2138 * Test symmetry when when u and beta are mirrored around 0.5 and 0 respectively.
2139 */
2140 @Test
2141 void testSymmetry() {
2142 final byte[] seed = RandomSource.KISS.createSeed();
2143 for (final double alpha : new double[] {1e-4, 0.78, 1, 1.23}) {
2144 for (final double beta : new double[] {-0.43, 0.23}) {
2145 for (final double gamma : new double[] {0.78, 1, 1.23}) {
2146 for (final double delta : new double[] {-0.43, 0, 0.23}) {
2147 // The sampler generates u then w.
2148 // If u is not -pi/4 then only a single long is used.
2149 // This can be reversed around 0 by reversing the upper 54-bits.
2150 // w will use 1 long only for fast lookup and then additional longs
2151 // for edge of the ziggurat sampling. Fast look-up is always used
2152 // when the lowest 8-bits create a value below 252.
2153
2154 // Use the same random source for two samplers.
2155 final UniformRandomProvider rng1 = RandomSource.KISS.create(seed);
2156 final UniformRandomProvider rng2 = RandomSource.KISS.create(seed);
2157
2158 // RNG which will not return 0 for every other long.
2159 final UniformRandomProvider forward = new SplitMix64(0) {
2160 private int i;
2161 @Override
2162 public long nextLong() {
2163 // Manipulate alternate longs
2164 if ((i++ & 0x1) == 0) {
2165 // This must not be Long.MIN_VALUE.
2166 // So set the lowest bit of the upper 54-bits.
2167 final long x = rng1.nextLong() >>> 10 | 1L;
2168 // Shift back
2169 return x << 10;
2170 }
2171 // For the exponential sample ensure the lowest 8-bits are < 252.
2172 long x;
2173 do {
2174 x = rng1.nextLong();
2175 } while ((x & 0xff) >= 252);
2176 return x;
2177 }
2178 };
2179
2180 // RNG which will not return 0 for every other long but this long is reversed.
2181 final UniformRandomProvider reverse = new SplitMix64(0) {
2182 private final long upper = 1L << 54;
2183 private int i;
2184 @Override
2185 public long nextLong() {
2186 // Manipulate alternate longs
2187 if ((i++ & 0x1) == 0) {
2188 // This must not be Long.MIN_VALUE.
2189 // So set the lowest bit of the upper 54-bits.
2190 final long x = rng2.nextLong() >>> 10 | 1L;
2191 // Reverse then shift back
2192 return (upper - x) << 10;
2193 }
2194 // For the exponential sample ensure the lowest 8-bits are < 252.
2195 long x;
2196 do {
2197 x = rng2.nextLong();
2198 } while ((x & 0xff) >= 252);
2199 return x;
2200 }
2201 };
2202
2203 final StableSampler s1 = StableSampler.of(forward, alpha, beta, gamma, delta);
2204 // Since mirroring applies before the shift of delta this must be negated too
2205 final StableSampler s2 = StableSampler.of(reverse, alpha, -beta, gamma, -delta);
2206 for (int i = 0; i < 100; i++) {
2207 Assertions.assertEquals(s1.sample(), -s2.sample());
2208 }
2209 }
2210 }
2211 }
2212 }
2213 }
2214
2215 /**
2216 * Test symmetry for the Levy case ({@code alpha = 0.5} and {@code beta = 1}.
2217 */
2218 @Test
2219 void testSymmetryLevy() {
2220 final double alpha = 0.5;
2221 final double beta = 1.0;
2222 final byte[] seed = RandomSource.KISS.createSeed();
2223 final UniformRandomProvider rng1 = RandomSource.KISS.create(seed);
2224 final UniformRandomProvider rng2 = RandomSource.KISS.create(seed);
2225 for (final double gamma : new double[] {0.78, 1, 1.23}) {
2226 for (final double delta : new double[] {-0.43, 0, 0.23}) {
2227 final StableSampler s1 = StableSampler.of(rng1, alpha, beta, gamma, delta);
2228 // Since mirroring applies before the shift of delta this must be negated too
2229 final StableSampler s2 = StableSampler.of(rng2, alpha, -beta, gamma, -delta);
2230 for (int i = 0; i < 100; i++) {
2231 Assertions.assertEquals(s1.sample(), -s2.sample());
2232 }
2233 }
2234 }
2235 }
2236
2237 /**
2238 * Test the toString method for cases not hit in the rest of the test suite.
2239 * This test asserts the toString method always contains the string 'stable'
2240 * even for parameters that create the Gaussian, Cauchy or Levy cases.
2241 */
2242 @Test
2243 void testToString() {
2244 final UniformRandomProvider rng = RandomAssert.seededRNG();
2245 for (final double[] p : new double[][] {
2246 {1.3, 0.1},
2247 {2.0, 0.0},
2248 {1.0, 0.0},
2249 {0.5, 1.0},
2250 {1e-5, 0},
2251 {1e-5, 0.1},
2252 {0.7, 0.1, 3.0, 4.5},
2253 }) {
2254 StableSampler sampler;
2255 if (p.length == 2) {
2256 sampler = StableSampler.of(rng, p[0], p[1]);
2257 } else {
2258 sampler = StableSampler.of(rng, p[0], p[1], p[2], p[3]);
2259 }
2260 final String s = sampler.toString().toLowerCase();
2261 Assertions.assertTrue(s.contains("stable"));
2262 }
2263 }
2264
2265 /**
2266 * Demonstrate the CMS sampler matches the Weron sampler when {@code alpha != 1}.
2267 * This shows the two are equivalent; they should match as the formulas are rearrangements.
2268 * Avoid testing as {@code alpha -> 1} as the Weron sampler loses bits of precision in
2269 * the output sample.
2270 *
2271 * <p>Note: Uses direct instantiation via the package-private constructors. This avoids
2272 * the factory method constructor to directly select the implementation. Constructor
2273 * parameters are not validated.
2274 */
2275 @Test
2276 void testImplementationsMatch() {
2277 // Avoid extreme samples. Do this by manipulating the output of nextLong.
2278 // Generation of the random deviate u uses the top 54-bits of the long.
2279 // Unset a high bit to ensure getU cannot approach pi/4.
2280 // Set a low bit to ensure getU cannot approach -pi/4.
2281 final long unsetHighBit = ~(1L << 54);
2282 final long setLowBit = 1L << 53;
2283 final double hi = getU(Long.MAX_VALUE & unsetHighBit);
2284 final double lo = getU(Long.MIN_VALUE | setLowBit);
2285 // The limits are roughly pi/4 and -pi/4
2286 Assertions.assertEquals(PI_4, hi, 2e-3);
2287 Assertions.assertEquals(-PI_4, lo, 2e-3);
2288 Assertions.assertEquals(0.0, lo + hi, 1e-3);
2289
2290 // Setting a bit ensure the exponential sampler cannot be zero
2291 final UniformRandomProvider rng = createRngWithSequence(setLowBit);
2292 final double w = ZigguratSampler.Exponential.of(rng).sample();
2293 Assertions.assertNotEquals(0.0, w);
2294 // This is the actual value; it is small but not extreme.
2295 Assertions.assertEquals(0.0036959349092519837, w);
2296
2297 final RandomSource source = RandomSource.XO_RO_SHI_RO_128_SS;
2298 final long seed = 0x83762b3daf1c43L;
2299 final UniformRandomProvider rng1 = new SplitMix64(0L) {
2300 private UniformRandomProvider delegate = source.create(seed);
2301 @Override
2302 public long next() {
2303 final long x = delegate.nextLong();
2304 return (x & unsetHighBit) | setLowBit;
2305 }
2306 };
2307 final UniformRandomProvider rng2 = new SplitMix64(0L) {
2308 private UniformRandomProvider delegate = source.create(seed);
2309 @Override
2310 public long next() {
2311 final long x = delegate.nextLong();
2312 return (x & unsetHighBit) | setLowBit;
2313 }
2314 };
2315
2316 // Not too close to alpha=1
2317 final double[] alphas = {0.3, 0.5, 1.2, 1.5};
2318 final double[] betas = {-0.5, -0.3, -0.1, 0};
2319
2320 final double relative = 1e-5;
2321 final double absolute = 1e-10;
2322
2323 for (final double alpha : alphas) {
2324 for (final double beta : betas) {
2325 final Supplier<String> msg = () -> String.format("alpha=%s, beta=%s", alpha, beta);
2326 // WARNING:
2327 // Created by direct access to package-private constructor.
2328 // This is for testing only as these do not validate the parameters.
2329 StableSampler s1;
2330 StableSampler s2;
2331 if (beta == 0) {
2332 s1 = new Beta0CMSStableSampler(rng1, alpha);
2333 s2 = new Beta0WeronStableSampler(rng2, alpha);
2334 } else {
2335 s1 = new CMSStableSampler(rng1, alpha, beta);
2336 s2 = new WeronStableSampler(rng2, alpha, beta);
2337 }
2338 for (int i = 0; i < 1000; i++) {
2339 final double x = s1.sample();
2340 final double y = s2.sample();
2341 Assertions.assertEquals(x, y, Math.max(absolute, Math.abs(x) * relative), msg);
2342 }
2343 }
2344 }
2345 }
2346
2347 /**
2348 * Demonstrate the general CMS sampler matches the {@code beta = 0} sampler.
2349 * The {@code beta = 0} sampler implements the same algorithm with cancelled terms removed.
2350 *
2351 * <p>Note: Uses direct instantiation via the package-private constructors. This avoids
2352 * the factory method constructor to directly select the implementation. Constructor
2353 * parameters are not validated.
2354 */
2355 @Test
2356 void testSpecializedBeta0CMSImplementation() {
2357 final RandomSource source = RandomSource.XO_RO_SHI_RO_128_SS;
2358 // Should be robust to any seed
2359 final byte[] seed = source.createSeed();
2360 final UniformRandomProvider rng1 = source.create(seed);
2361 final UniformRandomProvider rng2 = source.create(seed);
2362
2363 final double[] alphas = {0.3, 0.5, 1.2, 1.5};
2364 for (final double alpha : alphas) {
2365 // WARNING:
2366 // Created by direct access to package-private constructor.
2367 // This is for testing only as these do not validate the parameters.
2368 final StableSampler sampler1 = new CMSStableSampler(rng1, alpha, 0.0);
2369 final StableSampler sampler2 = new Beta0CMSStableSampler(rng2, alpha);
2370 RandomAssert.assertProduceSameSequence(sampler1, sampler2);
2371 }
2372 }
2373
2374 /**
2375 * Demonstrate the general Weron sampler matches the {@code beta = 0} sampler.
2376 * The {@code beta = 0} sampler implements the same algorithm with cancelled terms removed.
2377 *
2378 * <p>Note: Uses direct instantiation via the package-private constructors. This avoids
2379 * the factory method constructor to directly select the implementation. Constructor
2380 * parameters are not validated.
2381 */
2382 @Test
2383 void testSpecializedBeta0WeronImplementation() {
2384 final RandomSource source = RandomSource.XO_RO_SHI_RO_128_SS;
2385 // Should be robust to any seed
2386 final byte[] seed = source.createSeed();
2387 final UniformRandomProvider rng1 = source.create(seed);
2388 final UniformRandomProvider rng2 = source.create(seed);
2389
2390 final double[] alphas = {0.3, 0.5, 1.2, 1.5};
2391 for (final double alpha : alphas) {
2392 // WARNING:
2393 // Created by direct access to package-private constructor.
2394 // This is for testing only as these do not validate the parameters.
2395 final StableSampler sampler1 = new WeronStableSampler(rng1, alpha, 0.0);
2396 final StableSampler sampler2 = new Beta0WeronStableSampler(rng2, alpha);
2397 RandomAssert.assertProduceSameSequence(sampler1, sampler2);
2398 }
2399 }
2400
2401 /**
2402 * Test the Weron sampler when the term t1 is zero in the numerator.
2403 * This hits an edge case where sin(alpha * phi + atan(-zeta)) is zero.
2404 *
2405 * @see #testSinAlphaPhiMinusAtanZeta()
2406 */
2407 @Test
2408 void testWeronImplementationEdgeCase() {
2409 double alpha = 0.25;
2410 // Solved in testSinAlphaPhiMinusAtanZeta()
2411 double beta = -0.48021693505171;
2412 // Require phi = PI_4.
2413 // This is the equivalent of phi/2 = pi/5
2414 final long x = Long.MIN_VALUE >>> 1;
2415 final long[] longs = new long[] {
2416 // phi/2=pi/5, w=0
2417 x, 0,
2418 // phi/2=pi/5, w=large
2419 x, -1, -1, -1, -1, -1, -1, -1, -1, 0,
2420 // phi/2=pi/5, w=1
2421 x, 2703662416942444033L,
2422 };
2423
2424 // Validate series
2425 assertUWSequence(new double[] {
2426 PI_4 / 2, 0,
2427 PI_4 / 2, LARGE_W,
2428 PI_4 / 2, 1.0,
2429 }, longs);
2430
2431 final double zeta = -beta * Math.tan(alpha * PI_2);
2432 Assertions.assertEquals(0.0, alpha * PI_4 + Math.atan(-zeta));
2433
2434 final UniformRandomProvider rng = createRngWithSequence(longs);
2435 final StableSampler sampler = new WeronStableSampler(rng, alpha, beta);
2436 // zeta is the offset used to shift the 1-parameterization to the
2437 // 0-parameterization. This is returned when other terms multiply to zero.
2438 Assertions.assertEquals(zeta, sampler.sample());
2439 Assertions.assertEquals(zeta, sampler.sample());
2440 Assertions.assertEquals(zeta, sampler.sample());
2441 }
2442 }