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 < 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 < 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 }