Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Reed-Solomon decoding with c != 1 #216

Merged
merged 3 commits into from
Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 30 additions & 25 deletions galois/_codes/_reed_solomon.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,9 @@ def decode(self, codeword, errors=False):
is defined as :math:`\mathbf{m} = [m_{k-1}, \dots, m_1, m_0] \in \mathrm{GF}(q)^k`, which corresponds to the message
polynomial :math:`m(x) = m_{k-1} x^{k-1} + \dots + m_1 x + m_0`.

In decoding, the syndrome vector :math:`s` is computed by :math:`\mathbf{s} = \mathbf{c}\mathbf{H}^T`, where
:math:`\mathbf{H}` is the parity-check matrix. The equivalent polynomial operation is :math:`s(x) = c(x)\ \textrm{mod}\ g(x)`.
In decoding, the syndrome vector :math:`\mathbf{s}` is computed by :math:`\mathbf{s} = \mathbf{c}\mathbf{H}^T`, where
:math:`\mathbf{H}` is the parity-check matrix. The equivalent polynomial operation is the codeword polynomial evaluated
at each root of the generator polynomial, i.e. :math:`\mathbf{s} = [c(\alpha^{c}), c(\alpha^{c+1}), \dots, c(\alpha^{c+2t-1})]`.
A syndrome of zeros indicates the received codeword is a valid codeword and there are no errors. If the syndrome is non-zero,
the decoder will find an error-locator polynomial :math:`\sigma(x)` and the corresponding error locations and values.

Expand Down Expand Up @@ -411,7 +412,7 @@ def decode(self, codeword, errors=False):
syndrome = codeword.view(self.field) @ self.H[:,-ns:].T

if self.field.ufunc_mode != "python-calculate":
dec_codeword = self._decode_jit(codeword.astype(np.int64), syndrome.astype(np.int64), self.t, int(self.field.primitive_element), self._add_jit, self._subtract_jit, self._multiply_jit, self._reciprocal_jit, self._power_jit, self._berlekamp_massey_jit, self._poly_roots_jit, self._poly_eval_jit, self._convolve_jit, self.field.characteristic, self.field.degree, self.field._irreducible_poly_int)
dec_codeword = self._decode_jit(codeword.astype(np.int64), syndrome.astype(np.int64), self.c, self.t, int(self.field.primitive_element), self._add_jit, self._subtract_jit, self._multiply_jit, self._reciprocal_jit, self._power_jit, self._berlekamp_massey_jit, self._poly_roots_jit, self._poly_eval_jit, self._convolve_jit, self.field.characteristic, self.field.degree, self.field._irreducible_poly_int)
N_errors = dec_codeword[:, -1]

if self.systematic:
Expand Down Expand Up @@ -614,9 +615,9 @@ def is_narrow_sense(self):
# JIT-compiled implementation of the specified functions
###############################################################################

DECODE_CALCULATE_SIG = numba.types.FunctionType(int64[:,:](int64[:,:], int64[:,:], int64, int64, FieldClass._BINARY_CALCULATE_SIG, FieldClass._BINARY_CALCULATE_SIG, FieldClass._BINARY_CALCULATE_SIG, FieldClass._UNARY_CALCULATE_SIG, FieldClass._BINARY_CALCULATE_SIG, _lfsr.BERLEKAMP_MASSEY_CALCULATE_SIG, FieldClass._POLY_ROOTS_CALCULATE_SIG, FieldClass._POLY_EVALUATE_CALCULATE_SIG, FieldClass._CONVOLVE_CALCULATE_SIG, int64, int64, int64))
DECODE_CALCULATE_SIG = numba.types.FunctionType(int64[:,:](int64[:,:], int64[:,:], int64, int64, int64, FieldClass._BINARY_CALCULATE_SIG, FieldClass._BINARY_CALCULATE_SIG, FieldClass._BINARY_CALCULATE_SIG, FieldClass._UNARY_CALCULATE_SIG, FieldClass._BINARY_CALCULATE_SIG, _lfsr.BERLEKAMP_MASSEY_CALCULATE_SIG, FieldClass._POLY_ROOTS_CALCULATE_SIG, FieldClass._POLY_EVALUATE_CALCULATE_SIG, FieldClass._CONVOLVE_CALCULATE_SIG, int64, int64, int64))

