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 }