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 }