github.com/onflow/flow-go/crypto@v0.24.8/random/rand_utils.go (about)

     1  package random
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  	"gonum.org/v1/gonum/stat"
    10  )
    11  
    12  // BasicDistributionTest is a test function to run a basic statistic test on `randf` output.
    13  // `randf` is a function that outputs random integers.
    14  // It partitions all outputs into `n` continuous classes and computes the distribution
    15  // over the partition. Each class has a width of `classWidth`: first class is [0..classWidth-1],
    16  // secons class is [classWidth..2*classWidth-1], etc..
    17  // It computes the frequency of outputs in the `n` classes and computes the
    18  // standard deviation of frequencies. A small standard deviation is a necessary
    19  // condition for a uniform distribution of `randf` (though is not a guarantee of
    20  // uniformity)
    21  func BasicDistributionTest(t *testing.T, n uint64, classWidth uint64, randf func() (uint64, error)) {
    22  	// sample size should ideally be a high number multiple of `n`
    23  	// but if `n` is too small, we could use a small sample size so that the test
    24  	// isn't too slow
    25  	sampleSize := 1000 * n
    26  	if n < 100 {
    27  		sampleSize = (80000 / n) * n // highest multiple of n less than 80000
    28  	}
    29  	distribution := make([]float64, n)
    30  	// populate the distribution
    31  	for i := uint64(0); i < sampleSize; i++ {
    32  		r, err := randf()
    33  		require.NoError(t, err)
    34  		if n*classWidth != 0 {
    35  			require.Less(t, r, n*classWidth)
    36  		}
    37  		distribution[r/classWidth] += 1.0
    38  	}
    39  	EvaluateDistributionUniformity(t, distribution)
    40  }
    41  
    42  // EvaluateDistributionUniformity evaluates if the input distribution is close to uinform
    43  // through a basic quick test.
    44  // The test computes the standard deviation and checks it is small enough compared
    45  // to the distribution mean.
    46  func EvaluateDistributionUniformity(t *testing.T, distribution []float64) {
    47  	tolerance := 0.05
    48  	stdev := stat.StdDev(distribution, nil)
    49  	mean := stat.Mean(distribution, nil)
    50  	assert.Greater(t, tolerance*mean, stdev, fmt.Sprintf("basic randomness test failed: n: %d, stdev: %v, mean: %v", len(distribution), stdev, mean))
    51  }