1 /* boost random/sobol.hpp header file
2  *
3  * Copyright Justinas Vygintas Daugmaudis 2010-2018
4  * Distributed under the Boost Software License, Version 1.0. (See
5  * accompanying file LICENSE_1_0.txt or copy at
6  * http://www.boost.org/LICENSE_1_0.txt)
7  */
8 
9 #ifndef BOOST_RANDOM_SOBOL_HPP
10 #define BOOST_RANDOM_SOBOL_HPP
11 
12 #include <boost/random/detail/sobol_table.hpp>
13 #include <boost/random/detail/gray_coded_qrng.hpp>
14 
15 namespace boost {
16 namespace random {
17 
18 /** @cond */
19 namespace qrng_detail {
20 
21 // sobol_lattice sets up the random-number generator to produce a Sobol
22 // sequence of at most max dims-dimensional quasi-random vectors.
23 // Adapted from ACM TOMS algorithm 659, see
24 
25 // http://doi.acm.org/10.1145/42288.214372
26 
27 template<typename UIntType, unsigned w, typename SobolTables>
28 struct sobol_lattice
29 {
30   typedef UIntType value_type;
31 
32   BOOST_STATIC_ASSERT(w > 0u);
33   BOOST_STATIC_CONSTANT(unsigned, bit_count = w);
34 
35 private:
36   typedef std::vector<value_type> container_type;
37 
38 public:
sobol_latticeboost::random::qrng_detail::sobol_lattice39   explicit sobol_lattice(std::size_t dimension)
40   {
41     resize(dimension);
42   }
43 
44   // default copy c-tor is fine
45 
resizeboost::random::qrng_detail::sobol_lattice46   void resize(std::size_t dimension)
47   {
48     dimension_assert("Sobol", dimension, SobolTables::max_dimension);
49 
50     // Initialize the bit array
51     container_type cj(bit_count * dimension);
52 
53     // Initialize direction table in dimension 0
54     for (unsigned k = 0; k != bit_count; ++k)
55       cj[dimension*k] = static_cast<value_type>(1);
56 
57     // Initialize in remaining dimensions.
58     for (std::size_t dim = 1; dim < dimension; ++dim)
59     {
60       const typename SobolTables::value_type poly = SobolTables::polynomial(dim-1);
61       if (poly > std::numeric_limits<value_type>::max()) {
62         boost::throw_exception( std::range_error("sobol: polynomial value outside the given value type range") );
63       }
64       const unsigned degree = multiprecision::msb(poly); // integer log2(poly)
65 
66       // set initial values of m from table
67       for (unsigned k = 0; k != degree; ++k)
68         cj[dimension*k + dim] = SobolTables::minit(dim-1, k);
69 
70       // Calculate remaining elements for this dimension,
71       // as explained in Bratley+Fox, section 2.
72       for (unsigned j = degree; j < bit_count; ++j)
73       {
74         typename SobolTables::value_type p_i = poly;
75         const std::size_t bit_offset = dimension*j + dim;
76 
77         cj[bit_offset] = cj[dimension*(j-degree) + dim];
78         for (unsigned k = 0; k != degree; ++k, p_i >>= 1)
79         {
80           int rem = degree - k;
81           cj[bit_offset] ^= ((p_i & 1) * cj[dimension*(j-rem) + dim]) << rem;
82         }
83       }
84     }
85 
86     // Shift columns by appropriate power of 2.
87     unsigned p = 1u;
88     for (int j = bit_count-1-1; j >= 0; --j, ++p)
89     {
90       const std::size_t bit_offset = dimension * j;
91       for (std::size_t dim = 0; dim != dimension; ++dim)
92         cj[bit_offset + dim] <<= p;
93     }
94 
95     bits.swap(cj);
96   }
97 
iter_atboost::random::qrng_detail::sobol_lattice98   typename container_type::const_iterator iter_at(std::size_t n) const
99   {
100     BOOST_ASSERT(!(n > bits.size()));
101     return bits.begin() + n;
102   }
103 
104 private:
105   container_type bits;
106 };
107 
108 } // namespace qrng_detail
109 
110 typedef detail::qrng_tables::sobol default_sobol_table;
111 
112 /** @endcond */
113 
114 //!Instantiations of class template sobol_engine model a \quasi_random_number_generator.
115 //!The sobol_engine uses the algorithm described in
116 //! \blockquote
117 //![Bratley+Fox, TOMS 14, 88 (1988)]
118 //!and [Antonov+Saleev, USSR Comput. Maths. Math. Phys. 19, 252 (1980)]
119 //! \endblockquote
120 //!
121 //!\attention sobol_engine skips trivial zeroes at the start of the sequence. For example, the beginning
122 //!of the 2-dimensional Sobol sequence in @c uniform_01 distribution will look like this:
123 //!\code{.cpp}
124 //!0.5, 0.5,
125 //!0.75, 0.25,
126 //!0.25, 0.75,
127 //!0.375, 0.375,
128 //!0.875, 0.875,
129 //!...
130 //!\endcode
131 //!
132 //!In the following documentation @c X denotes the concrete class of the template
133 //!sobol_engine returning objects of type @c UIntType, u and v are the values of @c X.
134 //!
135 //!Some member functions may throw exceptions of type @c std::range_error. This
136 //!happens when the quasi-random domain is exhausted and the generator cannot produce
137 //!any more values. The length of the low discrepancy sequence is given by \f$L=Dimension \times (2^{w} - 1)\f$.
138 template<typename UIntType, unsigned w, typename SobolTables = default_sobol_table>
139 class sobol_engine
140   : public qrng_detail::gray_coded_qrng<
141       qrng_detail::sobol_lattice<UIntType, w, SobolTables>
142     >
143 {
144   typedef qrng_detail::sobol_lattice<UIntType, w, SobolTables> lattice_t;
145   typedef qrng_detail::gray_coded_qrng<lattice_t> base_t;
146 
147 public:
148   //!Effects: Constructs the default `s`-dimensional Sobol quasi-random number generator.
149   //!
150   //!Throws: bad_alloc, invalid_argument, range_error.
sobol_engine(std::size_t s)151   explicit sobol_engine(std::size_t s)
152     : base_t(s)
153   {}
154 
155   // default copy c-tor is fine
156 
157 #ifdef BOOST_RANDOM_DOXYGEN
158   //=========================Doxygen needs this!==============================
159   typedef UIntType result_type;
160 
161   /** @copydoc boost::random::niederreiter_base2_engine::min() */
BOOST_PREVENT_MACRO_SUBSTITUTION()162   static BOOST_CONSTEXPR result_type min BOOST_PREVENT_MACRO_SUBSTITUTION ()
163   { return (base_t::min)(); }
164 
165   /** @copydoc boost::random::niederreiter_base2_engine::max() */
BOOST_PREVENT_MACRO_SUBSTITUTION()166   static BOOST_CONSTEXPR result_type max BOOST_PREVENT_MACRO_SUBSTITUTION ()
167   { return (base_t::max)(); }
168 
169   /** @copydoc boost::random::niederreiter_base2_engine::dimension() */
dimension() const170   std::size_t dimension() const { return base_t::dimension(); }
171 
172   /** @copydoc boost::random::niederreiter_base2_engine::seed() */
seed()173   void seed()
174   {
175     base_t::seed();
176   }
177 
178   /** @copydoc boost::random::niederreiter_base2_engine::seed(UIntType) */
seed(UIntType init)179   void seed(UIntType init)
180   {
181     base_t::seed(init);
182   }
183 
184   /** @copydoc boost::random::niederreiter_base2_engine::operator()() */
operator ()()185   result_type operator()()
186   {
187     return base_t::operator()();
188   }
189 
190   /** @copydoc boost::random::niederreiter_base2_engine::discard(boost::uintmax_t) */
discard(boost::uintmax_t z)191   void discard(boost::uintmax_t z)
192   {
193     base_t::discard(z);
194   }
195 
196   /** Returns true if the two generators will produce identical sequences of outputs. */
BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(sobol_engine,x,y)197   BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(sobol_engine, x, y)
198   { return static_cast<const base_t&>(x) == y; }
199 
200   /** Returns true if the two generators will produce different sequences of outputs. */
201   BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(sobol_engine)
202 
203   /** Writes the textual representation of the generator to a @c std::ostream. */
BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os,sobol_engine,s)204   BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os, sobol_engine, s)
205   { return os << static_cast<const base_t&>(s); }
206 
207   /** Reads the textual representation of the generator from a @c std::istream. */
BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is,sobol_engine,s)208   BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, sobol_engine, s)
209   { return is >> static_cast<base_t&>(s); }
210 
211 #endif // BOOST_RANDOM_DOXYGEN
212 };
213 
214 /**
215  * @attention This specialization of \sobol_engine supports up to 3667 dimensions.
216  *
217  * Data on the primitive binary polynomials `a` and the corresponding starting values `m`
218  * for Sobol sequences in up to 21201 dimensions was taken from
219  *
220  *  @blockquote
221  *  S. Joe and F. Y. Kuo, Constructing Sobol sequences with better two-dimensional projections,
222  *  SIAM J. Sci. Comput. 30, 2635-2654 (2008).
223  *  @endblockquote
224  *
225  * See the original tables up to dimension 21201: https://web.archive.org/web/20170802022909/http://web.maths.unsw.edu.au/~fkuo/sobol/new-joe-kuo-6.21201
226  *
227  * For practical reasons the default table uses only the subset of binary polynomials `a` < 2<sup>16</sup>.
228  *
229  * However, it is possible to provide your own table to \sobol_engine should the default one be insufficient.
230  */
231 typedef sobol_engine<boost::uint_least64_t, 64u, default_sobol_table> sobol;
232 
233 } // namespace random
234 
235 } // namespace boost
236 
237 #endif // BOOST_RANDOM_SOBOL_HPP
238