def decode_calculate(codeword, syndrome, t, primitive_element, ADD, SUBTRACT, MULTIPLY, RECIPROCAL, POWER, BERLEKAMP_MASSEY, POLY_ROOTS, POLY_EVAL, CONVOLVE, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY): # pragma: no cover
def decode_calculate(codeword, syndrome, c, t, primitive_element, ADD, SUBTRACT, MULTIPLY, RECIPROCAL, POWER, BERLEKAMP_MASSEY, POLY_ROOTS, POLY_EVAL, CONVOLVE, CHARACTERISTIC, DEGREE, IRREDUCIBLE_POLY): # pragma: no cover
"""
References
----------
Expand All @@ -627,6 +628,7 @@ def decode_calculate(codeword, syndrome, t, primitive_element, ADD, SUBTRACT, MU

N = codeword.shape[0] # The number of codewords
n = codeword.shape[1] # The codeword size (could be less than the design n for shortened codes)
design_n = CHARACTERISTIC**DEGREE - 1 # The designed codeword size

# The last column of the returned decoded codeword is the number of corrected errors
dec_codeword = np.zeros((N, n + 1), dtype=dtype)
Expand All @@ -643,48 +645,51 @@ def decode_calculate(codeword, syndrome, t, primitive_element, ADD, SUBTRACT, MU
# The error-locator polynomial σ(x) = (1 - β1*x)(1 - β2*x)...(1 - βv*x) where βi are the inverse of the roots
# of σ(x).

# Compute the error-locator polynomial σ(x) its v-reversal σ(x^-v), since the syndrome is passed in backwards
# Compute the error-locator polynomial σ(x)
sigma = BERLEKAMP_MASSEY(syndrome[i,:], ADD, SUBTRACT, MULTIPLY, RECIPROCAL, *args)
sigma_rev = BERLEKAMP_MASSEY(syndrome[i,::-1], ADD, SUBTRACT, MULTIPLY, RECIPROCAL, *args)
v = sigma.size - 1 # The number of errors
v = sigma.size - 1 # The number of errors, which is the degree of the error-locator polynomial

if v > t:
dec_codeword[i, -1] = -1
dec_codeword[i,-1] = -1
continue

# Compute βi, the roots of σ(x^-v) which are the inverse roots of σ(x)
degrees = np.arange(sigma_rev.size - 1, -1, -1)
results = POLY_ROOTS(degrees, sigma_rev, primitive_element, ADD, MULTIPLY, POWER, *args)
beta = results[0,:] # The roots of σ(x^-v)
error_locations = results[1,:] # The roots as powers of the primitive element α
# Compute βi^-1, the roots of σ(x)
degrees = np.arange(sigma.size - 1, -1, -1)
results = POLY_ROOTS(degrees, sigma, primitive_element, ADD, MULTIPLY, POWER, *args)
beta_inv = results[0,:] # The roots βi^-1 of σ(x)
error_locations_inv = results[1,:] # The roots βi^-1 as powers of the primitive element α
error_locations = -error_locations_inv % design_n # The error locations as degrees of c(x)

if np.any(error_locations > n - 1):
# Indicates there are "errors" in the zero-ed portion of a shortened code, which indicates there are actually
# more errors than alleged. Return failure to decode.
dec_codeword[i, -1] = -1
dec_codeword[i,-1] = -1
continue

if beta.size != v:
dec_codeword[i, -1] = -1
if beta_inv.size != v:
dec_codeword[i,-1] = -1
continue

# Compute σ'(x)
sigma_prime = np.zeros(v, dtype=np.int64)
sigma_prime = np.zeros(v, dtype=dtype)
for j in range(v):
degree = v - j
sigma_prime[j] = MULTIPLY(degree % CHARACTERISTIC, sigma[j], *args) # Scalar multiplication

# The error-value evalulator polynomial Z0(x) = S0*σ0 + (S1*σ0 + S0*σ1)*x + (S2*σ0 + S1*σ1 + S0*σ2)*x^2 + ...
# The error-value evaluator polynomial Z0(x) = S0*σ0 + (S1*σ0 + S0*σ1)*x + (S2*σ0 + S1*σ1 + S0*σ2)*x^2 + ...
# with degree v-1
Z0 = CONVOLVE(sigma[-v:], syndrome[i,0:v][::-1], ADD, MULTIPLY, *args)[-v:]

# The error value δi = -Z0(βi^-1) / σ'(βi^-1)
# The error value δi = -1 * βi^(1-c) * Z0(βi^-1) / σ'(βi^-1)
for j in range(v):
beta_inv = RECIPROCAL(beta[j], *args)
Z0_i = POLY_EVAL(Z0, np.array([beta_inv], dtype=dtype), ADD, MULTIPLY, *args)[0] # NOTE: poly_eval() expects a 1-D array of values
sigma_prime_i = POLY_EVAL(sigma_prime, np.array([beta_inv], dtype=dtype), ADD, MULTIPLY, *args)[0] # NOTE: poly_eval() expects a 1-D array of values
delta_i = MULTIPLY(SUBTRACT(0, Z0_i, *args), RECIPROCAL(sigma_prime_i, *args), *args)
beta_i = POWER(beta_inv[j], c - 1, *args)
Z0_i = POLY_EVAL(Z0, np.array([beta_inv[j]], dtype=dtype), ADD, MULTIPLY, *args)[0] # NOTE: poly_eval() expects a 1-D array of values
sigma_prime_i = POLY_EVAL(sigma_prime, np.array([beta_inv[j]], dtype=dtype), ADD, MULTIPLY, *args)[0] # NOTE: poly_eval() expects a 1-D array of values
delta_i = MULTIPLY(beta_i, Z0_i, *args)
delta_i = MULTIPLY(delta_i, RECIPROCAL(sigma_prime_i, *args), *args)
delta_i = SUBTRACT(0, delta_i, *args)
dec_codeword[i, n - 1 - error_locations[j]] = SUBTRACT(dec_codeword[i, n - 1 - error_locations[j]], delta_i, *args)
dec_codeword[i, -1] = v # The number of corrected errors

dec_codeword[i,-1] = v # The number of corrected errors

return dec_codeword
50 changes: 25 additions & 25 deletions tests/codes/test_rs_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@
from .helper import random_errors

CODES = [
(15, 13), # GF(2^4) with t=1
(15, 11), # GF(2^4) with t=2
(15, 9), # GF(2^4) with t=3
(15, 7), # GF(2^4) with t=4
(15, 5), # GF(2^4) with t=5
(15, 3), # GF(2^4) with t=6
(15, 1), # GF(2^4) with t=7
(16, 14), # GF(17) with t=1
(16, 12), # GF(17) with t=2
(16, 10), # GF(17) with t=3
(26, 24), # GF(3^3) with t=1
(26, 22), # GF(3^3) with t=2
(26, 20), # GF(3^3) with t=3
(15, 13, 1), # GF(2^4) with t=1
(15, 11, 2), # GF(2^4) with t=2
(15, 9, 1), # GF(2^4) with t=3
(15, 7, 2), # GF(2^4) with t=4
(15, 5, 1), # GF(2^4) with t=5
(15, 3, 2), # GF(2^4) with t=6
(15, 1, 1), # GF(2^4) with t=7
(16, 14, 2), # GF(17) with t=1
(16, 12, 1), # GF(17) with t=2
(16, 10, 2), # GF(17) with t=3
(26, 24, 1), # GF(3^3) with t=1
(26, 22, 2), # GF(3^3) with t=2
(26, 20, 3), # GF(3^3) with t=3
]


Expand All @@ -48,9 +48,9 @@ def test_exceptions():
class TestSystematic:
@pytest.mark.parametrize("size", CODES)
def test_all_correctable(self, size):
n, k = size[0], size[1]
n, k, c = size[0], size[1], size[2]
N = 100
rs = galois.ReedSolomon(n, k)
rs = galois.ReedSolomon(n, k, c=c)
GF = rs.field
M = GF.Random((N, k))
C = rs.encode(M)
Expand All @@ -77,9 +77,9 @@ def test_all_correctable(self, size):

@pytest.mark.parametrize("size", CODES)
def test_some_uncorrectable(self, size):
n, k = size[0], size[1]
n, k, c = size[0], size[1], size[2]
N = 100
rs = galois.ReedSolomon(n, k)
rs = galois.ReedSolomon(n, k, c=c)
GF = rs.field
M = GF.Random((N, k))
C = rs.encode(M)
Expand Down Expand Up @@ -110,13 +110,13 @@ def test_some_uncorrectable(self, size):
class TestSystematicShortened:
@pytest.mark.parametrize("size", CODES)
def test_all_correctable(self, size):
n, k = size[0], size[1]
n, k, c = size[0], size[1], size[2]
if k == 1:
return
ks = k // 2 # Shorten the code in half
ns = n - (k - ks)
N = 100
rs = galois.ReedSolomon(n, k)
rs = galois.ReedSolomon(n, k, c=c)
GF = rs.field
M = GF.Random((N, ks))
C = rs.encode(M)
Expand All @@ -143,13 +143,13 @@ def test_all_correctable(self, size):

@pytest.mark.parametrize("size", CODES)
def test_some_uncorrectable(self, size):
n, k = size[0], size[1]
n, k, c = size[0], size[1], size[2]
if k == 1:
return
ks = k // 2 # Shorten the code in half
ns = n - (k - ks)
N = 100
rs = galois.ReedSolomon(n, k)
rs = galois.ReedSolomon(n, k, c=c)
GF = rs.field
M = GF.Random((N, ks))
C = rs.encode(M)
Expand Down Expand Up @@ -180,9 +180,9 @@ def test_some_uncorrectable(self, size):
class TestNonSystematic:
@pytest.mark.parametrize("size", CODES)
def test_all_correctable(self, size):
n, k = size[0], size[1]
n, k, c = size[0], size[1], size[2]
N = 100
rs = galois.ReedSolomon(n, k, systematic=False)
rs = galois.ReedSolomon(n, k, c=c, systematic=False)
GF = rs.field
M = GF.Random((N, k))
C = rs.encode(M)
Expand All @@ -209,9 +209,9 @@ def test_all_correctable(self, size):

@pytest.mark.parametrize("size", CODES)
def test_some_uncorrectable(self, size):
n, k = size[0], size[1]
n, k, c = size[0], size[1], size[2]
N = 100
rs = galois.ReedSolomon(n, k, systematic=False)
rs = galois.ReedSolomon(n, k, c=c, systematic=False)
GF = rs.field
M = GF.Random((N, k))
C = rs.encode(M)
Expand Down
26 changes: 13 additions & 13 deletions tests/codes/test_rs_encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@
import galois

CODES = [
(15, 13), # GF(2^4) with t=1
(15, 11), # GF(2^4) with t=2
(15, 9), # GF(2^4) with t=3
(15, 7), # GF(2^4) with t=4
(15, 5), # GF(2^4) with t=5
(15, 3), # GF(2^4) with t=6
(15, 1), # GF(2^4) with t=7
(16, 14), # GF(17) with t=1
(16, 12), # GF(17) with t=2
(16, 10), # GF(17) with t=3
(26, 24), # GF(3^3) with t=1
(26, 22), # GF(3^3) with t=2
(26, 20), # GF(3^3) with t=3
(15, 13, 1), # GF(2^4) with t=1
(15, 11, 2), # GF(2^4) with t=2
(15, 9, 1), # GF(2^4) with t=3
(15, 7, 2), # GF(2^4) with t=4
(15, 5, 1), # GF(2^4) with t=5
(15, 3, 2), # GF(2^4) with t=6
(15, 1, 1), # GF(2^4) with t=7
(16, 14, 2), # GF(17) with t=1
(16, 12, 1), # GF(17) with t=2
(16, 10, 2), # GF(17) with t=3
(26, 24, 1), # GF(3^3) with t=1
(26, 22, 2), # GF(3^3) with t=2
(26, 20, 3), # GF(3^3) with t=3
]


Expand Down