L64X256Mix.java

  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. package org.apache.commons.rng.core.source64;

  18. import java.util.stream.Stream;
  19. import org.apache.commons.rng.JumpableUniformRandomProvider;
  20. import org.apache.commons.rng.SplittableUniformRandomProvider;
  21. import org.apache.commons.rng.UniformRandomProvider;
  22. import org.apache.commons.rng.core.util.NumberFactory;
  23. import org.apache.commons.rng.core.util.RandomStreams;

  24. /**
  25.  * A 64-bit all purpose generator.
  26.  *
  27.  * <p>This is a member of the LXM family of generators: L=Linear congruential generator;
  28.  * X=Xor based generator; and M=Mix. This member uses a 64-bit LCG and 256-bit Xor-based
  29.  * generator. It is named as {@code "L64X256MixRandom"} in the {@code java.util.random}
  30.  * package introduced in JDK 17; the LXM family is described in further detail in:
  31.  *
  32.  * <blockquote>Steele and Vigna (2021) LXM: better splittable pseudorandom number generators
  33.  * (and almost as fast). Proceedings of the ACM on Programming Languages, Volume 5,
  34.  * Article 148, pp 1–31.</blockquote>
  35.  *
  36.  * <p>Memory footprint is 384 bits and the period is 2<sup>64</sup> (2<sup>256</sup> - 1).
  37.  *
  38.  * <p>This generator implements
  39.  * {@link org.apache.commons.rng.LongJumpableUniformRandomProvider LongJumpableUniformRandomProvider}.
  40.  * In addition instances created with a different additive parameter for the LCG are robust
  41.  * against accidental correlation in a multi-threaded setting. The additive parameters must be
  42.  * different in the most significant 63-bits.
  43.  *
  44.  * <p>This generator implements
  45.  * {@link org.apache.commons.rng.SplittableUniformRandomProvider SplittableUniformRandomProvider}.
  46.  * The stream of generators created using the {@code splits} methods support parallelisation
  47.  * and are robust against accidental correlation by using unique values for the additive parameter
  48.  * for each instance in the same stream. The primitive streaming methods support parallelisation
  49.  * but with no assurances of accidental correlation; each thread uses a new instance with a
  50.  * randomly initialised state.
  51.  *
  52.  * @see <a href="https://doi.org/10.1145/3485525">Steele &amp; Vigna (2021) Proc. ACM Programming
  53.  *      Languages 5, 1-31</a>
  54.  * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/package-summary.html">
  55.  *      JDK 17 java.util.random javadoc</a>
  56.  * @since 1.5
  57.  */
  58. public class L64X256Mix extends AbstractL64 implements SplittableUniformRandomProvider {
  59.     /** Size of the seed vector. */
  60.     private static final int SEED_SIZE = 6;
  61.     /** Size of the XBG state vector. */
  62.     private static final int XBG_STATE_SIZE = 4;
  63.     /** LCG multiplier. */
  64.     private static final long M = LXMSupport.M64;

  65.     /** State 0 of the XBG. */
  66.     private long x0;
  67.     /** State 1 of the XBG. */
  68.     private long x1;
  69.     /** State 2 of the XBG. */
  70.     private long x2;
  71.     /** State 3 of the XBG. */
  72.     private long x3;

  73.     /**
  74.      * Creates a new instance.
  75.      *
  76.      * @param seed Initial seed.
  77.      * If the length is larger than 6, only the first 6 elements will
  78.      * be used; if smaller, the remaining elements will be automatically
  79.      * set. A seed containing all zeros in the last four elements
  80.      * will create a non-functional XBG sub-generator and a low
  81.      * quality output with a period of 2<sup>64</sup>.
  82.      *
  83.      * <p>The 1st element is used to set the LCG increment; the least significant bit
  84.      * is set to odd to ensure a full period LCG. The 2nd element is used
  85.      * to set the LCG state.</p>
  86.      */
  87.     public L64X256Mix(long[] seed) {
  88.         super(seed = extendSeed(seed, SEED_SIZE));
  89.         x0 = seed[2];
  90.         x1 = seed[3];
  91.         x2 = seed[4];
  92.         x3 = seed[5];
  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>64</sup>.
  99.      *
  100.      * <p>The 1st element is used to set the LCG increment; the least significant bit
  101.      * is set to odd to ensure a full period LCG. The 2nd element is 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 L64X256Mix(long seed0, long seed1, long seed2, long seed3,
  112.                       long seed4, long seed5) {
  113.         super(seed0, seed1);
  114.         x0 = seed2;
  115.         x1 = seed3;
  116.         x2 = seed4;
  117.         x3 = seed5;
  118.     }

  119.     /**
  120.      * Creates a copy instance.
  121.      *
  122.      * @param source Source to copy.
  123.      */
  124.     protected L64X256Mix(L64X256Mix source) {
  125.         super(source);
  126.         x0 = source.x0;
  127.         x1 = source.x1;
  128.         x2 = source.x2;
  129.         x3 = source.x3;
  130.     }

  131.     /** {@inheritDoc} */
  132.     @Override
  133.     protected byte[] getStateInternal() {
  134.         return composeStateInternal(NumberFactory.makeByteArray(
  135.                                         new long[] {x0, x1, x2, x3}),
  136.                                     super.getStateInternal());
  137.     }

  138.     /** {@inheritDoc} */
  139.     @Override
  140.     protected void setStateInternal(byte[] s) {
  141.         final byte[][] c = splitStateInternal(s, XBG_STATE_SIZE * Long.BYTES);
  142.         final long[] tmp = NumberFactory.makeLongArray(c[0]);
  143.         x0 = tmp[0];
  144.         x1 = tmp[1];
  145.         x2 = tmp[2];
  146.         x3 = tmp[3];
  147.         super.setStateInternal(c[1]);
  148.     }

  149.     /** {@inheritDoc} */
  150.     @Override
  151.     public long next() {
  152.         // LXM generate.
  153.         // Old state is used for the output allowing parallel pipelining
  154.         // on processors that support multiple concurrent instructions.

  155.         long s0 = x0;
  156.         final long s = ls;

  157.         // Mix
  158.         final long z = LXMSupport.lea64(s + s0);

  159.         // LCG update
  160.         ls = M * s + la;

  161.         // XBG update
  162.         long s1 = x1;
  163.         long s2 = x2;
  164.         long s3 = x3;

  165.         final long t = s1 << 17;

  166.         s2 ^= s0;
  167.         s3 ^= s1;
  168.         s1 ^= s2;
  169.         s0 ^= s3;

  170.         s2 ^= t;

  171.         s3 = Long.rotateLeft(s3, 45);

  172.         x0 = s0;
  173.         x1 = s1;
  174.         x2 = s2;
  175.         x3 = s3;

  176.         return z;
  177.     }

  178.     /**
  179.      * {@inheritDoc}
  180.      *
  181.      * <p>The jump size is the equivalent of moving the state <em>backwards</em> by
  182.      * (2<sup>256</sup> - 1) positions. It can provide up to 2<sup>64</sup>
  183.      * non-overlapping subsequences.
  184.      */
  185.     @Override
  186.     public UniformRandomProvider jump() {
  187.         return super.jump();
  188.     }

  189.     /**
  190.      * {@inheritDoc}
  191.      *
  192.      * <p>The jump size is the equivalent of moving the state <em>backwards</em> by
  193.      * 2<sup>32</sup> (2<sup>256</sup> - 1) positions. It can provide up to
  194.      * 2<sup>32</sup> non-overlapping subsequences of length 2<sup>32</sup>
  195.      * (2<sup>256</sup> - 1); each subsequence can provide up to 2<sup>32</sup>
  196.      * non-overlapping subsequences of length (2<sup>256</sup> - 1) using the
  197.      * {@link #jump()} method.
  198.      */
  199.     @Override
  200.     public JumpableUniformRandomProvider longJump() {
  201.         return super.longJump();
  202.     }

  203.     /** {@inheritDoc} */
  204.     @Override
  205.     AbstractL64 copy() {
  206.         // This exists to ensure the jump function performed in the super class returns
  207.         // the correct class type. It should not be public.
  208.         return new L64X256Mix(this);
  209.     }

  210.     /** {@inheritDoc} */
  211.     @Override
  212.     public SplittableUniformRandomProvider split(UniformRandomProvider source) {
  213.         return create(source.nextLong(), source);
  214.     }

  215.     /** {@inheritDoc} */
  216.     @Override
  217.     public Stream<SplittableUniformRandomProvider> splits(long streamSize, SplittableUniformRandomProvider source) {
  218.         return RandomStreams.generateWithSeed(streamSize, source, L64X256Mix::create);
  219.     }

  220.     /**
  221.      * Create a new instance using the given {@code seed} and {@code source} of randomness
  222.      * to initialise the instance.
  223.      *
  224.      * @param seed Seed used to initialise the instance.
  225.      * @param source Source of randomness used to initialise the instance.
  226.      * @return A new instance.
  227.      */
  228.     private static SplittableUniformRandomProvider create(long seed, UniformRandomProvider source) {
  229.         // LCG state. The addition uses the input seed.
  230.         // The LCG addition parameter is set to odd so left-shift the seed.
  231.         final long s0 = seed << 1;
  232.         final long s1 = source.nextLong();
  233.         // XBG state must not be all zero
  234.         long x0 = source.nextLong();
  235.         long x1 = source.nextLong();
  236.         long x2 = source.nextLong();
  237.         long x3 = source.nextLong();
  238.         if ((x0 | x1 | x2 | x3) == 0) {
  239.             // SplitMix style seed ensures at least one non-zero value
  240.             long z = s1;
  241.             x0 = LXMSupport.lea64(z);
  242.             x1 = LXMSupport.lea64(z += LXMSupport.GOLDEN_RATIO_64);
  243.             x2 = LXMSupport.lea64(z += LXMSupport.GOLDEN_RATIO_64);
  244.             x3 = LXMSupport.lea64(z + LXMSupport.GOLDEN_RATIO_64);
  245.         }
  246.         return new L64X256Mix(s0, s1, x0, x1, x2, x3);
  247.     }
  248. }