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