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