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.examples.stress;
18  
19  import org.apache.commons.rng.UniformRandomProvider;
20  import org.apache.commons.rng.core.source32.IntProvider;
21  import org.apache.commons.rng.core.source32.RandomIntSource;
22  import org.apache.commons.rng.core.source64.RandomLongSource;
23  import org.apache.commons.rng.core.util.NumberFactory;
24  import org.apache.commons.rng.core.source64.LongProvider;
25  
26  import java.io.OutputStream;
27  import java.nio.ByteOrder;
28  import java.util.concurrent.ThreadLocalRandom;
29  
30  /**
31   * Utility methods for a {@link UniformRandomProvider}.
32   */
33  final class RNGUtils {
34      /** Name prefix for bit-reversed RNGs. */
35      private static final String BYTE_REVERSED = "Byte-reversed ";
36      /** Name prefix for bit-reversed RNGs. */
37      private static final String BIT_REVERSED = "Bit-reversed ";
38      /** Name prefix for hashcode mixed RNGs. */
39      private static final String HASH_CODE = "HashCode ^ ";
40      /** Name prefix for ThreadLocalRandom xor mixed RNGs. */
41      private static final String TLR_MIXED = "ThreadLocalRandom ^ ";
42      /** Name of xor operator for xor mixed RNGs. */
43      private static final String XOR = " ^ ";
44      /** Message for an unrecognised source64 mode. */
45      private static final String UNRECOGNISED_SOURCE_64_MODE = "Unrecognised source64 mode: ";
46      /** Message for an unrecognised native output type. */
47      private static final String UNRECOGNISED_NATIVE_TYPE = "Unrecognised native output type: ";
48      /** The source64 mode for the default LongProvider caching implementation. */
49      private static final Source64Mode SOURCE_64_DEFAULT = Source64Mode.LO_HI;
50  
51      /** No public construction. */
52      private RNGUtils() {}
53  
54      /**
55       * Gets the source64 mode for the default caching implementation in {@link LongProvider}.
56       *
57       * @return the source64 default mode
58       */
59      static Source64Mode getSource64Default() {
60          return SOURCE_64_DEFAULT;
61      }
62  
63      /**
64       * Wrap the random generator with a new instance that will reverse the byte order of
65       * the native type. The input must be either a {@link RandomIntSource} or
66       * {@link RandomLongSource}.
67       *
68       * @param rng The random generator.
69       * @return the byte reversed random generator.
70       * @throws ApplicationException If the input source native type is not recognised.
71       * @see Integer#reverseBytes(int)
72       * @see Long#reverseBytes(long)
73       */
74      static UniformRandomProvider createReverseBytesProvider(final UniformRandomProvider rng) {
75          if (rng instanceof RandomIntSource) {
76              return new IntProvider() {
77                  @Override
78                  public int next() {
79                      return Integer.reverseBytes(rng.nextInt());
80                  }
81  
82                  @Override
83                  public String toString() {
84                      return BYTE_REVERSED + rng.toString();
85                  }
86              };
87          }
88          if (rng instanceof RandomLongSource) {
89              return new LongProvider() {
90                  @Override
91                  public long next() {
92                      return Long.reverseBytes(rng.nextLong());
93                  }
94  
95                  @Override
96                  public String toString() {
97                      return BYTE_REVERSED + rng.toString();
98                  }
99              };
100         }
101         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng);
102     }
103 
104     /**
105      * Wrap the random generator with a new instance that will reverse the bits of
106      * the native type. The input must be either a {@link RandomIntSource} or
107      * {@link RandomLongSource}.
108      *
109      * @param rng The random generator.
110      * @return the bit reversed random generator.
111      * @throws ApplicationException If the input source native type is not recognised.
112      * @see Integer#reverse(int)
113      * @see Long#reverse(long)
114      */
115     static UniformRandomProvider createReverseBitsProvider(final UniformRandomProvider rng) {
116         if (rng instanceof RandomIntSource) {
117             return new IntProvider() {
118                 @Override
119                 public int next() {
120                     return Integer.reverse(rng.nextInt());
121                 }
122 
123                 @Override
124                 public String toString() {
125                     return BIT_REVERSED + rng.toString();
126                 }
127             };
128         }
129         if (rng instanceof RandomLongSource) {
130             return new LongProvider() {
131                 @Override
132                 public long next() {
133                     return Long.reverse(rng.nextLong());
134                 }
135 
136                 @Override
137                 public String toString() {
138                     return BIT_REVERSED + rng.toString();
139                 }
140             };
141         }
142         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng);
143     }
144 
145     /**
146      * Wrap the {@link RandomLongSource} with an {@link IntProvider} that will use the
147      * specified part of {@link RandomLongSource#next()} to create the int value.
148      *
149      * @param <R> The type of the generator.
150      * @param rng The random generator.
151      * @param mode the mode
152      * @return the int random generator.
153      * @throws ApplicationException If the input source native type is not 64-bit.
154      */
155     static <R extends RandomLongSource & UniformRandomProvider>
156             UniformRandomProvider createIntProvider(final R rng, Source64Mode mode) {
157         switch (mode) {
158         case INT:
159             return createIntProvider(rng);
160         case LO_HI:
161             return createLongLowerUpperBitsIntProvider(rng);
162         case HI_LO:
163             return createLongUpperLowerBitsIntProvider(rng);
164         case HI:
165             return createLongUpperBitsIntProvider(rng);
166         case LO:
167             return createLongLowerBitsIntProvider(rng);
168         case LONG:
169         default:
170             throw new IllegalArgumentException("Unsupported mode " + mode);
171         }
172     }
173 
174     /**
175      * Wrap the random generator with an {@link IntProvider} that will use
176      * {@link UniformRandomProvider#nextInt()}.
177      * An input {@link RandomIntSource} is returned unmodified.
178      *
179      * @param rng The random generator.
180      * @return the int random generator.
181      */
182     private static UniformRandomProvider createIntProvider(final UniformRandomProvider rng) {
183         if (!(rng instanceof RandomIntSource)) {
184             return new IntProvider() {
185                 @Override
186                 public int next() {
187                     return rng.nextInt();
188                 }
189 
190                 @Override
191                 public String toString() {
192                     return "Int bits " + rng.toString();
193                 }
194             };
195         }
196         return rng;
197     }
198 
199     /**
200      * Wrap the random generator with an {@link IntProvider} that will use the lower then upper
201      * 32-bits from {@link UniformRandomProvider#nextLong()}.
202      * An input {@link RandomIntSource} is returned unmodified.
203      *
204      * @param rng The random generator.
205      * @return the int random generator.
206      */
207     private static UniformRandomProvider createLongLowerUpperBitsIntProvider(final RandomLongSource rng) {
208         return new IntProvider() {
209             private long source = -1;
210 
211             @Override
212             public int next() {
213                 long next = source;
214                 if (next < 0) {
215                     // refill
216                     next = rng.next();
217                     // store hi
218                     source = next >>> 32;
219                     // extract low
220                     return (int) next;
221                 }
222                 final int v = (int) next;
223                 // reset
224                 source = -1;
225                 return v;
226             }
227 
228             @Override
229             public String toString() {
230                 return "Long lower-upper bits " + rng.toString();
231             }
232         };
233     }
234 
235     /**
236      * Wrap the random generator with an {@link IntProvider} that will use the lower then upper
237      * 32-bits from {@link UniformRandomProvider#nextLong()}.
238      * An input {@link RandomIntSource} is returned unmodified.
239      *
240      * @param rng The random generator.
241      * @return the int random generator.
242      */
243     private static UniformRandomProvider createLongUpperLowerBitsIntProvider(final RandomLongSource rng) {
244         return new IntProvider() {
245             private long source = -1;
246 
247             @Override
248             public int next() {
249                 long next = source;
250                 if (next < 0) {
251                     // refill
252                     next = rng.next();
253                     // store low
254                     source = next & 0xffff_ffffL;
255                     // extract hi
256                     return (int) (next >>> 32);
257                 }
258                 final int v = (int) next;
259                 // reset
260                 source = -1;
261                 return v;
262             }
263 
264             @Override
265             public String toString() {
266                 return "Long upper-lower bits " + rng.toString();
267             }
268         };
269     }
270 
271     /**
272      * Wrap the random generator with an {@link IntProvider} that will use the upper
273      * 32-bits of the {@code long} from {@link UniformRandomProvider#nextLong()}.
274      * The input must be a {@link RandomLongSource}.
275      *
276      * @param rng The random generator.
277      * @return the upper bits random generator.
278      * @throws ApplicationException If the input source native type is not 64-bit.
279      */
280     private static UniformRandomProvider createLongUpperBitsIntProvider(final RandomLongSource rng) {
281         return new IntProvider() {
282             @Override
283             public int next() {
284                 return (int) (rng.next() >>> 32);
285             }
286 
287             @Override
288             public String toString() {
289                 return "Long upper-bits " + rng.toString();
290             }
291         };
292     }
293 
294     /**
295      * Wrap the random generator with an {@link IntProvider} that will use the lower
296      * 32-bits of the {@code long} from {@link UniformRandomProvider#nextLong()}.
297      * The input must be a {@link RandomLongSource}.
298      *
299      * @param rng The random generator.
300      * @return the lower bits random generator.
301      * @throws ApplicationException If the input source native type is not 64-bit.
302      */
303     private static UniformRandomProvider createLongLowerBitsIntProvider(final RandomLongSource rng) {
304         return new IntProvider() {
305             @Override
306             public int next() {
307                 return (int) rng.next();
308             }
309 
310             @Override
311             public String toString() {
312                 return "Long lower-bits " + rng.toString();
313             }
314         };
315     }
316 
317     /**
318      * Wrap the random generator with a new instance that will combine the bits
319      * using a {@code xor} operation with a generated hash code. The input must be either
320      * a {@link RandomIntSource} or {@link RandomLongSource}.
321      *
322      * <pre>
323      * {@code
324      * System.identityHashCode(new Object()) ^ rng.nextInt()
325      * }
326      * </pre>
327      *
328      * Note: This generator will be slow.
329      *
330      * @param rng The random generator.
331      * @return the combined random generator.
332      * @throws ApplicationException If the input source native type is not recognised.
333      * @see System#identityHashCode(Object)
334      */
335     static UniformRandomProvider createHashCodeProvider(final UniformRandomProvider rng) {
336         if (rng instanceof RandomIntSource) {
337             return new IntProvider() {
338                 @Override
339                 public int next() {
340                     return System.identityHashCode(new Object()) ^ rng.nextInt();
341                 }
342 
343                 @Override
344                 public String toString() {
345                     return HASH_CODE + rng.toString();
346                 }
347             };
348         }
349         if (rng instanceof RandomLongSource) {
350             return new LongProvider() {
351                 @Override
352                 public long next() {
353                     final long mix = NumberFactory.makeLong(System.identityHashCode(new Object()),
354                                                             System.identityHashCode(new Object()));
355                     return mix ^ rng.nextLong();
356                 }
357 
358                 @Override
359                 public String toString() {
360                     return HASH_CODE + rng.toString();
361                 }
362             };
363         }
364         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng);
365     }
366 
367     /**
368      * Wrap the random generator with a new instance that will combine the bits
369      * using a {@code xor} operation with the output from {@link ThreadLocalRandom}.
370      * The input must be either a {@link RandomIntSource} or {@link RandomLongSource}.
371      *
372      * <pre>
373      * {@code
374      * ThreadLocalRandom.current().nextInt() ^ rng.nextInt()
375      * }
376      * </pre>
377      *
378      * @param rng The random generator.
379      * @return the combined random generator.
380      * @throws ApplicationException If the input source native type is not recognised.
381      */
382     static UniformRandomProvider createThreadLocalRandomProvider(final UniformRandomProvider rng) {
383         if (rng instanceof RandomIntSource) {
384             return new IntProvider() {
385                 @Override
386                 public int next() {
387                     return ThreadLocalRandom.current().nextInt() ^ rng.nextInt();
388                 }
389 
390                 @Override
391                 public String toString() {
392                     return TLR_MIXED + rng.toString();
393                 }
394             };
395         }
396         if (rng instanceof RandomLongSource) {
397             return new LongProvider() {
398                 @Override
399                 public long next() {
400                     return ThreadLocalRandom.current().nextLong() ^ rng.nextLong();
401                 }
402 
403                 @Override
404                 public String toString() {
405                     return TLR_MIXED + rng.toString();
406                 }
407             };
408         }
409         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng);
410     }
411 
412     /**
413      * Combine the two random generators using a {@code xor} operations.
414      * The input must be either a {@link RandomIntSource} or {@link RandomLongSource}.
415      * The returned type will match the native output type of {@code rng1}.
416      *
417      * <pre>
418      * {@code
419      * rng1.nextInt() ^ rng2.nextInt()
420      * }
421      * </pre>
422      *
423      * @param rng1 The first random generator.
424      * @param rng2 The second random generator.
425      * @return the combined random generator.
426      * @throws ApplicationException If the input source native type is not recognised.
427      */
428     static UniformRandomProvider createXorProvider(final UniformRandomProvider rng1,
429         final UniformRandomProvider rng2) {
430         if (rng1 instanceof RandomIntSource) {
431             return new IntProvider() {
432                 @Override
433                 public int next() {
434                     return rng1.nextInt() ^ rng2.nextInt();
435                 }
436 
437                 @Override
438                 public String toString() {
439                     return rng1.toString() + XOR + rng2.toString();
440                 }
441             };
442         }
443         if (rng1 instanceof RandomLongSource) {
444             return new LongProvider() {
445                 @Override
446                 public long next() {
447                     return rng1.nextLong() ^ rng2.nextLong();
448                 }
449 
450                 @Override
451                 public String toString() {
452                     return rng1.toString() + XOR + rng2.toString();
453                 }
454             };
455         }
456         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng1);
457     }
458 
459     /**
460      * Create a new instance to write batches of byte data from the specified RNG to the
461      * specified output stream.
462      *
463      * <p>This will detect the native output type of the RNG and create an appropriate
464      * data output for the raw bytes. The input must be either a {@link RandomIntSource} or
465      * {@link RandomLongSource}.</p>
466      *
467      * <p>If the RNG is a {@link RandomLongSource} then the byte output can be 32-bit or 64-bit.
468      * If 32-bit then the 64-bit output will be written as if 2 {@code int} values were generated
469      * sequentially from the {@code long} (order depends on the source64 mode). This setting is
470      * significant depending on the byte order. For example for a high-low source64 mode and
471      * using the Java standard big-endian representation the output is the same as the raw 64-bit
472      * output. If using little endian the output bytes will be written as:</p>
473      *
474      * <pre>
475      * 76543210  ->  4567  0123
476      * </pre>
477      *
478      * <h2>Note</h2>
479      *
480      * <p>The output from an implementation of RandomLongSource from the RNG core package
481      * may output the long bits as integers: in high-low order; in low-high order; using
482      * only the low bits; using only the high bits; or other combinations. This method
483      * allows testing the long output as if it were two int outputs, i.e. using the full
484      * bit output of a long provider with a stress test application that targets 32-bit
485      * random values (e.g. Test U01).
486      *
487      * <p>The results of stress testing can be used to determine if the provider
488      * implementation can use the upper, lower or both parts of the long output for int
489      * generation. In the case of the combined upper-lower output it is not expected that
490      * the order low-high or high-low is important given the stress test will consume
491      * thousands of numbers per test. The default 32-bit mode for a 64-bit source is high-low
492      * for backwards compatibility.
493      *
494      * @param rng The random generator.
495      * @param source64 The output mode for a 64-bit source
496      * @param out Output stream.
497      * @param byteSize Number of bytes values to write.
498      * @param byteOrder Byte order.
499      * @return the data output
500      * @throws ApplicationException If the input source native type is not recognised; or if
501      * the mode for a RandomLongSource is not one of: raw; hi-lo; or lo-hi.
502      */
503     static RngDataOutput createDataOutput(final UniformRandomProvider rng, Source64Mode source64,
504         OutputStream out, int byteSize, ByteOrder byteOrder) {
505         if (rng instanceof RandomIntSource) {
506             return RngDataOutput.ofInt(out, byteSize / 4, byteOrder);
507         }
508         if (rng instanceof RandomLongSource) {
509             switch (source64) {
510             case HI_LO:
511                 return RngDataOutput.ofLongAsHLInt(out, byteSize / 8, byteOrder);
512             case LO_HI:
513                 return RngDataOutput.ofLongAsLHInt(out, byteSize / 8, byteOrder);
514             case LONG:
515                 return RngDataOutput.ofLong(out, byteSize / 8, byteOrder);
516             // Note other options should have already been converted to an IntProvider
517             case INT:
518             case LO:
519             case HI:
520             default:
521                 throw new ApplicationException(UNRECOGNISED_SOURCE_64_MODE + source64);
522             }
523         }
524         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng);
525     }
526 
527     /**
528      * Parses the argument into an object suitable for the RandomSource constructor. Supports:
529      *
530      * <ul>
531      *   <li>Integer
532      * </ul>
533      *
534      * @param argument the argument
535      * @return the object
536      * @throws ApplicationException If the argument is not recognised
537      */
538     static Object parseArgument(String argument) {
539         try {
540             // Currently just support TWO_CMRES_SELECT which uses integers.
541             // Future RandomSource implementations may require other parsing, for example
542             // recognising a long by the suffix 'L'. This functionality
543             // could use Commons Lang NumberUtils.createNumber(String).
544             return Integer.parseInt(argument);
545         } catch (final NumberFormatException ex) {
546             throw new ApplicationException("Failed to parse RandomSource argument: " + argument, ex);
547         }
548     }
549 }