├── .gitignore
├── .idea
├── Randomness_Testing.iml
├── libraries
│ └── R_User_Library.xml
├── misc.xml
└── modules.xml
├── ApproximateEntropy.py
├── BinaryMatrix.py
├── Complexity.py
├── CumulativeSum.py
├── FrequencyTest.py
├── GUI.py
├── LICENSE
├── Main.py
├── Matrix.py
├── OLD_Main.py
├── README.md
├── RandomExcursions.py
├── RunTest.py
├── Serial.py
├── Spectral.py
├── TemplateMatching.py
├── Tools.py
├── Universal.py
├── data
├── data.e
├── data.pi
├── data.sqrt2
├── data.sqrt3
├── test_data.bin
├── test_data_01.txt
└── test_data_02.txt
├── result
├── 20180107_Binary_Data.txt
├── 20180107_Binary_File.txt
├── 20180107_String_File.txt
├── 20180117.txt
└── 20180117_URL_Result.txt
├── test_bin_file.py
├── test_e.py
├── test_pi.py
├── test_sqrt2.py
├── test_sqrt3.py
└── test_url_01.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | __pycache__/
3 |
--------------------------------------------------------------------------------
/.idea/Randomness_Testing.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/R_User_Library.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ApproximateEntropy.py:
--------------------------------------------------------------------------------
1 | from math import log as log
2 | from numpy import zeros as zeros
3 | from scipy.special import gammaincc as gammaincc
4 |
5 | class ApproximateEntropy:
6 |
7 | @staticmethod
8 | def approximate_entropy_test(binary_data:str, verbose=False, pattern_length=10):
9 | """
10 | from the NIST documentation http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf
11 |
12 | As with the Serial test of Section 2.11, the focus of this test is the frequency of all possible
13 | overlapping m-bit patterns across the entire sequence. The purpose of the test is to compare
14 | the frequency of overlapping blocks of two consecutive/adjacent lengths (m and m+1) against the
15 | expected result for a random sequence.
16 |
17 | :param binary_data: a binary string
18 | :param verbose True to display the debug message, False to turn off debug message
19 | :param pattern_length: the length of the pattern (m)
20 | :return: ((p_value1, bool), (p_value2, bool)) A tuple which contain the p_value and result of serial_test(True or False)
21 | """
22 | length_of_binary_data = len(binary_data)
23 |
24 | # Augment the n-bit sequence to create n overlapping m-bit sequences by appending m-1 bits
25 | # from the beginning of the sequence to the end of the sequence.
26 | # NOTE: documentation says m-1 bits but that doesnt make sense, or work.
27 | binary_data += binary_data[:pattern_length + 1:]
28 |
29 | # Get max length one patterns for m, m-1, m-2
30 | max_pattern = ''
31 | for i in range(pattern_length + 2):
32 | max_pattern += '1'
33 |
34 | # Keep track of each pattern's frequency (how often it appears)
35 | vobs_01 = zeros(int(max_pattern[0:pattern_length:], 2) + 1)
36 | vobs_02 = zeros(int(max_pattern[0:pattern_length + 1:], 2) + 1)
37 |
38 | for i in range(length_of_binary_data):
39 | # Work out what pattern is observed
40 | vobs_01[int(binary_data[i:i + pattern_length:], 2)] += 1
41 | vobs_02[int(binary_data[i:i + pattern_length + 1:], 2)] += 1
42 |
43 | # Calculate the test statistics and p values
44 | vobs = [vobs_01, vobs_02]
45 |
46 | sums = zeros(2)
47 | for i in range(2):
48 | for j in range(len(vobs[i])):
49 | if vobs[i][j] > 0:
50 | sums[i] += vobs[i][j] * log(vobs[i][j] / length_of_binary_data)
51 | sums /= length_of_binary_data
52 | ape = sums[0] - sums[1]
53 |
54 | xObs = 2.0 * length_of_binary_data * (log(2) - ape)
55 |
56 | p_value = gammaincc(pow(2, pattern_length - 1), xObs / 2.0)
57 |
58 | if verbose:
59 | print('Approximate Entropy Test DEBUG BEGIN:')
60 | print("\tLength of input:\t\t\t", length_of_binary_data)
61 | print('\tLength of each block:\t\t', pattern_length)
62 | print('\tApEn(m):\t\t\t\t\t', ape)
63 | print('\txObs:\t\t\t\t\t\t', xObs)
64 | print('\tP-Value:\t\t\t\t\t', p_value)
65 | print('DEBUG END.')
66 |
67 | return (p_value, (p_value >= 0.01))
--------------------------------------------------------------------------------
/BinaryMatrix.py:
--------------------------------------------------------------------------------
1 | from copy import copy as copy
2 |
3 | class BinaryMatrix:
4 |
5 | def __init__(self, matrix, rows, cols):
6 | """
7 | This class contains the algorithm specified in the NIST suite for computing the **binary rank** of a matrix.
8 | :param matrix: the matrix we want to compute the rank for
9 | :param rows: the number of rows
10 | :param cols: the number of columns
11 | :return: a BinaryMatrix object
12 | """
13 | self.M = rows
14 | self.Q = cols
15 | self.A = matrix
16 | self.m = min(rows, cols)
17 |
18 | def compute_rank(self, verbose=False):
19 | """
20 | This method computes the binary rank of self.matrix
21 | :param verbose: if this is true it prints out the matrix after the forward elimination and backward elimination
22 | operations on the rows. This was used to testing the method to check it is working as expected.
23 | :return: the rank of the matrix.
24 | """
25 | if verbose:
26 | print("Original Matrix\n", self.A)
27 |
28 | i = 0
29 | while i < self.m - 1:
30 | if self.A[i][i] == 1:
31 | self.perform_row_operations(i, True)
32 | else:
33 | found = self.find_unit_element_swap(i, True)
34 | if found == 1:
35 | self.perform_row_operations(i, True)
36 | i += 1
37 |
38 | if verbose:
39 | print("Intermediate Matrix\n", self.A)
40 |
41 | i = self.m - 1
42 | while i > 0:
43 | if self.A[i][i] == 1:
44 | self.perform_row_operations(i, False)
45 | else:
46 | if self.find_unit_element_swap(i, False) == 1:
47 | self.perform_row_operations(i, False)
48 | i -= 1
49 |
50 | if verbose:
51 | print("Final Matrix\n", self.A)
52 |
53 | return self.determine_rank()
54 |
55 | def perform_row_operations(self, i, forward_elimination):
56 | """
57 | This method performs the elementary row operations. This involves xor'ing up to two rows together depending on
58 | whether or not certain elements in the matrix contain 1's if the "current" element does not.
59 | :param i: the current index we are are looking at
60 | :param forward_elimination: True or False.
61 | """
62 | if forward_elimination:
63 | j = i + 1
64 | while j < self.M:
65 | if self.A[j][i] == 1:
66 | self.A[j, :] = (self.A[j, :] + self.A[i, :]) % 2
67 | j += 1
68 | else:
69 | j = i - 1
70 | while j >= 0:
71 | if self.A[j][i] == 1:
72 | self.A[j, :] = (self.A[j, :] + self.A[i, :]) % 2
73 | j -= 1
74 |
75 | def find_unit_element_swap(self, i, forward_elimination):
76 | """
77 | This given an index which does not contain a 1 this searches through the rows below the index to see which rows
78 | contain 1's, if they do then they swapped. This is done on the forward and backward elimination
79 | :param i: the current index we are looking at
80 | :param forward_elimination: True or False.
81 | """
82 | row_op = 0
83 | if forward_elimination:
84 | index = i + 1
85 | while index < self.M and self.A[index][i] == 0:
86 | index += 1
87 | if index < self.M:
88 | row_op = self.swap_rows(i, index)
89 | else:
90 | index = i - 1
91 | while index >= 0 and self.A[index][i] == 0:
92 | index -= 1
93 | if index >= 0:
94 | row_op = self.swap_rows(i, index)
95 | return row_op
96 |
97 | def swap_rows(self, i, ix):
98 | """
99 | This method just swaps two rows in a matrix. Had to use the copy package to ensure no memory leakage
100 | :param i: the first row we want to swap and
101 | :param ix: the row we want to swap it with
102 | :return: 1
103 | """
104 | temp = copy(self.A[i, :])
105 | self.A[i, :] = self.A[ix, :]
106 | self.A[ix, :] = temp
107 | return 1
108 |
109 | def determine_rank(self):
110 | """
111 | This method determines the rank of the transformed matrix
112 | :return: the rank of the transformed matrix
113 | """
114 | rank = self.m
115 | i = 0
116 | while i < self.M:
117 | all_zeros = 1
118 | for j in range(self.Q):
119 | if self.A[i][j] == 1:
120 | all_zeros = 0
121 | if all_zeros == 1:
122 | rank -= 1
123 | i += 1
124 | return rank
--------------------------------------------------------------------------------
/Complexity.py:
--------------------------------------------------------------------------------
1 | from copy import copy as copy
2 | from numpy import dot as dot
3 | from numpy import histogram as histogram
4 | from numpy import zeros as zeros
5 | from scipy.special import gammaincc as gammaincc
6 |
7 | class ComplexityTest:
8 |
9 | @staticmethod
10 | def linear_complexity_test(binary_data:str, verbose=False, block_size=500):
11 | """
12 | Note that this description is taken from the NIST documentation [1]
13 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf
14 | The focus of this test is the length of a linear feedback shift register (LFSR). The purpose of this test is to
15 | determine whether or not the sequence is complex enough to be considered random. Random sequences are
16 | characterized by longer LFSRs. An LFSR that is too short implies non-randomness.
17 |
18 | :param binary_data: a binary string
19 | :param verbose True to display the debug messgae, False to turn off debug message
20 | :param block_size: Size of the block
21 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
22 |
23 | """
24 |
25 | length_of_binary_data = len(binary_data)
26 |
27 | # The number of degrees of freedom;
28 | # K = 6 has been hard coded into the test.
29 | degree_of_freedom = 6
30 |
31 | # π0 = 0.010417, π1 = 0.03125, π2 = 0.125, π3 = 0.5, π4 = 0.25, π5 = 0.0625, π6 = 0.020833
32 | # are the probabilities computed by the equations in Section 3.10
33 | pi = [0.01047, 0.03125, 0.125, 0.5, 0.25, 0.0625, 0.020833]
34 |
35 | t2 = (block_size / 3.0 + 2.0 / 9) / 2 ** block_size
36 | mean = 0.5 * block_size + (1.0 / 36) * (9 + (-1) ** (block_size + 1)) - t2
37 |
38 | number_of_block = int(length_of_binary_data / block_size)
39 |
40 | if number_of_block > 1:
41 | block_end = block_size
42 | block_start = 0
43 | blocks = []
44 | for i in range(number_of_block):
45 | blocks.append(binary_data[block_start:block_end])
46 | block_start += block_size
47 | block_end += block_size
48 |
49 | complexities = []
50 | for block in blocks:
51 | complexities.append(ComplexityTest.berlekamp_massey_algorithm(block))
52 |
53 | t = ([-1.0 * (((-1) ** block_size) * (chunk - mean) + 2.0 / 9) for chunk in complexities])
54 | vg = histogram(t, bins=[-9999999999, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 9999999999])[0][::-1]
55 | im = ([((vg[ii] - number_of_block * pi[ii]) ** 2) / (number_of_block * pi[ii]) for ii in range(7)])
56 |
57 | xObs = 0.0
58 | for i in range(len(pi)):
59 | xObs += im[i]
60 |
61 | # P-Value = igamc(K/2, xObs/2)
62 | p_value = gammaincc(degree_of_freedom / 2.0, xObs / 2.0)
63 |
64 | if verbose:
65 | print('Linear Complexity Test DEBUG BEGIN:')
66 | print("\tLength of input:\t", length_of_binary_data)
67 | print('\tLength in bits of a block:\t', )
68 | print("\tDegree of Freedom:\t\t", degree_of_freedom)
69 | print('\tNumber of Blocks:\t', number_of_block)
70 | print('\tValue of Vs:\t\t', vg)
71 | print('\txObs:\t\t\t\t', xObs)
72 | print('\tP-Value:\t\t\t', p_value)
73 | print('DEBUG END.')
74 |
75 |
76 | return (p_value, (p_value >= 0.01))
77 | else:
78 | return (-1.0, False)
79 |
80 | @staticmethod
81 | def berlekamp_massey_algorithm(block_data):
82 | """
83 | An implementation of the Berlekamp Massey Algorithm. Taken from Wikipedia [1]
84 | [1] - https://en.wikipedia.org/wiki/Berlekamp-Massey_algorithm
85 | The Berlekamp–Massey algorithm is an algorithm that will find the shortest linear feedback shift register (LFSR)
86 | for a given binary output sequence. The algorithm will also find the minimal polynomial of a linearly recurrent
87 | sequence in an arbitrary field. The field requirement means that the Berlekamp–Massey algorithm requires all
88 | non-zero elements to have a multiplicative inverse.
89 | :param block_data:
90 | :return:
91 | """
92 | n = len(block_data)
93 | c = zeros(n)
94 | b = zeros(n)
95 | c[0], b[0] = 1, 1
96 | l, m, i = 0, -1, 0
97 | int_data = [int(el) for el in block_data]
98 | while i < n:
99 | v = int_data[(i - l):i]
100 | v = v[::-1]
101 | cc = c[1:l + 1]
102 | d = (int_data[i] + dot(v, cc)) % 2
103 | if d == 1:
104 | temp = copy(c)
105 | p = zeros(n)
106 | for j in range(0, l):
107 | if b[j] == 1:
108 | p[j + i - m] = 1
109 | c = (c + p) % 2
110 | if l <= 0.5 * i:
111 | l = i + 1 - l
112 | m = i
113 | b = temp
114 | i += 1
115 | return l
--------------------------------------------------------------------------------
/CumulativeSum.py:
--------------------------------------------------------------------------------
1 | from numpy import abs as abs
2 | from numpy import array as array
3 | from numpy import floor as floor
4 | from numpy import max as max
5 | from numpy import sqrt as sqrt
6 | from numpy import sum as sum
7 | from numpy import zeros as zeros
8 | from scipy.stats import norm as norm
9 |
10 | class CumulativeSums:
11 |
12 | @staticmethod
13 | def cumulative_sums_test(binary_data:str, mode=0, verbose=False):
14 | """
15 | from the NIST documentation http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf
16 |
17 | The focus of this test is the maximal excursion (from zero) of the random walk defined by the cumulative sum of
18 | adjusted (-1, +1) digits in the sequence. The purpose of the test is to determine whether the cumulative sum of
19 | the partial sequences occurring in the tested sequence is too large or too small relative to the expected
20 | behavior of that cumulative sum for random sequences. This cumulative sum may be considered as a random walk.
21 | For a random sequence, the excursions of the random walk should be near zero. For certain types of non-random
22 | sequences, the excursions of this random walk from zero will be large.
23 |
24 | :param binary_data: a binary string
25 | :param mode A switch for applying the test either forward through the input sequence (mode = 0)
26 | or backward through the sequence (mode = 1).
27 | :param verbose True to display the debug messgae, False to turn off debug message
28 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
29 |
30 | """
31 |
32 | length_of_binary_data = len(binary_data)
33 | counts = zeros(length_of_binary_data)
34 |
35 | # Determine whether forward or backward data
36 | if not mode == 0:
37 | binary_data = binary_data[::-1]
38 |
39 | counter = 0
40 | for char in binary_data:
41 | sub = 1
42 | if char == '0':
43 | sub = -1
44 | if counter > 0:
45 | counts[counter] = counts[counter -1] + sub
46 | else:
47 | counts[counter] = sub
48 |
49 | counter += 1
50 | # Compute the test statistic z =max1≤k≤n|Sk|, where max1≤k≤n|Sk| is the largest of the
51 | # absolute values of the partial sums Sk.
52 | abs_max = max(abs(counts))
53 |
54 | start = int(floor(0.25 * floor(-length_of_binary_data / abs_max + 1)))
55 | end = int(floor(0.25 * floor(length_of_binary_data / abs_max - 1)))
56 |
57 | terms_one = []
58 | for k in range(start, end + 1):
59 | sub = norm.cdf((4 * k - 1) * abs_max / sqrt(length_of_binary_data))
60 | terms_one.append(norm.cdf((4 * k + 1) * abs_max / sqrt(length_of_binary_data)) - sub)
61 |
62 | start = int(floor(0.25 * floor(-length_of_binary_data / abs_max - 3)))
63 | end = int(floor(0.25 * floor(length_of_binary_data / abs_max) - 1))
64 |
65 | terms_two = []
66 | for k in range(start, end + 1):
67 | sub = norm.cdf((4 * k + 1) * abs_max / sqrt(length_of_binary_data))
68 | terms_two.append(norm.cdf((4 * k + 3) * abs_max / sqrt(length_of_binary_data)) - sub)
69 |
70 | p_value = 1.0 - sum(array(terms_one))
71 | p_value += sum(array(terms_two))
72 |
73 | if verbose:
74 | print('Cumulative Sums Test DEBUG BEGIN:')
75 | print("\tLength of input:\t", length_of_binary_data)
76 | print('\tMode:\t\t\t\t', mode)
77 | print('\tValue of z:\t\t\t', abs_max)
78 | print('\tP-Value:\t\t\t', p_value)
79 | print('DEBUG END.')
80 |
81 | return (p_value, (p_value >= 0.01))
--------------------------------------------------------------------------------
/FrequencyTest.py:
--------------------------------------------------------------------------------
1 | from math import fabs as fabs
2 | from math import floor as floor
3 | from math import sqrt as sqrt
4 | from scipy.special import erfc as erfc
5 | from scipy.special import gammaincc as gammaincc
6 |
7 | class FrequencyTest:
8 |
9 | @staticmethod
10 | def monobit_test(binary_data:str, verbose=False):
11 | """
12 | The focus of the test is the proportion of zeroes and ones for the entire sequence.
13 | The purpose of this test is to determine whether the number of ones and zeros in a sequence are approximately
14 | the same as would be expected for a truly random sequence. The test assesses the closeness of the fraction of
15 | ones to 陆, that is, the number of ones and zeroes in a sequence should be about the same.
16 | All subsequent tests depend on the passing of this test.
17 |
18 | if p_value < 0.01, then conclude that the sequence is non-random (return False).
19 | Otherwise, conclude that the the sequence is random (return True).
20 |
21 | :param binary_data The seuqnce of bit being tested
22 | :param verbose True to display the debug messgae, False to turn off debug message
23 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
24 |
25 | """
26 |
27 | length_of_bit_string = len(binary_data)
28 |
29 | # Variable for S(n)
30 | count = 0
31 | # Iterate each bit in the string and compute for S(n)
32 | for bit in binary_data:
33 | if bit == '0':
34 | # If bit is 0, then -1 from the S(n)
35 | count -= 1
36 | elif bit == '1':
37 | # If bit is 1, then +1 to the S(n)
38 | count += 1
39 |
40 | # Compute the test statistic
41 | sObs = count / sqrt(length_of_bit_string)
42 |
43 | # Compute p-Value
44 | p_value = erfc(fabs(sObs) / sqrt(2))
45 |
46 | if verbose:
47 | print('Frequency Test (Monobit Test) DEBUG BEGIN:')
48 | print("\tLength of input:\t", length_of_bit_string)
49 | print('\t# of \'0\':\t\t\t', binary_data.count('0'))
50 | print('\t# of \'1\':\t\t\t', binary_data.count('1'))
51 | print('\tS(n):\t\t\t\t', count)
52 | print('\tsObs:\t\t\t\t', sObs)
53 | print('\tf:\t\t\t\t\t',fabs(sObs) / sqrt(2))
54 | print('\tP-Value:\t\t\t', p_value)
55 | print('DEBUG END.')
56 |
57 | # return a p_value and randomness result
58 | return (p_value, (p_value >= 0.01))
59 |
60 | @staticmethod
61 | def block_frequency(binary_data:str, block_size=128, verbose=False):
62 | """
63 | The focus of the test is the proportion of ones within M-bit blocks.
64 | The purpose of this test is to determine whether the frequency of ones in an M-bit block is approximately M/2,
65 | as would be expected under an assumption of randomness.
66 | For block size M=1, this test degenerates to test 1, the Frequency (Monobit) test.
67 |
68 | :param binary_data: The length of each block
69 | :param block_size: The seuqnce of bit being tested
70 | :param verbose True to display the debug messgae, False to turn off debug message
71 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
72 | """
73 |
74 | length_of_bit_string = len(binary_data)
75 |
76 |
77 | if length_of_bit_string < block_size:
78 | block_size = length_of_bit_string
79 |
80 | # Compute the number of blocks based on the input given. Discard the remainder
81 | number_of_blocks = floor(length_of_bit_string / block_size)
82 |
83 | if number_of_blocks == 1:
84 | # For block size M=1, this test degenerates to test 1, the Frequency (Monobit) test.
85 | return FrequencyTest.monobit_test(binary_data[0:block_size])
86 |
87 | # Initialized variables
88 | block_start = 0
89 | block_end = block_size
90 | proportion_sum = 0.0
91 |
92 | # Create a for loop to process each block
93 | for counter in range(number_of_blocks):
94 | # Partition the input sequence and get the data for block
95 | block_data = binary_data[block_start:block_end]
96 |
97 | # Determine the proportion 蟺i of ones in each M-bit
98 | one_count = 0
99 | for bit in block_data:
100 | if bit == '1':
101 | one_count += 1
102 | # compute π
103 | pi = one_count / block_size
104 |
105 | # Compute Σ(πi -½)^2.
106 | proportion_sum += pow(pi - 0.5, 2.0)
107 |
108 | # Next Block
109 | block_start += block_size
110 | block_end += block_size
111 |
112 | # Compute 4M Σ(πi -½)^2.
113 | result = 4.0 * block_size * proportion_sum
114 |
115 | # Compute P-Value
116 | p_value = gammaincc(number_of_blocks / 2, result / 2)
117 |
118 | if verbose:
119 | print('Frequency Test (Block Frequency Test) DEBUG BEGIN:')
120 | print("\tLength of input:\t", length_of_bit_string)
121 | print("\tSize of Block:\t\t", block_size)
122 | print('\tNumber of Blocks:\t', number_of_blocks)
123 | print('\tCHI Squared:\t\t', result)
124 | print('\t1st:\t\t\t\t', number_of_blocks / 2)
125 | print('\t2nd:\t\t\t\t', result / 2)
126 | print('\tP-Value:\t\t\t', p_value)
127 | print('DEBUG END.')
128 |
129 | return (p_value, (p_value >= 0.01))
--------------------------------------------------------------------------------
/GUI.py:
--------------------------------------------------------------------------------
1 | from tkinter import ttk
2 | from tkinter import Canvas
3 | from tkinter import DISABLED # Keep for Entry state if needed, though ttk uses 'readonly'
4 | from tkinter import Frame
5 | from tkinter import IntVar
6 | from tkinter import LabelFrame # ttk.LabelFrame is available if we want to switch this too
7 | from tkinter import Scrollbar
8 | from tkinter import StringVar
9 |
10 | class CustomButton:
11 |
12 | def __init__(self, master, title, x_coor, y_coor, width, action=None):
13 | # ttk.Button does not have a config method for font in the same way.
14 | # Style objects are preferred for ttk widgets. For simplicity here, let's assume default font or
15 | # handle styling at a higher level if necessary (e.g. via ttk.Style).
16 | self.button = ttk.Button(master, text=title, command=action)
17 | self.button.place(x=x_coor, y=y_coor, width=width, height=25)
18 |
19 | def config(self, **kwargs):
20 | if 'state' in kwargs:
21 | button_state = kwargs.pop('state')
22 | if button_state == 'disabled' or button_state == DISABLED:
23 | self.button.state(['disabled'])
24 | elif button_state == 'normal':
25 | self.button.state(['!disabled'])
26 |
27 | # Pass any other configuration options to the underlying ttk.Button
28 | if kwargs:
29 | self.button.configure(**kwargs)
30 |
31 | class Input:
32 |
33 | def __init__(self, master, title, x_coor, y_coor, has_button=False, action=None, button_xcoor=1050, button_width=180):
34 | # Setup Labels
35 | label = ttk.Label(master, text=title, font=("Calibri", 12))
36 | label.place(x=x_coor, y=y_coor, height=25)
37 |
38 | self.__data = StringVar()
39 | self.__data_entry = ttk.Entry(master, textvariable=self.__data, font=("Calibri", 10))
40 | self.__data_entry.place(x=150, y=y_coor, width=900, height=25)
41 |
42 | if has_button:
43 | self.__data_entry.config(state='readonly') # ttk.Entry uses 'readonly' for disabled look but selectable text
44 | button_title = 'Select ' + title
45 | # Using ttk.Button for consistency
46 | button = ttk.Button(master, text=button_title, command=action)
47 | button.place(x=button_xcoor, y=y_coor, width=180, height=25)
48 |
49 | def set_data(self, value):
50 | self.__data.set(value)
51 |
52 | def get_data(self):
53 | return self.__data.get()
54 |
55 | def change_state(self, state): # state can be 'normal', 'disabled', 'readonly'
56 | self.__data_entry.config(state=state)
57 |
58 | class LabelTag:
59 |
60 | def __init__(self, master, title, x_coor, y_coor, width, font_size=18, border=0, relief='flat'):
61 | # ttk.Label uses 'borderwidth' and 'relief' options directly in constructor.
62 | # Font can be set directly too.
63 | label = ttk.Label(master, text=title, borderwidth=border, relief=relief, font=("Calibri", font_size))
64 | label.place(x=x_coor, y=y_coor, width=width, height=25)
65 |
66 | class Options: # This class seems unused in Main.py based on current context, but updating it.
67 |
68 | def __init__(self, master, title, data, x_coor, y_coor, width):
69 | self.__selected = StringVar()
70 | # Ensure data is not empty and contains valid strings for OptionMenu
71 | if not data or not all(isinstance(item, str) for item in data):
72 | data = ["Default"] # Provide a default if data is invalid/empty
73 |
74 | label = ttk.Label(master, text=title, font=("Calibri", 12))
75 | label.place(x=x_coor, y=y_coor, height=25, width=100)
76 |
77 | self.__selected.set(data[0])
78 | # ttk.OptionMenu constructor is slightly different: master, variable, default_value, *values
79 | self.__option = ttk.OptionMenu(master, self.__selected, data[0], *data)
80 | self.__option.place(x=150, y=y_coor, height=25, width=width)
81 |
82 | def set_selected(self, data):
83 | self.__selected.set(data)
84 |
85 | def get_selected(self):
86 | return self.__selected.get()
87 |
88 | def update_data(self, data_list): # Assuming data_list is a list of strings
89 | # ttk.OptionMenu doesn't have direct option_clear/add. Recreate or set new menu.
90 | # For simplicity, if this method is crucial, it might need a more complex handling
91 | # such as destroying and recreating the OptionMenu or directly manipulating its internal menu.
92 | # A common approach is to update the StringVar and the list of options it points to,
93 | # then potentially re-initialize the OptionMenu if direct update isn't supported.
94 | # Given this class is likely unused, this simplification is acceptable for now.
95 | menu = self.__option["menu"]
96 | menu.delete(0, "end")
97 | if not data_list or not all(isinstance(item, str) for item in data_list):
98 | data_list = ["Default"]
99 |
100 | for string in data_list:
101 | menu.add_command(label=string, command=lambda value=string: self.__selected.set(value))
102 | self.__selected.set(data_list[0])
103 |
104 |
105 | class TestItem:
106 |
107 | def __init__(self, master, title, x_coor, y_coor, serial=False, p_value_x_coor=365, p_value_width=500, result_x_coor=870, result_width=350, font_size=12, two_columns=False):
108 | self.__chb_var = IntVar()
109 | self.__p_value = StringVar()
110 | self.__result = StringVar()
111 | self.__p_value_02 = StringVar()
112 | self.__result_02 = StringVar()
113 |
114 | # ttk.Checkbutton font is typically managed by style.
115 | # For direct font setting, it's less straightforward than tkinter.Checkbutton.
116 | # Using style is preferred. For now, let's omit direct font config on Checkbutton.
117 | checkbox = ttk.Checkbutton(master, text=title, variable=self.__chb_var)
118 | checkbox.place(x=x_coor, y=y_coor) # Consider adjusting height/width if needed
119 |
120 | p_value_entry = ttk.Entry(master, textvariable=self.__p_value, font=("Calibri", font_size))
121 | p_value_entry.config(state='readonly')
122 | p_value_entry.place(x=p_value_x_coor, y=y_coor, width=p_value_width, height=25)
123 |
124 | result_entry = ttk.Entry(master, textvariable=self.__result, font=("Calibri", font_size))
125 | result_entry.config(state='readonly')
126 | result_entry.place(x=result_x_coor, y=y_coor, width=result_width, height=25)
127 |
128 | if serial and two_columns:
129 | p_value_entry_02 = ttk.Entry(master, textvariable=self.__p_value_02, font=("Calibri", font_size))
130 | p_value_entry_02.config(state='readonly')
131 | p_value_entry_02.place(x=875, y=y_coor, width=235, height=25)
132 |
133 | result_entry_02 = ttk.Entry(master, textvariable=self.__result_02, font=("Calibri", font_size))
134 | result_entry_02.config(state='readonly')
135 | result_entry_02.place(x=1115, y=y_coor, width=110, height=25)
136 | elif serial and not two_columns: # This case seems specific for Serial test layout
137 | p_value_entry_02 = ttk.Entry(master, textvariable=self.__p_value_02, font=("Calibri", font_size))
138 | p_value_entry_02.config(state='readonly')
139 | p_value_entry_02.place(x=p_value_x_coor, y=y_coor+25, width=p_value_width, height=25) # Adjusted y
140 |
141 | result_entry_02 = ttk.Entry(master, textvariable=self.__result_02, font=("Calibri", font_size))
142 | result_entry_02.config(state='readonly')
143 | result_entry_02.place(x=result_x_coor, y=y_coor+25, width=result_width, height=25) # Adjusted y
144 |
145 | def get_check_box_value(self):
146 | return self.__chb_var.get()
147 |
148 | def set_check_box_value(self, value):
149 | self.__chb_var.set(value)
150 |
151 | def set_p_value(self, value):
152 | self.__p_value.set(value)
153 |
154 | def set_result_value(self, value):
155 | self.__result.set(value)
156 |
157 | def set_p_value_02(self, value):
158 | self.__p_value_02.set(value)
159 |
160 | def set_result_value_02(self, value):
161 | self.__result_02.set(value)
162 |
163 | def set_values(self, values):
164 | self.__p_value.set(values[0])
165 | self.__result.set(self.__get_result_string(values[1]))
166 |
167 | def set_p_2_values(self, values):
168 | self.__p_value_02(values[0])
169 | self.__result_02(self.__get_result_string(values[1]))
170 |
171 | def reset(self):
172 | self.set_check_box_value(0)
173 | self.set_p_value('')
174 | self.set_result_value('')
175 | self.set_p_value_02('')
176 | self.set_result_value_02('')
177 |
178 | def __get_result_string(self, result):
179 | if result == True:
180 | return 'Random'
181 | else:
182 | return 'Non-Random'
183 |
184 | class RandomExcursionTestItem:
185 |
186 | def __init__(self, master, title, x_coor, y_coor, data, variant=False, font_size=11):
187 | self.__chb_var = IntVar()
188 | self.__state = StringVar()
189 | self.__count = StringVar()
190 | self.__xObs = StringVar()
191 | self.__p_value = StringVar()
192 | self.__result = StringVar()
193 | self.__results = [] # This will store the list of tuples for excursion results
194 | self.__variant = variant
195 |
196 | # ttk.Checkbutton, font styling is less direct.
197 | checkbox = ttk.Checkbutton(master, text=title, variable=self.__chb_var)
198 | checkbox.place(x=x_coor, y=y_coor)
199 |
200 | # LabelTag is already updated to use ttk.Label
201 | state_label = LabelTag(master, 'State', (x_coor + 60), (y_coor + 30), width=100, font_size=font_size, border=2, relief='groove')
202 |
203 | # Ensure data for OptionMenu is valid
204 | if not data or not all(isinstance(item, str) for item in data):
205 | data = ["DefaultState"] # Fallback data
206 |
207 | if variant:
208 | self.__state.set(data[0] if data[0] in ['-9.0', '-8.0', '-7.0', '-6.0', '-5.0', '-4.0', '-3.0', '-2.0', '-1.0', '+1.0', '+2.0', '+3.0', '+4.0', '+5.0', '+6.0', '+7.0', '+8.0', '+9.0'] else data[0]) # Ensure initial value is in list
209 | else:
210 | self.__state.set(data[0] if data[0] in ['-4', '-3', '-2', '-1', '+1', '+2', '+3', '+4'] else data[0]) # Ensure initial value is in list
211 |
212 | state_option = ttk.OptionMenu(master, self.__state, self.__state.get(), *data)
213 | state_option.place(x=(x_coor + 60), y=(y_coor + 60), height=25, width=100)
214 | self.__state.trace_add("write", self.update) # Use trace_add for newer Tkinter versions
215 |
216 | entry_font = ("Calibri", font_size)
217 | if not variant:
218 | xObs_label = LabelTag(master, 'Chi^2', (x_coor + 165), (y_coor + 30), width=350, font_size=font_size, border=2, relief='groove')
219 | xObs_Entry = ttk.Entry(master, textvariable=self.__xObs, font=entry_font)
220 | xObs_Entry.config(state='readonly')
221 | xObs_Entry.place(x=(x_coor + 165), y=(y_coor + 60), width=350, height=25)
222 | else:
223 | count_label = LabelTag(master, 'Count', (x_coor + 165), (y_coor + 30), width=350, font_size=font_size, border=2, relief='groove')
224 | count_Entry = ttk.Entry(master, textvariable=self.__count, font=entry_font)
225 | count_Entry.config(state='readonly')
226 | count_Entry.place(x=(x_coor + 165), y=(y_coor + 60), width=350, height=25)
227 |
228 | p_value_label = LabelTag(master, 'P-Value', (x_coor + 520), (y_coor + 30), width=350, font_size=font_size, border=2, relief='groove')
229 | p_value_Entry = ttk.Entry(master, textvariable=self.__p_value, font=entry_font)
230 | p_value_Entry.config(state='readonly')
231 | p_value_Entry.place(x=(x_coor + 520), y=(y_coor + 60), width=350, height=25)
232 |
233 | conclusion_label = LabelTag(master, 'Result', (x_coor + 875), (y_coor + 30), width=150, font_size=font_size, border=2, relief='groove')
234 | conclusion_Entry = ttk.Entry(master, textvariable=self.__result, font=entry_font)
235 | conclusion_Entry.config(state='readonly')
236 | conclusion_Entry.place(x=(x_coor + 875), y=(y_coor + 60), width=150, height=25)
237 |
238 | def get_check_box_value(self):
239 | return self.__chb_var.get()
240 |
241 | def set_check_box_value(self, value):
242 | self.__chb_var.set(value)
243 |
244 | def set_results(self, results):
245 | self.__results = results
246 | self.update()
247 |
248 | def update(self, *_):
249 | match = False
250 | for result in self.__results:
251 | if result[0] == self.__state.get():
252 | if self.__variant:
253 | self.__count.set(result[2])
254 | else:
255 | self.__xObs.set(result[2])
256 |
257 | self.__p_value.set(result[3])
258 | self.__result.set(self.get_result_string(result[4]))
259 | match = True
260 |
261 | if not match:
262 | if self.__variant:
263 | self.__count.set('')
264 | else:
265 | self.__xObs.set('')
266 |
267 | self.__p_value.set('')
268 | self.__result.set('')
269 |
270 | def get_result_string(self, result):
271 | if result == True:
272 | return 'Random'
273 | else:
274 | return 'Non-Random'
275 |
276 | def reset(self):
277 | self.__chb_var.set('0')
278 | if self.__variant:
279 | self.__state.set('-1.0')
280 | self.__count.set('')
281 | else:
282 | self.__state.set('+1')
283 | self.__xObs.set('')
284 |
285 | self.__p_value.set('')
286 | self.__result.set('')
287 |
288 | class ScrollLabelFrame(LabelFrame):
289 | def __init__(self, parent, label):
290 | super().__init__(master=parent, text=label, padx=5, pady=5)
291 | self._canvas = Canvas(self, background="#ffffff")
292 | self.inner_frame = Frame(self._canvas, background="#ffffff")
293 | self._scroll_bar = Scrollbar(self, orient="vertical", command=self._canvas.yview)
294 | self._canvas.configure(yscrollcommand=self._scroll_bar.set)
295 | self._scroll_bar.pack(side="right", fill="y")
296 | self._canvas.pack(side="left", fill="both", expand=True)
297 | self._canvas_window = self._canvas.create_window((4,4), window=self.inner_frame, anchor="nw", #add view port frame to canvas
298 | tags="self.inner_frame")
299 |
300 | self.inner_frame.bind("", self.onFrameConfigure) # bind an event whenever the size of the viewPort frame changes.
301 | self._canvas.bind("", self.onCanvasConfigure) # bind an event whenever the size of the viewPort frame changes.
302 |
303 | self.onFrameConfigure(None)
304 |
305 | def onFrameConfigure(self, event):
306 | '''Reset the scroll region to encompass the inner frame'''
307 | self._canvas.configure(scrollregion=self._canvas.bbox(
308 | "all")) # whenever the size of the frame changes, alter the scroll region respectively.
309 |
310 | def onCanvasConfigure(self, event):
311 | '''Reset the canvas window to encompass inner frame when required'''
312 | canvas_width = event.width
313 | self._canvas.itemconfig(self._canvas_window, width=canvas_width)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2010-2019 Google, Inc. http://angularjs.org
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Main.py:
--------------------------------------------------------------------------------
1 | import os
2 | import threading
3 | import numpy as np
4 | from tkinter import *
5 | from tkinter.filedialog import askopenfilename # Removed askopenfilenames
6 | from tkinter.filedialog import asksaveasfile
7 | from tkinter import messagebox
8 | import queue
9 | from tkinter import ttk
10 |
11 | from GUI import CustomButton
12 | from GUI import Input
13 | from GUI import LabelTag
14 | from GUI import RandomExcursionTestItem
15 | from GUI import TestItem
16 | from Tools import Tools
17 |
18 | from ApproximateEntropy import ApproximateEntropy as aet
19 | from Complexity import ComplexityTest as ct
20 | from CumulativeSum import CumulativeSums as cst
21 | from FrequencyTest import FrequencyTest as ft
22 | from Matrix import Matrix as mt
23 | from RandomExcursions import RandomExcursions as ret
24 | from RunTest import RunTest as rt
25 | from Serial import Serial as serial
26 | from Spectral import SpectralTest as st
27 | from TemplateMatching import TemplateMatching as tm
28 | from Universal import Universal as ut
29 |
30 | class Main(Frame):
31 |
32 | def __init__(self, master=None):
33 |
34 | Frame.__init__(self, master=master)
35 | self._master = master
36 | self.init_variables()
37 | self.init_window()
38 |
39 | def init_variables(self):
40 |
41 | self._test_type = ['01. Frequency (Monobit) Test',
42 | '02. Frequency Test within a Block',
43 | '03. Runs Test',
44 | '04. Test for the Longest Run of Ones in a Block',
45 | '05. Binary Matrix Rank Test',
46 | '06. Discrete Fourier Transform (Spectral) Test',
47 | '07. Non-overlapping Template Matching Test',
48 | '08. Overlapping Template Matching Test',
49 | '09. Maurer\'s "Universal Statistical" Test',
50 | '10. Linear Complexity Test',
51 | '11. Serial Test',
52 | '12. Approximate Entropy Test',
53 | '13. Cumulative Sums Test (Forward)',
54 | '14. Cumulative Sums Test (Backward)',
55 | '15. Random Excursions Test',
56 | '16. Random Excursions Variant Test']
57 |
58 |
59 | self.__test_function = {
60 | 0:ft.monobit_test,
61 | 1:ft.block_frequency,
62 | 2:rt.run_test,
63 | 3:rt.longest_one_block_test,
64 | 4:mt.binary_matrix_rank_text,
65 | 5:st.spectral_test,
66 | 6:tm.non_overlapping_test,
67 | 7:tm.overlapping_patterns,
68 | 8:ut.statistical_test,
69 | 9:ct.linear_complexity_test,
70 | 10:serial.serial_test,
71 | 11:aet.approximate_entropy_test,
72 | 12:cst.cumulative_sums_test,
73 | 13:cst.cumulative_sums_test,
74 | 14:ret.random_excursions_test,
75 | 15:ret.variant_test
76 | }
77 |
78 | self._test_result = []
79 | self._test_string = []
80 | self._latest_results = []
81 | self._ui_queue = queue.Queue()
82 | self.__is_binary_file = False # Restored
83 | self.__is_data_file = False # Restored
84 | self.__file_name = "" # Restored for select_binary/data_file
85 |
86 | def init_window(self):
87 | frame_title = 'A Statistical Test Suite for Random and Pseudorandom Number Generators for Cryptographic Applications'
88 | title_label = LabelTag(self.master, frame_title, 0, 5, 1260)
89 |
90 | # Setup LabelFrame for Input - Reverted to original fixed height
91 | input_label_frame = LabelFrame(self.master, text="Input Data")
92 | input_label_frame.config(font=("Calibri", 14))
93 | input_label_frame.propagate(0) # Prevent resizing by children
94 | input_label_frame.place(x=20, y=30, width=1260, height=125) # Original height
95 |
96 | # Restore original Input widgets
97 | self.__binary_input = Input(input_label_frame, 'Binary Data', 10, 5)
98 | self.__binary_data_file_input = Input(input_label_frame, 'Binary Data File', 10, 35, True,
99 | self.select_binary_file, button_xcoor=1060, button_width=160)
100 | self.__string_data_file_input = Input(input_label_frame, 'String Data File', 10, 65, True,
101 | self.select_data_file, button_xcoor=1060, button_width=160)
102 |
103 | # Setup LabelFrame for Randomness Test - y position reverted
104 | self._stest_selection_label_frame = LabelFrame(self.master, text="Randomness Testing", padx=5, pady=5)
105 | self._stest_selection_label_frame.config(font=("Calibri", 14))
106 | self._stest_selection_label_frame.place(x=20, y=155, width=1260, height=450) # Original y and height
107 |
108 | test_type_label_01 = LabelTag(self._stest_selection_label_frame, 'Test Type', 10, 5, 250, 11, border=2,
109 | relief="groove")
110 | p_value_label_01 = LabelTag(self._stest_selection_label_frame, 'P-Value', 265, 5, 235, 11, border=2,
111 | relief="groove")
112 | result_label_01 = LabelTag(self._stest_selection_label_frame, 'Result', 505, 5, 110, 11, border=2,
113 | relief="groove")
114 |
115 | test_type_label_02 = LabelTag(self._stest_selection_label_frame, 'Test Type', 620, 5, 250, 11, border=2,
116 | relief="groove")
117 | p_value_label_02 = LabelTag(self._stest_selection_label_frame, 'P-Value', 875, 5, 235, 11, border=2,
118 | relief="groove")
119 | result_label_02 = LabelTag(self._stest_selection_label_frame, 'Result', 1115, 5, 110, 11, border=2,
120 | relief="groove")
121 |
122 | self._test = []
123 |
124 | self._monobit = TestItem(self._stest_selection_label_frame, self._test_type[0], 10, 35, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11)
125 | self._test.append(self._monobit)
126 |
127 | self._block = TestItem(self._stest_selection_label_frame, self._test_type[1], 620, 35, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11)
128 | self._test.append(self._block)
129 |
130 | self._run = TestItem(self._stest_selection_label_frame, self._test_type[2], 10, 60, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11)
131 | self._test.append(self._run)
132 |
133 | self._long_run = TestItem(self._stest_selection_label_frame, self._test_type[3], 620, 60, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11)
134 | self._test.append(self._long_run)
135 |
136 | self._rank = TestItem(self._stest_selection_label_frame, self._test_type[4], 10, 85, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11)
137 | self._test.append(self._rank)
138 |
139 | self._spectral = TestItem(self._stest_selection_label_frame, self._test_type[5], 620, 85, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11)
140 | self._test.append(self._spectral)
141 |
142 | self._non_overlappong = TestItem(self._stest_selection_label_frame, self._test_type[6], 10, 110, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11)
143 | self._test.append(self._non_overlappong)
144 |
145 | self._overlapping = TestItem(self._stest_selection_label_frame, self._test_type[7], 620, 110, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11)
146 | self._test.append(self._overlapping)
147 |
148 | self._universal = TestItem(self._stest_selection_label_frame, self._test_type[8], 10, 135, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11)
149 | self._test.append(self._universal)
150 |
151 | self._linear = TestItem(self._stest_selection_label_frame, self._test_type[9], 620, 135, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11)
152 | self._test.append(self._linear)
153 |
154 | self._serial = TestItem(self._stest_selection_label_frame, self._test_type[10], 10, 160, serial=True, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11, two_columns=True)
155 | self._test.append(self._serial)
156 |
157 | self._entropy = TestItem(self._stest_selection_label_frame, self._test_type[11], 10, 185, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11)
158 | self._test.append(self._entropy)
159 |
160 | self._cusum_f = TestItem(self._stest_selection_label_frame, self._test_type[12], 10, 210, p_value_x_coor=265, p_value_width=235, result_x_coor=505, result_width=110, font_size=11)
161 | self._test.append(self._cusum_f)
162 |
163 | self._cusum_r = TestItem(self._stest_selection_label_frame, self._test_type[13], 620, 210, p_value_x_coor=875, p_value_width=235, result_x_coor=1115, result_width=110, font_size=11)
164 | self._test.append(self._cusum_r)
165 |
166 | self._excursion = RandomExcursionTestItem(self._stest_selection_label_frame, self._test_type[14], 10, 235,
167 | ['-4', '-3', '-2', '-1', '+1', '+2', '+3', '+4'], font_size=11)
168 | self._test.append(self._excursion)
169 |
170 | self._variant = RandomExcursionTestItem(self._stest_selection_label_frame, self._test_type[15], 10, 325,
171 | ['-9.0', '-8.0', '-7.0', '-6.0', '-5.0', '-4.0', '-3.0', '-2.0',
172 | '-1.0',
173 | '+1.0', '+2.0', '+3.0', '+4.0', '+5.0', '+6.0', '+7.0', '+8.0',
174 | '+9.0'], variant=True, font_size=11)
175 | self._test.append(self._variant)
176 |
177 | self._result_field = [
178 | self._monobit,
179 | self._block,
180 | self._run,
181 | self._long_run,
182 | self._rank,
183 | self._spectral,
184 | self._non_overlappong,
185 | self._overlapping,
186 | self._universal,
187 | self._linear,
188 | self._serial,
189 | self._entropy,
190 | self._cusum_f,
191 | self._cusum_r
192 | ]
193 |
194 | select_all_button = CustomButton(self.master, 'Select All Test', 20, 615, 100, self.select_all)
195 | deselect_all_button = CustomButton(self.master, 'De-Select All Test', 125, 615, 150, self.deselect_all)
196 | self.execute_button = CustomButton(self.master, 'Execute Test', 280, 615, 100, self.execute)
197 | save_button = CustomButton(self.master, 'Save as Text File', 385, 615, 100, self.save_result_to_file)
198 | reset_button = CustomButton(self.master, 'Reset', 490, 615, 100, self.reset)
199 | exit_button = CustomButton(self.master, 'Exit Program', 595, 615, 100, self.exit) # This was 'exit' variable, changed to 'exit_button' for clarity
200 |
201 | # Frame for status elements - Adjusted y position
202 | status_frame = ttk.Frame(self.master)
203 | status_frame.place(x=20, y=635, width=1260, height=40)
204 |
205 | self.status_label = ttk.Label(status_frame, text="", font=("Calibri", 10), anchor="w")
206 | self.status_label.pack(side=TOP, fill=X, padx=5, pady=(0,2)) # pady to give a little space before progressbar
207 |
208 | self.progress_bar = ttk.Progressbar(status_frame, orient=HORIZONTAL, length=1260, mode='determinate')
209 | self.progress_bar.pack(side=BOTTOM, fill=X, padx=5, pady=(2,0))
210 | self.progress_bar['value'] = 0 # Ensure initial value is 0
211 |
212 | def select_binary_file(self):
213 | """
214 | Called tkinter.askopenfilename to give user an interface to select the binary input file and perform the following:
215 | 1. Clear Binary Data Input Field. (The textfield)
216 | 2. Set selected file name to Binary Data File Input Field.
217 | 3. Clear String Data file input field.
218 |
219 | :return: None
220 | """
221 | print('Select Binary File')
222 | self.__file_name = askopenfilename(initialdir=os.getcwd(), title="Select Binary Input File.")
223 | if self.__file_name:
224 | self.__binary_input.set_data('')
225 | self.__binary_data_file_input.set_data(self.__file_name)
226 | self.__string_data_file_input.set_data('')
227 | self.__is_binary_file = True
228 | self.__is_data_file = False
229 |
230 | def select_data_file(self):
231 | """
232 | Called tkinter.askopenfilename to give user an interface to select the string input file and perform the following:
233 | 1. Clear Binary Data Input Field. (The textfield)
234 | 2. Clear Binary Data File Input Field.
235 | 3. Set selected file name to String Data File Input Field.
236 |
237 | :return: None
238 | """
239 | print('Select Data File')
240 | self.__file_name = askopenfilename(initialdir=os.getcwd(), title="Select Data File.")
241 | if self.__file_name:
242 | self.__binary_input.set_data('')
243 | self.__binary_data_file_input.set_data('')
244 | self.__string_data_file_input.set_data(self.__file_name)
245 | self.__is_binary_file = False
246 | self.__is_data_file = True
247 |
248 | def select_all(self):
249 | """
250 | Select all test type displayed in the GUI. (Check all checkbox)
251 |
252 | :return: None
253 | """
254 | print('Select All Test')
255 | for item in self._test:
256 | item.set_check_box_value(1)
257 |
258 | def deselect_all(self):
259 | """
260 | Unchecked all checkbox
261 |
262 | :return: None
263 | """
264 | print('Deselect All Test')
265 | for item in self._test:
266 | item.set_check_box_value(0)
267 |
268 | def execute(self):
269 | """
270 | Execute the tests and display the result in the GUI
271 |
272 | :return: None
273 | """
274 | print('Execute')
275 |
276 | # Input validation and data preparation (reverted logic)
277 | if len(self.__binary_input.get_data().strip().rstrip()) == 0 and \
278 | len(self.__binary_data_file_input.get_data().strip().rstrip()) == 0 and \
279 | len(self.__string_data_file_input.get_data().strip().rstrip()) == 0:
280 | messagebox.showwarning("Warning", 'You must input the binary data or read the data from from the file.')
281 | return None
282 | elif (len(self.__binary_input.get_data().strip().rstrip()) > 0 and \
283 | (len(self.__binary_data_file_input.get_data().strip().rstrip()) > 0 or \
284 | len(self.__string_data_file_input.get_data().strip().rstrip()) > 0)) or \
285 | (len(self.__binary_data_file_input.get_data().strip().rstrip()) > 0 and \
286 | len(self.__string_data_file_input.get_data().strip().rstrip()) > 0):
287 | messagebox.showwarning("Warning", 'You can only use one input method at a time: direct binary, binary data file, or string data file.')
288 | return None
289 |
290 | input_data_sequences = [] # Will hold the single binary string to test
291 |
292 | if not len(self.__binary_input.get_data()) == 0:
293 | input_data_sequences.append(self.__binary_input.get_data().strip())
294 | status_message = "Processing direct binary input..."
295 | elif self.__is_binary_file and self.__file_name: # Binary Data File
296 | try:
297 | with open(self.__file_name, 'r') as handle:
298 | temp = [data.strip().rstrip() for data in handle]
299 | test_data = ''.join(temp)[:1000000]
300 | if not all(c in '01' for c in test_data):
301 | messagebox.showerror("Error", f"File {self.__file_name} contains non-binary characters.")
302 | return None
303 | if not test_data:
304 | messagebox.showwarning("Warning", f"Binary data file '{os.path.basename(self.__file_name)}' is empty or resulted in empty data.")
305 | return None
306 | input_data_sequences.append(test_data)
307 | status_message = f"Processing binary data file: {os.path.basename(self.__file_name)}..."
308 | except Exception as e:
309 | messagebox.showerror("Error reading binary data file", f"Could not read file {self.__file_name}: {e}")
310 | return None
311 | elif self.__is_data_file and self.__file_name: # String Data File
312 | processed_binary_data_list = []
313 | try:
314 | with open(self.__file_name, 'r') as handle:
315 | for item in handle:
316 | item_stripped = item.strip()
317 | if not item_stripped: continue
318 | if item_stripped.startswith('http://') or item_stripped.startswith('https://'):
319 | url_content = Tools.url_to_binary(item_stripped)
320 | processed_binary_data_list.append(Tools.string_to_binary(url_content))
321 | else:
322 | processed_binary_data_list.append(Tools.string_to_binary(item_stripped))
323 | test_data = "".join(processed_binary_data_list)
324 | if not test_data:
325 | messagebox.showwarning("Warning", f"String data file '{os.path.basename(self.__file_name)}' resulted in empty binary data.")
326 | return None
327 | # Basic validation for binary string (already done by Tools.string_to_binary generally)
328 | input_data_sequences.append(test_data)
329 | status_message = f"Processing string data file: {os.path.basename(self.__file_name)}..."
330 | except Exception as e:
331 | messagebox.showerror("Error processing string data file", f"Could not process file {self.__file_name}: {e}")
332 | return None
333 |
334 | if not input_data_sequences or not input_data_sequences[0]:
335 | messagebox.showwarning("Warning", "Input data is empty or could not be processed.")
336 | return None
337 |
338 | test_data_to_process = input_data_sequences[0] # Worker expects a single string
339 |
340 | # Length validation (re-add if needed, using self._test_min_lengths)
341 | # selected_test_indices = self._get_selected_test_indices() # This method was removed, need to inline or re-add
342 | # if not selected_test_indices:
343 | # messagebox.showwarning("Warning", "No tests selected.")
344 | # return None
345 | # validation_result = self._validate_input_length(len(test_data_to_process), selected_test_indices)
346 | # if isinstance(validation_result, str):
347 | # messagebox.showwarning("Input Data Warning", validation_result)
348 | # return None
349 |
350 | try:
351 | self.execute_button.config(state=DISABLED) # This should use the CustomButton's config
352 | self.status_label.config(text=status_message) # Use the status_message set above
353 | self.progress_bar['value'] = 0
354 | self.progress_bar['maximum'] = 100 # Default max, will be updated by 'start' msg from worker
355 |
356 | self._latest_results = [] # Clear previous results
357 |
358 | # Pass the determined test_data_to_process (list of paths or single string) to the worker
359 | worker_thread = threading.Thread(target=self._execute_tests_worker, args=(test_data_to_process,))
360 | worker_thread.start()
361 |
362 | self.master.after(100, self._process_ui_queue)
363 | except Exception as e:
364 | messagebox.showerror("Error", str(e))
365 | print(e)
366 |
367 | def _execute_tests_worker(self, test_data_input):
368 | """
369 | Worker method to execute randomness tests in a separate thread.
370 | Stores results in self._latest_results and appends to self._test_result (for single runs).
371 | Handles batch processing by iterating through files. (Reverted: only single string input)
372 | """
373 | # Reverted: This worker now only processes a single string.
374 | # selected_test_indices = self._get_selected_test_indices() # This method is removed
375 | # num_selected_tests = len(selected_test_indices)
376 |
377 | num_selected_tests = sum(1 for item in self._test if item.get_check_box_value() == 1)
378 |
379 | if num_selected_tests == 0:
380 | self._ui_queue.put({'type': 'error', 'message': 'No tests selected.'})
381 | return
382 |
383 | self._ui_queue.put({'type': 'start', 'total_tests': num_selected_tests}) # Removed mode
384 | try:
385 | current_run_results = [() for _ in range(len(self._test_type))]
386 | completed_count = 0
387 | test_idx = 0 # Reverted from iterating selected_test_indices
388 | for item in self._test: # Reverted
389 | if item.get_check_box_value() == 1:
390 | if test_idx == 13:
391 | current_run_results[test_idx] = self.__test_function[test_idx](test_data_input, mode=1)
392 | else:
393 | current_run_results[test_idx] = self.__test_function[test_idx](test_data_input)
394 | completed_count += 1
395 | self._ui_queue.put({
396 | 'type': 'progress',
397 | 'test_name': self._test_type[test_idx],
398 | 'completed_tests': completed_count,
399 | 'total_tests_in_current_run': num_selected_tests
400 | })
401 | test_idx += 1
402 |
403 | self._latest_results = current_run_results
404 | self._test_result.insert(0, self._latest_results)
405 | self._ui_queue.put({'type': 'complete', 'results': self._latest_results}) # Removed mode
406 | print("Test run completed in worker. Results sent to UI queue.")
407 | except Exception as e:
408 | print(f"Error in worker thread: {e}")
409 | self._ui_queue.put({'type': 'error', 'message': str(e)})
410 |
411 | def _process_ui_queue(self):
412 | """
413 | Process messages from the UI queue to update the GUI.
414 | """
415 | try:
416 | while True: # Process all messages currently in the queue
417 | msg = self._ui_queue.get_nowait()
418 |
419 | if msg['type'] == 'start':
420 | # Removed mode handling, progress bar max is always total_tests
421 | self.progress_bar['maximum'] = msg['total_tests'] if msg.get('total_tests', 0) > 0 else 100
422 | self.progress_bar['value'] = 0
423 | self.status_label.config(text=f"Test run started. Total selected tests: {msg['total_tests']}.")
424 | self.write_results([])
425 |
426 | elif msg['type'] == 'progress':
427 | self.progress_bar['value'] = msg['completed_tests']
428 | self.status_label.config(text=f"Running test {msg['completed_tests']}/{msg['total_tests_in_current_run']}: {msg['test_name']}...")
429 |
430 | elif msg['type'] == 'complete': # Reverted: only one type of complete
431 | self.status_label.config(text="Test run completed successfully.")
432 | self.write_results(msg['results'])
433 | messagebox.showinfo("Execute", "Test Run Complete.")
434 | self.progress_bar['value'] = 0
435 | self.execute_button.config(state=NORMAL)
436 | return
437 |
438 | elif msg['type'] == 'error':
439 | self.status_label.config(text=f"Error: {msg['message']}")
440 | messagebox.showerror("Error", msg['message'])
441 | self.progress_bar['value'] = 0
442 | self.execute_button.config(state=NORMAL)
443 | self._current_processing_mode = None
444 | return
445 |
446 | except queue.Empty:
447 | # If queue is empty, do nothing and continue polling
448 | pass
449 | except Exception as e:
450 | # Handle any other unexpected errors during UI update
451 | print(f"Error processing UI queue: {e}")
452 | self.status_label.config(text="Error updating UI.")
453 | self.execute_button.config(state=NORMAL) # Ensure button is re-enabled
454 | return # Stop polling on unexpected error
455 |
456 | self.master.after(100, self._process_ui_queue) # Continue polling
457 |
458 | def write_results(self, results):
459 | """
460 | Write the result in the GUI
461 |
462 | :param results: result of the randomness test
463 | :return: None
464 | """
465 | count = 0
466 | for result in results:
467 | if len(result) == 0:
468 | if count == 10:
469 | self._result_field[count].set_p_value('')
470 | self._result_field[count].set_result_value('')
471 | self._result_field[count].set_p_value_02('')
472 | self._result_field[count].set_result_value_02('')
473 | elif count == 14:
474 | self._excursion.set_results('')
475 | elif count == 15:
476 | self._variant.set_results('')
477 | else:
478 | self._result_field[count].set_p_value('')
479 | self._result_field[count].set_result_value('')
480 | else:
481 | if count == 10:
482 | self._result_field[count].set_p_value(result[0][0])
483 | self._result_field[count].set_result_value(self.get_result_string(result[0][1]))
484 | self._result_field[count].set_p_value_02(result[1][0])
485 | self._result_field[count].set_result_value_02(self.get_result_string(result[1][1]))
486 | elif count == 14:
487 | print(result)
488 | self._excursion.set_results(result)
489 | elif count == 15:
490 | print(result)
491 | self._variant.set_results(result)
492 | else:
493 | self._result_field[count].set_p_value(result[0])
494 | self._result_field[count].set_result_value(self.get_result_string(result[1]))
495 |
496 |
497 | count += 1
498 |
499 | def save_result_to_file(self): # Reverted signature
500 | print('Save to File')
501 | if not self._test_result: # Check if there are any results to save
502 | messagebox.showwarning("Save Warning", "No test results available to save.")
503 | return
504 |
505 | results_to_save = self._test_result[0] # Get the latest results
506 |
507 | # Determine original_file_info_string based on input method
508 | original_file_info_string = "Test Data Source: Unknown"
509 | if not len(self.__binary_input.get_data()) == 0:
510 | original_file_info_string = 'Test Data (Direct Input):\n' + self.__binary_input.get_data()
511 | elif self.__is_binary_file and self.__file_name:
512 | original_file_info_string = 'Test Data File (Binary):\n' + self.__file_name
513 | elif self.__is_data_file and self.__file_name:
514 | original_file_info_string = 'Test Data File (String/URL):\n' + self.__file_name
515 |
516 | try:
517 | # Use asksaveasfile to prompt user for filename
518 | output_file_obj = asksaveasfile(mode='w', defaultextension=".txt",
519 | title="Save Test Report As",
520 | filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
521 | if output_file_obj is None: # User cancelled
522 | return
523 |
524 | with output_file_obj: # Ensures file is closed automatically
525 | output_file_obj.write(original_file_info_string + '\n\n\n')
526 | output_file_obj.write('%-50s\t%-20s\t%-10s\n' % ('Type of Test', 'P-Value', 'Conclusion'))
527 | self._write_detailed_results_to_file(output_file_obj, results_to_save)
528 |
529 | messagebox.showinfo("Save", f"File save finished. Report saved to {output_file_obj.name}.")
530 | except Exception as e:
531 | messagebox.showerror("Save Error", f"Could not save report: {e}")
532 |
533 |
534 | def _write_detailed_results_to_file(self, output_file, result_data): # Name kept, but logic is original
535 | """
536 | Helper method to write the detailed test results to an open file object.
537 | """
538 | for test_idx in range(len(self._test_type)):
539 | # Check if test was selected and results exist for this index
540 | if test_idx < len(result_data) and self._test[test_idx].get_check_box_value() == 1 and result_data[test_idx]:
541 | current_result = result_data[test_idx]
542 | if not current_result:
543 | continue
544 |
545 | if test_idx == 10:
546 | output_file.write(self._test_type[test_idx] + ':\n') # Original logic for Serial
547 | output = '\t\t\t\t\t\t\t\t\t\t\t\t\t%-20s\t%s\n' % (
548 | str(current_result[0][0]), self.get_result_string(current_result[0][1]))
549 | output_file.write(output)
550 | output = '\t\t\t\t\t\t\t\t\t\t\t\t\t%-20s\t%s\n' % (
551 | str(current_result[1][0]), self.get_result_string(current_result[1][1]))
552 | output_file.write(output)
553 | elif test_idx == 14: # Original logic for Random Excursions
554 | output_file.write(self._test_type[test_idx] + ':\n')
555 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ('State ', 'Chi Squared', 'P-Value', 'Conclusion')
556 | output_file.write(output)
557 | for item in current_result:
558 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % (
559 | item[0], item[2], item[3], self.get_result_string(item[4]))
560 | output_file.write(output)
561 | elif test_idx == 15: # Original logic for Random Excursions Variant
562 | output_file.write(self._test_type[test_idx] + ':\n')
563 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ('State ', 'COUNTS', 'P-Value', 'Conclusion')
564 | output_file.write(output)
565 | for item in current_result:
566 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % (
567 | item[0], item[2], item[3], self.get_result_string(item[4]))
568 | output_file.write(output)
569 | else: # Original logic for other tests
570 | output = '%-50s\t%-20s\t%s\n' % (
571 | self._test_type[test_idx], str(current_result[0]), self.get_result_string(current_result[1]))
572 | output_file.write(output)
573 | elif self._test[test_idx].get_check_box_value() == 1:
574 | output_file.write(f"{self._test_type[test_idx]}\t-\tTest selected but no result data.\n")
575 |
576 |
577 | #def change_data(self):
578 | # index = int(self.__test_data.get_selected().split(' ')[0])
579 | # print(self.__test_result[index-1])
580 | # self.write_results(self.__test_result[index-1])
581 |
582 | def reset(self):
583 | """
584 | Reset the GUI:
585 | 1. Clear all input in the textfield.
586 | 2. Unchecked all checkbox
587 |
588 | :return: None
589 | """
590 | print('Reset')
591 | self.__binary_input.set_data('')
592 | self.__binary_data_file_input.set_data('') # Restored
593 | self.__string_data_file_input.set_data('') # Restored
594 |
595 | self.__is_binary_file = False
596 | self.__is_data_file = False
597 |
598 | # Resetting UI elements
599 | if hasattr(self, 'status_label'):
600 | self.status_label.config(text="")
601 | if hasattr(self, 'progress_bar'):
602 | self.progress_bar['value'] = 0
603 | # Removed data_info_label reset as it's being removed
604 |
605 | self._monobit.reset()
606 | self._block.reset()
607 | self._run.reset()
608 | self._long_run.reset()
609 | self._rank.reset()
610 | self._spectral.reset()
611 | self._non_overlappong.reset()
612 | self._overlapping.reset()
613 | self._universal.reset()
614 | self._linear.reset()
615 | self._serial.reset()
616 | self._entropy.reset()
617 | self._cusum_f.reset()
618 | self._cusum_r.reset()
619 | self._excursion.reset()
620 | self._variant.reset()
621 | #self.__test_data = Options(self.__stest_selection_label_frame, 'Input Data', [''], 10, 5, 900)
622 | self._test_result = []
623 | self._test_string = []
624 |
625 | def exit(self):
626 | """
627 | Exit this program normally
628 |
629 | :return: None
630 | """
631 | print('Exit')
632 | exit(0)
633 |
634 | def get_result_string(self, result):
635 | """
636 | Interpret the result and return either 'Random' or 'Non-Random'
637 |
638 | :param result: Result of the test (either True or False)
639 | :return: str (Either 'Random' for True and 'Non-Random' for False
640 | """
641 | if result:
642 | return 'Random'
643 | else:
644 | return 'Non-Random'
645 |
646 | if __name__ == '__main__':
647 | np.seterr('raise') # Make exceptions fatal, otherwise GUI might get inconsistent
648 | root = Tk()
649 | root.resizable(0, 0)
650 | root.geometry("%dx%d+0+0" % (1300, 650)) # Reverted window height
651 | title = 'Test Suite for NIST Random Numbers'
652 | root.title(title)
653 | app = Main(root)
654 | app.focus_displayof()
655 | app.mainloop()
--------------------------------------------------------------------------------
/Matrix.py:
--------------------------------------------------------------------------------
1 | from BinaryMatrix import BinaryMatrix as bm
2 | from math import exp as exp
3 | from math import floor as floor
4 | from numpy import zeros as zeros
5 |
6 | class Matrix:
7 |
8 | @staticmethod
9 | def binary_matrix_rank_text(binary_data:str, verbose=False, rows_in_matrix = 32, columns_in_matrix = 32):
10 | """
11 | Note that this description is taken from the NIST documentation [1]
12 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf
13 | The focus of the test is the rank of disjoint sub-matrices of the entire sequence. The purpose of this test is
14 | to check for linear dependence among fixed length sub strings of the original sequence. Note that this test
15 | also appears in the DIEHARD battery of tests.
16 |
17 | :param binary_data The seuqnce of bit being tested
18 | :param verbose True to display the debug messgae, False to turn off debug message
19 | :param rows_in_matrix Fixed for 32
20 | :param columns_in_matrix Fixed for 32
21 | :return (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
22 | """
23 |
24 | shape = (rows_in_matrix, columns_in_matrix)
25 | length_of_binary_data = len(binary_data)
26 | block_size = int(rows_in_matrix * columns_in_matrix)
27 | number_of_block = floor(length_of_binary_data / block_size)
28 | block_start = 0
29 | block_end = block_size
30 |
31 | if number_of_block > 0:
32 | max_ranks = [0, 0, 0]
33 |
34 | for im in range(number_of_block):
35 | block_data = binary_data[block_start:block_end]
36 | block = zeros(len(block_data))
37 |
38 | for count in range(len(block_data)):
39 | if block_data[count] == '1':
40 | block[count] = 1.0
41 |
42 | matrix = block.reshape(shape)
43 | ranker = bm(matrix, rows_in_matrix, columns_in_matrix)
44 | rank = ranker.compute_rank()
45 |
46 | if rank == rows_in_matrix:
47 | max_ranks[0] += 1
48 | elif rank == (rows_in_matrix - 1):
49 | max_ranks[1] += 1
50 | else:
51 | max_ranks[2] += 1
52 |
53 | block_start += block_size
54 | block_end += block_size
55 |
56 | pi = [1.0, 0.0, 0.0]
57 | for x in range(1, 50):
58 | pi[0] *= 1 - (1.0 / (2 ** x))
59 | pi[1] = 2 * pi[0]
60 | pi[2] = 1 - pi[0] - pi[1]
61 |
62 | xObs = 0.0
63 | for i in range(len(pi)):
64 | xObs += pow((max_ranks[i] - pi[i] * number_of_block), 2.0) / (pi[i] * number_of_block)
65 |
66 | p_value = exp(-xObs / 2)
67 |
68 | if verbose:
69 | print('Binary Matrix Rank Test DEBUG BEGIN:')
70 | print("\tLength of input:\t", length_of_binary_data)
71 | print("\tSize of Row:\t\t", rows_in_matrix)
72 | print("\tSize of Column:\t\t", columns_in_matrix)
73 | print('\tValue of N:\t\t\t', number_of_block)
74 | print('\tValue of Pi:\t\t', pi)
75 | print('\tValue of xObs:\t\t', xObs)
76 | print('\tP-Value:\t\t\t', p_value)
77 | print('DEBUG END.')
78 |
79 | return (p_value, (p_value >= 0.01))
80 | else:
81 | return (-1.0, False)
--------------------------------------------------------------------------------
/OLD_Main.py:
--------------------------------------------------------------------------------
1 | import os
2 | from tkinter import *
3 | from tkinter.filedialog import askopenfilename
4 | from tkinter.filedialog import asksaveasfile
5 | from tkinter import messagebox
6 |
7 | from ApproximateEntropy import ApproximateEntropy as aet
8 | from Complexity import ComplexityTest as ct
9 | from CumulativeSum import CumulativeSums as cst
10 | from FrequencyTest import FrequencyTest as ft
11 | from Matrix import Matrix as mt
12 | from RandomExcursions import RandomExcursions as ret
13 | from RunTest import RunTest as rt
14 | from Serial import Serial as serial
15 | from Spectral import SpectralTest as st
16 | from TemplateMatching import TemplateMatching as tm
17 | from Universal import Universal as ut
18 |
19 | from GUI import CustomButton
20 | from GUI import Input
21 | from GUI import LabelTag
22 | from GUI import Options
23 | from GUI import RandomExcursionTestItem
24 | from GUI import TestItem
25 |
26 | from Tools import Tools
27 |
28 | class Main(Frame):
29 |
30 | # Constructor. Initialized the variables.
31 | def __init__(self, master=None):
32 | Frame.__init__(self, master)
33 | self.master = master
34 | self.init_variables()
35 | self.init_window()
36 |
37 | def init_variables(self):
38 | self.__test_type = ['01. Frequency Test (Monobit)', '02. Frequency Test within a Block', '03. Run Test',
39 | '04. Longest Run of Ones in a Block', '05. Binary Matrix Rank Test',
40 | '06. Discrete Fourier Transform (Spectral) Test',
41 | '07. Non-Overlapping Template Matching Test',
42 | '08. Overlapping Template Matching Test', '09. Maurer\'s Universal Statistical test',
43 | '10. Linear Complexity Test', '11. Serial test', '12. Approximate Entropy Test',
44 | '13. Cummulative Sums (Forward) Test', '14. Cummulative Sums (Reverse) Test',
45 | '15. Random Excursions Test', '16. Random Excursions Variant Test']
46 |
47 | self.__test_function = {
48 | 0:ft.monobit_test,
49 | 1:ft.block_frequency,
50 | 2:rt.run_test,
51 | 3:rt.longest_one_block_test,
52 | 4:mt.binary_matrix_rank_text,
53 | 5:st.spectral_test,
54 | 6:tm.non_overlapping_test,
55 | 7:tm.overlapping_patterns,
56 | 8:ut.statistical_test,
57 | 9:ct.linear_complexity_test,
58 | 10:serial.serial_test,
59 | 11:aet.approximate_entropy_test,
60 | 12:cst.cumulative_sums_test,
61 | 13:cst.cumulative_sums_test,
62 | 14:ret.random_excursions_test,
63 | 15:ret.variant_test
64 | }
65 |
66 | self.__test_result = []
67 | self.__test_string = []
68 |
69 | def init_window(self):
70 |
71 | # Title Label
72 | frame_title = 'A Statistical Test Suite for Random and Pseudorandom Number Generators for Cryptographic Applications'
73 | title_label = LabelTag(self.master, frame_title, 0, 5, 1280)
74 |
75 | # Setup LabelFrame for Input
76 | input_label_frame = LabelFrame(self.master, text="Input Data")
77 | input_label_frame.config(font=("Calibri", 14))
78 | input_label_frame.propagate(0)
79 | input_label_frame.place(x=20, y=30, width=1240, height=125)
80 |
81 | self.__binary_input = Input(input_label_frame, 'Binary Data', 10, 5)
82 | self.__binary_data_file_input = Input(input_label_frame, 'Binary Data File', 10, 35, True, self.select_binary_file)
83 | self.__string_data_file_input = Input(input_label_frame, 'String Data File', 10, 65, True, self.select_data_file)
84 |
85 | # Setup LabelFrame for Randomness Test
86 | self.__stest_selection_label_frame = LabelFrame(self.master, text="Randomness Testing", padx=5, pady=5)
87 | self.__stest_selection_label_frame.config(font=("Calibri", 14))
88 | self.__stest_selection_label_frame.place(x=20, y=155, width=1240, height=600)
89 |
90 | #self.__test_data = Options(self.__stest_selection_label_frame, 'Input Data', [''], 10, 5, 900)
91 | #change_data_button = CustomButton(self.__stest_selection_label_frame, 'Change Data', 1050, 5, 180, action=self.change_data)
92 |
93 | test_type_label = LabelTag(self.__stest_selection_label_frame, 'Test Type', 10, 5, 350, 12, border=2,relief="groove")
94 | p_value_label = LabelTag(self.__stest_selection_label_frame, 'P-Value', 365, 5, 500, 12, border=2,relief="groove")
95 | result_label = LabelTag(self.__stest_selection_label_frame, 'Result', 870, 5, 350, 12, border=2,relief="groove")
96 |
97 | self.__test = []
98 |
99 | self.__monobit = TestItem(self.__stest_selection_label_frame, self.__test_type[0], 10, 35)
100 | self.__test.append(self.__monobit)
101 |
102 | self.__block = TestItem(self.__stest_selection_label_frame, self.__test_type[1], 10, 60)
103 | self.__test.append(self.__block)
104 |
105 | self.__run = TestItem(self.__stest_selection_label_frame, self.__test_type[2], 10, 85)
106 | self.__test.append(self.__run)
107 |
108 | self.__long_run = TestItem(self.__stest_selection_label_frame, self.__test_type[3], 10, 110)
109 | self.__test.append(self.__long_run)
110 |
111 | self.__rank = TestItem(self.__stest_selection_label_frame, self.__test_type[4], 10, 135)
112 | self.__test.append(self.__rank)
113 |
114 | self.__spectral = TestItem(self.__stest_selection_label_frame, self.__test_type[5], 10, 160)
115 | self.__test.append(self.__spectral)
116 |
117 | self.__non_overlappong = TestItem(self.__stest_selection_label_frame, self.__test_type[6], 10, 185)
118 | self.__test.append(self.__non_overlappong)
119 |
120 | self.__overlapping = TestItem(self.__stest_selection_label_frame, self.__test_type[7], 10, 210)
121 | self.__test.append(self.__overlapping)
122 |
123 | self.__universal = TestItem(self.__stest_selection_label_frame, self.__test_type[8], 10, 235)
124 | self.__test.append(self.__universal)
125 |
126 | self.__linear = TestItem(self.__stest_selection_label_frame, self.__test_type[9], 10, 260)
127 | self.__test.append(self.__linear)
128 |
129 | self.__serial = TestItem(self.__stest_selection_label_frame, self.__test_type[10], 10, 285, serial=True)
130 | self.__test.append(self.__serial)
131 |
132 | self.__entropy = TestItem(self.__stest_selection_label_frame, self.__test_type[11], 10, 310)
133 | self.__test.append(self.__entropy)
134 |
135 | self.__cusum_f = TestItem(self.__stest_selection_label_frame, self.__test_type[12], 10, 335)
136 | self.__test.append(self.__cusum_f)
137 |
138 | self.__cusum_r = TestItem(self.__stest_selection_label_frame, self.__test_type[13], 10, 360)
139 | self.__test.append(self.__cusum_r)
140 |
141 | self.__excursion = RandomExcursionTestItem(self.__stest_selection_label_frame, self.__test_type[14], 10, 385,
142 | ['-4', '-3', '-2', '-1', '+1', '+2', '+3', '+4'])
143 | self.__test.append(self.__excursion)
144 |
145 | self.__variant = RandomExcursionTestItem(self.__stest_selection_label_frame, self.__test_type[15], 10, 475,
146 | ['-9.0', '-8.0', '-7.0', '-6.0', '-5.0', '-4.0', '-3.0', '-2.0', '-1.0',
147 | '+1.0', '+2.0', '+3.0', '+4.0', '+5.0', '+6.0', '+7.0', '+8.0', '+9.0'], variant=True)
148 | self.__test.append(self.__variant)
149 |
150 | self.__result_field = [
151 | self.__monobit,
152 | self.__block,
153 | self.__run,
154 | self.__long_run,
155 | self.__rank,
156 | self.__spectral,
157 | self.__non_overlappong,
158 | self.__overlapping,
159 | self.__universal,
160 | self.__linear,
161 | self.__serial,
162 | self.__entropy,
163 | self.__cusum_f,
164 | self.__cusum_r
165 | ]
166 |
167 | select_all_button = CustomButton(self.master, 'Select All Test', 20, 760, 100, self.select_all)
168 | deselect_all_button = CustomButton(self.master, 'De-Select All Test', 125, 760, 150, self.deselect_all)
169 | execute_button = CustomButton(self.master, 'Execute Test', 280, 760, 100, self.execute)
170 | save_button = CustomButton(self.master, 'Save as Text File', 385, 760, 100, self.save_result_to_file)
171 | reset_button = CustomButton(self.master, 'Reset', 490, 760, 100, self.reset)
172 | exit = CustomButton(self.master, 'Exit Program', 595, 760, 100, self.exit)
173 |
174 | def select_binary_file(self):
175 | """
176 | Called tkinter.askopenfilename to give user an interface to select the binary input file and perform the following:
177 | 1. Clear Binary Data Input Field. (The textfield)
178 | 2. Set selected file name to Binary Data File Input Field.
179 | 3. Clear String Data file input field.
180 |
181 | :return: None
182 | """
183 | print('Select Binary File')
184 | self.__file_name = askopenfilename(initialdir=os.getcwd(), title="Select Binary Input File.")
185 | if self.__file_name:
186 | self.__binary_input.set_data('')
187 | self.__binary_data_file_input.set_data(self.__file_name)
188 | self.__string_data_file_input.set_data('')
189 | self.__is_binary_file = True
190 | self.__is_data_file = False
191 |
192 | def select_data_file(self):
193 | """
194 | Called tkinter.askopenfilename to give user an interface to select the string input file and perform the following:
195 | 1. Clear Binary Data Input Field. (The textfield)
196 | 2. Clear Binary Data File Input Field.
197 | 3. Set selected file name to String Data File Input Field.
198 |
199 | :return: None
200 | """
201 | print('Select Data File')
202 | self.__file_name = askopenfilename(initialdir=os.getcwd(), title="Select Data File.")
203 | if self.__file_name:
204 | self.__binary_input.set_data('')
205 | self.__binary_data_file_input.set_data('')
206 | self.__string_data_file_input.set_data(self.__file_name)
207 | self.__is_binary_file = False
208 | self.__is_data_file = True
209 |
210 | def select_all(self):
211 | """
212 | Select all test type displayed in the GUI. (Check all checkbox)
213 |
214 | :return: None
215 | """
216 | print('Select All Test')
217 | for item in self.__test:
218 | item.set_check_box_value(1)
219 |
220 | def deselect_all(self):
221 | """
222 | Unchecked all checkbox
223 |
224 | :return: None
225 | """
226 | print('Deselect All Test')
227 | for item in self.__test:
228 | item.set_check_box_value(0)
229 |
230 | def execute(self):
231 | """
232 | Execute the tests and display the result in the GUI
233 |
234 | :return: None
235 | """
236 | print('Execute')
237 |
238 | if len(self.__binary_input.get_data().strip().rstrip()) == 0 and\
239 | len(self.__binary_data_file_input.get_data().strip().rstrip()) == 0 and\
240 | len(self.__string_data_file_input.get_data().strip().rstrip()) == 0:
241 | messagebox.showwarning("Warning",
242 | 'You must input the binary data or read the data from from the file.')
243 | return None
244 | elif len(self.__binary_input.get_data().strip().rstrip()) > 0 and\
245 | len(self.__binary_data_file_input.get_data().strip().rstrip()) > 0 and\
246 | len(self.__string_data_file_input.get_data().strip().rstrip()) > 0:
247 | messagebox.showwarning("Warning",
248 | 'You can either input the binary data or read the data from from the file.')
249 | return None
250 |
251 | input = []
252 |
253 | if not len(self.__binary_input.get_data()) == 0:
254 | input.append(self.__binary_input.get_data())
255 | elif not len(self.__binary_data_file_input.get_data()) == 0:
256 | temp = []
257 | if self.__file_name:
258 | handle = open(self.__file_name)
259 | for data in handle:
260 | temp.append(data.strip().rstrip())
261 | test_data = ''.join(temp)
262 | input.append(test_data[:1000000])
263 | elif not len(self.__string_data_file_input.get_data()) == 0:
264 | data = []
265 | count = 1
266 | if self.__file_name:
267 | handle = open(self.__file_name)
268 | for item in handle:
269 | if item.startswith('http://'):
270 | url = Tools.url_to_binary(item)
271 | data.append(Tools.string_to_binary(url))
272 | else:
273 | data.append(Tools.string_to_binary(item))
274 | count += 1
275 | print(data)
276 | input.append(''.join(data))
277 |
278 | #print(data)
279 | #self.__test_data = Options(self.__stest_selection_label_frame, 'Input Data', data, 10, 5, 900)
280 |
281 | for test_data in input:
282 | count = 0
283 | results = [(), (), (), (), (), (), (), (), (), (), (), (), (), (), (), ()]
284 | for item in self.__test:
285 | if item.get_check_box_value() == 1:
286 | print(self.__test_type[count], ' selected. ', self.__test_function[count](test_data))
287 | if count == 13:
288 | results[count] = self.__test_function[count](test_data, mode=1)
289 | else:
290 | results[count] = self.__test_function[count](test_data)
291 | count += 1
292 | self.__test_result.append(results)
293 |
294 | self.write_results(self.__test_result[0])
295 | messagebox.showinfo("Execute", "Test Complete.")
296 |
297 | def write_results(self, results):
298 | """
299 | Write the result in the GUI
300 |
301 | :param results: result of the randomness test
302 | :return: None
303 | """
304 | count = 0
305 | for result in results:
306 | if not len(result) == 0:
307 | if count == 10:
308 | self.__result_field[count].set_p_value(result[0][0])
309 | self.__result_field[count].set_result_value(self.get_result_string(result[0][1]))
310 | self.__result_field[count].set_p_value_02(result[1][0])
311 | self.__result_field[count].set_result_value_02(self.get_result_string(result[1][1]))
312 | elif count == 14:
313 | print(result)
314 | self.__excursion.set_results(result)
315 | elif count == 15:
316 | print(result)
317 | self.__variant.set_results(result)
318 | else:
319 | self.__result_field[count].set_p_value(result[0])
320 | self.__result_field[count].set_result_value(self.get_result_string(result[1]))
321 |
322 |
323 | count += 1
324 |
325 | def save_result_to_file(self):
326 | print('Save to File')
327 | print(self.__test_result)
328 | if not len(self.__binary_input.get_data()) == 0:
329 | output_file = asksaveasfile(mode='w', defaultextension=".txt")
330 | output_file.write('Test Data:' + self.__binary_input.get_data() + '\n\n\n')
331 | result = self.__test_result[0]
332 | output_file.write('%-50s\t%-20s\t%-10s\n' % ('Type of Test', 'P-Value', 'Conclusion'))
333 | self.write_result_to_file(output_file, result)
334 | output_file.close()
335 | messagebox.showinfo("Save", "File save finished. You can check the output file for complete result.")
336 | elif not len(self.__binary_data_file_input.get_data()) == 0:
337 | output_file = asksaveasfile(mode='w', defaultextension=".txt")
338 | output_file.write('Test Data File:' + self.__binary_data_file_input.get_data() + '\n\n\n')
339 | result = self.__test_result[0]
340 | output_file.write('%-50s\t%-20s\t%-10s\n' % ('Type of Test', 'P-Value', 'Conclusion'))
341 | self.write_result_to_file(output_file, result)
342 | output_file.close()
343 | messagebox.showinfo("Save", "File save finished. You can check the output file for complete result.")
344 | elif not len(self.__string_data_file_input.get_data()) == 0:
345 | output_file = asksaveasfile(mode='w', defaultextension=".txt")
346 | output_file.write('Test Data File:' + self.__string_data_file_input.get_data() + '\n\n')
347 | #count = 0
348 | #for item in self.__test_string:
349 | # output_file.write('Test ' + str(count+1) + ':\n')
350 | # output_file.write('String to be tested: %s' % item)
351 | # output_file.write('Binary of the given String: %s\n\n' % Tools.string_to_binary(item))
352 | # output_file.write('Result:\n')
353 | # output_file.write('%-50s\t%-20s\t%-10s\n' % ('Type of Test', 'P-Value', 'Conclusion'))
354 | # self.write_result_to_file(output_file, self.__test_result[count])
355 | # output_file.write('\n\n')
356 | # count += 1
357 | result = self.__test_result[0]
358 | output_file.write('%-50s\t%-20s\t%-10s\n' % ('Type of Test', 'P-Value', 'Conclusion'))
359 | self.write_result_to_file(output_file, result)
360 | output_file.close()
361 | messagebox.showinfo("Save", "File save finished. You can check the output file for complete result.")
362 |
363 | def write_result_to_file(self, output_file, result):
364 | for count in range(16):
365 | if self.__test[count].get_check_box_value() == 1:
366 | if count == 10:
367 | output_file.write(self.__test_type[count] + ':\n')
368 | output = '\t\t\t\t\t\t\t\t\t\t\t\t\t%-20s\t%s\n' % (
369 | str(result[count][0][0]), self.get_result_string(result[count][0][1]))
370 | output_file.write(output)
371 | output = '\t\t\t\t\t\t\t\t\t\t\t\t\t%-20s\t%s\n' % (
372 | str(result[count][1][0]), self.get_result_string(result[count][1][1]))
373 | output_file.write(output)
374 | pass
375 | elif count == 14:
376 | output_file.write(self.__test_type[count] + ':\n')
377 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ('State ', 'Chi Squared', 'P-Value', 'Conclusion')
378 | output_file.write(output)
379 | for item in result[count]:
380 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % (
381 | item[0], item[2], item[3], self.get_result_string(item[4]))
382 | output_file.write(output)
383 | elif count == 15:
384 | output_file.write(self.__test_type[count] + ':\n')
385 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % ('State ', 'COUNTS', 'P-Value', 'Conclusion')
386 | output_file.write(output)
387 | for item in result[count]:
388 | output = '\t\t\t\t%-10s\t%-20s\t%-20s\t%s\n' % (
389 | item[0], item[2], item[3], self.get_result_string(item[4]))
390 | output_file.write(output)
391 | else:
392 | output = '%-50s\t%-20s\t%s\n' % (
393 | self.__test_type[count], str(result[count][0]), self.get_result_string(result[count][1]))
394 | output_file.write(output)
395 | count += 1
396 |
397 | #def change_data(self):
398 | # index = int(self.__test_data.get_selected().split(' ')[0])
399 | # print(self.__test_result[index-1])
400 | # self.write_results(self.__test_result[index-1])
401 |
402 | def reset(self):
403 | """
404 | Reset the GUI:
405 | 1. Clear all input in the textfield.
406 | 2. Unchecked all checkbox
407 |
408 | :return: None
409 | """
410 | print('Reset')
411 | self.__binary_input.set_data('')
412 | self.__binary_data_file_input.set_data('')
413 | self.__string_data_file_input.set_data('')
414 | self.__is_binary_file = False
415 | self.__is_data_file = False
416 | self.__monobit.reset()
417 | self.__block.reset()
418 | self.__run.reset()
419 | self.__long_run.reset()
420 | self.__rank.reset()
421 | self.__spectral.reset()
422 | self.__non_overlappong.reset()
423 | self.__overlapping.reset()
424 | self.__universal.reset()
425 | self.__linear.reset()
426 | self.__serial.reset()
427 | self.__entropy.reset()
428 | self.__cusum_f.reset()
429 | self.__cusum_r.reset()
430 | self.__excursion.reset()
431 | self.__variant.reset()
432 | #self.__test_data = Options(self.__stest_selection_label_frame, 'Input Data', [''], 10, 5, 900)
433 | self.__test_result = []
434 | self.__test_string = []
435 |
436 | def exit(self):
437 | """
438 | Exit this program normally
439 |
440 | :return: None
441 | """
442 | print('Exit')
443 | exit(0)
444 |
445 | def get_result_string(self, result):
446 | """
447 | Interpret the result and return either 'Random' or 'Non-Random'
448 |
449 | :param result: Result of the test (either True or False)
450 | :return: str (Either 'Random' for True and 'Non-Random' for False
451 | """
452 | if result == True:
453 | return 'Random'
454 | else:
455 | return 'Non-Random'
456 |
457 | if __name__ == '__main__':
458 | root = Tk()
459 | root.resizable(0,0)
460 | root.geometry("%dx%d+0+0" % (1280, 800))
461 | title = 'Test Suite for NIST Random Numbers'
462 | root.title(title)
463 | app = Main(root)
464 | app.focus_displayof()
465 | app.mainloop()
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NIST Randomness Testsuit
2 |
3 | This is a Python implementation of NIST's A Statistical Test Suite for Random and Pseudorandom Number Generators for Cryptographic Applications
4 |
5 | ## Getting Started
6 |
7 | ### Prerequisite:
8 | You need the following software and packages for this application:
9 | 1. Python 3.6 and above (Tested with Python 3.8 already)
10 | 2. Numpy and Scipy
11 | ```
12 | pip3 install numpy, scipy
13 | ```
14 |
15 | ### How to use:
16 | * You can start the program using your IDE feature (like run) to run Main.py or
17 | ```
18 | python3 Main.py
19 | ```
20 | * Once you saw the interface, you can start using the test suite.
21 | 
22 |
23 | * Input Data - Input Data contains Binary Data, Binary Data File and String Data File
24 | * Binary Data - You can only enter a BINARY STRING here (ex:1100100100001111110110101010001000100001011010001100001000110100110001001100011001100010100010111000)
25 | * Binary Data File - This will open a file dialog where you can select a file to be read by program.
26 | The file you selected should contain only one set of data in BINARY FORM. (For example, please refer to data/data.e)
27 | * String Data File - This will open a file dialog where you can select a file to be read by program.
28 | The file you selected can contain multiple set of data in STRING FORM. (For example, please refer to data/test_data_01.txt)
29 |
30 | * You can select the type of test you want to perform by clicking the corresponding checkbox or press "Select All Test" to select everything
31 | * You can cancel the selection by clicking the corresponding checkbox or press "De-Select All Test" to cancel everything
32 | * Once you have your data ready and selected the test you want to perform, then you can press "Execute Test" button to execute the test
33 | * The result will be displayed after the test done.
34 | * There are multiple result for Random Excursion Test. Initially the program will displayed state '+1'. You can chechk the other resuld
35 | by changing the state (using drop down) and press "Update" button
36 | * There are multiple result for Random Excursion Variant Test. Initially the program will displayed state '-1.0'. You can chechk the other resuld
37 | by changing the state (using drop down) and press "Update" button
38 | * You can save the result to a text file by pressing "Save as Text File" button.
39 | This will display a file dialog where you can enter the file name for your result.
40 | You can check the text file after the result is saved.
41 | * "Reset" button will clear all input and variables. It is strongly suggested you use this feature if you want to execute test for another set of data
42 | * "Exit" button will close this program
43 |
44 | ### Using this application in terminal (Command Line)
45 | * You can also used this application by importing necessary library to your python code
46 | ```
47 | import os
48 | from FrequencyTest import FrequencyTest
49 | from RunTest import RunTest
50 | from Matrix import Matrix
51 | from Spectral import SpectralTest
52 | from TemplateMatching import TemplateMatching
53 | from Universal import Universal
54 | from Complexity import ComplexityTest
55 | from Serial import Serial
56 | from ApproximateEntropy import ApproximateEntropy
57 | from CumulativeSum import CumulativeSums
58 | from RandomExcursions import RandomExcursions
59 |
60 | # Open Data File and read the binary data of e
61 | data_path = os.path.join(os.getcwd(), 'data', 'data.e')
62 | handle = open(data_path)
63 | data_list = []
64 |
65 | for line in handle:
66 | data_list.append(line.strip().rstrip())
67 |
68 | binary_data = ''.join(data_list)
69 |
70 | print('The statistical test of the Binary Expansion of e')
71 | print('2.01. Frequency Test:\t\t\t\t\t\t\t\t', FrequencyTest.monobit_test(binary_data[:1000000]))
72 | print('2.02. Block Frequency Test:\t\t\t\t\t\t\t', FrequencyTest.block_frequency(binary_data[:1000000]))
73 | print('2.03. Run Test:\t\t\t\t\t\t\t\t\t\t', RunTest.run_test(binary_data[:1000000]))
74 | print('2.04. Run Test (Longest Run of Ones): \t\t\t\t', RunTest.longest_one_block_test(binary_data[:1000000]))
75 | print('2.05. Binary Matrix Rank Test:\t\t\t\t\t\t', Matrix.binary_matrix_rank_text(binary_data[:1000000]))
76 | print('2.06. Discrete Fourier Transform (Spectral) Test:\t', SpectralTest.spectral_test(binary_data[:1000000]))
77 | print('2.07. Non-overlapping Template Matching Test:\t\t', TemplateMatching.non_overlapping_test(binary_data[:1000000], '000000001'))
78 | print('2.08. Overlappong Template Matching Test: \t\t\t', TemplateMatching.overlapping_patterns(binary_data[:1000000]))
79 | print('2.09. Universal Statistical Test:\t\t\t\t\t', Universal.statistical_test(binary_data[:1000000]))
80 | print('2.10. Linear Complexity Test:\t\t\t\t\t\t', ComplexityTest.linear_complexity_test(binary_data[:1000000]))
81 | print('2.11. Serial Test:\t\t\t\t\t\t\t\t\t', Serial.serial_test(binary_data[:1000000]))
82 | print('2.12. Approximate Entropy Test:\t\t\t\t\t\t', ApproximateEntropy.approximate_entropy_test(binary_data[:1000000]))
83 | print('2.13. Cumulative Sums (Forward):\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 0))
84 | print('2.13. Cumulative Sums (Backward):\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 1))
85 | result = RandomExcursions.random_excursions_test(binary_data[:1000000])
86 | print('2.14. Random Excursion Test:')
87 | print('\t\t STATE \t\t\t xObs \t\t\t\t P-Value \t\t\t Conclusion')
88 |
89 | for item in result:
90 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[2], '\t\t', repr(item[3]).ljust(14), '\t\t',
91 | (item[4] >= 0.01))
92 |
93 | result = RandomExcursions.variant_test(binary_data[:1000000])
94 |
95 | print('2.15. Random Excursion Variant Test:\t\t\t\t\t\t')
96 | print('\t\t STATE \t\t COUNTS \t\t\t P-Value \t\t Conclusion')
97 | for item in result:
98 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[2], '\t\t', repr(item[3]).ljust(14), '\t\t',
99 | (item[4] >= 0.01))
100 | ```
101 | * Output of the code above:
102 | ```
103 | The statistical test of the Binary Expansion of e
104 | 2.01. Frequency Test: (0.9537486285283232, True)
105 | 2.02. Block Frequency Test: (0.21107154370164066, True)
106 | 2.03. Run Test: (0.5619168850302545, True)
107 | 2.04. Run Test (Longest Run of Ones): (0.7189453298987654, True)
108 | 2.05. Binary Matrix Rank Test: (0.3061558375306767, True)
109 | 2.06. Discrete Fourier Transform (Spectral) Test: (0.8471867050687718, True)
110 | Non-Overlapping Template Test DEBUG BEGIN:
111 | Length of input: 1000000
112 | Value of Mean (µ): 244.125
113 | Value of Variance(σ): 236.03439331054688
114 | Value of W: [239. 235. 254. 278. 207. 229. 225. 242.]
115 | Value of xObs: 14.116057212121211
116 | P-Value: 0.07879013267666338
117 | DEBUG END.
118 | 2.07. Non-overlapping Template Matching Test: (0.07879013267666338, True)
119 | 2.08. Overlappong Template Matching Test: (0.11043368541387631, True)
120 | 2.09. Universal Statistical Test: (0.282567947825744, True)
121 | 2.10. Linear Complexity Test: (0.8263347704038304, True)
122 | 2.11. Serial Test: ((0.766181646833394, True), (0.46292132409575854, True))
123 | 2.12. Approximate Entropy Test: (0.7000733881151612, True)
124 | 2.13. Cumulative Sums (Forward): (0.6698864641681423, True)
125 | 2.13. Cumulative Sums (Backward): (0.7242653099698069, True)
126 | 2.14. Random Excursion Test:
127 | STATE xObs P-Value Conclusion
128 | '-4' 3.8356982129929085 0.5733056949947805 True
129 | '-3' 7.318707114093956 0.19799602021827734 True
130 | '-2' 7.861927251636425 0.16401104937943733 True
131 | '-1' 15.69261744966443 0.007778723096466819 False
132 | '+1' 2.4308724832214765 0.7868679051783156 True
133 | '+2' 4.7989062888391745 0.44091173664620265 True
134 | '+3' 2.3570405369127525 0.7978539716877826 True
135 | '+4' 2.4887672641992014 0.7781857852321322 True
136 | 2.15. Random Excursion Variant Test:
137 | STATE COUNTS P-Value Conclusion
138 | '-9.0' 1450 0.8589457398254003 True
139 | '-8.0' 1435 0.7947549562546549 True
140 | '-7.0' 1380 0.5762486184682754 True
141 | '-6.0' 1366 0.4934169340861271 True
142 | '-5.0' 1412 0.6338726691411485 True
143 | '-4.0' 1475 0.9172831477915963 True
144 | '-3.0' 1480 0.9347077918349618 True
145 | '-2.0' 1468 0.8160120366175745 True
146 | '-1.0' 1502 0.8260090128330382 True
147 | '+1.0' 1409 0.13786060890864768 True
148 | '+2.0' 1369 0.20064191385523023 True
149 | '+3.0' 1396 0.4412536221564536 True
150 | '+4.0' 1479 0.939290606067626 True
151 | '+5.0' 1599 0.5056826821687638 True
152 | '+6.0' 1628 0.4459347106499899 True
153 | '+7.0' 1619 0.5122068856164792 True
154 | '+8.0' 1620 0.5386346977772863 True
155 | '+9.0' 1610 0.5939303958223099 True
156 |
157 | Process finished with exit code 0
158 | ```
159 | * For more example, you can check test_pi.py, test_sqrt2.py, test_sqrt3.py
160 |
161 | ## Change logs
162 | ### 1.3
163 | * Changed screen layout to fixedthe issue with the resolution lower than 1920 x 1080
164 | ### 1.2
165 | * Fixed bug
166 | ### 1.1
167 | * Initial Release
168 |
--------------------------------------------------------------------------------
/RandomExcursions.py:
--------------------------------------------------------------------------------
1 | from math import isnan as isnan
2 | from numpy import abs as abs
3 | from numpy import append as append
4 | from numpy import array as array
5 | from numpy import clip as clip
6 | from numpy import cumsum as cumsum
7 | from numpy import ones as ones
8 | from numpy import sqrt as sqrt
9 | from numpy import sum as sum
10 | from numpy import transpose as transpose
11 | from numpy import where as where
12 | from numpy import zeros as zeros
13 | from scipy.special import erfc as erfc
14 | from scipy.special import gammaincc as gammaincc
15 |
16 | class RandomExcursions:
17 |
18 | @staticmethod
19 | def random_excursions_test(binary_data:str, verbose=False, state=1):
20 | """
21 | from the NIST documentation http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-22r1a.pdf
22 |
23 | The focus of this test is the total number of times that a particular state is visited (i.e., occurs) in a
24 | cumulative sum random walk. The purpose of this test is to detect deviations from the expected number
25 | of visits to various states in the random walk. This test is actually a series of eighteen tests (and
26 | conclusions), one test and conclusion for each of the states: -9, -8, …, -1 and +1, +2, …, +9.
27 |
28 | :param binary_data: a binary string
29 | :param verbose True to display the debug messgae, False to turn off debug message
30 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
31 | """
32 |
33 | length_of_binary_data = len(binary_data)
34 | # Form the normalized (-1, +1) sequence X in which the zeros and ones of the input sequence (ε)
35 | # are converted to values of –1 and +1 via X = X1, X2, … , Xn, where Xi = 2εi – 1.
36 | sequence_x = zeros(length_of_binary_data)
37 | for i in range(len(binary_data)):
38 | if binary_data[i] == '0':
39 | sequence_x[i] = -1.0
40 | else:
41 | sequence_x[i] = 1.0
42 |
43 | # Compute partial sums Si of successively larger subsequences, each starting with x1. Form the set S
44 | cumulative_sum = cumsum(sequence_x)
45 |
46 | # Form a new sequence S' by attaching zeros before and after the set S. That is, S' = 0, s1, s2, … , sn, 0.
47 | cumulative_sum = append(cumulative_sum, [0])
48 | cumulative_sum = append([0], cumulative_sum)
49 |
50 | # These are the states we are going to look at
51 | x_values = array([-4, -3, -2, -1, 1, 2, 3, 4])
52 | index = x_values.tolist().index(state)
53 |
54 | # Identify all the locations where the cumulative sum revisits 0
55 | position = where(cumulative_sum == 0)[0]
56 | # For this identify all the cycles
57 | cycles = []
58 | for pos in range(len(position) - 1):
59 | # Add this cycle to the list of cycles
60 | cycles.append(cumulative_sum[position[pos]:position[pos + 1] + 1])
61 | num_cycles = len(cycles)
62 |
63 | state_count = []
64 | for cycle in cycles:
65 | # Determine the number of times each cycle visits each state
66 | state_count.append(([len(where(cycle == state)[0]) for state in x_values]))
67 | state_count = transpose(clip(state_count, 0, 5))
68 |
69 | su = []
70 | for cycle in range(6):
71 | su.append([(sct == cycle).sum() for sct in state_count])
72 | su = transpose(su)
73 |
74 | pi = ([([RandomExcursions.get_pi_value(uu, state) for uu in range(6)]) for state in x_values])
75 | inner_term = num_cycles * array(pi)
76 | xObs = sum(1.0 * (array(su) - inner_term) ** 2 / inner_term, axis=1)
77 | p_values = ([gammaincc(2.5, cs / 2.0) for cs in xObs])
78 |
79 | if verbose:
80 | print('Random Excursion Test DEBUG BEGIN:')
81 | print("\tLength of input:\t", length_of_binary_data)
82 | count = 0
83 | print('\t\t STATE \t\t\t xObs \t\t\t\t\t\t p_value \t\t\t\t\t Result')
84 | for item in p_values:
85 | print('\t\t', repr(x_values[count]).rjust(2), ' \t\t ', xObs[count],' \t\t ', repr(item).rjust(21), ' \t\t\t ', (item >= 0.01))
86 | count += 1
87 | print('DEBUG END.')
88 |
89 | states = ['-4', '-3', '-2', '-1', '+1', '+2', '+3', '+4',]
90 | result = []
91 | count = 0
92 | for item in p_values:
93 | result.append((states[count], x_values[count], xObs[count], item, (item >= 0.01)))
94 | count += 1
95 |
96 | return result
97 |
98 | @staticmethod
99 | def variant_test(binary_data:str, verbose=False):
100 | """
101 | from the NIST documentation http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-22r1a.pdf
102 |
103 | :param binary_data:
104 | :param verbose:
105 | :return:
106 | """
107 | length_of_binary_data = len(binary_data)
108 | int_data = zeros(length_of_binary_data)
109 |
110 | for count in range(length_of_binary_data):
111 | int_data[count] = int(binary_data[count])
112 |
113 | sum_int = (2 * int_data) - ones(len(int_data))
114 | cumulative_sum = cumsum(sum_int)
115 |
116 | li_data = []
117 | index = []
118 | for count in sorted(set(cumulative_sum)):
119 | if abs(count) <= 9:
120 | index.append(count)
121 | li_data.append([count, len(where(cumulative_sum == count)[0])])
122 |
123 | j = RandomExcursions.get_frequency(li_data, 0) + 1
124 |
125 | p_values = []
126 | for count in (sorted(set(index))):
127 | if not count == 0:
128 | den = sqrt(2 * j * (4 * abs(count) - 2))
129 | p_values.append(erfc(abs(RandomExcursions.get_frequency(li_data, count) - j) / den))
130 |
131 | count = 0
132 | # Remove 0 from li_data so the number of element will be equal to p_values
133 | for data in li_data:
134 | if data[0] == 0:
135 | li_data.remove(data)
136 | index.remove(0)
137 | break
138 | count += 1
139 |
140 | if verbose:
141 | print('Random Excursion Variant Test DEBUG BEGIN:')
142 | print("\tLength of input:\t", length_of_binary_data)
143 | print('\tValue of j:\t\t', j)
144 | print('\tP-Values:')
145 | print('\t\t STATE \t\t COUNTS \t\t P-Value \t\t Conclusion')
146 | count = 0
147 | for item in p_values:
148 | print('\t\t', repr(li_data[count][0]).rjust(4), '\t\t', li_data[count][1], '\t\t', repr(item).ljust(14), '\t\t', (item >= 0.01))
149 | count += 1
150 | print('DEBUG END.')
151 |
152 |
153 | states = []
154 | for item in index:
155 | if item < 0:
156 | states.append(str(item))
157 | else:
158 | states.append('+' + str(item))
159 |
160 | result = []
161 | count = 0
162 | for item in p_values:
163 | result.append((states[count], li_data[count][0], li_data[count][1], item, (item >= 0.01)))
164 | count += 1
165 |
166 | return result
167 |
168 | @staticmethod
169 | def get_pi_value(k, x):
170 | """
171 | This method is used by the random_excursions method to get expected probabilities
172 | """
173 | if k == 0:
174 | out = 1 - 1.0 / (2 * abs(x))
175 | elif k >= 5:
176 | out = (1.0 / (2 * abs(x))) * (1 - 1.0 / (2 * abs(x))) ** 4
177 | else:
178 | out = (1.0 / (4 * x * x)) * (1 - 1.0 / (2 * abs(x))) ** (k - 1)
179 | return out
180 |
181 | @staticmethod
182 | def get_frequency(list_data, trigger):
183 | """
184 | This method is used by the random_excursions_variant method to get frequencies
185 | """
186 | frequency = 0
187 | for (x, y) in list_data:
188 | if x == trigger:
189 | frequency = y
190 | return frequency
--------------------------------------------------------------------------------
/RunTest.py:
--------------------------------------------------------------------------------
1 | from math import fabs as fabs
2 | from math import floor as floor
3 | from math import sqrt as sqrt
4 | from scipy.special import erfc as erfc
5 | from scipy.special import gammaincc as gammaincc
6 | from numpy import zeros
7 |
8 | class RunTest:
9 |
10 | @staticmethod
11 | def run_test(binary_data:str, verbose=False):
12 | """
13 | The focus of this test is the total number of runs in the sequence,
14 | where a run is an uninterrupted sequence of identical bits.
15 | A run of length k consists of exactly k identical bits and is bounded before
16 | and after with a bit of the opposite value. The purpose of the runs test is to
17 | determine whether the number of runs of ones and zeros of various lengths is as
18 | expected for a random sequence. In particular, this test determines whether the
19 | oscillation between such zeros and ones is too fast or too slow.
20 |
21 | :param binary_data: The seuqnce of bit being tested
22 | :param verbose True to display the debug messgae, False to turn off debug message
23 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
24 | """
25 | one_count = 0
26 | vObs = 0
27 | length_of_binary_data = len(binary_data)
28 |
29 | # Predefined tau = 2 / sqrt(n)
30 | # TODO Confirm with Frank about the discrepancy between the formula and the sample of 2.3.8
31 | tau = 2 / sqrt(length_of_binary_data)
32 |
33 | # Step 1 - Compute the pre-test proportion πof ones in the input sequence: π = Σjεj / n
34 | one_count = binary_data.count('1')
35 |
36 | pi = one_count / length_of_binary_data
37 |
38 | # Step 2 - If it can be shown that absolute value of (π - 0.5) is greater than or equal to tau
39 | # then the run test need not be performed.
40 | if abs(pi - 0.5) >= tau:
41 | ##print("The test should not have been run because of a failure to pass test 1, the Frequency (Monobit) test.")
42 | return (0.0000, False)
43 | else:
44 | # Step 3 - Compute vObs
45 | for item in range(1, length_of_binary_data):
46 | if binary_data[item] != binary_data[item - 1]:
47 | vObs += 1
48 | vObs += 1
49 |
50 | # Step 4 - Compute p_value = erfc((|vObs − 2nπ * (1−π)|)/(2 * sqrt(2n) * π * (1−π)))
51 | p_value = erfc(abs(vObs - (2 * (length_of_binary_data) * pi * (1 - pi))) / (2 * sqrt(2 * length_of_binary_data) * pi * (1 - pi)))
52 |
53 | if verbose:
54 | print('Run Test DEBUG BEGIN:')
55 | print("\tLength of input:\t\t\t\t", length_of_binary_data)
56 | print("\tTau (2/sqrt(length of input)):\t", tau)
57 | print('\t# of \'1\':\t\t\t\t\t\t', one_count)
58 | print('\t# of \'0\':\t\t\t\t\t\t', binary_data.count('0'))
59 | print('\tPI (1 count / length of input):\t', pi)
60 | print('\tvObs:\t\t\t\t\t\t\t', vObs)
61 | print('\tP-Value:\t\t\t\t\t\t', p_value)
62 | print('DEBUG END.')
63 |
64 | return (p_value, (p_value > 0.01))
65 |
66 | @staticmethod
67 | def longest_one_block_test(binary_data:str, verbose=False):
68 | """
69 | The focus of the test is the longest run of ones within M-bit blocks. The purpose of this test is to determine
70 | whether the length of the longest run of ones within the tested sequence is consistent with the length of the
71 | longest run of ones that would be expected in a random sequence. Note that an irregularity in the expected
72 | length of the longest run of ones implies that there is also an irregularity in the expected length of the
73 | longest run of zeroes. Therefore, only a test for ones is necessary.
74 |
75 | :param binary_data: The sequence of bits being tested
76 | :param verbose True to display the debug messgae, False to turn off debug message
77 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
78 | """
79 | length_of_binary_data = len(binary_data)
80 | # print('Length of binary string: ', length_of_binary_data)
81 |
82 | # Initialized k, m. n, pi and v_values
83 | if length_of_binary_data < 128:
84 | # Not enough data to run this test
85 | return (0.00000, False, 'Error: Not enough data to run this test')
86 | elif length_of_binary_data < 6272:
87 | k = 3
88 | m = 8
89 | v_values = [1, 2, 3, 4]
90 | pi_values = [0.21484375, 0.3671875, 0.23046875, 0.1875]
91 | elif length_of_binary_data < 750000:
92 | k = 5
93 | m = 128
94 | v_values = [4, 5, 6, 7, 8, 9]
95 | pi_values = [0.1174035788, 0.242955959, 0.249363483, 0.17517706, 0.102701071, 0.112398847]
96 | else:
97 | # If length_of_bit_string > 750000
98 | k = 6
99 | m = 10000
100 | v_values = [10, 11, 12, 13, 14, 15, 16]
101 | pi_values = [0.0882, 0.2092, 0.2483, 0.1933, 0.1208, 0.0675, 0.0727]
102 |
103 | number_of_blocks = floor(length_of_binary_data / m)
104 | block_start = 0
105 | block_end = m
106 | xObs = 0
107 | # This will intialized an array with a number of 0 you specified.
108 | frequencies = zeros(k + 1)
109 |
110 | # print('Number of Blocks: ', number_of_blocks)
111 |
112 | for count in range(number_of_blocks):
113 | block_data = binary_data[block_start:block_end]
114 | max_run_count = 0
115 | run_count = 0
116 |
117 | # This will count the number of ones in the block
118 | for bit in block_data:
119 | if bit == '1':
120 | run_count += 1
121 | max_run_count = max(max_run_count, run_count)
122 | else:
123 | max_run_count = max(max_run_count, run_count)
124 | run_count = 0
125 |
126 | max(max_run_count, run_count)
127 |
128 | #print('Block Data: ', block_data, '. Run Count: ', max_run_count)
129 |
130 | if max_run_count < v_values[0]:
131 | frequencies[0] += 1
132 | for j in range(k):
133 | if max_run_count == v_values[j]:
134 | frequencies[j] += 1
135 | if max_run_count > v_values[k - 1]:
136 | frequencies[k] += 1
137 |
138 | block_start += m
139 | block_end += m
140 |
141 | # print("Frequencies: ", frequencies)
142 | # Compute xObs
143 | for count in range(len(frequencies)):
144 | xObs += pow((frequencies[count] - (number_of_blocks * pi_values[count])), 2.0) / (
145 | number_of_blocks * pi_values[count])
146 |
147 | p_value = gammaincc(float(k / 2), float(xObs / 2))
148 |
149 | if verbose:
150 | print('Run Test (Longest Run of Ones in a Block) DEBUG BEGIN:')
151 | print("\tLength of input:\t\t\t\t", length_of_binary_data)
152 | print("\tSize of each Block:\t\t\t\t", m)
153 | print('\tNumber of Block:\t\t\t\t', number_of_blocks)
154 | print("\tValue of K:\t\t\t\t\t\t", k)
155 | print('\tValue of PIs:\t\t\t\t\t', pi_values)
156 | print('\tFrequencies:\t\t\t\t\t', frequencies)
157 | print('\txObs:\t\t\t\t\t\t\t', xObs)
158 | print('\tP-Value:\t\t\t\t\t\t', p_value)
159 | print('DEBUG END.')
160 |
161 | return (p_value, (p_value > 0.01))
--------------------------------------------------------------------------------
/Serial.py:
--------------------------------------------------------------------------------
1 | from numpy import zeros as zeros
2 | from scipy.special import gammaincc as gammaincc
3 | class Serial:
4 |
5 | @staticmethod
6 | def serial_test(binary_data:str, verbose=False, pattern_length=16):
7 | """
8 | From the NIST documentation http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf
9 |
10 | The focus of this test is the frequency of all possible overlapping m-bit patterns across the entire
11 | sequence. The purpose of this test is to determine whether the number of occurrences of the 2m m-bit
12 | overlapping patterns is approximately the same as would be expected for a random sequence. Random
13 | sequences have uniformity; that is, every m-bit pattern has the same chance of appearing as every other
14 | m-bit pattern. Note that for m = 1, the Serial test is equivalent to the Frequency test of Section 2.1.
15 |
16 | :param binary_data: a binary string
17 | :param verbose True to display the debug message, False to turn off debug message
18 | :param pattern_length: the length of the pattern (m)
19 | :return: ((p_value1, bool), (p_value2, bool)) A tuple which contain the p_value and result of serial_test(True or False)
20 | """
21 | length_of_binary_data = len(binary_data)
22 | binary_data += binary_data[:(pattern_length -1):]
23 |
24 | # Get max length one patterns for m, m-1, m-2
25 | max_pattern = ''
26 | for i in range(pattern_length + 1):
27 | max_pattern += '1'
28 |
29 | # Step 02: Determine the frequency of all possible overlapping m-bit blocks,
30 | # all possible overlapping (m-1)-bit blocks and
31 | # all possible overlapping (m-2)-bit blocks.
32 | vobs_01 = zeros(int(max_pattern[0:pattern_length:], 2) + 1)
33 | vobs_02 = zeros(int(max_pattern[0:pattern_length - 1:], 2) + 1)
34 | vobs_03 = zeros(int(max_pattern[0:pattern_length - 2:], 2) + 1)
35 |
36 | for i in range(length_of_binary_data):
37 | # Work out what pattern is observed
38 | vobs_01[int(binary_data[i:i + pattern_length:], 2)] += 1
39 | vobs_02[int(binary_data[i:i + pattern_length - 1:], 2)] += 1
40 | vobs_03[int(binary_data[i:i + pattern_length - 2:], 2)] += 1
41 |
42 | vobs = [vobs_01, vobs_02, vobs_03]
43 |
44 | # Step 03 Compute for ψs
45 | sums = zeros(3)
46 | for i in range(3):
47 | for j in range(len(vobs[i])):
48 | sums[i] += pow(vobs[i][j], 2)
49 | sums[i] = (sums[i] * pow(2, pattern_length - i) / length_of_binary_data) - length_of_binary_data
50 |
51 | # Cimpute the test statistics and p values
52 | #Step 04 Compute for ∇
53 | nabla_01 = sums[0] - sums[1]
54 | nabla_02 = sums[0] - 2.0 * sums[1] + sums[2]
55 |
56 | # Step 05 Compute for P-Value
57 | p_value_01 = gammaincc(pow(2, pattern_length - 1) / 2, nabla_01 / 2.0)
58 | p_value_02 = gammaincc(pow(2, pattern_length - 2) / 2, nabla_02 / 2.0)
59 |
60 | if verbose:
61 | print('Serial Test DEBUG BEGIN:')
62 | print("\tLength of input:\t", length_of_binary_data)
63 | print('\tValue of Sai:\t\t', sums)
64 | print('\tValue of Nabla:\t\t', nabla_01, nabla_02)
65 | print('\tP-Value 01:\t\t\t', p_value_01)
66 | print('\tP-Value 02:\t\t\t', p_value_02)
67 | print('DEBUG END.')
68 |
69 | return ((p_value_01, p_value_01 >= 0.01), (p_value_02, p_value_02 >= 0.01))
--------------------------------------------------------------------------------
/Spectral.py:
--------------------------------------------------------------------------------
1 | from math import fabs as fabs
2 | from math import floor as floor
3 | from math import log as log
4 | from math import sqrt as sqrt
5 | from numpy import where as where
6 | from scipy import fftpack as sff
7 | from scipy.special import erfc as erfc
8 |
9 | class SpectralTest:
10 |
11 | @staticmethod
12 | def spectral_test(binary_data:str, verbose=False):
13 | """
14 | Note that this description is taken from the NIST documentation [1]
15 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf
16 | The focus of this test is the peak heights in the Discrete Fourier Transform of the sequence. The purpose of
17 | this test is to detect periodic features (i.e., repetitive patterns that are near each other) in the tested
18 | sequence that would indicate a deviation from the assumption of randomness. The intention is to detect whether
19 | the number of peaks exceeding the 95 % threshold is significantly different than 5 %.
20 |
21 | :param binary_data: The seuqnce of bit being tested
22 | :param verbose True to display the debug messgae, False to turn off debug message
23 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
24 | """
25 | length_of_binary_data = len(binary_data)
26 | plus_one_minus_one = []
27 |
28 | # Step 1 - The zeros and ones of the input sequence (ε) are converted to values of –1 and +1
29 | # to create the sequence X = x1, x2, …, xn, where xi = 2εi – 1.
30 | for char in binary_data:
31 | if char == '0':
32 | plus_one_minus_one.append(-1)
33 | elif char == '1':
34 | plus_one_minus_one.append(1)
35 |
36 | # Step 2 - Apply a Discrete Fourier transform (DFT) on X to produce: S = DFT(X).
37 | # A sequence of complex variables is produced which represents periodic
38 | # components of the sequence of bits at different frequencies
39 | spectral = sff.fft(plus_one_minus_one)
40 |
41 | # Step 3 - Calculate M = modulus(S´) ≡ |S'|, where S´ is the substring consisting of the first n/2
42 | # elements in S, and the modulus function produces a sequence of peak heights.
43 | slice = floor(length_of_binary_data / 2)
44 | modulus = abs(spectral[0:slice])
45 |
46 | # Step 4 - Compute T = sqrt(log(1 / 0.05) * length_of_string) the 95 % peak height threshold value.
47 | # Under an assumption of randomness, 95 % of the values obtained from the test should not exceed T.
48 | tau = sqrt(log(1 / 0.05) * length_of_binary_data)
49 |
50 | # Step 5 - Compute N0 = .95n/2. N0 is the expected theoretical (95 %) number of peaks
51 | # (under the assumption of randomness) that are less than T.
52 | n0 = 0.95 * (length_of_binary_data / 2)
53 |
54 | # Step 6 - Compute N1 = the actual observed number of peaks in M that are less than T.
55 | n1 = len(where(modulus < tau)[0])
56 |
57 | # Step 7 - Compute d = (n_1 - n_0) / sqrt (length_of_string * (0.95) * (0.05) / 4)
58 | d = (n1 - n0) / sqrt(length_of_binary_data * (0.95) * (0.05) / 4)
59 |
60 | # Step 8 - Compute p_value = erfc(abs(d)/sqrt(2))
61 | p_value = erfc(fabs(d) / sqrt(2))
62 |
63 | if verbose:
64 | print('Discrete Fourier Transform (Spectral) Test DEBUG BEGIN:')
65 | print('\tLength of Binary Data:\t', length_of_binary_data)
66 | print('\tValue of T:\t\t\t\t', tau)
67 | print('\tValue of n1:\t\t\t', n1)
68 | print('\tValue of n0:\t\t\t', n0)
69 | print('\tValue of d:\t\t\t\t', d)
70 | print('\tP-Value:\t\t\t\t', p_value)
71 | print('DEBUG END.')
72 |
73 | return (p_value, (p_value >= 0.01))
74 |
--------------------------------------------------------------------------------
/TemplateMatching.py:
--------------------------------------------------------------------------------
1 | from math import floor as floor
2 | from numpy import array as array
3 | from numpy import exp as exp
4 | from numpy import zeros as zeros
5 | from scipy.special import gammaincc as gammaincc
6 | from scipy.special import hyp1f1 as hyp1f1
7 |
8 |
9 | class TemplateMatching:
10 |
11 | @staticmethod
12 | def non_overlapping_test(binary_data:str, verbose=False, template_pattern='000000001', block=8):
13 | """
14 | Note that this description is taken from the NIST documentation [1]
15 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf
16 | The focus of this test is the number of occurrences of pre-specified target strings. The purpose of this
17 | test is to detect generators that produce too many occurrences of a given non-periodic (aperiodic) pattern.
18 | For this test and for the Overlapping Template Matching test of Section 2.8, an m-bit window is used to
19 | search for a specific m-bit pattern. If the pattern is not found, the window slides one bit position. If the
20 | pattern is found, the window is reset to the bit after the found pattern, and the search resumes.
21 | :param binary_data: The seuqnce of bit being tested
22 | :param template_pattern: The pattern to match to
23 | :param verbose True to display the debug messgae, False to turn off debug message
24 | :param block The number of independent blocks. Has been fixed at 8 in the test code.
25 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
26 | """
27 |
28 | length_of_binary = len(binary_data)
29 | pattern_size = len(template_pattern)
30 | block_size = floor(length_of_binary / block)
31 | pattern_counts = zeros(block)
32 |
33 | # For each block in the data
34 | for count in range(block):
35 | block_start = count * block_size
36 | block_end = block_start + block_size
37 | block_data = binary_data[block_start:block_end]
38 | # Count the number of pattern hits
39 | inner_count = 0
40 | while inner_count < block_size:
41 | sub_block = block_data[inner_count:inner_count+pattern_size]
42 | if sub_block == template_pattern:
43 | pattern_counts[count] += 1
44 | inner_count += pattern_size
45 | else:
46 | inner_count += 1
47 |
48 | # Calculate the theoretical mean and variance
49 | # Mean - µ = (M-m+1)/2m
50 | mean = (block_size - pattern_size + 1) / pow(2, pattern_size)
51 | # Variance - σ2 = M((1/pow(2,m)) - ((2m -1)/pow(2, 2m)))
52 | variance = block_size * ((1 / pow(2, pattern_size)) - (((2 * pattern_size) - 1) / (pow(2, pattern_size * 2))))
53 |
54 | # Calculate the xObs Squared statistic for these pattern matches
55 | xObs = 0
56 | for count in range(block):
57 | xObs += pow((pattern_counts[count] - mean), 2.0) / variance
58 |
59 | # Calculate and return the p value statistic
60 | p_value = gammaincc((block / 2), (xObs / 2))
61 |
62 | if verbose:
63 | print('Non-Overlapping Template Test DEBUG BEGIN:')
64 | print("\tLength of input:\t\t", length_of_binary)
65 | print('\tValue of Mean (µ):\t\t', mean)
66 | print('\tValue of Variance(σ):\t', variance)
67 | print('\tValue of W:\t\t\t\t', pattern_counts)
68 | print('\tValue of xObs:\t\t\t', xObs)
69 | print('\tP-Value:\t\t\t\t', p_value)
70 | print('DEBUG END.')
71 |
72 | return (p_value, (p_value >= 0.01))
73 |
74 | @staticmethod
75 | def overlapping_patterns(binary_data:str, verbose=False, pattern_size=9, block_size=1032):
76 | """
77 | Note that this description is taken from the NIST documentation [1]
78 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf
79 | The focus of the Overlapping Template Matching test is the number of occurrences of pre-specified target
80 | strings. Both this test and the Non-overlapping Template Matching test of Section 2.7 use an m-bit
81 | window to search for a specific m-bit pattern. As with the test in Section 2.7, if the pattern is not found,
82 | the window slides one bit position. The difference between this test and the test in Section 2.7 is that
83 | when the pattern is found, the window slides only one bit before resuming the search.
84 |
85 | :param binary_data: a binary string
86 | :param verbose True to display the debug messgae, False to turn off debug message
87 | :param pattern_size: the length of the pattern
88 | :param block_size: the length of the block
89 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
90 | """
91 | length_of_binary_data = len(binary_data)
92 | pattern = ''
93 | for count in range(pattern_size):
94 | pattern += '1'
95 |
96 | number_of_block = floor(length_of_binary_data / block_size)
97 |
98 | # λ = (M-m+1)/pow(2, m)
99 | lambda_val = float(block_size - pattern_size + 1) / pow(2, pattern_size)
100 | # η = λ/2
101 | eta = lambda_val / 2.0
102 |
103 | pi = [TemplateMatching.get_prob(i, eta) for i in range(5)]
104 | diff = float(array(pi).sum())
105 | pi.append(1.0 - diff)
106 |
107 | pattern_counts = zeros(6)
108 | for i in range(number_of_block):
109 | block_start = i * block_size
110 | block_end = block_start + block_size
111 | block_data = binary_data[block_start:block_end]
112 | # Count the number of pattern hits
113 | pattern_count = 0
114 | j = 0
115 | while j < block_size:
116 | sub_block = block_data[j:j + pattern_size]
117 | if sub_block == pattern:
118 | pattern_count += 1
119 | j += 1
120 | if pattern_count <= 4:
121 | pattern_counts[pattern_count] += 1
122 | else:
123 | pattern_counts[5] += 1
124 |
125 | xObs = 0.0
126 | for i in range(len(pattern_counts)):
127 | xObs += pow(pattern_counts[i] - number_of_block * pi[i], 2.0) / (number_of_block * pi[i])
128 |
129 | p_value = gammaincc(5.0 / 2.0, xObs / 2.0)
130 |
131 | if verbose:
132 | print('Overlapping Template Test DEBUG BEGIN:')
133 | print("\tLength of input:\t\t", length_of_binary_data)
134 | print('\tValue of Vs:\t\t\t', pattern_counts)
135 | print('\tValue of xObs:\t\t\t', xObs)
136 | print('\tP-Value:\t\t\t\t', p_value)
137 | print('DEBUG END.')
138 |
139 |
140 | return (p_value, (p_value >= 0.01))
141 |
142 | @staticmethod
143 | def get_prob(u, x):
144 | out = 1.0 * exp(-x)
145 | if u != 0:
146 | out = 1.0 * x * exp(2 * -x) * (2 ** -u) * hyp1f1(u + 1, 2, x)
147 | return out
--------------------------------------------------------------------------------
/Tools.py:
--------------------------------------------------------------------------------
1 | class Tools:
2 |
3 | @staticmethod
4 | def string_to_binary(input:str):
5 | binary = []
6 | for char in input:
7 | temp = bin(ord(char))[2:]
8 | while(len(temp) < 8):
9 | temp = '0' + temp
10 | binary.append(temp)
11 |
12 | return ''.join(binary)
13 |
14 | @staticmethod
15 | def string_to_binary_no_concat(input: str):
16 | binary = []
17 | for char in input:
18 | binary.append(bin(ord(char))[2:])
19 |
20 | return ''.join(binary)
21 |
22 | @staticmethod
23 | def url_to_binary(input:str):
24 | binary = []
25 | url = input.split('/')[-1].split('.')[0]
26 |
27 | return url
28 |
29 | @staticmethod
30 | def bytes_to_binary(input:bytes):
31 | binary = []
32 | for b in input:
33 | binary.append(f'{b:08b}')
34 | return ''.join(binary)
35 |
--------------------------------------------------------------------------------
/Universal.py:
--------------------------------------------------------------------------------
1 | from math import floor as floor
2 | from math import log as log
3 | from math import sqrt as sqrt
4 | from numpy import zeros as zeros
5 | from scipy.special import erfc as erfc
6 |
7 | class Universal:
8 |
9 | @staticmethod
10 | def statistical_test(binary_data:str, verbose=False):
11 | """
12 | Note that this description is taken from the NIST documentation [1]
13 | [1] http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf
14 | The focus of this test is the number of bits between matching patterns (a measure that is related to the
15 | length of a compressed sequence). The purpose of the test is to detect whether or not the sequence can be
16 | significantly compressed without loss of information. A significantly compressible sequence is considered
17 | to be non-random. **This test is always skipped because the requirements on the lengths of the binary
18 | strings are too high i.e. there have not been enough trading days to meet the requirements.
19 |
20 | :param binary_data: a binary string
21 | :param verbose True to display the debug messgae, False to turn off debug message
22 | :return: (p_value, bool) A tuple which contain the p_value and result of frequency_test(True or False)
23 | """
24 | length_of_binary_data = len(binary_data)
25 | pattern_size = 5
26 | if length_of_binary_data >= 387840:
27 | pattern_size = 6
28 | if length_of_binary_data >= 904960:
29 | pattern_size = 7
30 | if length_of_binary_data >= 2068480:
31 | pattern_size = 8
32 | if length_of_binary_data >= 4654080:
33 | pattern_size = 9
34 | if length_of_binary_data >= 10342400:
35 | pattern_size = 10
36 | if length_of_binary_data >= 22753280:
37 | pattern_size = 11
38 | if length_of_binary_data >= 49643520:
39 | pattern_size = 12
40 | if length_of_binary_data >= 107560960:
41 | pattern_size = 13
42 | if length_of_binary_data >= 231669760:
43 | pattern_size = 14
44 | if length_of_binary_data >= 496435200:
45 | pattern_size = 15
46 | if length_of_binary_data >= 1059061760:
47 | pattern_size = 16
48 |
49 | if 5 < pattern_size < 16:
50 | # Create the biggest binary string of length pattern_size
51 | ones = ""
52 | for i in range(pattern_size):
53 | ones += "1"
54 |
55 | # How long the state list should be
56 | num_ints = int(ones, 2)
57 | vobs = zeros(num_ints + 1)
58 |
59 | # Keeps track of the blocks, and whether were are initializing or summing
60 | num_blocks = floor(length_of_binary_data / pattern_size)
61 | # Q = 10 * pow(2, pattern_size)
62 | init_bits = 10 * pow(2, pattern_size)
63 |
64 | test_bits = num_blocks - init_bits
65 |
66 | # These are the expected values assuming randomness (uniform)
67 | c = 0.7 - 0.8 / pattern_size + (4 + 32 / pattern_size) * pow(test_bits, -3 / pattern_size) / 15
68 | variance = [0, 0, 0, 0, 0, 0, 2.954, 3.125, 3.238, 3.311, 3.356, 3.384, 3.401, 3.410, 3.416, 3.419, 3.421]
69 | expected = [0, 0, 0, 0, 0, 0, 5.2177052, 6.1962507, 7.1836656, 8.1764248, 9.1723243,
70 | 10.170032, 11.168765, 12.168070, 13.167693, 14.167488, 15.167379]
71 | sigma = c * sqrt(variance[pattern_size] / test_bits)
72 |
73 | cumsum = 0.0
74 | # Examine each of the K blocks in the test segment and determine the number of blocks since the
75 | # last occurrence of the same L-bit block (i.e., i – Tj). Replace the value in the table with the
76 | # location of the current block (i.e., Tj= i). Add the calculated distance between re-occurrences of
77 | # the same L-bit block to an accumulating log2 sum of all the differences detected in the K blocks
78 | for i in range(num_blocks):
79 | block_start = i * pattern_size
80 | block_end = block_start + pattern_size
81 | block_data = binary_data[block_start: block_end]
82 | # Work out what state we are in
83 | int_rep = int(block_data, 2)
84 |
85 | # Initialize the state list
86 | if i < init_bits:
87 | vobs[int_rep] = i + 1
88 | else:
89 | initial = vobs[int_rep]
90 | vobs[int_rep] = i + 1
91 | cumsum += log(i - initial + 1, 2)
92 |
93 | # Compute the statistic
94 | phi = float(cumsum / test_bits)
95 | stat = abs(phi - expected[pattern_size]) / (float(sqrt(2)) * sigma)
96 |
97 | # Compute for P-Value
98 | p_value = erfc(stat)
99 |
100 | if verbose:
101 | print('Maurer\'s Universal Statistical Test DEBUG BEGIN:')
102 | print("\tLength of input:\t\t", length_of_binary_data)
103 | print('\tLength of each block:\t', pattern_size)
104 | print('\tNumber of Blocks:\t\t', init_bits)
105 | print('\tValue of phi:\t\t\t', phi)
106 | print('\tP-Value:\t\t\t\t', p_value)
107 | print('DEBUG END.')
108 |
109 | return (p_value, (p_value>=0.01))
110 | else:
111 | return (-1.0, False)
--------------------------------------------------------------------------------
/data/test_data.bin:
--------------------------------------------------------------------------------
1 | y�ЏU�lm�� �%��?�벗�R�'0Ƙ���gC4�f;�e�g�pYq�©�+��%P�M�K�W�R�~v=���� �Dd���ɐg�%���H�I�讐��A:80,� t�ɶ���) x�S��$�߮D�O��ݱ�2w���n�G2ux>x���_z@{���ȧ�Z���Ș���Y�+��<�%MN����2@-T}�?~\�f���������`Y�\�j��C�˶�f����Mg���$|kMe�� �x������h�Up{����cs�����Q�������^Z�[���J�V@zM�[�L�d%U�((m34�EH����o����e&S�������Ш��%�}��u���-����9G����tcy�9("q�llU��K4����ċ�,#tdt`%�o���(Ei���D�!X�_����GY{{\-伐����ߴolj�Lk�`<���{ W�i/����[��U���R��l�!r}NY5� �o�9������:#T͕�
--------------------------------------------------------------------------------
/data/test_data_01.txt:
--------------------------------------------------------------------------------
1 | 400568534f926c25400568534fe6c4f9400568535038979e4005685350b507e44005685351a813ff4005685352429fac4005685352ee2428400568535401e4084005685354a9a8fa4005685355a6f07e4005685356fc58d34005685359642c454005685359bd9fb3400568535a6a4cb6400568535ac8a044400568535b586d24400568535bb0bcf6400568535c0410c5400568535c64381b400568535cfce007400568535d4d83f4
--------------------------------------------------------------------------------
/data/test_data_02.txt:
--------------------------------------------------------------------------------
1 | http://g-ugc.oovoo.com/nemo-ugc/40056853a6d0a17d.jpg
2 | http://g-ugc.oovoo.com/nemo-ugc/40056853a6897dd2.png
3 | http://g-ugc.oovoo.com/nemo-ugc/40056853af4c2529.jpg
4 | http://g-ugc.oovoo.com/nemo-ugc/40056853af08f8b7.jpg
5 | http://g-ugc.oovoo.com/nemo-ugc/40056853af90292a.png
6 | http://g-ugc.oovoo.com/nemo-ugc/40056853afcb8996.png
7 | http://g-ugc.oovoo.com/nemo-ugc/40056853b1c2b900.jpg
8 | http://g-ugc.oovoo.com/nemo-ugc/40056853b23d01a5.png
9 | http://g-ugc.oovoo.com/nemo-ugc/40056853b2078cc0.jpg
10 | http://g-ugc.oovoo.com/nemo-ugc/40056853b2781477.png
--------------------------------------------------------------------------------
/result/20180107_Binary_Data.txt:
--------------------------------------------------------------------------------
1 | Test Data:1100100100001111110110101010001000100001011010001100001000110100110001001100011001100010100010111000
2 |
3 |
4 | Type of Test P-Value Conclusion
5 | 01. Frequency Test (Monobit) 0.109598583399 Random
6 | 02. Frequency Test within a Block 0.109598583399 Random
7 | 03. Run Test 0.500797917887 Random
8 | 04. Longest Run of Ones in a Block 0.0 Non-Random
9 | 05. Binary Matrix Rank Test -1.0 Non-Random
10 | 06. Discrete Fourier Transform (Spectral) Test 0.646355195539 Random
11 | 07. Non-Overlapping Template Matching Test 0.999999999443 Random
12 | 08. Overlapping Template Matching Test nan Non-Random
13 | 09. Maurer's Universal Statistical test -1.0 Non-Random
14 | 10. Linear Complexity Test -1.0 Non-Random
15 | 11. Serial test:
16 | 0.498961087459 Random
17 | 0.498530755297 Random
18 | 12. Approximate Entropy Test 1.0 Random
19 | 13. Cummulative Sums (Forward) Test 0.219194786814 Random
20 | 14. Cummulative Sums (Reverse) Test 0.114866221293 Random
21 | 15. Random Excursions Test:
22 | State Chi Squared P-Value Conclusion
23 | -4 8.17415362647 0.146895259998 Random
24 | -3 10.8628571429 0.0541679551199 Random
25 | -2 9.95238095238 0.0765958095212 Random
26 | -1 2.71428571429 0.743932648724 Random
27 | +1 1.28571428571 0.936394780791 Random
28 | +2 1.85361552028 0.869006618022 Random
29 | +3 8.05828571429 0.153052506984 Random
30 | +4 1.0 0.962565773247 Random
31 | 16. Random Excursions Variant Test:
32 | State COUNTS P-Value Conclusion
33 | -9.0 6 0.948317020766 Random
34 | -8.0 5 0.890230054943 Random
35 | -7.0 2 0.710917113389 Random
36 | -6.0 2 0.687013344233 Random
37 | -5.0 5 0.858586201337 Random
38 | -4.0 7 1.0 Random
39 | -3.0 5 0.811070129334 Random
40 | -2.0 4 0.643428843564 Random
41 | -1.0 5 0.592980098017 Random
42 | +1.0 7 1.0 Random
43 | +2.0 7 1.0 Random
44 | +3.0 4 0.719917853194 Random
45 |
--------------------------------------------------------------------------------
/result/20180107_Binary_File.txt:
--------------------------------------------------------------------------------
1 | Test Data File:/Users/stevenkhoang/PycharmProjects/Randomness_Testing/data/data.e
2 |
3 |
4 | Type of Test P-Value Conclusion
5 | 01. Frequency Test (Monobit) 0.953748628528 Random
6 | 02. Frequency Test within a Block 0.211071543702 Random
7 | 03. Run Test 0.56191688503 Random
8 | 04. Longest Run of Ones in a Block 0.718945329899 Random
9 | 05. Binary Matrix Rank Test 0.3061558375306767 Random
10 | 06. Discrete Fourier Transform (Spectral) Test 0.847186705069 Random
11 | 07. Non-Overlapping Template Matching Test 0.0787901326767 Random
12 | 08. Overlapping Template Matching Test 0.110433685414 Random
13 | 09. Maurer's Universal Statistical test 0.282567947826 Random
14 | 10. Linear Complexity Test 0.826334770404 Random
15 | 11. Serial test:
16 | 0.766181646833 Random
17 | 0.462921324096 Random
18 | 12. Approximate Entropy Test 0.700073388115 Random
19 | 13. Cummulative Sums (Forward) Test 0.669886464168 Random
20 | 14. Cummulative Sums (Reverse) Test 0.72426530997 Random
21 | 15. Random Excursions Test:
22 | State Chi Squared P-Value Conclusion
23 | -4 3.83569821299 0.573305694995 Random
24 | -3 7.31870711409 0.197996020218 Random
25 | -2 7.86192725164 0.164011049379 Random
26 | -1 15.6926174497 0.00777872309647 Non-Random
27 | +1 2.43087248322 0.786867905178 Random
28 | +2 4.79890628884 0.440911736646 Random
29 | +3 2.35704053691 0.797853971688 Random
30 | +4 2.4887672642 0.778185785232 Random
31 | 16. Random Excursions Variant Test:
32 | State COUNTS P-Value Conclusion
33 | -9.0 1450 0.858945739825 Random
34 | -8.0 1435 0.794754956255 Random
35 | -7.0 1380 0.576248618468 Random
36 | -6.0 1366 0.493416934086 Random
37 | -5.0 1412 0.633872669141 Random
38 | -4.0 1475 0.917283147792 Random
39 | -3.0 1480 0.934707791835 Random
40 | -2.0 1468 0.816012036618 Random
41 | -1.0 1502 0.826009012833 Random
42 | +1.0 1409 0.137860608909 Random
43 | +2.0 1369 0.200641913855 Random
44 | +3.0 1396 0.441253622156 Random
45 | +4.0 1479 0.939290606068 Random
46 | +5.0 1599 0.505682682169 Random
47 | +6.0 1628 0.44593471065 Random
48 | +7.0 1619 0.512206885616 Random
49 | +8.0 1620 0.538634697777 Random
50 | +9.0 1610 0.593930395822 Random
51 |
--------------------------------------------------------------------------------
/result/20180117.txt:
--------------------------------------------------------------------------------
1 | Test Data File:/Users/stevenang/PycharmProjects/randomness_testsuite/data/data.e
2 |
3 |
4 | Type of Test P-Value Conclusion
5 | 01. Frequency Test (Monobit) 0.9537486285283232 Random
6 | 02. Frequency Test within a Block 0.21107154370164066 Random
7 | 03. Run Test 0.5619168850302545 Random
8 | 04. Longest Run of Ones in a Block 0.7189453298987654 Random
9 | 05. Binary Matrix Rank Test 0.3061558375306767 Random
10 | 06. Discrete Fourier Transform (Spectral) Test 0.8471867050687718 Random
11 | 07. Non-Overlapping Template Matching Test 0.07879013267666338 Random
12 | 08. Overlapping Template Matching Test 0.11043368541387631 Random
13 | 09. Maurer's Universal Statistical test 0.282567947825744 Random
14 | 10. Linear Complexity Test 0.8263347704038304 Random
15 | 11. Serial test:
16 | 0.766181646833394 Random
17 | 0.46292132409575854 Random
18 | 12. Approximate Entropy Test 0.7000733881151612 Random
19 | 13. Cummulative Sums (Forward) Test 0.6698864641681423 Random
20 | 14. Cummulative Sums (Reverse) Test 0.7242653099698069 Random
21 | 15. Random Excursions Test:
22 | State Chi Squared P-Value Conclusion
23 | -4 3.8356982129929085 0.5733056949947805 Random
24 | -3 7.318707114093956 0.19799602021827734 Random
25 | -2 7.861927251636425 0.16401104937943733 Random
26 | -1 15.69261744966443 0.007778723096466819 Non-Random
27 | +1 2.4308724832214765 0.7868679051783156 Random
28 | +2 4.7989062888391745 0.44091173664620265 Random
29 | +3 2.3570405369127525 0.7978539716877826 Random
30 | +4 2.4887672641992014 0.7781857852321322 Random
31 | 16. Random Excursions Variant Test:
32 | State COUNTS P-Value Conclusion
33 | -9.0 1450 0.8589457398254003 Random
34 | -8.0 1435 0.7947549562546549 Random
35 | -7.0 1380 0.5762486184682754 Random
36 | -6.0 1366 0.4934169340861271 Random
37 | -5.0 1412 0.6338726691411485 Random
38 | -4.0 1475 0.9172831477915963 Random
39 | -3.0 1480 0.9347077918349618 Random
40 | -2.0 1468 0.8160120366175745 Random
41 | -1.0 1502 0.8260090128330382 Random
42 | +1.0 1409 0.13786060890864768 Random
43 | +2.0 1369 0.20064191385523023 Random
44 | +3.0 1396 0.4412536221564536 Random
45 | +4.0 1479 0.939290606067626 Random
46 | +5.0 1599 0.5056826821687638 Random
47 | +6.0 1628 0.4459347106499899 Random
48 | +7.0 1619 0.5122068856164792 Random
49 | +8.0 1620 0.5386346977772863 Random
50 | +9.0 1610 0.5939303958223099 Random
51 |
--------------------------------------------------------------------------------
/result/20180117_URL_Result.txt:
--------------------------------------------------------------------------------
1 | Test Data File:/Users/stevenang/PycharmProjects/randomness_testsuite/data/test_data_02.txt
2 |
3 | Type of Test P-Value Conclusion
4 | 01. Frequency Test (Monobit) 0.033310134921341245 Random
5 | 02. Frequency Test within a Block 0.7542651538756742 Random
6 | 03. Run Test 0.0940470056888701 Random
7 | 04. Longest Run of Ones in a Block 3.380651398651423e-10 Non-Random
8 | 05. Binary Matrix Rank Test -1.0 Non-Random
9 | 06. Discrete Fourier Transform (Spectral) Test 0.010978475273537742 Random
10 | 07. Non-Overlapping Template Matching Test 0.9880245046396146 Random
11 | 08. Overlapping Template Matching Test nan Non-Random
12 | 09. Maurer's Universal Statistical test -1.0 Non-Random
13 | 10. Linear Complexity Test -1.0 Non-Random
14 | 11. Serial test:
15 | 0.0 Non-Random
16 | 0.0 Non-Random
17 | 12. Approximate Entropy Test 0.9776438837438873 Random
18 | 13. Cummulative Sums (Forward) Test 0.06662026949983221 Random
19 | 14. Cummulative Sums (Reverse) Test 0.04821780377885077 Random
20 | 15. Random Excursions Test:
21 | State Chi Squared P-Value Conclusion
22 | -4 5.207792207792208 0.3910510265515671 Random
23 | -3 5.564363636363635 0.3509397816064102 Random
24 | -2 3.7205387205387206 0.5903104834669745 Random
25 | -1 3.909090909090909 0.5625775966348263 Random
26 | +1 11.363636363636363 0.044628250164521314 Random
27 | +2 17.031425364758697 0.00444058191019402 Non-Random
28 | +3 14.469672727272728 0.012885778756621539 Random
29 | +4 9.29699746317822 0.09778837672183648 Random
30 | 16. Random Excursions Variant Test:
31 | State COUNTS P-Value Conclusion
32 | -4.0 1 0.42034493503392867 Random
33 | -3.0 4 0.5045014597713458 Random
34 | -2.0 5 0.46018093544712035 Random
35 | -1.0 5 0.20082512269514552 Random
36 | +1.0 19 0.08808151166219029 Random
37 | +2.0 21 0.21835469056590173 Random
38 | +3.0 17 0.5672694352671082 Random
39 | +4.0 17 0.6287451762738331 Random
40 | +5.0 23 0.3937686346429927 Random
41 | +6.0 19 0.6070705891514693 Random
42 | +7.0 8 0.8591991407511987 Random
43 | +8.0 5 0.7411815058736042 Random
44 | +9.0 7 0.836138661741041 Random
45 |
--------------------------------------------------------------------------------
/test_bin_file.py:
--------------------------------------------------------------------------------
1 | import os
2 | from Tools import Tools
3 |
4 | from FrequencyTest import FrequencyTest as ft
5 | from RunTest import RunTest as rt
6 | from Matrix import Matrix as mt
7 | from Spectral import SpectralTest as st
8 | from TemplateMatching import TemplateMatching as tm
9 | from Universal import Universal as ut
10 | from Complexity import ComplexityTest as ct
11 | from Serial import Serial as serial
12 | from ApproximateEntropy import ApproximateEntropy as aet
13 | from CumulativeSum import CumulativeSums as cst
14 | from RandomExcursions import RandomExcursions as ret
15 |
16 | test_type = ['01. Frequency Test (Monobit)', '02. Frequency Test within a Block', '03. Run Test',
17 | '04. Longest Run of Ones in a Block', '05. Binary Matrix Rank Test',
18 | '06. Discrete Fourier Transform (Spectral) Test',
19 | '07. Non-Overlapping Template Matching Test',
20 | '08. Overlapping Template Matching Test', '09. Maurer\'s Universal Statistical test',
21 | '10. Linear Complexity Test', '11. Serial test', '12. Approximate Entropy Test',
22 | '13. Cummulative Sums (Forward) Test', '14. Cummulative Sums (Reverse) Test',
23 | '15. Random Excursions Test', '16. Random Excursions Variant Test']
24 |
25 | test_function = {
26 | 0:ft.monobit_test,
27 | 1:ft.block_frequency,
28 | 2:rt.run_test,
29 | 3:rt.longest_one_block_test,
30 | 4:mt.binary_matrix_rank_text,
31 | 5:st.spectral_test,
32 | 6:tm.non_overlapping_test,
33 | 7:tm.overlapping_patterns,
34 | 8:ut.statistical_test,
35 | 9:ct.linear_complexity_test,
36 | 10:serial.serial_test,
37 | 11:aet.approximate_entropy_test,
38 | 12:cst.cumulative_sums_test,
39 | 13:cst.cumulative_sums_test,
40 | 14:ret.random_excursions_test,
41 | 15:ret.variant_test
42 | }
43 |
44 | input = b''
45 | with open(os.path.join(os.getcwd(), 'data', 'test_data.bin'), 'rb') as input_file:
46 | input = input_file.read()
47 |
48 |
49 | binary = Tools.bytes_to_binary(input)
50 | count = 0
51 |
52 | for test in test_function:
53 | print(test_type[count%len(test_type)], test_function[count](binary))
54 | count += 1
55 |
--------------------------------------------------------------------------------
/test_e.py:
--------------------------------------------------------------------------------
1 | import os
2 | from FrequencyTest import FrequencyTest
3 | from RunTest import RunTest
4 | from Matrix import Matrix
5 | from Spectral import SpectralTest
6 | from TemplateMatching import TemplateMatching
7 | from Universal import Universal
8 | from Complexity import ComplexityTest
9 | from Serial import Serial
10 | from ApproximateEntropy import ApproximateEntropy
11 | from CumulativeSum import CumulativeSums
12 | from RandomExcursions import RandomExcursions
13 |
14 | # Open Data File and read the binary data of e
15 | data_path = os.path.join(os.getcwd(), 'data', 'data.e')
16 | handle = open(data_path)
17 | data_list = []
18 |
19 | for line in handle:
20 | data_list.append(line.strip().rstrip())
21 |
22 | binary_data = ''.join(data_list)
23 |
24 | print('The statistical test of the Binary Expansion of e')
25 | print('2.01. Frequency Test:\t\t\t\t\t\t\t\t', FrequencyTest.monobit_test(binary_data[:1000000]))
26 | print('2.02. Block Frequency Test:\t\t\t\t\t\t\t', FrequencyTest.block_frequency(binary_data[:1000000]))
27 | print('2.03. Run Test:\t\t\t\t\t\t\t\t\t\t', RunTest.run_test(binary_data[:1000000]))
28 | print('2.04. Run Test (Longest Run of Ones): \t\t\t\t', RunTest.longest_one_block_test(binary_data[:1000000]))
29 | print('2.05. Binary Matrix Rank Test:\t\t\t\t\t\t', Matrix.binary_matrix_rank_text(binary_data[:1000000]))
30 | print('2.06. Discrete Fourier Transform (Spectral) Test:\t', SpectralTest.spectral_test(binary_data[:1000000]))
31 | print('2.07. Non-overlapping Template Matching Test:\t\t', TemplateMatching.non_overlapping_test(binary_data[:1000000], '000000001'))
32 | print('2.08. Overlappong Template Matching Test: \t\t\t', TemplateMatching.overlapping_patterns(binary_data[:1000000]))
33 | print('2.09. Universal Statistical Test:\t\t\t\t\t', Universal.statistical_test(binary_data[:1000000]))
34 | print('2.10. Linear Complexity Test:\t\t\t\t\t\t', ComplexityTest.linear_complexity_test(binary_data[:1000000]))
35 | print('2.11. Serial Test:\t\t\t\t\t\t\t\t\t', Serial.serial_test(binary_data[:1000000]))
36 | print('2.12. Approximate Entropy Test:\t\t\t\t\t\t', ApproximateEntropy.approximate_entropy_test(binary_data[:1000000]))
37 | print('2.13. Cumulative Sums (Forward):\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 0))
38 | print('2.13. Cumulative Sums (Backward):\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 1))
39 | result = RandomExcursions.random_excursions_test(binary_data[:1000000])
40 | print('2.14. Random Excursion Test:')
41 | print('\t\t STATE \t\t\t xObs \t\t\t\t P-Value \t\t\t Conclusion')
42 |
43 | for item in result:
44 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[2], '\t\t', repr(item[3]).ljust(14), '\t\t',
45 | (item[4] >= 0.01))
46 |
47 | result = RandomExcursions.variant_test(binary_data[:1000000])
48 |
49 | print('2.15. Random Excursion Variant Test:\t\t\t\t\t\t')
50 | print('\t\t STATE \t\t COUNTS \t\t\t P-Value \t\t Conclusion')
51 | for item in result:
52 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[2], '\t\t', repr(item[3]).ljust(14), '\t\t',
53 | (item[4] >= 0.01))
--------------------------------------------------------------------------------
/test_pi.py:
--------------------------------------------------------------------------------
1 | import os
2 | from FrequencyTest import FrequencyTest
3 | from RunTest import RunTest
4 | from Matrix import Matrix
5 | from Spectral import SpectralTest
6 | from TemplateMatching import TemplateMatching
7 | from Universal import Universal
8 | from Complexity import ComplexityTest
9 | from Serial import Serial
10 | from ApproximateEntropy import ApproximateEntropy
11 | from CumulativeSum import CumulativeSums
12 | from RandomExcursions import RandomExcursions
13 |
14 | # Open Data File and read the binary data of e
15 | data_path = os.path.join(os.getcwd(), 'data', 'data.pi')
16 | handle = open(data_path)
17 | data_list = []
18 |
19 | for line in handle:
20 | data_list.append(line.strip().rstrip())
21 |
22 | binary_data = ''.join(data_list)
23 |
24 | print('The statistical test of the Binary Expansion of PI')
25 | print('2.1. Frequency Test:\t\t\t\t\t\t\t\t\t', FrequencyTest.monobit_test(binary_data[:1000000]))
26 | print('2.2. Block Frequency Test:\t\t\t\t\t\t\t\t', FrequencyTest.block_frequency(binary_data[:1000000]))
27 | print('2.3. Run Test:\t\t\t\t\t\t\t\t\t\t\t', RunTest.run_test(binary_data[:1000000]))
28 | print('2.4. Run Test (Longest Run of Ones): \t\t\t\t\t', RunTest.longest_one_block_test(binary_data[:1000000]))
29 | print('2.5. Binary Matrix Rank Test:\t\t\t\t\t\t\t', Matrix.binary_matrix_rank_text(binary_data[:1000000]))
30 | print('2.6. Discrete Fourier Transform (Spectral) Test: \t\t', SpectralTest.spectral_test(binary_data[:1000000]))
31 | print('2.7. Non-overlapping Template Matching Test:\t\t\t', TemplateMatching.non_overlapping_test(binary_data[:1000000], '000000001'))
32 | print('2.8. Overlappong Template Matching Test: \t\t\t\t', TemplateMatching.overlapping_patterns(binary_data[:1000000]))
33 | print('2.9. Universal Statistical Test:\t\t\t\t\t\t', Universal.statistical_test(binary_data[:1000000]))
34 | print('2.10. Linear Complexity Test:\t\t\t\t\t\t\t', ComplexityTest.linear_complexity_test(binary_data[:1000000]))
35 | print('2.11. Serial Test:\t\t\t\t\t\t\t\t\t\t', Serial.serial_test(binary_data[:1000000]))
36 | print('2.12. Approximate Entropy Test:\t\t\t\t\t\t\t', ApproximateEntropy.approximate_entropy_test(binary_data[:1000000]))
37 | print('2.13. Cumulative Sums (Forward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 0))
38 | print('2.13. Cumulative Sums (Backward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 1))
39 | result = RandomExcursions.random_excursions_test(binary_data[:1000000])
40 | print('2.14. Random Excursion Test:')
41 | print('\t\t STATE \t\t\t xObs \t\t\t\t P-Value \t\t\t Conclusion')
42 |
43 | for item in result:
44 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t',
45 | (item[3] >= 0.01))
46 |
47 | result = RandomExcursions.variant_test(binary_data[:1000000])
48 |
49 | print('2.15. Random Excursion Variant Test:\t\t\t\t\t\t')
50 | print('\t\t STATE \t\t COUNTS \t\t\t P-Value \t\t Conclusion')
51 | for item in result:
52 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t',
53 | (item[3] >= 0.01))
--------------------------------------------------------------------------------
/test_sqrt2.py:
--------------------------------------------------------------------------------
1 | import os
2 | from FrequencyTest import FrequencyTest
3 | from RunTest import RunTest
4 | from Matrix import Matrix
5 | from Spectral import SpectralTest
6 | from TemplateMatching import TemplateMatching
7 | from Universal import Universal
8 | from Complexity import ComplexityTest
9 | from Serial import Serial
10 | from ApproximateEntropy import ApproximateEntropy
11 | from CumulativeSum import CumulativeSums
12 | from RandomExcursions import RandomExcursions
13 |
14 | # Open Data File and read the binary data of e
15 | data_path = os.path.join(os.getcwd(), 'data', 'data.sqrt2')
16 | handle = open(data_path)
17 | data_list = []
18 |
19 | for line in handle:
20 | data_list.append(line.strip().rstrip())
21 |
22 | binary_data = ''.join(data_list)
23 |
24 | print('The statistical test of the Binary Expansion of SQRT(2)')
25 | print('2.1. Frequency Test:\t\t\t\t\t\t\t\t\t', FrequencyTest.monobit_test(binary_data[:1000000]))
26 | print('2.2. Block Frequency Test:\t\t\t\t\t\t\t\t', FrequencyTest.block_frequency(binary_data[:1000000]))
27 | print('2.3. Run Test:\t\t\t\t\t\t\t\t\t\t\t', RunTest.run_test(binary_data[:1000000]))
28 | print('2.4. Run Test (Longest Run of Ones): \t\t\t\t\t', RunTest.longest_one_block_test(binary_data[:1000000]))
29 | print('2.5. Binary Matrix Rank Test:\t\t\t\t\t\t\t', Matrix.binary_matrix_rank_text(binary_data[:1000000]))
30 | print('2.6. Discrete Fourier Transform (Spectral) Test: \t\t', SpectralTest.spectral_test(binary_data[:1000000]))
31 | print('2.7. Non-overlapping Template Matching Test:\t\t\t', TemplateMatching.non_overlapping_test(binary_data[:1000000], '000000001'))
32 | print('2.8. Overlappong Template Matching Test: \t\t\t\t', TemplateMatching.overlapping_patterns(binary_data[:1000000]))
33 | print('2.9. Universal Statistical Test:\t\t\t\t\t\t', Universal.statistical_test(binary_data[:1000000]))
34 | print('2.10. Linear Complexity Test:\t\t\t\t\t\t\t', ComplexityTest.linear_complexity_test(binary_data[:1000000]))
35 | print('2.11. Serial Test:\t\t\t\t\t\t\t\t\t\t', Serial.serial_test(binary_data[:1000000]))
36 | print('2.12. Approximate Entropy Test:\t\t\t\t\t\t\t', ApproximateEntropy.approximate_entropy_test(binary_data[:1000000]))
37 | print('2.13. Cumulative Sums (Forward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 0))
38 | print('2.13. Cumulative Sums (Backward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 1))
39 | result = RandomExcursions.random_excursions_test(binary_data[:1000000])
40 | print('2.14. Random Excursion Test:')
41 | print('\t\t STATE \t\t\t xObs \t\t\t\t P-Value \t\t\t Conclusion')
42 |
43 | for item in result:
44 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t',
45 | (item[3] >= 0.01))
46 |
47 | result = RandomExcursions.variant_test(binary_data[:1000000])
48 |
49 | print('2.15. Random Excursion Variant Test:\t\t\t\t\t\t')
50 | print('\t\t STATE \t\t COUNTS \t\t\t P-Value \t\t Conclusion')
51 | for item in result:
52 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t',
53 | (item[3] >= 0.01))
--------------------------------------------------------------------------------
/test_sqrt3.py:
--------------------------------------------------------------------------------
1 | import os
2 | from FrequencyTest import FrequencyTest
3 | from RunTest import RunTest
4 | from Matrix import Matrix
5 | from Spectral import SpectralTest
6 | from TemplateMatching import TemplateMatching
7 | from Universal import Universal
8 | from Complexity import ComplexityTest
9 | from Serial import Serial
10 | from ApproximateEntropy import ApproximateEntropy
11 | from CumulativeSum import CumulativeSums
12 | from RandomExcursions import RandomExcursions
13 |
14 | # Open Data File and read the binary data of e
15 | data_path = os.path.join(os.getcwd(), 'data', 'data.sqrt3')
16 | handle = open(data_path)
17 | data_list = []
18 |
19 | for line in handle:
20 | data_list.append(line.strip().rstrip())
21 |
22 | binary_data = ''.join(data_list)
23 |
24 | print('The statistical test of the Binary Expansion of SQRT(3)')
25 | print('2.1. Frequency Test:\t\t\t\t\t\t\t\t\t', FrequencyTest.monobit_test(binary_data[:1000000]))
26 | print('2.2. Block Frequency Test:\t\t\t\t\t\t\t\t', FrequencyTest.block_frequency(binary_data[:1000000]))
27 | print('2.3. Run Test:\t\t\t\t\t\t\t\t\t\t\t', RunTest.run_test(binary_data[:1000000]))
28 | print('2.4. Run Test (Longest Run of Ones): \t\t\t\t\t', RunTest.longest_one_block_test(binary_data[:1000000]))
29 | print('2.5. Binary Matrix Rank Test:\t\t\t\t\t\t\t', Matrix.binary_matrix_rank_text(binary_data[:1000000]))
30 | print('2.6. Discrete Fourier Transform (Spectral) Test: \t\t', SpectralTest.spectral_test(binary_data[:1000000]))
31 | print('2.7. Non-overlapping Template Matching Test:\t\t\t', TemplateMatching.non_overlapping_test(binary_data[:1000000], '000000001'))
32 | print('2.8. Overlappong Template Matching Test: \t\t\t\t', TemplateMatching.overlapping_patterns(binary_data[:1000000]))
33 | print('2.9. Universal Statistical Test:\t\t\t\t\t\t', Universal.statistical_test(binary_data[:1000000]))
34 | print('2.10. Linear Complexity Test:\t\t\t\t\t\t\t', ComplexityTest.linear_complexity_test(binary_data[:1000000]))
35 | print('2.11. Serial Test:\t\t\t\t\t\t\t\t\t\t', Serial.serial_test(binary_data[:1000000]))
36 | print('2.12. Approximate Entropy Test:\t\t\t\t\t\t\t', ApproximateEntropy.approximate_entropy_test(binary_data[:1000000]))
37 | print('2.13. Cumulative Sums (Forward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 0))
38 | print('2.13. Cumulative Sums (Backward):\t\t\t\t\t\t', CumulativeSums.cumulative_sums_test(binary_data[:1000000], 1))
39 | result = RandomExcursions.random_excursions_test(binary_data[:1000000])
40 | print('2.14. Random Excursion Test:')
41 | print('\t\t STATE \t\t\t xObs \t\t\t\t P-Value \t\t\t Conclusion')
42 |
43 | for item in result:
44 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t',
45 | (item[3] >= 0.01))
46 |
47 | result = RandomExcursions.variant_test(binary_data[:1000000])
48 |
49 | print('2.15. Random Excursion Variant Test:\t\t\t\t\t\t')
50 | print('\t\t STATE \t\t COUNTS \t\t\t P-Value \t\t Conclusion')
51 | for item in result:
52 | print('\t\t', repr(item[0]).rjust(4), '\t\t', item[1], '\t\t', repr(item[2]).ljust(14), '\t\t',
53 | (item[3] >= 0.01))
--------------------------------------------------------------------------------
/test_url_01.py:
--------------------------------------------------------------------------------
1 | import os
2 | from Tools import Tools
3 |
4 | from FrequencyTest import FrequencyTest as ft
5 | from RunTest import RunTest as rt
6 | from Matrix import Matrix as mt
7 | from Spectral import SpectralTest as st
8 | from TemplateMatching import TemplateMatching as tm
9 | from Universal import Universal as ut
10 | from Complexity import ComplexityTest as ct
11 | from Serial import Serial as serial
12 | from ApproximateEntropy import ApproximateEntropy as aet
13 | from CumulativeSum import CumulativeSums as cst
14 | from RandomExcursions import RandomExcursions as ret
15 |
16 | test_type = ['01. Frequency Test (Monobit)', '02. Frequency Test within a Block', '03. Run Test',
17 | '04. Longest Run of Ones in a Block', '05. Binary Matrix Rank Test',
18 | '06. Discrete Fourier Transform (Spectral) Test',
19 | '07. Non-Overlapping Template Matching Test',
20 | '08. Overlapping Template Matching Test', '09. Maurer\'s Universal Statistical test',
21 | '10. Linear Complexity Test', '11. Serial test', '12. Approximate Entropy Test',
22 | '13. Cummulative Sums (Forward) Test', '14. Cummulative Sums (Reverse) Test',
23 | '15. Random Excursions Test', '16. Random Excursions Variant Test']
24 |
25 | test_function = {
26 | 0:ft.monobit_test,
27 | 1:ft.block_frequency,
28 | 2:rt.run_test,
29 | 3:rt.longest_one_block_test,
30 | 4:mt.binary_matrix_rank_text,
31 | 5:st.spectral_test,
32 | 6:tm.non_overlapping_test,
33 | 7:tm.overlapping_patterns,
34 | 8:ut.statistical_test,
35 | 9:ct.linear_complexity_test,
36 | 10:serial.serial_test,
37 | 11:aet.approximate_entropy_test,
38 | 12:cst.cumulative_sums_test,
39 | 13:cst.cumulative_sums_test,
40 | 14:ret.random_excursions_test,
41 | 15:ret.variant_test
42 | }
43 |
44 | handle = open(os.path.join(os.getcwd(), 'data', 'test_data_01.txt'))
45 | count = 0
46 |
47 | for item in handle:
48 | binary = Tools.string_to_binary(item)
49 | print(item, Tools.string_to_binary(item), binary)
50 | count = 0
51 | for test in test_function:
52 | print(test_type[count%len(test_type)], test_function[count](binary))
53 | count += 1
--------------------------------------------------------------------------------