001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the "math NOTICE.txt" 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 */
017package squidpony.squidmath;
018
019import java.util.Arrays;
020
021/**
022 * Implementation of a Sobol sequence as a Quasi-Random Number Generator.
023 * <p>
024 * A Sobol sequence is a low-discrepancy sequence with the property that for all values of N,
025 * its subsequence (x1, ... xN) has a low discrepancy. It can be used to generate quasi-random
026 * points in a space S, which are equi-distributed. This is not a true random number generator,
027 * and is not even a pseudo-random number generator; the sequence generated from identical
028 * starting points with identical dimensions will be exactly the same. Calling this class'
029 * nextVector, nextIntVector, and nextLongVector methods all increment the position in the
030 * sequence, and do not use separate sequences for separate types.
031 * <p>
032 * The implementation already comes with support for up to 16 dimensions with direction numbers
033 * calculated from <a href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Stephen Joe and Frances Kuo</a>.
034 * <p>
035 * The generator supports two modes:
036 * <ul>
037 *   <li>sequential generation of points: {@link #nextVector()}, {@link #nextIntVector()},
038 *   {@link #nextLongVector()}, and the bounded variants on each of those</li>
039 *   <li>random access to the i-th point in the sequence: {@link #skipTo(int)}</li>
040 * </ul>
041 *
042 * For reference,
043 * <a href="http://en.wikipedia.org/wiki/Sobol_sequence">Sobol sequence (Wikipedia)</a>
044 * and the data, <a href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Sobol sequence direction numbers</a>
045 *
046 * Created by Tommy Ettinger on 5/2/2015 based off Apache Commons Math 4.
047 */
048public class SobolQRNG implements RandomnessSource {
049
050        /** The number of bits to use. */
051    private static final int BITS = 52;
052
053    /** The scaling factor. */
054    private static final double SCALE = Math.pow(2, BITS);
055
056    /** The maximum supported space dimension. */
057    private static final int MAX_DIMENSION = 16;
058
059    /** The first 16 dimensions' direction numbers. */
060    private static final int[][] RESOURCE_PRELOAD = new int[][]{
061            new int[]{2, 0,   1},
062            new int[]{3, 1,   1,3},
063            new int[]{4, 1,   1,3,1},
064            new int[]{5, 2,   1,1,1},
065            new int[]{6, 1,   1,1,3,3},
066            new int[]{7, 4,   1,3,5,13},
067            new int[]{8, 2,   1,1,5,5,17},
068            new int[]{9, 4,   1,1,5,5,5},
069            new int[]{10, 7,  1,1,7,11,19},
070            new int[]{11, 11, 1,1,5,1,1},
071            new int[]{12, 13, 1,1,1,3,11},
072            new int[]{13, 14, 1,3,5,5,31},
073            new int[]{14, 1,  1,3,3,9,7,49},
074            new int[]{15, 13, 1,1,1,15,21,21},
075            new int[]{16, 16, 1,3,1,13,27,49},
076    };
077
078    private static final long serialVersionUID = -6759002780425873173L;
079
080    /** Space dimension. */
081    private final int dimension;
082
083    /** The current index in the sequence. Starts at 1, not 0, because 0 acts differently and shouldn't be typical.*/
084    private int count = 1;
085
086    /** The direction vector for each component. */
087    private final long[][] direction;
088
089    /** The current state. */
090    private final long[] x;
091
092    /**
093     * Construct a new Sobol sequence generator for the given space dimension.
094     * You should call {@link #skipTo(int)} with a fairly large number (over 1000) to ensure the results aren't
095     * too obviously non-random. If you skipTo(1), all doubles in that result will be 0.5, and if you skipTo(0),
096     * all will be 0 (this class starts at index 1 instead of 0 for that reason). This is true for all dimensions.
097     *
098     * @param dimension the space dimension
099     * @throws ArithmeticException if the space dimension is outside the allowed range of [1, 16]
100     */
101    public SobolQRNG(final int dimension) throws ArithmeticException {
102        if (dimension < 1 || dimension > MAX_DIMENSION) {
103            throw new ArithmeticException("Dimension " + dimension + "is outside the range of" +
104                    "[1, 16]; higher dimensions are not supported.");
105        }
106
107        this.dimension = dimension;
108
109        // init data structures
110        direction = new long[dimension][BITS + 1];
111        x = new long[dimension];
112
113        // special case: dimension 1 -> use unit initialization
114        for (int i = 1; i <= BITS; i++) {
115            direction[0][i] = 1L << (BITS - i);
116        }
117        for (int d = 0; d < dimension-1; d++) {
118            initDirectionVector(RESOURCE_PRELOAD[d]);
119        }
120
121    }
122
123    /**
124     * Calculate the direction numbers from the given polynomial.
125     *
126     * @param m the initial direction numbers
127     */
128    private void initDirectionVector(final int[] m) {
129        final int s = m.length - 2, d = m[0] - 1, a = m[1];
130        for (int i = 1; i <= s; i++) {
131            direction[d][i] = (long) m[i+1] << (BITS - i);
132        }
133        for (int i = s + 1; i <= BITS; i++) {
134            direction[d][i] = direction[d][i - s] ^ (direction[d][i - s] >> s);
135            for (int k = 1; k <= s - 1; k++) {
136                direction[d][i] ^= ((a >> (s - 1 - k)) & 1) * direction[d][i - k];
137            }
138        }
139    }
140
141    /** Generate a random vector.
142     * @return a random vector as an array of double in the range [0.0, 1.0).
143     */
144    public double[] nextVector() {
145        final double[] v = new double[dimension];
146        if (count == 0) {
147            count++;
148            return v;
149        }
150
151        // find the index c of the rightmost 0
152        int c = 1 + Integer.numberOfTrailingZeros(count);
153
154        for (int i = 0; i < dimension; i++) {
155            x[i] ^= direction[i][c];
156            v[i] = (double) x[i] / SCALE;
157        }
158        count++;
159        return v;
160    }
161    /** Generate a random vector.
162     * @return a random vector as an array of double in the range [0.0, 1.0).
163     */
164    public double[] fillVector(double[] toFill) {
165        if (count == 0 || toFill == null) {
166            count++;
167            return toFill;
168        }
169
170        // find the index c of the rightmost 0
171        int c = 1 + Integer.numberOfTrailingZeros(count);
172
173        for (int i = 0; i < dimension && i < toFill.length; i++) {
174            x[i] ^= direction[i][c];
175            toFill[i] = (double) x[i] / SCALE;
176        }
177        count++;
178        return toFill;
179    }
180    /** Generate a random vector as a Coord, scaled between 0 (inclusive) and the given limits (exclusive).
181     * @param xLimit the upper exclusive bound for the Coord's x-coordinate
182     * @param yLimit the upper exclusive bound for the Coord's y-coordinate
183     * @return a random vector as a Coord between (0,0) inclusive and (xLimit,yLimit) exclusive
184     */
185    public Coord nextCoord(int xLimit, int yLimit) {
186        if (count == 0) {
187            count++;
188            return Coord.get(0, 0);
189        }
190
191        // find the index c of the rightmost 0
192        int cx, cy, c = 1 + Integer.numberOfTrailingZeros(count);
193
194        if(dimension <= 0)
195            return Coord.get(0, 0);
196        x[0] ^= direction[0][c];
197        cx = (int)((x[0] >>> 20) % xLimit);
198
199        if(dimension == 1)
200        {
201            x[0] ^= direction[0][1 + Integer.numberOfTrailingZeros(++count)];
202            cy = (int) ((x[0] >>> 20) % yLimit);
203        }
204        else {
205            x[1] ^= direction[1][c];
206            cy = (int) ((x[1] >>> 20) % yLimit);
207            // ensure the next number stays on track for other dimensions
208            if(dimension > 2)
209            {
210                for (int i = 2; i < dimension; i++) {
211                    x[i] ^= direction[i][c];
212                }
213            }
214        }
215        count++;
216        return Coord.get(cx, cy);
217    }
218
219    /** Generate a random vector.
220     * @param max the maximum exclusive value for the elements of the desired vector; minimum is 0 inclusive.
221     * @return a random vector as an array of double.
222     */
223    public double[] nextVector(final double max) {
224        final double[] v = new double[dimension];
225        if (count == 0) {
226            count++;
227            return v;
228        }
229
230        // find the index c of the rightmost 0
231        int c = 1 + Integer.numberOfTrailingZeros(count);
232
233        for (int i = 0; i < dimension; i++) {
234            x[i] ^= direction[i][c];
235            v[i] = (double) x[i] / SCALE * max;
236        }
237        count++;
238        return v;
239    }
240
241    /** Generate a random vector.
242     * @return a random vector as an array of long (only 52 bits are actually used for the result, plus sign bit).
243     */
244    public long[] nextLongVector() {
245        final long[] v = new long[dimension];
246        if (count == 0) {
247            count++;
248            return v;
249        }
250
251        // find the index c of the rightmost 0
252        int c = 1 + Integer.numberOfTrailingZeros(count);
253
254        for (int i = 0; i < dimension; i++) {
255            x[i] ^= direction[i][c];
256            v[i] = x[i];
257        }
258        count++;
259        return v;
260    }
261    /** Generate a random vector.
262     * @param max the maximum exclusive value for the elements of the desired vector; minimum is 0 inclusive.
263     * @return a random vector as an array of long (only 52 bits are actually used for the result, plus sign bit).
264     */
265    public long[] nextLongVector(final long max) {
266        final long[] v = new long[dimension];
267
268        if (count == 0) {
269            count++;
270            return v;
271        }
272
273        // find the index c of the rightmost 0
274        int c = 1 + Integer.numberOfTrailingZeros(count);
275
276        for (int i = 0; i < dimension; i++) {
277            x[i] ^= direction[i][c];
278            //suboptimal, but this isn't meant for quality of randomness, actually the opposite.
279            v[i] = (long)(x[i] / SCALE * max) % max;
280        }
281        count++;
282        return v;
283    }
284
285    /** Generate a random vector.
286     * @return a random vector as an array of int.
287     */
288    public int[] nextIntVector() {
289        final int[] v = new int[dimension];
290        if (count == 0) {
291            count++;
292            return v;
293        }
294
295        // find the index c of the rightmost 0
296        int c = 1 + Integer.numberOfTrailingZeros(count);
297
298        for (int i = 0; i < dimension; i++) {
299            x[i] ^= direction[i][c];
300            v[i] = (int) (x[i] >>> 20);
301        }
302        count++;
303        return v;
304    }
305
306    /** Generate a random vector.
307     * @param max the maximum exclusive value for the elements of the desired vector; minimum is 0 inclusive.
308     * @return a random vector as an array of int.
309     */
310    public int[] nextIntVector(final int max) {
311        final int[] v = new int[dimension];
312        if (count == 0) {
313            count++;
314            return v;
315        }
316
317        // find the index c of the rightmost 0
318        int c = 1 + Integer.numberOfTrailingZeros(count);
319
320        for (int i = 0; i < dimension; i++) {
321            x[i] ^= direction[i][c];
322            //suboptimal, but this isn't meant for quality of randomness, actually the opposite.
323            v[i] = (int) ((x[i] >>> 20) % max);
324        }
325        count++;
326        return v;
327    }
328
329    /**
330     * Skip to the i-th point in the Sobol sequence.
331     * <p>
332     * This operation can be performed in O(1).
333     * If index is somehow negative, this uses its absolute value instead of throwing an exception.
334     * If index is 0, the result will always be entirely 0.
335     * You should skipTo a number greater than 1000 if you want random-seeming individual numbers in each vector.
336     *
337     * @param index the index in the sequence to skip to
338     * @return the i-th point in the Sobol sequence
339     */
340    public double[] skipTo(final int index) {
341        if (index == 0) {
342            // reset x vector
343            Arrays.fill(x, 0);
344        } else {
345            final int i = (index > 0) ? (index - 1) : (-index - 1);
346            final long grayCode = i ^ (i >> 1); // compute the gray code of i = i XOR floor(i / 2)
347            for (int j = 0; j < dimension; j++) {
348                long result = 0;
349                for (int k = 1; k <= BITS; k++) {
350                    final long shift = grayCode >> (k - 1);
351                    if (shift == 0) {
352                        // stop, as all remaining bits will be zero
353                        break;
354                    }
355                    // the k-th bit of i
356                    final long ik = shift & 1;
357                    result ^= ik * direction[j][k];
358                }
359                x[j] = result;
360            }
361        }
362        count = (index >= 0) ? index : -index;
363        return nextVector();
364    }
365
366    /**
367     * Returns the index i of the next point in the Sobol sequence that will be returned
368     * by calling {@link #nextVector()}.
369     *
370     * @return the index of the next point
371     */
372    public int getNextIndex() {
373        return count;
374    }
375
376    /**
377     *
378     * @param bits the number of bits to be returned
379     * @return a quasi-random int with at most the specified number of bits
380     */
381    @Override
382    public int next(int bits) {
383        return nextIntVector()[0] >>> (32 - bits);
384    }
385
386    /**
387     * Using this method, any algorithm that needs to efficiently generate more
388     * than 32 bits of random data can interface with this randomness source.
389     * <p/>
390     * Get a random long between Long.MIN_VALUE and Long.MAX_VALUE (both inclusive).
391     *
392     * @return a random long between Long.MIN_VALUE and Long.MAX_VALUE (both inclusive)
393     */
394    @Override
395    public long nextLong() {
396        if (dimension > 1) {
397            long[] l = nextLongVector();
398            return (l[0] << 32) ^ l[1];
399        }
400        return ((long) nextIntVector()[0] << 32) ^ nextIntVector()[0];
401    }
402    public double nextDouble() {
403        return nextVector()[0];
404    }
405
406    public double nextDouble(double max) {
407        return nextVector(max)[0];
408    }
409    /**
410     * Produces a copy of this RandomnessSource that, if next() and/or nextLong() are called on this object and the
411     * copy, both will generate the same sequence of random numbers from the point copy() was called. This just needs to
412     * copy the state so it isn't shared, usually, and produce a new value with the same exact state.
413     * @return a copy of this RandomnessSource
414     */
415    @Override
416    public SobolQRNG copy() {
417        SobolQRNG next = new SobolQRNG(dimension);
418        next.count = count;
419        next.skipTo(count);
420        return next;
421    }
422    @Override
423    public String toString()
424    {
425        return "SobolQRNG with rank " + dimension + " and index " + count;
426    }
427
428    @Override
429    public boolean equals(Object o) {
430        if (this == o) return true;
431        if (o == null || getClass() != o.getClass()) return false;
432
433        SobolQRNG sobolQRNG = (SobolQRNG) o;
434
435        if (dimension != sobolQRNG.dimension) return false;
436        return (count == sobolQRNG.count);
437    }
438
439    @Override
440    public int hashCode() {
441        int result = 31 * dimension + count | 0; // bitwise OR with 0 is a GWT thing
442        result = 31 * result + CrossHash.hash(direction) | 0;
443        return  31 * result + CrossHash.hash(x) | 0;
444    }
445}