using System;
using System.Collections.Generic;
namespace Amphora.RandomComparisons
{
///
/// Something the checks measure: one of Amphora's generators, a way of starting several of them, or a control that
/// is bad in a known way.
///
public sealed class Subject
{
public readonly string Name;
///
/// Whether this is a known-bad generator, so a check it fails is the check working.
///
public readonly bool IsControl;
///
/// Whether all 64 bits of a word are real. A generator with 31 bits has them at the top and zeros below.
///
public readonly bool HasLowWindow;
///
/// Whether this is a generator itself, rather than several of them started together.
///
public readonly bool IsSingleStream;
///
/// Starts a stream of words from a seed.
///
public readonly Func Make;
///
/// The number of bits of state, or zero when the state can't be set directly.
///
public readonly int StateBits;
public readonly Func FromState;
///
/// The checks of the draws built on the generator, bounded and shaped, or null for a control.
///
public readonly Func> BoundedChecks;
public Subject(string name, bool isControl, bool hasLowWindow, bool isSingleStream,
Func make, int stateBits, Func fromState,
Func> boundedChecks)
{
Name = name;
IsControl = isControl;
HasLowWindow = hasLowWindow;
IsSingleStream = isSingleStream;
Make = make;
StateBits = stateBits;
FromState = fromState;
BoundedChecks = boundedChecks;
}
}
public static class Subjects
{
public const int InterleavedStreams = 16;
private static Subject Generator(string name, Func make,
int stateBits, Func fromState, bool isControl, bool hasLowWindow,
bool checkBounded)
where GeneratorType : struct, IRandomGenerator
{
return new Subject(name, isControl, hasLowWindow, isSingleStream: true,
seed => new GeneratorWords(make(seed)), stateBits,
fromState == null ? null : new Func(
state => new GeneratorWords(fromState(state))),
checkBounded ? new Func>((seed, scale) =>
{
List outcomes = BoundedChecks.Run(make(seed), scale);
outcomes.AddRange(DistributionChecks.Run(make(seed), scale));
return outcomes;
}) : null);
}
///
/// Returns a subject that interleaves streams started at adjacent seeds, as parallel workers seeded by their
/// index would feed one consumer.
///
private static Subject Adjacent(Subject single)
{
return new Subject(single.Name + " adjacent seeds x" + InterleavedStreams, single.IsControl,
single.HasLowWindow, isSingleStream: false,
seed =>
{
var sources = new WordSource[InterleavedStreams];
for (int i = 0; i < sources.Length; i++)
{
sources[i] = single.Make(seed + (ulong)i);
}
return new InterleavedWords(sources);
}, stateBits: 0, fromState: null, boundedChecks: null);
}
///
/// Returns a subject that interleaves streams made by jumping one generator forward, which the generator
/// promises won't overlap.
///
private static Subject Jumped(string name, Func make,
JumpFunction jump)
where GeneratorType : struct, IRandomGenerator
{
return new Subject(name + " jumped x" + InterleavedStreams, isControl: false, hasLowWindow: true,
isSingleStream: false,
seed =>
{
GeneratorType generator = make(seed);
var sources = new WordSource[InterleavedStreams];
for (int i = 0; i < sources.Length; i++)
{
sources[i] = new GeneratorWords(generator);
jump(ref generator);
}
return new InterleavedWords(sources);
}, stateBits: 0, fromState: null, boundedChecks: null);
}
///
/// Returns every subject, the generators first, then ways of starting several, then the controls.
///
public static List All()
{
var generators = new List
{
Generator("xoshiro256++", seed => new Xoshiro256PlusPlus(seed), stateBits: 256,
state => new Xoshiro256PlusPlus(state[0], state[1], state[2], state[3]), isControl: false,
hasLowWindow: true, checkBounded: true),
Generator("xoshiro128++", seed => new Xoshiro128PlusPlus(seed), stateBits: 128,
state => new Xoshiro128PlusPlus((uint)state[0], (uint)(state[0] >> 32), (uint)state[1],
(uint)(state[1] >> 32)), isControl: false, hasLowWindow: true, checkBounded: true),
Generator("SFC64", seed => new Sfc64(seed), stateBits: 256,
state => new Sfc64(state[0], state[1], state[2], state[3]), isControl: false,
hasLowWindow: true, checkBounded: true),
Generator("SplitMix64", seed => new SplitMix64(seed), stateBits: 64,
state => new SplitMix64(state[0]), isControl: false, hasLowWindow: true, checkBounded: true),
};
var returnMe = new List(generators);
foreach (Subject generator in generators)
{
returnMe.Add(Adjacent(generator));
}
returnMe.Add(Jumped("xoshiro256++", seed => new Xoshiro256PlusPlus(seed),
(ref Xoshiro256PlusPlus generator) => generator.Jump()));
returnMe.Add(Jumped("xoshiro128++", seed => new Xoshiro128PlusPlus(seed),
(ref Xoshiro128PlusPlus generator) => generator.Jump()));
returnMe.Add(Generator("RANDU", seed => new Randu(seed), stateBits: 0, fromState: null, isControl: true,
hasLowWindow: false, checkBounded: false));
returnMe.Add(Generator("MINSTD", seed => new Minstd(seed), stateBits: 31,
state => new Minstd(state[0]), isControl: true, hasLowWindow: false, checkBounded: false));
returnMe.Add(Generator("LCG64", seed => new Lcg64(seed), stateBits: 64, state => new Lcg64(state[0]),
isControl: true, hasLowWindow: true, checkBounded: false));
returnMe.Add(Generator("xorshift64", seed => new Xorshift64(seed), stateBits: 64,
state => new Xorshift64(state[0]), isControl: true, hasLowWindow: true, checkBounded: false));
returnMe.Add(Generator("xoshiro256+", seed => new Xoshiro256Plus(seed), stateBits: 256,
state => new Xoshiro256Plus(state[0], state[1], state[2], state[3]), isControl: true,
hasLowWindow: true, checkBounded: false));
returnMe.Add(new Subject("System.Random", isControl: true, hasLowWindow: true, isSingleStream: true,
seed => new SystemRandomWords(seed), stateBits: 0, fromState: null, boundedChecks: null));
return returnMe;
}
}
}