L128X256Mix.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 128-bit LCG and 256-bit Xor-based
  29.  * generator. It is named as {@code "L128X256MixRandom"} 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 512 bits and the period is 2<sup>128</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 127-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 L128X256Mix extends AbstractL128 implements SplittableUniformRandomProvider {
  59.     /** Size of the seed vector. */
  60.     private static final int SEED_SIZE = 8;
  61.     /** Size of the XBG state vector. */
  62.     private static final int XBG_STATE_SIZE = 4;
  63.     /** Low half of 128-bit LCG multiplier. */
  64.     private static final long ML = LXMSupport.M128L;

  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 8, only the first 8 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>128</sup>.
  82.      *
  83.      * <p>The 1st and 2nd elements are used to set the LCG increment; the least significant bit
  84.      * is set to odd to ensure a full period LCG. The 3rd and 4th elements are used
  85.      * to set the LCG state.</p>
  86.      */
  87.     public L128X256Mix(long[] seed) {
  88.         super(seed = extendSeed(seed, SEED_SIZE));
  89.         x0 = seed[4];
  90.         x1 = seed[5];
  91.         x2 = seed[6];
  92.         x3 = seed[7];
  93.     }

  94.     /**
  95.      * Creates a new instance using an 8 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.      * @param seed6 Initial seed element 6.
  111.      * @param seed7 Initial seed element 7.
  112.      */
  113.     public L128X256Mix(long seed0, long seed1, long seed2, long seed3,
  114.                        long seed4, long seed5, long seed6, long seed7) {
  115.         super(seed0, seed1, seed2, seed3);
  116.         x0 = seed4;
  117.         x1 = seed5;
  118.         x2 = seed6;
  119.         x3 = seed7;
  120.     }

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

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

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

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

  157.         long s0 = x0;
  158.         final long sh = lsh;

  159.         // Mix
  160.         final long z = LXMSupport.lea64(sh + s0);

  161.         // LCG update
  162.         // The LCG is, in effect, "s = m * s + a" where m = ((1LL << 64) + ML)
  163.         final long sl = lsl;
  164.         final long al = lal;
  165.         final long u = ML * sl;
  166.         // High half
  167.         lsh = ML * sh + LXMSupport.unsignedMultiplyHigh(ML, sl) + sl + lah +
  168.               // Carry propagation
  169.               LXMSupport.unsignedAddHigh(u, al);
  170.         // Low half
  171.         lsl = u + al;

  172.         // XBG update
  173.         long s1 = x1;
  174.         long s2 = x2;
  175.         long s3 = x3;

  176.         final long t = s1 << 17;

  177.         s2 ^= s0;
  178.         s3 ^= s1;
  179.         s1 ^= s2;
  180.         s0 ^= s3;

  181.         s2 ^= t;

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

  183.         x0 = s0;
  184.         x1 = s1;
  185.         x2 = s2;
  186.         x3 = s3;

  187.         return z;
  188.     }

  189.     /**
  190.      * {@inheritDoc}
  191.      *
  192.      * <p>The jump size is the equivalent of moving the state <em>backwards</em> by
  193.      * (2<sup>256</sup> - 1) positions. It can provide up to 2<sup>128</sup>
  194.      * non-overlapping subsequences.
  195.      */
  196.     @Override
  197.     public UniformRandomProvider jump() {
  198.         return super.jump();
  199.     }

  200.     /**
  201.      * {@inheritDoc}
  202.      *
  203.      * <p>The jump size is the equivalent of moving the state <em>backwards</em> by
  204.      * 2<sup>64</sup> (2<sup>256</sup> - 1) positions. It can provide up to
  205.      * 2<sup>64</sup> non-overlapping subsequences of length 2<sup>64</sup>
  206.      * (2<sup>256</sup> - 1); each subsequence can provide up to 2<sup>64</sup>
  207.      * non-overlapping subsequences of length (2<sup>256</sup> - 1) using the
  208.      * {@link #jump()} method.
  209.      */
  210.     @Override
  211.     public JumpableUniformRandomProvider longJump() {
  212.         return super.longJump();
  213.     }

  214.     /** {@inheritDoc} */
  215.     @Override
  216.     AbstractL128 copy() {
  217.         // This exists to ensure the jump function performed in the super class returns
  218.         // the correct class type. It should not be public.
  219.         return new L128X256Mix(this);
  220.     }

  221.     /** {@inheritDoc} */
  222.     @Override
  223.     public SplittableUniformRandomProvider split(UniformRandomProvider source) {
  224.         return create(source.nextLong(), source);
  225.     }

  226.     /** {@inheritDoc} */
  227.     @Override
  228.     public Stream<SplittableUniformRandomProvider> splits(long streamSize, SplittableUniformRandomProvider source) {
  229.         return RandomStreams.generateWithSeed(streamSize, source, L128X256Mix::create);
  230.     }

  231.     /**
  232.      * Create a new instance using the given {@code seed} and {@code source} of randomness
  233.      * to initialise the instance.
  234.      *
  235.      * @param seed Seed used to initialise the instance.
  236.      * @param source Source of randomness used to initialise the instance.
  237.      * @return A new instance.
  238.      */
  239.     private static SplittableUniformRandomProvider create(long seed, UniformRandomProvider source) {
  240.         // LCG state. The addition lower-half uses the input seed.
  241.         // The LCG addition parameter is set to odd so left-shift the seed.
  242.         final long s0 = source.nextLong();
  243.         final long s1 = seed << 1;
  244.         final long s2 = source.nextLong();
  245.         final long s3 = source.nextLong();
  246.         // XBG state must not be all zero
  247.         long x0 = source.nextLong();
  248.         long x1 = source.nextLong();
  249.         long x2 = source.nextLong();
  250.         long x3 = source.nextLong();
  251.         if ((x0 | x1 | x2 | x3) == 0) {
  252.             // SplitMix style seed ensures at least one non-zero value
  253.             long z = s3;
  254.             x0 = LXMSupport.lea64(z);
  255.             x1 = LXMSupport.lea64(z += LXMSupport.GOLDEN_RATIO_64);
  256.             x2 = LXMSupport.lea64(z += LXMSupport.GOLDEN_RATIO_64);
  257.             x3 = LXMSupport.lea64(z + LXMSupport.GOLDEN_RATIO_64);
  258.         }
  259.         // The LCG addition parameter is set to odd so left-shift the seed
  260.         return new L128X256Mix(s0, s1, s2, s3, x0, x1, x2, x3);
  261.     }
  262. }