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