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