| // Copyright 2017 The Abseil Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "absl/random/beta_distribution.h" | |
| #include <algorithm> | |
| #include <cfloat> | |
| #include <cmath> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstdlib> | |
| #include <iterator> | |
| #include <limits> | |
| #include <random> | |
| #include <sstream> | |
| #include <string> | |
| #include <type_traits> | |
| #include <unordered_map> | |
| #include <utility> | |
| #include <vector> | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "absl/log/log.h" | |
| #include "absl/numeric/internal/representation.h" | |
| #include "absl/random/internal/chi_square.h" | |
| #include "absl/random/internal/distribution_test_util.h" | |
| #include "absl/random/internal/pcg_engine.h" | |
| #include "absl/random/internal/sequence_urbg.h" | |
| #include "absl/random/random.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_format.h" | |
| #include "absl/strings/str_replace.h" | |
| #include "absl/strings/strip.h" | |
| namespace { | |
| template <typename IntType> | |
| class BetaDistributionInterfaceTest : public ::testing::Test {}; | |
| constexpr bool ShouldExerciseLongDoubleTests() { | |
| // long double arithmetic is not supported well by either GCC or Clang on | |
| // most platforms specifically not when implemented in terms of double-double; | |
| // see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99048, | |
| // https://bugs.llvm.org/show_bug.cgi?id=49131, and | |
| // https://bugs.llvm.org/show_bug.cgi?id=49132. | |
| // So a conservative choice here is to disable long-double tests pretty much | |
| // everywhere except on x64 but only if long double is not implemented as | |
| // double-double. | |
| #if defined(__i686__) && defined(__x86_64__) | |
| return !absl::numeric_internal::IsDoubleDouble(); | |
| #else | |
| return false; | |
| #endif | |
| } | |
| using RealTypes = | |
| std::conditional_t<ShouldExerciseLongDoubleTests(), | |
| ::testing::Types<float, double, long double>, | |
| ::testing::Types<float, double>>; | |
| TYPED_TEST_SUITE(BetaDistributionInterfaceTest, RealTypes); | |
| TYPED_TEST(BetaDistributionInterfaceTest, SerializeTest) { | |
| // The threshold for whether std::exp(1/a) is finite. | |
| const TypeParam kSmallA = | |
| 1.0f / std::log((std::numeric_limits<TypeParam>::max)()); | |
| // The threshold for whether a * std::log(a) is finite. | |
| const TypeParam kLargeA = | |
| std::exp(std::log((std::numeric_limits<TypeParam>::max)()) - | |
| std::log(std::log((std::numeric_limits<TypeParam>::max)()))); | |
| using param_type = typename absl::beta_distribution<TypeParam>::param_type; | |
| constexpr int kCount = 1000; | |
| absl::InsecureBitGen gen; | |
| const TypeParam kValues[] = { | |
| TypeParam(1e-20), TypeParam(1e-12), TypeParam(1e-8), TypeParam(1e-4), | |
| TypeParam(1e-3), TypeParam(0.1), TypeParam(0.25), | |
| std::nextafter(TypeParam(0.5), TypeParam(0)), // 0.5 - epsilon | |
| std::nextafter(TypeParam(0.5), TypeParam(1)), // 0.5 + epsilon | |
| TypeParam(0.5), TypeParam(1.0), // | |
| std::nextafter(TypeParam(1), TypeParam(0)), // 1 - epsilon | |
| std::nextafter(TypeParam(1), TypeParam(2)), // 1 + epsilon | |
| TypeParam(12.5), TypeParam(1e2), TypeParam(1e8), TypeParam(1e12), | |
| TypeParam(1e20), // | |
| kSmallA, // | |
| std::nextafter(kSmallA, TypeParam(0)), // | |
| std::nextafter(kSmallA, TypeParam(1)), // | |
| kLargeA, // | |
| std::nextafter(kLargeA, TypeParam(0)), // | |
| std::nextafter(kLargeA, std::numeric_limits<TypeParam>::max()), | |
| // Boundary cases. | |
| std::numeric_limits<TypeParam>::max(), | |
| std::numeric_limits<TypeParam>::epsilon(), | |
| std::nextafter(std::numeric_limits<TypeParam>::min(), | |
| TypeParam(1)), // min + epsilon | |
| std::numeric_limits<TypeParam>::min(), // smallest normal | |
| std::numeric_limits<TypeParam>::denorm_min(), // smallest denorm | |
| std::numeric_limits<TypeParam>::min() / 2, // denorm | |
| std::nextafter(std::numeric_limits<TypeParam>::min(), | |
| TypeParam(0)), // denorm_max | |
| }; | |
| for (TypeParam alpha : kValues) { | |
| for (TypeParam beta : kValues) { | |
| LOG(INFO) << absl::StreamFormat("Smoke test for Beta(%a, %a)", alpha, | |
| beta); | |
| param_type param(alpha, beta); | |
| absl::beta_distribution<TypeParam> before(alpha, beta); | |
| EXPECT_EQ(before.alpha(), param.alpha()); | |
| EXPECT_EQ(before.beta(), param.beta()); | |
| { | |
| absl::beta_distribution<TypeParam> via_param(param); | |
| EXPECT_EQ(via_param, before); | |
| EXPECT_EQ(via_param.param(), before.param()); | |
| } | |
| // Smoke test. | |
| for (int i = 0; i < kCount; ++i) { | |
| auto sample = before(gen); | |
| EXPECT_TRUE(std::isfinite(sample)); | |
| EXPECT_GE(sample, before.min()); | |
| EXPECT_LE(sample, before.max()); | |
| } | |
| // Validate stream serialization. | |
| std::stringstream ss; | |
| ss << before; | |
| absl::beta_distribution<TypeParam> after(3.8f, 1.43f); | |
| EXPECT_NE(before.alpha(), after.alpha()); | |
| EXPECT_NE(before.beta(), after.beta()); | |
| EXPECT_NE(before.param(), after.param()); | |
| EXPECT_NE(before, after); | |
| ss >> after; | |
| EXPECT_EQ(before.alpha(), after.alpha()); | |
| EXPECT_EQ(before.beta(), after.beta()); | |
| EXPECT_EQ(before, after) // | |
| << ss.str() << " " // | |
| << (ss.good() ? "good " : "") // | |
| << (ss.bad() ? "bad " : "") // | |
| << (ss.eof() ? "eof " : "") // | |
| << (ss.fail() ? "fail " : ""); | |
| } | |
| } | |
| } | |
| TYPED_TEST(BetaDistributionInterfaceTest, DegenerateCases) { | |
| // We use a fixed bit generator for distribution accuracy tests. This allows | |
| // these tests to be deterministic, while still testing the qualify of the | |
| // implementation. | |
| absl::random_internal::pcg64_2018_engine rng(0x2B7E151628AED2A6); | |
| // Extreme cases when the params are abnormal. | |
| constexpr int kCount = 1000; | |
| const TypeParam kSmallValues[] = { | |
| std::numeric_limits<TypeParam>::min(), | |
| std::numeric_limits<TypeParam>::denorm_min(), | |
| std::nextafter(std::numeric_limits<TypeParam>::min(), | |
| TypeParam(0)), // denorm_max | |
| std::numeric_limits<TypeParam>::epsilon(), | |
| }; | |
| const TypeParam kLargeValues[] = { | |
| std::numeric_limits<TypeParam>::max() * static_cast<TypeParam>(0.9999), | |
| std::numeric_limits<TypeParam>::max() - 1, | |
| std::numeric_limits<TypeParam>::max(), | |
| }; | |
| { | |
| // Small alpha and beta. | |
| // Useful WolframAlpha plots: | |
| // * plot InverseBetaRegularized[x, 0.0001, 0.0001] from 0.495 to 0.505 | |
| // * Beta[1.0, 0.0000001, 0.0000001] | |
| // * Beta[0.9999, 0.0000001, 0.0000001] | |
| for (TypeParam alpha : kSmallValues) { | |
| for (TypeParam beta : kSmallValues) { | |
| int zeros = 0; | |
| int ones = 0; | |
| absl::beta_distribution<TypeParam> d(alpha, beta); | |
| for (int i = 0; i < kCount; ++i) { | |
| TypeParam x = d(rng); | |
| if (x == 0.0) { | |
| zeros++; | |
| } else if (x == 1.0) { | |
| ones++; | |
| } | |
| } | |
| EXPECT_EQ(ones + zeros, kCount); | |
| if (alpha == beta) { | |
| EXPECT_NE(ones, 0); | |
| EXPECT_NE(zeros, 0); | |
| } | |
| } | |
| } | |
| } | |
| { | |
| // Small alpha, large beta. | |
| // Useful WolframAlpha plots: | |
| // * plot InverseBetaRegularized[x, 0.0001, 10000] from 0.995 to 1 | |
| // * Beta[0, 0.0000001, 1000000] | |
| // * Beta[0.001, 0.0000001, 1000000] | |
| // * Beta[1, 0.0000001, 1000000] | |
| for (TypeParam alpha : kSmallValues) { | |
| for (TypeParam beta : kLargeValues) { | |
| absl::beta_distribution<TypeParam> d(alpha, beta); | |
| for (int i = 0; i < kCount; ++i) { | |
| EXPECT_EQ(d(rng), 0.0); | |
| } | |
| } | |
| } | |
| } | |
| { | |
| // Large alpha, small beta. | |
| // Useful WolframAlpha plots: | |
| // * plot InverseBetaRegularized[x, 10000, 0.0001] from 0 to 0.001 | |
| // * Beta[0.99, 1000000, 0.0000001] | |
| // * Beta[1, 1000000, 0.0000001] | |
| for (TypeParam alpha : kLargeValues) { | |
| for (TypeParam beta : kSmallValues) { | |
| absl::beta_distribution<TypeParam> d(alpha, beta); | |
| for (int i = 0; i < kCount; ++i) { | |
| EXPECT_EQ(d(rng), 1.0); | |
| } | |
| } | |
| } | |
| } | |
| { | |
| // Large alpha and beta. | |
| absl::beta_distribution<TypeParam> d(std::numeric_limits<TypeParam>::max(), | |
| std::numeric_limits<TypeParam>::max()); | |
| for (int i = 0; i < kCount; ++i) { | |
| EXPECT_EQ(d(rng), 0.5); | |
| } | |
| } | |
| { | |
| // Large alpha and beta but unequal. | |
| absl::beta_distribution<TypeParam> d( | |
| std::numeric_limits<TypeParam>::max(), | |
| std::numeric_limits<TypeParam>::max() * 0.9999); | |
| for (int i = 0; i < kCount; ++i) { | |
| TypeParam x = d(rng); | |
| EXPECT_NE(x, 0.5f); | |
| EXPECT_FLOAT_EQ(x, 0.500025f); | |
| } | |
| } | |
| } | |
| class BetaDistributionModel { | |
| public: | |
| explicit BetaDistributionModel(::testing::tuple<double, double> p) | |
| : alpha_(::testing::get<0>(p)), beta_(::testing::get<1>(p)) {} | |
| double Mean() const { return alpha_ / (alpha_ + beta_); } | |
| double Variance() const { | |
| return alpha_ * beta_ / (alpha_ + beta_ + 1) / (alpha_ + beta_) / | |
| (alpha_ + beta_); | |
| } | |
| double Kurtosis() const { | |
| return 3 + 6 * | |
| ((alpha_ - beta_) * (alpha_ - beta_) * (alpha_ + beta_ + 1) - | |
| alpha_ * beta_ * (2 + alpha_ + beta_)) / | |
| alpha_ / beta_ / (alpha_ + beta_ + 2) / (alpha_ + beta_ + 3); | |
| } | |
| protected: | |
| const double alpha_; | |
| const double beta_; | |
| }; | |
| class BetaDistributionTest | |
| : public ::testing::TestWithParam<::testing::tuple<double, double>>, | |
| public BetaDistributionModel { | |
| public: | |
| BetaDistributionTest() : BetaDistributionModel(GetParam()) {} | |
| protected: | |
| template <class D> | |
| bool SingleZTestOnMeanAndVariance(double p, size_t samples); | |
| template <class D> | |
| bool SingleChiSquaredTest(double p, size_t samples, size_t buckets); | |
| absl::InsecureBitGen rng_; | |
| }; | |
| template <class D> | |
| bool BetaDistributionTest::SingleZTestOnMeanAndVariance(double p, | |
| size_t samples) { | |
| D dis(alpha_, beta_); | |
| std::vector<double> data; | |
| data.reserve(samples); | |
| for (size_t i = 0; i < samples; i++) { | |
| const double variate = dis(rng_); | |
| EXPECT_FALSE(std::isnan(variate)); | |
| // Note that equality is allowed on both sides. | |
| EXPECT_GE(variate, 0.0); | |
| EXPECT_LE(variate, 1.0); | |
| data.push_back(variate); | |
| } | |
| // We validate that the sample mean and sample variance are indeed from a | |
| // Beta distribution with the given shape parameters. | |
| const auto m = absl::random_internal::ComputeDistributionMoments(data); | |
| // The variance of the sample mean is variance / n. | |
| const double mean_stddev = std::sqrt(Variance() / static_cast<double>(m.n)); | |
| // The variance of the sample variance is (approximately): | |
| // (kurtosis - 1) * variance^2 / n | |
| const double variance_stddev = std::sqrt( | |
| (Kurtosis() - 1) * Variance() * Variance() / static_cast<double>(m.n)); | |
| // z score for the sample variance. | |
| const double z_variance = (m.variance - Variance()) / variance_stddev; | |
| const double max_err = absl::random_internal::MaxErrorTolerance(p); | |
| const double z_mean = absl::random_internal::ZScore(Mean(), m); | |
| const bool pass = | |
| absl::random_internal::Near("z", z_mean, 0.0, max_err) && | |
| absl::random_internal::Near("z_variance", z_variance, 0.0, max_err); | |
| if (!pass) { | |
| LOG(INFO) << "Beta(" << alpha_ << ", " << beta_ << "), mean: sample " | |
| << m.mean << ", expect " << Mean() << ", which is " | |
| << std::abs(m.mean - Mean()) / mean_stddev | |
| << " stddevs away, variance: sample " << m.variance << ", expect " | |
| << Variance() << ", which is " | |
| << std::abs(m.variance - Variance()) / variance_stddev | |
| << " stddevs away."; | |
| } | |
| return pass; | |
| } | |
| template <class D> | |
| bool BetaDistributionTest::SingleChiSquaredTest(double p, size_t samples, | |
| size_t buckets) { | |
| constexpr double kErr = 1e-7; | |
| std::vector<double> cutoffs, expected; | |
| const double bucket_width = 1.0 / static_cast<double>(buckets); | |
| int i = 1; | |
| int unmerged_buckets = 0; | |
| for (; i < buckets; ++i) { | |
| const double p = bucket_width * static_cast<double>(i); | |
| const double boundary = | |
| absl::random_internal::BetaIncompleteInv(alpha_, beta_, p); | |
| // The intention is to add `boundary` to the list of `cutoffs`. It becomes | |
| // problematic, however, when the boundary values are not monotone, due to | |
| // numerical issues when computing the inverse regularized incomplete | |
| // Beta function. In these cases, we merge that bucket with its previous | |
| // neighbor and merge their expected counts. | |
| if ((cutoffs.empty() && boundary < kErr) || | |
| (!cutoffs.empty() && boundary <= cutoffs.back())) { | |
| unmerged_buckets++; | |
| continue; | |
| } | |
| if (boundary >= 1.0 - 1e-10) { | |
| break; | |
| } | |
| cutoffs.push_back(boundary); | |
| expected.push_back(static_cast<double>(1 + unmerged_buckets) * | |
| bucket_width * static_cast<double>(samples)); | |
| unmerged_buckets = 0; | |
| } | |
| cutoffs.push_back(std::numeric_limits<double>::infinity()); | |
| // Merge all remaining buckets. | |
| expected.push_back(static_cast<double>(buckets - i + 1) * bucket_width * | |
| static_cast<double>(samples)); | |
| // Make sure that we don't merge all the buckets, making this test | |
| // meaningless. | |
| EXPECT_GE(cutoffs.size(), 3) << alpha_ << ", " << beta_; | |
| D dis(alpha_, beta_); | |
| std::vector<int32_t> counts(cutoffs.size(), 0); | |
| for (int i = 0; i < samples; i++) { | |
| const double x = dis(rng_); | |
| auto it = std::upper_bound(cutoffs.begin(), cutoffs.end(), x); | |
| counts[std::distance(cutoffs.begin(), it)]++; | |
| } | |
| // Null-hypothesis is that the distribution is beta distributed with the | |
| // provided alpha, beta params (not estimated from the data). | |
| const int dof = cutoffs.size() - 1; | |
| const double chi_square = absl::random_internal::ChiSquare( | |
| counts.begin(), counts.end(), expected.begin(), expected.end()); | |
| const bool pass = | |
| (absl::random_internal::ChiSquarePValue(chi_square, dof) >= p); | |
| if (!pass) { | |
| for (size_t i = 0; i < cutoffs.size(); i++) { | |
| LOG(INFO) << "cutoff[" << i << "] = " << cutoffs[i] << ", actual count " | |
| << counts[i] << ", expected " << static_cast<int>(expected[i]); | |
| } | |
| LOG(INFO) << "Beta(" << alpha_ << ", " << beta_ << ") " | |
| << absl::random_internal::kChiSquared << " " << chi_square | |
| << ", p = " | |
| << absl::random_internal::ChiSquarePValue(chi_square, dof); | |
| } | |
| return pass; | |
| } | |
| TEST_P(BetaDistributionTest, TestSampleStatistics) { | |
| static constexpr int kRuns = 20; | |
| static constexpr double kPFail = 0.02; | |
| const double p = | |
| absl::random_internal::RequiredSuccessProbability(kPFail, kRuns); | |
| static constexpr int kSampleCount = 10000; | |
| static constexpr int kBucketCount = 100; | |
| int failed = 0; | |
| for (int i = 0; i < kRuns; ++i) { | |
| if (!SingleZTestOnMeanAndVariance<absl::beta_distribution<double>>( | |
| p, kSampleCount)) { | |
| failed++; | |
| } | |
| if (!SingleChiSquaredTest<absl::beta_distribution<double>>( | |
| 0.005, kSampleCount, kBucketCount)) { | |
| failed++; | |
| } | |
| } | |
| // Set so that the test is not flaky at --runs_per_test=10000 | |
| EXPECT_LE(failed, 5); | |
| } | |
| std::string ParamName( | |
| const ::testing::TestParamInfo<::testing::tuple<double, double>>& info) { | |
| std::string name = absl::StrCat("alpha_", ::testing::get<0>(info.param), | |
| "__beta_", ::testing::get<1>(info.param)); | |
| return absl::StrReplaceAll(name, {{"+", "_"}, {"-", "_"}, {".", "_"}}); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| TestSampleStatisticsCombinations, BetaDistributionTest, | |
| ::testing::Combine(::testing::Values(0.1, 0.2, 0.9, 1.1, 2.5, 10.0, 123.4), | |
| ::testing::Values(0.1, 0.2, 0.9, 1.1, 2.5, 10.0, 123.4)), | |
| ParamName); | |
| INSTANTIATE_TEST_SUITE_P( | |
| TestSampleStatistics_SelectedPairs, BetaDistributionTest, | |
| ::testing::Values(std::make_pair(0.5, 1000), std::make_pair(1000, 0.5), | |
| std::make_pair(900, 1000), std::make_pair(10000, 20000), | |
| std::make_pair(4e5, 2e7), std::make_pair(1e7, 1e5)), | |
| ParamName); | |
| // NOTE: absl::beta_distribution is not guaranteed to be stable. | |
| TEST(BetaDistributionTest, StabilityTest) { | |
| // absl::beta_distribution stability relies on the stability of | |
| // absl::random_interna::RandU64ToDouble, std::exp, std::log, std::pow, | |
| // and std::sqrt. | |
| // | |
| // This test also depends on the stability of std::frexp. | |
| using testing::ElementsAre; | |
| absl::random_internal::sequence_urbg urbg({ | |
| 0xffff00000000e6c8ull, 0xffff0000000006c8ull, 0x800003766295CFA9ull, | |
| 0x11C819684E734A41ull, 0x832603766295CFA9ull, 0x7fbe76c8b4395800ull, | |
| 0xB3472DCA7B14A94Aull, 0x0003eb76f6f7f755ull, 0xFFCEA50FDB2F953Bull, | |
| 0x13CCA830EB61BD96ull, 0x0334FE1EAA0363CFull, 0x00035C904C70A239ull, | |
| 0x00009E0BCBAADE14ull, 0x0000000000622CA7ull, 0x4864f22c059bf29eull, | |
| 0x247856d8b862665cull, 0xe46e86e9a1337e10ull, 0xd8c8541f3519b133ull, | |
| 0xffe75b52c567b9e4ull, 0xfffff732e5709c5bull, 0xff1f7f0b983532acull, | |
| 0x1ec2e8986d2362caull, 0xC332DDEFBE6C5AA5ull, 0x6558218568AB9702ull, | |
| 0x2AEF7DAD5B6E2F84ull, 0x1521B62829076170ull, 0xECDD4775619F1510ull, | |
| 0x814c8e35fe9a961aull, 0x0c3cd59c9b638a02ull, 0xcb3bb6478a07715cull, | |
| 0x1224e62c978bbc7full, 0x671ef2cb04e81f6eull, 0x3c1cbd811eaf1808ull, | |
| 0x1bbc23cfa8fac721ull, 0xa4c2cda65e596a51ull, 0xb77216fad37adf91ull, | |
| 0x836d794457c08849ull, 0xe083df03475f49d7ull, 0xbc9feb512e6b0d6cull, | |
| 0xb12d74fdd718c8c5ull, 0x12ff09653bfbe4caull, 0x8dd03a105bc4ee7eull, | |
| 0x5738341045ba0d85ull, 0xf3fd722dc65ad09eull, 0xfa14fd21ea2a5705ull, | |
| 0xffe6ea4d6edb0c73ull, 0xD07E9EFE2BF11FB4ull, 0x95DBDA4DAE909198ull, | |
| 0xEAAD8E716B93D5A0ull, 0xD08ED1D0AFC725E0ull, 0x8E3C5B2F8E7594B7ull, | |
| 0x8FF6E2FBF2122B64ull, 0x8888B812900DF01Cull, 0x4FAD5EA0688FC31Cull, | |
| 0xD1CFF191B3A8C1ADull, 0x2F2F2218BE0E1777ull, 0xEA752DFE8B021FA1ull, | |
| }); | |
| // Convert the real-valued result into a unit64 where we compare | |
| // 5 (float) or 10 (double) decimal digits plus the base-2 exponent. | |
| auto float_to_u64 = [](float d) { | |
| int exp = 0; | |
| auto f = std::frexp(d, &exp); | |
| return (static_cast<uint64_t>(1e5 * f) * 10000) + std::abs(exp); | |
| }; | |
| auto double_to_u64 = [](double d) { | |
| int exp = 0; | |
| auto f = std::frexp(d, &exp); | |
| return (static_cast<uint64_t>(1e10 * f) * 10000) + std::abs(exp); | |
| }; | |
| std::vector<uint64_t> output(20); | |
| { | |
| // Algorithm Joehnk (float) | |
| absl::beta_distribution<float> dist(0.1f, 0.2f); | |
| std::generate(std::begin(output), std::end(output), | |
| [&] { return float_to_u64(dist(urbg)); }); | |
| EXPECT_EQ(44, urbg.invocations()); | |
| EXPECT_THAT(output, // | |
| testing::ElementsAre( | |
| 998340000, 619030004, 500000001, 999990000, 996280000, | |
| 500000001, 844740004, 847210001, 999970000, 872320000, | |
| 585480007, 933280000, 869080042, 647670031, 528240004, | |
| 969980004, 626050008, 915930002, 833440033, 878040015)); | |
| } | |
| urbg.reset(); | |
| { | |
| // Algorithm Joehnk (double) | |
| absl::beta_distribution<double> dist(0.1, 0.2); | |
| std::generate(std::begin(output), std::end(output), | |
| [&] { return double_to_u64(dist(urbg)); }); | |
| EXPECT_EQ(44, urbg.invocations()); | |
| EXPECT_THAT( | |
| output, // | |
| testing::ElementsAre( | |
| 99834713000000, 61903356870004, 50000000000001, 99999721170000, | |
| 99628374770000, 99999999990000, 84474397860004, 84721276240001, | |
| 99997407490000, 87232528120000, 58548364780007, 93328932910000, | |
| 86908237770042, 64767917930031, 52824581970004, 96998544140004, | |
| 62605946270008, 91593604380002, 83345031740033, 87804397230015)); | |
| } | |
| urbg.reset(); | |
| { | |
| // Algorithm Cheng 1 | |
| absl::beta_distribution<double> dist(0.9, 2.0); | |
| std::generate(std::begin(output), std::end(output), | |
| [&] { return double_to_u64(dist(urbg)); }); | |
| EXPECT_EQ(62, urbg.invocations()); | |
| EXPECT_THAT( | |
| output, // | |
| testing::ElementsAre( | |
| 62069004780001, 64433204450001, 53607416560000, 89644295430008, | |
| 61434586310019, 55172615890002, 62187161490000, 56433684810003, | |
| 80454622050005, 86418558710003, 92920514700001, 64645184680001, | |
| 58549183380000, 84881283650005, 71078728590002, 69949694970000, | |
| 73157461710001, 68592191300001, 70747623900000, 78584696930005)); | |
| } | |
| urbg.reset(); | |
| { | |
| // Algorithm Cheng 2 | |
| absl::beta_distribution<double> dist(1.5, 2.5); | |
| std::generate(std::begin(output), std::end(output), | |
| [&] { return double_to_u64(dist(urbg)); }); | |
| EXPECT_EQ(54, urbg.invocations()); | |
| EXPECT_THAT( | |
| output, // | |
| testing::ElementsAre( | |
| 75000029250001, 76751482860001, 53264575220000, 69193133650005, | |
| 78028324470013, 91573587560002, 59167523770000, 60658618560002, | |
| 80075870540000, 94141320460004, 63196592770003, 78883906300002, | |
| 96797992590001, 76907587800001, 56645167560000, 65408302280003, | |
| 53401156320001, 64731238570000, 83065573750001, 79788333820001)); | |
| } | |
| } | |
| // This is an implementation-specific test. If any part of the implementation | |
| // changes, then it is likely that this test will change as well. Also, if | |
| // dependencies of the distribution change, such as RandU64ToDouble, then this | |
| // is also likely to change. | |
| TEST(BetaDistributionTest, AlgorithmBounds) { | |
| #if (defined(__i386__) || defined(_M_IX86)) && FLT_EVAL_METHOD != 0 | |
| // We're using an x87-compatible FPU, and intermediate operations are | |
| // performed with 80-bit floats. This produces slightly different results from | |
| // what we expect below. | |
| GTEST_SKIP() | |
| << "Skipping the test because we detected x87 floating-point semantics"; | |
| #endif | |
| { | |
| absl::random_internal::sequence_urbg urbg( | |
| {0x7fbe76c8b4395800ull, 0x8000000000000000ull}); | |
| // u=0.499, v=0.5 | |
| absl::beta_distribution<double> dist(1e-4, 1e-4); | |
| double a = dist(urbg); | |
| EXPECT_EQ(a, 2.0202860861567108529e-09); | |
| EXPECT_EQ(2, urbg.invocations()); | |
| } | |
| // Test that both the float & double algorithms appropriately reject the | |
| // initial draw. | |
| { | |
| // 1/alpha = 1/beta = 2. | |
| absl::beta_distribution<float> dist(0.5, 0.5); | |
| // first two outputs are close to 1.0 - epsilon, | |
| // thus: (u ^ 2 + v ^ 2) > 1.0 | |
| absl::random_internal::sequence_urbg urbg( | |
| {0xffff00000006e6c8ull, 0xffff00000007c7c8ull, 0x800003766295CFA9ull, | |
| 0x11C819684E734A41ull}); | |
| { | |
| double y = absl::beta_distribution<double>(0.5, 0.5)(urbg); | |
| EXPECT_EQ(4, urbg.invocations()); | |
| EXPECT_EQ(y, 0.9810668952633862) << y; | |
| } | |
| // ...and: log(u) * a ~= log(v) * b ~= -0.02 | |
| // thus z ~= -0.02 + log(1 + e(~0)) | |
| // ~= -0.02 + 0.69 | |
| // thus z > 0 | |
| urbg.reset(); | |
| { | |
| float x = absl::beta_distribution<float>(0.5, 0.5)(urbg); | |
| EXPECT_EQ(4, urbg.invocations()); | |
| EXPECT_NEAR(0.98106688261032104, x, 0.0000005) << x << "f"; | |
| } | |
| } | |
| } | |
| } // namespace |