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"); } } } }