using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Threading.Tasks; namespace Amphora.RandomComparisons { /// /// Draws the picture that made RANDU famous. Consecutive draws are taken three at a time as points in a cube. A /// generator that puts them on a few planes shows as stripes through a thin slice of the cube, where a good one /// shows as noise. /// public static class Hyperplanes { public const int PanelSize = 480; public const int TitleHeight = 24; public const int Margin = 8; public const int Columns = 5; public const int SliceBits = 10; public const int CoordinateBits = 24; /// /// Counts the points of a thin slice of the cube, by their other two coordinates. Triples overlap, each draw /// being the end of one triple and the start of the next. /// public static int[] Slice(Subject subject, ulong seed, long draws) { var counts = new int[PanelSize * PanelSize]; var stream = new WordStream(subject.Make(seed)); int first = (int)Windows.Take(stream.Next(), Window.High, CoordinateBits); int second = (int)Windows.Take(stream.Next(), Window.High, CoordinateBits); int sliceStart = 1 << (CoordinateBits - 1); int sliceEnd = sliceStart + (1 << (CoordinateBits - SliceBits)); for (long i = 2; i < draws; i++) { int third = (int)Windows.Take(stream.Next(), Window.High, CoordinateBits); if (third >= sliceStart && third < sliceEnd) { int x = (int)((long)first * PanelSize >> CoordinateBits); int y = (int)((long)second * PanelSize >> CoordinateBits); counts[y * PanelSize + x]++; } first = second; second = third; } return counts; } public static void Write(string path, IReadOnlyList subjects, ulong seed, Scale scale) { long draws = scale.Count(1L << 27, minimum: 1L << 22); var slices = new int[subjects.Count][]; Parallel.For(0, subjects.Count, index => slices[index] = Slice(subjects[index], seed, draws)); int rows = (subjects.Count + Columns - 1) / Columns; int cellWidth = PanelSize + Margin; int cellHeight = PanelSize + TitleHeight + Margin; using (var bitmap = new Bitmap(Columns * cellWidth + Margin, rows * cellHeight + Margin)) using (Graphics graphics = Graphics.FromImage(bitmap)) using (var font = new Font("Arial", 11)) { graphics.Clear(Color.FromArgb(24, 24, 28)); for (int index = 0; index < subjects.Count; index++) { int left = Margin + index % Columns * cellWidth; int top = Margin + index / Columns * cellHeight; graphics.DrawString(subjects[index].Name + (subjects[index].IsControl ? " (control)" : ""), font, Brushes.White, left, top); graphics.FillRectangle(Brushes.Black, left, top + TitleHeight, PanelSize, PanelSize); for (int y = 0; y < PanelSize; y++) { for (int x = 0; x < PanelSize; x++) { if (slices[index][y * PanelSize + x] > 0) { bitmap.SetPixel(left + x, top + TitleHeight + y, Color.White); } } } } bitmap.Save(path, ImageFormat.Png); } } } }