diff --git a/src/ecc/codes/classical/bch.jl b/src/ecc/codes/classical/bch.jl index 70580a909..6cd6fce5c 100644 --- a/src/ecc/codes/classical/bch.jl +++ b/src/ecc/codes/classical/bch.jl @@ -1,40 +1,44 @@ -"""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 @@ -42,26 +46,36 @@ 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") @@ -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 @@ -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 @@ -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))) diff --git a/test/test_ecc_bch.jl b/test/test_ecc_bch.jl index 10cbece67..403a29d07 100644 --- a/test/test_ecc_bch.jl +++ b/test/test_ecc_bch.jl @@ -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 @@ -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`. @@ -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 @@ -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 diff --git a/test/test_ecc_throws.jl b/test/test_ecc_throws.jl index 53055f8cf..1c6fcff1d 100644 --- a/test/test_ecc_throws.jl +++ b/test/test_ecc_throws.jl @@ -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)