View Javadoc

1   /*
2    * Copyright 2013 University of Glasgow.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package broadwick.rng;
17  
18  import java.io.Serializable;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Set;
24  import java.util.TreeSet;
25  import lombok.EqualsAndHashCode;
26  import lombok.Getter;
27  import lombok.ToString;
28  import lombok.extern.slf4j.Slf4j;
29  import org.apache.commons.math3.random.MersenneTwister;
30  import org.apache.commons.math3.random.RandomDataGenerator;
31  import org.apache.commons.math3.random.Well19937c;
32  import org.apache.commons.math3.random.Well44497b;
33  
34  /**
35   * Random number generator singleton class This class is not thread safe.
36   */
37  @EqualsAndHashCode
38  @ToString
39  @Slf4j
40  public class RNG implements Serializable {
41  
42      /**
43       * Create a random number generator using the default JDK-provied PRNG.
44       */
45      public RNG() {
46          // By default, the implementation provided in RandomDataImpl uses
47          // the JDK-provided PRNG. Like most other PRNGs, the JDK generator
48          // generates sequences of random numbers based on an initial
49          // "seed value".
50          generator = new RandomDataGenerator();
51      }
52  
53      /**
54       * Create a random number generator from a given generator using the current time as a seed.
55       * @param gen the random number generator to use.
56       */
57      public RNG(final Generator gen) {
58          if (gen.equals(Generator.MERSENNE)) {
59              generator = new RandomDataGenerator(new MersenneTwister(System.currentTimeMillis() * Thread.currentThread().getId()));
60              name = "Mersenne Twister";
61          } else if (gen.equals(Generator.Well19937c)) {
62              generator = new RandomDataGenerator(new Well19937c(System.currentTimeMillis() * Thread.currentThread().getId()));
63              name = "Well19937c";
64          } else if (gen.equals(Generator.Well44497b)) {
65              generator = new RandomDataGenerator(new Well44497b(System.currentTimeMillis() * Thread.currentThread().getId()));
66              name = "Well44497b";
67          } else {
68              // By default, the implementation provided in RandomDataImpl uses
69              // the JDK-provided PRNG. Like most other PRNGs, the JDK generator
70              // generates sequences of random numbers based on an initial
71              // "seed value".
72              generator = new RandomDataGenerator();
73          }
74      }
75  
76      /**
77       * Get a list of valid geometries for the lattice.
78       * @return List<> a list of valid lattice geometries (in uppercase)
79       */
80      public static List<String> validGenerators() {
81          final ArrayList<String> generators = new ArrayList<>(3);
82          for (Generator value : Generator.values()) {
83              generators.add(value.name());
84          }
85          return generators;
86      }
87  
88      /**
89       * Seed the random number generator.
90       * @param seed the seed to use in the Rng
91       */
92      public final void seed(final int seed) {
93          generator.reSeed(seed);
94      }
95  
96      /**
97       * Generates a uniformly distributed random value from the open interval ( <code>0.0</code>, <code>1.0</code>)
98       * (i.e., endpoints excluded).
99       * <p>
100      * <strong>Definition</strong>: <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3662.htm">
101      * Uniform Distribution</a>
102      * <code>0.0</code> and <code>1.0 - 0.0</code> are the <a href =
103      * "http://www.itl.nist.gov/div898/handbook/eda/section3/eda364.htm">
104      * location and scale parameters</a>, respectively.</p>
105      * @return uniformly distributed random value between lower and upper (exclusive)
106      */
107     public final double getDouble() {
108         return generator.nextUniform(0.0, 1.0);
109     }
110 
111     /**
112      * Generates a uniformly distributed random value from the open interval ( <code>lower</code>, <code>upper</code>)
113      * (i.e., endpoints excluded).
114      * <p>
115      * <strong>Definition</strong>: <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3662.htm">
116      * Uniform Distribution</a>
117      * <code>lower</code> and <code>upper - lower</code> are the <a href =
118      * "http://www.itl.nist.gov/div898/handbook/eda/section3/eda364.htm">
119      * location and scale parameters</a>, respectively.</p>
120      * <p>
121      * <strong>Preconditions</strong>:<ul> <li><code>lower < upper</code> (otherwise an IllegalArgumentException is
122      * thrown.)</li> </ul></p>
123      * @param lower lower endpoint of the interval of support
124      * @param upper upper endpoint of the interval of support
125      * @return uniformly distributed random value between lower and upper (exclusive)
126      */
127     public final double getDouble(final double lower, final double upper) {
128         double lo = lower;
129         double hi = upper;
130         if (lower >= upper) {
131             hi = lower;
132             lo = upper;
133         }
134         if (lower == upper) {
135             return lower;
136         }
137         return generator.nextUniform(lo, hi);
138     }
139 
140     /**
141      * Generates a uniformly distributed random integer between <code>lower</code> and <code>upper</code> (endpoints
142      * included).
143      * <p>
144      * The generated integer will be random, but not cryptographically secure. To generate cryptographically secure
145      * integer sequences, use <code>nextSecureInt</code>.</p>
146      * <p>
147      * <strong>Preconditions</strong>:<ul>
148      * <li><code>lower < upper</code> (otherwise an IllegalArgumentException is thrown.)</li> </ul></p>
149      * @param lower lower bound for generated integer
150      * @param upper upper bound for generated integer
151      * @return a random integer greater than or equal to <code>lower</code> and less than or equal * * * to
152      *         <code>upper</code>. If lower == upper then lower is returned.
153      */
154     public final int getInteger(final int lower, final int upper) {
155         int lo = lower;
156         int hi = upper;
157         if (lower >= upper) {
158             hi = lower;
159             lo = upper;
160         }
161         if (lower == upper) {
162             return lower;
163         }
164         return generator.nextInt(lo, hi);
165     }
166 
167     /**
168      * Generates a random value from the Poisson distribution with the given mean.
169      * <p>
170      * <strong>Definition</strong>: <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda366j.htm">
171      * Poisson Distribution</a>
172      * </p>
173      * <p>
174      * <strong>Preconditions</strong>: <ul>
175      * <li>The specified mean <i>must</i> be positive (otherwise an IllegalArgumentException is thrown.)</li> </ul></p>
176      * @param mean Mean of the distribution
177      * @return poisson deviate with the specified mean
178      */
179     public final long getPoisson(final double mean) {
180         return generator.nextPoisson(mean);
181     }
182 
183     /**
184      * Generates a random value from the Normal (or Gaussian) distribution with the given mean and standard deviation.
185      * <p>
186      * <strong>Definition</strong>:
187      * <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3661.htm">
188      * Normal Distribution</a></p>
189      * <p>
190      * <strong>Preconditions</strong>: <ul>
191      * <li><code>sigma > 0</code> (otherwise an IllegalArgumentException is thrown.)</li> </ul></p>
192      * @param mu    Mean of the distribution
193      * @param sigma Standard deviation given as a percentage of the mean.
194      * @return random value from Gaussian distribution with mean = mu, standard deviation = sigma
195      */
196     public final double getGaussian(final double mu, final double sigma) {
197         return generator.nextGaussian(mu, sigma);
198     }
199 
200     /**
201      * Get a boolean (true/false) value.
202      * @return boolean random true/false value
203      */
204     public final boolean getBoolean() {
205         return getDouble(0.0, 1.0) < 0.5;
206     }
207 
208     /**
209      * Generates a random value from the binomial distribution with the given N and p. The returned value is a random
210      * integer drawn from <DIV ALIGN="CENTER" CLASS="mathdisplay"> <I>P</I>(<I>x</I>) = (<SUP>n</SUP><SUB>x</SUB>)
211      * <I>p</I><SUP>x</SUP>(1-<I>p</I>)<SUP>(<I>n-x</I>)</SUP> </DIV><P>
212      * </P>
213      * Successive draws from this distribution can be combined, i.e. if X ~ getBinomial(n, p) and Y ~ getBinomial(m, p)
214      * are independent binomial variables , then X + Y is again a binomial variable; its distribution is
215      * getBinomial(n+m, p)
216      * @param n the number of trials.
217      * @param p the probability of success of each trial.
218      * @return the number of successes on n trials.
219      */
220     public final int getBinomial(final int n, final double p) {
221         int x = 0;
222         for (int i = 0; i < n; i++) {
223             if (generator.nextUniform(0.0, 1.0) < p) {
224                 x++;
225             }
226         }
227         return x;
228     }
229 
230     /**
231      * Randomly pick an object from an array of objects.
232      * @param <T>     generic type of the array elements.
233      * @param objects an array of objects, one of whom is to be picked.
234      * @return a random element from objects.
235      */
236     //public final <T> T newArray(Class<T>[] objects) {
237     public final <T> T selectOneOf(final T[] objects) {
238         return objects[getInteger(0, objects.length - 1)];
239     }
240 
241     /**
242      * Randomly pick an object from an array of objects.
243      * @param <T>           generic type of the array elements.
244      * @param objects       an array of objects, one of whom is to be picked.
245      * @param probabilities the probabilities of selecting each of the objects.
246      * @return a random element from objects.
247      */
248     public final <T> T selectOneOf(final T[] objects, final double[] probabilities) {
249         final double r = getDouble();
250         double cdf = 0.0;
251         for (int i = 0; i < objects.length; i++) {
252             cdf += probabilities[i];
253             if (r <= cdf) {
254                 return objects[i];
255             }
256         }
257         return objects[objects.length];
258     }
259 
260     /**
261      * @param set a Set in which to look for a random element
262      * @param <T> generic type of the Set elements
263      * @return a random element in the Set or null if the set is empty
264      */
265     public <T> T selectOneOf(Set<T> set) {
266         int item = getInteger(0, set.size() - 1);
267         int i = 0;
268         for (T obj : set) {
269             if (i == item) {
270                 return obj;
271             }
272             i++;
273         }
274         return null;
275     }
276 
277     /**
278      * Randomly pick an object from an array of objects.
279      * @param <T>     The type of the elements in the collection
280      * @param objects an array of objects, one of whom is to be picked.
281      * @return a random element from objects.
282      */
283     public final <T> T selectOneOf(final Collection<T> objects) {
284 
285         final int n = getInteger(0, objects.size() - 1);
286         if (objects instanceof List) {
287             return ((List<T>) objects).get(n);
288         } else {
289             return com.google.common.collect.Iterators.get(objects.iterator(), n);
290         }
291     }
292 
293     /**
294      * Randomly pick N objects from an array of objects. Note, this assumes that N much much less than the size of the
295      * array being sampled from, if this is not the case this method is VERY slow.
296      * @param <T>     The type of the elements in the collection
297      * @param objects an array of objects, one of whom is to be picked.
298      * @param n       the number of objects we will select.
299      * @return a random element from objects.
300      */
301     public final <T> List<T> selectManyOf(final T[] objects, final int n) {
302 
303         if (n > objects.length) {
304             throw new IllegalArgumentException(String.format("Cannot select %d elements from array of %d objects", n, objects.length));
305         }
306 
307         // this uses simple rejection sampling, a faster method (especially if n is close to objects.size() can be found
308         // at http://lemire.me/blog/archives/2013/08/16/picking-n-distinct-numbers-at-random-how-to-do-it-fast/
309         final List<T> list = new ArrayList<>(n);
310         final Set<Integer> sampled = new TreeSet<>();
311         for (int i = 0; i < n; i++) {
312             int index = getInteger(0, objects.length - 1);
313             while (sampled.contains(index)) {
314                 index = getInteger(0, objects.length - 1);
315             }
316             sampled.add(index);
317             list.add(objects[index]);
318         }
319 
320         return list;
321     }
322 
323     /**
324      * Randomly pick N objects from an array of objects. Note, this assumes that N much much less than the size of the
325      * array being sampled from, if this is not the case this method is VERY slow.
326      * @param <T>     The type of the elements in the collection
327      * @param objects an array of objects, one of whom is to be picked.
328      * @param n       the number of objects we will select.
329      * @return a random element from objects.
330      */
331     public final <T> List<T> selectManyOf(final Collection<T> objects, final int n) {
332         if (objects.size() < n) {
333             throw new IllegalArgumentException(String.format("Cannot select %d elements from a Collection of %d objects", n, objects.size()));
334         }
335         final Set<Integer> s = new HashSet<>(n);
336         while (s.size() < n) {
337             s.add(getInteger(0, objects.size() - 1));
338         }
339 
340         final List<T> list = new ArrayList<>(n);
341         int i = 0;
342         for (T obj : objects) {
343             if (s.contains(i)) {
344                 list.add(obj);
345             }
346             i = i + 1;
347         }
348 
349         if (list.size() != n) {
350             log.error("Could not correctly select correct number of objects from a collection of objects.");
351         }
352 
353         return list;
354     }
355 
356     /**
357      * Randomly pick N objects from an array of objects. Note, this assumes that N much much less than the size of the
358      * array being sampled from, if this is not the case this method is VERY slow.
359      * @param <T>     The type of the elements in the collection
360      * @param objects an array of objects, one of whom is to be picked.
361      * @param n       the number of objects we will select.
362      * @return a random element from objects.
363      */
364     public final <T> Set<T> selectManyOf(final Set<T> objects, final int n) {
365         if (objects.size() < n) {
366             throw new IllegalArgumentException(String.format("Cannot select %d elements from a Set of %d objects", n, objects.size()));
367         }
368 
369         final Set<Integer> s = new HashSet<>(n);
370         while (s.size() < n) {
371             s.add(getInteger(0, objects.size() - 1));
372         }
373 
374         final Set<T> list = new HashSet<>(n);
375         int i = 0;
376         for (T obj : objects) {
377             if (s.contains(i)) {
378                 list.add(obj);
379             }
380             i = i + 1;
381         }
382 
383         if (list.size() != n) {
384             log.error("Could not correctly select correct number of objects from a collection of objects.");
385         }
386 
387         return list;
388     }
389 
390     /**
391      * Random number generator type.
392      */
393     private final RandomDataGenerator generator;
394     @SuppressWarnings("PMD.UnusedPrivateField")
395     @Getter
396     private String name;
397 
398     /**
399      * Random number generator type.
400      */
401     public static enum Generator {
402 
403         /*
404          * This generator features an extremely long period (219937-1) and 623-dimensional equidistribution up to 32 bits accuracy.
405          * The home page for this generator is located at http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html.
406          * This generator is described in a paper by Makoto Matsumoto and Takuji Nishimura in 1998:
407          * "Mersenne Twister: A 623-Dimensionally Equidistributed Uniform Pseudo-Random Number Generator",
408          * ACM Transactions on Modeling and Computer Simulation, Vol. 8, No. 1, January 1998, pp 3--30
409          */
410         MERSENNE,
411         /*
412          * WELL19937c pseudo-random number generator from François Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
413          * This generator is described in a paper by François Panneton, Pierre L'Ecuyer and Makoto Matsumoto
414          * "Improved Long-Period Generators Based on Linear Recurrences Modulo 2" ACM Transactions on Mathematical Software, 32, 1 (2006)
415          */
416         Well19937c,
417         /*
418          * WELL44497b pseudo-random number generator from François Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
419          * This generator is described in a paper by François Panneton, Pierre L'Ecuyer and Makoto Matsumoto
420          * "Improved Long-Period Generators Based on Linear Recurrences Modulo 2" ACM Transactions on Mathematical Software, 32, 1 (2006)
421          */
422         Well44497b
423     };
424 }