155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
--------------------------------------------------------------------------------
/reedmuller/reedmuller.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """reedmuller.py
3 |
4 | Implementation of Reed-Muller codes for Python.
5 | See the class ReedMuller for the documentation."""
6 |
7 | import operator
8 | import itertools
9 | from functools import reduce
10 |
11 |
12 | def _binom(n, k):
13 | """Binomial coefficienct (n-k)!/k!."""
14 | return reduce(operator.mul, range(n - k + 1, n + 1)) // reduce(operator.mul, range(1, k + 1))
15 |
16 |
17 | def _construct_vector(m, i):
18 | """Construct the vector for x_i of length 2^m, which has form:
19 | A string of 2^{m-i-1} 1s followed by 2^{m-i-1} 0s, repeated
20 | 2^m / (2*2^{m-i-1}) = 2^{m-1}/2^{m-i-1} = 2^i times.
21 | NOTE: we must have 0 <= i < m."""
22 | return ([1] * (2 ** (m - i - 1)) + [0] * (2 ** (m - i - 1))) * (2 ** i)
23 |
24 |
25 | def _vector_mult(*vecs):
26 | """For any number of length-n vectors, pairwise multiply the entries, e.g. for
27 | x = (x_0, ..., x_{n-1}), y = (y_0, ..., y_{n-1}),
28 | xy = (x_0y_0, x_1y_1, ..., x_{n-1}y{n-1})."""
29 | assert (len(set(map(len, vecs))) == 1)
30 | return list(map(lambda a: reduce(operator.mul, a, 1), list(zip(*vecs))))
31 |
32 |
33 | def _vector_add(*vecs):
34 | """For any number of length-n vectors, pairwise add the entries, e.g. for
35 | x = (x_0, ..., x_{n-1}), y = (y_0, ..., y_{n-1}),
36 | xy = (x_0+y_0, x_1+y_1, ..., x_{n-1}+y{n-1})."""
37 | assert (len(set(map(len, vecs))) == 1)
38 | return list(map(lambda a: reduce(operator.add, a, 0), list(zip(*vecs))))
39 |
40 |
41 | def _vector_neg(x):
42 | """Take the negation of a vector over Z_2, i.e. swap 1 and 0."""
43 | return list(map(lambda a: 1 - a, x))
44 |
45 |
46 | def _vector_reduce(x, modulo):
47 | """Reduce each entry of a vector modulo the value supplied."""
48 | return list(map(lambda a: a % modulo, x))
49 |
50 |
51 | def _dot_product(x, y):
52 | """Calculate the dot product of two vectors."""
53 | assert (len(x) == len(y))
54 | return sum(_vector_mult(x, y))
55 |
56 |
57 | def _generate_all_rows(m, S):
58 | """Generate all rows over the monomials in S, e.g. if S = {0,2}, we want to generate
59 | a list of four rows, namely:
60 | phi(x_0) * phi(x_2)
61 | phi(x_0) * !phi(x_2)
62 | !phi(x_0) * phi(x_2)
63 | !phi(x_0) * !phi(x_2).
64 |
65 | We do this using recursion on S."""
66 |
67 | if not S:
68 | return [[1] * (2 ** m)]
69 |
70 | i, Srest = S[0], S[1:]
71 |
72 | # Find all the rows over Srest.
73 | Srest_rows = _generate_all_rows(m, Srest)
74 |
75 | # Now, for both the representation of x_i and !x_i, return the rows multiplied by these.
76 | xi_row = _construct_vector(m, i)
77 | not_xi_row = _vector_neg(xi_row)
78 | return [_vector_mult(xi_row, row) for row in Srest_rows] + [_vector_mult(not_xi_row, row) for row in Srest_rows]
79 |
80 |
81 | class ReedMuller:
82 | """A class representing a Reed-Muller code RM(r,m), which encodes words of length:
83 | k = C(m,0) + C(m,1) + ... + C(m,r)
84 | to words of length n = 2^m.
85 | Note that C(m,0) + ... + C(m,m) = 2^m, so k <= n in all cases, as expected.
86 | The code RM(r,m) has weight 2^{m-r}, and thus, can correct up to 2^{m-r-1}-1 errors."""
87 |
88 | def __init__(self, r, m):
89 | """Create a Reed-Muller coder / decoder for RM(r,m)."""
90 | self.r, self.m = (r, m)
91 | self._construct_matrix()
92 | self.k = len(self.M[0])
93 | self.n = 2 ** m
94 |
95 | def strength(self):
96 | """Return the strength of the code, i.e. the number of errors we can correct."""
97 | return 2 ** (self.m - self.r - 1) - 1
98 |
99 | def message_length(self):
100 | """The length of a message to be encoded."""
101 | return self.k
102 |
103 | def block_length(self):
104 | """The length of a coded message."""
105 | return self.n
106 |
107 | def _construct_matrix(self):
108 | # Construct all of the x_i rows.
109 | x_rows = [_construct_vector(self.m, i) for i in range(self.m)]
110 |
111 | # For every s-set S for all 0 <= s <= r, create the row that is the product of the x_j vectors for j in S.
112 | self.matrix_by_row = [reduce(_vector_mult, [x_rows[i] for i in S], [1] * (2 ** self.m))
113 | for s in range(self.r + 1)
114 | for S in itertools.combinations(range(self.m), s)]
115 |
116 | # To decode, for each row of the matrix, what we need is a list of all vectors consisting of the representations
117 | # of all monomials not in the row. These are the rows that are used in voting to determine if there is a 0 or 1
118 | # in the position corresponding to the row.
119 | self.voting_rows = [_generate_all_rows(self.m, [i for i in range(self.m) if i not in S])
120 | for s in range(self.r + 1)
121 | for S in itertools.combinations(range(self.m), s)]
122 |
123 | # Now the only thing we need are a list of the indices of the rows corresponding to monomials of degree i.
124 | self.row_indices_by_degree = [0]
125 | for degree in range(1, self.r + 1):
126 | self.row_indices_by_degree.append(self.row_indices_by_degree[degree - 1] + _binom(self.m, degree))
127 |
128 | # Now we want the transpose for the code matrix, to facilitate multiplying vectors on the right by the matrix.
129 | self.M = list(zip(*self.matrix_by_row))
130 |
131 | def encode(self, word):
132 | """Encode a length-k vector to a length-n vector."""
133 | assert (len(word) == self.k)
134 | return [_dot_product(word, col) % 2 for col in self.M]
135 |
136 | def decode(self, eword):
137 | """Decode a length-n vector back to its original length-k vector using majority logic."""
138 | # We want to iterate over each row r of the matrix and determine if a 0 or 1 appears in
139 | # position r of the original word w using majority logic.
140 |
141 | row = self.k - 1
142 | word = [-1] * self.k
143 |
144 | for degree in range(self.r, -1, -1):
145 | # We calculate the entries for the degree. We need the range of rows of the code matrix
146 | # corresponding to degree r.
147 | upper_r = self.row_indices_by_degree[degree]
148 | lower_r = 0 if degree == 0 else self.row_indices_by_degree[degree - 1] + 1
149 |
150 | # Now iterate over these rows to determine the value of word for positions lower_r
151 | # through upper_r inclusive.
152 | for pos in range(lower_r, upper_r + 1):
153 | # We vote for the value of this position based on the vectors in voting_rows.
154 | votes = [_dot_product(eword, vrow) % 2 for vrow in self.voting_rows[pos]]
155 |
156 | # If there is a tie, there is nothing we can do.
157 | if votes.count(0) == votes.count(1):
158 | return None
159 |
160 | # Otherwise, we set the position to the winner.
161 | word[pos] = 0 if votes.count(0) > votes.count(1) else 1
162 |
163 | # Now we need to modify the word. We want to calculate the product of what we just
164 | # voted on with the rows of the matrix.
165 | # QUESTION: do we JUST do this with what we've calculated (word[lower_r] to word[upper_r]),
166 | # or do we do it with word[lower_r] to word[k-1]?
167 | s = [_dot_product(word[lower_r:upper_r + 1], column[lower_r:upper_r + 1]) % 2 for column in self.M]
168 | eword = _vector_reduce(_vector_add(eword, s), 2)
169 |
170 | # We have now decoded.
171 | return word
172 |
173 | def __repr__(self):
174 | return '' % (self.r, self.m, self.strength())
175 |
176 |
177 | def _generate_all_vectors(n):
178 | """Generator to yield all possible length-n vectors in Z_2."""
179 | v = [0] * n
180 | while True:
181 | yield v
182 |
183 | # Generate the next vector by adding 1 to the end.
184 | # Then keep modding by 2 and moving any excess back up the vector.
185 | v[n - 1] = v[n - 1] + 1
186 | pos = n - 1
187 | while pos >= 0 and v[pos] == 2:
188 | v[pos] = 0
189 | pos = pos - 1
190 | if pos >= 0:
191 | v[pos] += 1
192 |
193 | # Terminate if we reach the all-0 vector again.
194 | if v == [0] * n:
195 | break
196 |
197 |
198 | def _characteristic_vector(n, S):
199 | """Return the characteristic vector of the subset S of an n-set."""
200 | return [0 if i not in S else 1 for i in range(n)]
201 |
202 |
203 | if __name__ == '__main__':
204 | # Check for correct command-line arguments and if not present, print informative message.
205 | import sys
206 |
207 | if len(sys.argv) != 3:
208 | sys.stderr.write('Usage: %s r m\n' % (sys.argv[0],))
209 | sys.exit(1)
210 | r, m = map(int, sys.argv[1:])
211 | if (m <= r):
212 | sys.stderr.write('We require r > m.\n')
213 | sys.exit(2)
214 |
215 | # Create the code.
216 | rm = ReedMuller(r, m)
217 | strength = rm.strength()
218 | message_length = rm.message_length()
219 | block_length = rm.block_length()
220 |
221 | # Create a list of all possible errors up to the maximum strength.
222 | error_vectors = [_characteristic_vector(block_length, S)
223 | for numerrors in range(strength + 1)
224 | for S in itertools.combinations(range(block_length), numerrors)]
225 |
226 | # Encode every possible message of message_length.
227 | success = True
228 | for word in _generate_all_vectors(message_length):
229 | codeword = rm.encode(word)
230 |
231 | # Now produce all correctable errors and make sure we still decode to the right word.
232 | for error in error_vectors:
233 | error_codeword = _vector_reduce(_vector_add(codeword, error), 2)
234 | error_word = rm.decode(error_codeword)
235 | if error_word != word:
236 | print('ERROR: encode(%s) => %s, decode(%s+%s=%s) => %s' % (word, codeword, codeword,
237 | error, error_codeword, error_word))
238 | success = False
239 |
240 | if success:
241 | print('RM(%s,%s): success.' % (r, m))
242 |
--------------------------------------------------------------------------------