-
Notifications
You must be signed in to change notification settings - Fork 56
/
BinaryMatrix.py
124 lines (112 loc) · 4.24 KB
/
BinaryMatrix.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from copy import copy as copy
class BinaryMatrix:
def __init__(self, matrix, rows, cols):
"""
This class contains the algorithm specified in the NIST suite for computing the **binary rank** of a matrix.
:param matrix: the matrix we want to compute the rank for
:param rows: the number of rows
:param cols: the number of columns
:return: a BinaryMatrix object
"""
self.M = rows
self.Q = cols
self.A = matrix
self.m = min(rows, cols)
def compute_rank(self, verbose=False):
"""
This method computes the binary rank of self.matrix
:param verbose: if this is true it prints out the matrix after the forward elimination and backward elimination
operations on the rows. This was used to testing the method to check it is working as expected.
:return: the rank of the matrix.
"""
if verbose:
print("Original Matrix\n", self.A)
i = 0
while i < self.m - 1:
if self.A[i][i] == 1:
self.perform_row_operations(i, True)
else:
found = self.find_unit_element_swap(i, True)
if found == 1:
self.perform_row_operations(i, True)
i += 1
if verbose:
print("Intermediate Matrix\n", self.A)
i = self.m - 1
while i > 0:
if self.A[i][i] == 1:
self.perform_row_operations(i, False)
else:
if self.find_unit_element_swap(i, False) == 1:
self.perform_row_operations(i, False)
i -= 1
if verbose:
print("Final Matrix\n", self.A)
return self.determine_rank()
def perform_row_operations(self, i, forward_elimination):
"""
This method performs the elementary row operations. This involves xor'ing up to two rows together depending on
whether or not certain elements in the matrix contain 1's if the "current" element does not.
:param i: the current index we are are looking at
:param forward_elimination: True or False.
"""
if forward_elimination:
j = i + 1
while j < self.M:
if self.A[j][i] == 1:
self.A[j, :] = (self.A[j, :] + self.A[i, :]) % 2
j += 1
else:
j = i - 1
while j >= 0:
if self.A[j][i] == 1:
self.A[j, :] = (self.A[j, :] + self.A[i, :]) % 2
j -= 1
def find_unit_element_swap(self, i, forward_elimination):
"""
This given an index which does not contain a 1 this searches through the rows below the index to see which rows
contain 1's, if they do then they swapped. This is done on the forward and backward elimination
:param i: the current index we are looking at
:param forward_elimination: True or False.
"""
row_op = 0
if forward_elimination:
index = i + 1
while index < self.M and self.A[index][i] == 0:
index += 1
if index < self.M:
row_op = self.swap_rows(i, index)
else:
index = i - 1
while index >= 0 and self.A[index][i] == 0:
index -= 1
if index >= 0:
row_op = self.swap_rows(i, index)
return row_op
def swap_rows(self, i, ix):
"""
This method just swaps two rows in a matrix. Had to use the copy package to ensure no memory leakage
:param i: the first row we want to swap and
:param ix: the row we want to swap it with
:return: 1
"""
temp = copy(self.A[i, :])
self.A[i, :] = self.A[ix, :]
self.A[ix, :] = temp
return 1
def determine_rank(self):
"""
This method determines the rank of the transformed matrix
:return: the rank of the transformed matrix
"""
rank = self.m
i = 0
while i < self.M:
all_zeros = 1
for j in range(self.Q):
if self.A[i][j] == 1:
all_zeros = 0
if all_zeros == 1:
rank -= 1
i += 1
return rank