View Javadoc
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.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  import org.apache.commons.rng.UniformRandomProvider;
23  import org.apache.commons.rng.sampling.RandomAssert;
24  
25  /**
26   * List of samplers.
27   */
28  public final class ContinuousSamplersList {
29      /** List of all RNGs implemented in the library. */
30      private static final List<ContinuousSamplerTestData> LIST = new ArrayList<>();
31  
32      static {
33          try {
34              // This test uses reference distributions from commons-math3 to compute the expected
35              // PMF. These distributions have a dual functionality to compute the PMF and perform
36              // sampling. When no sampling is needed for the created distribution, it is advised
37              // to pass null as the random generator via the appropriate constructors to avoid the
38              // additional initialisation overhead.
39              org.apache.commons.math3.random.RandomGenerator unusedRng = null;
40  
41              // List of distributions to test.
42  
43              // Gaussian ("inverse method").
44              final double meanNormal = -123.45;
45              final double sigmaNormal = 6.789;
46              add(LIST, new org.apache.commons.math3.distribution.NormalDistribution(unusedRng, meanNormal, sigmaNormal),
47                  RandomAssert.createRNG());
48              // Gaussian (DEPRECATED "Box-Muller").
49              add(LIST, new org.apache.commons.math3.distribution.NormalDistribution(unusedRng, meanNormal, sigmaNormal),
50                  new BoxMullerGaussianSampler(RandomAssert.createRNG(), meanNormal, sigmaNormal));
51              // Gaussian ("Box-Muller").
52              add(LIST, new org.apache.commons.math3.distribution.NormalDistribution(unusedRng, meanNormal, sigmaNormal),
53                  GaussianSampler.of(new BoxMullerNormalizedGaussianSampler(RandomAssert.createRNG()),
54                                     meanNormal, sigmaNormal));
55              // Gaussian ("Marsaglia").
56              add(LIST, new org.apache.commons.math3.distribution.NormalDistribution(unusedRng, meanNormal, sigmaNormal),
57                  GaussianSampler.of(new MarsagliaNormalizedGaussianSampler(RandomAssert.createRNG()),
58                                     meanNormal, sigmaNormal));
59              // Gaussian ("Ziggurat").
60              add(LIST, new org.apache.commons.math3.distribution.NormalDistribution(unusedRng, meanNormal, sigmaNormal),
61                  GaussianSampler.of(new ZigguratNormalizedGaussianSampler(RandomAssert.createRNG()),
62                                     meanNormal, sigmaNormal));
63              // Gaussian ("Modified ziggurat").
64              add(LIST, new org.apache.commons.math3.distribution.NormalDistribution(unusedRng, meanNormal, sigmaNormal),
65                  GaussianSampler.of(ZigguratSampler.NormalizedGaussian.of(RandomAssert.createRNG()),
66                                     meanNormal, sigmaNormal));
67  
68              // Beta ("inverse method").
69              final double alphaBeta = 4.3;
70              final double betaBeta = 2.1;
71              add(LIST, new org.apache.commons.math3.distribution.BetaDistribution(unusedRng, alphaBeta, betaBeta),
72                  RandomAssert.createRNG());
73              // Beta ("Cheng").
74              add(LIST, new org.apache.commons.math3.distribution.BetaDistribution(unusedRng, alphaBeta, betaBeta),
75                  ChengBetaSampler.of(RandomAssert.createRNG(), alphaBeta, betaBeta));
76              add(LIST, new org.apache.commons.math3.distribution.BetaDistribution(unusedRng, betaBeta, alphaBeta),
77                  ChengBetaSampler.of(RandomAssert.createRNG(), betaBeta, alphaBeta));
78              // Beta ("Cheng", alternate algorithm).
79              final double alphaBetaAlt = 0.5678;
80              final double betaBetaAlt = 0.1234;
81              add(LIST, new org.apache.commons.math3.distribution.BetaDistribution(unusedRng, alphaBetaAlt, betaBetaAlt),
82                  ChengBetaSampler.of(RandomAssert.createRNG(), alphaBetaAlt, betaBetaAlt));
83              add(LIST, new org.apache.commons.math3.distribution.BetaDistribution(unusedRng, betaBetaAlt, alphaBetaAlt),
84                  ChengBetaSampler.of(RandomAssert.createRNG(), betaBetaAlt, alphaBetaAlt));
85  
86              // Cauchy ("inverse method").
87              final double medianCauchy = 0.123;
88              final double scaleCauchy = 4.5;
89              add(LIST, new org.apache.commons.math3.distribution.CauchyDistribution(unusedRng, medianCauchy, scaleCauchy),
90                  RandomAssert.createRNG());
91  
92              // Chi-square ("inverse method").
93              final int dofChi2 = 12;
94              add(LIST, new org.apache.commons.math3.distribution.ChiSquaredDistribution(unusedRng, dofChi2),
95                  RandomAssert.createRNG());
96  
97              // Exponential ("inverse method").
98              final double meanExp = 3.45;
99              add(LIST, new org.apache.commons.math3.distribution.ExponentialDistribution(unusedRng, meanExp),
100                 RandomAssert.createRNG());
101             // Exponential.
102             add(LIST, new org.apache.commons.math3.distribution.ExponentialDistribution(unusedRng, meanExp),
103                 AhrensDieterExponentialSampler.of(RandomAssert.createRNG(), meanExp));
104             // Exponential ("Modified ziggurat").
105             add(LIST, new org.apache.commons.math3.distribution.ExponentialDistribution(unusedRng, meanExp),
106                 ZigguratSampler.Exponential.of(RandomAssert.createRNG(), meanExp));
107 
108             // F ("inverse method").
109             final int numDofF = 4;
110             final int denomDofF = 7;
111             add(LIST, new org.apache.commons.math3.distribution.FDistribution(unusedRng, numDofF, denomDofF),
112                 RandomAssert.createRNG());
113 
114             // Gamma ("inverse method").
115             final double alphaGammaSmallerThanOne = 0.1234;
116             final double alphaGammaLargerThanOne = 2.345;
117             final double thetaGamma = 3.456;
118             add(LIST, new org.apache.commons.math3.distribution.GammaDistribution(unusedRng, alphaGammaLargerThanOne, thetaGamma),
119                 RandomAssert.createRNG());
120             // Gamma (alpha < 1).
121             add(LIST, new org.apache.commons.math3.distribution.GammaDistribution(unusedRng, alphaGammaSmallerThanOne, thetaGamma),
122                 AhrensDieterMarsagliaTsangGammaSampler.of(RandomAssert.createRNG(),
123                                                           alphaGammaSmallerThanOne, thetaGamma));
124             // Gamma (alpha > 1).
125             add(LIST, new org.apache.commons.math3.distribution.GammaDistribution(unusedRng, alphaGammaLargerThanOne, thetaGamma),
126                 AhrensDieterMarsagliaTsangGammaSampler.of(RandomAssert.createRNG(),
127                                                           alphaGammaLargerThanOne, thetaGamma));
128 
129             // Gumbel ("inverse method").
130             final double muGumbel = -4.56;
131             final double betaGumbel = 0.123;
132             add(LIST, new org.apache.commons.math3.distribution.GumbelDistribution(unusedRng, muGumbel, betaGumbel),
133                 RandomAssert.createRNG());
134 
135             // Laplace ("inverse method").
136             final double muLaplace = 12.3;
137             final double betaLaplace = 5.6;
138             add(LIST, new org.apache.commons.math3.distribution.LaplaceDistribution(unusedRng, muLaplace, betaLaplace),
139                 RandomAssert.createRNG());
140 
141             // Levy ("inverse method").
142             final double muLevy = -1.098;
143             final double cLevy = 0.76;
144             add(LIST, new org.apache.commons.math3.distribution.LevyDistribution(unusedRng, muLevy, cLevy),
145                 RandomAssert.createRNG());
146             // Levy sampler
147             add(LIST, new org.apache.commons.math3.distribution.LevyDistribution(unusedRng, muLevy, cLevy),
148                 LevySampler.of(RandomAssert.createRNG(), muLevy, cLevy));
149             add(LIST, new org.apache.commons.math3.distribution.LevyDistribution(unusedRng, 0.0, 1.0),
150                 LevySampler.of(RandomAssert.createRNG(), 0.0, 1.0));
151 
152             // Log normal ("inverse method").
153             final double muLogNormal = 2.345;
154             final double sigmaLogNormal = 0.1234;
155             add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal, sigmaLogNormal),
156                 RandomAssert.createRNG());
157             // Log-normal (DEPRECATED "Box-Muller").
158             add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal, sigmaLogNormal),
159                 new BoxMullerLogNormalSampler(RandomAssert.createRNG(), muLogNormal, sigmaLogNormal));
160             // Log-normal ("Box-Muller").
161             add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal, sigmaLogNormal),
162                 LogNormalSampler.of(new BoxMullerNormalizedGaussianSampler(RandomAssert.createRNG()),
163                                     muLogNormal, sigmaLogNormal));
164             // Log-normal ("Marsaglia").
165             add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal, sigmaLogNormal),
166                 LogNormalSampler.of(new MarsagliaNormalizedGaussianSampler(RandomAssert.createRNG()),
167                                     muLogNormal, sigmaLogNormal));
168             // Log-normal ("Ziggurat").
169             add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal, sigmaLogNormal),
170                 LogNormalSampler.of(new ZigguratNormalizedGaussianSampler(RandomAssert.createRNG()),
171                                     muLogNormal, sigmaLogNormal));
172             // Log-normal negative mean
173             final double muLogNormal2 = -1.1;
174             final double sigmaLogNormal2 = 2.3;
175             add(LIST, new org.apache.commons.math3.distribution.LogNormalDistribution(unusedRng, muLogNormal2, sigmaLogNormal2),
176                     LogNormalSampler.of(new ZigguratNormalizedGaussianSampler(RandomAssert.createRNG()),
177                                         muLogNormal2, sigmaLogNormal2));
178 
179             // Logistic ("inverse method").
180             final double muLogistic = -123.456;
181             final double sLogistic = 7.89;
182             add(LIST, new org.apache.commons.math3.distribution.LogisticDistribution(unusedRng, muLogistic, sLogistic),
183                 RandomAssert.createRNG());
184 
185             // Nakagami ("inverse method").
186             final double muNakagami = 78.9;
187             final double omegaNakagami = 23.4;
188             final double inverseAbsoluteAccuracyNakagami = org.apache.commons.math3.distribution.NakagamiDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY;
189             add(LIST, new org.apache.commons.math3.distribution.NakagamiDistribution(unusedRng, muNakagami, omegaNakagami, inverseAbsoluteAccuracyNakagami),
190                 RandomAssert.createRNG());
191 
192             // Pareto ("inverse method").
193             final double scalePareto = 23.45;
194             final double shapePareto = 0.1234;
195             add(LIST, new org.apache.commons.math3.distribution.ParetoDistribution(unusedRng, scalePareto, shapePareto),
196                 RandomAssert.createRNG());
197             // Pareto.
198             add(LIST, new org.apache.commons.math3.distribution.ParetoDistribution(unusedRng, scalePareto, shapePareto),
199                 InverseTransformParetoSampler.of(RandomAssert.createRNG(), scalePareto, shapePareto));
200 
201             // Stable distributions.
202             // Gaussian case: alpha=2
203             add(LIST, new org.apache.commons.math3.distribution.NormalDistribution(unusedRng, 0, Math.sqrt(2)),
204                 StableSampler.of(RandomAssert.createRNG(), 2, 0));
205             add(LIST, new org.apache.commons.math3.distribution.NormalDistribution(unusedRng, 3.4, 0.75 * Math.sqrt(2)),
206                     StableSampler.of(RandomAssert.createRNG(), 2, 0, 0.75, 3.4));
207             // Cauchy case: alpha=1, beta=0, gamma=2.73, delta=0.87
208             add(LIST, new org.apache.commons.math3.distribution.CauchyDistribution(unusedRng, 0.87, 2.73),
209                 StableSampler.of(RandomAssert.createRNG(), 1, 0, 2.73, 0.87));
210             // Levy case: alpha=0.5, beta=0, gamma=5.7, delta=-1.23
211             // The 0-parameterization requires the reference distribution (1-parameterization) is shifted:
212             // S0(Z) = S1(Z) + gamma * beta * tan(pi * alpha / 2); gamma = 5.7, beta = -1, alpha = 0.5
213             // = gamma * -1 * tan(pi/4) = -gamma
214             add(LIST, new org.apache.commons.math3.distribution.LevyDistribution(unusedRng, -1.23 - 5.7, 5.7),
215                 StableSampler.of(RandomAssert.createRNG(), 0.5, 1.0, 5.7, -1.23));
216             // Levy case: alpha=0.5, beta=0.
217             // The 0-parameterization requires the reference distribution is shifted by -tan(pi/4) = -1
218             add(LIST, new org.apache.commons.math3.distribution.LevyDistribution(unusedRng, -1.0, 1.0),
219                     StableSampler.of(RandomAssert.createRNG(), 0.5, 1.0, 1.0, 0.0));
220 
221             // No Stable distribution in Commons Math: Deciles computed using Nolan's STABLE program:
222             // https://edspace.american.edu/jpnolan/stable/
223 
224             // General case (alpha > 1): alpha=1.3, beta=0.4, gamma=1.5, delta=-6.4
225             add(LIST, new double[] {-8.95069776039550, -7.89186827865320, -7.25070352695719, -6.71497820795024,
226                 -6.19542020516881, -5.63245847779003, -4.94643432673952, -3.95462242999135,
227                 -1.90020994991840, Double.POSITIVE_INFINITY},
228                 StableSampler.of(RandomAssert.createRNG(), 1.3, 0.4, 1.5, -6.4));
229             // General case (alpha < 1): alpha=0.8, beta=-0.3, gamma=0.75, delta=3.25
230             add(LIST, new double[] {-1.60557902637291, 1.45715153372767, 2.39577970333297, 2.86274746879986,
231                 3.15907259287483, 3.38633464572309, 3.60858199662215, 3.96001854555454, 5.16261950198042,
232                 Double.POSITIVE_INFINITY},
233                 StableSampler.of(RandomAssert.createRNG(), 0.8, -0.3, 0.75, 3.25));
234             // Alpha 1 case: alpha=1.0, beta=0.3
235             add(LIST, new double[] {-2.08189340389400, -0.990511737972781, -0.539025554211755, -0.204710171216492,
236                 0.120388569770401, 0.497197960523146, 1.01228394387185, 1.89061920660563, 4.20559140293206,
237                 Double.POSITIVE_INFINITY},
238                 StableSampler.of(RandomAssert.createRNG(), 1.0, 0.3));
239             // Symmetric case (beta=0): alpha=1.3, beta=0.0
240             add(LIST, new double[] {-2.29713832179280, -1.26781259700375, -0.739212223404616, -0.346771353386198,
241                 0.00000000000000, 0.346771353386198, 0.739212223404616, 1.26781259700376, 2.29713832179280,
242                 Double.POSITIVE_INFINITY},
243                 StableSampler.of(RandomAssert.createRNG(), 1.3, 0.0));
244 
245             // This is the smallest alpha where the CDF can be reliably computed.
246             // Small alpha case: alpha=0.1, beta=-0.2
247             add(LIST, new double[] {-14345498.0855558, -4841.68845914421, -22.6430159400915, -0.194461655962062,
248                 0.299822962206354E-1, 0.316768853375197E-1, 0.519382255860847E-1, 21.8595769961580,
249                 147637.033822552, Double.POSITIVE_INFINITY},
250                 StableSampler.of(RandomAssert.createRNG(), 0.1, -0.2));
251 
252             // T ("inverse method").
253             final double dofT = 0.76543;
254             add(LIST, new org.apache.commons.math3.distribution.TDistribution(unusedRng, dofT),
255                 RandomAssert.createRNG());
256             // T.
257             add(LIST, new org.apache.commons.math3.distribution.TDistribution(unusedRng, dofT),
258                 TSampler.of(RandomAssert.createRNG(), dofT));
259             // T with 'large' degrees of freedom.
260             final double dofTlarge = 30;
261             add(LIST, new org.apache.commons.math3.distribution.TDistribution(unusedRng, dofTlarge),
262                 TSampler.of(RandomAssert.createRNG(), dofTlarge));
263             // T with 'huge' degrees of freedom (approaches a normal distribution).
264             // Deciles are computed incorrectly using Commons Math; values computed using Matlab.
265             // Note: DF is below the switch to using a normal distribution.
266             final double dofTHuge = 1e15;
267             add(LIST, new double[] {-1.2815515655446015, -0.84162123357291463, -0.52440051270804089,
268                 -0.25334710313579983, 0, 0.25334710313579983, 0.52440051270804089, 0.84162123357291474,
269                 1.2815515655446015, Double.POSITIVE_INFINITY},
270                 TSampler.of(RandomAssert.createRNG(), dofTHuge));
271 
272             // Triangular ("inverse method").
273             final double aTriangle = -0.76543;
274             final double cTriangle = -0.65432;
275             final double bTriangle = -0.54321;
276             add(LIST, new org.apache.commons.math3.distribution.TriangularDistribution(unusedRng, aTriangle, cTriangle, bTriangle),
277                 RandomAssert.createRNG());
278 
279             // Uniform ("inverse method").
280             final double loUniform = -1.098;
281             final double hiUniform = 0.76;
282             add(LIST, new org.apache.commons.math3.distribution.UniformRealDistribution(unusedRng, loUniform, hiUniform),
283                 RandomAssert.createRNG());
284             // Uniform.
285             add(LIST, new org.apache.commons.math3.distribution.UniformRealDistribution(unusedRng, loUniform, hiUniform),
286                 ContinuousUniformSampler.of(RandomAssert.createRNG(), loUniform, hiUniform));
287 
288             // Weibull ("inverse method").
289             final double alphaWeibull = 678.9;
290             final double betaWeibull = 98.76;
291             add(LIST, new org.apache.commons.math3.distribution.WeibullDistribution(unusedRng, alphaWeibull, betaWeibull),
292                 RandomAssert.createRNG());
293         } catch (Exception e) {
294             // CHECKSTYLE: stop Regexp
295             System.err.println("Unexpected exception while creating the list of samplers: " + e);
296             e.printStackTrace(System.err);
297             // CHECKSTYLE: resume Regexp
298             throw new RuntimeException(e);
299         }
300     }
301 
302     /**
303      * Class contains only static methods.
304      */
305     private ContinuousSamplersList() {}
306 
307     /**
308      * @param list List of data (one the "parameters" tested by the Junit parametric test).
309      * @param dist Distribution to which the samples are supposed to conform.
310      * @param rng Generator of uniformly distributed sequences.
311      */
312     private static void add(List<ContinuousSamplerTestData> list,
313                             final org.apache.commons.math3.distribution.RealDistribution dist,
314                             UniformRandomProvider rng) {
315         final ContinuousSampler inverseMethodSampler =
316             InverseTransformContinuousSampler.of(rng,
317                 new ContinuousInverseCumulativeProbabilityFunction() {
318                     @Override
319                     public double inverseCumulativeProbability(double p) {
320                         return dist.inverseCumulativeProbability(p);
321                     }
322                     @Override
323                     public String toString() {
324                         return dist.toString();
325                     }
326                 });
327         list.add(new ContinuousSamplerTestData(inverseMethodSampler,
328                                                getDeciles(dist)));
329     }
330 
331     /**
332      * @param list List of data (one the "parameters" tested by the Junit parametric test).
333      * @param dist Distribution to which the samples are supposed to conform.
334      * @param sampler Sampler.
335      */
336     private static void add(List<ContinuousSamplerTestData> list,
337                             final org.apache.commons.math3.distribution.RealDistribution dist,
338                             final ContinuousSampler sampler) {
339         list.add(new ContinuousSamplerTestData(sampler,
340                                                getDeciles(dist)));
341     }
342 
343     /**
344      * @param list List of data (one the "parameters" tested by the Junit parametric test).
345      * @param deciles Deciles of the given distribution.
346      * @param sampler Sampler.
347      */
348     private static void add(List<ContinuousSamplerTestData> list,
349                             final double[] deciles,
350                             final ContinuousSampler sampler) {
351         list.add(new ContinuousSamplerTestData(sampler,
352                                                deciles));
353     }
354 
355     /**
356      * Subclasses that are "parametric" tests can forward the call to
357      * the "@Parameters"-annotated method to this method.
358      *
359      * @return the list of all generators.
360      */
361     public static Iterable<ContinuousSamplerTestData> list() {
362         return Collections.unmodifiableList(LIST);
363     }
364 
365     /**
366      * @param dist Distribution.
367      * @return the deciles of the given distribution.
368      */
369     private static double[] getDeciles(org.apache.commons.math3.distribution.RealDistribution dist) {
370         final int last = 9;
371         final double[] deciles = new double[last];
372         final double ten = 10;
373         for (int i = 0; i < last; i++) {
374             deciles[i] = dist.inverseCumulativeProbability((i + 1) / ten);
375         }
376         return deciles;
377     }
378 }