1 
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
9 // Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 // THE SOFTWARE.
28 //
29 
30 #pragma once
31 
32 // appleseed.foundation headers.
33 #include "foundation/math/permutation.h"
34 #include "foundation/math/primes.h"
35 #include "foundation/math/qmc.h"
36 #include "foundation/math/rng/distribution.h"
37 #include "foundation/math/vector.h"
38 #include "foundation/utility/test/helpers.h"
39 
40 // Standard headers.
41 #include <cassert>
42 #include <cstddef>
43 
44 // Unit test case declarations.
45 DECLARE_TEST_CASE(Foundation_Math_Sampling_QMCSamplingContext, InitialStateIsCorrect);
46 DECLARE_TEST_CASE(Foundation_Math_Sampling_QMCSamplingContext, TestCopyConstructor);
47 DECLARE_TEST_CASE(Foundation_Math_Sampling_QMCSamplingContext, TestAssignmentOperator);
48 DECLARE_TEST_CASE(Foundation_Math_Sampling_QMCSamplingContext, TestSplitting);
49 DECLARE_TEST_CASE(Foundation_Math_Sampling_QMCSamplingContext, TestDoubleSplitting);
50 
51 namespace foundation
52 {
53 
54 //
55 // A sampling context featuring:
56 //
57 //   - deterministic sampling based on Halton sequences
58 //   - Faure digit scrambling
59 //   - Cranley-Patterson rotation
60 //   - Monte Carlo padding
61 //
62 // Reference:
63 //
64 //   Kollig and Keller, Efficient Multidimensional Sampling
65 //   www.uni-kl.de/AG-Heinrich/EMS.pdf
66 //
67 
68 template <typename RNG>
69 class QMCSamplingContext
70 {
71   public:
72     // Random number generator type.
73     typedef RNG RNGType;
74 
75     // This sampler can operate in two modes:
76     //   1. In QMC mode, it uses possibly patent-encumbered techniques.
77     //   2. In RNG mode, it works like `RNGSamplingContext` and sticks to random sampling.
78     enum Mode { QMCMode, RNGMode };
79 
80     // Construct a sampling context of dimension 0.
81     // The resulting sampling context cannot be used directly;
82     // only child contexts obtained by splitting can.
83     QMCSamplingContext(
84         RNG&            rng,
85         const Mode      mode,
86         const size_t    base_instance = 0);
87 
88     // Construct a sampling context for a given number of dimensions
89     // and samples. Set `sample_count` to 0 if the required number of
90     // samples is unknown or infinite.
91     QMCSamplingContext(
92         RNG&            rng,
93         const Mode      mode,
94         const size_t    dimension,
95         const size_t    sample_count,
96         const size_t    instance = 0);
97 
98     // Assignment operator.
99     // Both sampling contexts must use the same RNG.
100     QMCSamplingContext& operator=(const QMCSamplingContext& rhs);
101 
102     // Trajectory splitting: return a child sampling context for
103     // a given number of dimensions and samples.
104     QMCSamplingContext split(
105         const size_t    dimension,
106         const size_t    sample_count) const;
107 
108     // In-place trajectory splitting.
109     void split_in_place(
110         const size_t    dimension,
111         const size_t    sample_count);
112 
113     // Set the instance number.
114     void set_instance(const size_t instance);
115 
116     // Return the next sample in [0,1)^N.
117     // Works for scalars and `foundation::Vector<>`.
118     template <typename T> T next2();
119 
120     // Return the total dimension of this sampler.
121     size_t get_total_dimension() const;
122 
123     // Return the total instance number of this sampler.
124     size_t get_total_instance() const;
125 
126   private:
127     GRANT_ACCESS_TO_TEST_CASE(Foundation_Math_Sampling_QMCSamplingContext, InitialStateIsCorrect);
128     GRANT_ACCESS_TO_TEST_CASE(Foundation_Math_Sampling_QMCSamplingContext, TestCopyConstructor);
129     GRANT_ACCESS_TO_TEST_CASE(Foundation_Math_Sampling_QMCSamplingContext, TestAssignmentOperator);
130     GRANT_ACCESS_TO_TEST_CASE(Foundation_Math_Sampling_QMCSamplingContext, TestSplitting);
131     GRANT_ACCESS_TO_TEST_CASE(Foundation_Math_Sampling_QMCSamplingContext, TestDoubleSplitting);
132 
133     typedef Vector<double, 4> VectorType;
134 
135     RNG&        m_rng;
136     Mode        m_mode;
137 
138     size_t      m_base_dimension;
139     size_t      m_base_instance;
140 
141     size_t      m_dimension;
142     size_t      m_sample_count;
143 
144     size_t      m_instance;
145     VectorType  m_offset;
146 
147     // Cranley-Patterson rotation.
148     template <typename T>
149     static T rotate(T x, const T offset);
150 
151     QMCSamplingContext(
152         RNG&            rng,
153         const Mode      mode,
154         const size_t    base_dimension,
155         const size_t    base_instance,
156         const size_t    dimension,
157         const size_t    sample_count);
158 
159     void compute_offset();
160 
161     template <typename T> struct Tag {};
162 
163     template <typename T> T next2(Tag<T>);
164     template <typename T, size_t N> Vector<T, N> next2(Tag<Vector<T, N>>);
165 };
166 
167 
168 //
169 // QMCSamplingContext class implementation.
170 //
171 
172 template <typename RNG>
QMCSamplingContext(RNG & rng,const Mode mode,const size_t base_instance)173 inline QMCSamplingContext<RNG>::QMCSamplingContext(
174     RNG&                rng,
175     const Mode          mode,
176     const size_t        base_instance)
177   : m_rng(rng)
178   , m_mode(mode)
179   , m_base_dimension(0)
180   , m_base_instance(base_instance)
181   , m_dimension(0)
182   , m_sample_count(0)
183   , m_instance(0)
184   , m_offset(0.0)
185 {
186 }
187 
188 template <typename RNG>
QMCSamplingContext(RNG & rng,const Mode mode,const size_t dimension,const size_t sample_count,const size_t instance)189 inline QMCSamplingContext<RNG>::QMCSamplingContext(
190     RNG&                rng,
191     const Mode          mode,
192     const size_t        dimension,
193     const size_t        sample_count,
194     const size_t        instance)
195   : m_rng(rng)
196   , m_mode(mode)
197   , m_base_dimension(0)
198   , m_base_instance(0)
199   , m_dimension(dimension)
200   , m_sample_count(sample_count)
201   , m_instance(instance)
202   , m_offset(0.0)
203 {
204     assert(dimension <= VectorType::Dimension);
205 }
206 
207 template <typename RNG>
QMCSamplingContext(RNG & rng,const Mode mode,const size_t base_dimension,const size_t base_instance,const size_t dimension,const size_t sample_count)208 inline QMCSamplingContext<RNG>::QMCSamplingContext(
209     RNG&                rng,
210     const Mode          mode,
211     const size_t        base_dimension,
212     const size_t        base_instance,
213     const size_t        dimension,
214     const size_t        sample_count)
215   : m_rng(rng)
216   , m_mode(mode)
217   , m_base_dimension(base_dimension)
218   , m_base_instance(base_instance)
219   , m_dimension(dimension)
220   , m_sample_count(sample_count)
221   , m_instance(0)
222 {
223     assert(dimension <= VectorType::Dimension);
224 
225     if (m_mode == QMCMode)
226         compute_offset();
227 }
228 
229 template <typename RNG> inline
230 QMCSamplingContext<RNG>&
231 QMCSamplingContext<RNG>::operator=(const QMCSamplingContext& rhs)
232 {
233     assert(&m_rng == &rhs.m_rng);
234 
235     m_mode = rhs.m_mode;
236     m_base_dimension = rhs.m_base_dimension;
237     m_base_instance = rhs.m_base_instance;
238     m_dimension = rhs.m_dimension;
239     m_sample_count = rhs.m_sample_count;
240     m_instance = rhs.m_instance;
241     m_offset = rhs.m_offset;
242 
243     return *this;
244 }
245 
246 template <typename RNG>
split(const size_t dimension,const size_t sample_count)247 inline QMCSamplingContext<RNG> QMCSamplingContext<RNG>::split(
248     const size_t        dimension,
249     const size_t        sample_count) const
250 {
251     return
252         QMCSamplingContext(
253             m_rng,
254             m_mode,
255             m_base_dimension + m_dimension,         // dimension allocation
256             m_base_instance + m_instance,           // decorrelation by generalization
257             dimension,
258             sample_count);
259 }
260 
261 template <typename RNG>
split_in_place(const size_t dimension,const size_t sample_count)262 inline void QMCSamplingContext<RNG>::split_in_place(
263     const size_t        dimension,
264     const size_t        sample_count)
265 {
266     assert(m_sample_count == 0 || m_instance == m_sample_count);    // can't split in the middle of a sequence
267     assert(dimension <= VectorType::Dimension);
268 
269     m_base_dimension += m_dimension;                // dimension allocation
270     m_base_instance += m_instance;                  // decorrelation by generalization
271     m_dimension = dimension;
272     m_sample_count = sample_count;
273     m_instance = 0;
274 
275     if (m_mode == QMCMode)
276         compute_offset();
277 }
278 
279 template <typename RNG>
set_instance(const size_t instance)280 inline void QMCSamplingContext<RNG>::set_instance(const size_t instance)
281 {
282     m_instance = instance;
283 }
284 
285 template <typename RNG>
286 template <typename T>
next2()287 inline T QMCSamplingContext<RNG>::next2()
288 {
289     return next2(Tag<T>());
290 }
291 
292 template <typename RNG>
get_total_dimension()293 inline size_t QMCSamplingContext<RNG>::get_total_dimension() const
294 {
295     return m_base_dimension + m_dimension;
296 }
297 
298 template <typename RNG>
get_total_instance()299 inline size_t QMCSamplingContext<RNG>::get_total_instance() const
300 {
301     return m_base_instance + m_instance;
302 }
303 
304 template <typename RNG>
305 template <typename T>
rotate(T x,const T offset)306 inline T QMCSamplingContext<RNG>::rotate(T x, const T offset)
307 {
308     assert(offset >= T(0.0));
309 
310     x += offset;
311 
312     if (x >= T(1.0))
313         x -= T(1.0);
314 
315     return x;
316 }
317 
318 template <typename RNG>
compute_offset()319 inline void QMCSamplingContext<RNG>::compute_offset()
320 {
321     for (size_t i = 0, d = m_base_dimension; i < m_dimension; ++i, ++d)
322     {
323         if (d < FaurePermutationTableSize)
324         {
325             assert(d < PrimeTableSize);
326 
327             m_offset[i] =
328                 fast_permuted_radical_inverse<double>(
329                     d,
330                     FaurePermutations[d],
331                     m_base_instance);
332         }
333         else
334         {
335             // Monte Carlo padding.
336             m_offset[i] = rand_double2(m_rng);
337         }
338     }
339 }
340 
341 template <typename RNG>
342 template <typename T>
next2(Tag<T>)343 inline T QMCSamplingContext<RNG>::next2(Tag<T>)
344 {
345     return next2(Tag<Vector<T, 1>>())[0];
346 }
347 
348 template <typename RNG>
349 template <typename T, size_t N>
next2(Tag<Vector<T,N>>)350 inline Vector<T, N> QMCSamplingContext<RNG>::next2(Tag<Vector<T, N>>)
351 {
352     Vector<T, N> v;
353 
354     assert(m_sample_count == 0 || m_instance < m_sample_count);
355     assert(N == m_dimension);
356     assert(N <= PrimeTableSize);
357 
358     if (m_mode == QMCMode)
359     {
360         if (m_instance < PrecomputedHaltonSequenceSize)
361         {
362             for (size_t i = 0; i < N; ++i)
363             {
364                 v[i] = static_cast<T>(PrecomputedHaltonSequence[m_instance * 4 + i]);
365                 v[i] = rotate(v[i], static_cast<T>(m_offset[i]));
366             }
367         }
368         else
369         {
370             v[0] = radical_inverse_base2<T>(m_instance);
371             v[0] = rotate(v[0], static_cast<T>(m_offset[0]));
372 
373             for (size_t i = 1; i < N; ++i)
374             {
375                 v[i] = fast_radical_inverse<T>(i, m_instance);
376                 v[i] = rotate(v[i], static_cast<T>(m_offset[i]));
377             }
378         }
379     }
380     else
381     {
382         for (size_t i = 0; i < N; ++i)
383             v[i] = rand2<T>(m_rng);
384     }
385 
386     ++m_instance;
387 
388     return v;
389 }
390 
391 }   // namespace foundation
392