├── LICENSE ├── README.md ├── generate_samples.cpp ├── images ├── pmj02bn.jpg ├── sfaure03.jpg └── ssobol02.jpg ├── makefile ├── sampling ├── bn_utils.cpp ├── bn_utils.h ├── rng.h ├── sfaure.cpp ├── sfaure.h ├── shalton.cpp ├── shalton.h ├── shuffling.cpp ├── shuffling.h ├── ssobol.cpp ├── ssobol.h ├── utils.h └── xor_values.h ├── shuffle_indices.cpp └── xor_values.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Andrew Helmer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stochastic Generation of (t,s) Sample Sequences 2 | 3 | This is a C++ implementation of a large portion of the techniques and algorithms described in ["Stochastic Generation of (t,s) Sample Sequences"](https://graphics.pixar.com/library/SampleSequences/), by Helmer (that's me!), [Christensen](https://www.seanet.com/~myandper/per.htm), and [Kensler](http://eastfarthing.com/) (2021). 4 | 5 | Short-version for people who aren't familiar with the paper: if you want to generate Owen-scrambled Sobol', Halton, or Faure sequences, or pmj02(bn) sequences, I don't think there are any faster or simpler open-source implementations out there. 6 | 7 | ## Generating Samples 8 | 9 | This repository has two main goals. The first is to make it easy for people to generate various sequences from the paper, with different options that the techniques provide. The second is to provide reference implementations, so that others can build on these sequences and techniques and maybe better understand the paper. 10 | 11 | To generate samples, one should first build the "generate_samples" utility. The example commands given here will apply to shells in Linux or macOS. In a shell, you'd first run: 12 | 13 |
make generate_samples
14 | 15 | One can then generate different sequences by using the --seq= flag to specify what kind of sample sequence to generate, --n= to specify the number samples, and --nd= to specify the number of dimensions of a multidimensional sequence. For example, one can generate 4096 samples of a 5D stochastic Sobol' sequence with the following Bash command: 16 | 17 |
./generate_samples --seq=ssobol --n=4096 --nd=5 > /my/samples/directory/ssobol.txt
18 | 19 | ### Available Sample Sequences 20 | 21 | This library implements seven sample sequences (or algorithms) that can be generated: 22 | 23 | #### ssobol 24 | 25 | The stochastic Sobol' sequence. One can generate up to 64 dimensions. The first two dimensions form a base-2 (0,2)-sequence, which means that they are stratified on all base-2 elementary intervals for a power-of-two prefix of samples. This is shown for the first 16 samples: 26 | 27 |

28 |
29 |

30 | 31 | Actually this is true for all power-of-two "disjoint subsequences". So it's not just the first 16 samples, but the next 16, and the 16 after that, and so on. 32 | 33 | #### pmj02 34 | 35 | We can generate the pmj02 sequence from "Progressive Multi-Jittered Sample Sequences" by Christensen, Kensler, and Kilpatrick (2018), but using the Stochastic Generation approach as described in our supplemental material. This is only a 2D sequence, so the flag --nd=2 must be set. For most intents and purposes, this will be the same as the first two dimensions of the ssobol sequence, although the points will be ordered differently within each power of two. 36 | 37 | #### sfaure(03|05|07|011) 38 | 39 | This library implements sfaure03, sfaure05, sfaure07, and sfaure011 sequences. The sfaure03 sequence is a base-3 (0,3)-sequence. This means that it's a 3-dimensional sequence where any power-of-three disjoint subsequence of samples is going to be distributed on all base-3 elementary intervals. Here's Figure 7 from the paper, showing how well stratified that is, in 3D, in all three 2D projections, and all three 1D projections: 40 | 41 |

42 |
43 |

44 | 45 | If you wanted to estimate a 3D integral, and you can accept the limitation of only using powers of three, this will give very low error. The same is true using an sfaure05 sequence for a 5D integral, at powers of five. The code would be very easy to extend to higher prime bases (e.g. a stochastic Faure (0,13)-sequence), but they probably wouldn't be very useful in practice. 46 | 47 | #### shalton 48 | 49 | The stochastic Halton sequence. The Halton sequence doesn't have all of the elementary interval stratifications of the sfaure sequences or the first two dimensions of the ssobol sequence, but it does a pretty nice job of guaranteeing stratifications on all lower-dimensional projections across different sample counts. 50 | 51 | The Halton sequence is constructed with subsequent prime number bases for each dimension, e.g. the first two dimension is base-2, the second dimension is base-3, the third is base-5, base-7, etc. This implementation goes up to 10 dimensions (base 29), though it could be easily extended to higher. 52 | 53 | So for any subsequence (not just disjoint!) of 2a * 3b * 5c ... points, where the exponents are >= 0, the points are stratified in the 2a x 3b x 5c ... grid within the unit hypercube. Those grids include all lower dimensional projections (i.e. when some exponents are zero). For example, any subsequence of 72 points will be stratified in the 8x9 grid in the first two dimensions. Any subsequence of 15 points will be stratified in the 3x5 grid in the second and third dimension. And so on. 54 | 55 | ### Best-Candidate Sampling 56 | 57 | Every sequence can be augmented with best-candidate sampling in the first two dimensions using the "--bn2d" flag. Rather than picking any random point in a valid strata, many possible candidates are evaluated, and the one that is furthest from all previous points (in those two dimensions) will be used. This can slightly improve point spacing on these sequences. For instance, to generate a pmj02bn sequence, the command would be: 58 | 59 |
./generate_samples --seq=pmj02 --n=4096 --nd=2 > /my/samples/directory/pmj02bn.txt
60 | 61 | Here's a simple comparison of 64 pmj02 points vs. 64 pmj02bn points. The spacing is slightly improved overall, though not dramatically. 62 | 63 |

64 |
65 |

66 | 67 | This was limited to only the first two dimensions for simplicity. But it would be easy to extend the implementation to apply to other dimensions, where it would be appropriate. It could be useful for higher dimension pairs of the Faure or Halton sequences. If you wanted to do this, take a look at the function "GetBestFaurePoint" in sfaure.cpp, and "GetBestHaltonPoint" in shalton.cpp. 68 | 69 | ### Correlated Swapping vs. Owen-Scrambling 70 | 71 | By default, the shalton and sfaure sequences are generated with correlated swapping, which is described in Section 4.7 of the paper. You can instead use "uncorrelated swapping", which would generate sequences that are identical to fully Owen-scrambled sequences, using the --owen flag. For example, the command: 72 | 73 |
./generate_samples --seq=sfaure05 --owen --n=3125 --nd=5 > /my/samples/directory/sfaure05.txt
74 | 75 | will generate an Owen-scrambled Faure (0,5)-sequence. 76 | 77 | We haven't observed much differences between the quality of sequences generated with correlated swapping and Owen-scrambling, but it's still nice to include both here and provide reference implementations for either. One thing to note is that the choice of strata is done randomly even when best-candidate sampling is used. One can get a better sequence when also incorporating multiple strata choices into the best-candidate sampling, and we did this for Figure 2c of the supplemental material. This was omitted because the code to keep track of the available strata is quite complex and ugly, and readability was an important goal for this code. 78 | 79 | ### Shuffling and Decorrelation 80 | 81 | With the --shuffle flag, the ssobol and sfaure sequences will be *progressively* shuffled, which will decorrelate subsequent runs of a particular sequence. Because the shuffling is done progressively, any power-of-b prefix of the sequence will be unchanged, so best-candidate sampling will still improve distances. See Section 5.2 of the paper for more information on this. 82 | 83 | ## Performance 84 | 85 | This repository is meant to somewhat balance generality and readability with the high-performance aspects of the paper. This means that the performance is not as fast as it would be, if the code was written for a single specific sequence and set of options. For example, Listing 2 from the paper, which shows a simple implementation of *only* a 2D Sobol' (0,2)-sequence, without best-candidate sampling, shuffling, or the possibility of higher dimensions, will be about 3x faster than generating the same sequence using this code. Even so, the code in this library is faster than any other published method. 86 | 87 | We also didn't include the "index mapping" precomputation of the sfaure sequences. For one-off generation of each sequence, it wouldn't be any faster, so it didn't make a ton of sense to include here. However if you wanted to generate a lot of sfaure sequences, that precomputation would be helpful. 88 | 89 | ## Progressive Shuffling Indices 90 | 91 | Also included is a command-line utility to generate shuffled index arrays. You would *make* this separately: 92 | 93 |
make shuffle_indices
94 | 95 | And then if you wanted to generate an array of base-3 shuffled indices, of length 9, you would do something like: 96 | 97 |
./shuffle_indices --n=9 --b=3
98 | 99 | which might output: 100 | 101 |
4 5 3 0 2 1 7 8 6
102 | 103 | If you set --progressive, this will do progressive shuffling, maintaining the original prefix samples at each power of the base. For example 104 | 105 |
./shuffle_indices --n=9 --b=3 --progressive
106 | 107 | might give: 108 | 109 |
0 1 2 3 5 4 8 7 6
110 | 111 | ## Xor-value Generation 112 | 113 | The xor_values.py file can be used to generate the xor-values for the sfaure and pmj02 sequences, straight from their mathematical definitions. The xor-values for the Sobol' sequence are based on the choice of direction numbers, which is itself a complex process, so this was omitted. But the PBRT v3 renderer has a set of generator matrices for the Sobol' sequence, which were used to obtain the xor-values in this code. You can find PBRT's generator matrices here: https://raw.githubusercontent.com/mmp/pbrt-v3/main/src/core/sobolmatrices.cpp 114 | 115 | ## Licensing 116 | 117 | Licensed under the MIT Open Source license. See the [LICENSE](/LICENSE) file. 118 | -------------------------------------------------------------------------------- /generate_samples.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * This file implements the "generate_samples" command-line utility. 6 | * Running "make generate_samples" will build the generate_samples command-line 7 | * util, which can be used to stochastically generate different sample 8 | * sequences. 9 | * 10 | * The required arguments are: 11 | * --seq= for which sampler to use 12 | * Valid algorithms are: 13 | * { ssobol, ssobol-stateless, pmj02, shalton, sfaure(03|05|07|011), uniform } 14 | * --n= is "number of samples" 15 | * --nd= is "number of dimensions" 16 | * 17 | * e.g. 18 | * ./generate_samples --a=shalton --n=500 --nd=5 19 | * 20 | * Optional arguments are: 21 | * --owen will use full owen-scrambling rather than correlated swapping for the 22 | * sfaure and shalton sequences. 23 | * --shuffle to perform progressive shuffling, which 24 | * will decorrelate multiple runs of the sequence, while maintaining good 25 | * progressive properties. 26 | * --bn2d will use best-candidate sampling to improve minimum distances for the 27 | * first two dimensions only. Note that for the higher base sequences, it 28 | * will NOT select the strata or delta-strata based on minimum distances. 29 | * That choice is still random. 30 | */ 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "sampling/bn_utils.h" 40 | #include "sampling/ssobol.h" 41 | #include "sampling/sfaure.h" 42 | #include "sampling/shalton.h" 43 | #include "sampling/utils.h" 44 | 45 | using std::string; 46 | 47 | namespace { 48 | // Print out n-dimensional floating point arrays as tuples, one point per line. 49 | // For example, a sequence of N 3D points would be... 50 | // (x_0, y_0, z_0) 51 | // (x_1, y_1, z_1) 52 | // (x_2, y_2, z_2) 53 | // ... 54 | // (x_(n-1), y_(n-1), z_(n-1)) 55 | void printNdPoints(int num_samples, int nd, double* samples) { 56 | for (int i = 0; i < num_samples; i++) { 57 | printf("("); 58 | for (int d = 0; d < nd; d++) { 59 | if (d > 0) printf(", "); 60 | printf("%.17g", samples[i*nd + d]); 61 | } 62 | printf(")\n"); 63 | } 64 | } 65 | 66 | // If the string "arg" starts with the string "arg_name", take the rest of 67 | // arg and put it in *val. 68 | inline void maybeGetStringArg( 69 | const char* arg_name, const char* arg, string* val) { 70 | const int len = strlen(arg_name); 71 | if (strncmp(arg_name, arg, len) == 0) { 72 | *val = string(arg+len); 73 | } 74 | } 75 | 76 | // If the string "arg" starts with the string "arg_name", take the rest of 77 | // arg, parse it as an integer, and put it in *val. 78 | inline void maybeGetIntArg(const char* arg_name, const char* arg, int* val) { 79 | const int len = strlen(arg_name); 80 | if (strncmp(arg_name, arg, len) == 0) { 81 | *val = atoi(arg+len); 82 | } 83 | } 84 | 85 | // If the string "arg" starts with the string "arg_name", set *val to true. 86 | inline void maybeGetBoolArg(const char* arg_name, const char* arg, bool* val) { 87 | const int len = strlen(arg_name); 88 | if (strncmp(arg_name, arg, len) == 0) { 89 | *val = true; 90 | } 91 | } 92 | } // namespace 93 | 94 | int main(int argc, const char** argv) { 95 | typedef void (*sample_fn)(int, int, bool, int, bool, double*); 96 | static const std::unordered_map kSeqMap = { 97 | {"ssobol", &sampling::GetStochasticSobolSamples}, 98 | {"ssobol-stateless", &sampling::GetStochasticSobolStatelessSamples}, 99 | {"pmj02", &sampling::GetPMJ02Samples}, 100 | {"sfaure03", &sampling::GetStochasticFaure03Samples}, 101 | {"sfaure05", &sampling::GetStochasticFaure05Samples}, 102 | {"sfaure07", &sampling::GetStochasticFaure07Samples}, 103 | {"sfaure011", &sampling::GetStochasticFaure011Samples}, 104 | {"shalton", &sampling::GetStochasticHaltonSamples}, 105 | }; 106 | 107 | argc--; 108 | argv++; 109 | 110 | string sequence; 111 | int num_samples = -1; 112 | int num_dims = -1; 113 | bool owen = false; 114 | bool shuffle = false; 115 | bool bn2d = false; 116 | for (int i = 0; i < argc; i++) { 117 | maybeGetStringArg("--seq=", argv[i], &sequence); 118 | maybeGetIntArg("--n=", argv[i], &num_samples); 119 | maybeGetIntArg("--nd=", argv[i], &num_dims); 120 | maybeGetBoolArg("--owen", argv[i], &owen); 121 | maybeGetBoolArg("--shuffle", argv[i], &shuffle); 122 | maybeGetBoolArg("--bn2d", argv[i], &bn2d); 123 | } 124 | 125 | ASSERT(num_samples > 0, 126 | "Need to set --n to a value greater than zero."); 127 | ASSERT(num_dims >= 1, 128 | "Need to set --nd to a value >= 1."); 129 | ASSERT(!bn2d || num_dims >= 2, 130 | "Need at least two dimensions to use --bn2d flag."); 131 | 132 | const int num_candidates = bn2d ? sampling::kBestCandidateSamples : 1; 133 | 134 | auto find_seq = kSeqMap.find(sequence); 135 | if (find_seq != kSeqMap.end()) { 136 | sample_fn get_samples = find_seq->second; 137 | auto samples = std::make_unique(num_samples*num_dims); 138 | auto start = std::chrono::high_resolution_clock::now(); 139 | (*get_samples)(num_samples, num_dims, shuffle, num_candidates, owen, 140 | samples.get()); 141 | auto end = std::chrono::high_resolution_clock::now(); 142 | const double milliseconds = 143 | std::chrono::duration_cast(end - start) 144 | .count() / 1000.0; 145 | std::cerr.precision(4); 146 | std::cerr << num_samples << " points generated in " 147 | << milliseconds << "ms\n"; 148 | printNdPoints(num_samples, num_dims, samples.get()); 149 | 150 | return 0; 151 | } 152 | 153 | printf("%s isn't a valid sequence.\n", sequence.c_str()); 154 | return 1; 155 | } 156 | -------------------------------------------------------------------------------- /images/pmj02bn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andrew-Helmer/stochastic-generation/dbc6e9f2c6bf03ed60fe88cb2893e52d2de2195c/images/pmj02bn.jpg -------------------------------------------------------------------------------- /images/sfaure03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andrew-Helmer/stochastic-generation/dbc6e9f2c6bf03ed60fe88cb2893e52d2de2195c/images/sfaure03.jpg -------------------------------------------------------------------------------- /images/ssobol02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Andrew-Helmer/stochastic-generation/dbc6e9f2c6bf03ed60fe88cb2893e52d2de2195c/images/ssobol02.jpg -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS = -Wall -std=c++14 -I. -O2 2 | BASE_SRCS = $(wildcard sampling/*.cpp) 3 | 4 | all: generate_samples shuffle_indices 5 | 6 | release: CXXFLAGS += -O3 7 | release: generate_samples shuffle_indices 8 | 9 | generate_samples: generate_samples.cpp $(BASE_SRCS) 10 | g++ $(CXXFLAGS) -o generate_samples generate_samples.cpp $(BASE_SRCS) 11 | 12 | shuffle_indices: shuffle_indices.cpp $(BASE_SRCS) 13 | g++ $(CXXFLAGS) -o shuffle_indices shuffle_indices.cpp $(BASE_SRCS) 14 | 15 | clean: 16 | rm -Rf generate_samples shuffle_indices -------------------------------------------------------------------------------- /sampling/bn_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * This file implements the SampleGrid2D class, which is used to store 2D points 6 | * in a uniform grid to accelerate nearest-neighbor searches. 7 | */ 8 | #include "bn_utils.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "utils.h" 15 | 16 | namespace sampling { 17 | namespace { 18 | inline int GetGridIndex(const SampleGrid2D::Point2D& point, 19 | const int grid_width) { 20 | const int x_pos = point.x * grid_width; 21 | const int y_pos = point.y * grid_width; 22 | return y_pos * grid_width + x_pos; 23 | } 24 | } 25 | 26 | void SampleGrid2D::AddSample(const Point2D sample) { 27 | int grid_idx = GetGridIndex(sample, width_); 28 | // If there's already a point in this grid cell, we need to repeatedly 29 | // subdivide the grid until there that point and the new point won't be in the 30 | // same grid cell. 31 | if (sample_grid_[grid_idx] != nullptr) { 32 | // Figure out how many times we need to subdivide the grid. 33 | Point2D conflicting_point = *sample_grid_[grid_idx]; 34 | int subdivisions = 1; 35 | int temp_width = width_ * 2; 36 | while (static_cast(conflicting_point.x * temp_width) 37 | == static_cast(sample.x * temp_width) && 38 | static_cast(conflicting_point.y * temp_width) 39 | == static_cast(sample.y * temp_width)) { 40 | subdivisions++; 41 | temp_width <<= 1; 42 | } 43 | 44 | // Subdivide the grid and recalculate the grid index for our new point. 45 | SubdivideGrid(subdivisions); 46 | grid_idx = GetGridIndex(sample, width_); 47 | } 48 | points_.push_back(sample); 49 | sample_grid_[grid_idx] = &(points_.back()); 50 | } 51 | 52 | void SampleGrid2D::SubdivideGrid(const int subdivisions) { 53 | width_ <<= subdivisions; 54 | // We don't use resize because there's no reason to do the copy. Instead we 55 | // just allocate a new vector, which is fine because it's still a geometric 56 | // growth. 57 | sample_grid_ = std::vector(width_*width_, nullptr); 58 | const int num_points = points_.size(); 59 | // Add the points back into the grid. 60 | for (int i = 0; i < num_points; i++) { 61 | int grid_idx = GetGridIndex(points_[i], width_); 62 | sample_grid_[grid_idx] = &points_[i]; 63 | } 64 | } 65 | 66 | namespace { 67 | // Get the distance between two points, with wrap-around. 68 | inline double GetToroidalDistanceSq(const SampleGrid2D::Point2D& p0, 69 | const SampleGrid2D::Point2D& p1) { 70 | double x_diff = abs(p1.x-p0.x); 71 | if (x_diff > 0.5) x_diff = 1.0 - x_diff; 72 | double y_diff = abs(p1.y-p0.y); 73 | if (y_diff > 0.5) y_diff = 1.0 - y_diff; 74 | 75 | return (x_diff*x_diff)+(y_diff*y_diff); 76 | } 77 | // Wrap an integer around a fixed length (i.e. into the [0, limit) range). 78 | inline int WrapInt(const int index, 79 | const int limit) { 80 | if (index < 0) return index+limit; 81 | if (index >= limit) return index-limit; 82 | return index; 83 | } 84 | 85 | } // namespace 86 | 87 | double SampleGrid2D::GetMinDistSqFast(const Point2D sample, 88 | const double max_min_dist_sq, 89 | Point2D* nearest) const { 90 | double min_dist_sq = GetToroidalDistanceSq(sample, points_[0]); 91 | const Point2D* nearest_ptr = &points_[0]; 92 | 93 | const double cell_width = 1.0 / width_; 94 | 95 | // Each iteration of this loop checks the cells on the boundary of the 96 | // outer_width * outer_width grid. 97 | int outer_width = 1; 98 | int num_cells_to_check = 1; 99 | int starting_x_pos = sample.x * width_; 100 | int starting_y_pos = sample.y * width_; 101 | while (outer_width <= (width_ + 1)) { 102 | // This next loop starts at the starting_x_pos and starting_y_pos, and 103 | // iterates over the boundary cells, checking the distances of any point 104 | // in the cells. It starts out going from left to right, then turns 90 105 | // degrees once it reaches the corner, and so on until it gets back to the 106 | // original cell. 107 | int x_pos = starting_x_pos, y_pos = starting_y_pos; 108 | int x_dir = 1, y_dir = 0; 109 | for (int i = 0; i < num_cells_to_check; i++) { 110 | const int lookup_x_pos = WrapInt(x_pos, width_); 111 | const int lookup_y_pos = WrapInt(y_pos, width_); 112 | const int grid_idx = lookup_y_pos * width_ + lookup_x_pos; 113 | Point2D* point = sample_grid_[grid_idx]; 114 | if (point != nullptr) { 115 | double dist_sq = GetToroidalDistanceSq(sample, *point); 116 | if (dist_sq < min_dist_sq) { 117 | min_dist_sq = dist_sq; 118 | nearest_ptr = point; 119 | if (min_dist_sq < max_min_dist_sq) break; 120 | } 121 | } 122 | 123 | // Turn 90 degrees. 124 | if (i > 0 && (i % (outer_width-1) == 0)) { 125 | y_dir = -y_dir; 126 | std::swap(x_dir, y_dir); 127 | } 128 | x_pos += x_dir; 129 | y_pos += y_dir; 130 | } 131 | 132 | num_cells_to_check = 2*(2*outer_width+2); // Outer square - inner square. 133 | outer_width += 2; 134 | starting_x_pos--; 135 | starting_y_pos--; 136 | 137 | double min_grid_dist = std::max((outer_width / 2) - 1, 0) * cell_width; 138 | if (min_dist_sq < (min_grid_dist*min_grid_dist) || 139 | min_dist_sq < max_min_dist_sq) { 140 | break; 141 | } 142 | } 143 | 144 | if (nearest != nullptr) *nearest = *nearest_ptr; 145 | return min_dist_sq; 146 | } 147 | 148 | int SampleGrid2D::GetBestCandidate( 149 | const std::vector& candidates) const { 150 | int best_candidate = 0; 151 | Point2D prev_nearest; 152 | double max_min_dist_sq = GetMinDistSqFast(candidates[0], 0.0, &prev_nearest); 153 | const int n_candidates = candidates.size(); 154 | for (int i = 1; i < n_candidates; i++) { 155 | if (GetToroidalDistanceSq(candidates[i], prev_nearest) < max_min_dist_sq) 156 | continue; 157 | 158 | double min_dist_sq = 159 | GetMinDistSqFast(candidates[i], max_min_dist_sq, &prev_nearest); 160 | if (min_dist_sq > max_min_dist_sq) { 161 | max_min_dist_sq = min_dist_sq; 162 | best_candidate = i; 163 | } 164 | } 165 | return best_candidate; 166 | } 167 | 168 | } // namespace sampling 169 | -------------------------------------------------------------------------------- /sampling/bn_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * This headers defines utilities for performing best-candidate sampling in 6 | * to improve minimum distances between points. 7 | */ 8 | #ifndef SAMPLING_BN_UTILS_H 9 | #define SAMPLING_BN_UTILS_H 10 | 11 | #include 12 | #include 13 | 14 | namespace sampling { 15 | 16 | static constexpr int kBestCandidateSamples = 100; 17 | 18 | /* 19 | * SampleGrid2D is a simple utility class to keep track of points in a 2D 20 | * uniform grid, and query minimum distances within that grid. When points are 21 | * added to the grid, if there is another point in the same grid cell, the grid 22 | * is subdivided until there are an equal number of points in each cell. 23 | * 24 | * We use this rather than a tree-structure like a kd-tree or quadtree, because 25 | * the sample sequences we're working on all have 2D stratification guarantees, 26 | * so the sample grid will never get too much bigger than the number of samples 27 | * (although it's not great with a higher-base sequence, like the SFaure011). 28 | */ 29 | class SampleGrid2D { 30 | public: 31 | SampleGrid2D() : sample_grid_(1, nullptr) {} 32 | 33 | struct Point2D { 34 | double x; 35 | double y; 36 | }; 37 | 38 | // Add a new sample point into the grid. This may automatically subdivide the 39 | // grid, if the new point is in the same cell as a previous point. 40 | void AddSample(const Point2D sample); 41 | 42 | // From a list of candidates, return the index of the candidate with the 43 | // furthest minimum toroidal distance from points in the grid. 44 | int GetBestCandidate(const std::vector& candidates) const; 45 | 46 | private: 47 | double GetMinDistSqFast(const Point2D sample, 48 | const double max_min_dist_sq, 49 | Point2D* nearest) const; 50 | void SubdivideGrid(const int subdivisions); 51 | 52 | // The actual grid is just stored as a vector of pointers. 53 | std::vector sample_grid_; 54 | // We keep track of all our points individually. We use a deque to make sure 55 | // that pointers are persistent. A memory arena would probably be better, but 56 | // this is simple. 57 | std::deque points_; 58 | int width_ = 1; 59 | }; 60 | 61 | } // namespace sampling 62 | 63 | #endif // SAMPLING_BN_UTILS_H 64 | -------------------------------------------------------------------------------- /sampling/rng.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under MIT Open-Source License: see LICENSE. 3 | * Note from Andrew Helmer: virtually everything in here was taken from another 4 | * source, either a paper or something with a permissive license, so I do not 5 | * claim copyright here. 6 | * 7 | * Implementations of pseudorandom number generations and hash functions. 8 | * These are implemented in the header file, because it makes the sampling code 9 | * much faster. 10 | */ 11 | #ifndef SAMPLING_RNG_H 12 | #define SAMPLING_RNG_H 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace sampling { 19 | 20 | /* 21 | * C++ standard random number generation. Can be used to seed the LCD and PCG 22 | * classes below. 23 | */ 24 | static uint64_t GetDeviceUniformInt() { 25 | thread_local static std::random_device r; 26 | thread_local static std::default_random_engine gen(r()); 27 | thread_local static std::uniform_int_distribution uniform; 28 | 29 | static const std::uniform_int_distribution::param_type param( 30 | 0, std::numeric_limits::max()); 31 | 32 | return uniform(gen, param); 33 | } 34 | 35 | 36 | #define USE_PCG32 1 37 | /* 38 | * Random number generator. Uses either PCG32 (Permuted Congruential Generator) 39 | * or a linear congruential generator, equivalent to drand48(), depending on 40 | * whether USE_PCG32 is set to 1. 41 | */ 42 | class RNG { 43 | #if USE_PCG32 44 | // PCG implementation based on the supplemental code provided in 45 | // * "Practical Hash-based Owen Scrambling" (Burley 2020), which is in turn 46 | // from pcg-random.org. 47 | public: 48 | RNG() { 49 | pcg32_srandom_r(GetDeviceUniformInt(), 0); 50 | } 51 | explicit RNG(uint64_t seed) { 52 | pcg32_srandom_r(seed, 0); 53 | } 54 | uint32_t GetUniformInt() { 55 | uint64_t oldstate = state; 56 | // Advance internal state 57 | state = oldstate * 6364136223846793005ULL + (inc|1); 58 | // Calculate output function (XSH RR), uses old state for max ILP 59 | uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; 60 | uint32_t rot = oldstate >> 59u; 61 | return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); 62 | } 63 | double GetUniformFloat() { 64 | static constexpr double S = static_cast(1.0/(uint_fast64_t(0x100000000))); 65 | return GetUniformInt() * S; 66 | } 67 | 68 | private: 69 | void pcg32_srandom_r(uint64_t initstate, 70 | uint64_t initseq) { 71 | state = 0U; 72 | inc = (initseq << 1u) | 1u; 73 | GetUniformInt(); 74 | state += initstate; 75 | GetUniformInt(); 76 | } 77 | 78 | uint64_t state; 79 | uint64_t inc; 80 | #else 81 | // Linear congruential generator provided by Andrew Kensler. Should give 82 | // results equivalent to drand48(), but it's faster to have it in a header file. 83 | public: 84 | RNG() {drand48_rng = GetDeviceUniformInt();} 85 | explicit RNG(uint64_t seed) : drand48_rng(seed) {} 86 | uint64_t GetUniformInt() { 87 | drand48_rng = 0x5deece66d * drand48_rng + 0xb; 88 | return drand48_rng >> 17 & 0x7fffffff; 89 | } 90 | double GetUniformFloat() { 91 | return static_cast(GetUniformInt()) / 2147483648.0; 92 | } 93 | 94 | private: 95 | uint64_t drand48_rng; 96 | #endif 97 | public: 98 | // Get an integer in the range [min, max), without bias. 99 | uint32_t GetUniformInt(uint32_t min, uint32_t max) { 100 | static constexpr uint32_t int_max = std::numeric_limits::max(); 101 | const uint32_t range = max-min; 102 | uint32_t base_int; 103 | // Reject a uniform int that's too high, otherwise the values are biased. 104 | const int max_value = int_max - (int_max % range); 105 | do { 106 | base_int = this->GetUniformInt(); 107 | } while (base_int > max_value); 108 | return (base_int % range) + min; 109 | } 110 | }; 111 | 112 | /* 113 | * permute() from Correlated Multi-Jittered Sampling (Kensler 2013). 114 | * For a given seed, 115 | * this will map every index to a different, unique, permuted index in [0, len]. 116 | */ 117 | inline uint32_t Permute(uint32_t idx, uint32_t len, uint32_t seed) { 118 | uint32_t mask = len-1; 119 | mask |= mask >> 1; 120 | mask |= mask >> 2; 121 | mask |= mask >> 4; 122 | mask |= mask >> 8; 123 | mask |= mask >> 16; 124 | 125 | do { 126 | idx ^= seed; idx *= 0xe170893d; 127 | idx ^= seed >> 16; 128 | idx ^= (idx & mask) >> 4; 129 | idx ^= seed >> 8; idx *= 0x0929eb3f; 130 | idx ^= seed >> 23; 131 | idx ^= (idx & mask) >> 1; idx *= 1 | seed >> 27; 132 | idx *= 0x6935fa69; 133 | idx ^= (idx & mask) >> 11; idx *= 0x74dcb303; 134 | idx ^= (idx & mask) >> 2; idx *= 0x9e501cc3; 135 | idx ^= (idx & mask) >> 2; idx *= 0xc860a3df; 136 | idx &= mask; 137 | idx ^= idx >> 5; 138 | } while (idx >= len); 139 | return (idx + seed) % len; 140 | } 141 | 142 | /* 143 | * Hash functions from supplemental code of 144 | * "Practical Hash-based Owen Scrambling" (Burley 2020). 145 | */ 146 | inline uint32_t CombineHashes(uint32_t seed, uint32_t v) { 147 | return seed ^ (v + (seed << 6) + (seed >> 2)); 148 | } 149 | 150 | inline uint32_t Hash(uint32_t x) { 151 | // finalizer from murmurhash3 152 | x ^= x >> 16; 153 | x *= 0x85ebca6bu; 154 | x ^= x >> 13; 155 | x *= 0xc2b2ae35u; 156 | x ^= x >> 16; 157 | return x; 158 | } 159 | 160 | } // namespace sampling 161 | 162 | #endif // SAMPLING_RNG_H 163 | -------------------------------------------------------------------------------- /sampling/sfaure.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * Implementations of stochastic/scrambled Faure (0,s)-sequences. 6 | */ 7 | #include "sfaure.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "bn_utils.h" 15 | #include "rng.h" 16 | #include "shuffling.h" 17 | #include "utils.h" 18 | #include "xor_values.h" 19 | 20 | namespace sampling { 21 | namespace { 22 | 23 | using std::vector; 24 | 25 | // Computes the strata offsets from a previous pass to this pass. This is used 26 | // for correlated swapping. 27 | template 28 | vector GetStrataOffsets(const int pass, const int nd, 29 | const uint32_t baseSeed) { 30 | vector strata_offsets(nd); 31 | for (int d = 0; d < nd; d++) { 32 | const uint32_t seed = CombineHashes(baseSeed, Hash(d)); 33 | int strataOffset = (Permute(pass, b - 1, seed) + 1); 34 | // We actually want the offsets from a previous pass, not the previous 35 | // power, so we subtract the offset from the previous pass. 36 | if (pass > 0) strataOffset -= (Permute(pass - 1, b - 1, seed) + 1); 37 | strata_offsets[d] = (b + strataOffset) % b; 38 | } 39 | return strata_offsets; 40 | } 41 | 42 | // For a given sample, calculates the strata that sample should be placed in, 43 | // one for each dimension, using correlated swapping. 44 | template 45 | inline void GetFaureStrataCS(const int prev_pass_idx, 46 | const int nd, 47 | const int log_n, 48 | const int num_strata, 49 | const vector& strata_offsets, 50 | const double* samples, 51 | vector* strata) { 52 | for (int d = 0; d < nd; d++) { 53 | const int prev_idx = 54 | CarrylessAdd(prev_pass_idx, xor_vals[d][log_n]); 55 | const int prev_stratum = samples[prev_idx * nd + d] * num_strata; 56 | (*strata)[d] = AddLSDigit(prev_stratum, strata_offsets[d]); 57 | } 58 | } 59 | 60 | // Generates a sample point in a given set of 1D strata. 61 | inline void GetFaurePoint(const vector& strata, 62 | const int num_strata, 63 | const int nd, 64 | RNG* rng, 65 | double* sample) { 66 | for (int d = 0; d < nd; d++) { 67 | // Xor-values for the Halton sequence are always zero, so we just need to 68 | // subtract to get the index in previous pass. 69 | sample[d] = (rng->GetUniformFloat() + strata[d]) / num_strata; 70 | } 71 | } 72 | 73 | // Given a set of 1D strata, one in each dimension, generates a number of 74 | // candidate points in those strata, finds the candidate with the highest 75 | // minimum 2D distance using the sample_grid, and then writes that point into 76 | // the "sample" value. 77 | inline void GetBestFaurePoint(const vector& strata, 78 | const int num_strata, 79 | const SampleGrid2D& sample_grid, 80 | const int n_candidates, 81 | const int nd, 82 | RNG* rng, 83 | double* sample) { 84 | // Generate a single n-dimensional point in the output. 85 | GetFaurePoint(strata, num_strata, nd, rng, sample); 86 | 87 | if (n_candidates > 1) { 88 | // We actually only generate 2D candidates. 89 | std::vector candidates(n_candidates); 90 | candidates[0] = {sample[0], sample[1]}; 91 | for (int cand_idx = 1; cand_idx < n_candidates; cand_idx++) { 92 | double candidate[2]; 93 | GetFaurePoint(strata, num_strata, /*nd=*/2, rng, candidate); 94 | candidates[cand_idx] = {candidate[0], candidate[1]}; 95 | } 96 | int best_candidate = sample_grid.GetBestCandidate(candidates); 97 | // Write the best two dimensions into the output sample. 98 | sample[0] = candidates[best_candidate].x; 99 | sample[1] = candidates[best_candidate].y; 100 | } 101 | } 102 | 103 | // Generalized implementation of stochastically generated Faure sequences, 104 | // with correlated swapping. 105 | template 106 | void GetSFaureCSSamples(const int num_samples, 107 | const int nd, 108 | const int n_candidates, 109 | double* samples) { 110 | ASSERT(nd <= b, "Only " << b << " dimensions allowed."); 111 | RNG rng; 112 | for (int d = 0; d < nd; d++) samples[d] = rng.GetUniformFloat(); 113 | 114 | SampleGrid2D sample_grid; 115 | if (n_candidates > 1) sample_grid.AddSample({samples[0], samples[1]}); 116 | 117 | int next_sample = 1; 118 | int num_1d_strata = 1; 119 | 120 | vector strata(nd); 121 | for (int log_n = 0; next_sample < num_samples; log_n++) { 122 | num_1d_strata *= b; 123 | 124 | const uint32_t strata_offset_seed = rng.GetUniformInt(); 125 | const int prevLen = next_sample; 126 | for (int pass = 0; pass < b - 1; pass++) { 127 | const vector strata_offsets = 128 | GetStrataOffsets(pass, nd, strata_offset_seed); 129 | for (int i = 0; i < prevLen; i++) { 130 | const int prev_pass_idx = i + (prevLen * pass); 131 | 132 | GetFaureStrataCS(prev_pass_idx, nd, log_n, 133 | num_1d_strata, strata_offsets, 134 | samples, &strata); 135 | double* sample = &(samples[next_sample * nd]); 136 | GetBestFaurePoint( 137 | strata, num_1d_strata, sample_grid, n_candidates, nd, &rng, sample); 138 | 139 | if (n_candidates > 1) sample_grid.AddSample({sample[0], sample[1]}); 140 | 141 | next_sample++; 142 | if (next_sample >= num_samples) break; 143 | } 144 | if (next_sample >= num_samples) break; 145 | } 146 | } 147 | } 148 | 149 | // The Owen-scrambled version of the stochastic Faure sequence is the same 150 | // except that we use independently chosen strata offsets for each new sample. 151 | template 152 | int GetStrataOffset(const uint32_t base_seed, const int sample_interval, 153 | const int pass) { 154 | uint32_t seed = CombineHashes(base_seed, Hash(sample_interval)); 155 | int strata_offset = (Permute(pass, b - 1, seed) + 1); 156 | if (pass > 0) strata_offset -= (Permute(pass - 1, b - 1, seed) + 1); 157 | return (b + strata_offset) % b; 158 | } 159 | 160 | template 161 | inline void GetFaureStrataOwen(const int prev_pass_idx, 162 | const int nd, 163 | const int log_n, 164 | const int pass, 165 | const int num_strata, 166 | const vector& strata_offset_seeds, 167 | const double* samples, 168 | vector* strata) { 169 | for (int d = 0; d < nd; d++) { 170 | const int prev_idx = 171 | CarrylessAdd(prev_pass_idx, xor_vals[d][log_n]); 172 | const int prev_stratum = samples[prev_idx * nd + d] * num_strata; 173 | 174 | // Get the strata offset for this interval and pass. 175 | const int strata_interval = prev_stratum / b; 176 | const int strata_offset = 177 | GetStrataOffset(strata_offset_seeds[d], strata_interval, pass); 178 | 179 | (*strata)[d] = AddLSDigit(prev_stratum, strata_offset); 180 | } 181 | } 182 | 183 | template 184 | void GetSFaureOwenSamples(int num_samples, int nd, int n_candidates, 185 | double* samples) { 186 | ASSERT(nd <= b, "Only " << b << " dimensions allowed."); 187 | RNG rng; 188 | for (int d = 0; d < nd; d++) samples[d] = rng.GetUniformFloat(); 189 | 190 | SampleGrid2D sample_grid; 191 | if (n_candidates > 1) sample_grid.AddSample({samples[0], samples[1]}); 192 | 193 | int next_sample = 1; 194 | int num_1d_strata = 1; 195 | 196 | vector strata_offset_seeds(nd); 197 | vector strata(nd); 198 | for (int log_n = 0; next_sample < num_samples; log_n++) { 199 | num_1d_strata *= b; 200 | 201 | // Get the base seeds for each dimension. 202 | for (int d = 0; d < nd; d++) strata_offset_seeds[d] = rng.GetUniformInt(); 203 | 204 | const int prevLen = next_sample; 205 | for (int pass = 0; pass < b - 1; pass++) { 206 | for (int i = 0; i < prevLen; i++) { 207 | const int prev_pass_idx = i + (prevLen * pass); 208 | 209 | GetFaureStrataOwen( 210 | prev_pass_idx, nd, log_n, pass, num_1d_strata, strata_offset_seeds, 211 | samples, &strata); 212 | 213 | double* sample = &(samples[next_sample * nd]); 214 | GetBestFaurePoint( 215 | strata, num_1d_strata, sample_grid, n_candidates, nd, &rng, sample); 216 | 217 | if (n_candidates > 1) sample_grid.AddSample({sample[0], sample[1]}); 218 | 219 | next_sample++; 220 | if (next_sample >= num_samples) break; 221 | } 222 | if (next_sample >= num_samples) break; 223 | } 224 | } 225 | } 226 | 227 | template 228 | void GetSFaureSamples(int num_samples, int nd, bool shuffle, 229 | int candidates, bool owen, double* samples) { 230 | if (!owen) { 231 | GetSFaureCSSamples( 232 | num_samples, nd, candidates, samples); 233 | } else { 234 | GetSFaureOwenSamples( 235 | num_samples, nd, candidates, samples); 236 | } 237 | 238 | if (shuffle) ProgressiveShuffleSamples(num_samples, nd, samples); 239 | } 240 | } // namespace 241 | 242 | /* 243 | * These functions are basically just wrappers around SFaureSamples<>. 244 | */ 245 | void GetStochasticFaure03Samples(int num_samples, int nd, bool shuffle, 246 | int candidates, bool owen, double* samples) { 247 | GetSFaureSamples<3, FAURE03_XOR_SIZE, faure03_xors>( 248 | num_samples, nd, shuffle, candidates, owen, samples); 249 | } 250 | 251 | void GetStochasticFaure05Samples(int num_samples, int nd, bool shuffle, 252 | int candidates, bool owen, double* samples) { 253 | GetSFaureSamples<5, FAURE05_XOR_SIZE, faure05_xors>( 254 | num_samples, nd, shuffle, candidates, owen, samples); 255 | } 256 | 257 | void GetStochasticFaure07Samples(int num_samples, int nd, bool shuffle, 258 | int candidates, bool owen, double* samples) { 259 | GetSFaureSamples<7, FAURE07_XOR_SIZE, faure07_xors>( 260 | num_samples, nd, shuffle, candidates, owen, samples); 261 | } 262 | 263 | void GetStochasticFaure011Samples(int num_samples, int nd, bool shuffle, 264 | int candidates, bool owen, double* samples) { 265 | GetSFaureSamples<11, FAURE011_XOR_SIZE, faure011_xors>( 266 | num_samples, nd, shuffle, candidates, owen, samples); 267 | } 268 | } // namespace sampling 269 | -------------------------------------------------------------------------------- /sampling/sfaure.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * Defines functions to generate stochastic/scrambled Faure (0,s)-sequences. 6 | * Can generate (0,3), (0,5), (0,7), and (0,11) sequences, with or without 7 | * decorrelation (shuffling), with or without best-candidate samping for the 8 | * first two dimensions. 9 | */ 10 | #ifndef SAMPLING_SFAURE_H 11 | #define SAMPLING_SFAURE_H 12 | 13 | namespace sampling { 14 | 15 | // Fills the nd-dimensional sample array with a stochastically generated 16 | // Faure sequences. These are all (0,s)-sequences, and the chosen dimensionality 17 | // must be <= to the base, which is value after the zero. I.e. the (0,5) 18 | // can only generate up to 5 dimensions. 19 | // 20 | // If owen=false, then these sequences use Correlated Shuffling from the paper, 21 | // otherwise they use "un"correlated shuffling, i.e. the sequences are 22 | // equivalent to fully Owen-scrambled sequences. We haven't observed a 23 | // noticeable difference in the quality of either sequence. 24 | void GetStochasticFaure03Samples(const int num_samples, 25 | const int nd, 26 | const bool shuffle, 27 | const int candidates, 28 | const bool owen, 29 | double *samples); 30 | void GetStochasticFaure05Samples(const int num_samples, 31 | const int nd, 32 | const bool shuffle, 33 | const int candidates, 34 | const bool owen, 35 | double *samples); 36 | void GetStochasticFaure07Samples(const int num_samples, 37 | const int nd, 38 | const bool shuffle, 39 | const int candidates, 40 | const bool owen, 41 | double *samples); 42 | void GetStochasticFaure011Samples(const int num_samples, 43 | const int nd, 44 | const bool shuffle, 45 | const int candidates, 46 | const bool owen, 47 | double *samples); 48 | 49 | } // namespace sampling 50 | 51 | #endif // SAMPLING_SFAURE_H 52 | -------------------------------------------------------------------------------- /sampling/shalton.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * Implementations of stochastic/scrambled Halton sequences. 6 | */ 7 | #include "shalton.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "bn_utils.h" 15 | #include "rng.h" 16 | #include "utils.h" 17 | 18 | namespace sampling { 19 | 20 | using std::vector; 21 | 22 | namespace { 23 | /* 24 | * We statically define the prime numbers and the functions to add least 25 | * significant digits in those bases, which make them faster. 26 | */ 27 | static constexpr int MAX_HALTON_DIM = 10; 28 | static constexpr uint32_t primes[MAX_HALTON_DIM] = { 29 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 30 | }; 31 | typedef unsigned (*add_ls_fn) (unsigned, unsigned); 32 | static constexpr add_ls_fn add_ls_digit_fns[MAX_HALTON_DIM] = { 33 | &AddLSDigit, 34 | &AddLSDigit, 35 | &AddLSDigit, 36 | &AddLSDigit, 37 | &AddLSDigit, 38 | &AddLSDigit, 39 | &AddLSDigit, 40 | &AddLSDigit, 41 | &AddLSDigit, 42 | &AddLSDigit, 43 | }; 44 | 45 | // Computes a stratum offset from a previous pass to this pass. This is used 46 | // for correlated swapping. 47 | int GetStratumOffset(const int pass, 48 | const int b, 49 | const uint32_t seed) { 50 | int stratum_offset = (Permute(pass, b - 1, seed) + 1); 51 | // We actually want the offsets from a previous pass, not the previous 52 | // power, so we subtract the offset from the previous pass. 53 | if (pass > 0) stratum_offset -= (Permute(pass - 1, b - 1, seed) + 1); 54 | return (b + stratum_offset) % b; 55 | } 56 | 57 | // With correlated swapping, we calculate all the 1D strata for a new point. 58 | inline void GetHaltonStrataCS(const int i, 59 | const int nd, 60 | const vector& num_strata, 61 | const vector& strata_offsets, 62 | const double* samples, 63 | vector* strata) { 64 | for (int d = 0; d < nd; d++) { 65 | const int base = primes[d]; 66 | // The beautiful thing about the Halton sequence is that, although all the 67 | // dimensions are in different bases, the "xor_values" are all zero. This 68 | // means we can find the previous sample index using only a subtraction. 69 | const int prev_idx = i - num_strata[d]/base; 70 | const int prev_stratum = samples[prev_idx * nd + d] * num_strata[d]; 71 | (*strata)[d] = add_ls_digit_fns[d](prev_stratum, strata_offsets[d]); 72 | } 73 | } 74 | 75 | // Given a set of strata, one for each dimension, generate a new point. 76 | inline void GetHaltonPoint(const vector& strata, 77 | const vector& num_strata, 78 | const int nd, 79 | RNG* rng, 80 | double* sample) { 81 | for (int d = 0; d < nd; d++) { 82 | // Xor-values for the Halton sequence are always zero, so we just need to 83 | // subtract to get the index in previous pass. 84 | sample[d] = (rng->GetUniformFloat() + strata[d]) / num_strata[d]; 85 | } 86 | } 87 | 88 | // Given a set of 1D strata, one in each dimension, generates a number of 89 | // candidate points in those strata, finds the candidate with the highest 90 | // minimum 2D distance using the sample_grid, and then writes that point into 91 | // the "sample" value. 92 | inline void GetBestHaltonPoint(const vector& strata, 93 | const vector& num_strata, 94 | const SampleGrid2D& sample_grid, 95 | const int n_candidates, 96 | const int nd, 97 | RNG* rng, 98 | double* sample) { 99 | // Generate a single n-dimensional point in the output. 100 | GetHaltonPoint(strata, num_strata, nd, rng, sample); 101 | 102 | if (n_candidates > 1) { 103 | // We actually only generate 2D candidates. 104 | std::vector candidates(n_candidates); 105 | candidates[0] = {sample[0], sample[1]}; 106 | for (int cand_idx = 1; cand_idx < n_candidates; cand_idx++) { 107 | double candidate[2]; 108 | GetHaltonPoint(strata, num_strata, /*nd=*/2, rng, candidate); 109 | candidates[cand_idx] = {candidate[0], candidate[1]}; 110 | } 111 | int best_candidate = sample_grid.GetBestCandidate(candidates); 112 | // Write the best two dimensions into the output sample. 113 | sample[0] = candidates[best_candidate].x; 114 | sample[1] = candidates[best_candidate].y; 115 | } 116 | } 117 | 118 | void GetStochasticHaltonCSSamples(const int num_samples, 119 | const int nd, 120 | const int n_candidates, 121 | double *samples) { 122 | ASSERT(nd <= MAX_HALTON_DIM, 123 | "Only " << MAX_HALTON_DIM << " dimensions allowed."); 124 | RNG rng; 125 | for (int d = 0; d < nd; d++) 126 | samples[d] = rng.GetUniformFloat(); 127 | 128 | SampleGrid2D sample_grid; 129 | if (n_candidates > 1) sample_grid.AddSample({samples[0], samples[1]}); 130 | 131 | // Because the Halton sequence has a different base for each dimension, we 132 | // need to keep track of a bunch of things separately for each dimension. 133 | vector num_strata(nd, 1); 134 | vector cur_pass(nd, 0); 135 | vector strata_hash_seeds(nd); 136 | vector strata_offsets(nd); 137 | vector strata(nd); 138 | for (int i = 1; i < num_samples; i++) { 139 | for (int d = 0; d < nd; d++) { 140 | const int base = primes[d]; 141 | if (i >= num_strata[d]) { 142 | // If all of the 1D strata are occupied, we subdivide the 1D strata, 143 | // reset the pass to zero. 144 | num_strata[d] *= base; 145 | cur_pass[d] = 0; 146 | strata_hash_seeds[d] = rng.GetUniformInt(); 147 | strata_offsets[d] = 148 | GetStratumOffset(/*pass=*/0, base, strata_hash_seeds[d]); 149 | } else if (i*base >= num_strata[d]*(cur_pass[d]+2)) { 150 | // Check to see if we've reached the next "pass" for this dimension. 151 | cur_pass[d] += 1; 152 | strata_offsets[d] = 153 | GetStratumOffset(cur_pass[d], base, strata_hash_seeds[d]); 154 | } 155 | } 156 | 157 | GetHaltonStrataCS( 158 | i, nd, num_strata, strata_offsets, samples, &strata); 159 | 160 | double* sample = &(samples[i * nd]); 161 | GetBestHaltonPoint(strata, num_strata, sample_grid, n_candidates, nd, &rng, 162 | sample); 163 | if (n_candidates > 1) sample_grid.AddSample({sample[0], sample[1]}); 164 | } 165 | } 166 | 167 | int GetStratumOffset(const int pass, 168 | const int sample_interval, 169 | const int b, 170 | const uint32_t base_seed) { 171 | uint32_t seed = CombineHashes(base_seed, Hash(sample_interval)); 172 | return GetStratumOffset(pass, b, seed); 173 | } 174 | 175 | inline void GetHaltonStrataOwen(const int i, 176 | const int nd, 177 | const vector& num_strata, 178 | const vector& cur_pass, 179 | const vector& strata_hash_seeds, 180 | const double* samples, 181 | vector* strata) { 182 | for (int d = 0; d < nd; d++) { 183 | const int base = primes[d]; 184 | // The beautiful thing about the Halton sequence is that, although all the 185 | // dimensions are in different bases, the "xor_values" are all zero. This 186 | // means we can find the previous sample index using only a subtraction. 187 | const int prev_idx = i - num_strata[d] / base; 188 | const int prev_stratum = samples[prev_idx * nd + d] * num_strata[d]; 189 | const int interval = prev_stratum / base; 190 | // We independently calculate the strata offsets/deltas for each sample. 191 | const int stratum_offset = 192 | GetStratumOffset(cur_pass[d], interval, base, strata_hash_seeds[d]); 193 | (*strata)[d] = add_ls_digit_fns[d](prev_stratum, stratum_offset); 194 | } 195 | } 196 | 197 | void GetStochasticHaltonOwenSamples(const int num_samples, 198 | const int nd, 199 | const int n_candidates, 200 | double *samples) { 201 | ASSERT(nd <= MAX_HALTON_DIM, 202 | "Only " << MAX_HALTON_DIM << " dimensions allowed."); 203 | RNG rng; 204 | for (int d = 0; d < nd; d++) 205 | samples[d] = rng.GetUniformFloat(); 206 | 207 | SampleGrid2D sample_grid; 208 | if (n_candidates > 1) sample_grid.AddSample({samples[0], samples[1]}); 209 | 210 | // Because the Halton sequence has a different base for each dimension, we 211 | // need to keep track of a bunch of things separately for each dimension. 212 | vector num_strata(nd, 1); 213 | vector cur_pass(nd, 0); 214 | vector strata_hash_seeds(nd); 215 | vector strata(nd); 216 | for (int i = 1; i < num_samples; i++) { 217 | for (int d = 0; d < nd; d++) { 218 | const int base = primes[d]; 219 | if (i >= num_strata[d]) { 220 | // If all of the 1D strata are occupied, we subdivide the 1D strata, 221 | // reset the pass to zero. 222 | num_strata[d] *= base; 223 | cur_pass[d] = 0; 224 | strata_hash_seeds[d] = rng.GetUniformInt(); 225 | } else if (i*base >= num_strata[d]*(cur_pass[d]+2)) { 226 | // Check to see if we've reached the next "pass" for this dimension. 227 | cur_pass[d] += 1; 228 | } 229 | } 230 | 231 | GetHaltonStrataOwen( 232 | i, nd, num_strata, cur_pass, strata_hash_seeds, samples, &strata); 233 | 234 | double* sample = &(samples[i * nd]); 235 | GetBestHaltonPoint(strata, num_strata, sample_grid, n_candidates, nd, &rng, 236 | sample); 237 | if (n_candidates > 1) sample_grid.AddSample({sample[0], sample[1]}); 238 | } 239 | } 240 | } // namespace 241 | 242 | void GetStochasticHaltonSamples(const int num_samples, 243 | const int nd, 244 | const bool shuffle, 245 | const int candidates, 246 | const bool owen, 247 | double *samples) { 248 | ASSERT(nd <= MAX_HALTON_DIM, 249 | "Only " << MAX_HALTON_DIM << " dimensions allowed."); 250 | ASSERT(!shuffle, "Cannot effectively shuffle Halton sequences."); 251 | if (owen) { 252 | GetStochasticHaltonOwenSamples(num_samples, nd, candidates, samples); 253 | } else { 254 | GetStochasticHaltonCSSamples(num_samples, nd, candidates, samples); 255 | } 256 | } 257 | } // namespace sampling 258 | -------------------------------------------------------------------------------- /sampling/shalton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * Defines a function to generate stochastic/scrambled Halton sequences. 6 | */ 7 | #ifndef SAMPLING_SHALTON_H 8 | #define SAMPLING_SHALTON_H 9 | 10 | #include 11 | 12 | namespace sampling { 13 | 14 | // Fills the nd-dimensional sample array with a stochastically generated 15 | // Halton sequence. 16 | void GetStochasticHaltonSamples(const int num_samples, 17 | const int nd, 18 | const bool shuffle, 19 | const int candidates, 20 | const bool owen, 21 | double *samples); 22 | 23 | } // namespace sampling 24 | 25 | #endif // SAMPLING_SHALTON_H 26 | -------------------------------------------------------------------------------- /sampling/shuffling.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * This file just implements a specialized version of progressive shuffling for 6 | * base 2, which will be much faster and cleaner. 7 | */ 8 | #include "shuffling.h" 9 | 10 | #include 11 | 12 | #include "rng.h" 13 | #include "utils.h" 14 | 15 | namespace sampling { 16 | 17 | template <> 18 | std::vector GetShuffledIndices<2>(const int length) { 19 | std::vector randomized_indices(length); 20 | std::vector bit_reversed_indices(length); 21 | RNG rng; 22 | bit_reversed_indices[0] = 0; 23 | randomized_indices[0] = rng.GetUniformInt(0, length); 24 | int interval_width = length / 2; 25 | for (int prev_len = 1; prev_len < length; prev_len *= 2) { 26 | for (int i = 0; i < prev_len && (prev_len+i) < length; i++) { 27 | bit_reversed_indices[i+prev_len] = 28 | (bit_reversed_indices[i] ^ interval_width); 29 | randomized_indices[i+prev_len] = 30 | (randomized_indices[i] ^ interval_width) 31 | ^ (rng.GetUniformInt(0, interval_width)); 32 | } 33 | interval_width /= 2; // Or interval_width >>= 1, if you prefer. 34 | } 35 | 36 | // We reindex the array by itself, using the randomized indices. 37 | for (int i = 0; i < length; i++) { 38 | bit_reversed_indices[i] = randomized_indices[bit_reversed_indices[i]]; 39 | } 40 | return bit_reversed_indices; 41 | } 42 | 43 | } // namespace sampling 44 | -------------------------------------------------------------------------------- /sampling/shuffling.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * This header defines and implements various functions to do hierarchical 6 | * shuffling of arrays, basically Owen-scrambling on the array indices. 7 | */ 8 | #ifndef SAMPLING_SHUFFLING_H 9 | #define SAMPLING_SHUFFLING_H 10 | 11 | #include 12 | 13 | #include "utils.h" 14 | #include "rng.h" 15 | 16 | namespace sampling { 17 | /* 18 | * GetShuffledIndices is the core of the shuffling functionality. 19 | * Starting from an array of indices, e.g. {0, 1, 2, 3, ...}, it performs an 20 | * Owen-scrambling in the given base on those indices. 21 | * 22 | * See https://andrew-helmer.github.io/tree-shuffling/ for an explanation of 23 | * how it works in base-2. Fundamentally it calculates both an Owen-scrambled 24 | * van der Corput sequence, using the Stochastic Generation approach, and then 25 | * "reverses" the scrambling using an unscrambled van der Corput sequence. 26 | */ 27 | template 28 | int GetIntervalOffset(const uint32_t base_seed, const int interval, 29 | const int pass) { 30 | uint32_t seed = CombineHashes(base_seed, Hash(interval)); 31 | int strata_offset = (Permute(pass, b - 1, seed) + 1); 32 | if (pass > 0) strata_offset -= (Permute(pass - 1, b - 1, seed) + 1); 33 | return (b + strata_offset) % b; 34 | } 35 | 36 | template 37 | std::vector GetShuffledIndices(const int length) { 38 | std::vector randomized_indices(length); 39 | std::vector digit_reversed_indices(length); 40 | RNG rng; 41 | 42 | digit_reversed_indices[0] = 0; 43 | randomized_indices[0] = rng.GetUniformInt(0, length); 44 | int interval_width = length / base; 45 | for (int prev_len = 1; prev_len < length; prev_len *= base) { 46 | ASSERT(prev_len*base <= length, 47 | "The length of the array to be shuffled needs to be a power " 48 | "of the base."); 49 | 50 | uint32_t offset_rnd_seed = rng.GetUniformInt(); 51 | for (int pass = 0; pass < base-1; pass++) { 52 | for (int i = 0; i < prev_len; i++) { 53 | const int idx = prev_len*(pass+1) + i; 54 | const int prev_idx = idx - prev_len; 55 | int prev_interval = digit_reversed_indices[prev_idx] / interval_width; 56 | int next_interval = AddLSDigit(prev_interval, 1); 57 | digit_reversed_indices[idx] = interval_width*next_interval; 58 | 59 | prev_interval = randomized_indices[prev_idx] / interval_width; 60 | int higher_interval = prev_interval / base; 61 | int interval_offset = 62 | GetIntervalOffset(offset_rnd_seed, higher_interval, pass); 63 | next_interval = AddLSDigit(prev_interval, interval_offset); 64 | randomized_indices[idx] = 65 | rng.GetUniformInt(0, interval_width) + interval_width*next_interval; 66 | } 67 | } 68 | interval_width /= base; 69 | } 70 | 71 | // We reindex the array by itself, using the randomized indices. 72 | for (int i = 0; i < length; i++) { 73 | digit_reversed_indices[i] = randomized_indices[digit_reversed_indices[i]]; 74 | } 75 | return digit_reversed_indices; 76 | } 77 | 78 | // Declared here for a higher performance base-2 specialization in 79 | /// shuffling.cpp. 80 | template <> 81 | std::vector GetShuffledIndices<2>(const int length); 82 | 83 | // Implements Progressive Shuffling from the paper. It only shuffles points 84 | // that were generated in the same "pass" in their base. 85 | template 86 | std::vector GetProgressiveShuffledIndices(const int num_samples) { 87 | std::vector indices(num_samples); 88 | for (int i = 0; i < base; i++) indices[i] = i; 89 | for (int power = base; power < num_samples; power *= base) { 90 | for (int pass = 1; pass < base && (pass*power < num_samples); pass++) { 91 | ASSERT((pass+1)*power <= num_samples, 92 | "Progressive shuffling is only implemented for sequences " 93 | "that are a constant multiple of the base. Increase the sample " 94 | "count to " << ((pass+1)*power) << " samples."); 95 | std::vector shuffled_indices = GetShuffledIndices(power); 96 | for (int i = 0; i < power; i++) { 97 | indices[i + pass*power] = shuffled_indices[i] + pass*power; 98 | } 99 | } 100 | } 101 | return indices; 102 | } 103 | 104 | /* 105 | * This function will perform Progressive Shuffling on a set of N-dimensional 106 | * samples in a given base. 107 | */ 108 | template 109 | void ProgressiveShuffleSamples(const int num_samples, const int nd, 110 | double* samples) { 111 | // Copy samples into a temp vector. 112 | std::vector temp_samples(num_samples*nd); 113 | for (int i = 0; i < num_samples*nd; i++) temp_samples[i] = samples[i]; 114 | 115 | // Generates shuffled indices. 116 | std::vector shuffled_indices = 117 | GetProgressiveShuffledIndices(num_samples); 118 | 119 | // Writes out shuffled samples. 120 | for (int i = 0; i < num_samples; i++) { 121 | const int idx = shuffled_indices[i]; 122 | for (int d = 0; d < nd; d++) samples[idx*nd + d] = temp_samples[i*nd + d]; 123 | } 124 | } 125 | 126 | } // namespace sampling 127 | 128 | #endif // SAMPLING_SHUFFLING_H 129 | -------------------------------------------------------------------------------- /sampling/ssobol.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * This file implements Stochastic Sobol' and pmj02 sampling, along with 6 | * optional best-candidate sampling (for the first two dimensions). 7 | */ 8 | #include "ssobol.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "bn_utils.h" 16 | #include "rng.h" 17 | #include "shuffling.h" 18 | #include "utils.h" 19 | #include "xor_values.h" 20 | 21 | namespace sampling { 22 | namespace { 23 | inline void GetSobolPoint(const std::vector& strata, 24 | const double i_strata, 25 | const int nd, 26 | RNG* rng, 27 | double* sample) { 28 | for (int d = 0; d < nd; d++) { 29 | sample[d] = (rng->GetUniformFloat() + strata[d]) * i_strata; 30 | } 31 | } 32 | } // namespace 33 | 34 | void GetStochasticSobolSamples(const int num_samples, 35 | const int nd, 36 | const bool shuffle, 37 | const int n_candidates, 38 | const bool owen, 39 | double* samples) { 40 | ASSERT(nd <= MAX_SOBOL_DIM, "Stochastic Sobol' only works up to " 41 | << MAX_SOBOL_DIM << "dimensions."); 42 | ERRIF(owen, "--owen flag is meaningless for ssobol sequence."); 43 | 44 | RNG rng; 45 | // Generate first sample randomly. 46 | for (int d = 0; d < nd; d++) samples[d] = rng.GetUniformFloat(); 47 | 48 | SampleGrid2D sample_grid; 49 | if (n_candidates > 1) sample_grid.AddSample({samples[0], samples[1]}); 50 | // Used to temporarily store candidate positions for each new point. 51 | std::vector candidates(n_candidates); 52 | 53 | // Used to temporarily store the strata for each dimension, for a new point. 54 | std::vector strata(nd); 55 | for (int log_n = 0; (1 << log_n) < num_samples; log_n++) { 56 | int prev_len = 1 << log_n; 57 | int n_strata = prev_len * 2; 58 | double i_strata = 1.0 / n_strata; 59 | for (int i = 0; i < prev_len && (prev_len + i) < num_samples; i++) { 60 | for (int d = 0; d < nd; d++) { 61 | // It may be better if the sobol_xors were transposed for this, so that 62 | // the inner loop corresponded to contiguous array values. 63 | int prev_idx = i ^ sobol_xors[d][log_n]; 64 | int prev_stratum = samples[prev_idx * nd + d] * n_strata; 65 | strata[d] = prev_stratum^1; 66 | } 67 | 68 | double* sample = &(samples[(prev_len + i) * nd]); 69 | GetSobolPoint(strata, i_strata, nd, &rng, sample); 70 | 71 | // Do best-candidate sampling. 72 | if (n_candidates > 1) { 73 | candidates[0] = {sample[0], sample[1]}; 74 | for (int cand_idx = 1; cand_idx < n_candidates; cand_idx++) { 75 | double candidate[2]; 76 | GetSobolPoint(strata, i_strata, 2, &rng, candidate); 77 | candidates[cand_idx] = {candidate[0], candidate[1]}; 78 | } 79 | int best_candidate = sample_grid.GetBestCandidate(candidates); 80 | sample_grid.AddSample(candidates[best_candidate]); 81 | sample[0] = candidates[best_candidate].x; 82 | sample[1] = candidates[best_candidate].y; 83 | } 84 | } 85 | } 86 | 87 | if (shuffle) ProgressiveShuffleSamples<2>(num_samples, nd, samples); 88 | } 89 | 90 | namespace { 91 | inline void GetPMJ02Point(const int x_stratum, 92 | const int y_stratum, 93 | const double i_strata, 94 | RNG* rng, 95 | double* sample) { 96 | sample[0] = (rng->GetUniformFloat() + x_stratum) * i_strata; 97 | sample[1] = (rng->GetUniformFloat() + y_stratum) * i_strata; 98 | } 99 | } // namespace 100 | 101 | void GetPMJ02Samples(const int num_samples, 102 | const int nd, 103 | const bool shuffle, 104 | const int n_candidates, 105 | const bool owen, 106 | double* samples) { 107 | ASSERT(nd == 2, "PMJ02 only works for 2 dimensions. Use ssobol instead."); 108 | ERRIF(owen, "--owen flag is meaningless for pmj02 sequence."); 109 | 110 | RNG rng; 111 | // Generate first sample randomly. 112 | for (int d = 0; d < 2; d++) samples[d] = rng.GetUniformFloat(); 113 | 114 | SampleGrid2D sample_grid; 115 | if (n_candidates > 1) sample_grid.AddSample({samples[0], samples[1]}); 116 | // Used to temporarily store candidate positions for each new point. 117 | std::vector candidates(n_candidates); 118 | 119 | for (int log_n = 0; (1 << log_n) < num_samples; log_n++) { 120 | int prev_len = 1 << log_n; 121 | int n_strata = prev_len * 2; 122 | double i_strata = 1.0 / n_strata; 123 | for (int i = 0; i < prev_len && (prev_len + i) < num_samples; i++) { 124 | const int prev_x_idx = i ^ pmj02_xors[0][log_n]; 125 | const int prev_x_stratum = samples[prev_x_idx*2] * n_strata; 126 | const int x_stratum = prev_x_stratum^1; 127 | 128 | const int prev_y_idx = i ^ pmj02_xors[1][log_n]; 129 | const int prev_y_stratum = samples[prev_y_idx*2 + 1] * n_strata; 130 | const int y_stratum = prev_y_stratum^1; 131 | 132 | double* sample = &(samples[(prev_len + i) * 2]); 133 | GetPMJ02Point(x_stratum, y_stratum, i_strata, &rng, sample); 134 | 135 | // Do best-candidate sampling. 136 | if (n_candidates > 1) { 137 | candidates[0] = {sample[0], sample[1]}; 138 | for (int cand_idx = 1; cand_idx < n_candidates; cand_idx++) { 139 | double candidate[2]; 140 | GetPMJ02Point(x_stratum, y_stratum, i_strata, &rng, candidate); 141 | candidates[cand_idx] = {candidate[0], candidate[1]}; 142 | } 143 | int best_candidate = sample_grid.GetBestCandidate(candidates); 144 | sample_grid.AddSample(candidates[best_candidate]); 145 | sample[0] = candidates[best_candidate].x; 146 | sample[1] = candidates[best_candidate].y; 147 | } 148 | } 149 | } 150 | 151 | if (shuffle) ProgressiveShuffleSamples<2>(num_samples, nd, samples); 152 | } 153 | 154 | /* 155 | * From here on down are implementations of the "stateless" scrambled Sobol' 156 | * function from Section 5.3 of the paper. 157 | */ 158 | namespace { 159 | /* 160 | * randfloat() from Correlated Multi-Jittered Sampling by Andrew Kensler (2013). 161 | */ 162 | inline uint32_t HashToRndInt(int index, unsigned seed) { 163 | unsigned result = index; 164 | result ^= seed; 165 | result ^= result >> 17; 166 | result ^= result >> 10; result *= 0xb36534e5; 167 | result ^= result >> 12; 168 | result ^= result >> 21; result *= 0x93fc4795; 169 | result ^= 0xdf6e307f; 170 | result ^= result >> 17; result *= 1 | seed >> 18; 171 | return result; 172 | } 173 | inline double HashToRnd(int index, unsigned seed) { 174 | return static_cast< double >(HashToRndInt(index, seed)) / 4298115584.0; 175 | } 176 | 177 | /* 178 | * Gets the index of the most significant bit, or -1 if the value is zero. 179 | * Copied from https://stackoverflow.com/a/31718095/624250 with the addition of 180 | * a specific check for zero. 181 | */ 182 | inline int GetMSB(uint32_t v) { 183 | if (v == 0) return -1; 184 | 185 | static constexpr int MultiplyDeBruijnBitPosition[32] = { 186 | 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 187 | 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 188 | }; 189 | 190 | // First round down to one less than a power of 2. 191 | v |= v >> 1; 192 | v |= v >> 2; 193 | v |= v >> 4; 194 | v |= v >> 8; 195 | v |= v >> 16; 196 | 197 | return MultiplyDeBruijnBitPosition[(uint32_t)( v * 0x07C4ACDDU ) >> 27]; 198 | } 199 | } // namespace 200 | 201 | double GetSobolStateless(int idx, int dim, uint32_t seed, int nd) { 202 | assert(nd <= MAX_SOBOL_DIM); 203 | // Base case, return first randomly placed point. 204 | if (!idx) return HashToRnd(dim, seed); 205 | 206 | // Determine stratum size and place in previous strata. 207 | int log_n = GetMSB(idx); // (Right-most bit is numbered zero) 208 | int prev_len = 1 << log_n; 209 | int n_strata = prev_len * 2; 210 | int i = idx - prev_len; 211 | 212 | // Recursively get stratum of previous sample. 213 | int prev_stratum = 214 | GetSobolStateless(i ^ sobol_xors[dim][log_n], dim, seed, nd) * n_strata; 215 | // Generate new sample in adjacent stratum. 216 | return ((prev_stratum ^ 1) + HashToRnd(idx * nd + dim, seed)) / n_strata; 217 | } 218 | 219 | double GetSobolStatelessIter(int idx, int dim, uint32_t seed, int nd) { 220 | assert(nd <= MAX_SOBOL_DIM); 221 | uint32_t bits = HashToRndInt(idx * nd + dim, seed); 222 | int msb = GetMSB(idx); 223 | while (idx > 0) { 224 | int next_idx = idx ^ (1 << msb); 225 | next_idx ^= sobol_xors[dim][msb]; 226 | int next_msb = GetMSB(next_idx); 227 | 228 | // The main key here is that we use some number of bits from the *next* 229 | // value, depending on the gap between this msb and the next msb. 230 | uint32_t rand_bits = HashToRndInt(next_idx * nd + dim, seed); 231 | int bits_to_set = (msb - next_msb); 232 | // The ^1 here corresponds to stratum swapping. 233 | rand_bits = (rand_bits >> (32 - bits_to_set)) ^ 1; 234 | bits = (rand_bits << (32 - bits_to_set)) ^ (bits >> bits_to_set); 235 | 236 | msb = next_msb; 237 | idx = next_idx; 238 | } 239 | 240 | return static_cast(bits) / 4298115584.0; 241 | } 242 | 243 | void GetStochasticSobolStatelessSamples(const int num_samples, 244 | const int nd, 245 | const bool shuffle, 246 | const int n_candidates, 247 | const bool owen, 248 | double* samples) { 249 | ERRIF(owen, "--owen flag is meaningless for stochastic Sobol sequences."); 250 | ASSERT(n_candidates == 1, 251 | "Best candidate sampling doesn't work for stateless Sobol " 252 | "sampler."); 253 | RNG rng; 254 | const uint32_t seed = rng.GetUniformInt(); 255 | for (int i = 0; i < num_samples; i++) { 256 | for (int d = 0; d < nd; d++) { 257 | samples[i*nd + d] = GetSobolStateless(i, d, seed, nd); 258 | } 259 | } 260 | 261 | if (shuffle) ProgressiveShuffleSamples<2>(num_samples, nd, samples); 262 | } 263 | 264 | } // namespace sampling 265 | -------------------------------------------------------------------------------- /sampling/ssobol.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * This header defines functions to stochastically generate both the 6 | * high-dimensional Sobol' sequence, as well as the pmj02 sequence from 7 | * "Progressive Multi-Jittered Sample Sequences" 8 | * by Christensen, Kensler, and Kilpatrick (2018) 9 | */ 10 | #ifndef SAMPLING_SSOBOL_H 11 | #define SAMPLING_SSOBOL_H 12 | 13 | #include 14 | 15 | namespace sampling { 16 | 17 | // Fills the nd-dimensional sample array with an Owen-scrambled 18 | // Sobol sequence, using the stochastic generation algorithm. Each of the nd 19 | // coordinates are stored for a sample are stored contiguously (i.e. in 20 | // "sample major" order). 21 | void GetStochasticSobolSamples(const int num_samples, 22 | const int nd, 23 | const bool shuffle, 24 | const int candidates, 25 | const bool owen, 26 | double* samples); 27 | 28 | // Fills the nd-dimensional sample array with a PMJ02 sequence, using the 29 | // stochastic generation algorithm and the xor-values derived in the 30 | // supplemental materials. ND must be 2, for higher dimensions use the 31 | // ssobol sequence. 32 | void GetPMJ02Samples(const int num_samples, 33 | const int nd, 34 | const bool shuffle, 35 | const int candidates, 36 | const bool owen, 37 | double* samples); 38 | 39 | // Query for an arbitrary coordinate from a scrambled Sobol (0,2)-sequence. Seed 40 | // can be the same across all dimensions, or it can be different for each 41 | // dimension. 42 | // 43 | // nd is the number of dimensions shared by the same seed value, to get separate 44 | // hashed values for each sample index. If a different seed is provided for each 45 | // dimension, this can be used with nd=1. 46 | double GetSobolStateless(const int idx, 47 | const int dim, 48 | const uint32_t seed, 49 | const int nd = 2); 50 | // Same as above but implemented iteratively. 51 | double GetSobolStatelessIter(const int idx, 52 | const int dim, 53 | const uint32_t seed, 54 | const int nd = 2); 55 | 56 | // Pretty much just for testing the stateless Sobol functions, but similar 57 | // to the GetStochasticSobolSamples above. Note that best candidate sampling 58 | // doesn't work, candidates must be equal to 1. 59 | void GetStochasticSobolStatelessSamples(const int num_samples, 60 | const int nd, 61 | const bool shuffle, 62 | const int candidates, 63 | const bool owen, 64 | double* samples); 65 | 66 | } // namespace sampling 67 | 68 | #endif // SAMPLING_SSOBOL_H 69 | -------------------------------------------------------------------------------- /sampling/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * Just a few utilities used in various places in the code. 6 | */ 7 | #ifndef SAMPLING_UTILS_H 8 | #define SAMPLING_UTILS_H 9 | 10 | #include 11 | #include 12 | 13 | # define ASSERT(condition, message) \ 14 | do { \ 15 | if (!(condition)) { \ 16 | std::cerr << "Assertion `" #condition "` failed in " << __FILE__ \ 17 | << " line " << __LINE__ << ": " << message << std::endl; \ 18 | std::terminate(); \ 19 | } \ 20 | } while (false) 21 | # define ERRIF(condition, message) \ 22 | do { \ 23 | if ((condition)) { \ 24 | std::cerr << message << std::endl; \ 25 | } \ 26 | } while (false) 27 | 28 | namespace sampling { 29 | /* 30 | * Generalization of bitwise xor to other prime bases. Adds the BASE digits of i 31 | * and j modulo the base. 32 | */ 33 | template 34 | unsigned CarrylessAdd(unsigned i, unsigned j) { 35 | unsigned sum = 0, bPow = 1; 36 | while (j > 0 && i > 0) { 37 | sum += ((i + j) % BASE) * bPow; 38 | i /= BASE; j /= BASE; 39 | bPow *= BASE; 40 | } 41 | return sum + bPow*(i+j); 42 | } 43 | 44 | /* 45 | * Adds a single digit in an arbitrary base to the least significant digit, 46 | * without carrying. Specialized (faster) case of the above function, used for 47 | * strata swapping. j should be a single digit, i.e. 0 <= j < BASE. 48 | */ 49 | template 50 | unsigned AddLSDigit(unsigned i, unsigned j) { 51 | int i_lsb = i % BASE; 52 | i = i - i_lsb; 53 | int lsb = i_lsb+j; 54 | return i + (lsb >= BASE ? lsb-BASE : lsb); 55 | } 56 | 57 | } // namespace sampling 58 | 59 | #endif // SAMPLING_UTILS_H 60 | -------------------------------------------------------------------------------- /sampling/xor_values.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * These are xor_values for the various sequences we implement. 6 | */ 7 | #ifndef SAMPLING_XOR_VALUES_H 8 | #define SAMPLING_XOR_VALUES_H 9 | 10 | #include 11 | 12 | namespace sampling { 13 | 14 | static constexpr uint32_t pmj02_xors[2][32] = { 15 | {0x0, 0x0, 0x2, 0x6, 0x6, 0xe, 0x36, 0x4e, 0x16, 0x2e, 0x276, 0x6ce, 0x716, 0xc2e, 0x3076, 0x40ce, 0x116, 0x22e, 0x20676, 0x60ece, 0x61716, 0xe2c2e, 0x367076, 0x4ec0ce, 0x170116, 0x2c022e, 0x2700676, 0x6c00ece, 0x7001716, 0xc002c2e, 0x30007076, 0x4000c0ce}, 16 | {0x0, 0x1, 0x3, 0x3, 0x7, 0x1b, 0x27, 0xb, 0x17, 0x13b, 0x367, 0x38b, 0x617, 0x183b, 0x2067, 0x8b, 0x117, 0x1033b, 0x30767, 0x30b8b, 0x71617, 0x1b383b, 0x276067, 0xb808b, 0x160117, 0x138033b, 0x3600767, 0x3800b8b, 0x6001617, 0x1800383b, 0x20006067, 0x808b} 17 | }; 18 | 19 | /* 20 | * Derived from the direction numbers of Joe & Kuo (2008), generated from the 21 | * generator matrices provided with the PBRT renderer. 22 | */ 23 | static constexpr int MAX_SOBOL_DIM = 64; 24 | static constexpr uint32_t sobol_xors[MAX_SOBOL_DIM][32] = { 25 | {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 26 | {0x0, 0x1, 0x1, 0x7, 0x1, 0x13, 0x15, 0x7f, 0x1, 0x103, 0x105, 0x70f, 0x111, 0x1333, 0x1555, 0x7fff, 0x1, 0x10003, 0x10005, 0x7000f, 0x10011, 0x130033, 0x150055, 0x7f00ff, 0x10101, 0x1030303, 0x1050505, 0x70f0f0f, 0x1111111, 0x13333333, 0x15555555, 0x7fffffff}, 27 | {0x0, 0x1, 0x3, 0x1, 0x5, 0x1f, 0x2b, 0x3d, 0x11, 0x133, 0x377, 0x199, 0x445, 0x1ccf, 0x2ddb, 0x366d, 0x101, 0x10303, 0x30707, 0x10909, 0x51515, 0x1f3f3f, 0x2b6b6b, 0x3dbdbd, 0x101011, 0x1303033, 0x3707077, 0x1909099, 0x4515145, 0x1cf3f3cf, 0x2db6b6db, 0x36dbdb6d}, 28 | {0x0, 0x1, 0x0, 0x3, 0xd, 0xc, 0x5, 0x4f, 0x14, 0xe7, 0x329, 0x39c, 0x11, 0x1033, 0x44, 0x30bb, 0xd1cd, 0xc2ec, 0x5415, 0x4fc3f, 0x15054, 0xe5c97, 0x32e5b9, 0x39725c, 0x101, 0x1000303, 0x404, 0x3000b0b, 0xd001d1d, 0xc002c2c, 0x5004545, 0x4f00cfcf}, 29 | {0x0, 0x0, 0x0, 0x5, 0xa, 0x14, 0x11, 0x22, 0x44, 0x19d, 0x33a, 0x674, 0x101, 0x202, 0x404, 0x5d0d, 0xba1a, 0x17434, 0x14151, 0x282a2, 0x50544, 0x1a4e9d, 0x349d3a, 0x693a74, 0x10001, 0x20002, 0x40004, 0x50d000d, 0xa1a001a, 0x14340034, 0x11510051, 0x22a200a2}, 30 | {0x0, 0x0, 0x2, 0x6, 0x3, 0x6, 0x2a, 0x72, 0x5, 0xa, 0x21e, 0x636, 0x35f, 0x6be, 0x2bc2, 0x713a, 0x11, 0x22, 0x20066, 0x600ee, 0x30123, 0x60246, 0x2a06ca, 0x720fd2, 0x51155, 0xa22aa, 0x21e67fe, 0x636ed56, 0x35e26af, 0x6bc4d5e, 0x2bc4d7e2, 0x7135e29a}, 31 | {0x0, 0x1, 0x1, 0x2, 0x9, 0xb, 0x3d, 0x7a, 0x41, 0x1c3, 0x45, 0x8a, 0xf59, 0x1eb, 0x223d, 0x447a, 0x1001, 0x13003, 0x15005, 0x2a00a, 0x89019, 0x9b02b, 0x3ad07d, 0x75a0fa, 0x551141, 0x1ff33c3, 0x15445, 0x2a88a, 0xeac8f59, 0x3f591eb, 0x241eb23d, 0x483d647a}, 32 | {0x0, 0x0, 0x1, 0x2, 0x1, 0x5, 0xa, 0x31, 0x62, 0x75, 0x11, 0x22, 0x455, 0x8aa, 0x501, 0x1675, 0x2cea, 0xcfa1, 0x19f42, 0x1f125, 0x101, 0x202, 0x100505, 0x200a0a, 0x101111, 0x502525, 0xa04a4a, 0x310b1b1, 0x6216362, 0x7527775, 0x1141511, 0x2282a22}, 33 | {0x0, 0x0, 0x1, 0x2, 0x5, 0x9, 0x12, 0xd, 0x1a, 0x1d, 0x41, 0x82, 0x545, 0xa8a, 0x1155, 0x2e69, 0x5cd2, 0x17cd, 0x2f9a, 0xf15d, 0x1001, 0x2002, 0x105005, 0x20a00a, 0x515015, 0x929029, 0x1252052, 0xd8d08d, 0x1b1a11a, 0x1f1d21d, 0x4541441, 0x8a82882}, 34 | {0x0, 0x0, 0x3, 0x6, 0xf, 0xf, 0x1e, 0x4d, 0x9a, 0x145, 0x55, 0xaa, 0xdab, 0x1b56, 0x3a53, 0x35a3, 0x6b46, 0x10869, 0x210d2, 0x5ff41, 0x1111, 0x2222, 0x307777, 0x60eeee, 0xf1eeef, 0xf2dddf, 0x1e5bbbe, 0x4dc111d, 0x9b8223a, 0x14672215, 0x5114405, 0xa22880a}, 35 | {0x0, 0x0, 0x1, 0x0, 0x0, 0x17, 0x2e, 0x6b, 0xb8, 0x170, 0x115, 0x22a, 0x141, 0x8a8, 0x1150, 0x689b, 0xd136, 0x14af7, 0x344d8, 0x689b0, 0x10111, 0x20222, 0x150555, 0x80888, 0x101110, 0x1473447, 0x28e688e, 0x65be55b, 0xa39a238, 0x14734470, 0x14404145, 0x2880828a}, 36 | {0x0, 0x0, 0x0, 0x4, 0xe, 0x1b, 0x36, 0x6c, 0x34, 0xf2, 0x145, 0x28a, 0x514, 0x1f3c, 0x21e6, 0x5917, 0xb22e, 0x1645c, 0x1ace4, 0x18fba, 0x11011, 0x22022, 0x44044, 0x4cc0cc, 0xffe1fe, 0x188b38b, 0x3116716, 0x622ce2c, 0x2675274, 0xdfd0dd2, 0x11410115, 0x2282022a}, 37 | {0x0, 0x1, 0x1, 0x3, 0xc, 0x1d, 0x7, 0x49, 0xaf, 0xcc, 0x151, 0x7f3, 0x15, 0x57b, 0x2adc, 0x45ad, 0x4ef7, 0x1d319, 0x3639f, 0x1610c, 0x11101, 0x133303, 0x155505, 0x3bbb0b, 0xdddc1c, 0x1eeed3d, 0x333747, 0x45559c9, 0xb445eaf, 0xe220ecc, 0x10114451, 0x7033ccf3}, 38 | {0x0, 0x1, 0x3, 0x1, 0xe, 0x2, 0x3, 0x45, 0xc9, 0x5b, 0x3a2, 0xe6, 0x5, 0x100f, 0x301b, 0x102d, 0xe066, 0x20aa, 0x314f, 0x453d1, 0xc96ed, 0x5bb37, 0x3a392a, 0xe4b7e, 0x11, 0x1000033, 0x3000077, 0x1000099, 0xe0001fe, 0x2000202, 0x3000473, 0x45000c95}, 39 | {0x0, 0x0, 0x0, 0x7, 0x5, 0xd, 0x1b, 0x36, 0x6c, 0x159, 0x87, 0x8f, 0x145, 0x28a, 0x514, 0x7cf3, 0x4001, 0xf6d9, 0x1fcf7, 0x3f9ee, 0x7f3dc, 0x16117d, 0xdc05b, 0x27673, 0x11011, 0x22022, 0x44044, 0x70ff0ff, 0x5145145, 0xd2fd2fd, 0x1b5eb5eb, 0x36bd6bd6}, 40 | {0x0, 0x1, 0x0, 0x2, 0x8, 0x2, 0x21, 0x23, 0x84, 0x1ca, 0x118, 0x4e2, 0x401, 0x1c03, 0x1004, 0x80a, 0xe018, 0xa822, 0x39461, 0xbca3, 0xe5184, 0x138bca, 0x5e518, 0x7da4e2, 0x100001, 0x1300003, 0x400004, 0x2a0000a, 0x9800018, 0x200022, 0x27100061, 0x293000a3}, 41 | {0x0, 0x0, 0x0, 0x7, 0xb, 0xf, 0x27, 0x4e, 0x9c, 0xd, 0x1e1, 0x6ed, 0x415, 0x82a, 0x1054, 0x4cc3, 0xddc7, 0x4e63, 0x3eb8b, 0x7d716, 0xfae2c, 0x8cee9, 0x9d875, 0x55bf89, 0x100111, 0x200222, 0x400444, 0x7f00fff, 0xab01aab, 0xdf02ddf, 0x21706117, 0x42e0c22e}, 42 | {0x0, 0x1, 0x0, 0x6, 0x7, 0x7, 0x2d, 0x37, 0xb4, 0x6, 0x3d3, 0x4a3, 0x451, 0x1cf3, 0x1144, 0x5b6e, 0x28a7, 0xe797, 0x379fd, 0x18a07, 0xde7f4, 0x8dbe6, 0x23f223, 0x7b5253, 0x101101, 0x1303303, 0x404404, 0x6e0ee0e, 0x6716717, 0x5725727, 0x2bd6bd6d, 0x3c7bc7b7}, 43 | {0x0, 0x0, 0x1, 0x2, 0xb, 0xc, 0x33, 0x66, 0xbf, 0x17e, 0x3d, 0x434, 0x505, 0xa0a, 0x111, 0x222, 0xc777, 0x5c9c, 0x28ebf, 0x51d7e, 0xcb443, 0x196886, 0x170dc9, 0x7f98e4, 0x110011, 0x220022, 0x1550055, 0x2aa00aa, 0xaab01ab, 0xeec02ec, 0x34430743, 0x68860e86}, 44 | {0x0, 0x1, 0x2, 0x7, 0x8, 0x11, 0x2b, 0x3, 0x85, 0x10a, 0x391, 0x428, 0x8d3, 0x153d, 0x5, 0x400f, 0x801e, 0x1c033, 0x20078, 0x440f5, 0xac1c7, 0xc28f, 0x214791, 0x428f22, 0xe459d5, 0x10a3c88, 0x234bb9f, 0x54fa1c9, 0x11, 0x10000033, 0x20000066, 0x700000ff}, 45 | {0x0, 0x1, 0x2, 0x2, 0x5, 0x19, 0x14, 0x9, 0x9b, 0x136, 0x15a, 0x23d, 0xd71, 0x8f4, 0x41, 0x40c3, 0x8186, 0x828a, 0x14555, 0x64e79, 0x51554, 0x262c9, 0x26a75b, 0x4d4eb6, 0x57d3da, 0x8dc57d, 0x35b0131, 0x23715f4, 0x1001, 0x10003003, 0x20006006, 0x2000a00a}, 46 | {0x0, 0x0, 0x2, 0x3, 0xd, 0x1c, 0x2e, 0xf, 0x1e, 0x122, 0x1e9, 0x63b, 0xf54, 0x157a, 0x55, 0xaa, 0x81fe, 0xc257, 0x346f9, 0x70c0c, 0xb9cb6, 0x3e983, 0x7d306, 0x48750a, 0x7b769d, 0x18d9ba7, 0x3d34244, 0x55069b2, 0x1111, 0x2222, 0x20006666, 0x3000bbbb}, 47 | {0x0, 0x1, 0x1, 0x1, 0x0, 0x6, 0x16, 0x11, 0xb3, 0xd5, 0x19, 0x110, 0x146, 0xe36, 0x101, 0x4303, 0x4505, 0x4909, 0x1010, 0x1a626, 0x5d656, 0x4d191, 0x2d72b3, 0x3797d5, 0x25d19, 0x4d1910, 0x40d746, 0x3a39836, 0x10001, 0x10030003, 0x10050005, 0x10090009}, 48 | {0x0, 0x1, 0x0, 0x2, 0x3, 0x12, 0x3a, 0x1d, 0xa7, 0x74, 0x1d2, 0x77, 0xb4a, 0x18e2, 0x151, 0x43f3, 0x544, 0x882a, 0xd6e3, 0x4bd92, 0xee35a, 0x7f30d, 0x281517, 0x1fcc34, 0x707e72, 0x1725c7, 0x2ceb76a, 0x60dec42, 0x11101, 0x10033303, 0x44404, 0x200aaa0a}, 49 | {0x0, 0x1, 0x0, 0x3, 0x9, 0xd, 0x3b, 0x27, 0xe9, 0x9c, 0xd1, 0x7ef, 0x3e3, 0x1381, 0x415, 0x4c3f, 0x1054, 0xec97, 0x225ed, 0x3f649, 0xf2a27, 0xb544b, 0x3dfcdd, 0x2d512c, 0x275e85, 0x1c4b2a3, 0xb62e5f, 0x42d8195, 0x100111, 0x10300333, 0x400444, 0x30b00bbb}, 50 | {0x0, 0x0, 0x1, 0x4, 0xd, 0x4, 0x16, 0x2b, 0x56, 0x7, 0x3f4, 0x5ef, 0x7cc, 0x38a, 0x445, 0x88a, 0x5551, 0x1333c, 0x33329, 0x199b4, 0x4cc8e, 0x844c7, 0x10898e, 0x957db, 0xe33524, 0x14f3d93, 0x1a98bfc, 0x64e722, 0x101011, 0x202022, 0x10505055, 0x40c0c0cc}, 51 | {0x0, 0x1, 0x1, 0x5, 0xd, 0x8, 0x1c, 0x39, 0xcb, 0x5d, 0x395, 0x405, 0x2e8, 0x2fc, 0x541, 0x4fc3, 0x5045, 0x17a4d, 0x32e5d, 0x28228, 0x63b5c, 0xc33f9, 0x34540b, 0x1cfc1d, 0xfd63d5, 0x13e5c45, 0xe7e0e8, 0x1e91fc, 0x111001, 0x10333003, 0x10555005, 0x50ddd00d}, 52 | {0x0, 0x0, 0x3, 0x3, 0x0, 0x12, 0x0, 0x3f, 0x7e, 0x13d, 0x39, 0x3f0, 0xd6e, 0xfc0, 0x555, 0xaaa, 0xdaab, 0xe557, 0x5550, 0x4755a, 0x15540, 0xda573, 0x1b4ae6, 0x407a59, 0x1bc40d, 0xda5730, 0x335b3b6, 0x3695cc0, 0x111111, 0x222222, 0x30777777, 0x30bbbbbb}, 53 | {0x0, 0x1, 0x2, 0x3, 0x5, 0x16, 0x38, 0x41, 0x43, 0x86, 0x34b, 0x7d5, 0x6b6, 0x278, 0x1001, 0x7003, 0xe006, 0x700b, 0x1015, 0x6e036, 0x98078, 0x1c50c1, 0x4f143, 0x9e286, 0xa6774b, 0x1081fd5, 0xc6f6b6, 0x2b9a278, 0x1000001, 0x13000003, 0x26000006, 0x3b00000b}, 54 | {0x0, 0x0, 0x2, 0x1, 0x3, 0xd, 0xb, 0x4b, 0x96, 0xba, 0x293, 0x5ed, 0xcdf, 0x15c5, 0x1045, 0x208a, 0xe19e, 0xd26d, 0x1f49f, 0x19bd9, 0x663a7, 0x1e4127, 0x3c824e, 0x586d2, 0xcc481f, 0x1a6d119, 0x2fd6863, 0x6208391, 0x1001011, 0x2002022, 0x26006066, 0x19009099}, 55 | {0x0, 0x1, 0x1, 0x2, 0xe, 0x0, 0x38, 0x53, 0x75, 0x19f, 0x33e, 0x142, 0xa60, 0x508, 0x1105, 0x730f, 0x1511, 0x2a22, 0x27e66, 0x220a0, 0x9f998, 0x19209f, 0xb61a1, 0x5da2e3, 0xbb45c6, 0xcdce4a, 0x32413e0, 0x3373928, 0x1010011, 0x13030033, 0x15050055, 0x2a0a00aa}, 56 | {0x0, 0x1, 0x2, 0x6, 0x5, 0x11, 0x6, 0x55, 0x7f, 0xfe, 0x56, 0x6d1, 0x725, 0x17be, 0x1111, 0x7333, 0xe666, 0x16eee, 0x445, 0x76221, 0x5a226, 0x18c885, 0x9598f, 0x12b31e, 0x54f736, 0x15762c1, 0xad5075, 0x6a0925e, 0x1010101, 0x13030303, 0x26060606, 0x6e0e0e0e}, 57 | {0x0, 0x1, 0x0, 0x4, 0x4, 0xe, 0x15, 0x65, 0x2f, 0x194, 0xbc, 0x5c4, 0x9d6, 0x1461, 0x1411, 0x7c33, 0x5044, 0x1f0cc, 0x1154, 0x1dace, 0x14105, 0x148ab5, 0x1d9fdf, 0x522ad4, 0x767f7c, 0x19a8184, 0x38e3cb6, 0x68ca671, 0x1100101, 0x13300303, 0x4400404, 0x4cc00c0c}, 58 | {0x0, 0x0, 0x1, 0x2, 0xe, 0x1, 0x14, 0x6f, 0xde, 0x153, 0x2a6, 0x3ea, 0xd0f, 0x168c, 0x1455, 0x28aa, 0x501, 0xa02, 0x21e06, 0x2def5, 0x10144, 0x16ba63, 0x2d74c6, 0x6c53ef, 0xd8a7de, 0x69e862, 0x2e1f603, 0x61fd77c, 0x1101111, 0x2202222, 0x15505555, 0x2aa0aaaa}, 59 | {0x0, 0x0, 0x3, 0x5, 0x0, 0x9, 0x20, 0x71, 0xe2, 0xd7, 0xbd, 0x710, 0x959, 0x260, 0x1501, 0x2a02, 0xab07, 0x1a90d, 0x15010, 0x5d29, 0xfe060, 0x1065f1, 0x20cbe2, 0x1139d7, 0x72ddbd, 0x1065f10, 0x3bff459, 0x215c260, 0x1110001, 0x2220002, 0x37770007, 0x5ddd000d}, 60 | {0x0, 0x0, 0x3, 0x1, 0x5, 0xd, 0x5, 0x77, 0xee, 0xc5, 0x34f, 0x45b, 0xa73, 0x1eeb, 0x1515, 0x2a2a, 0xab6b, 0xfdbd, 0x5011, 0x10b49, 0x44401, 0x11975b, 0x232eb6, 0x14e481, 0xbd2d83, 0x1eebf87, 0x3499b8f, 0x4921cf7, 0x1110111, 0x2220222, 0x37770777, 0x19990999}, 61 | {0x0, 0x1, 0x3, 0x3, 0xb, 0x12, 0x3, 0x7d, 0x7, 0xf3, 0x2ef, 0x13f, 0x18a, 0x1e47, 0x1551, 0x7ff3, 0xaab7, 0x557b, 0x3006b, 0x75592, 0x5abb3, 0x13a96d, 0x14fbb7, 0x1a5e03, 0xc9b0df, 0xf3260f, 0x128e9aa, 0x4bea0f7, 0x1111101, 0x13333303, 0x37777707, 0x3bbbbb0b}, 62 | {0x0, 0x1, 0x0, 0x6, 0x8, 0xa, 0x7, 0x22, 0x1d, 0x127, 0x74, 0x6a6, 0x938, 0x972, 0x13, 0x2f1a, 0x151, 0x103f3, 0x544, 0x60d6e, 0x81f98, 0xa220a, 0x752f7, 0x228002, 0x1c4a8d, 0x124df97, 0x712a34, 0x6abeb46, 0x926fcb8, 0x95390d2, 0x4756e3, 0x2f94823a}, 63 | {0x0, 0x1, 0x1, 0x7, 0x8, 0x1f, 0x2e, 0x6, 0x2b, 0x17d, 0x187, 0x689, 0xbe8, 0x1959, 0x2002, 0x137a, 0x445, 0x10ccf, 0x11551, 0x73ff3, 0x86678, 0x1ff303, 0x2fa256, 0x43b1e, 0x2fe347, 0x17025c9, 0x1906e5b, 0x6b0b2ed, 0xb812e48, 0x19b2ee7d, 0x219be88a, 0x1111e812}, 64 | {0x0, 0x1, 0x0, 0x5, 0xe, 0x1a, 0x12, 0x15, 0x2d, 0x177, 0xb4, 0x4f1, 0xd56, 0x1c42, 0x1bca, 0x1c9, 0x451, 0x10cf3, 0x1144, 0x5379d, 0xe7e7e, 0x1ae51a, 0x1359f2, 0x177885, 0x29ecbd, 0x17a35c7, 0xa7b2f4, 0x4c13ba1, 0xd25c5b6, 0x1cbfe0e2, 0x1ab63dea, 0x3e6cb19}, 65 | {0x0, 0x1, 0x0, 0x5, 0xf, 0x1a, 0x24, 0x1f, 0x4d, 0x1d7, 0x134, 0x611, 0x85b, 0x1582, 0x3fd4, 0x3edb, 0x1051, 0x130f3, 0x4144, 0x5d39d, 0xef62f, 0x19ad1a, 0x225f24, 0x16deaf, 0x5996dd, 0x1eabb67, 0x1665b74, 0x6f37b41, 0x9d93b2b, 0x16d42d22, 0x38313794, 0x351255ab}, 66 | {0x0, 0x0, 0x3, 0x4, 0x6, 0x11, 0x6, 0x20, 0x5f, 0xbe, 0x29d, 0x784, 0x232, 0x1f4f, 0x1002, 0x460, 0x1155, 0x22aa, 0x376ab, 0x4cffc, 0x772ae, 0x122ea5, 0x232be, 0x2a8020, 0x4be913, 0x97d226, 0x2f39f79, 0x770ecd4, 0x306e75a, 0x1c885a43, 0x154232aa, 0xc89abe0}, 67 | {0x0, 0x1, 0x2, 0x6, 0x2, 0x19, 0x1, 0x69, 0x63, 0x1a5, 0x34a, 0x452, 0x4f6, 0x102b, 0x19a3, 0x4f5b, 0x1405, 0x13c0f, 0x2781e, 0x6d836, 0x3685a, 0x1a74dd, 0x41545, 0x64374d, 0x70b8ef, 0x191c931, 0x3239262, 0x4a6551a, 0x5eaff2e, 0x13e9ec87, 0x1d5e832f, 0x40902e37}, 68 | {0x0, 0x1, 0x0, 0x0, 0x9, 0x1d, 0x3d, 0x26, 0x65, 0x1af, 0x194, 0x328, 0xc1d, 0x1529, 0x2c69, 0x197e, 0x1411, 0x13c33, 0x5044, 0xa088, 0x8f589, 0x1e27ed, 0x3b23ad, 0x2ef2c6, 0x76d335, 0x19b755f, 0x1db4cd4, 0x3b699a8, 0xdad79cd, 0x16ac53b9, 0x2b189ef9, 0x1285169e}, 69 | {0x0, 0x0, 0x1, 0x1, 0x8, 0xb, 0x14, 0x3b, 0x69, 0xd2, 0xcd, 0x221, 0xdd8, 0x5d3, 0x974, 0x7c3, 0x1441, 0x2882, 0x14545, 0x1b649, 0x9e618, 0x916eb, 0x100554, 0x32727b, 0x7a6f29, 0xf4de52, 0x93d38d, 0x2a91661, 0xc758bd8, 0x7102d13, 0xcd48474, 0xd814b03}, 70 | {0x0, 0x1, 0x2, 0x7, 0x0, 0x1, 0x8, 0x18, 0x71, 0x193, 0x326, 0x5df, 0x710, 0xf51, 0x17c8, 0x2418, 0x1501, 0x13f03, 0x27e06, 0x7c30f, 0x15010, 0x3b521, 0xde848, 0x137898, 0x62a471, 0x1a7ec93, 0x34fd926, 0x5385edf, 0x62a4710, 0xd362a51, 0x13bc3fc8, 0x2c6d5c18}, 71 | {0x0, 0x1, 0x3, 0x7, 0xb, 0x7, 0x32, 0x56, 0x87, 0x89, 0x95, 0xad, 0x6c1, 0x1475, 0xa5e, 0x3f22, 0x4015, 0x1c03f, 0x2c06b, 0x4c0c3, 0xdc1c7, 0xec2cb, 0x2e869a, 0x638eee, 0xe6dfeb, 0x2b603d, 0x1b01f91, 0x286e0c9, 0x37061d5, 0x196be2f1, 0x18cc46c6, 0x10e3318a}, 72 | {0x0, 0x1, 0x2, 0x7, 0xa, 0x14, 0x2a, 0x7f, 0x8d, 0x97, 0x12e, 0xcb, 0x7a2, 0xf44, 0x1d92, 0x47b, 0x4051, 0x1c0f3, 0x381e6, 0x4c33f, 0xc873a, 0x190e74, 0x309c4a, 0x40f0cf, 0xee3a1d, 0x324e27, 0x649c4e, 0x2fb76bb, 0x24e0502, 0x49c0a04, 0xae46032, 0x254c1d0b}, 73 | {0x0, 0x0, 0x2, 0x0, 0xa, 0x16, 0x3, 0x5, 0xa9, 0x152, 0x1f6, 0x548, 0x48a, 0xa46, 0x28bb, 0x538d, 0x4441, 0x8882, 0x39986, 0x22208, 0xcee9a, 0x1b55b6, 0x12dc83, 0x2675c5, 0xc58fe9, 0x18b1fd2, 0x9d2076, 0x62c7f48, 0x1ff9e0a, 0x7423c6, 0x332d6a7b, 0x641444cd}, 74 | {0x0, 0x0, 0x0, 0x4, 0x9, 0x1c, 0xa, 0x9, 0xc3, 0x186, 0x30c, 0x114, 0x3eb, 0xd44, 0x3d5e, 0x6e5b, 0x5005, 0xa00a, 0x14014, 0x7c03c, 0xed07d, 0x10c0cc, 0x1c2162, 0x23d2ad, 0xaff6cf, 0x15fed9e, 0x2bfdb3c, 0x3c06d44, 0x62f2c47, 0x4c1d854, 0x25dde826, 0x5b2b2737}, 75 | {0x0, 0x1, 0x2, 0x5, 0x1, 0x3, 0x12, 0x72, 0xcf, 0x51, 0xa2, 0x8b, 0xd3f, 0x1bb1, 0x2cae, 0x32ce, 0x5055, 0x1f0ff, 0x3e1fe, 0x693a9, 0x45505, 0x9fa5f, 0x3b0ba, 0x41909a, 0xa059c3, 0xe0ea45, 0x1c1d48a, 0x223f0d7, 0xba5c5f3, 0x16ebd225, 0x31535f76, 0x15748696}, 76 | {0x0, 0x1, 0x0, 0x3, 0x9, 0x14, 0x39, 0x3d, 0xe7, 0x29, 0x39c, 0x511, 0xaf, 0x50c, 0x158f, 0x5853, 0x5415, 0x1fc3f, 0x15054, 0x15c97, 0xeb5ed, 0x1a93a4, 0x21320d, 0x1b6d99, 0x87340b, 0x895c1d, 0x21cd02c, 0x6b0fc45, 0x5cdd4e3, 0xe8911fc, 0x4e65743, 0x60ad802f}, 77 | {0x0, 0x0, 0x2, 0x2, 0x8, 0x1f, 0x1c, 0x3e, 0xf5, 0x1ea, 0x3e, 0x442, 0xf8, 0xb93, 0x2a6c, 0x50e6, 0x5511, 0xaa22, 0x3fe66, 0x2aa, 0xff998, 0x13f0cf, 0xfe99c, 0x182d5e, 0x912fa5, 0x1225f4a, 0x166e1de, 0x7ab2262, 0x59b8778, 0x349bda3, 0x3794d0ac, 0x6a4f4086}, 78 | {0x0, 0x1, 0x1, 0x3, 0x7, 0x3, 0x3c, 0x30, 0xff, 0x11, 0x233, 0x255, 0x6bb, 0xf67, 0x413, 0x7fbc, 0x6bb0, 0x1e00f, 0x101, 0x40303, 0x40505, 0xc0b0b, 0x1c1717, 0xc2323, 0xf07c7c, 0xc0b0b0, 0x3fdfeff, 0x461311, 0x8ca3533, 0x95e5f55, 0x1afaadbb, 0x3db34867}, 79 | {0x0, 0x1, 0x3, 0x6, 0xe, 0x0, 0x12, 0x1e, 0x55, 0x1b, 0x22d, 0x641, 0xc82, 0x1d32, 0x360, 0x2346, 0x30b2, 0xb607, 0x145, 0x403cf, 0xc06db, 0x180db6, 0x3819e6, 0x28a0, 0x48479a, 0x78bb66, 0x1550441, 0x6e97b7, 0x8b3b8d9, 0x1909e605, 0x3213cc0a, 0x74fab77a}, 80 | {0x0, 0x0, 0x0, 0x2, 0x1, 0x1a, 0x2f, 0x3a, 0xc1, 0x21, 0x42, 0x84, 0x54a, 0x31, 0x337a, 0x538f, 0x63fa, 0x1bbe1, 0x401, 0x802, 0x1004, 0x8280a, 0x44411, 0x68e83a, 0xbdbc6f, 0xeae8ba, 0x30305c1, 0x8c8621, 0x1190c42, 0x2321884, 0x157d3d4a, 0x44e431}, 81 | {0x0, 0x1, 0x0, 0x3, 0x2, 0x1d, 0x20, 0x5a, 0xc5, 0x2d, 0x277, 0xb4, 0x71f, 0x68a, 0x3c81, 0x4ee0, 0xaa22, 0x1ba59, 0x451, 0x40cf3, 0x1144, 0xc2e7b, 0x84db2, 0x74f8ad, 0x819e60, 0x16b53fa, 0x31378d5, 0xbc1fbd, 0x9c420c7, 0x2f07ef4, 0x1c24dd2f, 0x1ab9c4aa}, 82 | {0x0, 0x0, 0x0, 0x5, 0xf, 0x2, 0x36, 0x19, 0xff, 0x33, 0x66, 0xcc, 0xb67, 0x1c31, 0x206, 0x653a, 0x291b, 0x1dc11, 0x505, 0xa0a, 0x1414, 0x143939, 0x3c6363, 0x8aaaa, 0xd9afae, 0x66fffd, 0x3fa0503, 0xc6f5ff, 0x18debfe, 0x31bd7fc, 0x2dea8dfb, 0x700839f5}, 83 | {0x0, 0x0, 0x1, 0x4, 0x2, 0xb, 0x3, 0x6e, 0xcd, 0x59, 0xb2, 0x33d, 0xbac, 0x122, 0x1f03, 0x10ab, 0xeefe, 0x1fa35, 0x1141, 0x2282, 0x45445, 0x10cf0c, 0x93692, 0x2e91eb, 0x86383, 0x1b6356e, 0x3296f8d, 0x1435d19, 0x286ba32, 0xc4e297d, 0x2f179cac, 0x6b36ba2}, 84 | {0x0, 0x0, 0x1, 0x6, 0xf, 0xa, 0xc, 0x66, 0x95, 0x5f, 0xbe, 0x323, 0xf3a, 0x1895, 0x1da6, 0xc44, 0xfe62, 0x15e53, 0x1155, 0x22aa, 0x45401, 0x18ed56, 0x3de953, 0x2a82a2, 0x349abc, 0x196b29e, 0x24cbed1, 0x15b1613, 0x2b62c26, 0xc374e5f, 0x3d02c4f2, 0x61e8b3d1}, 85 | {0x0, 0x1, 0x2, 0x5, 0x9, 0x1a, 0x3d, 0x18, 0x5b, 0x69, 0x2bb, 0x576, 0x885, 0x17b1, 0x3c2a, 0x6975, 0x158, 0xc023, 0x1441, 0x43cc3, 0x87986, 0x14e74d, 0x25f259, 0x6b46ba, 0xf23b3d, 0x6bc698, 0x17c8b9b, 0x18bac29, 0xa9cf47b, 0x1539e8f6, 0x23f87dc5, 0x5d6c0ff1}, 86 | {0x0, 0x0, 0x0, 0x4, 0x9, 0x11, 0x27, 0x40, 0xdd, 0x6f, 0xde, 0x1bc, 0xac4, 0x17e7, 0x297f, 0x592d, 0xac40, 0x1fd1b, 0x1455, 0x28aa, 0x5154, 0x10f3fc, 0x25f3ad, 0x47dba5, 0x9bf24b, 0x10f3fc0, 0x36ec839, 0x193fae3, 0x327f5c6, 0x64feb8c, 0x2ad03c94, 0x5c3383cb}, 87 | {0x0, 0x0, 0x1, 0x3, 0xd, 0x13, 0x29, 0x16, 0x9, 0x77, 0xee, 0x3ab, 0x521, 0x1f63, 0x2f09, 0x42ef, 0x11c2, 0x66cf, 0x1515, 0x2a2a, 0x44141, 0xc9797, 0x35b8b9, 0x4fcccf, 0xa35a5d, 0x53a5ae, 0x31a8bd, 0x1f0f7db, 0x3e1efb6, 0xe3328b7, 0x1596a6b5, 0x7ebbebdf}, 88 | {0x0, 0x0, 0x2, 0x3, 0x3, 0x1f, 0x2e, 0x58, 0x1, 0x7d, 0xfa, 0x50e, 0x56f, 0x157, 0x34eb, 0x4e06, 0x95f8, 0x7f7d, 0x1551, 0x2aa2, 0x87fe6, 0xc957b, 0xd6ae3, 0x7f3f0f, 0xbf2b0e, 0x16e0358, 0x114451, 0x1d8e3ed, 0x3b1c7da, 0x14d2486e, 0x14ae3b5f, 0x7e71ae7}, 89 | }; 90 | 91 | static constexpr int FAURE03_XOR_SIZE = 21; 92 | static constexpr uint32_t faure03_xors[3][FAURE03_XOR_SIZE] = { 93 | {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 94 | {0x0, 0x1, 0x8, 0x1, 0x20, 0xe5, 0x38, 0x39d, 0x19a0, 0x1, 0x4ce8, 0x26725, 0x4d00, 0x99cee, 0x44c888, 0x10d49d, 0x115df00, 0x7b25f51, 0x99c8, 0x1719ab85, 0xb8c45970}, 95 | {0x0, 0x2, 0x5, 0x2, 0x3e, 0x9e, 0x1d, 0x626, 0x1004, 0x2, 0x99ce, 0x18086, 0x99fe, 0x129fda, 0x2f769e, 0x8bb86, 0x1d8d2ee, 0x4cfa6d2, 0x4ce5, 0x2e305626, 0x737aa4b4}, 96 | }; 97 | 98 | static constexpr int FAURE05_XOR_SIZE = 14; 99 | static constexpr uint32_t faure05_xors[5][FAURE05_XOR_SIZE] = { 100 | {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 101 | {0x0, 0x1, 0xe, 0x56, 0x270, 0x1, 0xc3e, 0xab0f, 0x41a90, 0x1dc43d, 0x186e, 0x95a1bf, 0x828e1dd, 0x3220140e}, 102 | {0x0, 0x2, 0x15, 0x2b, 0x19e, 0x2, 0x1875, 0x10098, 0x20e44, 0x13c4de, 0x30d5, 0x12b1288, 0xc3f039c, 0x1921652b}, 103 | {0x0, 0x3, 0x6, 0x75, 0x126, 0x3, 0x24af, 0x4995, 0x59604, 0xe0e5f, 0xc36, 0x1bf5e65, 0x380682c, 0x4422222f}, 104 | {0x0, 0x4, 0x13, 0x40, 0xea, 0x4, 0x30ec, 0xe866, 0x30f74, 0xb3320, 0x24a3, 0x254e7b6, 0xb132e21, 0x25587f8e}, 105 | }; 106 | 107 | static constexpr int FAURE07_XOR_SIZE = 12; 108 | static constexpr uint32_t faure07_xors[7][FAURE07_XOR_SIZE] = { 109 | {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 110 | {0x0, 0x1, 0x14, 0xb0, 0x5af, 0x34e3, 0x1cb90, 0x1, 0xc9104, 0xfb53a1, 0x8a3ac06, 0x476bfa7b}, 111 | {0x0, 0x2, 0x1f, 0x135, 0x23c, 0x1f84, 0x16d80, 0x2, 0x1921ff, 0x1858e76, 0xf2afd4d, 0x1c1402ad}, 112 | {0x0, 0x3, 0x2f, 0x6f, 0x72d, 0xb88, 0x1377f, 0x3, 0x25b2ff, 0x24e9e07, 0x572df6e, 0x5a2c60f1}, 113 | {0x0, 0x4, 0xc, 0xfd, 0x32f, 0x3a39, 0xfca0, 0x4, 0x3243fd, 0x96cc6e, 0xc6b49c0, 0x2801b132}, 114 | {0x0, 0x5, 0x18, 0x45, 0x8e8, 0x2915, 0xa1df, 0x5, 0x3ed4f9, 0x12d984d, 0x36319c9, 0x6feb4dce}, 115 | {0x0, 0x6, 0x29, 0xe6, 0x451, 0x18f6, 0x7c77, 0x6, 0x4b65fa, 0x20338de, 0xb4a46da, 0x363df6be}, 116 | }; 117 | 118 | static constexpr int FAURE011_XOR_SIZE = 10; 119 | static constexpr uint32_t faure011_xors[11][FAURE011_XOR_SIZE] = { 120 | {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 121 | {0x0, 0x1, 0x20, 0x1c4, 0x175f, 0x12825, 0x10815f, 0xc0544e, 0x9d60449, 0x7d15b085}, 122 | {0x0, 0x2, 0x33, 0x34c, 0x2e4d, 0x25283, 0x3ec9e, 0x5c10b0, 0x6d65a04, 0x653d9e07}, 123 | {0x0, 0x3, 0x44, 0x488, 0x616, 0x1167c, 0x130598, 0x126bfde, 0x27b63bb, 0x471683f6}, 124 | {0x0, 0x4, 0x5e, 0xcf, 0x1b93, 0x21df8, 0x57667, 0xb029c9, 0xc06fdb9, 0x2f60b5a5}, 125 | {0x0, 0x5, 0x76, 0x1fe, 0x30e8, 0xbeea, 0x15f86b, 0x3f5317, 0x89939a8, 0xfd5a44d}, 126 | {0x0, 0x6, 0x13, 0x36c, 0xc8e, 0x1db7d, 0x9c8d8, 0xfb6a05, 0x524bac8, 0x82b88868}, 127 | {0x0, 0x7, 0x27, 0x509, 0x20fd, 0x90f9, 0x16b9b2, 0x9471cd, 0x181d0cb, 0x6ebc105e}, 128 | {0x0, 0x8, 0x39, 0x13a, 0x3494, 0x1c19f, 0xbd507, 0x316f6c, 0xa9f7beb, 0x549ad223}, 129 | {0x0, 0x9, 0x54, 0x2ce, 0x13eb, 0x521e, 0x1a0569, 0xe376ec, 0x7f33bd8, 0x3fdc17e2}, 130 | {0x0, 0xa, 0x6d, 0x42a, 0x2719, 0x15d1e, 0xde7d4, 0x70c7f2, 0x41d9001, 0x2354d3fa}, 131 | }; 132 | 133 | } // namespace sampling 134 | 135 | #endif // SAMPLING_XOR_VALUES_H 136 | -------------------------------------------------------------------------------- /shuffle_indices.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Andrew Helmer 2021. 3 | * Licensed under MIT Open-Source License: see LICENSE. 4 | * 5 | * Running "make shuffle_indices" will build a shuffle_indices command-line 6 | * util, which can output arrays of shuffled indices that can shuffle a base-b 7 | * (t,s)-sequence into another base-b (t,s)-sequence, maintaining its 8 | * progressive properties, but decorrelating it from other runs. See Section 5.2 9 | * of the paper. 10 | * 11 | * The required arguments are: 12 | * --n= is the "number of samples" 13 | * --b= or --base= is the base to perform the shuffling in. 14 | * 15 | * e.g. 16 | * ./shuffle_indices --n=9 --b=3 17 | * 18 | * Optional arguments are: 19 | * --prog or --progressive will perform progressive shuffling rather than full 20 | * shuffling. This will fully decorrelate stochastic sequences, but 21 | * power-of-b prefixes of the original sequence will be maintained. 22 | * 23 | * Note that if --progressive is not used, --n must be a power-of-b, n=b^m for 24 | * some m. If --progressive is used, it can be a constant multiple of a 25 | * power-of-b, n = k*b^m, where 1 <= k <= b. 26 | */ 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "sampling/shuffling.h" 35 | #include "sampling/utils.h" 36 | 37 | using std::string; 38 | 39 | namespace { 40 | // If the string "arg" starts with the string "arg_name", take the rest of 41 | // arg, parse it as an integer, and put it in *val. 42 | inline void maybeGetIntArg(const char* arg_name, const char* arg, int* val) { 43 | const int len = strlen(arg_name); 44 | if (strncmp(arg_name, arg, len) == 0) { 45 | *val = atoi(arg+len); 46 | } 47 | } 48 | 49 | // If the string "arg" starts with the string "arg_name", set *val to true. 50 | inline void maybeGetBoolArg(const char* arg_name, const char* arg, bool* val) { 51 | const int len = strlen(arg_name); 52 | if (strncmp(arg_name, arg, len) == 0) { 53 | *val = true; 54 | } 55 | } 56 | 57 | } // namespace 58 | 59 | int main(int argc, const char** argv) { 60 | argc--; 61 | argv++; 62 | 63 | int num_samples = -1; 64 | int base = -1; 65 | bool progressive = false; 66 | for (int i = 0; i < argc; i++) { 67 | maybeGetIntArg("--n=", argv[i], &num_samples); 68 | maybeGetIntArg("--b=", argv[i], &base); 69 | maybeGetIntArg("--base=", argv[i], &base); 70 | maybeGetBoolArg("--prog", argv[i], &progressive); 71 | maybeGetBoolArg("--progressive", argv[i], &progressive); 72 | } 73 | 74 | ASSERT(num_samples > 0, 75 | "--n must be set to a value greater than zero."); 76 | ASSERT(base > 0, 77 | "--b must be set to a value greater than zero."); 78 | std::vector shuffled_indices; 79 | if (progressive) { 80 | switch (base) { 81 | case 2: 82 | shuffled_indices = 83 | sampling::GetProgressiveShuffledIndices<2>(num_samples); 84 | break; 85 | case 3: 86 | shuffled_indices = 87 | sampling::GetProgressiveShuffledIndices<3>(num_samples); 88 | break; 89 | case 5: 90 | shuffled_indices = 91 | sampling::GetProgressiveShuffledIndices<5>(num_samples); 92 | break; 93 | case 7: 94 | shuffled_indices = 95 | sampling::GetProgressiveShuffledIndices<7>(num_samples); 96 | break; 97 | case 11: 98 | shuffled_indices = 99 | sampling::GetProgressiveShuffledIndices<11>(num_samples); 100 | break; 101 | default: 102 | ASSERT(false, "base " << base << " not supported. " 103 | "Must be 2, 3, 5, 7, or 11."); 104 | return 1; 105 | } 106 | } else { 107 | switch (base) { 108 | case 2: 109 | shuffled_indices = sampling::GetShuffledIndices<2>(num_samples); 110 | break; 111 | case 3: 112 | shuffled_indices = sampling::GetShuffledIndices<3>(num_samples); 113 | break; 114 | case 5: 115 | shuffled_indices = sampling::GetShuffledIndices<5>(num_samples); 116 | break; 117 | case 7: 118 | shuffled_indices = sampling::GetShuffledIndices<7>(num_samples); 119 | break; 120 | case 11: 121 | shuffled_indices = sampling::GetShuffledIndices<11>(num_samples); 122 | break; 123 | default: 124 | ASSERT(false, "base " << base << " not supported. " 125 | "Must be 2, 3, 5, 7, or 11."); 126 | return 1; 127 | } 128 | } 129 | 130 | for (int i = 0; i < num_samples; i++) { 131 | std::cout << shuffled_indices[i] << " "; 132 | } 133 | std::cout << std::endl; 134 | return 1; 135 | } 136 | -------------------------------------------------------------------------------- /xor_values.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Generates the Faure and pmj02 xor_values, used in xor_values.h. 4 | 5 | This works by constructing the generator matrices, and then "reversing" those 6 | matrices as explained in Section 4.4 of the paper. 7 | """ 8 | 9 | import numpy as np 10 | import scipy.linalg 11 | 12 | def get_xor_values(gen_matrix, base=2): 13 | """Compute xor-values from a non-singular upper triangular matrix. 14 | 15 | Listing 3 from the paper. 16 | """ 17 | # Invert the matrix. 18 | m_inv = np.linalg.inv(gen_matrix).astype(int) % base 19 | # Truncate the diagonal. 20 | m_inv -= np.identity(m_inv.shape[0], dtype=int) 21 | 22 | # Compute the xor-values from the negated columns. 23 | # For base 2, base_pow = (1,2,4,8,16...) 24 | base_pow = np.power(base, np.arange(0, gen_matrix.shape[0])) 25 | return [np.sum((-col % base)*base_pow) for col in m_inv.T] 26 | 27 | def print_hex(vals): 28 | """For an array of numbers, print them as hexadecimal values.""" 29 | array_elements = ", ".join([hex(val) for val in vals]) 30 | string = "{{{0}}}".format(array_elements) 31 | print(string) 32 | 33 | def get_stirling_matrix(size, base=2): 34 | """Construct a matrix of unsigned Stirling numbers of the first kind.""" 35 | m = np.identity(size).astype(int) 36 | for k in range(size): 37 | for n in range(k, size): 38 | if (n == 0) and (k == 0): m[k][n] = 1 39 | elif (k == 0): m[k][n] = ((n - 1) * m[k][n-1]) % base 40 | else: 41 | m[k][n] = (m[k-1][n-1] + (n - 1) * m[k][n-1]) % base 42 | return m 43 | 44 | def print_pmj02_xors(n): 45 | """Print pmj02 xor-values using Stirling numbers. 46 | 47 | See section 1.1 of the supplemental materials. 48 | """ 49 | s = get_stirling_matrix(n+1) 50 | xor_vals = get_xor_values(s[:n, :n], base=2) 51 | print_hex(xor_vals) 52 | # The second matrix is shifted up and to the left by one. 53 | xor_vals = get_xor_values(s[1:n+1, 1:n+1], base=2) 54 | print_hex(xor_vals) 55 | 56 | def print_faure_xors(base, n): 57 | """Print Faure sequences xor-values""" 58 | print("Base {0} Faure (0,{0}) xor-values: ".format(base)) 59 | m = np.identity(n) 60 | # Faure sequences are constructed from exponentiated Pascal matrices. 61 | pascal_matrix = scipy.linalg.pascal(n, kind='upper') % base 62 | 63 | for i in range(base): 64 | xor_vals = get_xor_values(m, base) 65 | print_hex(xor_vals) 66 | m = np.matmul(m, pascal_matrix) % base 67 | 68 | print_pmj02_xors(32) 69 | print_faure_xors(2, 32) 70 | print_faure_xors(3, 21) 71 | print_faure_xors(5, 14) 72 | print_faure_xors(7, 12) 73 | print_faure_xors(11, 10) --------------------------------------------------------------------------------