View Javadoc
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.math4.legacy.random;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.nio.charset.Charset;
24  import java.util.Arrays;
25  import java.util.NoSuchElementException;
26  import java.util.StringTokenizer;
27  import java.util.function.Supplier;
28  
29  import org.apache.commons.math4.legacy.exception.MathInternalError;
30  import org.apache.commons.math4.legacy.exception.MathParseException;
31  import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
32  import org.apache.commons.math4.legacy.exception.OutOfRangeException;
33  import org.apache.commons.math4.core.jdkmath.JdkMath;
34  
35  /**
36   * Implementation of a Sobol sequence.
37   * <p>
38   * A Sobol sequence is a low-discrepancy sequence with the property that for all values of N,
39   * its subsequence (x1, ... xN) has a low discrepancy. It can be used to generate pseudo-random
40   * points in a space S, which are equi-distributed.
41   * <p>
42   * The implementation already comes with support for up to 21201 dimensions with direction numbers
43   * calculated from <a href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Stephen Joe and Frances Kuo</a>.
44   * <p>
45   * The generator supports two modes:
46   * <ul>
47   *   <li>sequential generation of points: {@link #get()}</li>
48   *   <li>random access to the i-th point in the sequence: {@link #skipTo(int)}</li>
49   * </ul>
50   *
51   * @see <a href="http://en.wikipedia.org/wiki/Sobol_sequence">Sobol sequence (Wikipedia)</a>
52   * @see <a href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Sobol sequence direction numbers</a>
53   *
54   * @since 3.3
55   */
56  public class SobolSequenceGenerator implements Supplier<double[]> {
57      /** The number of bits to use. */
58      private static final int BITS = 52;
59  
60      /** The scaling factor. */
61      private static final double SCALE = JdkMath.pow(2, BITS);
62  
63      /** The maximum supported space dimension. */
64      private static final int MAX_DIMENSION = 21201;
65  
66      /** The resource containing the direction numbers. */
67      private static final String RESOURCE_NAME = "/assets/org/apache/commons/math4/legacy/random/new-joe-kuo-6.21201";
68  
69      /** Character set for file input. */
70      private static final String FILE_CHARSET = "US-ASCII";
71  
72      /** Space dimension. */
73      private final int dimension;
74  
75      /** The current index in the sequence. */
76      private int count;
77  
78      /** The direction vector for each component. */
79      private final long[][] direction;
80  
81      /** The current state. */
82      private final long[] x;
83  
84      /**
85       * Construct a new Sobol sequence generator for the given space dimension.
86       *
87       * @param dimension the space dimension
88       * @throws OutOfRangeException if the space dimension is outside the allowed range of [1, 21201]
89       */
90      public SobolSequenceGenerator(final int dimension) {
91          if (dimension < 1 || dimension > MAX_DIMENSION) {
92              throw new OutOfRangeException(dimension, 1, MAX_DIMENSION);
93          }
94  
95          // initialize the other dimensions with direction numbers from a resource
96          final InputStream is = getClass().getResourceAsStream(RESOURCE_NAME);
97          if (is == null) {
98              throw new MathInternalError();
99          }
100 
101         this.dimension = dimension;
102 
103         // init data structures
104         direction = new long[dimension][BITS + 1];
105         x = new long[dimension];
106 
107         try {
108             initFromStream(is);
109         } catch (IOException e) {
110             // the internal resource file could not be read -> should not happen
111             throw new MathInternalError();
112         } catch (MathParseException e) {
113             // the internal resource file could not be parsed -> should not happen
114             throw new MathInternalError();
115         } finally {
116             try {
117                 is.close();
118             } catch (IOException e) { // NOPMD
119                 // ignore
120             }
121         }
122     }
123 
124     /**
125      * Construct a new Sobol sequence generator for the given space dimension with
126      * direction vectors loaded from the given stream.
127      * <p>
128      * The expected format is identical to the files available from
129      * <a href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Stephen Joe and Frances Kuo</a>.
130      * The first line will be ignored as it is assumed to contain only the column headers.
131      * The columns are:
132      * <ul>
133      *  <li>d: the dimension</li>
134      *  <li>s: the degree of the primitive polynomial</li>
135      *  <li>a: the number representing the coefficients</li>
136      *  <li>m: the list of initial direction numbers</li>
137      * </ul>
138      * Example:
139      * <pre>
140      * d       s       a       m_i
141      * 2       1       0       1
142      * 3       2       1       1 3
143      * </pre>
144      * <p>
145      * The input stream <i>must</i> be an ASCII text containing one valid direction vector per line.
146      *
147      * @param dimension the space dimension
148      * @param is the stream to read the direction vectors from
149      * @throws NotStrictlyPositiveException if the space dimension is &lt; 1
150      * @throws OutOfRangeException if the space dimension is outside the range [1, max], where
151      *   max refers to the maximum dimension found in the input stream
152      * @throws MathParseException if the content in the stream could not be parsed successfully
153      * @throws IOException if an error occurs while reading from the input stream
154      */
155     public SobolSequenceGenerator(final int dimension, final InputStream is)
156         throws IOException {
157         if (dimension < 1) {
158             throw new NotStrictlyPositiveException(dimension);
159         }
160 
161         this.dimension = dimension;
162 
163         // init data structures
164         direction = new long[dimension][BITS + 1];
165         x = new long[dimension];
166 
167         // initialize the other dimensions with direction numbers from the stream
168         int lastDimension = initFromStream(is);
169         if (lastDimension < dimension) {
170             throw new OutOfRangeException(dimension, 1, lastDimension);
171         }
172     }
173 
174     /**
175      * Load the direction vector for each dimension from the given stream.
176      * <p>
177      * The input stream <i>must</i> be an ASCII text containing one
178      * valid direction vector per line.
179      *
180      * @param is the input stream to read the direction vector from
181      * @return the last dimension that has been read from the input stream
182      * @throws IOException if the stream could not be read
183      * @throws MathParseException if the content could not be parsed successfully
184      */
185     private int initFromStream(final InputStream is) throws IOException {
186         // special case: dimension 1 -> use unit initialization
187         for (int i = 1; i <= BITS; i++) {
188             direction[0][i] = 1L << (BITS - i);
189         }
190 
191         final Charset charset = Charset.forName(FILE_CHARSET);
192         final BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset));
193         int dim = -1;
194 
195         try {
196             // ignore first line
197             reader.readLine();
198 
199             int lineNumber = 2;
200             int index = 1;
201             String line = null;
202             while ( (line = reader.readLine()) != null) {
203                 StringTokenizer st = new StringTokenizer(line, " ");
204                 try {
205                     dim = Integer.parseInt(st.nextToken());
206                     if (dim >= 2 && dim <= dimension) { // we have found the right dimension
207                         final int s = Integer.parseInt(st.nextToken());
208                         final int a = Integer.parseInt(st.nextToken());
209                         final int[] m = new int[s + 1];
210                         for (int i = 1; i <= s; i++) {
211                             m[i] = Integer.parseInt(st.nextToken());
212                         }
213                         initDirectionVector(index++, a, m);
214                     }
215 
216                     if (dim > dimension) {
217                         return dim;
218                     }
219                 } catch (NoSuchElementException e) {
220                     throw new MathParseException(line, lineNumber);
221                 } catch (NumberFormatException e) {
222                     throw new MathParseException(line, lineNumber);
223                 }
224                 lineNumber++;
225             }
226         } finally {
227             reader.close();
228         }
229 
230         return dim;
231     }
232 
233     /**
234      * Calculate the direction numbers from the given polynomial.
235      *
236      * @param d the dimension, zero-based
237      * @param a the coefficients of the primitive polynomial
238      * @param m the initial direction numbers
239      */
240     private void initDirectionVector(final int d, final int a, final int[] m) {
241         final int s = m.length - 1;
242         for (int i = 1; i <= s; i++) {
243             direction[d][i] = ((long) m[i]) << (BITS - i);
244         }
245         for (int i = s + 1; i <= BITS; i++) {
246             direction[d][i] = direction[d][i - s] ^ (direction[d][i - s] >> s);
247             for (int k = 1; k <= s - 1; k++) {
248                 direction[d][i] ^= ((a >> (s - 1 - k)) & 1) * direction[d][i - k];
249             }
250         }
251     }
252 
253     /** {@inheritDoc} */
254     @Override
255     public double[] get() {
256         final double[] v = new double[dimension];
257         if (count == 0) {
258             count++;
259             return v;
260         }
261 
262         // find the index c of the rightmost 0
263         int c = 1;
264         int value = count - 1;
265         while ((value & 1) == 1) {
266             value >>= 1;
267             c++;
268         }
269 
270         for (int i = 0; i < dimension; i++) {
271             x[i] ^= direction[i][c];
272             v[i] = (double) x[i] / SCALE;
273         }
274         count++;
275         return v;
276     }
277 
278     /**
279      * Skip to the i-th point in the Sobol sequence.
280      * <p>
281      * This operation can be performed in O(1).
282      *
283      * @param index the index in the sequence to skip to
284      * @return the i-th point in the Sobol sequence
285      * @throws org.apache.commons.math4.legacy.exception.NotPositiveException NotPositiveException if index &lt; 0
286      */
287     public double[] skipTo(final int index) {
288         if (index == 0) {
289             // reset x vector
290             Arrays.fill(x, 0);
291         } else {
292             final int i = index - 1;
293             final long grayCode = i ^ (i >> 1); // compute the gray code of i = i XOR floor(i / 2)
294             for (int j = 0; j < dimension; j++) {
295                 long result = 0;
296                 for (int k = 1; k <= BITS; k++) {
297                     final long shift = grayCode >> (k - 1);
298                     if (shift == 0) {
299                         // stop, as all remaining bits will be zero
300                         break;
301                     }
302                     // the k-th bit of i
303                     final long ik = shift & 1;
304                     result ^= ik * direction[j][k];
305                 }
306                 x[j] = result;
307             }
308         }
309         count = index;
310         return get();
311     }
312 
313     /**
314      * Returns the index i of the next point in the Sobol sequence that will be returned
315      * by calling {@link #get()}.
316      *
317      * @return the index of the next point
318      */
319     public int getNextIndex() {
320         return count;
321     }
322 }