using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Amphora.RandomComparisons
{
///
/// Runs the checks on a good generator under many seeds. If a check measures what it claims to, its probabilities
/// are spread evenly over 0 to 1. A check whose probabilities pile up near 0 or 1 is miscalibrated, and would raise
/// false alarms or hide real flaws.
///
public static class Calibration
{
///
/// Returns the chance that n values drawn evenly from 0 to 1 stray from the even spread as far as these do, by
/// the Kolmogorov-Smirnov test.
///
public static double UniformityP(List values)
{
var sorted = new List(values);
sorted.Sort();
int n = sorted.Count;
double largest = 0;
for (int i = 0; i < n; i++)
{
largest = Math.Max(largest, Math.Max((i + 1.0) / n - sorted[i], sorted[i] - (double)i / n));
}
double lambda = (Math.Sqrt(n) + 0.12 + 0.11 / Math.Sqrt(n)) * largest;
double sum = 0;
for (int k = 1; k <= 100; k++)
{
double term = 2 * Math.Exp(-2 * k * k * lambda * lambda);
sum += (k % 2 == 1) ? term : -term;
if (term < 1e-16)
{
break;
}
}
return Math.Max(0, Math.Min(1, sum));
}
private static bool DependsOnTheSeed(NamedOutcome outcome)
{
return !outcome.ExpectsFailure && !outcome.Outcome.IsInformation &&
!outcome.Name.StartsWith("first ", StringComparison.Ordinal) &&
!outcome.Name.Contains("stays in its range");
}
public static void Run(Subject subject, Scale scale, ulong firstSeed, int seeds, TextWriter output)
{
var outcomes = new List[seeds];
var options = new ParallelOptions { MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount - 1) };
Parallel.For(0, seeds, options, index => outcomes[index] = Battery.Run(subject, scale,
firstSeed + (ulong)index));
output.WriteLine(subject.Name + " under " + seeds + " seeds. Each line gives the chance that the " +
"check's probabilities are evenly spread, and how many fell under 0.01 and under 0.001, against the " +
"expected " + (seeds / 100.0).ToString("0.#") + " and " + (seeds / 1000.0).ToString("0.##") + ".");
foreach (string name in outcomes[0].Where(DependsOnTheSeed).Select(outcome => outcome.Name))
{
var values = new List();
foreach (List run in outcomes)
{
values.Add(run.First(outcome => outcome.Name == name).Outcome.P);
}
int underHundredth = values.Count(value => value < 0.01);
int underThousandth = values.Count(value => value < 0.001);
double p = UniformityP(values);
output.WriteLine($" {name,-44} {(p < 0.001 ? "MISCALIBRATED" : "ok"),-14} uniformity p={p:0.####} " +
$"{underHundredth} under 0.01, {underThousandth} under 0.001");
}
}
}
}