blob: 359898569c55c333fe18bb341fac006b7d644839 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.rng.core.source32;
import java.util.SplittableRandom;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
/**
* Tests for {@link LXMSupport}.
*
* <p>Note: The LXM generators require the LCG component to be advanced in a single
* jump operation by half the period 2<sup>k/2</sup>, where {@code k} is the LCG
* state size. This is performed in a single step using coefficients {@code m'} and
* {@code c'}.
*
* <p>This class contains a generic algorithm to compute an arbitrary
* LCG update for any power of 2. This can be bootstrap tested using small powers of
* 2 by manual LCG iteration. Small powers can then be used to verify increasingly
* large powers. The main {@link LXMSupport} class contains precomputed coefficients
* {@code m'} and a precursor to {@code c'} for a jump of 2<sup>k/2</sup>
* assuming the multiplier {@code m} used in the LXM generators. The coefficient
* {@code c'} is computed by multiplying by the generator additive constant. The output
* coefficients have the following properties:
* <ul>
* <li>The multiplier {@code m'} is constant.
* <li>The lower half of the multiplier {@code m'} is 1.
* <li>The lower half of the additive parameter {@code c'} is 0.
* <li>The upper half of the additive parameter {@code c'} is odd when {@code c} is odd.
* </ul>
*/
class LXMSupportTest {
/** A mask to clear the lower 5 bits of an integer. */
private static final int CLEAR_LOWER_5 = -1 << 5;
@Test
void testLea32() {
// Code generated using the reference c code provided by Guy Steele.
// Note: A java implementation requires using the unsigned shift '>>>' operator.
//
// uint32_t lea32(uint32_t z) {
// z = (z ^ (z >> 16));
// z *= 0xd36d884b;
// z = (z ^ (z >> 16));
// z *= 0xd36d884b;
// return z ^ (z >> 16);
// }
final int[] expected = {
0x4fe04eac, 0x7bc5cb6c, 0x29af7e05, 0xf80de147,
0xb90bc13a, 0x6fbce371, 0x3dbbfab0, 0xcf366cd9,
0x90c9c2a2, 0x988ea20f, 0x75fc2207, 0x58197217,
0xdbc584a5, 0x5d232d06, 0xd4faec13, 0xfa1fb8fd,
0xea45a9d4, 0xb2bdb43c, 0x0502b325, 0x2ebca83c,
0x3337cf53, 0x5531f920, 0x3edf02c5, 0xa9b79cf7,
0x80ff2c21, 0xaba7498f, 0xa7dd9739, 0xb85c77ea,
0x4e846134, 0x99461d4e, 0x87c027b1, 0xc8c37ec7,
0x457301bc, 0xa8d33a18, 0x87420fcc, 0x1c5a5b6d,
0x44b0e3ff, 0x45459875, 0x99d6ef70, 0x12e06019,
};
int state = 0x012de1ba;
final int increment = 0xc8161b42;
for (final int e : expected) {
Assertions.assertEquals(e, LXMSupport.lea32(state += increment));
}
}
@ParameterizedTest
@CsvSource({
"235642368, 72987979, 792134597",
// LXM 32-bit multiplier:
// -1380669139 == 0xadb4a92d
"-1380669139, 617328132, 236746283",
"-1380669139, -789374989, -180293891",
"-1380669139, 67236828, 236784628",
"-1380669139, -13421542, 42",
})
void testLcgAdvancePow2(int m, int c, int state) {
// Bootstrap the first powers
int s = state;
for (int i = 0; i < 1; i++) {
s = m * s + c;
}
Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 0), "2^0 cycles");
for (int i = 0; i < 1; i++) {
s = m * s + c;
}
Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 1), "2^1 cycles");
for (int i = 0; i < 2; i++) {
s = m * s + c;
}
Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 2), "2^2 cycles");
for (int i = 0; i < 4; i++) {
s = m * s + c;
}
Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 3), "2^3 cycles");
// Larger powers should align
for (int n = 3; n < 31; n++) {
final int n1 = n + 1;
Assertions.assertEquals(
lcgAdvancePow2(lcgAdvancePow2(state, m, c, n), m, c, n),
lcgAdvancePow2(state, m, c, n1), () -> "2^" + n1 + " cycles");
}
// Larger/negative powers are ignored
for (final int i : new int[] {32, 67868, Integer.MAX_VALUE, Integer.MIN_VALUE, -26762, -2, -1}) {
final int n = i;
Assertions.assertEquals(state, lcgAdvancePow2(state, m, c, n),
() -> "2^" + n + " cycles");
}
}
@Test
void testLcg32Advance2Pow16Constants() {
// Computing with a addition of 1 will compute:
// m^(2^16)
// product {m^(2^i) + 1} for i in [0, 15]
final int[] out = new int[2];
lcgAdvancePow2(LXMSupport.M32, 1, 16, out);
Assertions.assertEquals(LXMSupport.M32P, out[0], "m'");
Assertions.assertEquals(LXMSupport.C32P, out[1], "c'");
// Check the special values of the low half
Assertions.assertEquals(1, out[0] & 0xffff, "low m'");
Assertions.assertEquals(0, out[1] & 0xffff, "low c'");
}
/**
* Test the precomputed 32-bit LCG advance constant in {@link LXMSupport} can be used
* to compute the same jump as the bootstrap tested generic version in this class.
*/
@Test
void testLcgAdvance2Pow16() {
final SplittableRandom r = new SplittableRandom();
final int[] out = new int[2];
for (int i = 0; i < 2000; i++) {
// Must be odd
final int c = r.nextInt() | 1;
lcgAdvancePow2(LXMSupport.M32, c, 16, out);
final int a = out[1];
// Test assumptions
Assertions.assertEquals(1, (a >>> 16) & 0x1, "High half c' should be odd");
Assertions.assertEquals(0, a & 0xffff, "Low half c' should be 0");
// This value can be computed from the constant
Assertions.assertEquals(a, LXMSupport.C32P * c);
}
}
/**
* Compute the multiplier {@code m'} and addition {@code c'} to advance the state of a
* 32-bit Linear Congruential Generator (LCG) a number of consecutive steps:
*
* <pre>
* s = m' * s + c'
* </pre>
*
* <p>A number of consecutive steps can be computed in a single multiply and add
* operation. This method computes the accumulated multiplier and addition for the
* given number of steps expressed as a power of 2. Provides support to advance for
* 2<sup>k</sup> for {@code k in [0, 31)}. Any power {@code >= 32} is ignored as this
* would wrap the generator to the same point. Negative powers are ignored but do not
* throw an exception.
*
* <p>Based on the algorithm from:
*
* <blockquote>Brown, F.B. (1994) Random number generation with arbitrary strides,
* Transactions of the American Nuclear Society 71.</blockquote>
*
* @param m Multiplier
* @param c Constant
* @param k Number of advance steps as a power of 2 (range [0, 31])
* @param out Output result [m', c']
* @see <A
* href="https://www.osti.gov/biblio/89100-random-number-generation-arbitrary-strides">
* Brown, F.B. (1994) Random number generation with arbitrary strides, Transactions of
* the American Nuclear Society 71</a>
*/
private static void lcgAdvancePow2(int m, int c, int k, int[] out) {
// If any bits above the first 5 are set then this would wrap the generator to
// the same point as multiples of period (2^32).
// It also identifies negative powers to ignore.
if ((k & CLEAR_LOWER_5) != 0) {
// m'=1, c'=0
out[0] = 1;
out[1] = 0;
return;
}
int mp = m;
int a = c;
for (int i = k; i != 0; i--) {
// Update the multiplier and constant for the next power of 2
a = (mp + 1) * a;
mp *= mp;
}
out[0] = mp;
out[1] = a;
}
/**
* Compute the advanced state of a 32-bit Linear Congruential Generator (LCG). The
* base generator advance step is:
*
* <pre>
* s = m * s + c
* </pre>
*
* <p>A number of consecutive steps can be computed in a single multiply and add
* operation. This method computes the update coefficients and applies them to the
* given state.
*
* <p>This method is used for testing only. For arbitrary jumps an efficient implementation
* would inline the computation of the update coefficients; or for repeat jumps of the same
* size pre-compute the coefficients once.
*
* <p>This is package-private for use in {@link AbstractLXMTest} to provide jump functionality
* to a composite LXM generator.
*
* @param s State
* @param m Multiplier
* @param c Constant
* @param k Number of advance steps as a power of 2 (range [0, 31])
* @return the new state
* @see <A
* href="https://www.osti.gov/biblio/89100-random-number-generation-arbitrary-strides">
* Brown, F.B. (1994) Random number generation with arbitrary strides, Transactions of
* the American Nuclear Society 71</a>
*/
static int lcgAdvancePow2(int s, int m, int c, int k) {
final int[] out = new int[2];
lcgAdvancePow2(m, c, k, out);
final int mp = out[0];
final int ap = out[1];
return mp * s + ap;
}
}