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
18 package org.apache.commons.rng.core.source64;
19
20 import java.util.stream.Stream;
21 import org.apache.commons.rng.JumpableUniformRandomProvider;
22 import org.apache.commons.rng.SplittableUniformRandomProvider;
23 import org.apache.commons.rng.UniformRandomProvider;
24 import org.apache.commons.rng.core.util.NumberFactory;
25 import org.apache.commons.rng.core.util.RandomStreams;
26
27 /**
28 * A 64-bit all purpose generator.
29 *
30 * <p>This is a member of the LXM family of generators: L=Linear congruential generator;
31 * X=Xor based generator; and M=Mix. This member uses a 128-bit LCG and 256-bit Xor-based
32 * generator. It is named as {@code "L128X256MixRandom"} in the {@code java.util.random}
33 * package introduced in JDK 17; the LXM family is described in further detail in:
34 *
35 * <blockquote>Steele and Vigna (2021) LXM: better splittable pseudorandom number generators
36 * (and almost as fast). Proceedings of the ACM on Programming Languages, Volume 5,
37 * Article 148, pp 1–31.</blockquote>
38 *
39 * <p>Memory footprint is 512 bits and the period is 2<sup>128</sup> (2<sup>256</sup> - 1).
40 *
41 * <p>This generator implements
42 * {@link org.apache.commons.rng.LongJumpableUniformRandomProvider LongJumpableUniformRandomProvider}.
43 * In addition instances created with a different additive parameter for the LCG are robust
44 * against accidental correlation in a multi-threaded setting. The additive parameters must be
45 * different in the most significant 127-bits.
46 *
47 * <p>This generator implements
48 * {@link org.apache.commons.rng.SplittableUniformRandomProvider SplittableUniformRandomProvider}.
49 * The stream of generators created using the {@code splits} methods support parallelisation
50 * and are robust against accidental correlation by using unique values for the additive parameter
51 * for each instance in the same stream. The primitive streaming methods support parallelisation
52 * but with no assurances of accidental correlation; each thread uses a new instance with a
53 * randomly initialised state.
54 *
55 * @see <a href="https://doi.org/10.1145/3485525">Steele & Vigna (2021) Proc. ACM Programming
56 * Languages 5, 1-31</a>
57 * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/package-summary.html">
58 * JDK 17 java.util.random javadoc</a>
59 * @since 1.5
60 */
61 public class L128X256Mix extends AbstractL128 implements SplittableUniformRandomProvider {
62 /** Size of the seed vector. */
63 private static final int SEED_SIZE = 8;
64 /** Size of the XBG state vector. */
65 private static final int XBG_STATE_SIZE = 4;
66 /** Low half of 128-bit LCG multiplier. */
67 private static final long ML = LXMSupport.M128L;
68
69 /** State 0 of the XBG. */
70 private long x0;
71 /** State 1 of the XBG. */
72 private long x1;
73 /** State 2 of the XBG. */
74 private long x2;
75 /** State 3 of the XBG. */
76 private long x3;
77
78 /**
79 * Creates a new instance.
80 *
81 * @param seed Initial seed.
82 * If the length is larger than 8, only the first 8 elements will
83 * be used; if smaller, the remaining elements will be automatically
84 * set. A seed containing all zeros in the last four elements
85 * will create a non-functional XBG sub-generator and a low
86 * quality output with a period of 2<sup>128</sup>.
87 *
88 * <p>The 1st and 2nd elements are used to set the LCG increment; the least significant bit
89 * is set to odd to ensure a full period LCG. The 3rd and 4th elements are used
90 * to set the LCG state.</p>
91 */
92 public L128X256Mix(long[] seed) {
93 super(seed = extendSeed(seed, SEED_SIZE));
94 x0 = seed[4];
95 x1 = seed[5];
96 x2 = seed[6];
97 x3 = seed[7];
98 }
99
100 /**
101 * Creates a new instance using an 8 element seed.
102 * A seed containing all zeros in the last four elements
103 * will create a non-functional XBG sub-generator and a low
104 * quality output with a period of 2<sup>128</sup>.
105 *
106 * <p>The 1st and 2nd elements are used to set the LCG increment; the least significant bit
107 * is set to odd to ensure a full period LCG. The 3rd and 4th elements are used
108 * to set the LCG state.</p>
109 *
110 * @param seed0 Initial seed element 0.
111 * @param seed1 Initial seed element 1.
112 * @param seed2 Initial seed element 2.
113 * @param seed3 Initial seed element 3.
114 * @param seed4 Initial seed element 4.
115 * @param seed5 Initial seed element 5.
116 * @param seed6 Initial seed element 6.
117 * @param seed7 Initial seed element 7.
118 */
119 public L128X256Mix(long seed0, long seed1, long seed2, long seed3,
120 long seed4, long seed5, long seed6, long seed7) {
121 super(seed0, seed1, seed2, seed3);
122 x0 = seed4;
123 x1 = seed5;
124 x2 = seed6;
125 x3 = seed7;
126 }
127
128 /**
129 * Creates a copy instance.
130 *
131 * @param source Source to copy.
132 */
133 protected L128X256Mix(L128X256Mix source) {
134 super(source);
135 x0 = source.x0;
136 x1 = source.x1;
137 x2 = source.x2;
138 x3 = source.x3;
139 }
140
141 /** {@inheritDoc} */
142 @Override
143 protected byte[] getStateInternal() {
144 return composeStateInternal(NumberFactory.makeByteArray(
145 new long[] {x0, x1, x2, x3}),
146 super.getStateInternal());
147 }
148
149 /** {@inheritDoc} */
150 @Override
151 protected void setStateInternal(byte[] s) {
152 final byte[][] c = splitStateInternal(s, XBG_STATE_SIZE * Long.BYTES);
153 final long[] tmp = NumberFactory.makeLongArray(c[0]);
154 x0 = tmp[0];
155 x1 = tmp[1];
156 x2 = tmp[2];
157 x3 = tmp[3];
158 super.setStateInternal(c[1]);
159 }
160
161 /** {@inheritDoc} */
162 @Override
163 public long next() {
164 // LXM generate.
165 // Old state is used for the output allowing parallel pipelining
166 // on processors that support multiple concurrent instructions.
167
168 long s0 = x0;
169 final long sh = lsh;
170
171 // Mix
172 final long z = LXMSupport.lea64(sh + s0);
173
174 // LCG update
175 // The LCG is, in effect, "s = m * s + a" where m = ((1LL << 64) + ML)
176 final long sl = lsl;
177 final long al = lal;
178 final long u = ML * sl;
179 // High half
180 lsh = ML * sh + LXMSupport.unsignedMultiplyHigh(ML, sl) + sl + lah +
181 // Carry propagation
182 LXMSupport.unsignedAddHigh(u, al);
183 // Low half
184 lsl = u + al;
185
186 // XBG update
187 long s1 = x1;
188 long s2 = x2;
189 long s3 = x3;
190
191 final long t = s1 << 17;
192
193 s2 ^= s0;
194 s3 ^= s1;
195 s1 ^= s2;
196 s0 ^= s3;
197
198 s2 ^= t;
199
200 s3 = Long.rotateLeft(s3, 45);
201
202 x0 = s0;
203 x1 = s1;
204 x2 = s2;
205 x3 = s3;
206
207 return z;
208 }
209
210 /**
211 * {@inheritDoc}
212 *
213 * <p>The jump size is the equivalent of moving the state <em>backwards</em> by
214 * (2<sup>256</sup> - 1) positions. It can provide up to 2<sup>128</sup>
215 * non-overlapping subsequences.
216 */
217 @Override
218 public UniformRandomProvider jump() {
219 return super.jump();
220 }
221
222 /**
223 * {@inheritDoc}
224 *
225 * <p>The jump size is the equivalent of moving the state <em>backwards</em> by
226 * 2<sup>64</sup> (2<sup>256</sup> - 1) positions. It can provide up to
227 * 2<sup>64</sup> non-overlapping subsequences of length 2<sup>64</sup>
228 * (2<sup>256</sup> - 1); each subsequence can provide up to 2<sup>64</sup>
229 * non-overlapping subsequences of length (2<sup>256</sup> - 1) using the
230 * {@link #jump()} method.
231 */
232 @Override
233 public JumpableUniformRandomProvider longJump() {
234 return super.longJump();
235 }
236
237 /** {@inheritDoc} */
238 @Override
239 AbstractL128 copy() {
240 // This exists to ensure the jump function performed in the super class returns
241 // the correct class type. It should not be public.
242 return new L128X256Mix(this);
243 }
244
245 /** {@inheritDoc} */
246 @Override
247 public SplittableUniformRandomProvider split(UniformRandomProvider source) {
248 return create(source.nextLong(), source);
249 }
250
251 /** {@inheritDoc} */
252 @Override
253 public Stream<SplittableUniformRandomProvider> splits(long streamSize, SplittableUniformRandomProvider source) {
254 return RandomStreams.generateWithSeed(streamSize, source, L128X256Mix::create);
255 }
256
257 /**
258 * Create a new instance using the given {@code seed} and {@code source} of randomness
259 * to initialise the instance.
260 *
261 * @param seed Seed used to initialise the instance.
262 * @param source Source of randomness used to initialise the instance.
263 * @return A new instance.
264 */
265 private static SplittableUniformRandomProvider create(long seed, UniformRandomProvider source) {
266 // LCG state. The addition lower-half uses the input seed.
267 // The LCG addition parameter is set to odd so left-shift the seed.
268 final long s0 = source.nextLong();
269 final long s1 = seed << 1;
270 final long s2 = source.nextLong();
271 final long s3 = source.nextLong();
272 // XBG state must not be all zero
273 long x0 = source.nextLong();
274 long x1 = source.nextLong();
275 long x2 = source.nextLong();
276 long x3 = source.nextLong();
277 if ((x0 | x1 | x2 | x3) == 0) {
278 // SplitMix style seed ensures at least one non-zero value
279 long z = s3;
280 x0 = LXMSupport.lea64(z);
281 x1 = LXMSupport.lea64(z += LXMSupport.GOLDEN_RATIO_64);
282 x2 = LXMSupport.lea64(z += LXMSupport.GOLDEN_RATIO_64);
283 x3 = LXMSupport.lea64(z + LXMSupport.GOLDEN_RATIO_64);
284 }
285 // The LCG addition parameter is set to odd so left-shift the seed
286 return new L128X256Mix(s0, s1, s2, s3, x0, x1, x2, x3);
287 }
288 }