Skip to content

Commit 44f618a

Browse files
Fe-r-ozKrastanov
andauthored
Adding classical Bose–Chaudhuri–Hocquenghem code to ECC module (#263)
Co-authored-by: Stefan Krastanov <[email protected]>
1 parent 2e80e87 commit 44f618a

File tree

8 files changed

+246
-4
lines changed

8 files changed

+246
-4
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ LDPCDecoders = "0.3.1"
5353
LinearAlgebra = "1.9"
5454
MacroTools = "0.5.9"
5555
Makie = "0.20"
56-
Nemo = "0.38, 0.39, 0.40, 0.41, 0.42, 0.43, 0.44, 0.45"
56+
Nemo = "0.42, 0.43, 0.44, 0.45"
5757
Plots = "1.38.0"
5858
PrecompileTools = "1.2"
5959
PyQDecoders = "0.2.0"

docs/src/references.bib

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,41 @@ @book{djordjevic2021quantum
341341
year={2021},
342342
publisher={Academic Press}
343343
}
344+
345+
@article{hocquenghem1959codes,
346+
title={Codes correcteurs d'erreurs},
347+
author={Hocquenghem, Alexis},
348+
journal={Chiffers},
349+
volume={2},
350+
pages={147--156},
351+
year={1959}
352+
}
353+
354+
@article{bose1960class,
355+
title={On a class of error correcting binary group codes},
356+
author={Bose, Raj Chandra and Ray-Chaudhuri, Dwijendra K},
357+
journal={Information and control},
358+
volume={3},
359+
number={1},
360+
pages={68--79},
361+
year={1960},
362+
publisher={Elsevier}
363+
}
364+
365+
@article{bose1960further,
366+
title={Further results on error correcting binary group codes},
367+
author={Bose, Raj Chandra and Ray-Chaudhuri, Dwijendra K},
368+
journal={Information and Control},
369+
volume={3},
370+
number={3},
371+
pages={279--290},
372+
year={1960},
373+
publisher={Elsevier}
374+
}
375+
376+
@book{error2024lin,
377+
title={Error Control Coding},
378+
author={Lin, Shu and Costello, Daniel},
379+
year={2024},
380+
publisher={Pearson}
381+
}

docs/src/references.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ For classical code construction routines:
4343
- [raaphorst2003reed](@cite)
4444
- [abbe2020reed](@cite)
4545
- [djordjevic2021quantum](@cite)
46+
- [hocquenghem1959codes](@cite)
47+
- [bose1960class](@cite)
48+
- [bose1960further](@cite)
49+
- [error2024lin](@cite)
4650

4751
# References
4852

src/ecc/ECC.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ using DocStringExtensions
88
using Combinatorics: combinations
99
using SparseArrays: sparse
1010
using Statistics: std
11-
using Nemo: ZZ, residue_ring, matrix
11+
using Nemo: ZZ, residue_ring, matrix, finite_field, GF, minpoly, coeff, lcm, FqPolyRingElem, FqFieldElem, is_zero, degree, defining_polynomial, is_irreducible
1212

1313
abstract type AbstractECC end
1414

@@ -57,6 +57,13 @@ function iscss(c::AbstractECC)
5757
return iscss(typeof(c))
5858
end
5959

60+
"""
61+
Generator Polynomial `g(x)`
62+
63+
In a [polynomial code](https://en.wikipedia.org/wiki/Polynomial_code), the generator polynomial `g(x)` is a polynomial of the minimal degree over a finite field `F`. The set of valid codewords in the code consists of all polynomials that are divisible by `g(x)` without remainder.
64+
"""
65+
function generator_polynomial end
66+
6067
parity_checks(s::Stabilizer) = s
6168
Stabilizer(c::AbstractECC) = parity_checks(c)
6269
MixedDestabilizer(c::AbstractECC; kwarg...) = MixedDestabilizer(Stabilizer(c); kwarg...)
@@ -347,4 +354,5 @@ include("codes/toric.jl")
347354
include("codes/gottesman.jl")
348355
include("codes/surface.jl")
349356
include("codes/classical/reedmuller.jl")
357+
include("codes/classical/bch.jl")
350358
end #module

src/ecc/codes/classical/bch.jl

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""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).
2+
3+
The binary parity check matrix can be obtained from the following matrix over GF(2) field elements:
4+
5+
```
6+
1 (α¹)¹ (α¹)² (α¹)³ ... (α¹)ⁿ ⁻ ¹
7+
1 (α³)¹ (α³)² (α³)³ ... (α³)ⁿ ⁻ ¹
8+
1 (α⁵)¹ (α⁵)² (α⁵)³ ... (α⁵)ⁿ ⁻ ¹
9+
. . . . ... .
10+
. . . . ... .
11+
. . . . ... .
12+
1 (α²ᵗ ⁻ ¹)¹ (α²ᵗ ⁻ ¹)² (α²ᵗ ⁻ ¹)³ ... (α²ᵗ ⁻ ¹)ⁿ ⁻ ¹
13+
```
14+
15+
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.
16+
17+
The BCH code is cyclic as its generator polynomial, `g(x)` divides `xⁿ - 1`, so `mod (xⁿ - 1, g(x)) = 0`.
18+
19+
You might be interested in consulting [bose1960further](@cite) and [error2024lin](@cite) as well.
20+
21+
The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/q-ary_bch).
22+
"""
23+
24+
abstract type AbstractPolynomialCode <: ClassicalCode end
25+
26+
"""
27+
`BCH(m, t)`
28+
- `m`: The positive integer defining the degree of the finite (Galois) field, GF(2ᵐ).
29+
- `t`: The positive integer specifying the number of correctable errors.
30+
"""
31+
struct BCH <: AbstractPolynomialCode
32+
m::Int
33+
t::Int
34+
function BCH(m, t)
35+
if m < 3 || t < 0 || t >= 2 ^ (m - 1)
36+
throw(ArgumentError("Invalid parameters: `m` and `t` must be positive. Additionally, ensure `m ≥ 3` and `t < 2ᵐ ⁻ ¹` to obtain a valid code."))
37+
end
38+
new(m, t)
39+
end
40+
end
41+
42+
"""
43+
Generator Polynomial of BCH Codes
44+
45+
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).
46+
47+
`generator_polynomial(BCH(m, t))`
48+
49+
- `m`: The positive integer defining the degree of the finite (Galois) field, GF(2ᵐ).
50+
- `t`: The positive integer specifying the number of correctable errors.
51+
52+
The generator polynomial `g(x)` is the fundamental polynomial used for encoding and decoding BCH codes. It has the following properties:
53+
54+
1. Roots: It has `α`, `α²`, `α³`, ..., `α²ᵗ` as its roots, where `α` is a primitive element of the Galois Field GF(2ᵐ).
55+
2. Error Correction: A BCH code with generator polynomial `g(x)` can correct up to `t` errors in a codeword of length `2ᵐ - 1`.
56+
3. Minimal Polynomials: `g(x)` is the least common multiple (LCM) of the minimal polynomials `φᵢ(x)` of `αⁱ` for `i` from `1` to `2ᵗ`.
57+
58+
Useful definitions and background:
59+
60+
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.
61+
62+
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ᵗ`.
63+
64+
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.
65+
"""
66+
function generator_polynomial(b::BCH)
67+
GF2ʳ, a = finite_field(2, b.m, "a")
68+
GF2x, x = GF(2)["x"]
69+
minimal_poly = FqPolyRingElem[]
70+
for i in 1:2 * b.t
71+
if i % 2 != 0
72+
push!(minimal_poly, minpoly(GF2x, a ^ i))
73+
end
74+
end
75+
gx = lcm(minimal_poly)
76+
return gx
77+
end
78+
79+
function parity_checks(b::BCH)
80+
GF2ʳ, a = finite_field(2, b.m, "a")
81+
HField = Matrix{FqFieldElem}(undef, b.t, 2 ^ b.m - 1)
82+
for i in 1:b.t
83+
for j in 1:2 ^ b.m - 1
84+
base = 2 * i - 1
85+
HField[i, j] = (a ^ base) ^ (j - 1)
86+
end
87+
end
88+
H = Matrix{Bool}(undef, b.m * b.t, 2 ^ b.m - 1)
89+
for i in 1:b.t
90+
row_start = (i - 1) * b.m + 1
91+
row_end = row_start + b.m - 1
92+
for j in 1:2 ^ b.m - 1
93+
t_tuple = Bool[]
94+
for k in 0:b.m - 1
95+
push!(t_tuple, !is_zero(coeff(HField[i, j], k)))
96+
end
97+
H[row_start:row_end, j] .= vec(t_tuple')
98+
end
99+
end
100+
return H
101+
end
102+
103+
code_n(b::BCH) = 2 ^ b.m - 1
104+
code_k(b::BCH) = 2 ^ b.m - 1 - degree(generator_polynomial(BCH(b.m, b.t)))

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ end
6464
@doset "ecc_encoding"
6565
@doset "ecc_gottesman"
6666
@doset "ecc_reedmuller"
67+
@doset "ecc_bch"
6768
@doset "ecc_syndromes"
6869
@doset "ecc_throws"
6970
@doset "precompile"

test/test_ecc_bch.jl

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using Test
2+
using LinearAlgebra
3+
using QuantumClifford
4+
using QuantumClifford.ECC
5+
using QuantumClifford.ECC: AbstractECC, BCH, generator_polynomial
6+
using Nemo: ZZ, residue_ring, matrix, finite_field, GF, minpoly, coeff, lcm, FqPolyRingElem, FqFieldElem, is_zero, degree, defining_polynomial, is_irreducible
7+
8+
"""
9+
- 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.
10+
- The parameter `2 * t + 1` is usually called the designed distance of the `t`-bit error correcting BCH code.
11+
"""
12+
function check_designed_distance(matrix, t)
13+
n_cols = size(matrix, 2)
14+
for num_cols in 1:2 * t
15+
for i in 1:n_cols - num_cols + 1
16+
combo = matrix[:, i:(i + num_cols - 1)]
17+
sum_cols = sum(combo, dims = 2)
18+
if all(sum_cols .== 0)
19+
return false # Minimum distance is not greater than `2 * t`.
20+
end
21+
end
22+
end
23+
return true # Minimum distance is at least `2 * t + 1`.
24+
end
25+
26+
@testset "Testing properties of BCH codes" begin
27+
m_cases = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
28+
for m in m_cases
29+
n = 2 ^ m - 1
30+
for t in rand(1:m, 2)
31+
H = parity_checks(BCH(m, t))
32+
@test check_designed_distance(H, t) == true
33+
# n - k == degree of generator polynomial, `g(x)` == rank of binary parity check matrix, `H`.
34+
mat = matrix(GF(2), parity_checks(BCH(m, t)))
35+
computed_rank = rank(mat)
36+
@test computed_rank == degree(generator_polynomial(BCH(m, t)))
37+
@test code_k(BCH(m, t)) == n - degree(generator_polynomial(BCH(m, t)))
38+
# BCH code is cyclic as its generator polynomial, `g(x)` divides `xⁿ - 1`, so `mod (xⁿ - 1, g(x))` = 0.
39+
gx = generator_polynomial(BCH(m, t))
40+
GF2x, x = GF(2)["x"]
41+
@test mod(x ^ n - 1, gx) == 0
42+
end
43+
end
44+
45+
#example taken from Ch6 of Error Control Coding by Lin, Shu and Costello, Daniel
46+
@test parity_checks(BCH(4, 2)) == [1 0 0 0 1 0 0 1 1 0 1 0 1 1 1;
47+
0 1 0 0 1 1 0 1 0 1 1 1 1 0 0;
48+
0 0 1 0 0 1 1 0 1 0 1 1 1 1 0;
49+
0 0 0 1 0 0 1 1 0 1 0 1 1 1 1;
50+
1 0 0 0 1 1 0 0 0 1 1 0 0 0 1;
51+
0 0 0 1 1 0 0 0 1 1 0 0 0 1 1;
52+
0 0 1 0 1 0 0 1 0 1 0 0 1 0 1;
53+
0 1 1 1 1 0 1 1 1 1 0 1 1 1 1]
54+
55+
# Examples taken from https://web.ntpu.edu.tw/~yshan/BCH_code.pdf.
56+
GF2x, x = GF(2)["x"]
57+
GF2⁴, a = finite_field(2, 4, "a")
58+
GF2⁶, b = finite_field(2, 6, "b")
59+
@test defining_polynomial(GF2x, GF2⁴) == x ^ 4 + x + 1
60+
@test is_irreducible(defining_polynomial(GF2x, GF2⁴)) == true
61+
@test generator_polynomial(BCH(4, 2)) == x ^ 8 + x ^ 7 + x ^ 6 + x ^ 4 + 1
62+
@test generator_polynomial(BCH(4, 3)) == x ^ 10 + x ^ 8 + x ^ 5 + x ^ 4 + x ^ 2 + x + 1
63+
64+
# 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.
65+
# 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⁶)`.
66+
67+
test_cases = [(6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7), (6, 10), (6, 11), (6, 13), (6, 15)]
68+
@test defining_polynomial(GF2x, GF2⁶) == x ^ 6 + x ^ 4 + x ^ 3 + x + 1
69+
@test is_irreducible(defining_polynomial(GF2x, GF2⁶)) == true
70+
for i in 1:length(test_cases)
71+
m, t = test_cases[i]
72+
if t == 1
73+
@test generator_polynomial(BCH(m, t)) == defining_polynomial(GF2x, GF2⁶)
74+
else
75+
prev_t = test_cases[i - 1][2]
76+
@test generator_polynomial(BCH(m, t)) == generator_polynomial(BCH(m, prev_t)) * minpoly(GF2x, b ^ (t + prev_t - (t - prev_t - 1)))
77+
end
78+
end
79+
80+
results = [57 51 45 39 36 30 24 18 16 10 7]
81+
for (result, (m, t)) in zip(results, test_cases)
82+
@test code_k(BCH(m, t)) == result
83+
@test check_designed_distance(parity_checks(BCH(m, t)), t) == true
84+
end
85+
end

test/test_ecc_throws.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using Test
22
using QuantumClifford
3-
using QuantumClifford.ECC
4-
using QuantumClifford.ECC: ReedMuller
3+
using QuantumClifford.ECC: ReedMuller, BCH
54

65
@test_throws ArgumentError ReedMuller(-1, 3)
76
@test_throws ArgumentError ReedMuller(1, 0)
7+
8+
@test_throws ArgumentError BCH(2, 2)
9+
@test_throws ArgumentError BCH(3, 4)

0 commit comments

Comments
 (0)