001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.rng.core.source64;
019
020import java.util.stream.Stream;
021import org.apache.commons.rng.JumpableUniformRandomProvider;
022import org.apache.commons.rng.SplittableUniformRandomProvider;
023import org.apache.commons.rng.UniformRandomProvider;
024import org.apache.commons.rng.core.util.NumberFactory;
025import org.apache.commons.rng.core.util.RandomStreams;
026
027/**
028 * A 64-bit all purpose generator.
029 *
030 * <p>This is a member of the LXM family of generators: L=Linear congruential generator;
031 * X=Xor based generator; and M=Mix. This member uses a 64-bit LCG and 256-bit Xor-based
032 * generator. It is named as {@code "L64X256MixRandom"} in the {@code java.util.random}
033 * package introduced in JDK 17; the LXM family is described in further detail in:
034 *
035 * <blockquote>Steele and Vigna (2021) LXM: better splittable pseudorandom number generators
036 * (and almost as fast). Proceedings of the ACM on Programming Languages, Volume 5,
037 * Article 148, pp 1–31.</blockquote>
038 *
039 * <p>Memory footprint is 384 bits and the period is 2<sup>64</sup> (2<sup>256</sup> - 1).
040 *
041 * <p>This generator implements
042 * {@link org.apache.commons.rng.LongJumpableUniformRandomProvider LongJumpableUniformRandomProvider}.
043 * In addition instances created with a different additive parameter for the LCG are robust
044 * against accidental correlation in a multi-threaded setting. The additive parameters must be
045 * different in the most significant 63-bits.
046 *
047 * <p>This generator implements
048 * {@link org.apache.commons.rng.SplittableUniformRandomProvider SplittableUniformRandomProvider}.
049 * The stream of generators created using the {@code splits} methods support parallelisation
050 * and are robust against accidental correlation by using unique values for the additive parameter
051 * for each instance in the same stream. The primitive streaming methods support parallelisation
052 * but with no assurances of accidental correlation; each thread uses a new instance with a
053 * randomly initialised state.
054 *
055 * @see <a href="https://doi.org/10.1145/3485525">Steele &amp; Vigna (2021) Proc. ACM Programming
056 *      Languages 5, 1-31</a>
057 * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/package-summary.html">
058 *      JDK 17 java.util.random javadoc</a>
059 * @since 1.5
060 */
061public class L64X256Mix extends AbstractL64 implements SplittableUniformRandomProvider {
062    /** Size of the seed vector. */
063    private static final int SEED_SIZE = 6;
064    /** Size of the XBG state vector. */
065    private static final int XBG_STATE_SIZE = 4;
066    /** LCG multiplier. */
067    private static final long M = LXMSupport.M64;
068
069    /** State 0 of the XBG. */
070    private long x0;
071    /** State 1 of the XBG. */
072    private long x1;
073    /** State 2 of the XBG. */
074    private long x2;
075    /** State 3 of the XBG. */
076    private long x3;
077
078    /**
079     * Creates a new instance.
080     *
081     * @param seed Initial seed.
082     * If the length is larger than 6, only the first 6 elements will
083     * be used; if smaller, the remaining elements will be automatically
084     * set. A seed containing all zeros in the last four elements
085     * will create a non-functional XBG sub-generator and a low
086     * quality output with a period of 2<sup>64</sup>.
087     *
088     * <p>The 1st element is used to set the LCG increment; the least significant bit
089     * is set to odd to ensure a full period LCG. The 2nd element is used
090     * to set the LCG state.</p>
091     */
092    public L64X256Mix(long[] seed) {
093        super(seed = extendSeed(seed, SEED_SIZE));
094        x0 = seed[2];
095        x1 = seed[3];
096        x2 = seed[4];
097        x3 = seed[5];
098    }
099
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}