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}