summaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons/math3/random/SobolSequenceGenerator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/apache/commons/math3/random/SobolSequenceGenerator.java')
-rw-r--r--src/main/java/org/apache/commons/math3/random/SobolSequenceGenerator.java329
1 files changed, 329 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/math3/random/SobolSequenceGenerator.java b/src/main/java/org/apache/commons/math3/random/SobolSequenceGenerator.java
new file mode 100644
index 0000000..7b91c7b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/SobolSequenceGenerator.java
@@ -0,0 +1,329 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math3.random;
+
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+/**
+ * Implementation of a Sobol sequence.
+ *
+ * <p>A Sobol sequence is a low-discrepancy sequence with the property that for all values of N, its
+ * subsequence (x1, ... xN) has a low discrepancy. It can be used to generate pseudo-random points
+ * in a space S, which are equi-distributed.
+ *
+ * <p>The implementation already comes with support for up to 1000 dimensions with direction numbers
+ * calculated from <a href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Stephen Joe and Frances
+ * Kuo</a>.
+ *
+ * <p>The generator supports two modes:
+ *
+ * <ul>
+ * <li>sequential generation of points: {@link #nextVector()}
+ * <li>random access to the i-th point in the sequence: {@link #skipTo(int)}
+ * </ul>
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Sobol_sequence">Sobol sequence (Wikipedia)</a>
+ * @see <a href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Sobol sequence direction numbers</a>
+ * @since 3.3
+ */
+public class SobolSequenceGenerator implements RandomVectorGenerator {
+
+ /** The number of bits to use. */
+ private static final int BITS = 52;
+
+ /** The scaling factor. */
+ private static final double SCALE = FastMath.pow(2, BITS);
+
+ /** The maximum supported space dimension. */
+ private static final int MAX_DIMENSION = 1000;
+
+ /** The resource containing the direction numbers. */
+ private static final String RESOURCE_NAME =
+ "/assets/org/apache/commons/math3/random/new-joe-kuo-6.1000";
+
+ /** Character set for file input. */
+ private static final String FILE_CHARSET = "US-ASCII";
+
+ /** Space dimension. */
+ private final int dimension;
+
+ /** The current index in the sequence. */
+ private int count = 0;
+
+ /** The direction vector for each component. */
+ private final long[][] direction;
+
+ /** The current state. */
+ private final long[] x;
+
+ /**
+ * Construct a new Sobol sequence generator for the given space dimension.
+ *
+ * @param dimension the space dimension
+ * @throws OutOfRangeException if the space dimension is outside the allowed range of [1, 1000]
+ */
+ public SobolSequenceGenerator(final int dimension) throws OutOfRangeException {
+ if (dimension < 1 || dimension > MAX_DIMENSION) {
+ throw new OutOfRangeException(dimension, 1, MAX_DIMENSION);
+ }
+
+ // initialize the other dimensions with direction numbers from a resource
+ final InputStream is = getClass().getResourceAsStream(RESOURCE_NAME);
+ if (is == null) {
+ throw new MathInternalError();
+ }
+
+ this.dimension = dimension;
+
+ // init data structures
+ direction = new long[dimension][BITS + 1];
+ x = new long[dimension];
+
+ try {
+ initFromStream(is);
+ } catch (IOException e) {
+ // the internal resource file could not be read -> should not happen
+ throw new MathInternalError();
+ } catch (MathParseException e) {
+ // the internal resource file could not be parsed -> should not happen
+ throw new MathInternalError();
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) { // NOPMD
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Construct a new Sobol sequence generator for the given space dimension with direction vectors
+ * loaded from the given stream.
+ *
+ * <p>The expected format is identical to the files available from <a
+ * href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Stephen Joe and Frances Kuo</a>. The first
+ * line will be ignored as it is assumed to contain only the column headers. The columns are:
+ *
+ * <ul>
+ * <li>d: the dimension
+ * <li>s: the degree of the primitive polynomial
+ * <li>a: the number representing the coefficients
+ * <li>m: the list of initial direction numbers
+ * </ul>
+ *
+ * Example:
+ *
+ * <pre>
+ * d s a m_i
+ * 2 1 0 1
+ * 3 2 1 1 3
+ * </pre>
+ *
+ * <p>The input stream <i>must</i> be an ASCII text containing one valid direction vector per
+ * line.
+ *
+ * @param dimension the space dimension
+ * @param is the stream to read the direction vectors from
+ * @throws NotStrictlyPositiveException if the space dimension is &lt; 1
+ * @throws OutOfRangeException if the space dimension is outside the range [1, max], where max
+ * refers to the maximum dimension found in the input stream
+ * @throws MathParseException if the content in the stream could not be parsed successfully
+ * @throws IOException if an error occurs while reading from the input stream
+ */
+ public SobolSequenceGenerator(final int dimension, final InputStream is)
+ throws NotStrictlyPositiveException, MathParseException, IOException {
+
+ if (dimension < 1) {
+ throw new NotStrictlyPositiveException(dimension);
+ }
+
+ this.dimension = dimension;
+
+ // init data structures
+ direction = new long[dimension][BITS + 1];
+ x = new long[dimension];
+
+ // initialize the other dimensions with direction numbers from the stream
+ int lastDimension = initFromStream(is);
+ if (lastDimension < dimension) {
+ throw new OutOfRangeException(dimension, 1, lastDimension);
+ }
+ }
+
+ /**
+ * Load the direction vector for each dimension from the given stream.
+ *
+ * <p>The input stream <i>must</i> be an ASCII text containing one valid direction vector per
+ * line.
+ *
+ * @param is the input stream to read the direction vector from
+ * @return the last dimension that has been read from the input stream
+ * @throws IOException if the stream could not be read
+ * @throws MathParseException if the content could not be parsed successfully
+ */
+ private int initFromStream(final InputStream is) throws MathParseException, IOException {
+
+ // special case: dimension 1 -> use unit initialization
+ for (int i = 1; i <= BITS; i++) {
+ direction[0][i] = 1l << (BITS - i);
+ }
+
+ final Charset charset = Charset.forName(FILE_CHARSET);
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset));
+ int dim = -1;
+
+ try {
+ // ignore first line
+ reader.readLine();
+
+ int lineNumber = 2;
+ int index = 1;
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ StringTokenizer st = new StringTokenizer(line, " ");
+ try {
+ dim = Integer.parseInt(st.nextToken());
+ if (dim >= 2 && dim <= dimension) { // we have found the right dimension
+ final int s = Integer.parseInt(st.nextToken());
+ final int a = Integer.parseInt(st.nextToken());
+ final int[] m = new int[s + 1];
+ for (int i = 1; i <= s; i++) {
+ m[i] = Integer.parseInt(st.nextToken());
+ }
+ initDirectionVector(index++, a, m);
+ }
+
+ if (dim > dimension) {
+ return dim;
+ }
+ } catch (NoSuchElementException e) {
+ throw new MathParseException(line, lineNumber);
+ } catch (NumberFormatException e) {
+ throw new MathParseException(line, lineNumber);
+ }
+ lineNumber++;
+ }
+ } finally {
+ reader.close();
+ }
+
+ return dim;
+ }
+
+ /**
+ * Calculate the direction numbers from the given polynomial.
+ *
+ * @param d the dimension, zero-based
+ * @param a the coefficients of the primitive polynomial
+ * @param m the initial direction numbers
+ */
+ private void initDirectionVector(final int d, final int a, final int[] m) {
+ final int s = m.length - 1;
+ for (int i = 1; i <= s; i++) {
+ direction[d][i] = ((long) m[i]) << (BITS - i);
+ }
+ for (int i = s + 1; i <= BITS; i++) {
+ direction[d][i] = direction[d][i - s] ^ (direction[d][i - s] >> s);
+ for (int k = 1; k <= s - 1; k++) {
+ direction[d][i] ^= ((a >> (s - 1 - k)) & 1) * direction[d][i - k];
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public double[] nextVector() {
+ final double[] v = new double[dimension];
+ if (count == 0) {
+ count++;
+ return v;
+ }
+
+ // find the index c of the rightmost 0
+ int c = 1;
+ int value = count - 1;
+ while ((value & 1) == 1) {
+ value >>= 1;
+ c++;
+ }
+
+ for (int i = 0; i < dimension; i++) {
+ x[i] ^= direction[i][c];
+ v[i] = (double) x[i] / SCALE;
+ }
+ count++;
+ return v;
+ }
+
+ /**
+ * Skip to the i-th point in the Sobol sequence.
+ *
+ * <p>This operation can be performed in O(1).
+ *
+ * @param index the index in the sequence to skip to
+ * @return the i-th point in the Sobol sequence
+ * @throws NotPositiveException if index &lt; 0
+ */
+ public double[] skipTo(final int index) throws NotPositiveException {
+ if (index == 0) {
+ // reset x vector
+ Arrays.fill(x, 0);
+ } else {
+ final int i = index - 1;
+ final long grayCode = i ^ (i >> 1); // compute the gray code of i = i XOR floor(i / 2)
+ for (int j = 0; j < dimension; j++) {
+ long result = 0;
+ for (int k = 1; k <= BITS; k++) {
+ final long shift = grayCode >> (k - 1);
+ if (shift == 0) {
+ // stop, as all remaining bits will be zero
+ break;
+ }
+ // the k-th bit of i
+ final long ik = shift & 1;
+ result ^= ik * direction[j][k];
+ }
+ x[j] = result;
+ }
+ }
+ count = index;
+ return nextVector();
+ }
+
+ /**
+ * Returns the index i of the next point in the Sobol sequence that will be returned by calling
+ * {@link #nextVector()}.
+ *
+ * @return the index of the next point
+ */
+ public int getNextIndex() {
+ return count;
+ }
+}