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.core.source32;
18  
19  import java.util.SplittableRandom;
20  import org.junit.jupiter.api.Assertions;
21  import org.junit.jupiter.api.Test;
22  import org.junit.jupiter.params.ParameterizedTest;
23  import org.junit.jupiter.params.provider.CsvSource;
24  
25  /**
26   * Tests for {@link LXMSupport}.
27   *
28   * <p>Note: The LXM generators require the LCG component to be advanced in a single
29   * jump operation by half the period 2<sup>k/2</sup>, where {@code k} is the LCG
30   * state size. This is performed in a single step using coefficients {@code m'} and
31   * {@code c'}.
32   *
33   * <p>This class contains a generic algorithm to compute an arbitrary
34   * LCG update for any power of 2. This can be bootstrap tested using small powers of
35   * 2 by manual LCG iteration. Small powers can then be used to verify increasingly
36   * large powers. The main {@link LXMSupport} class contains precomputed coefficients
37   * {@code m'} and a precursor to {@code c'} for a jump of 2<sup>k/2</sup>
38   * assuming the multiplier {@code m} used in the LXM generators. The coefficient
39   * {@code c'} is computed by multiplying by the generator additive constant. The output
40   * coefficients have the following properties:
41   * <ul>
42   * <li>The multiplier {@code m'} is constant.
43   * <li>The lower half of the multiplier {@code m'} is 1.
44   * <li>The lower half of the additive parameter {@code c'} is 0.
45   * <li>The upper half of the additive parameter {@code c'} is odd when {@code c} is odd.
46   * </ul>
47   */
48  class LXMSupportTest {
49      /** A mask to clear the lower 5 bits of an integer. */
50      private static final int CLEAR_LOWER_5 = -1 << 5;
51  
52      @Test
53      void testLea32() {
54          // Code generated using the reference c code provided by Guy Steele.
55          // Note: A java implementation requires using the unsigned shift '>>>' operator.
56          //
57          // uint32_t lea32(uint32_t z) {
58          //    z = (z ^ (z >> 16));
59          //    z *= 0xd36d884b;
60          //    z = (z ^ (z >> 16));
61          //    z *= 0xd36d884b;
62          //    return z ^ (z >> 16);
63          // }
64          final int[] expected = {
65              0x4fe04eac, 0x7bc5cb6c, 0x29af7e05, 0xf80de147,
66              0xb90bc13a, 0x6fbce371, 0x3dbbfab0, 0xcf366cd9,
67              0x90c9c2a2, 0x988ea20f, 0x75fc2207, 0x58197217,
68              0xdbc584a5, 0x5d232d06, 0xd4faec13, 0xfa1fb8fd,
69              0xea45a9d4, 0xb2bdb43c, 0x0502b325, 0x2ebca83c,
70              0x3337cf53, 0x5531f920, 0x3edf02c5, 0xa9b79cf7,
71              0x80ff2c21, 0xaba7498f, 0xa7dd9739, 0xb85c77ea,
72              0x4e846134, 0x99461d4e, 0x87c027b1, 0xc8c37ec7,
73              0x457301bc, 0xa8d33a18, 0x87420fcc, 0x1c5a5b6d,
74              0x44b0e3ff, 0x45459875, 0x99d6ef70, 0x12e06019,
75          };
76          int state = 0x012de1ba;
77          final int increment = 0xc8161b42;
78  
79          for (final int e : expected) {
80              Assertions.assertEquals(e, LXMSupport.lea32(state += increment));
81          }
82      }
83  
84      @ParameterizedTest
85      @CsvSource({
86          "235642368, 72987979, 792134597",
87          // LXM 32-bit multiplier:
88          // -1380669139 == 0xadb4a92d
89          "-1380669139, 617328132, 236746283",
90          "-1380669139, -789374989, -180293891",
91          "-1380669139, 67236828, 236784628",
92          "-1380669139, -13421542, 42",
93      })
94      void testLcgAdvancePow2(int m, int c, int state) {
95          // Bootstrap the first powers
96          int s = state;
97          for (int i = 0; i < 1; i++) {
98              s = m * s + c;
99          }
100         Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 0), "2^0 cycles");
101         for (int i = 0; i < 1; i++) {
102             s = m * s + c;
103         }
104         Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 1), "2^1 cycles");
105         for (int i = 0; i < 2; i++) {
106             s = m * s + c;
107         }
108         Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 2), "2^2 cycles");
109         for (int i = 0; i < 4; i++) {
110             s = m * s + c;
111         }
112         Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 3), "2^3 cycles");
113 
114         // Larger powers should align
115         for (int n = 3; n < 31; n++) {
116             final int n1 = n + 1;
117             Assertions.assertEquals(
118                 lcgAdvancePow2(lcgAdvancePow2(state, m, c, n), m, c, n),
119                 lcgAdvancePow2(state, m, c, n1), () -> "2^" + n1 + " cycles");
120         }
121 
122         // Larger/negative powers are ignored
123         for (final int i : new int[] {32, 67868, Integer.MAX_VALUE, Integer.MIN_VALUE, -26762, -2, -1}) {
124             final int n = i;
125             Assertions.assertEquals(state, lcgAdvancePow2(state, m, c, n),
126                 () -> "2^" + n + " cycles");
127         }
128     }
129 
130     @Test
131     void testLcg32Advance2Pow16Constants() {
132         // Computing with a addition of 1 will compute:
133         // m^(2^16)
134         // product {m^(2^i) + 1}  for i in [0, 15]
135         final int[] out = new int[2];
136         lcgAdvancePow2(LXMSupport.M32, 1, 16, out);
137         Assertions.assertEquals(LXMSupport.M32P, out[0], "m'");
138         Assertions.assertEquals(LXMSupport.C32P, out[1], "c'");
139         // Check the special values of the low half
140         Assertions.assertEquals(1, out[0] & 0xffff, "low m'");
141         Assertions.assertEquals(0, out[1] & 0xffff, "low c'");
142     }
143 
144     /**
145      * Test the precomputed 32-bit LCG advance constant in {@link LXMSupport} can be used
146      * to compute the same jump as the bootstrap tested generic version in this class.
147      */
148     @Test
149     void testLcgAdvance2Pow16() {
150         final SplittableRandom r = new SplittableRandom();
151         final int[] out = new int[2];
152 
153         for (int i = 0; i < 2000; i++) {
154             // Must be odd
155             final int c = r.nextInt() | 1;
156             lcgAdvancePow2(LXMSupport.M32, c, 16, out);
157             final int a = out[1];
158             // Test assumptions
159             Assertions.assertEquals(1, (a >>> 16) & 0x1, "High half c' should be odd");
160             Assertions.assertEquals(0, a & 0xffff, "Low half c' should be 0");
161             // This value can be computed from the constant
162             Assertions.assertEquals(a, LXMSupport.C32P * c);
163         }
164     }
165 
166     /**
167      * Compute the multiplier {@code m'} and addition {@code c'} to advance the state of a
168      * 32-bit Linear Congruential Generator (LCG) a number of consecutive steps:
169      *
170      * <pre>
171      * s = m' * s + c'
172      * </pre>
173      *
174      * <p>A number of consecutive steps can be computed in a single multiply and add
175      * operation. This method computes the accumulated multiplier and addition for the
176      * given number of steps expressed as a power of 2. Provides support to advance for
177      * 2<sup>k</sup> for {@code k in [0, 31)}. Any power {@code >= 32} is ignored as this
178      * would wrap the generator to the same point. Negative powers are ignored but do not
179      * throw an exception.
180      *
181      * <p>Based on the algorithm from:
182      *
183      * <blockquote>Brown, F.B. (1994) Random number generation with arbitrary strides,
184      * Transactions of the American Nuclear Society 71.</blockquote>
185      *
186      * @param m Multiplier
187      * @param c Constant
188      * @param k Number of advance steps as a power of 2 (range [0, 31])
189      * @param out Output result [m', c']
190      * @see <A
191      * href="https://www.osti.gov/biblio/89100-random-number-generation-arbitrary-strides">
192      * Brown, F.B. (1994) Random number generation with arbitrary strides, Transactions of
193      * the American Nuclear Society 71</a>
194      */
195     private static void lcgAdvancePow2(int m, int c, int k, int[] out) {
196         // If any bits above the first 5 are set then this would wrap the generator to
197         // the same point as multiples of period (2^32).
198         // It also identifies negative powers to ignore.
199         if ((k & CLEAR_LOWER_5) != 0) {
200             // m'=1, c'=0
201             out[0] = 1;
202             out[1] = 0;
203             return;
204         }
205 
206         int mp = m;
207         int a = c;
208 
209         for (int i = k; i != 0; i--) {
210             // Update the multiplier and constant for the next power of 2
211             a = (mp + 1) * a;
212             mp *= mp;
213         }
214         out[0] = mp;
215         out[1] = a;
216     }
217 
218     /**
219      * Compute the advanced state of a 32-bit Linear Congruential Generator (LCG). The
220      * base generator advance step is:
221      *
222      * <pre>
223      * s = m * s + c
224      * </pre>
225      *
226      * <p>A number of consecutive steps can be computed in a single multiply and add
227      * operation. This method computes the update coefficients and applies them to the
228      * given state.
229      *
230      * <p>This method is used for testing only. For arbitrary jumps an efficient implementation
231      * would inline the computation of the update coefficients; or for repeat jumps of the same
232      * size pre-compute the coefficients once.
233      *
234      * <p>This is package-private for use in {@link AbstractLXMTest} to provide jump functionality
235      * to a composite LXM generator.
236      *
237      * @param s State
238      * @param m Multiplier
239      * @param c Constant
240      * @param k Number of advance steps as a power of 2 (range [0, 31])
241      * @return the new state
242      * @see <A
243      * href="https://www.osti.gov/biblio/89100-random-number-generation-arbitrary-strides">
244      * Brown, F.B. (1994) Random number generation with arbitrary strides, Transactions of
245      * the American Nuclear Society 71</a>
246      */
247     static int lcgAdvancePow2(int s, int m, int c, int k) {
248         final int[] out = new int[2];
249         lcgAdvancePow2(m, c, k, out);
250         final int mp = out[0];
251         final int ap = out[1];
252         return mp * s + ap;
253     }
254 }