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 byte-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 native output type. */
45      private static final String UNRECOGNISED_NATIVE_TYPE = "Unrecognised native output type: ";
46      /** Message when not a RandomLongSource. */
47      private static final String NOT_LONG_SOURCE = "Not a 64-bit long generator: ";
48  
49      /** No public construction. */
50      private RNGUtils() {}
51  
52      /**
53       * Wrap the random generator with a new instance that will reverse the byte order of
54       * the native type. The input must be either a {@link RandomIntSource} or
55       * {@link RandomLongSource}.
56       *
57       * @param rng The random generator.
58       * @return the byte reversed random generator.
59       * @throws ApplicationException If the input source native type is not recognised.
60       * @see Integer#reverseBytes(int)
61       * @see Long#reverseBytes(long)
62       */
63      static UniformRandomProvider createReverseBytesProvider(final UniformRandomProvider rng) {
64          if (rng instanceof RandomIntSource) {
65              return new IntProvider() {
66                  @Override
67                  public int next() {
68                      return Integer.reverseBytes(rng.nextInt());
69                  }
70  
71                  @Override
72                  public String toString() {
73                      return BYTE_REVERSED + rng.toString();
74                  }
75              };
76          }
77          if (rng instanceof RandomLongSource) {
78              return new LongProvider() {
79                  @Override
80                  public long next() {
81                      return Long.reverseBytes(rng.nextLong());
82                  }
83  
84                  @Override
85                  public String toString() {
86                      return BYTE_REVERSED + rng.toString();
87                  }
88              };
89          }
90          throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng);
91      }
92  
93      /**
94       * Wrap the random generator with a new instance that will reverse the bits of
95       * the native type. The input must be either a {@link RandomIntSource} or
96       * {@link RandomLongSource}.
97       *
98       * @param rng The random generator.
99       * @return the bit reversed random generator.
100      * @throws ApplicationException If the input source native type is not recognised.
101      * @see Integer#reverse(int)
102      * @see Long#reverse(long)
103      */
104     static UniformRandomProvider createReverseBitsProvider(final UniformRandomProvider rng) {
105         if (rng instanceof RandomIntSource) {
106             return new IntProvider() {
107                 @Override
108                 public int next() {
109                     return Integer.reverse(rng.nextInt());
110                 }
111 
112                 @Override
113                 public String toString() {
114                     return BIT_REVERSED + rng.toString();
115                 }
116             };
117         }
118         if (rng instanceof RandomLongSource) {
119             return new LongProvider() {
120                 @Override
121                 public long next() {
122                     return Long.reverse(rng.nextLong());
123                 }
124 
125                 @Override
126                 public String toString() {
127                     return BIT_REVERSED + rng.toString();
128                 }
129             };
130         }
131         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng);
132     }
133 
134     /**
135      * Wrap the random generator with an {@link IntProvider} that will use
136      * {@link UniformRandomProvider#nextInt()}.
137      * An input {@link RandomIntSource} is returned unmodified.
138      *
139      * @param rng The random generator.
140      * @return the int random generator.
141      */
142     static UniformRandomProvider createIntProvider(final UniformRandomProvider rng) {
143         if (!(rng instanceof RandomIntSource)) {
144             return new IntProvider() {
145                 @Override
146                 public int next() {
147                     return rng.nextInt();
148                 }
149 
150                 @Override
151                 public String toString() {
152                     return "Int bits " + rng.toString();
153                 }
154             };
155         }
156         return rng;
157     }
158 
159     /**
160      * Wrap the random generator with an {@link IntProvider} that will use the upper
161      * 32-bits of the {@code long} from {@link UniformRandomProvider#nextLong()}.
162      * The input must be a {@link RandomLongSource}.
163      *
164      * @param rng The random generator.
165      * @return the upper bits random generator.
166      * @throws ApplicationException If the input source native type is not 64-bit.
167      */
168     static UniformRandomProvider createLongUpperBitsIntProvider(final UniformRandomProvider rng) {
169         if (rng instanceof RandomLongSource) {
170             return new IntProvider() {
171                 @Override
172                 public int next() {
173                     return (int) (rng.nextLong() >>> 32);
174                 }
175 
176                 @Override
177                 public String toString() {
178                     return "Long upper-bits " + rng.toString();
179                 }
180             };
181         }
182         throw new ApplicationException(NOT_LONG_SOURCE + rng);
183     }
184 
185     /**
186      * Wrap the random generator with an {@link IntProvider} that will use the lower
187      * 32-bits of the {@code long} from {@link UniformRandomProvider#nextLong()}.
188      * The input must be a {@link RandomLongSource}.
189      *
190      * @param rng The random generator.
191      * @return the lower bits random generator.
192      * @throws ApplicationException If the input source native type is not 64-bit.
193      */
194     static UniformRandomProvider createLongLowerBitsIntProvider(final UniformRandomProvider rng) {
195         if (rng instanceof RandomLongSource) {
196             return new IntProvider() {
197                 @Override
198                 public int next() {
199                     return (int) rng.nextLong();
200                 }
201 
202                 @Override
203                 public String toString() {
204                     return "Long lower-bits " + rng.toString();
205                 }
206             };
207         }
208         throw new ApplicationException(NOT_LONG_SOURCE + rng);
209     }
210 
211     /**
212      * Wrap the random generator with a new instance that will combine the bits
213      * using a {@code xor} operation with a generated hash code. The input must be either
214      * a {@link RandomIntSource} or {@link RandomLongSource}.
215      *
216      * <pre>
217      * {@code
218      * System.identityHashCode(new Object()) ^ rng.nextInt()
219      * }
220      * </pre>
221      *
222      * Note: This generator will be slow.
223      *
224      * @param rng The random generator.
225      * @return the combined random generator.
226      * @throws ApplicationException If the input source native type is not recognised.
227      * @see System#identityHashCode(Object)
228      */
229     static UniformRandomProvider createHashCodeProvider(final UniformRandomProvider rng) {
230         if (rng instanceof RandomIntSource) {
231             return new IntProvider() {
232                 @Override
233                 public int next() {
234                     return System.identityHashCode(new Object()) ^ rng.nextInt();
235                 }
236 
237                 @Override
238                 public String toString() {
239                     return HASH_CODE + rng.toString();
240                 }
241             };
242         }
243         if (rng instanceof RandomLongSource) {
244             return new LongProvider() {
245                 @Override
246                 public long next() {
247                     final long mix = NumberFactory.makeLong(System.identityHashCode(new Object()),
248                                                             System.identityHashCode(new Object()));
249                     return mix ^ rng.nextLong();
250                 }
251 
252                 @Override
253                 public String toString() {
254                     return HASH_CODE + rng.toString();
255                 }
256             };
257         }
258         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng);
259     }
260 
261     /**
262      * Wrap the random generator with a new instance that will combine the bits
263      * using a {@code xor} operation with the output from {@link ThreadLocalRandom}.
264      * The input must be either a {@link RandomIntSource} or {@link RandomLongSource}.
265      *
266      * <pre>
267      * {@code
268      * ThreadLocalRandom.current().nextInt() ^ rng.nextInt()
269      * }
270      * </pre>
271      *
272      * @param rng The random generator.
273      * @return the combined random generator.
274      * @throws ApplicationException If the input source native type is not recognised.
275      */
276     static UniformRandomProvider createThreadLocalRandomProvider(final UniformRandomProvider rng) {
277         if (rng instanceof RandomIntSource) {
278             return new IntProvider() {
279                 @Override
280                 public int next() {
281                     return ThreadLocalRandom.current().nextInt() ^ rng.nextInt();
282                 }
283 
284                 @Override
285                 public String toString() {
286                     return TLR_MIXED + rng.toString();
287                 }
288             };
289         }
290         if (rng instanceof RandomLongSource) {
291             return new LongProvider() {
292                 @Override
293                 public long next() {
294                     return ThreadLocalRandom.current().nextLong() ^ rng.nextLong();
295                 }
296 
297                 @Override
298                 public String toString() {
299                     return TLR_MIXED + rng.toString();
300                 }
301             };
302         }
303         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng);
304     }
305 
306     /**
307      * Combine the two random generators using a {@code xor} operations.
308      * The input must be either a {@link RandomIntSource} or {@link RandomLongSource}.
309      * The returned type will match the native output type of {@code rng1}.
310      *
311      * <pre>
312      * {@code
313      * rng1.nextInt() ^ rng2.nextInt()
314      * }
315      * </pre>
316      *
317      * @param rng1 The first random generator.
318      * @param rng2 The second random generator.
319      * @return the combined random generator.
320      * @throws ApplicationException If the input source native type is not recognised.
321      */
322     static UniformRandomProvider createXorProvider(final UniformRandomProvider rng1,
323         final UniformRandomProvider rng2) {
324         if (rng1 instanceof RandomIntSource) {
325             return new IntProvider() {
326                 @Override
327                 public int next() {
328                     return rng1.nextInt() ^ rng2.nextInt();
329                 }
330 
331                 @Override
332                 public String toString() {
333                     return rng1.toString() + XOR + rng2.toString();
334                 }
335             };
336         }
337         if (rng1 instanceof RandomLongSource) {
338             return new LongProvider() {
339                 @Override
340                 public long next() {
341                     return rng1.nextLong() ^ rng2.nextLong();
342                 }
343 
344                 @Override
345                 public String toString() {
346                     return rng1.toString() + XOR + rng2.toString();
347                 }
348             };
349         }
350         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng1);
351     }
352 
353     /**
354      * Create a new instance to write batches of byte data from the specified RNG to the
355      * specified output stream.
356      *
357      * <p>This will detect the native output type of the RNG and create an appropriate
358      * data output for the raw bytes. The input must be either a {@link RandomIntSource} or
359      * {@link RandomLongSource}.</p>
360      *
361      * <p>If the RNG is a {@link RandomLongSource} then the byte output can be 32-bit or 64-bit.
362      * If 32-bit then the 64-bit output will be written as if 2 {@code int} values were generated
363      * sequentially from the upper then lower 32-bits of the {@code long}. This setting is
364      * significant depending on the byte order. If using the Java standard big-endian
365      * representation the flag has no effect and the output will be the same. If using little
366      * endian the output bytes will be written as:</p>
367      *
368      * <pre>
369      * 76543210  ->  4567  0123
370      * </pre>
371      *
372      * @param rng The random generator.
373      * @param raw64 Set to true for 64-bit byte output.
374      * @param out Output stream.
375      * @param byteSize Number of bytes values to write.
376      * @param byteOrder Byte order.
377      * @return the data output
378      * @throws ApplicationException If the input source native type is not recognised.
379      */
380     static RngDataOutput createDataOutput(final UniformRandomProvider rng, boolean raw64,
381         OutputStream out, int byteSize, ByteOrder byteOrder) {
382         if (rng instanceof RandomIntSource) {
383             return RngDataOutput.ofInt(out, byteSize / 4, byteOrder);
384         }
385         if (rng instanceof RandomLongSource) {
386             return raw64 ?
387                 RngDataOutput.ofLong(out, byteSize / 8, byteOrder) :
388                 RngDataOutput.ofLongAsInt(out, byteSize / 8, byteOrder);
389         }
390         throw new ApplicationException(UNRECOGNISED_NATIVE_TYPE + rng);
391     }
392 
393     /**
394      * Parses the argument into an object suitable for the RandomSource constructor. Supports:
395      *
396      * <ul>
397      *   <li>Integer
398      * </ul>
399      *
400      * @param argument the argument
401      * @return the object
402      * @throws ApplicationException If the argument is not recognised
403      */
404     static Object parseArgument(String argument) {
405         try {
406             // Currently just support TWO_CMRES_SELECT which uses integers.
407             // Future RandomSource implementations may require other parsing, for example
408             // recognising a long by the suffix 'L'. This functionality
409             // could use Commons Lang NumberUtils.createNumber(String).
410             return Integer.parseInt(argument);
411         } catch (final NumberFormatException ex) {
412             throw new ApplicationException("Failed to parse RandomSource argument: " + argument, ex);
413         }
414     }
415 }