Skip to content

Commit

Permalink
improvements to documentation and add more tests for BCH codes
Browse files Browse the repository at this point in the history
  • Loading branch information
Fe-r-oz committed Nov 29, 2024
1 parent 5fcf353 commit 659f22c
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 60 deletions.
111 changes: 63 additions & 48 deletions src/ecc/codes/classical/bch.jl
Original file line number Diff line number Diff line change
@@ -1,67 +1,81 @@
"""The family of Bose–Chaudhuri–Hocquenghem (BCH) codes, as discovered in 1959 by Alexis Hocquenghem [hocquenghem1959codes](@cite), and independently in 1960 by Raj Chandra Bose and D.K. Ray-Chaudhuri [bose1960class](@cite).
The binary parity check matrix can be obtained from the following matrix over GF(2) field elements:
```
1 (α¹)¹ (α¹)² (α¹)³ ... (α¹)ⁿ ⁻ ¹
1 (α³)¹ (α³)² (α³)³ ... (α³)ⁿ ⁻ ¹
1 (α⁵)¹ (α⁵)² (α⁵)³ ... (α⁵)ⁿ ⁻ ¹
. . . . ... .
. . . . ... .
. . . . ... .
1 (α²ᵗ ⁻ ¹)¹ (α²ᵗ ⁻ ¹)² (α²ᵗ ⁻ ¹)³ ... (α²ᵗ ⁻ ¹)ⁿ ⁻ ¹
```
The entries of the matrix are in GF(2ᵐ). Each element in GF(2ᵐ) can be represented by an `m`-tuple (a binary column vector of length `m`). If each entry of `H` is replaced by its corresponding `m`-tuple, we obtain a binary parity check matrix for the code.
The BCH code is cyclic as its generator polynomial, `g(x)` divides `xⁿ - 1`, so `mod (xⁿ - 1, g(x)) = 0`.
You might be interested in consulting [bose1960further](@cite) and [error2024lin](@cite) as well.
The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/q-ary_bch).
"""

abstract type AbstractPolynomialCode <: ClassicalCode end

"""
`BCH(m, t)`
- `m`: The positive integer defining the degree of the finite (Galois) field, GF(2ᵐ).
- `t`: The positive integer specifying the number of correctable errors.
The family of Bose–Chaudhuri–Hocquenghem (BCH) codes, as discovered in 1959 by
Alexis Hocquenghem [hocquenghem1959codes](@cite), and independently in 1960 by
Raj Chandra Bose and D.K. Ray-Chaudhuri [bose1960class](@cite).
The BCH code, denoted as `BCH(m, t)`, is defined by `m`, a positive integer that
specifies the degree of the finite (Galois) field `GF(2ᵐ)`, and `t`, a positive
integer that indicates the number of correctable errors. The binary parity check
matrix can be obtained from the following matrix over ``GF(2ᵐ)`` field elements:
\$\$
\\begin{matrix}
1 & (\\alpha^1)^1 & (\\alpha^1)^2 & (\\alpha^1)^3 & \\dots & (\\alpha^1)^{n-1} \\\\
1 & (\\alpha^3)^1 & (\\alpha^3)^2 & (\\alpha^3)^3 & \\dots & (\\alpha^3)^{n-1} \\\\
1 & (\\alpha^5)^1 & (\\alpha^5)^2 & (\\alpha^5)^3 & \\dots & (\\alpha^5)^{n-1} \\\\
\\vdots & \\vdots & \\vdots & \\vdots & \\ddots & \\vdots \\\\
1 & (\\alpha^{2t-1})^1 & (\\alpha^{2t-1})^2 & (\\alpha^{2t-1})^3 & \\dots & (\\alpha^{2t-1})^{n-1}
\\end{matrix}
\$\$
The entries of the matrix are in `GF(2ᵐ)`. Each element in `GF(2ᵐ)` can be represented
by an `m`-tuple (a binary column vector of length `m`). If each entry of `H` is replaced
by its corresponding `m`-tuple, we obtain a binary parity check matrix for the code.
The BCH code is cyclic as its generator polynomial, `g(x)` divides `xⁿ - 1`, so
`mod (xⁿ - 1, g(x)) = 0`.
You might be interested in consulting [bose1960further](@cite) and [error2024lin](@cite)
as well.
The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/q-ary_bch).
"""
struct BCH <: AbstractPolynomialCode
m::Int
t::Int
m::Int
t::Int
function BCH(m, t)
if m < 3 || t < 0 || t >= 2 ^ (m - 1)
throw(ArgumentError("Invalid parameters: `m` and `t` must be positive. Additionally, ensure `m ≥ 3` and `t < 2ᵐ ⁻ ¹` to obtain a valid code."))
end
m < 3 && throw(ArgumentError("m must be greater than or equal to 3"))
t >= 2^(m - 1) && throw(ArgumentError("t must be less than 2ᵐ ⁻ ¹"))
m * t > 2^m - 1 && throw(ArgumentError("m*t must be greater than or equal to 2ᵐ - 1"))
new(m, t)
end
end

