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

Bug fix to the parity_checks(ReedMuller(r, m)) along with RecursiveReedMuller code for cross-reference #277

Merged
merged 33 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
635f43c
Significantly polishing PR #244, Adding more details from literature
May 18, 2024
3300ac0
Setting up for reviw: Adding test throw when r > m
May 18, 2024
710f7ab
improving codepatch to more than 82% by testing all code
May 18, 2024
12b7f96
Merge branch 'QuantumSavory:master' into PolishRM
Fe-r-oz Jun 15, 2024
01eae9f
applying fix, introducing RecursiveReedMuler code
Jun 15, 2024
c1803ad
applying formatting
Jun 15, 2024
ddcb7db
formatting
Jun 15, 2024
fa5b3db
Testing RREFs of parity check matrices
Jun 15, 2024
65a1322
code review suggestions
Jun 22, 2024
8d91101
Minor fix
Jun 22, 2024
353e41a
adding code review suggestions
Jun 22, 2024
28494f9
remove RowEchelon
Jun 22, 2024
6d52a64
Adding CHANGELOG, removing tractibility constraint, improving throw e…
Jun 23, 2024
80007be
Improving CHANGELOG
Jun 23, 2024
093d5bb
Adding **fix** in CHANGELOG
Jun 23, 2024
736207a
create separate entry for CHANGELOG
Jun 23, 2024
bcb946e
Merge branch 'master' into PolishRM
Fe-r-oz Jun 23, 2024
6a66ce8
Minor Formatting
Jun 23, 2024
7925ec8
Merge branch 'PolishRM' of https://github.com/Fe-r-oz/QuantumClifford…
Jun 23, 2024
a10e3c9
From abstract QQ to realistic GF(2)
Jun 25, 2024
65d1649
Merge branch 'master' into PolishRM
Fe-r-oz Jun 29, 2024
c17ab4d
Fix CHANGELOG
Jun 29, 2024
9d33424
Polishing up and Formatting
Jun 29, 2024
740e200
Setting up for review
Jun 29, 2024
fea7d7f
Merge branch 'master' into PolishRM
Fe-r-oz Jul 9, 2024
56654a4
fix CHANGELOG
Jul 9, 2024
691e3f2
Merge branch 'master' into PolishRM
Fe-r-oz Jul 20, 2024
155c503
Merge branch 'master' into PolishRM
Fe-r-oz Jul 26, 2024
49feb51
Merge branch 'master' into PolishRM
Fe-r-oz Aug 4, 2024
34be478
Update src/ecc/codes/classical/recursivereedmuller.jl
Fe-r-oz Aug 11, 2024
7ed1bcd
Merge branch 'QuantumSavory:master' into PolishRM
Fe-r-oz Aug 11, 2024
61157fd
adding codereview suggestions
Fe-r-oz Aug 11, 2024
0623a9e
minor edits to changelog
Krastanov Sep 14, 2024
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

# News

## dev

- **(fix)** Bug fix to the `parity_checks(ReedMuller(r, m))` of classical Reed-Muller code (it was returning generator matrix).
- `RecursiveReedMuller` code implementation as an alternative implementation of `ReedMuller`.

## v0.9.9 - 2024-08-05

- `inv` is implemented for all Clifford operator types (symbolic, dense, sparse).
Expand Down
7 changes: 6 additions & 1 deletion src/ecc/ECC.jl
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
module ECC

using LinearAlgebra
using LinearAlgebra: I
Fe-r-oz marked this conversation as resolved.
Show resolved Hide resolved
using QuantumClifford
using QuantumClifford: AbstractOperation, AbstractStabilizer, Stabilizer
import QuantumClifford: Stabilizer, MixedDestabilizer, nqubits
using DocStringExtensions
using Combinatorics: combinations
using SparseArrays: sparse
using Statistics: std
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, echelon_form

abstract type AbstractECC end

