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 128-bit Xor-based
32 * generator. It is named as {@code "L128X128MixRandom"} 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>128</sup> (2<sup>128</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 L128X128Mix extends AbstractL128 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 = 2;
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
74 /**
75 * Creates a new instance.
76 *
77 * @param seed Initial seed.
78 * If the length is larger than 6, only the first 6 elements will
79 * be used; if smaller, the remaining elements will be automatically
80 * set. A seed containing all zeros in the last four elements
81 * will create a non-functional XBG sub-generator and a low
82 * quality output with a period of 2<sup>128</sup>.
83 *
84 * <p>The 1st and 2nd elements are used to set the LCG increment; the least significant bit
85 * is set to odd to ensure a full period LCG. The 3rd and 4th elements are used
86 * to set the LCG state.</p>
87 */
88 public L128X128Mix(long[] seed) {
89 super(seed = extendSeed(seed, SEED_SIZE));
90 x0 = seed[4];
91 x1 = seed[5];
92 }
93
94 /**
95 * Creates a new instance using a 6 element seed.
96 * A seed containing all zeros in the last four elements
97 * will create a non-functional XBG sub-generator and a low
98 * quality output with a period of 2<sup>128</sup>.
99 *
100 * <p>The 1st and 2nd elements are used to set the LCG increment; the least significant bit
101 * is set to odd to ensure a full period LCG. The 3rd and 4th elements are used
102 * to set the LCG state.</p>
103 *
104 * @param seed0 Initial seed element 0.
105 * @param seed1 Initial seed element 1.
106 * @param seed2 Initial seed element 2.
107 * @param seed3 Initial seed element 3.
108 * @param seed4 Initial seed element 4.
109 * @param seed5 Initial seed element 5.
110 */
111 public L128X128Mix(long seed0, long seed1, long seed2, long seed3,
112 long seed4, long seed5) {
113 super(seed0, seed1, seed2, seed3);
114 x0 = seed4;
115 x1 = seed5;
116 }
117
118 /**
119 * Creates a copy instance.
120 *
121 * @param source Source to copy.
122 */
123 protected L128X128Mix(L128X128Mix source) {
124 super(source);
125 x0 = source.x0;
126 x1 = source.x1;
127 }
128
129 /** {@inheritDoc} */
130 @Override
131 protected byte[] getStateInternal() {
132 return composeStateInternal(NumberFactory.makeByteArray(
133 new long[] {x0, x1}),
134 super.getStateInternal());
135 }
136
137 /** {@inheritDoc} */
138 @Override
139 protected void setStateInternal(byte[] s) {
140 final byte[][] c = splitStateInternal(s, XBG_STATE_SIZE * Long.BYTES);
141 final long[] tmp = NumberFactory.makeLongArray(c[0]);
142 x0 = tmp[0];
143 x1 = tmp[1];
144 super.setStateInternal(c[1]);
145 }
146
147 /** {@inheritDoc} */
148 @Override
149 public long next() {
150 // LXM generate.
151 // Old state is used for the output allowing parallel pipelining
152 // on processors that support multiple concurrent instructions.
153
154 final long s0 = x0;
155 final long sh = lsh;
156
157 // Mix
158 final long z = LXMSupport.lea64(sh + s0);
159
160 // LCG update
161 // The LCG is, in effect, "s = m * s + a" where m = ((1LL << 64) + ML)
162 final long sl = lsl;
163 final long al = lal;
164 final long u = ML * sl;
165 // High half
166 lsh = ML * sh + LXMSupport.unsignedMultiplyHigh(ML, sl) + sl + lah +
167 // Carry propagation
168 LXMSupport.unsignedAddHigh(u, al);
169 // Low half
170 lsl = u + al;
171
172 // XBG update
173 long s1 = x1;
174
175 s1 ^= s0;
176 x0 = Long.rotateLeft(s0, 24) ^ s1 ^ (s1 << 16); // a, b
177 x1 = Long.rotateLeft(s1, 37); // c
178
179 return z;
180 }
181
182 /**
183 * {@inheritDoc}
184 *
185 * <p>The jump size is the equivalent of moving the state <em>backwards</em> by
186 * (2<sup>128</sup> - 1) positions. It can provide up to 2<sup>128</sup>
187 * non-overlapping subsequences.
188 */
189 @Override
190 public UniformRandomProvider jump() {
191 return super.jump();
192 }
193
194 /**
195 * {@inheritDoc}
196 *
197 * <p>The jump size is the equivalent of moving the state <em>backwards</em> by
198 * 2<sup>64</sup> (2<sup>128</sup> - 1) positions. It can provide up to
199 * 2<sup>64</sup> non-overlapping subsequences of length 2<sup>64</sup>
200 * (2<sup>128</sup> - 1); each subsequence can provide up to 2<sup>64</sup>
201 * non-overlapping subsequences of length (2<sup>128</sup> - 1) using the
202 * {@link #jump()} method.
203 */
204 @Override
205 public JumpableUniformRandomProvider longJump() {
206 return super.longJump();
207 }
208
209 /** {@inheritDoc} */
210 @Override
211 AbstractL128 copy() {
212 // This exists to ensure the jump function performed in the super class returns
213 // the correct class type. It should not be public.
214 return new L128X128Mix(this);
215 }
216
217 /** {@inheritDoc} */
218 @Override
219 public SplittableUniformRandomProvider split(UniformRandomProvider source) {
220 return create(source.nextLong(), source);
221 }
222
223 /** {@inheritDoc} */
224 @Override
225 public Stream<SplittableUniformRandomProvider> splits(long streamSize, SplittableUniformRandomProvider source) {
226 return RandomStreams.generateWithSeed(streamSize, source, L128X128Mix::create);
227 }
228
229 /**
230 * Create a new instance using the given {@code seed} and {@code source} of randomness
231 * to initialise the instance.
232 *
233 * @param seed Seed used to initialise the instance.
234 * @param source Source of randomness used to initialise the instance.
235 * @return A new instance.
236 */
237 private static SplittableUniformRandomProvider create(long seed, UniformRandomProvider source) {
238 // LCG state. The addition lower-half uses the input seed.
239 // The LCG addition parameter is set to odd so left-shift the seed.
240 final long s0 = source.nextLong();
241 final long s1 = seed << 1;
242 final long s2 = source.nextLong();
243 final long s3 = source.nextLong();
244 // XBG state must not be all zero
245 long x0 = source.nextLong();
246 long x1 = source.nextLong();
247 if ((x0 | x1) == 0) {
248 // SplitMix style seed ensures at least one non-zero value
249 final long z = s3;
250 x0 = LXMSupport.lea64(z);
251 x1 = LXMSupport.lea64(z + LXMSupport.GOLDEN_RATIO_64);
252 }
253 return new L128X128Mix(s0, s1, s2, s3, x0, x1);
254 }
255 }