using System;
namespace Amphora.RandomComparisons
{
///
/// Fills square binary matrices from consecutive draws and counts how often each rank comes up. A generator whose
/// bits are linear over GF(2) makes rows that depend on each other more or less often than random rows do.
///
public static class BinaryRank
{
///
/// Returns the rank over GF(2) of a matrix of up to 64 rows, each a word whose low bits are the columns. The
/// rows are used up.
///
public static int Rank(ulong[] rows, int size)
{
int rank = 0;
for (int column = 0; column < size; column++)
{
ulong mask = 1UL << column;
int pivot = -1;
for (int row = rank; row < size; row++)
{
if ((rows[row] & mask) != 0)
{
pivot = row;
break;
}
}
if (pivot < 0)
{
continue;
}
ulong pivotRow = rows[pivot];
rows[pivot] = rows[rank];
rows[rank] = pivotRow;
for (int row = rank + 1; row < size; row++)
{
if ((rows[row] & mask) != 0)
{
rows[row] ^= pivotRow;
}
}
rank++;
}
return rank;
}
///
/// Counts the matrices of a full rank, one short, two short, and further short. A size of 64 uses whole words.
/// A size of 32 uses the window's half of each word.
///
public static Outcome Run(WordStream stream, Window window, int size, long matrices)
{
var rows = new ulong[size];
var observed = new double[4];
for (long matrix = 0; matrix < matrices; matrix++)
{
for (int row = 0; row < size; row++)
{
ulong word = stream.Next();
rows[row] = size == 64 ? word : Windows.Take(word, window, size);
}
observed[Math.Min(size - Rank(rows, size), 3)]++;
}
var expected = new double[4];
for (int shortBy = 0; shortBy < 3; shortBy++)
{
expected[shortBy] = Stats.BinaryRankProbability(size, size - shortBy) * matrices;
}
expected[3] = matrices - expected[0] - expected[1] - expected[2];
double degrees = Stats.ChiSquareOfCategories(observed, expected, 5, out double statistic);
return new Outcome(Stats.ChiSquareTwoSided(statistic, degrees), size + "x" + size + " matrices of rank " +
size + ", " + (size - 1) + ", " + (size - 2) + " or less: " + observed[0] + ", " + observed[1] + ", " +
observed[2] + ", " + observed[3] + " (expected " + expected[0].ToString("0") + ", " +
expected[1].ToString("0") + ", " + expected[2].ToString("0") + ", " + expected[3].ToString("0") + ")");
}
}
}