Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame] | 1 | // Copyright 2017 The Abseil Authors. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | #include "absl/random/beta_distribution.h" |
| 16 | |
| 17 | #include <algorithm> |
| 18 | #include <cstddef> |
| 19 | #include <cstdint> |
| 20 | #include <iterator> |
| 21 | #include <random> |
| 22 | #include <sstream> |
| 23 | #include <string> |
| 24 | #include <unordered_map> |
| 25 | #include <vector> |
| 26 | |
| 27 | #include "gmock/gmock.h" |
| 28 | #include "gtest/gtest.h" |
| 29 | #include "absl/base/internal/raw_logging.h" |
| 30 | #include "absl/random/internal/chi_square.h" |
| 31 | #include "absl/random/internal/distribution_test_util.h" |
Austin Schuh | b4691e9 | 2020-12-31 12:37:18 -0800 | [diff] [blame^] | 32 | #include "absl/random/internal/pcg_engine.h" |
Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame] | 33 | #include "absl/random/internal/sequence_urbg.h" |
| 34 | #include "absl/random/random.h" |
| 35 | #include "absl/strings/str_cat.h" |
| 36 | #include "absl/strings/str_format.h" |
| 37 | #include "absl/strings/str_replace.h" |
| 38 | #include "absl/strings/strip.h" |
| 39 | |
| 40 | namespace { |
| 41 | |
| 42 | template <typename IntType> |
| 43 | class BetaDistributionInterfaceTest : public ::testing::Test {}; |
| 44 | |
| 45 | using RealTypes = ::testing::Types<float, double, long double>; |
| 46 | TYPED_TEST_CASE(BetaDistributionInterfaceTest, RealTypes); |
| 47 | |
| 48 | TYPED_TEST(BetaDistributionInterfaceTest, SerializeTest) { |
| 49 | // The threshold for whether std::exp(1/a) is finite. |
| 50 | const TypeParam kSmallA = |
| 51 | 1.0f / std::log((std::numeric_limits<TypeParam>::max)()); |
| 52 | // The threshold for whether a * std::log(a) is finite. |
| 53 | const TypeParam kLargeA = |
| 54 | std::exp(std::log((std::numeric_limits<TypeParam>::max)()) - |
| 55 | std::log(std::log((std::numeric_limits<TypeParam>::max)()))); |
| 56 | const TypeParam kLargeAPPC = std::exp( |
| 57 | std::log((std::numeric_limits<TypeParam>::max)()) - |
| 58 | std::log(std::log((std::numeric_limits<TypeParam>::max)())) - 10.0f); |
| 59 | using param_type = typename absl::beta_distribution<TypeParam>::param_type; |
| 60 | |
| 61 | constexpr int kCount = 1000; |
| 62 | absl::InsecureBitGen gen; |
| 63 | const TypeParam kValues[] = { |
| 64 | TypeParam(1e-20), TypeParam(1e-12), TypeParam(1e-8), TypeParam(1e-4), |
| 65 | TypeParam(1e-3), TypeParam(0.1), TypeParam(0.25), |
| 66 | std::nextafter(TypeParam(0.5), TypeParam(0)), // 0.5 - epsilon |
| 67 | std::nextafter(TypeParam(0.5), TypeParam(1)), // 0.5 + epsilon |
| 68 | TypeParam(0.5), TypeParam(1.0), // |
| 69 | std::nextafter(TypeParam(1), TypeParam(0)), // 1 - epsilon |
| 70 | std::nextafter(TypeParam(1), TypeParam(2)), // 1 + epsilon |
| 71 | TypeParam(12.5), TypeParam(1e2), TypeParam(1e8), TypeParam(1e12), |
| 72 | TypeParam(1e20), // |
| 73 | kSmallA, // |
| 74 | std::nextafter(kSmallA, TypeParam(0)), // |
| 75 | std::nextafter(kSmallA, TypeParam(1)), // |
| 76 | kLargeA, // |
| 77 | std::nextafter(kLargeA, TypeParam(0)), // |
| 78 | std::nextafter(kLargeA, std::numeric_limits<TypeParam>::max()), |
| 79 | kLargeAPPC, // |
| 80 | std::nextafter(kLargeAPPC, TypeParam(0)), |
| 81 | std::nextafter(kLargeAPPC, std::numeric_limits<TypeParam>::max()), |
| 82 | // Boundary cases. |
| 83 | std::numeric_limits<TypeParam>::max(), |
| 84 | std::numeric_limits<TypeParam>::epsilon(), |
| 85 | std::nextafter(std::numeric_limits<TypeParam>::min(), |
| 86 | TypeParam(1)), // min + epsilon |
| 87 | std::numeric_limits<TypeParam>::min(), // smallest normal |
| 88 | std::numeric_limits<TypeParam>::denorm_min(), // smallest denorm |
| 89 | std::numeric_limits<TypeParam>::min() / 2, // denorm |
| 90 | std::nextafter(std::numeric_limits<TypeParam>::min(), |
| 91 | TypeParam(0)), // denorm_max |
| 92 | }; |
| 93 | for (TypeParam alpha : kValues) { |
| 94 | for (TypeParam beta : kValues) { |
| 95 | ABSL_INTERNAL_LOG( |
Austin Schuh | b4691e9 | 2020-12-31 12:37:18 -0800 | [diff] [blame^] | 96 | INFO, absl::StrFormat("Smoke test for Beta(%a, %a)", alpha, beta)); |
Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame] | 97 | |
| 98 | param_type param(alpha, beta); |
| 99 | absl::beta_distribution<TypeParam> before(alpha, beta); |
| 100 | EXPECT_EQ(before.alpha(), param.alpha()); |
| 101 | EXPECT_EQ(before.beta(), param.beta()); |
| 102 | |
| 103 | { |
| 104 | absl::beta_distribution<TypeParam> via_param(param); |
| 105 | EXPECT_EQ(via_param, before); |
| 106 | EXPECT_EQ(via_param.param(), before.param()); |
| 107 | } |
| 108 | |
| 109 | // Smoke test. |
| 110 | for (int i = 0; i < kCount; ++i) { |
| 111 | auto sample = before(gen); |
| 112 | EXPECT_TRUE(std::isfinite(sample)); |
| 113 | EXPECT_GE(sample, before.min()); |
| 114 | EXPECT_LE(sample, before.max()); |
| 115 | } |
| 116 | |
| 117 | // Validate stream serialization. |
| 118 | std::stringstream ss; |
| 119 | ss << before; |
| 120 | absl::beta_distribution<TypeParam> after(3.8f, 1.43f); |
| 121 | EXPECT_NE(before.alpha(), after.alpha()); |
| 122 | EXPECT_NE(before.beta(), after.beta()); |
| 123 | EXPECT_NE(before.param(), after.param()); |
| 124 | EXPECT_NE(before, after); |
| 125 | |
| 126 | ss >> after; |
| 127 | |
| 128 | #if defined(__powerpc64__) || defined(__PPC64__) || defined(__powerpc__) || \ |
| 129 | defined(__ppc__) || defined(__PPC__) |
| 130 | if (std::is_same<TypeParam, long double>::value) { |
| 131 | // Roundtripping floating point values requires sufficient precision |
| 132 | // to reconstruct the exact value. It turns out that long double |
| 133 | // has some errors doing this on ppc. |
| 134 | if (alpha <= std::numeric_limits<double>::max() && |
| 135 | alpha >= std::numeric_limits<double>::lowest()) { |
| 136 | EXPECT_EQ(static_cast<double>(before.alpha()), |
| 137 | static_cast<double>(after.alpha())) |
| 138 | << ss.str(); |
| 139 | } |
| 140 | if (beta <= std::numeric_limits<double>::max() && |
| 141 | beta >= std::numeric_limits<double>::lowest()) { |
| 142 | EXPECT_EQ(static_cast<double>(before.beta()), |
| 143 | static_cast<double>(after.beta())) |
| 144 | << ss.str(); |
| 145 | } |
| 146 | continue; |
| 147 | } |
| 148 | #endif |
| 149 | |
| 150 | EXPECT_EQ(before.alpha(), after.alpha()); |
| 151 | EXPECT_EQ(before.beta(), after.beta()); |
| 152 | EXPECT_EQ(before, after) // |
| 153 | << ss.str() << " " // |
| 154 | << (ss.good() ? "good " : "") // |
| 155 | << (ss.bad() ? "bad " : "") // |
| 156 | << (ss.eof() ? "eof " : "") // |
| 157 | << (ss.fail() ? "fail " : ""); |
| 158 | } |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | TYPED_TEST(BetaDistributionInterfaceTest, DegenerateCases) { |
Austin Schuh | b4691e9 | 2020-12-31 12:37:18 -0800 | [diff] [blame^] | 163 | // We use a fixed bit generator for distribution accuracy tests. This allows |
| 164 | // these tests to be deterministic, while still testing the qualify of the |
| 165 | // implementation. |
| 166 | absl::random_internal::pcg64_2018_engine rng(0x2B7E151628AED2A6); |
| 167 | |
Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame] | 168 | // Extreme cases when the params are abnormal. |
Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame] | 169 | constexpr int kCount = 1000; |
| 170 | const TypeParam kSmallValues[] = { |
| 171 | std::numeric_limits<TypeParam>::min(), |
| 172 | std::numeric_limits<TypeParam>::denorm_min(), |
| 173 | std::nextafter(std::numeric_limits<TypeParam>::min(), |
| 174 | TypeParam(0)), // denorm_max |
| 175 | std::numeric_limits<TypeParam>::epsilon(), |
| 176 | }; |
| 177 | const TypeParam kLargeValues[] = { |
| 178 | std::numeric_limits<TypeParam>::max() * static_cast<TypeParam>(0.9999), |
| 179 | std::numeric_limits<TypeParam>::max() - 1, |
| 180 | std::numeric_limits<TypeParam>::max(), |
| 181 | }; |
| 182 | { |
| 183 | // Small alpha and beta. |
| 184 | // Useful WolframAlpha plots: |
| 185 | // * plot InverseBetaRegularized[x, 0.0001, 0.0001] from 0.495 to 0.505 |
| 186 | // * Beta[1.0, 0.0000001, 0.0000001] |
| 187 | // * Beta[0.9999, 0.0000001, 0.0000001] |
| 188 | for (TypeParam alpha : kSmallValues) { |
| 189 | for (TypeParam beta : kSmallValues) { |
| 190 | int zeros = 0; |
| 191 | int ones = 0; |
| 192 | absl::beta_distribution<TypeParam> d(alpha, beta); |
| 193 | for (int i = 0; i < kCount; ++i) { |
Austin Schuh | b4691e9 | 2020-12-31 12:37:18 -0800 | [diff] [blame^] | 194 | TypeParam x = d(rng); |
Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame] | 195 | if (x == 0.0) { |
| 196 | zeros++; |
| 197 | } else if (x == 1.0) { |
| 198 | ones++; |
| 199 | } |
| 200 | } |
| 201 | EXPECT_EQ(ones + zeros, kCount); |
| 202 | if (alpha == beta) { |
| 203 | EXPECT_NE(ones, 0); |
| 204 | EXPECT_NE(zeros, 0); |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | { |
| 210 | // Small alpha, large beta. |
| 211 | // Useful WolframAlpha plots: |
| 212 | // * plot InverseBetaRegularized[x, 0.0001, 10000] from 0.995 to 1 |
| 213 | // * Beta[0, 0.0000001, 1000000] |
| 214 | // * Beta[0.001, 0.0000001, 1000000] |
| 215 | // * Beta[1, 0.0000001, 1000000] |
| 216 | for (TypeParam alpha : kSmallValues) { |
| 217 | for (TypeParam beta : kLargeValues) { |
| 218 | absl::beta_distribution<TypeParam> d(alpha, beta); |
| 219 | for (int i = 0; i < kCount; ++i) { |
Austin Schuh | b4691e9 | 2020-12-31 12:37:18 -0800 | [diff] [blame^] | 220 | EXPECT_EQ(d(rng), 0.0); |
Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame] | 221 | } |
| 222 | } |
| 223 | } |
| 224 | } |
| 225 | { |
| 226 | // Large alpha, small beta. |
| 227 | // Useful WolframAlpha plots: |
| 228 | // * plot InverseBetaRegularized[x, 10000, 0.0001] from 0 to 0.001 |
| 229 | // * Beta[0.99, 1000000, 0.0000001] |
| 230 | // * Beta[1, 1000000, 0.0000001] |
| 231 | for (TypeParam alpha : kLargeValues) { |
| 232 | for (TypeParam beta : kSmallValues) { |
| 233 | absl::beta_distribution<TypeParam> d(alpha, beta); |
| 234 | for (int i = 0; i < kCount; ++i) { |
Austin Schuh | b4691e9 | 2020-12-31 12:37:18 -0800 | [diff] [blame^] | 235 | EXPECT_EQ(d(rng), 1.0); |
Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame] | 236 | } |
| 237 | } |
| 238 | } |
| 239 | } |
| 240 | { |
| 241 | // Large alpha and beta. |
| 242 | absl::beta_distribution<TypeParam> d(std::numeric_limits<TypeParam>::max(), |
| 243 | std::numeric_limits<TypeParam>::max()); |
| 244 | for (int i = 0; i < kCount; ++i) { |
Austin Schuh | b4691e9 | 2020-12-31 12:37:18 -0800 | [diff] [blame^] | 245 | EXPECT_EQ(d(rng), 0.5); |
Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame] | 246 | } |
| 247 | } |
| 248 | { |
| 249 | // Large alpha and beta but unequal. |
| 250 | absl::beta_distribution<TypeParam> d( |
| 251 | std::numeric_limits<TypeParam>::max(), |
| 252 | std::numeric_limits<TypeParam>::max() * 0.9999); |
| 253 | for (int i = 0; i < kCount; ++i) { |
Austin Schuh | b4691e9 | 2020-12-31 12:37:18 -0800 | [diff] [blame^] | 254 | TypeParam x = d(rng); |
Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame] | 255 | EXPECT_NE(x, 0.5f); |
| 256 | EXPECT_FLOAT_EQ(x, 0.500025f); |
| 257 | } |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | class BetaDistributionModel { |
| 262 | public: |
| 263 | explicit BetaDistributionModel(::testing::tuple<double, double> p) |
| 264 | : alpha_(::testing::get<0>(p)), beta_(::testing::get<1>(p)) {} |
| 265 | |
| 266 | double Mean() const { return alpha_ / (alpha_ + beta_); } |
| 267 | |
| 268 | double Variance() const { |
| 269 | return alpha_ * beta_ / (alpha_ + beta_ + 1) / (alpha_ + beta_) / |
| 270 | (alpha_ + beta_); |
| 271 | } |
| 272 | |
| 273 | double Kurtosis() const { |
| 274 | return 3 + 6 * |
| 275 | ((alpha_ - beta_) * (alpha_ - beta_) * (alpha_ + beta_ + 1) - |
| 276 | alpha_ * beta_ * (2 + alpha_ + beta_)) / |
| 277 | alpha_ / beta_ / (alpha_ + beta_ + 2) / (alpha_ + beta_ + 3); |
| 278 | } |
| 279 | |
| 280 | protected: |
| 281 | const double alpha_; |
| 282 | const double beta_; |
| 283 | }; |
| 284 | |
| 285 | class BetaDistributionTest |
| 286 | : public ::testing::TestWithParam<::testing::tuple<double, double>>, |
| 287 | public BetaDistributionModel { |
| 288 | public: |
| 289 | BetaDistributionTest() : BetaDistributionModel(GetParam()) {} |
| 290 | |
| 291 | protected: |
| 292 | template <class D> |
| 293 | bool SingleZTestOnMeanAndVariance(double p, size_t samples); |
| 294 | |
| 295 | template <class D> |
| 296 | bool SingleChiSquaredTest(double p, size_t samples, size_t buckets); |
| 297 | |
| 298 | absl::InsecureBitGen rng_; |
| 299 | }; |
| 300 | |
| 301 | template <class D> |
| 302 | bool BetaDistributionTest::SingleZTestOnMeanAndVariance(double p, |
| 303 | size_t samples) { |
| 304 | D dis(alpha_, beta_); |
| 305 | |
| 306 | std::vector<double> data; |
| 307 | data.reserve(samples); |
| 308 | for (size_t i = 0; i < samples; i++) { |
| 309 | const double variate = dis(rng_); |
| 310 | EXPECT_FALSE(std::isnan(variate)); |
| 311 | // Note that equality is allowed on both sides. |
| 312 | EXPECT_GE(variate, 0.0); |
| 313 | EXPECT_LE(variate, 1.0); |
| 314 | data.push_back(variate); |
| 315 | } |
| 316 | |
| 317 | // We validate that the sample mean and sample variance are indeed from a |
| 318 | // Beta distribution with the given shape parameters. |
| 319 | const auto m = absl::random_internal::ComputeDistributionMoments(data); |
| 320 | |
| 321 | // The variance of the sample mean is variance / n. |
| 322 | const double mean_stddev = std::sqrt(Variance() / static_cast<double>(m.n)); |
| 323 | |
| 324 | // The variance of the sample variance is (approximately): |
| 325 | // (kurtosis - 1) * variance^2 / n |
| 326 | const double variance_stddev = std::sqrt( |
| 327 | (Kurtosis() - 1) * Variance() * Variance() / static_cast<double>(m.n)); |
| 328 | // z score for the sample variance. |
| 329 | const double z_variance = (m.variance - Variance()) / variance_stddev; |
| 330 | |
| 331 | const double max_err = absl::random_internal::MaxErrorTolerance(p); |
| 332 | const double z_mean = absl::random_internal::ZScore(Mean(), m); |
| 333 | const bool pass = |
| 334 | absl::random_internal::Near("z", z_mean, 0.0, max_err) && |
| 335 | absl::random_internal::Near("z_variance", z_variance, 0.0, max_err); |
| 336 | if (!pass) { |
| 337 | ABSL_INTERNAL_LOG( |
| 338 | INFO, |
| 339 | absl::StrFormat( |
| 340 | "Beta(%f, %f), " |
| 341 | "mean: sample %f, expect %f, which is %f stddevs away, " |
| 342 | "variance: sample %f, expect %f, which is %f stddevs away.", |
| 343 | alpha_, beta_, m.mean, Mean(), |
| 344 | std::abs(m.mean - Mean()) / mean_stddev, m.variance, Variance(), |
| 345 | std::abs(m.variance - Variance()) / variance_stddev)); |
| 346 | } |
| 347 | return pass; |
| 348 | } |
| 349 | |
| 350 | template <class D> |
| 351 | bool BetaDistributionTest::SingleChiSquaredTest(double p, size_t samples, |
| 352 | size_t buckets) { |
| 353 | constexpr double kErr = 1e-7; |
| 354 | std::vector<double> cutoffs, expected; |
| 355 | const double bucket_width = 1.0 / static_cast<double>(buckets); |
| 356 | int i = 1; |
| 357 | int unmerged_buckets = 0; |
| 358 | for (; i < buckets; ++i) { |
| 359 | const double p = bucket_width * static_cast<double>(i); |
| 360 | const double boundary = |
| 361 | absl::random_internal::BetaIncompleteInv(alpha_, beta_, p); |
| 362 | // The intention is to add `boundary` to the list of `cutoffs`. It becomes |
| 363 | // problematic, however, when the boundary values are not monotone, due to |
| 364 | // numerical issues when computing the inverse regularized incomplete |
| 365 | // Beta function. In these cases, we merge that bucket with its previous |
| 366 | // neighbor and merge their expected counts. |
| 367 | if ((cutoffs.empty() && boundary < kErr) || |
| 368 | (!cutoffs.empty() && boundary <= cutoffs.back())) { |
| 369 | unmerged_buckets++; |
| 370 | continue; |
| 371 | } |
| 372 | if (boundary >= 1.0 - 1e-10) { |
| 373 | break; |
| 374 | } |
| 375 | cutoffs.push_back(boundary); |
| 376 | expected.push_back(static_cast<double>(1 + unmerged_buckets) * |
| 377 | bucket_width * static_cast<double>(samples)); |
| 378 | unmerged_buckets = 0; |
| 379 | } |
| 380 | cutoffs.push_back(std::numeric_limits<double>::infinity()); |
| 381 | // Merge all remaining buckets. |
| 382 | expected.push_back(static_cast<double>(buckets - i + 1) * bucket_width * |
| 383 | static_cast<double>(samples)); |
| 384 | // Make sure that we don't merge all the buckets, making this test |
| 385 | // meaningless. |
| 386 | EXPECT_GE(cutoffs.size(), 3) << alpha_ << ", " << beta_; |
| 387 | |
| 388 | D dis(alpha_, beta_); |
| 389 | |
| 390 | std::vector<int32_t> counts(cutoffs.size(), 0); |
| 391 | for (int i = 0; i < samples; i++) { |
| 392 | const double x = dis(rng_); |
| 393 | auto it = std::upper_bound(cutoffs.begin(), cutoffs.end(), x); |
| 394 | counts[std::distance(cutoffs.begin(), it)]++; |
| 395 | } |
| 396 | |
| 397 | // Null-hypothesis is that the distribution is beta distributed with the |
| 398 | // provided alpha, beta params (not estimated from the data). |
| 399 | const int dof = cutoffs.size() - 1; |
| 400 | |
| 401 | const double chi_square = absl::random_internal::ChiSquare( |
| 402 | counts.begin(), counts.end(), expected.begin(), expected.end()); |
| 403 | const bool pass = |
| 404 | (absl::random_internal::ChiSquarePValue(chi_square, dof) >= p); |
| 405 | if (!pass) { |
| 406 | for (int i = 0; i < cutoffs.size(); i++) { |
| 407 | ABSL_INTERNAL_LOG( |
| 408 | INFO, absl::StrFormat("cutoff[%d] = %f, actual count %d, expected %d", |
| 409 | i, cutoffs[i], counts[i], |
| 410 | static_cast<int>(expected[i]))); |
| 411 | } |
| 412 | |
| 413 | ABSL_INTERNAL_LOG( |
| 414 | INFO, absl::StrFormat( |
| 415 | "Beta(%f, %f) %s %f, p = %f", alpha_, beta_, |
| 416 | absl::random_internal::kChiSquared, chi_square, |
| 417 | absl::random_internal::ChiSquarePValue(chi_square, dof))); |
| 418 | } |
| 419 | return pass; |
| 420 | } |
| 421 | |
| 422 | TEST_P(BetaDistributionTest, TestSampleStatistics) { |
| 423 | static constexpr int kRuns = 20; |
| 424 | static constexpr double kPFail = 0.02; |
| 425 | const double p = |
| 426 | absl::random_internal::RequiredSuccessProbability(kPFail, kRuns); |
| 427 | static constexpr int kSampleCount = 10000; |
| 428 | static constexpr int kBucketCount = 100; |
| 429 | int failed = 0; |
| 430 | for (int i = 0; i < kRuns; ++i) { |
| 431 | if (!SingleZTestOnMeanAndVariance<absl::beta_distribution<double>>( |
| 432 | p, kSampleCount)) { |
| 433 | failed++; |
| 434 | } |
| 435 | if (!SingleChiSquaredTest<absl::beta_distribution<double>>( |
| 436 | 0.005, kSampleCount, kBucketCount)) { |
| 437 | failed++; |
| 438 | } |
| 439 | } |
| 440 | // Set so that the test is not flaky at --runs_per_test=10000 |
| 441 | EXPECT_LE(failed, 5); |
| 442 | } |
| 443 | |
| 444 | std::string ParamName( |
| 445 | const ::testing::TestParamInfo<::testing::tuple<double, double>>& info) { |
| 446 | std::string name = absl::StrCat("alpha_", ::testing::get<0>(info.param), |
| 447 | "__beta_", ::testing::get<1>(info.param)); |
| 448 | return absl::StrReplaceAll(name, {{"+", "_"}, {"-", "_"}, {".", "_"}}); |
| 449 | } |
| 450 | |
| 451 | INSTANTIATE_TEST_CASE_P( |
| 452 | TestSampleStatisticsCombinations, BetaDistributionTest, |
| 453 | ::testing::Combine(::testing::Values(0.1, 0.2, 0.9, 1.1, 2.5, 10.0, 123.4), |
| 454 | ::testing::Values(0.1, 0.2, 0.9, 1.1, 2.5, 10.0, 123.4)), |
| 455 | ParamName); |
| 456 | |
| 457 | INSTANTIATE_TEST_CASE_P( |
| 458 | TestSampleStatistics_SelectedPairs, BetaDistributionTest, |
| 459 | ::testing::Values(std::make_pair(0.5, 1000), std::make_pair(1000, 0.5), |
| 460 | std::make_pair(900, 1000), std::make_pair(10000, 20000), |
| 461 | std::make_pair(4e5, 2e7), std::make_pair(1e7, 1e5)), |
| 462 | ParamName); |
| 463 | |
| 464 | // NOTE: absl::beta_distribution is not guaranteed to be stable. |
| 465 | TEST(BetaDistributionTest, StabilityTest) { |
| 466 | // absl::beta_distribution stability relies on the stability of |
| 467 | // absl::random_interna::RandU64ToDouble, std::exp, std::log, std::pow, |
| 468 | // and std::sqrt. |
| 469 | // |
| 470 | // This test also depends on the stability of std::frexp. |
| 471 | using testing::ElementsAre; |
| 472 | absl::random_internal::sequence_urbg urbg({ |
| 473 | 0xffff00000000e6c8ull, 0xffff0000000006c8ull, 0x800003766295CFA9ull, |
| 474 | 0x11C819684E734A41ull, 0x832603766295CFA9ull, 0x7fbe76c8b4395800ull, |
| 475 | 0xB3472DCA7B14A94Aull, 0x0003eb76f6f7f755ull, 0xFFCEA50FDB2F953Bull, |
| 476 | 0x13CCA830EB61BD96ull, 0x0334FE1EAA0363CFull, 0x00035C904C70A239ull, |
| 477 | 0x00009E0BCBAADE14ull, 0x0000000000622CA7ull, 0x4864f22c059bf29eull, |
| 478 | 0x247856d8b862665cull, 0xe46e86e9a1337e10ull, 0xd8c8541f3519b133ull, |
| 479 | 0xffe75b52c567b9e4ull, 0xfffff732e5709c5bull, 0xff1f7f0b983532acull, |
| 480 | 0x1ec2e8986d2362caull, 0xC332DDEFBE6C5AA5ull, 0x6558218568AB9702ull, |
| 481 | 0x2AEF7DAD5B6E2F84ull, 0x1521B62829076170ull, 0xECDD4775619F1510ull, |
| 482 | 0x814c8e35fe9a961aull, 0x0c3cd59c9b638a02ull, 0xcb3bb6478a07715cull, |
| 483 | 0x1224e62c978bbc7full, 0x671ef2cb04e81f6eull, 0x3c1cbd811eaf1808ull, |
| 484 | 0x1bbc23cfa8fac721ull, 0xa4c2cda65e596a51ull, 0xb77216fad37adf91ull, |
| 485 | 0x836d794457c08849ull, 0xe083df03475f49d7ull, 0xbc9feb512e6b0d6cull, |
| 486 | 0xb12d74fdd718c8c5ull, 0x12ff09653bfbe4caull, 0x8dd03a105bc4ee7eull, |
| 487 | 0x5738341045ba0d85ull, 0xf3fd722dc65ad09eull, 0xfa14fd21ea2a5705ull, |
| 488 | 0xffe6ea4d6edb0c73ull, 0xD07E9EFE2BF11FB4ull, 0x95DBDA4DAE909198ull, |
| 489 | 0xEAAD8E716B93D5A0ull, 0xD08ED1D0AFC725E0ull, 0x8E3C5B2F8E7594B7ull, |
| 490 | 0x8FF6E2FBF2122B64ull, 0x8888B812900DF01Cull, 0x4FAD5EA0688FC31Cull, |
| 491 | 0xD1CFF191B3A8C1ADull, 0x2F2F2218BE0E1777ull, 0xEA752DFE8B021FA1ull, |
| 492 | }); |
| 493 | |
| 494 | // Convert the real-valued result into a unit64 where we compare |
| 495 | // 5 (float) or 10 (double) decimal digits plus the base-2 exponent. |
| 496 | auto float_to_u64 = [](float d) { |
| 497 | int exp = 0; |
| 498 | auto f = std::frexp(d, &exp); |
| 499 | return (static_cast<uint64_t>(1e5 * f) * 10000) + std::abs(exp); |
| 500 | }; |
| 501 | auto double_to_u64 = [](double d) { |
| 502 | int exp = 0; |
| 503 | auto f = std::frexp(d, &exp); |
| 504 | return (static_cast<uint64_t>(1e10 * f) * 10000) + std::abs(exp); |
| 505 | }; |
| 506 | |
| 507 | std::vector<uint64_t> output(20); |
| 508 | { |
| 509 | // Algorithm Joehnk (float) |
| 510 | absl::beta_distribution<float> dist(0.1f, 0.2f); |
| 511 | std::generate(std::begin(output), std::end(output), |
| 512 | [&] { return float_to_u64(dist(urbg)); }); |
| 513 | EXPECT_EQ(44, urbg.invocations()); |
| 514 | EXPECT_THAT(output, // |
| 515 | testing::ElementsAre( |
| 516 | 998340000, 619030004, 500000001, 999990000, 996280000, |
| 517 | 500000001, 844740004, 847210001, 999970000, 872320000, |
| 518 | 585480007, 933280000, 869080042, 647670031, 528240004, |
| 519 | 969980004, 626050008, 915930002, 833440033, 878040015)); |
| 520 | } |
| 521 | |
| 522 | urbg.reset(); |
| 523 | { |
| 524 | // Algorithm Joehnk (double) |
| 525 | absl::beta_distribution<double> dist(0.1, 0.2); |
| 526 | std::generate(std::begin(output), std::end(output), |
| 527 | [&] { return double_to_u64(dist(urbg)); }); |
| 528 | EXPECT_EQ(44, urbg.invocations()); |
| 529 | EXPECT_THAT( |
| 530 | output, // |
| 531 | testing::ElementsAre( |
| 532 | 99834713000000, 61903356870004, 50000000000001, 99999721170000, |
| 533 | 99628374770000, 99999999990000, 84474397860004, 84721276240001, |
| 534 | 99997407490000, 87232528120000, 58548364780007, 93328932910000, |
| 535 | 86908237770042, 64767917930031, 52824581970004, 96998544140004, |
| 536 | 62605946270008, 91593604380002, 83345031740033, 87804397230015)); |
| 537 | } |
| 538 | |
| 539 | urbg.reset(); |
| 540 | { |
| 541 | // Algorithm Cheng 1 |
| 542 | absl::beta_distribution<double> dist(0.9, 2.0); |
| 543 | std::generate(std::begin(output), std::end(output), |
| 544 | [&] { return double_to_u64(dist(urbg)); }); |
| 545 | EXPECT_EQ(62, urbg.invocations()); |
| 546 | EXPECT_THAT( |
| 547 | output, // |
| 548 | testing::ElementsAre( |
| 549 | 62069004780001, 64433204450001, 53607416560000, 89644295430008, |
| 550 | 61434586310019, 55172615890002, 62187161490000, 56433684810003, |
| 551 | 80454622050005, 86418558710003, 92920514700001, 64645184680001, |
| 552 | 58549183380000, 84881283650005, 71078728590002, 69949694970000, |
| 553 | 73157461710001, 68592191300001, 70747623900000, 78584696930005)); |
| 554 | } |
| 555 | |
| 556 | urbg.reset(); |
| 557 | { |
| 558 | // Algorithm Cheng 2 |
| 559 | absl::beta_distribution<double> dist(1.5, 2.5); |
| 560 | std::generate(std::begin(output), std::end(output), |
| 561 | [&] { return double_to_u64(dist(urbg)); }); |
| 562 | EXPECT_EQ(54, urbg.invocations()); |
| 563 | EXPECT_THAT( |
| 564 | output, // |
| 565 | testing::ElementsAre( |
| 566 | 75000029250001, 76751482860001, 53264575220000, 69193133650005, |
| 567 | 78028324470013, 91573587560002, 59167523770000, 60658618560002, |
| 568 | 80075870540000, 94141320460004, 63196592770003, 78883906300002, |
| 569 | 96797992590001, 76907587800001, 56645167560000, 65408302280003, |
| 570 | 53401156320001, 64731238570000, 83065573750001, 79788333820001)); |
| 571 | } |
| 572 | } |
| 573 | |
| 574 | // This is an implementation-specific test. If any part of the implementation |
| 575 | // changes, then it is likely that this test will change as well. Also, if |
| 576 | // dependencies of the distribution change, such as RandU64ToDouble, then this |
| 577 | // is also likely to change. |
| 578 | TEST(BetaDistributionTest, AlgorithmBounds) { |
| 579 | { |
| 580 | absl::random_internal::sequence_urbg urbg( |
| 581 | {0x7fbe76c8b4395800ull, 0x8000000000000000ull}); |
| 582 | // u=0.499, v=0.5 |
| 583 | absl::beta_distribution<double> dist(1e-4, 1e-4); |
| 584 | double a = dist(urbg); |
| 585 | EXPECT_EQ(a, 2.0202860861567108529e-09); |
| 586 | EXPECT_EQ(2, urbg.invocations()); |
| 587 | } |
| 588 | |
| 589 | // Test that both the float & double algorithms appropriately reject the |
| 590 | // initial draw. |
| 591 | { |
| 592 | // 1/alpha = 1/beta = 2. |
| 593 | absl::beta_distribution<float> dist(0.5, 0.5); |
| 594 | |
| 595 | // first two outputs are close to 1.0 - epsilon, |
| 596 | // thus: (u ^ 2 + v ^ 2) > 1.0 |
| 597 | absl::random_internal::sequence_urbg urbg( |
| 598 | {0xffff00000006e6c8ull, 0xffff00000007c7c8ull, 0x800003766295CFA9ull, |
| 599 | 0x11C819684E734A41ull}); |
| 600 | { |
| 601 | double y = absl::beta_distribution<double>(0.5, 0.5)(urbg); |
| 602 | EXPECT_EQ(4, urbg.invocations()); |
| 603 | EXPECT_EQ(y, 0.9810668952633862) << y; |
| 604 | } |
| 605 | |
| 606 | // ...and: log(u) * a ~= log(v) * b ~= -0.02 |
| 607 | // thus z ~= -0.02 + log(1 + e(~0)) |
| 608 | // ~= -0.02 + 0.69 |
| 609 | // thus z > 0 |
| 610 | urbg.reset(); |
| 611 | { |
| 612 | float x = absl::beta_distribution<float>(0.5, 0.5)(urbg); |
| 613 | EXPECT_EQ(4, urbg.invocations()); |
| 614 | EXPECT_NEAR(0.98106688261032104, x, 0.0000005) << x << "f"; |
| 615 | } |
| 616 | } |
| 617 | } |
| 618 | |
| 619 | } // namespace |