#define PROB_DISTR_PRIVATE
#include "orconfig.h"
#include "test/test.h"
#include "core/or/or.h"
#include "lib/math/prob_distr.h"
#include "lib/math/fp.h"
#include "lib/crypt_ops/crypto_rand.h"
#include "test/rng_test_helpers.h"
#include <float.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static size_t
floor_to_size_t(double d)
{
double integral_d = floor(d);
return (size_t)integral_d;
}
static size_t
ceil_to_size_t(double d)
{
double integral_d = ceil(d);
return (size_t)integral_d;
}
static double
logpmf_geometric(unsigned n, double p)
{
if (p >= 1) {
if (n == 1)
return 0;
else
return -HUGE_VAL;
}
return (n - 1)*log1p(-p) + log(p);
}
static double
logistichalf(double x)
{
if (x < log(DBL_EPSILON/8)) {
return -0.5;
} else {
return -expm1(-x)/(2*(1 + exp(-x)));
}
}
static double
logsumexp(double *A, size_t n)
{
double maximum, sum;
size_t i;
if (n == 0)
return log(0);
maximum = A[0];
for (i = 1; i < n; i++) {
if (A[i] > maximum)
maximum = A[i];
}
sum = 0;
for (i = n; i --> 0;)
sum += exp(A[i] - maximum);
return log(sum) + maximum;
}
static double
log1mexp(double x)
{
if (-log(2) < x)
return log(-expm1(x));
else
return log1p(-exp(x));
}
#define arraycount(A) (sizeof(A)/sizeof(A[0]))
static double
relerr(double expected, double actual)
{
if (fabs(expected) <= 0 || tor_isinf(expected)) {
if (actual <= expected && actual >= expected)
return 0;
else
return 1;
} else {
return fabs((expected - actual)/expected);
}
}
#define CHECK_RELERR(expected, actual) do { \
double check_expected = (expected); \
double check_actual = (actual); \
const char *str_expected = #expected; \
const char *str_actual = #actual; \
double check_relerr = relerr(expected, actual); \
if (!(relerr(check_expected, check_actual) <= relerr_bound)) { \
log_warn(LD_GENERAL, "%s:%d: case %u: relerr(%s=%.17e, %s=%.17e)" \
" = %.17e > %.17e\n", \
__func__, __LINE__, (unsigned) i, \
str_expected, check_expected, \
str_actual, check_actual, \
check_relerr, relerr_bound); \
ok = false; \
} \
} while (0)
#define CHECK_LE(a, b) do { \
double check_a = (a); \
double check_b = (b); \
const char *str_a = #a; \
const char *str_b = #b; \
if (!(check_a <= check_b)) { \
log_warn(LD_GENERAL, "%s:%d: case %u: %s=%.17e > %s=%.17e\n", \
__func__, __LINE__, (unsigned) i, \
str_a, check_a, str_b, check_b); \
ok = false; \
} \
} while (0)
static void
test_logit_logistic(void *arg)
{
(void) arg;
static const struct {
double x;
double p;
double phalf;
} cases[] = {
{ -HUGE_VAL, 0, -0.5 },
{ -1000, 0, -0.5 },
{ -710, 4.47628622567513e-309, -0.5 },
{ -708, 3.307553003638408e-308, -0.5 },
{ -2, .11920292202211755, -.3807970779778824 },
{ -1.0000001, .2689414017088022, -.23105859829119776 },
{ -1, .2689414213699951, -.23105857863000487 },
{ -0.9999999, .26894144103118883, -.2310585589688111 },
{ -4.000000000537333e-5, .49999, -1.0000000000010001e-5 },
{ -4.000000000533334e-5, .49999, -.00001 },
{ -4.000000108916878e-9, .499999999, -1.0000000272292198e-9 },
{ -4e-9, .499999999, -1e-9 },
{ -4e-16, .5, -1e-16 },
{ -4e-300, .5, -1e-300 },
{ 0, .5, 0 },
{ 4e-300, .5, 1e-300 },
{ 4e-16, .5, 1e-16 },
{ 3.999999886872274e-9, .500000001, 9.999999717180685e-10 },
{ 4e-9, .500000001, 1e-9 },
{ 4.0000000005333336e-5, .50001, .00001 },
{ 8.000042667076272e-3, .502, .002 },
{ 0.9999999, .7310585589688111, .2310585589688111 },
{ 1, .7310585786300049, .23105857863000487 },
{ 1.0000001, .7310585982911977, .23105859829119774 },
{ 2, .8807970779778823, .3807970779778824 },
{ 708, 1, .5 },
{ 710, 1, .5 },
{ 1000, 1, .5 },
{ HUGE_VAL, 1, .5 },
};
double relerr_bound = 3e-15;
size_t i;
bool ok = true;
for (i = 0; i < arraycount(cases); i++) {
double x = cases[i].x;
double p = cases[i].p;
double phalf = cases[i].phalf;
CHECK_RELERR(logistic(x), cdf_logistic(x, 0, 1));
CHECK_RELERR(logistic(-x), sf_logistic(x, 0, 1));
CHECK_RELERR(logit(p), icdf_logistic(p, 0, 1));
CHECK_RELERR(-logit(p), isf_logistic(p, 0, 1));
CHECK_RELERR(cdf_logistic(x, 0, 1), cdf_logistic(x*2, 0, 2));
CHECK_RELERR(sf_logistic(x, 0, 1), sf_logistic(x*2, 0, 2));
CHECK_RELERR(icdf_logistic(p, 0, 1), icdf_logistic(p, 0, 2)/2);
CHECK_RELERR(isf_logistic(p, 0, 1), isf_logistic(p, 0, 2)/2);
CHECK_RELERR(cdf_logistic(x, 0, 1), cdf_logistic(x/2, 0, .5));
CHECK_RELERR(sf_logistic(x, 0, 1), sf_logistic(x/2, 0, .5));
CHECK_RELERR(icdf_logistic(p, 0, 1), icdf_logistic(p, 0,.5)*2);
CHECK_RELERR(isf_logistic(p, 0, 1), isf_logistic(p, 0, .5)*2);
CHECK_RELERR(cdf_logistic(x, 0, 1), cdf_logistic(x*2 + 1, 1, 2));
CHECK_RELERR(sf_logistic(x, 0, 1), sf_logistic(x*2 + 1, 1, 2));
if (fabs(p) > DBL_EPSILON && fabs(p) < 0.4) {
CHECK_RELERR(icdf_logistic(p, 0, 1),
(icdf_logistic(p, 1, 2) - 1)/2);
CHECK_RELERR(isf_logistic(p, 0, 1),
(isf_logistic(p, 1, 2) - 1)/2);
}
CHECK_RELERR(p, logistic(x));
CHECK_RELERR(phalf, logistichalf(x));
if ((0 < p && p < 1) || tor_isinf(x)) {
if (phalf >= p - 0.5 && phalf <= p - 0.5)
CHECK_RELERR(x, logit(p));
if (p >= 0.5 + phalf && p <= 0.5 + phalf)
CHECK_RELERR(x, logithalf(phalf));
}
CHECK_RELERR(-phalf, logistichalf(-x));
if (fabs(phalf) < 0.5 || tor_isinf(x))
CHECK_RELERR(-x, logithalf(-phalf));
if (p < 1 || tor_isinf(x)) {
CHECK_RELERR(1 - p, logistic(-x));
if (p > .75 || tor_isinf(x))
CHECK_RELERR(-x, logit(1 - p));
} else {
CHECK_LE(logistic(-x), 1e-300);
}
}
for (i = 0; i <= 100; i++) {
double p0 = (double)i/100;
CHECK_RELERR(logit(p0/(1 + M_E)), sample_logistic(0, 0, p0));
CHECK_RELERR(-logit(p0/(1 + M_E)), sample_logistic(1, 0, p0));
CHECK_RELERR(logithalf(p0*(0.5 - 1/(1 + M_E))),
sample_logistic(0, 1, p0));
CHECK_RELERR(-logithalf(p0*(0.5 - 1/(1 + M_E))),
sample_logistic(1, 1, p0));
}
if (!ok)
printf("fail logit/logistic / logistic cdf/sf\n");
tt_assert(ok);
done:
;
}
static void
test_log_logistic(void *arg)
{
(void) arg;
static const struct {
double x;
double p;
double np;
} cases[] = {
{ 0, 0, 1 },
{ 1e-300, 1e-300, 1 },
{ 1e-17, 1e-17, 1 },
{ 1e-15, 1e-15, .999999999999999 },
{ .1, .09090909090909091, .90909090909090909 },
{ .25, .2, .8 },
{ .5, .33333333333333333, .66666666666666667 },
{ .75, .42857142857142855, .5714285714285714 },
{ .9999, .49997499874993756, .5000250012500626 },
{ .99999999, .49999999749999996, .5000000025 },
{ .999999999999999, .49999999999999994, .5000000000000002 },
{ 1, .5, .5 },
};
double relerr_bound = 3e-15;
size_t i;
bool ok = true;
for (i = 0; i < arraycount(cases); i++) {
double x = cases[i].x;
double p = cases[i].p;
double np = cases[i].np;
CHECK_RELERR(p, cdf_log_logistic(x, 1, 1));
CHECK_RELERR(p, cdf_log_logistic(x/2, .5, 1));
CHECK_RELERR(p, cdf_log_logistic(x*2, 2, 1));
CHECK_RELERR(p, cdf_log_logistic(sqrt(x), 1, 2));
CHECK_RELERR(p, cdf_log_logistic(sqrt(x)/2, .5, 2));
CHECK_RELERR(p, cdf_log_logistic(sqrt(x)*2, 2, 2));
if (2*sqrt(DBL_MIN) < x) {
CHECK_RELERR(p, cdf_log_logistic(x*x, 1, .5));
CHECK_RELERR(p, cdf_log_logistic(x*x/2, .5, .5));
CHECK_RELERR(p, cdf_log_logistic(x*x*2, 2, .5));
}
CHECK_RELERR(np, sf_log_logistic(x, 1, 1));
CHECK_RELERR(np, sf_log_logistic(x/2, .5, 1));
CHECK_RELERR(np, sf_log_logistic(x*2, 2, 1));
CHECK_RELERR(np, sf_log_logistic(sqrt(x), 1, 2));
CHECK_RELERR(np, sf_log_logistic(sqrt(x)/2, .5, 2));
CHECK_RELERR(np, sf_log_logistic(sqrt(x)*2, 2, 2));
if (2*sqrt(DBL_MIN) < x) {
CHECK_RELERR(np, sf_log_logistic(x*x, 1, .5));
CHECK_RELERR(np, sf_log_logistic(x*x/2, .5, .5));
CHECK_RELERR(np, sf_log_logistic(x*x*2, 2, .5));
}
CHECK_RELERR(np, cdf_log_logistic(1/x, 1, 1));
CHECK_RELERR(np, cdf_log_logistic(1/(2*x), .5, 1));
CHECK_RELERR(np, cdf_log_logistic(2/x, 2, 1));
CHECK_RELERR(np, cdf_log_logistic(1/sqrt(x), 1, 2));
CHECK_RELERR(np, cdf_log_logistic(1/(2*sqrt(x)), .5, 2));
CHECK_RELERR(np, cdf_log_logistic(2/sqrt(x), 2, 2));
if (2*sqrt(DBL_MIN) < x && x < 1/(2*sqrt(DBL_MIN))) {
CHECK_RELERR(np, cdf_log_logistic(1/(x*x), 1, .5));
CHECK_RELERR(np, cdf_log_logistic(1/(2*x*x), .5, .5));
CHECK_RELERR(np, cdf_log_logistic(2/(x*x), 2, .5));
}
CHECK_RELERR(p, sf_log_logistic(1/x, 1, 1));
CHECK_RELERR(p, sf_log_logistic(1/(2*x), .5, 1));
CHECK_RELERR(p, sf_log_logistic(2/x, 2, 1));
CHECK_RELERR(p, sf_log_logistic(1/sqrt(x), 1, 2));
CHECK_RELERR(p, sf_log_logistic(1/(2*sqrt(x)), .5, 2));
CHECK_RELERR(p, sf_log_logistic(2/sqrt(x), 2, 2));
if (2*sqrt(DBL_MIN) < x && x < 1/(2*sqrt(DBL_MIN))) {
CHECK_RELERR(p, sf_log_logistic(1/(x*x), 1, .5));
CHECK_RELERR(p, sf_log_logistic(1/(2*x*x), .5, .5));
CHECK_RELERR(p, sf_log_logistic(2/(x*x), 2, .5));
}
CHECK_RELERR(x, icdf_log_logistic(p, 1, 1));
CHECK_RELERR(x/2, icdf_log_logistic(p, .5, 1));
CHECK_RELERR(x*2, icdf_log_logistic(p, 2, 1));
CHECK_RELERR(x, icdf_log_logistic(p, 1, 1));
CHECK_RELERR(sqrt(x)/2, icdf_log_logistic(p, .5, 2));
CHECK_RELERR(sqrt(x)*2, icdf_log_logistic(p, 2, 2));
CHECK_RELERR(sqrt(x), icdf_log_logistic(p, 1, 2));
CHECK_RELERR(x*x/2, icdf_log_logistic(p, .5, .5));
CHECK_RELERR(x*x*2, icdf_log_logistic(p, 2, .5));
if (np < .9) {
CHECK_RELERR(x, isf_log_logistic(np, 1, 1));
CHECK_RELERR(x/2, isf_log_logistic(np, .5, 1));
CHECK_RELERR(x*2, isf_log_logistic(np, 2, 1));
CHECK_RELERR(sqrt(x), isf_log_logistic(np, 1, 2));
CHECK_RELERR(sqrt(x)/2, isf_log_logistic(np, .5, 2));
CHECK_RELERR(sqrt(x)*2, isf_log_logistic(np, 2, 2));
CHECK_RELERR(x*x, isf_log_logistic(np, 1, .5));
CHECK_RELERR(x*x/2, isf_log_logistic(np, .5, .5));
CHECK_RELERR(x*x*2, isf_log_logistic(np, 2, .5));
CHECK_RELERR(1/x, icdf_log_logistic(np, 1, 1));
CHECK_RELERR(1/(2*x), icdf_log_logistic(np, .5, 1));
CHECK_RELERR(2/x, icdf_log_logistic(np, 2, 1));
CHECK_RELERR(1/sqrt(x), icdf_log_logistic(np, 1, 2));
CHECK_RELERR(1/(2*sqrt(x)),
icdf_log_logistic(np, .5, 2));
CHECK_RELERR(2/sqrt(x), icdf_log_logistic(np, 2, 2));
CHECK_RELERR(1/(x*x), icdf_log_logistic(np, 1, .5));
CHECK_RELERR(1/(2*x*x), icdf_log_logistic(np, .5, .5));
CHECK_RELERR(2/(x*x), icdf_log_logistic(np, 2, .5));
}
CHECK_RELERR(1/x, isf_log_logistic(p, 1, 1));
CHECK_RELERR(1/(2*x), isf_log_logistic(p, .5, 1));
CHECK_RELERR(2/x, isf_log_logistic(p, 2, 1));
CHECK_RELERR(1/sqrt(x), isf_log_logistic(p, 1, 2));
CHECK_RELERR(1/(2*sqrt(x)), isf_log_logistic(p, .5, 2));
CHECK_RELERR(2/sqrt(x), isf_log_logistic(p, 2, 2));
CHECK_RELERR(1/(x*x), isf_log_logistic(p, 1, .5));
CHECK_RELERR(1/(2*x*x), isf_log_logistic(p, .5, .5));
CHECK_RELERR(2/(x*x), isf_log_logistic(p, 2, .5));
}
for (i = 0; i <= 100; i++) {
double p0 = (double)i/100;
CHECK_RELERR(0.5*p0/(1 - 0.5*p0), sample_log_logistic(0, p0));
CHECK_RELERR((1 - 0.5*p0)/(0.5*p0),
sample_log_logistic(1, p0));
}
if (!ok)
printf("fail log logistic cdf/sf\n");
tt_assert(ok);
done:
;
}
static void
test_weibull(void *arg)
{
(void) arg;
static const struct {
double x;
double p;
double np;
} cases[] = {
{ 0, 0, 1 },
{ 1e-300, 1e-300, 1 },
{ 1e-17, 1e-17, 1 },
{ .1, .09516258196404043, .9048374180359595 },
{ .5, .3934693402873666, .6065306597126334 },
{ .6931471805599453, .5, .5 },
{ 1, .6321205588285577, .36787944117144233 },
{ 10, .9999546000702375, 4.5399929762484854e-5 },
{ 36, .9999999999999998, 2.319522830243569e-16 },
{ 37, .9999999999999999, 8.533047625744066e-17 },
{ 38, 1, 3.1391327920480296e-17 },
{ 100, 1, 3.720075976020836e-44 },
{ 708, 1, 3.307553003638408e-308 },
{ 710, 1, 4.47628622567513e-309 },
{ 1000, 1, 0 },
{ HUGE_VAL, 1, 0 },
};
double relerr_bound = 3e-15;
size_t i;
bool ok = true;
for (i = 0; i < arraycount(cases); i++) {
double x = cases[i].x;
double p = cases[i].p;
double np = cases[i].np;
CHECK_RELERR(p, cdf_weibull(x, 1, 1));
CHECK_RELERR(p, cdf_weibull(x/2, .5, 1));
CHECK_RELERR(p, cdf_weibull(x*2, 2, 1));
if (x <= 0 ||
sqrt(DBL_MIN) <= x) {
CHECK_RELERR(p, cdf_weibull(x*x, 1, .5));
CHECK_RELERR(p, cdf_weibull(x*x/2, .5, .5));
CHECK_RELERR(p, cdf_weibull(x*x*2, 2, .5));
}
CHECK_RELERR(p, cdf_weibull(sqrt(x), 1, 2));
CHECK_RELERR(p, cdf_weibull(sqrt(x)/2, .5, 2));
CHECK_RELERR(p, cdf_weibull(sqrt(x)*2, 2, 2));
CHECK_RELERR(np, sf_weibull(x, 1, 1));
CHECK_RELERR(np, sf_weibull(x/2, .5, 1));
CHECK_RELERR(np, sf_weibull(x*2, 2, 1));
CHECK_RELERR(np, sf_weibull(x*x, 1, .5));
CHECK_RELERR(np, sf_weibull(x*x/2, .5, .5));
CHECK_RELERR(np, sf_weibull(x*x*2, 2, .5));
if (x >= 10) {
double t = -expm1(-x*(2*DBL_EPSILON + DBL_EPSILON));
relerr_bound = t + DBL_EPSILON + t*DBL_EPSILON;
if (relerr_bound < 3e-15)
relerr_bound = 3e-15;
CHECK_RELERR(np, sf_weibull(sqrt(x), 1, 2));
CHECK_RELERR(np, sf_weibull(sqrt(x)/2, .5, 2));
CHECK_RELERR(np, sf_weibull(sqrt(x)*2, 2, 2));
}
if (p <= 0.75) {
CHECK_RELERR(x, icdf_weibull(p, 1, 1));
CHECK_RELERR(x/2, icdf_weibull(p, .5, 1));
CHECK_RELERR(x*2, icdf_weibull(p, 2, 1));
}
if (p >= 0.25 && !tor_isinf(x) && np > 0) {
CHECK_RELERR(x, isf_weibull(np, 1, 1));
CHECK_RELERR(x/2, isf_weibull(np, .5, 1));
CHECK_RELERR(x*2, isf_weibull(np, 2, 1));
}
}
for (i = 0; i <= 100; i++) {
double p0 = (double)i/100;
CHECK_RELERR(3*sqrt(-log(p0/2)), sample_weibull(0, p0, 3, 2));
CHECK_RELERR(3*sqrt(-log1p(-p0/2)),
sample_weibull(1, p0, 3, 2));
}
if (!ok)
printf("fail Weibull cdf/sf\n");
tt_assert(ok);
done:
;
}
static void
test_genpareto(void *arg)
{
(void) arg;
struct {
double xi, x, p, np;
} cases[] = {
{ 0, 0, 0, 1 },
{ 1e-300, .004, 3.992010656008528e-3, .9960079893439915 },
{ 1e-300, .1, .09516258196404043, .9048374180359595 },
{ 1e-300, 1, .6321205588285577, .36787944117144233 },
{ 1e-300, 10, .9999546000702375, 4.5399929762484854e-5 },
{ 1e-200, 1e-16, 9.999999999999999e-17, .9999999999999999 },
{ 1e-16, 1e-200, 9.999999999999998e-201, 1 },
{ 1e-16, 1e-16, 1e-16, 1 },
{ 1e-16, .004, 3.992010656008528e-3, .9960079893439915 },
{ 1e-16, .1, .09516258196404043, .9048374180359595 },
{ 1e-16, 1, .6321205588285577, .36787944117144233 },
{ 1e-16, 10, .9999546000702375, 4.539992976248509e-5 },
{ 1e-10, 1e-6, 9.999995000001667e-7, .9999990000005 },
{ 1e-8, 1e-8, 9.999999950000001e-9, .9999999900000001 },
{ 1, 1e-300, 1e-300, 1 },
{ 1, 1e-16, 1e-16, .9999999999999999 },
{ 1, .1, .09090909090909091, .9090909090909091 },
{ 1, 1, .5, .5 },
{ 1, 10, .9090909090909091, .0909090909090909 },
{ 1, 100, .9900990099009901, .0099009900990099 },
{ 1, 1000, .999000999000999, 9.990009990009992e-4 },
{ 10, 1e-300, 1e-300, 1 },
{ 10, 1e-16, 9.999999999999995e-17, .9999999999999999 },
{ 10, .1, .06696700846319258, .9330329915368074 },
{ 10, 1, .21320655780322778, .7867934421967723 },
{ 10, 10, .3696701667040189, .6303298332959811 },
{ 10, 100, .49886285755007337, .5011371424499267 },
{ 10, 1000, .6018968102992647, .3981031897007353 },
};
double xi_array[] = { -1.5, -1, -1e-30, 0, 1e-30, 1, 1.5 };
size_t i, j;
double relerr_bound = 3e-15;
bool ok = true;
for (i = 0; i < arraycount(cases); i++) {
double xi = cases[i].xi;
double x = cases[i].x;
double p = cases[i].p;
double np = cases[i].np;
CHECK_RELERR(p, cdf_genpareto(x, 0, 1, xi));
CHECK_RELERR(p, cdf_genpareto(x*2, 0, 2, xi));
CHECK_RELERR(p, cdf_genpareto(x/2, 0, .5, xi));
CHECK_RELERR(np, sf_genpareto(x, 0, 1, xi));
CHECK_RELERR(np, sf_genpareto(x*2, 0, 2, xi));
CHECK_RELERR(np, sf_genpareto(x/2, 0, .5, xi));
if (p < .5) {
CHECK_RELERR(x, icdf_genpareto(p, 0, 1, xi));
CHECK_RELERR(x*2, icdf_genpareto(p, 0, 2, xi));
CHECK_RELERR(x/2, icdf_genpareto(p, 0, .5, xi));
}
if (np < .5) {
CHECK_RELERR(x, isf_genpareto(np, 0, 1, xi));
CHECK_RELERR(x*2, isf_genpareto(np, 0, 2, xi));
CHECK_RELERR(x/2, isf_genpareto(np, 0, .5, xi));
}
}
for (i = 0; i < arraycount(xi_array); i++) {
for (j = 0; j <= 100; j++) {
double p0 = (j == 0 ? 2*DBL_MIN : (double)j/100);
if (fabs(xi_array[i]) <= 0) {
CHECK_RELERR(-log(p0/2),
sample_genpareto(0, p0, 0));
CHECK_RELERR(-log1p(-p0/2),
sample_genpareto(1, p0, 0));
} else {
CHECK_RELERR(expm1(-xi_array[i]*log(p0/2))/xi_array[i],
sample_genpareto(0, p0, xi_array[i]));
CHECK_RELERR((j == 0 ? DBL_MIN :
expm1(-xi_array[i]*log1p(-p0/2))/xi_array[i]),
sample_genpareto(1, p0, xi_array[i]));
}
CHECK_RELERR(isf_genpareto(p0/2, 0, 1, xi_array[i]),
sample_genpareto(0, p0, xi_array[i]));
CHECK_RELERR(icdf_genpareto(p0/2, 0, 1, xi_array[i]),
sample_genpareto(1, p0, xi_array[i]));
}
}
tt_assert(ok);
done:
;
}
static void
test_uniform_interval(void *arg)
{
(void) arg;
struct {
double t, a, b;
} cases[] = {
{ 0, 0, 0 },
{ 0, 0, 1 },
{ 0, 1.0000000000000007, 3.999999999999995 },
{ 0, 4000, 4000 },
{ 0.42475836677491291, 4000, 4000 },
{ 0, -DBL_MAX, DBL_MAX },
{ 0.25, -DBL_MAX, DBL_MAX },
{ 0.5, -DBL_MAX, DBL_MAX },
};
size_t i = 0;
bool ok = true;
for (i = 0; i < arraycount(cases); i++) {
double t = cases[i].t;
double a = cases[i].a;
double b = cases[i].b;
CHECK_LE(a, sample_uniform_interval(t, a, b));
CHECK_LE(sample_uniform_interval(t, a, b), b);
CHECK_LE(a, sample_uniform_interval(1 - t, a, b));
CHECK_LE(sample_uniform_interval(1 - t, a, b), b);
CHECK_LE(sample_uniform_interval(t, -b, -a), -a);
CHECK_LE(-b, sample_uniform_interval(t, -b, -a));
CHECK_LE(sample_uniform_interval(1 - t, -b, -a), -a);
CHECK_LE(-b, sample_uniform_interval(1 - t, -b, -a));
}
tt_assert(ok);
done:
;
}
static const size_t NSAMPLES = 100000;
static const unsigned NTRIALS = 4;
static const unsigned NPASSES_MIN = 1;
#define PSI_DF 100
static const double PSI_CRITICAL = 135.807;
static bool
psi_test(const size_t C[PSI_DF], const double logP[PSI_DF], size_t N)
{
double psi = 0;
double c = 0;
double t, u;
size_t i;
for (i = 0; i < PSI_DF; i++) {
if (C[i] == 0)
continue;
t = C[i]*(log((double)C[i]/N) - logP[i]) - c;
u = psi + t;
c = (u - psi) - t;
psi = u;
}
psi *= 2;
return psi <= PSI_CRITICAL;
}
static bool
test_stochastic_geometric_impl(double p)
{
const struct geometric_t geometric = {
.base = GEOMETRIC(geometric),
.p = p,
};
double logP[PSI_DF] = {0};
unsigned ntry = NTRIALS, npass = 0;
unsigned i;
size_t j;
for (i = 0; i < PSI_DF - 1; i++)
logP[i] = logpmf_geometric(i + 1, p);
logP[PSI_DF - 1] = log1mexp(logsumexp(logP, PSI_DF - 1));
while (ntry --> 0) {
size_t C[PSI_DF] = {0};
for (j = 0; j < NSAMPLES; j++) {
double n_tmp = dist_sample(&geometric.base);
tor_assert(ceil(n_tmp) <= n_tmp && ceil(n_tmp) >= n_tmp);
tor_assert(n_tmp >= 1);
tor_assert(n_tmp <= (double)UINT_MAX);
unsigned n = (unsigned) n_tmp;
if (n > PSI_DF)
n = PSI_DF;
C[n - 1]++;
}
if (psi_test(C, logP, NSAMPLES)) {
if (++npass >= NPASSES_MIN)
break;
}
}
if (npass >= NPASSES_MIN) {
return true;
} else {
printf("fail %s sampler\n", "geometric");
return false;
}
}
static void
bin_cdfs(const struct dist_t *dist, double lo, double hi, double *logP,
size_t n)
{
#define CDF(x) dist_cdf(dist, x)
#define SF(x) dist_sf(dist, x)
const double w = (hi - lo)/(n - 2);
double halfway = dist_icdf(dist, 0.5);
double x_0, x_1;
size_t i;
size_t n2 = ceil_to_size_t((halfway - lo)/w);
tor_assert(lo <= halfway);
tor_assert(halfway <= hi);
tor_assert(n2 <= n);
x_1 = lo;
logP[0] = log(CDF(x_1) - 0);
for (i = 1; i < n2; i++) {
x_0 = x_1;
x_1 = (i <= n/2 ? lo + i*w : hi - (n - 2 - i)*w);
logP[i] = log(CDF(x_1) - CDF(x_0));
}
x_0 = hi;
logP[n - 1] = log(SF(x_0) - 0);
for (i = 1; i < n - n2; i++) {
x_1 = x_0;
x_0 = (i <= n/2 ? hi - i*w : lo + (n - 2 - i)*w);
logP[n - i - 1] = log(SF(x_0) - SF(x_1));
}
#undef SF
#undef CDF
}
static void
bin_samples(const struct dist_t *dist, double lo, double hi, size_t *C,
size_t n)
{
const double w = (hi - lo)/(n - 2);
size_t i;
for (i = 0; i < NSAMPLES; i++) {
double x = dist_sample(dist);
size_t bin;
if (x < lo)
bin = 0;
else if (x < hi)
bin = 1 + floor_to_size_t((x - lo)/w);
else
bin = n - 1;
tor_assert(bin < n);
C[bin]++;
}
}
static bool
test_psi_dist_sample(const struct dist_t *dist)
{
double logP[PSI_DF] = {0};
unsigned ntry = NTRIALS, npass = 0;
double lo = dist_icdf(dist, 1/(double)(PSI_DF + 2));
double hi = dist_isf(dist, 1/(double)(PSI_DF + 2));
bin_cdfs(dist, lo, hi, logP, PSI_DF);
while (ntry --> 0) {
size_t C[PSI_DF] = {0};
bin_samples(dist, lo, hi, C, PSI_DF);
if (psi_test(C, logP, NSAMPLES)) {
if (++npass >= NPASSES_MIN)
break;
}
}
if (npass >= NPASSES_MIN) {
return true;
} else {
printf("fail %s sampler\n", dist_name(dist));
return false;
}
}
static void
write_stochastic_warning(void)
{
if (tinytest_cur_test_has_failed()) {
printf("\n"
"NOTE: This is a stochastic test, and we expect it to fail from\n"
"time to time, with some low probability. If you see it fail more\n"
"than one trial in 100, though, please tell us.\n\n");
}
}
static void
test_stochastic_uniform(void *arg)
{
(void) arg;
const struct uniform_t uniform01 = {
.base = UNIFORM(uniform01),
.a = 0,
.b = 1,
};
const struct uniform_t uniform_pos = {
.base = UNIFORM(uniform_pos),
.a = 1.23,
.b = 4.56,
};
const struct uniform_t uniform_neg = {
.base = UNIFORM(uniform_neg),
.a = -10,
.b = -1,
};
const struct uniform_t uniform_cross = {
.base = UNIFORM(uniform_cross),
.a = -1.23,
.b = 4.56,
};
const struct uniform_t uniform_subnormal = {
.base = UNIFORM(uniform_subnormal),
.a = 4e-324,
.b = 4e-310,
};
const struct uniform_t uniform_subnormal_cross = {
.base = UNIFORM(uniform_subnormal_cross),
.a = -4e-324,
.b = 4e-310,
};
bool ok = true, tests_failed = true;
testing_enable_reproducible_rng();
ok &= test_psi_dist_sample(&uniform01.base);
ok &= test_psi_dist_sample(&uniform_pos.base);
ok &= test_psi_dist_sample(&uniform_neg.base);
ok &= test_psi_dist_sample(&uniform_cross.base);
ok &= test_psi_dist_sample(&uniform_subnormal.base);
ok &= test_psi_dist_sample(&uniform_subnormal_cross.base);
tt_assert(ok);
tests_failed = false;
done:
if (tests_failed) {
write_stochastic_warning();
}
testing_disable_reproducible_rng();
}
static bool
test_stochastic_logistic_impl(double mu, double sigma)
{
const struct logistic_t dist = {
.base = LOGISTIC(dist),
.mu = mu,
.sigma = sigma,
};
return test_psi_dist_sample(&dist.base);
}
static bool
test_stochastic_log_logistic_impl(double alpha, double beta)
{
const struct log_logistic_t dist = {
.base = LOG_LOGISTIC(dist),
.alpha = alpha,
.beta = beta,
};
return test_psi_dist_sample(&dist.base);
}
static bool
test_stochastic_weibull_impl(double lambda, double k)
{
const struct weibull_t dist = {
.base = WEIBULL(dist),
.lambda = lambda,
.k = k,
};
return test_psi_dist_sample(&dist.base);
}
static bool
test_stochastic_genpareto_impl(double mu, double sigma, double xi)
{
const struct genpareto_t dist = {
.base = GENPARETO(dist),
.mu = mu,
.sigma = sigma,
.xi = xi,
};
return test_psi_dist_sample(&dist.base);
}
static void
test_stochastic_genpareto(void *arg)
{
bool ok = 0;
bool tests_failed = true;
(void) arg;
testing_enable_reproducible_rng();
ok = test_stochastic_genpareto_impl(0, 1, -0.25);
tt_assert(ok);
ok = test_stochastic_genpareto_impl(0, 1, -1e-30);
tt_assert(ok);
ok = test_stochastic_genpareto_impl(0, 1, 0);
tt_assert(ok);
ok = test_stochastic_genpareto_impl(0, 1, 1e-30);
tt_assert(ok);
ok = test_stochastic_genpareto_impl(0, 1, 0.25);
tt_assert(ok);
ok = test_stochastic_genpareto_impl(-1, 1, -0.25);
tt_assert(ok);
ok = test_stochastic_genpareto_impl(1, 2, 0.25);
tt_assert(ok);
tests_failed = false;
done:
if (tests_failed) {
write_stochastic_warning();
}
testing_disable_reproducible_rng();
}
static void
test_stochastic_geometric(void *arg)
{
bool ok = 0;
bool tests_failed = true;
(void) arg;
testing_enable_reproducible_rng();
ok = test_stochastic_geometric_impl(0.1);
tt_assert(ok);
ok = test_stochastic_geometric_impl(0.5);
tt_assert(ok);
ok = test_stochastic_geometric_impl(0.9);
tt_assert(ok);
ok = test_stochastic_geometric_impl(1);
tt_assert(ok);
tests_failed = false;
done:
if (tests_failed) {
write_stochastic_warning();
}
testing_disable_reproducible_rng();
}
static void
test_stochastic_logistic(void *arg)
{
bool ok = 0;
bool tests_failed = true;
(void) arg;
testing_enable_reproducible_rng();
ok = test_stochastic_logistic_impl(0, 1);
tt_assert(ok);
ok = test_stochastic_logistic_impl(0, 1e-16);
tt_assert(ok);
ok = test_stochastic_logistic_impl(1, 10);
tt_assert(ok);
ok = test_stochastic_logistic_impl(-10, 100);
tt_assert(ok);
tests_failed = false;
done:
if (tests_failed) {
write_stochastic_warning();
}
testing_disable_reproducible_rng();
}
static void
test_stochastic_log_logistic(void *arg)
{
bool ok = 0;
(void) arg;
testing_enable_reproducible_rng();
ok = test_stochastic_log_logistic_impl(1, 1);
tt_assert(ok);
ok = test_stochastic_log_logistic_impl(1, 10);
tt_assert(ok);
ok = test_stochastic_log_logistic_impl(M_E, 1e-1);
tt_assert(ok);
ok = test_stochastic_log_logistic_impl(exp(-10), 1e-2);
tt_assert(ok);
done:
write_stochastic_warning();
testing_disable_reproducible_rng();
}
static void
test_stochastic_weibull(void *arg)
{
bool ok = 0;
(void) arg;
testing_enable_reproducible_rng();
ok = test_stochastic_weibull_impl(1, 0.5);
tt_assert(ok);
ok = test_stochastic_weibull_impl(1, 1);
tt_assert(ok);
ok = test_stochastic_weibull_impl(1, 1.5);
tt_assert(ok);
ok = test_stochastic_weibull_impl(1, 2);
tt_assert(ok);
ok = test_stochastic_weibull_impl(10, 1);
tt_assert(ok);
done:
write_stochastic_warning();
testing_disable_reproducible_rng();
UNMOCK(crypto_rand);
}
struct testcase_t prob_distr_tests[] = {
{ "logit_logistics", test_logit_logistic, TT_FORK, NULL, NULL },
{ "log_logistic", test_log_logistic, TT_FORK, NULL, NULL },
{ "weibull", test_weibull, TT_FORK, NULL, NULL },
{ "genpareto", test_genpareto, TT_FORK, NULL, NULL },
{ "uniform_interval", test_uniform_interval, TT_FORK, NULL, NULL },
END_OF_TESTCASES
};
struct testcase_t slow_stochastic_prob_distr_tests[] = {
{ "stochastic_genpareto", test_stochastic_genpareto, TT_FORK, NULL, NULL },
{ "stochastic_geometric", test_stochastic_geometric, TT_FORK, NULL, NULL },
{ "stochastic_uniform", test_stochastic_uniform, TT_FORK, NULL, NULL },
{ "stochastic_logistic", test_stochastic_logistic, TT_FORK, NULL, NULL },
{ "stochastic_log_logistic", test_stochastic_log_logistic, TT_FORK, NULL,
NULL },
{ "stochastic_weibull", test_stochastic_weibull, TT_FORK, NULL, NULL },
END_OF_TESTCASES
};