• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/gaussian_distribution.h"
16 
17 #include <algorithm>
18 #include <cmath>
19 #include <cstddef>
20 #include <ios>
21 #include <iterator>
22 #include <random>
23 #include <string>
24 #include <vector>
25 
26 #include "gmock/gmock.h"
27 #include "gtest/gtest.h"
28 #include "absl/base/internal/raw_logging.h"
29 #include "absl/base/macros.h"
30 #include "absl/random/internal/chi_square.h"
31 #include "absl/random/internal/distribution_test_util.h"
32 #include "absl/random/internal/sequence_urbg.h"
33 #include "absl/random/random.h"
34 #include "absl/strings/str_cat.h"
35 #include "absl/strings/str_format.h"
36 #include "absl/strings/str_replace.h"
37 #include "absl/strings/strip.h"
38 
39 namespace {
40 
41 using absl::random_internal::kChiSquared;
42 
43 template <typename RealType>
44 class GaussianDistributionInterfaceTest : public ::testing::Test {};
45 
46 using RealTypes = ::testing::Types<float, double, long double>;
47 TYPED_TEST_CASE(GaussianDistributionInterfaceTest, RealTypes);
48 
TYPED_TEST(GaussianDistributionInterfaceTest,SerializeTest)49 TYPED_TEST(GaussianDistributionInterfaceTest, SerializeTest) {
50   using param_type =
51       typename absl::gaussian_distribution<TypeParam>::param_type;
52 
53   const TypeParam kParams[] = {
54       // Cases around 1.
55       1,                                           //
56       std::nextafter(TypeParam(1), TypeParam(0)),  // 1 - epsilon
57       std::nextafter(TypeParam(1), TypeParam(2)),  // 1 + epsilon
58       // Arbitrary values.
59       TypeParam(1e-8), TypeParam(1e-4), TypeParam(2), TypeParam(1e4),
60       TypeParam(1e8), TypeParam(1e20), TypeParam(2.5),
61       // Boundary cases.
62       std::numeric_limits<TypeParam>::infinity(),
63       std::numeric_limits<TypeParam>::max(),
64       std::numeric_limits<TypeParam>::epsilon(),
65       std::nextafter(std::numeric_limits<TypeParam>::min(),
66                      TypeParam(1)),           // min + epsilon
67       std::numeric_limits<TypeParam>::min(),  // smallest normal
68       // There are some errors dealing with denorms on apple platforms.
69       std::numeric_limits<TypeParam>::denorm_min(),  // smallest denorm
70       std::numeric_limits<TypeParam>::min() / 2,
71       std::nextafter(std::numeric_limits<TypeParam>::min(),
72                      TypeParam(0)),  // denorm_max
73   };
74 
75   constexpr int kCount = 1000;
76   absl::InsecureBitGen gen;
77 
78   // Use a loop to generate the combinations of {+/-x, +/-y}, and assign x, y to
79   // all values in kParams,
80   for (const auto mod : {0, 1, 2, 3}) {
81     for (const auto x : kParams) {
82       if (!std::isfinite(x)) continue;
83       for (const auto y : kParams) {
84         const TypeParam mean = (mod & 0x1) ? -x : x;
85         const TypeParam stddev = (mod & 0x2) ? -y : y;
86         const param_type param(mean, stddev);
87 
88         absl::gaussian_distribution<TypeParam> before(mean, stddev);
89         EXPECT_EQ(before.mean(), param.mean());
90         EXPECT_EQ(before.stddev(), param.stddev());
91 
92         {
93           absl::gaussian_distribution<TypeParam> via_param(param);
94           EXPECT_EQ(via_param, before);
95           EXPECT_EQ(via_param.param(), before.param());
96         }
97 
98         // Smoke test.
99         auto sample_min = before.max();
100         auto sample_max = before.min();
101         for (int i = 0; i < kCount; i++) {
102           auto sample = before(gen);
103           if (sample > sample_max) sample_max = sample;
104           if (sample < sample_min) sample_min = sample;
105           EXPECT_GE(sample, before.min()) << before;
106           EXPECT_LE(sample, before.max()) << before;
107         }
108         if (!std::is_same<TypeParam, long double>::value) {
109           ABSL_INTERNAL_LOG(
110               INFO, absl::StrFormat("Range{%f, %f}: %f, %f", mean, stddev,
111                                     sample_min, sample_max));
112         }
113 
114         std::stringstream ss;
115         ss << before;
116 
117         if (!std::isfinite(mean) || !std::isfinite(stddev)) {
118           // Streams do not parse inf/nan.
119           continue;
120         }
121 
122         // Validate stream serialization.
123         absl::gaussian_distribution<TypeParam> after(-0.53f, 2.3456f);
124 
125         EXPECT_NE(before.mean(), after.mean());
126         EXPECT_NE(before.stddev(), after.stddev());
127         EXPECT_NE(before.param(), after.param());
128         EXPECT_NE(before, after);
129 
130         ss >> after;
131 
132 #if defined(__powerpc64__) || defined(__PPC64__) || defined(__powerpc__) || \
133     defined(__ppc__) || defined(__PPC__)
134         if (std::is_same<TypeParam, long double>::value) {
135           // Roundtripping floating point values requires sufficient precision
136           // to reconstruct the exact value.  It turns out that long double
137           // has some errors doing this on ppc, particularly for values
138           // near {1.0 +/- epsilon}.
139           if (mean <= std::numeric_limits<double>::max() &&
140               mean >= std::numeric_limits<double>::lowest()) {
141             EXPECT_EQ(static_cast<double>(before.mean()),
142                       static_cast<double>(after.mean()))
143                 << ss.str();
144           }
145           if (stddev <= std::numeric_limits<double>::max() &&
146               stddev >= std::numeric_limits<double>::lowest()) {
147             EXPECT_EQ(static_cast<double>(before.stddev()),
148                       static_cast<double>(after.stddev()))
149                 << ss.str();
150           }
151           continue;
152         }
153 #endif
154 
155         EXPECT_EQ(before.mean(), after.mean());
156         EXPECT_EQ(before.stddev(), after.stddev())  //
157             << ss.str() << " "                      //
158             << (ss.good() ? "good " : "")           //
159             << (ss.bad() ? "bad " : "")             //
160             << (ss.eof() ? "eof " : "")             //
161             << (ss.fail() ? "fail " : "");
162       }
163     }
164   }
165 }
166 
167 // http://www.itl.nist.gov/div898/handbook/eda/section3/eda3661.htm
168 
169 class GaussianModel {
170  public:
GaussianModel(double mean,double stddev)171   GaussianModel(double mean, double stddev) : mean_(mean), stddev_(stddev) {}
172 
mean() const173   double mean() const { return mean_; }
variance() const174   double variance() const { return stddev() * stddev(); }
stddev() const175   double stddev() const { return stddev_; }
skew() const176   double skew() const { return 0; }
kurtosis() const177   double kurtosis() const { return 3.0; }
178 
179   // The inverse CDF, or PercentPoint function.
InverseCDF(double p)180   double InverseCDF(double p) {
181     ABSL_ASSERT(p >= 0.0);
182     ABSL_ASSERT(p < 1.0);
183     return mean() + stddev() * -absl::random_internal::InverseNormalSurvival(p);
184   }
185 
186  private:
187   const double mean_;
188   const double stddev_;
189 };
190 
191 struct Param {
192   double mean;
193   double stddev;
194   double p_fail;  // Z-Test probability of failure.
195   int trials;     // Z-Test trials.
196 };
197 
198 // GaussianDistributionTests implements a z-test for the gaussian
199 // distribution.
200 class GaussianDistributionTests : public testing::TestWithParam<Param>,
201                                   public GaussianModel {
202  public:
GaussianDistributionTests()203   GaussianDistributionTests()
204       : GaussianModel(GetParam().mean, GetParam().stddev) {}
205 
206   // SingleZTest provides a basic z-squared test of the mean vs. expected
207   // mean for data generated by the poisson distribution.
208   template <typename D>
209   bool SingleZTest(const double p, const size_t samples);
210 
211   // SingleChiSquaredTest provides a basic chi-squared test of the normal
212   // distribution.
213   template <typename D>
214   double SingleChiSquaredTest();
215 
216   absl::InsecureBitGen rng_;
217 };
218 
219 template <typename D>
SingleZTest(const double p,const size_t samples)220 bool GaussianDistributionTests::SingleZTest(const double p,
221                                             const size_t samples) {
222   D dis(mean(), stddev());
223 
224   std::vector<double> data;
225   data.reserve(samples);
226   for (size_t i = 0; i < samples; i++) {
227     const double x = dis(rng_);
228     data.push_back(x);
229   }
230 
231   const double max_err = absl::random_internal::MaxErrorTolerance(p);
232   const auto m = absl::random_internal::ComputeDistributionMoments(data);
233   const double z = absl::random_internal::ZScore(mean(), m);
234   const bool pass = absl::random_internal::Near("z", z, 0.0, max_err);
235 
236   // NOTE: Informational statistical test:
237   //
238   // Compute the Jarque-Bera test statistic given the excess skewness
239   // and kurtosis. The statistic is drawn from a chi-square(2) distribution.
240   // https://en.wikipedia.org/wiki/Jarque%E2%80%93Bera_test
241   //
242   // The null-hypothesis (normal distribution) is rejected when
243   // (p = 0.05 => jb > 5.99)
244   // (p = 0.01 => jb > 9.21)
245   // NOTE: JB has a large type-I error rate, so it will reject the
246   // null-hypothesis even when it is true more often than the z-test.
247   //
248   const double jb =
249       static_cast<double>(m.n) / 6.0 *
250       (std::pow(m.skewness, 2.0) + std::pow(m.kurtosis - 3.0, 2.0) / 4.0);
251 
252   if (!pass || jb > 9.21) {
253     ABSL_INTERNAL_LOG(
254         INFO, absl::StrFormat("p=%f max_err=%f\n"
255                               " mean=%f vs. %f\n"
256                               " stddev=%f vs. %f\n"
257                               " skewness=%f vs. %f\n"
258                               " kurtosis=%f vs. %f\n"
259                               " z=%f vs. 0\n"
260                               " jb=%f vs. 9.21",
261                               p, max_err, m.mean, mean(), std::sqrt(m.variance),
262                               stddev(), m.skewness, skew(), m.kurtosis,
263                               kurtosis(), z, jb));
264   }
265   return pass;
266 }
267 
268 template <typename D>
SingleChiSquaredTest()269 double GaussianDistributionTests::SingleChiSquaredTest() {
270   const size_t kSamples = 10000;
271   const int kBuckets = 50;
272 
273   // The InverseCDF is the percent point function of the
274   // distribution, and can be used to assign buckets
275   // roughly uniformly.
276   std::vector<double> cutoffs;
277   const double kInc = 1.0 / static_cast<double>(kBuckets);
278   for (double p = kInc; p < 1.0; p += kInc) {
279     cutoffs.push_back(InverseCDF(p));
280   }
281   if (cutoffs.back() != std::numeric_limits<double>::infinity()) {
282     cutoffs.push_back(std::numeric_limits<double>::infinity());
283   }
284 
285   D dis(mean(), stddev());
286 
287   std::vector<int32_t> counts(cutoffs.size(), 0);
288   for (int j = 0; j < kSamples; j++) {
289     const double x = dis(rng_);
290     auto it = std::upper_bound(cutoffs.begin(), cutoffs.end(), x);
291     counts[std::distance(cutoffs.begin(), it)]++;
292   }
293 
294   // Null-hypothesis is that the distribution is a gaussian distribution
295   // with the provided mean and stddev (not estimated from the data).
296   const int dof = static_cast<int>(counts.size()) - 1;
297 
298   // Our threshold for logging is 1-in-50.
299   const double threshold = absl::random_internal::ChiSquareValue(dof, 0.98);
300 
301   const double expected =
302       static_cast<double>(kSamples) / static_cast<double>(counts.size());
303 
304   double chi_square = absl::random_internal::ChiSquareWithExpected(
305       std::begin(counts), std::end(counts), expected);
306   double p = absl::random_internal::ChiSquarePValue(chi_square, dof);
307 
308   // Log if the chi_square value is above the threshold.
309   if (chi_square > threshold) {
310     for (int i = 0; i < cutoffs.size(); i++) {
311       ABSL_INTERNAL_LOG(
312           INFO, absl::StrFormat("%d : (%f) = %d", i, cutoffs[i], counts[i]));
313     }
314 
315     ABSL_INTERNAL_LOG(
316         INFO, absl::StrCat("mean=", mean(), " stddev=", stddev(), "\n",   //
317                            " expected ", expected, "\n",                  //
318                            kChiSquared, " ", chi_square, " (", p, ")\n",  //
319                            kChiSquared, " @ 0.98 = ", threshold));
320   }
321   return p;
322 }
323 
TEST_P(GaussianDistributionTests,ZTest)324 TEST_P(GaussianDistributionTests, ZTest) {
325   // TODO(absl-team): Run these tests against std::normal_distribution<double>
326   // to validate outcomes are similar.
327   const size_t kSamples = 10000;
328   const auto& param = GetParam();
329   const int expected_failures =
330       std::max(1, static_cast<int>(std::ceil(param.trials * param.p_fail)));
331   const double p = absl::random_internal::RequiredSuccessProbability(
332       param.p_fail, param.trials);
333 
334   int failures = 0;
335   for (int i = 0; i < param.trials; i++) {
336     failures +=
337         SingleZTest<absl::gaussian_distribution<double>>(p, kSamples) ? 0 : 1;
338   }
339   EXPECT_LE(failures, expected_failures);
340 }
341 
TEST_P(GaussianDistributionTests,ChiSquaredTest)342 TEST_P(GaussianDistributionTests, ChiSquaredTest) {
343   const int kTrials = 20;
344   int failures = 0;
345 
346   for (int i = 0; i < kTrials; i++) {
347     double p_value =
348         SingleChiSquaredTest<absl::gaussian_distribution<double>>();
349     if (p_value < 0.0025) {  // 1/400
350       failures++;
351     }
352   }
353   // There is a 0.05% chance of producing at least one failure, so raise the
354   // failure threshold high enough to allow for a flake rate of less than one in
355   // 10,000.
356   EXPECT_LE(failures, 4);
357 }
358 
GenParams()359 std::vector<Param> GenParams() {
360   return {
361       // Mean around 0.
362       Param{0.0, 1.0, 0.01, 100},
363       Param{0.0, 1e2, 0.01, 100},
364       Param{0.0, 1e4, 0.01, 100},
365       Param{0.0, 1e8, 0.01, 100},
366       Param{0.0, 1e16, 0.01, 100},
367       Param{0.0, 1e-3, 0.01, 100},
368       Param{0.0, 1e-5, 0.01, 100},
369       Param{0.0, 1e-9, 0.01, 100},
370       Param{0.0, 1e-17, 0.01, 100},
371 
372       // Mean around 1.
373       Param{1.0, 1.0, 0.01, 100},
374       Param{1.0, 1e2, 0.01, 100},
375       Param{1.0, 1e-2, 0.01, 100},
376 
377       // Mean around 100 / -100
378       Param{1e2, 1.0, 0.01, 100},
379       Param{-1e2, 1.0, 0.01, 100},
380       Param{1e2, 1e6, 0.01, 100},
381       Param{-1e2, 1e6, 0.01, 100},
382 
383       // More extreme
384       Param{1e4, 1e4, 0.01, 100},
385       Param{1e8, 1e4, 0.01, 100},
386       Param{1e12, 1e4, 0.01, 100},
387   };
388 }
389 
ParamName(const::testing::TestParamInfo<Param> & info)390 std::string ParamName(const ::testing::TestParamInfo<Param>& info) {
391   const auto& p = info.param;
392   std::string name = absl::StrCat("mean_", absl::SixDigits(p.mean), "__stddev_",
393                                   absl::SixDigits(p.stddev));
394   return absl::StrReplaceAll(name, {{"+", "_"}, {"-", "_"}, {".", "_"}});
395 }
396 
397 INSTANTIATE_TEST_SUITE_P(All, GaussianDistributionTests,
398                          ::testing::ValuesIn(GenParams()), ParamName);
399 
400 // NOTE: absl::gaussian_distribution is not guaranteed to be stable.
TEST(GaussianDistributionTest,StabilityTest)401 TEST(GaussianDistributionTest, StabilityTest) {
402   // absl::gaussian_distribution stability relies on the underlying zignor
403   // data, absl::random_interna::RandU64ToDouble, std::exp, std::log, and
404   // std::abs.
405   absl::random_internal::sequence_urbg urbg(
406       {0x0003eb76f6f7f755ull, 0xFFCEA50FDB2F953Bull, 0xC332DDEFBE6C5AA5ull,
407        0x6558218568AB9702ull, 0x2AEF7DAD5B6E2F84ull, 0x1521B62829076170ull,
408        0xECDD4775619F1510ull, 0x13CCA830EB61BD96ull, 0x0334FE1EAA0363CFull,
409        0xB5735C904C70A239ull, 0xD59E9E0BCBAADE14ull, 0xEECC86BC60622CA7ull});
410 
411   std::vector<int> output(11);
412 
413   {
414     absl::gaussian_distribution<double> dist;
415     std::generate(std::begin(output), std::end(output),
416                   [&] { return static_cast<int>(10000000.0 * dist(urbg)); });
417 
418     EXPECT_EQ(13, urbg.invocations());
419     EXPECT_THAT(output,  //
420                 testing::ElementsAre(1494, 25518841, 9991550, 1351856,
421                                      -20373238, 3456682, 333530, -6804981,
422                                      -15279580, -16459654, 1494));
423   }
424 
425   urbg.reset();
426   {
427     absl::gaussian_distribution<float> dist;
428     std::generate(std::begin(output), std::end(output),
429                   [&] { return static_cast<int>(1000000.0f * dist(urbg)); });
430 
431     EXPECT_EQ(13, urbg.invocations());
432     EXPECT_THAT(
433         output,  //
434         testing::ElementsAre(149, 2551884, 999155, 135185, -2037323, 345668,
435                              33353, -680498, -1527958, -1645965, 149));
436   }
437 }
438 
439 // This is an implementation-specific test. If any part of the implementation
440 // changes, then it is likely that this test will change as well.
441 // Also, if dependencies of the distribution change, such as RandU64ToDouble,
442 // then this is also likely to change.
TEST(GaussianDistributionTest,AlgorithmBounds)443 TEST(GaussianDistributionTest, AlgorithmBounds) {
444   absl::gaussian_distribution<double> dist;
445 
446   // In ~95% of cases, a single value is used to generate the output.
447   // for all inputs where |x| < 0.750461021389 this should be the case.
448   //
449   // The exact constraints are based on the ziggurat tables, and any
450   // changes to the ziggurat tables may require adjusting these bounds.
451   //
452   // for i in range(0, len(X)-1):
453   //   print i, X[i+1]/X[i], (X[i+1]/X[i] > 0.984375)
454   //
455   // 0.125 <= |values| <= 0.75
456   const uint64_t kValues[] = {
457       0x1000000000000100ull, 0x2000000000000100ull, 0x3000000000000100ull,
458       0x4000000000000100ull, 0x5000000000000100ull, 0x6000000000000100ull,
459       // negative values
460       0x9000000000000100ull, 0xa000000000000100ull, 0xb000000000000100ull,
461       0xc000000000000100ull, 0xd000000000000100ull, 0xe000000000000100ull};
462 
463   // 0.875 <= |values| <= 0.984375
464   const uint64_t kExtraValues[] = {
465       0x7000000000000100ull, 0x7800000000000100ull,  //
466       0x7c00000000000100ull, 0x7e00000000000100ull,  //
467       // negative values
468       0xf000000000000100ull, 0xf800000000000100ull,  //
469       0xfc00000000000100ull, 0xfe00000000000100ull};
470 
471   auto make_box = [](uint64_t v, uint64_t box) {
472     return (v & 0xffffffffffffff80ull) | box;
473   };
474 
475   // The box is the lower 7 bits of the value. When the box == 0, then
476   // the algorithm uses an escape hatch to select the result for large
477   // outputs.
478   for (uint64_t box = 0; box < 0x7f; box++) {
479     for (const uint64_t v : kValues) {
480       // Extra values are added to the sequence to attempt to avoid
481       // infinite loops from rejection sampling on bugs/errors.
482       absl::random_internal::sequence_urbg urbg(
483           {make_box(v, box), 0x0003eb76f6f7f755ull, 0x5FCEA50FDB2F953Bull});
484 
485       auto a = dist(urbg);
486       EXPECT_EQ(1, urbg.invocations()) << box << " " << std::hex << v;
487       if (v & 0x8000000000000000ull) {
488         EXPECT_LT(a, 0.0) << box << " " << std::hex << v;
489       } else {
490         EXPECT_GT(a, 0.0) << box << " " << std::hex << v;
491       }
492     }
493     if (box > 10 && box < 100) {
494       // The center boxes use the fast algorithm for more
495       // than 98.4375% of values.
496       for (const uint64_t v : kExtraValues) {
497         absl::random_internal::sequence_urbg urbg(
498             {make_box(v, box), 0x0003eb76f6f7f755ull, 0x5FCEA50FDB2F953Bull});
499 
500         auto a = dist(urbg);
501         EXPECT_EQ(1, urbg.invocations()) << box << " " << std::hex << v;
502         if (v & 0x8000000000000000ull) {
503           EXPECT_LT(a, 0.0) << box << " " << std::hex << v;
504         } else {
505           EXPECT_GT(a, 0.0) << box << " " << std::hex << v;
506         }
507       }
508     }
509   }
510 
511   // When the box == 0, the fallback algorithm uses a ratio of uniforms,
512   // which consumes 2 additional values from the urbg.
513   // Fallback also requires that the initial value be > 0.9271586026096681.
514   auto make_fallback = [](uint64_t v) { return (v & 0xffffffffffffff80ull); };
515 
516   double tail[2];
517   {
518     // 0.9375
519     absl::random_internal::sequence_urbg urbg(
520         {make_fallback(0x7800000000000000ull), 0x13CCA830EB61BD96ull,
521          0x00000076f6f7f755ull});
522     tail[0] = dist(urbg);
523     EXPECT_EQ(3, urbg.invocations());
524     EXPECT_GT(tail[0], 0);
525   }
526   {
527     // -0.9375
528     absl::random_internal::sequence_urbg urbg(
529         {make_fallback(0xf800000000000000ull), 0x13CCA830EB61BD96ull,
530          0x00000076f6f7f755ull});
531     tail[1] = dist(urbg);
532     EXPECT_EQ(3, urbg.invocations());
533     EXPECT_LT(tail[1], 0);
534   }
535   EXPECT_EQ(tail[0], -tail[1]);
536   EXPECT_EQ(418610, static_cast<int64_t>(tail[0] * 100000.0));
537 
538   // When the box != 0, the fallback algorithm computes a wedge function.
539   // Depending on the box, the threshold for varies as high as
540   // 0.991522480228.
541   {
542     // 0.9921875, 0.875
543     absl::random_internal::sequence_urbg urbg(
544         {make_box(0x7f00000000000000ull, 120), 0xe000000000000001ull,
545          0x13CCA830EB61BD96ull});
546     tail[0] = dist(urbg);
547     EXPECT_EQ(2, urbg.invocations());
548     EXPECT_GT(tail[0], 0);
549   }
550   {
551     // -0.9921875, 0.875
552     absl::random_internal::sequence_urbg urbg(
553         {make_box(0xff00000000000000ull, 120), 0xe000000000000001ull,
554          0x13CCA830EB61BD96ull});
555     tail[1] = dist(urbg);
556     EXPECT_EQ(2, urbg.invocations());
557     EXPECT_LT(tail[1], 0);
558   }
559   EXPECT_EQ(tail[0], -tail[1]);
560   EXPECT_EQ(61948, static_cast<int64_t>(tail[0] * 100000.0));
561 
562   // Fallback rejected, try again.
563   {
564     // -0.9921875, 0.0625
565     absl::random_internal::sequence_urbg urbg(
566         {make_box(0xff00000000000000ull, 120), 0x1000000000000001,
567          make_box(0x1000000000000100ull, 50), 0x13CCA830EB61BD96ull});
568     dist(urbg);
569     EXPECT_EQ(3, urbg.invocations());
570   }
571 }
572 
573 }  // namespace
574