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 64-bit LCG and 256-bit Xor-based
32 * generator. It is named as {@code "L64X256MixRandom"} 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 384 bits and the period is 2<sup>64</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 63-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 L64X256Mix extends AbstractL64 implements SplittableUniformRandomProvider {
62 /** Size of the seed vector. */
63 private static final int SEED_SIZE = 6;
64 /** Size of the XBG state vector. */
65 private static final int XBG_STATE_SIZE = 4;
66 /** LCG multiplier. */
67 private static final long M = LXMSupport.M64;
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 6, only the first 6 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>64</sup>.
87 *
88 * <p>The 1st element is used to set the LCG increment; the least significant bit
89 * is set to odd to ensure a full period LCG. The 2nd element is used
90 * to set the LCG state.</p>
91 */
92 public L64X256Mix(long[] seed) {
93 super(seed = extendSeed(seed, SEED_SIZE));
94 x0 = seed[2];
95 x1 = seed[3];
96 x2 = seed[4];
97 x3 = seed[5];
98 }
99
100 /**
101 * Creates a new instance using a 6 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>64</sup>.
105 *
106 * <p>The 1st element is used to set the LCG increment; the least significant bit
107 * is set to odd to ensure a full period LCG. The 2nd element is 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 */
117 public L64X256Mix(long seed0, long seed1, long seed2, long seed3,
118 long seed4, long seed5) {
119 super(seed0, seed1);
120 x0 = seed2;
121 x1 = seed3;
122 x2 = seed4;
123 x3 = seed5;
124 }
125
126 /**
127 * Creates a copy instance.
128 *
129 * @param source Source to copy.
130 */
131 protected L64X256Mix(L64X256Mix source) {
132 super(source);
133 x0 = source.x0;
134 x1 = source.x1;
135 x2 = source.x2;
136 x3 = source.x3;
137 }
138
139 /** {@inheritDoc} */
140 @Override
141 protected byte[] getStateInternal() {
142 return composeStateInternal(NumberFactory.makeByteArray(
143 new long[] {x0, x1, x2, x3}),
144 super.getStateInternal());
145 }
146
147 /** {@inheritDoc} */
148 @Override
149 protected void setStateInternal(byte[] s) {
150 final byte[][] c = splitStateInternal(s, XBG_STATE_SIZE * Long.BYTES);
151 final long[] tmp = NumberFactory.makeLongArray(c[0]);
152 x0 = tmp[0];
153 x1 = tmp[1];
154 x2 = tmp[2];
155 x3 = tmp[3];
156 super.setStateInternal(c[1]);
157 }
158
159 /** {@inheritDoc} */
160 @Override
161 public long next() {
162 // LXM generate.
163 // Old state is used for the output allowing parallel pipelining
164 // on processors that support multiple concurrent instructions.
165
166 long s0 = x0;
167 final long s = ls;
168
169 // Mix
170 final long z = LXMSupport.lea64(s + s0);
171
172 // LCG update
173 ls = M * s + la;
174
175 // XBG update
176 long s1 = x1;
177 long s2 = x2;
178 long s3 = x3;
179
180 final long t = s1 << 17;
181
182 s2 ^= s0;
183 s3 ^= s1;
184 s1 ^= s2;
185 s0 ^= s3;
186
187 s2 ^= t;
188
189 s3 = Long.rotateLeft(s3, 45);
190
191 x0 = s0;
192 x1 = s1;
193 x2 = s2;
194 x3 = s3;
195
196 return z;
197 }
198
199 /**
200 * {@inheritDoc}
201 *
202 * <p>The jump size is the equivalent of moving the state <em>backwards</em> by
203 * (2<sup>256</sup> - 1) positions. It can provide up to 2<sup>64</sup>
204 * non-overlapping subsequences.
205 */
206 @Override
207 public UniformRandomProvider jump() {
208 return super.jump();
209 }
210
211 /**
212 * {@inheritDoc}
213 *
214 * <p>The jump size is the equivalent of moving the state <em>backwards</em> by
215 * 2<sup>32</sup> (2<sup>256</sup> - 1) positions. It can provide up to
216 * 2<sup>32</sup> non-overlapping subsequences of length 2<sup>32</sup>
217 * (2<sup>256</sup> - 1); each subsequence can provide up to 2<sup>32</sup>
218 * non-overlapping subsequences of length (2<sup>256</sup> - 1) using the
219 * {@link #jump()} method.
220 */
221 @Override
222 public JumpableUniformRandomProvider longJump() {
223 return super.longJump();
224 }
225
226 /** {@inheritDoc} */
227 @Override
228 AbstractL64 copy() {
229 // This exists to ensure the jump function performed in the super class returns
230 // the correct class type. It should not be public.
231 return new L64X256Mix(this);
232 }
233
234 /** {@inheritDoc} */
235 @Override
236 public SplittableUniformRandomProvider split(UniformRandomProvider source) {
237 return create(source.nextLong(), source);
238 }
239
240 /** {@inheritDoc} */
241 @Override
242 public Stream<SplittableUniformRandomProvider> splits(long streamSize, SplittableUniformRandomProvider source) {
243 return RandomStreams.generateWithSeed(streamSize, source, L64X256Mix::create);
244 }
245
246 /**
247 * Create a new instance using the given {@code seed} and {@code source} of randomness
248 * to initialise the instance.
249 *
250 * @param seed Seed used to initialise the instance.
251 * @param source Source of randomness used to initialise the instance.
252 * @return A new instance.
253 */
254 private static SplittableUniformRandomProvider create(long seed, UniformRandomProvider source) {
255 // LCG state. The addition uses the input seed.
256 // The LCG addition parameter is set to odd so left-shift the seed.
257 final long s0 = seed << 1;
258 final long s1 = source.nextLong();
259 // XBG state must not be all zero
260 long x0 = source.nextLong();
261 long x1 = source.nextLong();
262 long x2 = source.nextLong();
263 long x3 = source.nextLong();
264 if ((x0 | x1 | x2 | x3) == 0) {
265 // SplitMix style seed ensures at least one non-zero value
266 long z = s1;
267 x0 = LXMSupport.lea64(z);
268 x1 = LXMSupport.lea64(z += LXMSupport.GOLDEN_RATIO_64);
269 x2 = LXMSupport.lea64(z += LXMSupport.GOLDEN_RATIO_64);
270 x3 = LXMSupport.lea64(z + LXMSupport.GOLDEN_RATIO_64);
271 }
272 return new L64X256Mix(s0, s1, x0, x1, x2, x3);
273 }
274 }