blob: abaea695ab9172b645f72274a2d862b104de4412 [file] [log] [blame]
/*---------------------------------------------------------
* Copyright 2022 The Go Authors. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
import assert from 'assert';
import { promptNext4Weeks } from '../../src/utils/randomDayutils';
// Set this to run the test. There's no point in running it over
// and over, but it should be run if promptNext4Weeks is changed.
const runNextDaysTest = false;
// Test that a year of generated dates looks uniform. This test relies on
// statistical tests, so in principle, it could fail. The parameters for the
// statistics are chosen so there should be no more than a 1 in 1,000,000 chance.
// (Further, if it passes once and the code it correct,
// it then becomes a test of the random number generator, which is pointless.)
// (this test takes about 40 msec on a 2018 Macbook Pro)
suite('random day tests', () => {
test('next days', () => {
if (!runNextDaysTest) {
return;
}
const newYear = new Date('2024-01-01');
const day = 24 * 3600 * 1000;
const seen4 = new Array<number>(366);
for (let i = 0; i < 366; i++) {
seen4[i] = 0;
}
for (let i = 0; i < 366; i++) {
for (let j = 0; j < 100; j++) {
const today = new Date(newYear.getTime() + day * i);
const next = promptNext4Weeks(today);
assert((next.getTime() - today.getTime()) % day === 0);
const days = (next.getTime() - today.getTime()) / day;
assert(days >= 1 && days <= 28, 'days: ' + days);
const doy = Math.floor((next.getTime() - new Date(next.getFullYear(), 0, 0).getTime()) / day);
seen4[doy - 1]++;
}
}
assert.ok(isUniform(seen4));
// console.log(`elapsed ${new Date().getTime() - now.getTime()} ms}`);
});
});
// decide if the contnts of x look like a uniform distribution, This assumes x.length > 50 or so,
// and approximates the chi-squared distribution with a normal distribution. (see the Wikipedia article)
// The approximation is that sqrt(2*chi2) ~ N(sqrt(2*df-1) is good enough for our purposes.
// The change of getting a 4.8 sigma deviation is about 1 in 1,000,000.
function isUniform(x: number[], bound = 4.8): boolean {
const k = x.length;
const df = k - 1;
const sum = x.reduce((sum, current) => sum + current, 0);
const exp = sum / k;
const chi2 = x.reduce((sum, current) => sum + ((current - exp) * (current - exp)) / exp, 0);
const sd = Math.sqrt(2 * chi2) - Math.sqrt(2 * df - 1);
// sd would be the standard deviation in units of 1. 1
let ret = sd < bound && sd > -bound;
// and make sure the individual values aren't crazy (5 std devs of normal has prob 3e-7)
const expsd = 5 * Math.sqrt(exp);
x.map((v) => {
if (v < exp - expsd || v > exp + expsd) ret = false;
});
// console.log(`sd: ${sd} bound: ${bound} expsd: ${expsd} exp: ${exp} chi2: ${chi2} df: ${df}`);
return ret;
}