Expand Down Expand Up @@ -70,6 +71,9 @@ In a [polynomial code](https://en.wikipedia.org/wiki/Polynomial_code), the gener
"""
function generator_polynomial end

"""The generator matrix of a code."""
function generator end
Fe-r-oz marked this conversation as resolved.
Show resolved Hide resolved

parity_checks(s::Stabilizer) = s
Stabilizer(c::AbstractECC) = parity_checks(c)
MixedDestabilizer(c::AbstractECC; kwarg...) = MixedDestabilizer(Stabilizer(c); kwarg...)
Expand Down Expand Up @@ -363,4 +367,5 @@ include("codes/concat.jl")
include("codes/random_circuit.jl")
include("codes/classical/reedmuller.jl")
include("codes/classical/bch.jl")
include("codes/classical/recursivereedmuller.jl")
end #module
64 changes: 64 additions & 0 deletions src/ecc/codes/classical/recursivereedmuller.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
Fe-r-oz marked this conversation as resolved.
Show resolved Hide resolved
A construction of the Reed-Muller class of codes using the recursive definition.

The Plotkin `(u, u + v)` construction defines a recursive relation between generator matrices of Reed-Muller `(RM)` codes [abbe2020reed](@cite). To derive the generator matrix `G(m, r)` for `RM(r, m)`, the generator matrices of lower-order codes are utilized:
- `G(r - 1, m - 1)`: Generator matrix of `RM(r - 1, m - 1)`
- `G(r, m - 1)`: Generator matrix of `RM(r, m - 1)`

The generator matrix `G(m, r)` of `RM(m, r)` is formulated as follows in matrix notation:

```math
G(m, r) = \begin{bmatrix}
G(r, m - 1) & G(r, m - 1) \\
0 & G(r - 1, m - 1)
\end{bmatrix}
```

Here, the matrix 0 denotes an all-zero matrix with dimensions matching `G(r - 1, m - 1)`. This recursive approach facilitates the construction of higher-order Reed-Muller codes based on the generator matrices of lower-order codes.

In addition, the dimension of `RM(m - r - 1, m)` equals the dimension of the dual of `RM(r, m)`. Thus, `RM(m - r - 1, m) = RM(r, m)^⊥` shows that the [dual code](https://en.wikipedia.org/wiki/Dual_code) of `RM(r, m)` is `RM(m − r − 1, m)`, indicating the parity check matrix of `RM(r, m)` is the generator matrix for `RM(m - r - 1, m)`.
Fe-r-oz marked this conversation as resolved.
Show resolved Hide resolved

See also: `ReedMuller`
"""
struct RecursiveReedMuller <: ClassicalCode
r::Int
m::Int

function RecursiveReedMuller(r, m)
if r < 0 || r > m
throw(ArgumentError("Invalid parameters: r must be non-negative and r ≤ m in order to valid code."))
end
new(r, m)
end
end

function _recursiveReedMuller(r::Int, m::Int)
if r == 1 && m == 1
return Matrix{Int}([1 1; 0 1])
elseif r == m
return Matrix{Int}(I, 2^m, 2^m)
elseif r == 0
return Matrix{Int}(ones(1, 2^m))
else
Gᵣₘ₋₁ = _recursiveReedMuller(r, m - 1)
Gᵣ₋₁ₘ₋₁ = _recursiveReedMuller(r - 1, m - 1)
return vcat(hcat(Gᵣₘ₋₁, Gᵣₘ₋₁), hcat(zeros(Int, size(Gᵣ₋₁ₘ₋₁)...), Gᵣ₋₁ₘ₋₁))
end
end

function generator(c::RecursiveReedMuller)
return _recursiveReedMuller(c.r, c.m)
end

function parity_checks(c::RecursiveReedMuller)
H = generator(RecursiveReedMuller(c.m - c.r - 1, c.m))
return H
end

code_n(c::RecursiveReedMuller) = 2 ^ c.m

code_k(c::RecursiveReedMuller) = sum(binomial.(c.m, 0:c.r))

distance(c::RecursiveReedMuller) = 2 ^ (c.m - c.r)

rate(c::RecursiveReedMuller) = code_k(c::RecursiveReedMuller) / code_n(c::RecursiveReedMuller)
52 changes: 37 additions & 15 deletions src/ecc/codes/classical/reedmuller.jl
Original file line number Diff line number Diff line change
@@ -1,38 +1,60 @@
"""The family of Reed-Muller codes, as discovered by Muller in his 1954 paper [muller1954application](@cite) and Reed who proposed the first efficient decoding algorithm [reed1954class](@cite).

Let `m` be a positive integer and `r` a nonnegative integer with `r ≤ m`. These linear codes, denoted as `RM(r, m)`, have order `r` (where `0 ≤ r ≤ m`) and codeword length `n` of `2ᵐ`.

Two special cases of generator(RM(r, m)) exist:
1. `generator(RM(0, m))`: This is the `0ᵗʰ`-order `RM` code, similar to the binary repetition code with length `2ᵐ`. It's characterized by a single basis vector containing all ones.
2. `generator(RM(m, m))`: This is the `mᵗʰ`-order `RM` code. It encompasses the entire field `F(2ᵐ)`, representing all possible binary strings of length `2ᵐ`.

You might be interested in consulting [raaphorst2003reed](@cite), [abbe2020reed](@cite), and [djordjevic2021quantum](@cite) as well.

The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/reed_muller)
"""
The dimension of `RM(m - r - 1, m)` equals the dimension of the dual of `RM(r, m)`. Thus, `RM(m - r - 1, m) = RM(r, m)^⊥` shows that the [dual code](https://en.wikipedia.org/wiki/Dual_code) of `RM(r, m)` is `RM(m − r − 1, m)`, indicating the parity check matrix of `RM(r, m)` is the generator matrix for `RM(m - r - 1, m)`.

abstract type ClassicalCode end
The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/reed_muller).
Fe-r-oz marked this conversation as resolved.
Show resolved Hide resolved

See also: `RecursiveReedMuller`
"""
struct ReedMuller <: ClassicalCode
r::Int
m::Int

function ReedMuller(r, m)
if r < 0 || m < 1 || m >= 11
throw(ArgumentError("Invalid parameters: r must be non-negative and m must be positive and < 11 in order to obtain a valid code and to remain tractable"))
if r < 0 || r > m
throw(ArgumentError("Invalid parameters: r must be non-negative and r ≤ m in order to valid code."))
end
new(r, m)
end
end

function variables_xi(m, i)
return repeat([fill(1, 2^(m - i - 1)); fill(0, 2^(m - i - 1))], outer = 2^i)
function _variablesₓᵢ_rm(m, i)
return repeat([fill(1, 2 ^ (m - i - 1)); fill(0, 2 ^ (m - i - 1))], outer = 2 ^ i)
end

function vmult(vecs...)
function _vmult_rm(vecs...)
return [reduce(*, a, init=1) for a in zip(vecs...)]
end

function parity_checks(c::ReedMuller)
r=c.r
m=c.m
xi = [variables_xi(m, i) for i in 0:m - 1]
row_matrices = [reduce(vmult, [xi[i + 1] for i in S], init = ones(Int, 2^m)) for s in 0:r for S in combinations(0:m - 1, s)]
function generator(c::ReedMuller)
r = c.r
m = c.m
xᵢ = [_variablesₓᵢ_rm(m, i) for i in 0:m - 1]
row_matrices = [reduce(_vmult_rm, [xᵢ[i + 1] for i in S], init = ones(Int, 2 ^ m)) for s in 0:r for S in combinations(0:m - 1, s)]
rows = length(row_matrices)
cols = length(row_matrices[1])
H = reshape(vcat(row_matrices...), cols, rows)'
end
G = reshape(vcat(row_matrices...), cols, rows)'
G = Matrix{Bool}(G)
return G
end

function parity_checks(c::ReedMuller)
H = generator(ReedMuller(c.m - c.r - 1, c.m))
return H
end

code_n(c::ReedMuller) = 2 ^ c.m

code_k(c::ReedMuller) = sum(binomial.(c.m, 0:c.r))

distance(c::ReedMuller) = 2 ^ (c.m - c.r)

rate(c::ReedMuller) = code_k(c::ReedMuller) / code_n(c::ReedMuller)
164 changes: 113 additions & 51 deletions test/test_ecc_reedmuller.jl
Original file line number Diff line number Diff line change
@@ -1,68 +1,130 @@
@testitem "Reed-Muller" begin
using Nemo
using Combinatorics
using Test
using Nemo: echelon_form, matrix, GF
using LinearAlgebra
using QuantumClifford
using QuantumClifford.ECC
using QuantumClifford.ECC: AbstractECC, ReedMuller
using QuantumClifford.ECC: AbstractECC, ReedMuller, generator, RecursiveReedMuller

function binomial_coeff_sum(r, m)
total = 0
for i in 0:r
total += length(combinations(1:m, i))
function designed_distance(matrix, m, r)
distance = 2 ^ (m - r)
for row in eachrow(matrix)
count = sum(row)
if count < distance
return false
end
end
return total
return true
end

@testset "Test RM(r, m) Matrix Rank" begin
for m in 2:5
for r in 0:m - 1
H = parity_checks(ReedMuller(r, m))
mat = Nemo.matrix(Nemo.GF(2), H)
# Generate binary matrix representing all possible binary strings of length `2ᵐ` encompassing the entire field `F(2ᵐ)`.
function check_RM_m_m(m::Int)
n = 2^m
matrix = Matrix{Bool}(undef, n, n)
for i in 0:(n-1)
for j in 0:(n-1)
matrix[i+1, j+1] = ((i & j) == j)
end
end
return matrix
end

@testset "Test RM(r, m) properties" begin
for m in 3:10
for r in 3:m-1
H = generator(ReedMuller(r, m))
mat = matrix(GF(2), H)
computed_rank = LinearAlgebra.rank(mat)
expected_rank = binomial_coeff_sum(r, m)
expected_rank = sum(binomial.(m, 0:r))
@test computed_rank == expected_rank
@test designed_distance(H, m, r) == true
@test rate(ReedMuller(r, m)) == sum(binomial.(m, 0:r)) / 2 ^ m == rate(RecursiveReedMuller(r, m))
@test code_n(ReedMuller(r, m)) == 2 ^ m == code_n(RecursiveReedMuller(r, m))
@test code_k(ReedMuller(r, m)) == sum(binomial.(m, 0:r)) == code_k(RecursiveReedMuller(r, m))
@test distance(ReedMuller(r, m)) == 2 ^ (m - r) == distance(RecursiveReedMuller(r, m))
H₁ = generator(RecursiveReedMuller(r, m))
# generator(RecursiveReedMuller(r, m)) is canonically equivalent to the generator(ReedMuller(r, m)) under reduced row echelon form.
@test echelon_form(matrix(GF(2), Matrix{Int64}(H))) == echelon_form(matrix(GF(2), Matrix{Int64}(H₁)))
H = parity_checks(ReedMuller(r, m))
H₁ = parity_checks(RecursiveReedMuller(r, m))
# parity_checks(RecursiveReedMuller(r, m)) is canonically equivalent to the parity_checks(ReedMuller(r, m)) under reduced row echelon form.
@test echelon_form(matrix(GF(2), Matrix{Int64}(H))) == echelon_form(matrix(GF(2), Matrix{Int64}(H₁)))
# dim(ReedMuller(m - r - 1, m)) = dim(ReedMuller(r, m)^⊥).
# ReedMuller(m - r - 1, m) = ReedMuller(r, m)^⊥ ∴ parity check matrix (H) of ReedMuller(r, m) is the generator matrix (G) for ReedMuller(m - r - 1, m).
H₁ = parity_checks(ReedMuller(m - r - 1, m))
G₂ = generator(ReedMuller(r, m))
@test size(H₁) == size(G₂)
@test echelon_form(matrix(GF(2), Matrix{Int64}(H₁))) == echelon_form(matrix(GF(2), Matrix{Int64}(G₂)))
G₃ = generator(ReedMuller(m - r - 1, m))
H₄ = parity_checks(ReedMuller(r, m))
@test size(G₃) == size(H₄)
# dim(RecursiveReedMuller(m - r - 1, m)) = dim(RecursiveReedMuller(r, m)^⊥).
# RecursiveReedMuller(m - r - 1, m) = RecursiveReedMuller(r, m)^⊥ ∴ parity check matrix (H) of RecursiveReedMuller(r, m) is the generator matrix (G) for RecursiveReedMuller(m - r - 1, m).
H₁ = parity_checks(RecursiveReedMuller(m - r - 1, m))
G₂ = generator(RecursiveReedMuller(r, m))
@test echelon_form(matrix(GF(2), Matrix{Int64}(H₄))) == echelon_form(matrix(GF(2), Matrix{Int64}(G₃)))
@test echelon_form(matrix(GF(2), Matrix{Int64}(H₁))) == echelon_form(matrix(GF(2), Matrix{Int64}(G₂)))
G₃ = generator(RecursiveReedMuller(m - r - 1, m))
H₄ = parity_checks(RecursiveReedMuller(r, m))
@test size(G₃) == size(H₄)
@test echelon_form(matrix(GF(2), Matrix{Int64}(H₄))) == echelon_form(matrix(GF(2), Matrix{Int64}(G₃)))
end
end
end

@testset "Testing common examples of RM(r,m) codes [raaphorst2003reed](@cite), [djordjevic2021quantum](@cite), [abbe2020reed](@cite)" begin
@testset "Test special case 1: generator(RM(0, m))" begin
for m in 3:10
H = generator(ReedMuller(0, m))
expected = ones(Int64, 1, 2^m)
@test H == expected
end
end

#RM(0,3)
@test parity_checks(ReedMuller(0,3)) == [1 1 1 1 1 1 1 1]
@testset "Test special case 2: generator(RM(m, m))" begin
for m in 3:10
H = generator(ReedMuller(m, m))
expected = check_RM_m_m(m)
@test echelon_form(matrix(GF(2), Matrix{Int64}(H))) == echelon_form(matrix(GF(2), expected))
end
end

#RM(1,3)
@test parity_checks(ReedMuller(1,3)) == [1 1 1 1 1 1 1 1;
1 1 1 1 0 0 0 0;
1 1 0 0 1 1 0 0;
1 0 1 0 1 0 1 0]
#RM(2,3)
@test parity_checks(ReedMuller(2,3)) == [1 1 1 1 1 1 1 1;
1 1 1 1 0 0 0 0;
1 1 0 0 1 1 0 0;
1 0 1 0 1 0 1 0;
1 1 0 0 0 0 0 0;
1 0 1 0 0 0 0 0;
1 0 0 0 1 0 0 0]
#RM(3,3)
@test parity_checks(ReedMuller(3,3)) == [1 1 1 1 1 1 1 1;
1 1 1 1 0 0 0 0;
1 1 0 0 1 1 0 0;
1 0 1 0 1 0 1 0;
1 1 0 0 0 0 0 0;
1 0 1 0 0 0 0 0;
1 0 0 0 1 0 0 0;
1 0 0 0 0 0 0 0]
#RM(2,4)
@test parity_checks(ReedMuller(2,4)) == [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0;
1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0;
1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0;
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0;
1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0;
1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0;
1 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0;
1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0;
1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0;
1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0]
@testset "Test common examples of RM(r,m) codes" begin
# Examples from [raaphorst2003reed](@cite), [djordjevic2021quantum](@cite), [abbe2020reed](@cite).
# RM(0,3)
@test generator(ReedMuller(0,3)) == [1 1 1 1 1 1 1 1]
# RM(1,3)
@test generator(ReedMuller(1,3)) == [1 1 1 1 1 1 1 1;
1 1 1 1 0 0 0 0;
1 1 0 0 1 1 0 0;
1 0 1 0 1 0 1 0]
# RM(2,3)
@test generator(ReedMuller(2,3)) == [1 1 1 1 1 1 1 1;
1 1 1 1 0 0 0 0;
1 1 0 0 1 1 0 0;
1 0 1 0 1 0 1 0;
1 1 0 0 0 0 0 0;
1 0 1 0 0 0 0 0;
1 0 0 0 1 0 0 0]
# RM(3,3)
@test generator(ReedMuller(3,3)) == [1 1 1 1 1 1 1 1;
1 1 1 1 0 0 0 0;
1 1 0 0 1 1 0 0;
1 0 1 0 1 0 1 0;
1 1 0 0 0 0 0 0;
1 0 1 0 0 0 0 0;
1 0 0 0 1 0 0 0;
1 0 0 0 0 0 0 0]
# RM(2,4)
@test generator(ReedMuller(2,4)) == [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0;
1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0;
1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0;
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0;
1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0;
1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0;
1 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0;
1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0;
1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0;
1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0]
end
end
8 changes: 7 additions & 1 deletion test/test_ecc_throws.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
@testitem "ECC throws" begin
using QuantumClifford.ECC: ReedMuller, BCH

using QuantumClifford.ECC: ReedMuller, BCH, RecursiveReedMuller

@test_throws ArgumentError ReedMuller(-1, 3)
@test_throws ArgumentError ReedMuller(1, 0)
@test_throws ArgumentError ReedMuller(4, 2)

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

@test_throws ArgumentError RecursiveReedMuller(-1, 3)
@test_throws ArgumentError RecursiveReedMuller(1, 0)
@test_throws ArgumentError RecursiveReedMuller(4, 2)
end
Loading