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