"""
Generator Polynomial of BCH Codes
This function calculates the generator polynomial `g(x)` of a `t`-bit error-correcting BCH code of binary length `n = 2ᵐ - 1`. The binary code is derived from a code over the finite Galois field GF(2).
`generator_polynomial(BCH(m, t))`
- `m`: The positive integer defining the degree of the finite (Galois) field, GF(2ᵐ).
- `t`: The positive integer specifying the number of correctable errors.
This function calculates the generator polynomial `g(x)` of a `t`-bit error-correcting
BCH code of binary length `n = 2ᵐ - 1`. The binary code is derived from a code over the
finite Galois field `GF(2ᵐ)`.
The generator polynomial `g(x)` is the fundamental polynomial used for encoding and decoding BCH codes. It has the following properties:
The generator polynomial `g(x)` is the fundamental polynomial used for encoding and
decoding BCH codes. It has the following properties:
1. Roots: It has `α`, `α²`, `α³`, ..., `α²ᵗ` as its roots, where `α` is a primitive element of the Galois Field GF(2ᵐ).
2. Error Correction: A BCH code with generator polynomial `g(x)` can correct up to `t` errors in a codeword of length `2ᵐ - 1`.
3. Minimal Polynomials: `g(x)` is the least common multiple (LCM) of the minimal polynomials `φᵢ(x)` of `αⁱ` for `i` from `1` to `2ᵗ`.
- Roots: It has `α`, `α²`, `α³`, ..., `α²ᵗ` as its roots, where `α` is a primitive element
of the Galois Field `GF(2ᵐ)`.
- Error Correction: A BCH code with generator polynomial `g(x)` can correct up to `t` errors
in a codeword of length `2ᵐ - 1`.
- Minimal Polynomials: `g(x)` is the least common multiple (LCM) of the minimal polynomials
`φᵢ(x)` of `αⁱ` for `i` from `1` to `2ᵗ`.
Useful definitions and background:
Minimal Polynomial: The minimal polynomial of a field element `α` in GF(2ᵐ) is the polynomial of the lowest degree over GF(2) that has `α` as a root.
Minimal Polynomial: The minimal polynomial of a field element `α` in GF(2ᵐ) is the polynomial
of the lowest degree over `GF(2ᵐ)` that has `α` as a root.
Least Common Multiple (LCM): The LCM of two or more polynomials `fᵢ(x)` is the polynomial with the lowest degree that is a multiple of all `fᵢ(x)`. It ensures that `g(x)` has all the roots of `φᵢ(x)` for `i = 1` to `2ᵗ`.
Least Common Multiple (LCM): The LCM of two or more polynomials `fᵢ(x)` is the polynomial
with the lowest degree that is a multiple of all `fᵢ(x)`. It ensures that `g(x)` has all
the roots of `φᵢ(x)` for `i = 1` to `2ᵗ`.
Conway polynomial: The finite Galois field `GF(2ᵐ)` can have multiple distinct primitive polynomials of the same degree due to existence of several irreducible polynomials of that degree, each generating the field through different roots. Nemo.jl uses [Conway polynomial](https://en.wikipedia.org/wiki/Conway_polynomial_(finite_fields)), a standard way to represent the primitive polynomial for finite Galois fields `GF(pᵐ)` of degree `m`, where `p` is a prime number.
Conway polynomial: The finite Galois field `GF(2ᵐ)` can have multiple distinct primitive
polynomials of the same degree due to existence of several irreducible polynomials of that
degree, each generating the field through different roots.
Nemo.jl uses [Conway polynomial](https://en.wikipedia.org/wiki/Conway_polynomial_(finite_fields)),
a standard way to represent the primitive polynomial for finite Galois fields `GF(pᵐ)` of degree
`m`, where `p` is a prime number.
"""
function generator_polynomial(b::BCH)
GF2ʳ, a = finite_field(2, b.m, "a")
Expand All @@ -70,7 +84,7 @@ function generator_polynomial(b::BCH)
for i in 1:2 * b.t
if i % 2 != 0
push!(minimal_poly, minpoly(GF2x, a ^ i))
end
end
end
gx = lcm(minimal_poly)
return gx
Expand All @@ -81,7 +95,7 @@ function parity_checks(b::BCH)
HField = Matrix{FqFieldElem}(undef, b.t, 2 ^ b.m - 1)
for i in 1:b.t
for j in 1:2 ^ b.m - 1
base = 2 * i - 1
base = 2 * i - 1
HField[i, j] = (a ^ base) ^ (j - 1)
end
end
Expand All @@ -93,12 +107,13 @@ function parity_checks(b::BCH)
t_tuple = Bool[]
for k in 0:b.m - 1
push!(t_tuple, !is_zero(coeff(HField[i, j], k)))
end
end
H[row_start:row_end, j] .= vec(t_tuple')
end
end
end
return H
end

code_n(b::BCH) = 2 ^ b.m - 1

code_k(b::BCH) = 2 ^ b.m - 1 - degree(generator_polynomial(BCH(b.m, b.t)))
75 changes: 63 additions & 12 deletions test/test_ecc_bch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
using LinearAlgebra
using QuantumClifford.ECC
using QuantumClifford.ECC: AbstractECC, BCH, generator_polynomial
using Nemo: ZZ, residue_ring, matrix, finite_field, GF, minpoly, coeff, lcm, FqPolyRingElem, FqFieldElem, is_zero, degree, defining_polynomial, is_irreducible
using Nemo: ZZ, residue_ring, matrix, finite_field, GF, minpoly, coeff, lcm, FqPolyRingElem, FqFieldElem, is_zero, degree, defining_polynomial, is_irreducible

"""
- To prove that `t`-bit error correcting BCH code indeed has minimum distance of at least `2 * t + 1`, it is shown that no `2 * t` or fewer columns of its binary parity check matrix `H` sum to zero. A formal mathematical proof can be found on pages 168 and 169 of Ch6 of Error Control Coding by Lin, Shu and Costello, Daniel.
- The parameter `2 * t + 1` is usually called the designed distance of the `t`-bit error correcting BCH code.
"""
# To prove that t-bit error correcting BCH code indeed has minimum distance
# of at least 2 * t + 1, it is shown that no 2 * t or fewer columns of its
# binary parity check matrix H sum to zero. A formal mathematical proof can
# be found on pages 168 and 169 of Ch6 of Error Control Coding by Lin, Shu
# and Costello, Daniel. The parameter 2 * t + 1 is usually called the designed
# distance of the t-bit error correcting BCH code.
function check_designed_distance(matrix, t)
n_cols = size(matrix, 2)
for num_cols in 1:2 * t
Expand All @@ -23,10 +25,12 @@
end

@testset "Testing properties of BCH codes" begin
m_cases = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
m_cases = [3, 4, 5, 6, 7, 8, 9, 10]
for m in m_cases
n = 2 ^ m - 1
for t in rand(1:m, 2)
lower_bound = round(Int, (2^m - 1) / m)
@test lower_bound < n
for t in lower_bound
H = parity_checks(BCH(m, t))
@test check_designed_distance(H, t) == true
# n - k == degree of generator polynomial, `g(x)` == rank of binary parity check matrix, `H`.
Expand Down Expand Up @@ -60,12 +64,15 @@
@test generator_polynomial(BCH(4, 2)) == x ^ 8 + x ^ 7 + x ^ 6 + x ^ 4 + 1
@test generator_polynomial(BCH(4, 3)) == x ^ 10 + x ^ 8 + x ^ 5 + x ^ 4 + x ^ 2 + x + 1

# Nemo.jl uses [Conway polynomial](https://en.wikipedia.org/wiki/Conway_polynomial_(finite_fields)), a standard way to represent the primitive polynomial for finite Galois fields `GF(pᵐ)` of degree `m`, where `p` is a prime number.
# The `GF(2⁶)`'s Conway polynomial is `p(z) = z⁶ + z⁴ + z³ + z + 1`. In contrast, the polynomial given in https://web.ntpu.edu.tw/~yshan/BCH_code.pdf is `p(z) = z⁶ + z + 1`. Because both polynomials are irreducible, they are also primitive polynomials for `GF(2⁶)`.
# Nemo.jl uses [Conway polynomial](https://en.wikipedia.org/wiki/Conway_polynomial_(finite_fields)),
# a standard way to represent the primitive polynomial for finite Galois fields GF(pᵐ) of degree m,
# where p is a prime number. The GF(2⁶)'s Conway polynomial is p(z) = z⁶ + z⁴ + z³ + z + 1. In contrast,
# the polynomial given in https://web.ntpu.edu.tw/~yshan/BCH_code.pdf is p(z) = z⁶ + z + 1. Because both
# polynomials are irreducible, they are also primitive polynomials for `GF(2⁶)`.

test_cases = [(6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7), (6, 10), (6, 11), (6, 13), (6, 15)]
test_cases = [(6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7)]
@test defining_polynomial(GF2x, GF2⁶) == x ^ 6 + x ^ 4 + x ^ 3 + x + 1
@test is_irreducible(defining_polynomial(GF2x, GF2⁶)) == true
@test is_irreducible(defining_polynomial(GF2x, GF2⁶)) == true
for i in 1:length(test_cases)
m, t = test_cases[i]
if t == 1
Expand All @@ -76,10 +83,54 @@
end
end

results = [57 51 45 39 36 30 24 18 16 10 7]
results = [57 51 45 39 36 30 24]
for (result, (m, t)) in zip(results, test_cases)
@test code_k(BCH(m, t)) == result
@test check_designed_distance(parity_checks(BCH(m, t)), t) == true
end

# Reproduce some results from Table, page 8-9 of https://web.ntpu.edu.tw/~yshan/BCH_code.pdf.
n = 7
@test code_k(BCH(Int(log2(n + 1)), 1)) == 4
n = 15
k_values = [11, 7, 5]
t_values = collect(1:3)
for (t, expected_k) in zip(t_values, k_values)
@test code_k(BCH(Int(log2(n + 1)), t)) == expected_k
end
n = 31
k_values = [26, 21, 16, 11]
t_values = collect(1:3)
push!(t_values, 5)
for (t, expected_k) in zip(t_values, k_values)
@test code_k(BCH(Int(log2(n + 1)), t)) == expected_k
end
n = 63
k_values = collect(57:-6:39)
push!(k_values, 36)
push!(k_values, 30)
push!(k_values, 24)
t_values = collect(1:7)
for (t, expected_k) in zip(t_values, k_values)
@test code_k(BCH(Int(log2(n + 1)), t)) == expected_k
end
n = 127
k_values = collect(120:-7:36)
t_values = collect(1:7)
push!(t_values, 9)
push!(t_values, 10)
push!(t_values, 11)
push!(t_values, 13)
push!(t_values, 14)
for (t, expected_k) in zip(t_values, k_values)
@test code_k(BCH(Int(log2(n + 1)), t)) == expected_k
end
n = 1023
k_values = collect(1013:-10:863)
push!(k_values, 858)
t_values = collect(1:17)
for (t, expected_k) in zip(t_values, k_values)
@test code_k(BCH(Int(log2(n + 1)), t)) == expected_k
end
end
end
5 changes: 5 additions & 0 deletions test/test_ecc_throws.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
@test_throws ArgumentError ReedMuller(4, 2)

@test_throws ArgumentError BCH(2, 2)
@test_throws ArgumentError BCH(-2, 2)
@test_throws ArgumentError BCH(2, -3)
@test_throws ArgumentError BCH(3, 3)
@test_throws ArgumentError BCH(3, 4)
@test_throws ArgumentError BCH(4, 4)
@test_throws ArgumentError BCH(4, 100)

@test_throws ArgumentError RecursiveReedMuller(-1, 3)
@test_throws ArgumentError RecursiveReedMuller(1, 0)
Expand Down

0 comments on commit 659f22c

Please sign in to comment.