using System; using System.Collections.Generic; using System.IO; using System.Reflection; namespace Amphora.RandomComparisons { /// /// Compares random number generators against each other and against controls that are bad in known ways. /// public static class Program { public const string QuickOption = "--quick"; public const string SeedOption = "--seed"; public const string PictureOption = "--picture"; public const string NoPictureOption = "--no-picture"; public const string SelfCheckOption = "--self-check"; public const string RawOption = "--raw"; public const string CalibrateOption = "--calibrate"; public const ulong DefaultSeed = 20261016; public const int QuickDivisor = 8; public const string Usage = "Usage: Amphora.RandomComparisons [--quick] [--seed N] [--picture PATH | " + "--no-picture] [--self-check] [--calibrate SEEDS] [--raw NAME] [text in the subject names to run]"; private static void PrintOutcomes(Subject subject, List outcomes) { Console.WriteLine(subject.Name + (subject.IsControl ? " (control)" : "")); foreach (NamedOutcome outcome in outcomes) { string verdict = outcome.Outcome.Verdict; if (outcome.ExpectsFailure) { verdict = verdict == "FAIL" ? "fails as it should" : "DID NOT FAIL"; } Console.WriteLine( $" {outcome.Name,-44} {verdict,-8} {outcome.Outcome.PText,-12} {outcome.Outcome.Detail}"); } Console.WriteLine(); } private static int CountVerdicts(List outcomes, string verdict) { int returnMe = 0; foreach (NamedOutcome outcome in outcomes) { if (!outcome.ExpectsFailure && outcome.Outcome.Verdict == verdict) { returnMe++; } } return returnMe; } private static void PrintSummary(IReadOnlyList subjects, List[] outcomes) { Console.WriteLine("Summary: checks failed (p < " + Outcome.FailBelow + ") and suspect (p < " + Outcome.SuspectBelow + "), out of the checks run."); for (int i = 0; i < subjects.Count; i++) { int run = 0; foreach (NamedOutcome outcome in outcomes[i]) { if (!outcome.ExpectsFailure && !outcome.Outcome.IsInformation) { run++; } } string name = subjects[i].Name + (subjects[i].IsControl ? " (control)" : ""); Console.WriteLine($" {name,-40} {CountVerdicts(outcomes[i], "FAIL"),3} failed " + $"{CountVerdicts(outcomes[i], "SUSPECT"),3} suspect of {run}"); } } private static string DefaultPicturePath() { return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "RandomComparisons-hyperplanes.png"); } /// /// Writes a subject's words to standard output forever, as binary, for a tester like PractRand to read. /// private static int WriteRaw(Subject subject, ulong seed) { var stream = new WordStream(subject.Make(seed)); var bytes = new byte[8 * 4096]; using (Stream output = Console.OpenStandardOutput()) { while (true) { for (int i = 0; i < 4096; i++) { BitConverter.GetBytes(stream.Next()).CopyTo(bytes, i * 8); } output.Write(bytes, 0, bytes.Length); } } } public static int Main(string[] args) { bool quick = false; bool selfCheck = false; bool picture = true; string picturePath = null; string rawName = null; string filter = ""; int calibrateSeeds = 0; ulong seed = DefaultSeed; for (int i = 0; i < args.Length; i++) { string arg = args[i]; if (arg == QuickOption) { quick = true; } else if (arg == SelfCheckOption) { selfCheck = true; } else if (arg == NoPictureOption) { picture = false; } else if (arg == PictureOption && i + 1 < args.Length) { picturePath = args[++i]; } else if (arg == SeedOption && i + 1 < args.Length && ulong.TryParse(args[i + 1], out seed)) { i++; } else if (arg == RawOption && i + 1 < args.Length) { rawName = args[++i]; } else if (arg == CalibrateOption && i + 1 < args.Length && int.TryParse(args[i + 1], out calibrateSeeds)) { i++; } else if (arg.StartsWith("--", StringComparison.Ordinal) || filter != "") { Console.Error.WriteLine(Usage); return 2; } else { filter = arg; } } if (selfCheck) { return SelfCheck.Run(Console.Out); } List everything = Subjects.All(); if (rawName != null) { Subject chosen = everything.Find(subject => subject.Name.IndexOf(rawName, StringComparison.OrdinalIgnoreCase) >= 0); if (chosen == null) { Console.Error.WriteLine("No subject is named like '" + rawName + "'."); return 2; } return WriteRaw(chosen, seed); } var subjects = everything.FindAll(subject => subject.Name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0); if (subjects.Exists(subject => subject.Name.Equals(filter, StringComparison.OrdinalIgnoreCase))) { subjects = subjects.FindAll(subject => subject.Name.Equals(filter, StringComparison.OrdinalIgnoreCase)); } var scale = new Scale(quick ? QuickDivisor : 1); if (calibrateSeeds > 0) { foreach (Subject subject in subjects) { Calibration.Run(subject, scale, seed, calibrateSeeds, Console.Out); Console.WriteLine(); } return 0; } string size = quick ? ", quick (1/" + QuickDivisor + " the work)" : ""; Console.WriteLine($".NET {Environment.Version}, {IntPtr.Size * 8}-bit, {Environment.ProcessorCount} " + $"logical processors, seed {seed}{size}."); Console.WriteLine("Each check gives the chance a fair generator lands this far from the expected result, " + "on either side. Under " + Outcome.FailBelow + " is a FAIL and under " + Outcome.SuspectBelow + " is SUSPECT. A control is meant to fail some. A few SUSPECT among a hundred checks is chance, so " + "rerun with another --seed before believing one."); Console.WriteLine(); if (picture && subjects.FindAll(subject => subject.IsSingleStream).Count > 0) { string path = picturePath ?? DefaultPicturePath(); Hyperplanes.Write(path, subjects.FindAll(subject => subject.IsSingleStream), seed, scale); Console.WriteLine("Wrote the hyperplane picture to " + path); Console.WriteLine(); } List[] outcomes = Battery.RunAll(subjects, scale, seed, subject => Console.Error.WriteLine("finished " + subject.Name)); for (int i = 0; i < subjects.Count; i++) { PrintOutcomes(subjects[i], outcomes[i]); } PrintSummary(subjects, outcomes); return 0; } } }