47 if (name ==
"sampling_policy")
49 if (setting !=
"reproducible_v1" && setting !=
"legacy")
50 throw std::invalid_argument(
"sampling_policy must be reproducible_v1 or legacy");
52 else if (name ==
"sampling_max_memory_mb")
55 const auto result = std::from_chars(setting.data(), setting.data() + setting.size(), parsed);
56 if (result.ec != std::errc{} || result.ptr != setting.data() + setting.size() || !parsed ||
57 parsed > std::numeric_limits<size_t>::max() / (
size_t{1} << 20))
58 throw std::invalid_argument(
"sampling_max_memory_mb must be a positive size");
143 const size_t block = BlockSize(count, shots, budget);
144 const size_t blocks = count / block + (count % block != 0);
145 constexpr size_t bytesPerBlock = 2 *
sizeof(double) + 1;
146 return std::max<size_t>(4096, blocks * bytesPerBlock +
sizeof(
double) + 1024);
149 Prepared(
size_t count,
size_t shots,
Probability probability,
Options options = {}) : count(count), probability(std::move(probability)), options(options)
152 throw std::domain_error(
"Sampling requires nonempty probability support");
154 throw std::invalid_argument(
"Sampling scratch budget is too small");
156 blockSize = BlockSize(count, shots, options.
memoryBytes);
157 const size_t blocks = count / blockSize + (count % blockSize != 0);
158 if (blocks > (options.
memoryBytes -
sizeof(
double)) / (2 *
sizeof(
double) + 1) || blocks >
static_cast<size_t>(std::numeric_limits<int64_t>::max()))
159 throw std::length_error(
"Sampling probability index exceeds the scratch budget");
160 prefix.resize(blocks + 1, 0.);
161 masses.resize(blocks, 0.);
162 std::vector<unsigned char> invalid(blocks, 0);
166#pragma omp parallel for if (threads > 1) num_threads(threads) schedule(static) reduction(+ : nonzero)
168 for (int64_t b = 0; b < static_cast<int64_t>(blocks); ++b)
172 const size_t begin = size_t(b) * blockSize, end = std::min(count, begin + blockSize);
173 for (
size_t i = begin; i < end; ++i)
175 const double p = this->probability(i);
176 if (!std::isfinite(p) || p < 0.)
184 if (std::find(invalid.begin(), invalid.end(), 1) != invalid.end())
185 throw std::domain_error(
"Sampling probabilities must be finite and nonnegative");
186 for (
size_t b = 0; b < blocks; ++b)
187 prefix[b + 1] = prefix[b] + masses[b];
188 mass = prefix.back();
190 std::vector<unsigned char>().swap(invalid);
191 aliasCount = nonzero <= count / 4 ? nonzero : count;
194 const size_t available = options.
memoryBytes - IndexBytes();
195 const bool fitsAlias = available >= 1024 && aliasCount <= (available - 1024) / AliasRowBytes();
196 const bool useAlias =
201 throw std::length_error(
"Alias table exceeds the sampling scratch budget");
202 alias = std::make_unique<ProbabilityAlias>(aliasCount, count, mass, this->probability, blockSize, masses);
204 else if (count <= 65536 && available >= 1024 && count <= (available - 1024) /
sizeof(
double))
206 localCdf.resize(count);
207 for (
size_t b = 0; b < blocks; ++b)
210 const size_t begin = b * blockSize, end = std::min(count, begin + blockSize);
211 for (
size_t i = begin; i < end; ++i)
212 localCdf[i] = sum += this->probability(i);
224 return alias ? aliasCount : count;
229 return alias ? alias->SourceRow(category) : category;
234 return alias ? alias->Sample(uniform) :
Sample(uniform);
249 const size_t tables = IndexBytes() + localCdf.size() *
sizeof(double) + (alias ? aliasCount * AliasRowBytes() : 0);
250 return options.memoryBytes - tables;
256 return alias->SourceRow(alias->Sample(uniform));
257 const double target = Target(uniform);
258 const size_t b = Block(target);
259 const double local = LocalTarget(target, b);
260 const size_t begin = b * blockSize, end = std::min(count, begin + blockSize);
261 if (!localCdf.empty())
263 const auto it = std::upper_bound(localCdf.begin() + begin, localCdf.begin() + end, local);
264 if (it != localCdf.begin() + end)
265 return static_cast<size_t>(it - localCdf.begin());
268 size_t lastPositive = begin;
269 for (
size_t i = begin; i < end; ++i)
271 const double p = probability(i);
282 void Draw(
const std::vector<double> &uniforms, std::vector<size_t> &out,
int threads)
const
284 out.resize(uniforms.size());
285 if (threads == 1 && !alias && count > 65536 && uniforms.size() >= 128)
287 std::vector<std::pair<double, size_t>> ordered;
288 ordered.reserve(uniforms.size());
289 for (
size_t i = 0; i < uniforms.size(); ++i)
290 ordered.emplace_back(Target(uniforms[i]), i);
291 std::sort(ordered.begin(), ordered.end());
292 size_t previousBlock = std::numeric_limits<size_t>::max(), row = 0, end = 0, lastPositive = 0;
294 for (
const auto &[target, shot] : ordered)
296 const size_t b = Block(target);
297 if (b != previousBlock)
301 end = std::min(count, row + blockSize);
302 sum = probability(row);
305 const double local = LocalTarget(target, b);
306 while (sum <= local && row + 1 < end)
308 const double p = probability(++row);
313 out[shot] = sum > local ? row : lastPositive;
318#pragma omp parallel for if (threads > 1) num_threads(threads) schedule(static)
320 for (int64_t i = 0; i < static_cast<int64_t>(uniforms.size()); ++i)
321 out[i] =
Sample(uniforms[i]);
325 static size_t BlockSize(
size_t count,
size_t shots,
size_t budget)
328 throw std::invalid_argument(
"Sampling scratch budget is too small");
332 const size_t maxBlocks = (budget - 1024 -
sizeof(double)) / (2 *
sizeof(
double) + 1);
333 size_t block = shots >= count / 16 && shots >= 65536 ? 64 : 256;
334 while (count / block + (count % block != 0) > maxBlocks)
339 size_t AliasRowBytes()
const
341 return sizeof(double) + (aliasCount - 1 <= std::numeric_limits<uint32_t>::max() ?
sizeof(uint32_t) :
sizeof(uint64_t)) +
342 (aliasCount != count ?
sizeof(size_t) : 0);
345 size_t IndexBytes()
const
347 return (prefix.size() + masses.size()) *
sizeof(double);
350 double Target(
double uniform)
const
352 return std::min(uniform * mass, std::nextafter(mass, 0.));
355 size_t Block(
double target)
const
357 return static_cast<size_t>(std::upper_bound(prefix.begin() + 1, prefix.end(), target) - prefix.begin() - 1);
360 double LocalTarget(
double target,
size_t block)
const
362 return std::min(target - prefix[block], std::nextafter(masses[block], 0.));
365 size_t count, blockSize = 256, aliasCount = 0;
369 std::vector<double> prefix, masses, localCdf;
370 std::unique_ptr<ProbabilityAlias> alias;
413 if (options.memoryBytes < 4096 || !options.batchSize)
414 throw std::invalid_argument(
"Sampling scratch budget is too small");
416 for (
const auto &row : rows)
418 const double p = probability(row);
419 if (!std::isfinite(p) || p < 0.)
420 throw std::domain_error(
"Sampling probabilities must be finite and nonnegative");
424 const size_t batch = std::min({shots, options.batchSize, options.memoryBytes /
sizeof(double)});
425 std::vector<double> targets;
426 targets.reserve(batch);
427 std::vector<bool> key;
430 for (
size_t begin = 0; begin < shots;)
432 const size_t count = std::min(batch, shots - begin);
433 targets.resize(count);
434 for (
double &target : targets)
435 target = std::min(uniform() * mass, std::nextafter(mass, 0.));
436 std::sort(targets.begin(), targets.end());
438 double cumulative = 0.;
439 for (
const auto &row : rows)
441 cumulative += probability(row);
442 const size_t first = sampled;
443 while (sampled < count && targets[sampled] < cumulative)
445 if (sampled != first)
449 for (
size_t i = 0; i < bits; ++i)
450 key[i] = bit(row, i);
451 result[key] += sampled - first;
456 for (
size_t i = 0; i < bits; ++i)
459 result[packed] += sampled - first;
462 if (sampled == count)
472template <
bool Many,
class Plan,
class Bit,
class Uniform>
Counts<Many> Count(
const Plan &plan,
size_t shots,
size_t bits,
const Bit &bit, Uniform &&uniform)
477 const auto &options = plan.Settings();
478 const size_t count = plan.Categories();
479 const size_t available = plan.ScratchAvailable();
482 constexpr size_t padding = 128 /
sizeof(size_t);
483 const size_t stride = count <= (
size_t{1} << 20) ? ((count + padding - 1) / padding + 1) * padding : 0;
484 const bool histogram = stride && shots >= std::max<size_t>(1024, count / 4) && stride <= available / (2 *
sizeof(size_t));
485 int threads =
Threads(options.multithreading, shots);
491 threads =
static_cast<int>(std::min({size_t(threads), available / (2 *
sizeof(size_t) * stride), std::max<size_t>(1, shots / count)}));
492 std::vector<bool> key;
495 auto insert = [&](
size_t row,
size_t amount) {
498 for (
size_t i = 0; i < bits; ++i)
499 key[i] = bit(row, i);
500 result[key] += amount;
505 for (
size_t i = 0; i < bits; ++i)
508 result[packed] += amount;
512 std::vector<size_t> bins(histogram ? stride * threads : 0, 0);
513 if (threads == 1 && (histogram || plan.Method() ==
Family::Alias || shots < 128 || count <= 65536))
515 for (
size_t shot = 0; shot < shots; ++shot)
518 ++bins[plan.SampleCategory(uniform())];
520 insert(plan.Sample(uniform()), 1);
525 const size_t scratch = available - bins.size() *
sizeof(size_t);
526 const size_t batch = std::max<size_t>(1, std::min({shots, options.batchSize, scratch / 32}));
527 std::vector<double> uniforms;
528 std::vector<size_t> sampled;
529 for (
size_t begin = 0; begin < shots;)
531 const size_t size = std::min(batch, shots - begin);
532 uniforms.resize(size);
533 for (
double &value : uniforms)
538#pragma omp parallel if (threads > 1) num_threads(threads)
543 worker =
static_cast<size_t>(omp_get_thread_num());
545 size_t *local = bins.data() + worker * stride;
547#pragma omp for schedule(static)
549 for (int64_t i = 0; i < static_cast<int64_t>(size); ++i)
550 ++local[plan.SampleCategory(uniforms[i])];
555 plan.Draw(uniforms, sampled, threads);
556 for (
size_t row : sampled)
563 for (
size_t row = 0; row < count; ++row)
565 size_t amount = bins[row];
566 for (
int worker = 1; worker < threads; ++worker)
567 amount += bins[
size_t(worker) * stride + row];
569 insert(plan.SourceRow(row), amount);