This builds an alias table via the O(N) algorithm from Vose 1991, "A linear algorithm for generating random numbers with a given distribution," IEEE Transactions on Software Engineering 17(9), 972-975. Basic idea: creating each alias table entry combines one overweighted sample and one underweighted sample into one alias table entry plus a residual sample (the overweighted sample minus some of i
| 46 | // have 2 valid entries to combine. By definition, in these corner cases, all remaining unhandled samples |
| 47 | // actually have the average weight (within numerical precision limits) |
| 48 | AliasTable::AliasTable(ref<Device> pDevice, std::vector<float> weights, std::mt19937& rng) : mCount((uint32_t)weights.size()) |
| 49 | { |
| 50 | // Use >= since we reserve 0xFFFFFFFFu as an invalid flag marker during construction. |
| 51 | if (weights.size() >= std::numeric_limits<uint32_t>::max()) |
| 52 | FALCOR_THROW("Too many entries for alias table."); |
| 53 | |
| 54 | std::uniform_int_distribution<uint32_t> rngDist; |
| 55 | |
| 56 | mpWeights = |
| 57 | pDevice->createStructuredBuffer(sizeof(float), mCount, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, weights.data()); |
| 58 | |
| 59 | // Our working set / intermediate buffers (underweight & overweight); initialize to "invalid" |
| 60 | std::vector<uint32_t> lowIdx(mCount, 0xFFFFFFFFu); |
| 61 | std::vector<uint32_t> highIdx(mCount, 0xFFFFFFFFu); |
| 62 | |
| 63 | // Sum element weights, use double to minimize precision issues |
| 64 | mWeightSum = 0.0; |
| 65 | for (float f : weights) |
| 66 | mWeightSum += f; |
| 67 | |
| 68 | // Find the average weight |
| 69 | float avgWeight = float(mWeightSum / double(mCount)); |
| 70 | |
| 71 | // Initialize working set. Inset inputs into our lists of above-average or below-average weight elements. |
| 72 | int lowCount = 0; |
| 73 | int highCount = 0; |
| 74 | for (uint32_t i = 0; i < mCount; ++i) |
| 75 | { |
| 76 | if (weights[i] < avgWeight) |
| 77 | lowIdx[lowCount++] = i; |
| 78 | else |
| 79 | highIdx[highCount++] = i; |
| 80 | } |
| 81 | |
| 82 | // Create alias table entries by merging above- and below-average samples |
| 83 | std::vector<AliasTable::Item> items(mCount); |
| 84 | for (uint32_t i = 0; i < mCount; ++i) |
| 85 | { |
| 86 | // Usual case: We have an above-average and below-average sample we can combine into one alias table entry |
| 87 | if ((lowIdx[i] != 0xFFFFFFFFu) && (highIdx[i] != 0xFFFFFFFFu)) |
| 88 | { |
| 89 | // Create an alias table tuple: |
| 90 | items[i] = {weights[lowIdx[i]] / avgWeight, highIdx[i], lowIdx[i], 0}; |
| 91 | |
| 92 | // We've removed some weight from element highIdx[i]; update it's weight, then re-enter it |
| 93 | // on the end of either the above-average or below-average lists. |
| 94 | float updatedWeight = (weights[lowIdx[i]] + weights[highIdx[i]]) - avgWeight; |
| 95 | weights[highIdx[i]] = updatedWeight; |
| 96 | if (updatedWeight < avgWeight) |
| 97 | lowIdx[lowCount++] = highIdx[i]; |
| 98 | else |
| 99 | highIdx[highCount++] = highIdx[i]; |
| 100 | } |
| 101 | |
| 102 | // The next two cases can only occur towards the end of table creation, because either: |
| 103 | // (a) all the remaining possible alias table entries have weight *exactly* equal to avgWeight, |
| 104 | // which means these alias table entries only have one input item that is selected |
| 105 | // with 100% probability |
nothing calls this directly
no test coverage detected